Bug fixes

This commit is contained in:
Marvin W 2020-06-08 22:52:19 +02:00
parent 60f7532abf
commit 484d01fb83
No known key found for this signature in database
GPG key ID: 072E9235DB996F2A
7 changed files with 178 additions and 76 deletions

View file

@ -212,11 +212,7 @@ class UnifiedLocationClient private constructor(context: Context) {
@Synchronized @Synchronized
fun requestLocationUpdates(listener: LocationListener, interval: Long, count: Int) { fun requestLocationUpdates(listener: LocationListener, interval: Long, count: Int) {
for (request in requests) { requests.removeAll(requests.filter { it.listener === listener })
if (request.listener === listener) {
requests.remove(request)
}
}
requests.add(LocationRequest(listener, interval, count)) requests.add(LocationRequest(listener, interval, count))
updateServiceInterval() updateServiceInterval()
updateBinding() updateBinding()
@ -224,13 +220,7 @@ class UnifiedLocationClient private constructor(context: Context) {
@Synchronized @Synchronized
fun removeLocationUpdates(listener: LocationListener) { fun removeLocationUpdates(listener: LocationListener) {
for (request in requests) { removeRequests(requests.filter { it.listener === listener })
if (request.listener === listener) {
requests.remove(request)
}
}
updateServiceInterval()
updateBinding()
} }
private suspend fun refAndGetService(): UnifiedLocationService = suspendCoroutine { continuation -> refAndGetServiceContinued(continuation) } private suspend fun refAndGetService(): UnifiedLocationService = suspendCoroutine { continuation -> refAndGetServiceContinued(continuation) }
@ -293,7 +283,7 @@ class UnifiedLocationClient private constructor(context: Context) {
service.getFromLocationWithOptions(latitude, longitude, maxResults, locale, options, AddressContinuation(continuation)) service.getFromLocationWithOptions(latitude, longitude, maxResults, locale, options, AddressContinuation(continuation))
configureContinuationTimeout(continuation, timeout) configureContinuationTimeout(continuation, timeout)
} }
} catch (e: RemoteException) { } catch (e: Exception) {
Log.w(TAG, "Failed to request geocode", e) Log.w(TAG, "Failed to request geocode", e)
return emptyList() return emptyList()
} finally { } finally {
@ -368,7 +358,7 @@ class UnifiedLocationClient private constructor(context: Context) {
suspend fun setGeocoderBackends(backends: Array<String>) { suspend fun setGeocoderBackends(backends: Array<String>) {
try { try {
refAndGetService().locationBackends = backends refAndGetService().geocoderBackends = backends
} catch (e: RemoteException) { } catch (e: RemoteException) {
Log.w(TAG, "Failed to handle request", e) Log.w(TAG, "Failed to handle request", e)
} finally { } finally {
@ -399,10 +389,16 @@ class UnifiedLocationClient private constructor(context: Context) {
} }
@Synchronized @Synchronized
private fun removeRequest(request: LocationRequest) { private fun removeRequestPendingRemoval() {
requests.remove(request) removeRequests(requests.filter { it.needsRemoval })
updateServiceInterval() }
updateBinding()
private fun removeRequests(removalNeeded: List<LocationRequest>) {
if (removalNeeded.isNotEmpty()) {
requests.removeAll(removalNeeded)
updateServiceInterval()
updateBinding()
}
} }
@Synchronized @Synchronized
@ -459,6 +455,7 @@ class UnifiedLocationClient private constructor(context: Context) {
for (request in requests) { for (request in requests) {
request.handleLocation(location) request.handleLocation(location)
} }
removeRequestPendingRemoval()
} }
}, options) }, options)
updateServiceInterval() updateServiceInterval()
@ -501,6 +498,10 @@ class UnifiedLocationClient private constructor(context: Context) {
private inner class LocationRequest(val listener: LocationListener, var interval: Long, var pendingCount: Int) { private inner class LocationRequest(val listener: LocationListener, var interval: Long, var pendingCount: Int) {
private var lastUpdate: Long = 0 private var lastUpdate: Long = 0
private var failed: Boolean = false
val needsRemoval: Boolean
get() = pendingCount <= 0 || failed
fun reset(interval: Long, count: Int) { fun reset(interval: Long, count: Int) {
this.interval = interval this.interval = interval
this.pendingCount = count this.pendingCount = count
@ -508,6 +509,7 @@ class UnifiedLocationClient private constructor(context: Context) {
@Synchronized @Synchronized
fun handleLocation(location: Location) { fun handleLocation(location: Location) {
if (needsRemoval) return
if (lastUpdate > System.currentTimeMillis()) { if (lastUpdate > System.currentTimeMillis()) {
lastUpdate = System.currentTimeMillis() lastUpdate = System.currentTimeMillis()
} }
@ -520,13 +522,10 @@ class UnifiedLocationClient private constructor(context: Context) {
listener.onLocation(location) listener.onLocation(location)
} catch (e: Exception) { } catch (e: Exception) {
Log.w(TAG, "Listener threw uncaught exception, stopping location request", e) Log.w(TAG, "Listener threw uncaught exception, stopping location request", e)
removeRequest(this) failed = true
} }
} }
if (pendingCount == 0) {
removeRequest(this)
}
} }
} }

View file

@ -152,7 +152,7 @@ class UnifiedLocationServiceRoot(private val service: UnifiedLocationServiceEntr
override fun setGeocoderBackends(backends: Array<String>) { override fun setGeocoderBackends(backends: Array<String>) {
if (Binder.getCallingUid() != myUid()) throw SecurityException("Only allowed from same UID") if (Binder.getCallingUid() != myUid()) throw SecurityException("Only allowed from same UID")
Preferences(service).locationBackends = backends Preferences(service).geocoderBackends = backends
reloadPreferences() reloadPreferences()
} }

View file

@ -12,7 +12,10 @@ import android.content.Intent.ACTION_VIEW
import android.content.pm.PackageManager.GET_META_DATA import android.content.pm.PackageManager.GET_META_DATA
import android.content.res.ColorStateList import android.content.res.ColorStateList
import android.graphics.Color import android.graphics.Color
import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.provider.Settings
import android.util.Log
import android.util.TypedValue import android.util.TypedValue
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
@ -21,9 +24,10 @@ import androidx.annotation.AttrRes
import androidx.annotation.ColorInt import androidx.annotation.ColorInt
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.databinding.Observable import androidx.databinding.Observable
import androidx.databinding.Observable.OnPropertyChangedCallback
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.launch import kotlinx.coroutines.delay
import org.microg.nlp.client.UnifiedLocationClient import org.microg.nlp.client.UnifiedLocationClient
import org.microg.nlp.ui.BackendType.GEOCODER import org.microg.nlp.ui.BackendType.GEOCODER
import org.microg.nlp.ui.BackendType.LOCATION import org.microg.nlp.ui.BackendType.LOCATION
@ -73,40 +77,82 @@ class BackendDetailsFragment : Fragment(R.layout.backend_details) {
return ColorStateList(arrayOf(emptyArray<Int>().toIntArray()), arrayOf(withAlpha).toIntArray()) return ColorStateList(arrayOf(emptyArray<Int>().toIntArray()), arrayOf(withAlpha).toIntArray())
} }
private lateinit var binding: BackendDetailsBinding
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val binding = BackendDetailsBinding.inflate(inflater, container, false) binding = BackendDetailsBinding.inflate(inflater, container, false)
binding.fragment = this binding.fragment = this
binding.switchWidget.trackTintList = switchBarTrackTintColor
lifecycleScope.launchWhenStarted {
val entry = createBackendInfo()
binding.entry = entry
binding.executePendingBindings()
if (entry?.type == LOCATION) {
val client = UnifiedLocationClient[entry.context]
val location = client.getLastLocationForBackend(entry.serviceInfo.packageName, entry.serviceInfo.name, entry.firstSignatureDigest)
?: return@launchWhenStarted
var locationString = "${location.latitude.toStringWithDigits(6)}, ${location.longitude.toStringWithDigits(6)}"
val address = client.getFromLocation(location.latitude, location.longitude, 1, Locale.getDefault().toString()).singleOrNull()
if (address != null) {
val addressLine = StringBuilder()
var i = 0
addressLine.append(address.getAddressLine(i))
while (addressLine.length < 10 && address.maxAddressLineIndex > i) {
i++
addressLine.append(", ")
addressLine.append(address.getAddressLine(i))
}
locationString = addressLine.toString()
}
binding.lastLocationString = locationString
binding.executePendingBindings()
}
}
return binding.root return binding.root
} }
override fun onResume() {
super.onResume()
binding.switchWidget.trackTintList = switchBarTrackTintColor
lifecycleScope.launchWhenStarted { initContent(createBackendInfo()) }
}
private suspend fun initContent(entry: BackendInfo?) {
binding.entry = entry
binding.executePendingBindings()
updateContent(entry)
entry?.addOnPropertyChangedCallback(object : OnPropertyChangedCallback() {
override fun onPropertyChanged(sender: Observable?, propertyId: Int) {
if (propertyId == BR.enabled) {
lifecycleScope.launchWhenStarted { initContent(entry) }
}
}
})
}
private var updateInProgress = false
private suspend fun updateContent(entry: BackendInfo?) {
if (entry?.type == LOCATION && entry.enabled) {
if (updateInProgress) return
updateInProgress = true
val client = UnifiedLocationClient[entry.context]
val locationTemp = client.getLastLocationForBackend(entry.serviceInfo.packageName, entry.serviceInfo.name, entry.firstSignatureDigest)
val location = when (locationTemp) {
null -> {
delay(500L) // Wait short time to ensure backend was activated
Log.d(TAG, "Location was not available, requesting once")
client.forceNextUpdate = true
client.getSingleLocation()
val secondAttempt = client.getLastLocationForBackend(entry.serviceInfo.packageName, entry.serviceInfo.name, entry.firstSignatureDigest)
if (secondAttempt == null) {
Log.d(TAG, "Location still not available, waiting or giving up")
delay(WAIT_FOR_RESULT)
client.getLastLocationForBackend(entry.serviceInfo.packageName, entry.serviceInfo.name, entry.firstSignatureDigest)
} else {
secondAttempt
}
}
else -> locationTemp
} ?: return
var locationString = "${location.latitude.toStringWithDigits(6)}, ${location.longitude.toStringWithDigits(6)}"
val address = client.getFromLocation(location.latitude, location.longitude, 1, Locale.getDefault().toString()).singleOrNull()
if (address != null) {
val addressLine = StringBuilder()
var i = 0
addressLine.append(address.getAddressLine(i))
while (addressLine.length < 10 && address.maxAddressLineIndex > i) {
i++
addressLine.append(", ")
addressLine.append(address.getAddressLine(i))
}
locationString = addressLine.toString()
}
updateInProgress = false
binding.lastLocationString = locationString
binding.executePendingBindings()
} else {
Log.d(TAG, "Location is not available for this backend (type: ${entry?.type}, enabled ${entry?.enabled}")
binding.lastLocationString = ""
binding.executePendingBindings()
}
}
fun onBackendEnabledChanged(entry: BackendInfo) { fun onBackendEnabledChanged(entry: BackendInfo) {
entry.enabled = !entry.enabled entry.enabled = !entry.enabled
} }
@ -130,6 +176,14 @@ class BackendDetailsFragment : Fragment(R.layout.backend_details) {
entry.settingsActivity?.let { activityName -> startExternalActivity(entry.serviceInfo.packageName, activityName) } entry.settingsActivity?.let { activityName -> startExternalActivity(entry.serviceInfo.packageName, activityName) }
} }
fun onAppClicked(entry: BackendInfo) {
val intent = Intent()
intent.action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS
val uri = Uri.fromParts("package", entry.serviceInfo.packageName, null)
intent.data = uri
requireContext().startActivity(intent)
}
private suspend fun createBackendInfo(): BackendInfo? { private suspend fun createBackendInfo(): BackendInfo? {
val activity = activity ?: return null val activity = activity ?: return null
val intent = activity.intent ?: return null val intent = activity.intent ?: return null
@ -147,9 +201,11 @@ class BackendDetailsFragment : Fragment(R.layout.backend_details) {
} }
companion object { companion object {
val ACTION = "org.microg.nlp.ui.BACKEND_DETAILS" const val ACTION = "org.microg.nlp.ui.BACKEND_DETAILS"
val EXTRA_TYPE = "org.microg.nlp.ui.BackendDetailsFragment.type" const val EXTRA_TYPE = "org.microg.nlp.ui.BackendDetailsFragment.type"
val EXTRA_PACKAGE = "org.microg.nlp.ui.BackendDetailsFragment.package" const val EXTRA_PACKAGE = "org.microg.nlp.ui.BackendDetailsFragment.package"
val EXTRA_NAME = "org.microg.nlp.ui.BackendDetailsFragment.name" const val EXTRA_NAME = "org.microg.nlp.ui.BackendDetailsFragment.name"
private const val TAG = "USettings"
private const val WAIT_FOR_RESULT = 5000L
} }
} }

View file

@ -62,6 +62,9 @@ class BackendInfo(val context: Context, val serviceInfo: ServiceInfo, val type:
val settingsActivity: String? by lazy { serviceInfo.metaData?.getString(Constants.METADATA_BACKEND_SETTINGS_ACTIVITY) } val settingsActivity: String? by lazy { serviceInfo.metaData?.getString(Constants.METADATA_BACKEND_SETTINGS_ACTIVITY) }
val aboutActivity: String? by lazy { serviceInfo.metaData?.getString(Constants.METADATA_BACKEND_ABOUT_ACTIVITY) } val aboutActivity: String? by lazy { serviceInfo.metaData?.getString(Constants.METADATA_BACKEND_ABOUT_ACTIVITY) }
override fun equals(other: Any?): Boolean {
return other is BackendInfo && other.name == name && other.enabled == enabled && other.appName == appName && other.unsignedComponent == unsignedComponent && other.backendSummary == backendSummary
}
} }
enum class BackendType { LOCATION, GEOCODER } enum class BackendType { LOCATION, GEOCODER }

View file

@ -16,7 +16,9 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.DefaultItemAnimator
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import kotlinx.coroutines.delay
import org.microg.nlp.api.Constants.* import org.microg.nlp.api.Constants.*
import org.microg.nlp.client.UnifiedLocationClient import org.microg.nlp.client.UnifiedLocationClient
import org.microg.nlp.ui.BackendDetailsFragment.Companion.EXTRA_NAME import org.microg.nlp.ui.BackendDetailsFragment.Companion.EXTRA_NAME
@ -32,36 +34,39 @@ class BackendListFragment : Fragment(R.layout.backend_list) {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val binding = BackendListBinding.inflate(inflater, container, false) val binding = BackendListBinding.inflate(inflater, container, false)
binding.fragment = this binding.fragment = this
lifecycleScope.launchWhenStarted { updateAdapters() }
return binding.root return binding.root
} }
fun onBackendSelected(entry: BackendInfo) { override fun onResume() {
super.onResume()
lifecycleScope.launchWhenStarted { updateAdapters() }
}
fun onBackendSelected(entry: BackendInfo?) {
if (entry == null) return
val intent = Intent(BackendDetailsFragment.ACTION) val intent = Intent(BackendDetailsFragment.ACTION)
//intent.`package` = requireContext().packageName intent.`package` = requireContext().packageName
intent.putExtra(EXTRA_TYPE, entry.type.name) intent.putExtra(EXTRA_TYPE, entry.type.name)
intent.putExtra(EXTRA_PACKAGE, entry.serviceInfo.packageName) intent.putExtra(EXTRA_PACKAGE, entry.serviceInfo.packageName)
intent.putExtra(EXTRA_NAME, entry.serviceInfo.name) intent.putExtra(EXTRA_NAME, entry.serviceInfo.name)
context?.packageManager?.queryIntentActivities(intent, 0)?.forEach {
Log.d("USettings", it.activityInfo.name)
}
startActivity(intent) startActivity(intent)
} }
private suspend fun updateAdapters() { private suspend fun updateAdapters() {
val context = requireContext() val context = requireContext()
locationAdapter.entries = createBackendInfoList(context, Intent(ACTION_LOCATION_BACKEND), UnifiedLocationClient[context].getLocationBackends(), BackendType.LOCATION) locationAdapter.setEntries(createBackendInfoList(context, Intent(ACTION_LOCATION_BACKEND), UnifiedLocationClient[context].getLocationBackends(), BackendType.LOCATION))
geocoderAdapter.entries = createBackendInfoList(context, Intent(ACTION_GEOCODER_BACKEND), UnifiedLocationClient[context].getGeocoderBackends(), BackendType.GEOCODER) geocoderAdapter.setEntries(createBackendInfoList(context, Intent(ACTION_GEOCODER_BACKEND), UnifiedLocationClient[context].getGeocoderBackends(), BackendType.GEOCODER))
} }
private fun createBackendInfoList(context: Context, intent: Intent, enabledBackends: Array<String>, type: BackendType): Array<BackendInfo> { private fun createBackendInfoList(context: Context, intent: Intent, enabledBackends: Array<String>, type: BackendType): Array<BackendInfo?> {
val backends = context.packageManager.queryIntentServices(intent, GET_META_DATA).map { BackendInfo(context, it.serviceInfo, type, lifecycleScope, enabledBackends) } val backends = context.packageManager.queryIntentServices(intent, GET_META_DATA).map { BackendInfo(context, it.serviceInfo, type, lifecycleScope, enabledBackends) }
if (backends.isEmpty()) return arrayOf(null)
return backends.toTypedArray() return backends.toTypedArray()
} }
} }
class BackendSettingsLineViewHolder(val binding: BackendListEntryBinding) : RecyclerView.ViewHolder(binding.root) { class BackendSettingsLineViewHolder(val binding: BackendListEntryBinding) : RecyclerView.ViewHolder(binding.root) {
fun bind(fragment: BackendListFragment, entry: BackendInfo) { fun bind(fragment: BackendListFragment, entry: BackendInfo?) {
binding.fragment = fragment binding.fragment = fragment
binding.entry = entry binding.entry = entry
binding.executePendingBindings() binding.executePendingBindings()
@ -69,11 +74,47 @@ class BackendSettingsLineViewHolder(val binding: BackendListEntryBinding) : Recy
} }
class BackendSettingsLineAdapter(val fragment: BackendListFragment) : RecyclerView.Adapter<BackendSettingsLineViewHolder>() { class BackendSettingsLineAdapter(val fragment: BackendListFragment) : RecyclerView.Adapter<BackendSettingsLineViewHolder>() {
var entries: Array<BackendInfo> = emptyArray() private val entries: MutableList<BackendInfo?> = arrayListOf()
set(value) {
field = value fun addOrUpdateEntry(entry: BackendInfo?) {
notifyDataSetChanged() if (entry == null) {
if (entries.contains(null)) return
entries.add(entry)
notifyItemInserted(entries.size - 1)
} else {
val oldIndex = entries.indexOfFirst { it?.unsignedComponent == entry.unsignedComponent }
if (oldIndex != -1) {
if (entries[oldIndex] == entry) return
entries.removeAt(oldIndex)
}
val targetIndex = when (val i = entries.indexOfFirst { it == null || it.name.toString() > entry.name.toString() }) {
-1 -> entries.size
else -> i
}
entries.add(targetIndex, entry)
when (oldIndex) {
targetIndex -> notifyItemChanged(targetIndex)
-1 -> notifyItemInserted(targetIndex)
else -> notifyItemMoved(oldIndex, targetIndex)
}
} }
}
fun removeEntry(entry: BackendInfo?) {
val index = entries.indexOfFirst { it == entry || it?.unsignedComponent == entry?.unsignedComponent }
entries.removeAt(index)
notifyItemRemoved(index)
}
fun setEntries(entries: Array<BackendInfo?>) {
val oldEntries = this.entries.toTypedArray()
for (oldEntry in oldEntries) {
if (!entries.any { it == oldEntry || it?.unsignedComponent == oldEntry?.unsignedComponent }) {
removeEntry(oldEntry)
}
}
entries.forEach { addOrUpdateEntry(it) }
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BackendSettingsLineViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BackendSettingsLineViewHolder {
return BackendSettingsLineViewHolder(BackendListEntryBinding.inflate(LayoutInflater.from(parent.context), parent, false)) return BackendSettingsLineViewHolder(BackendListEntryBinding.inflate(LayoutInflater.from(parent.context), parent, false))

View file

@ -48,6 +48,7 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_centerHorizontal="true" android:layout_centerHorizontal="true"
android:onClick='@{() -> fragment.onAppClicked(entry)}'
android:gravity="center_horizontal" android:gravity="center_horizontal"
android:orientation="vertical"> android:orientation="vertical">
@ -262,7 +263,7 @@
android:paddingLeft="?attr/listPreferredItemPaddingLeft" android:paddingLeft="?attr/listPreferredItemPaddingLeft"
android:paddingEnd="?attr/listPreferredItemPaddingEnd" android:paddingEnd="?attr/listPreferredItemPaddingEnd"
android:paddingRight="?attr/listPreferredItemPaddingRight" android:paddingRight="?attr/listPreferredItemPaddingRight"
android:visibility='@{lastLocationString == null ? View.GONE : View.VISIBLE}'> android:visibility='@{lastLocationString == null || lastLocationString == "" ? View.GONE : View.VISIBLE}'>
<RelativeLayout <RelativeLayout
android:layout_width="wrap_content" android:layout_width="wrap_content"

View file

@ -32,7 +32,7 @@
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_weight="1" android:layout_weight="1"
android:background="?android:attr/selectableItemBackground" android:background="?android:attr/selectableItemBackground"
android:clickable="true" android:clickable='@{entry != null}'
android:clipToPadding="false" android:clipToPadding="false"
android:focusable="true" android:focusable="true"
android:gravity="start|center_vertical" android:gravity="start|center_vertical"
@ -73,7 +73,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:ellipsize="marquee" android:ellipsize="marquee"
android:singleLine="true" android:singleLine="true"
android:text='@{entry.name}' android:text='@{entry.name ?? "No provider installed"}'
android:textAppearance="?android:attr/textAppearanceListItem" android:textAppearance="?android:attr/textAppearanceListItem"
tools:text="Mozilla Location Service" /> tools:text="Mozilla Location Service" />
</RelativeLayout> </RelativeLayout>
@ -86,7 +86,8 @@
android:gravity="start|center_vertical" android:gravity="start|center_vertical"
android:orientation="horizontal" android:orientation="horizontal"
android:paddingTop="16dp" android:paddingTop="16dp"
android:paddingBottom="16dp"> android:paddingBottom="16dp"
android:visibility='@{entry == null ? View.GONE : View.VISIBLE}'>
<View <View
android:layout_width="1dp" android:layout_width="1dp"
@ -99,7 +100,8 @@
android:layout_height="match_parent" android:layout_height="match_parent"
android:gravity="center" android:gravity="center"
android:minWidth="64dp" android:minWidth="64dp"
android:orientation="vertical"> android:orientation="vertical"
android:visibility='@{entry == null ? View.GONE : View.VISIBLE}'>
<androidx.appcompat.widget.SwitchCompat <androidx.appcompat.widget.SwitchCompat
android:layout_width="match_parent" android:layout_width="match_parent"