From 484d01fb834d08047d4e9bcbfe815e5f2040abc5 Mon Sep 17 00:00:00 2001 From: Marvin W Date: Mon, 8 Jun 2020 22:52:19 +0200 Subject: [PATCH] Bug fixes --- .../nlp/client/UnifiedLocationClient.kt | 43 +++--- .../nlp/service/UnifiedLocationServiceRoot.kt | 2 +- .../microg/nlp/ui/BackendDetailsFragment.kt | 124 +++++++++++++----- .../kotlin/org/microg/nlp/ui/BackendInfo.kt | 3 + .../org/microg/nlp/ui/BackendListFragment.kt | 69 ++++++++-- ui/src/main/res/layout/backend_details.xml | 3 +- ui/src/main/res/layout/backend_list_entry.xml | 10 +- 7 files changed, 178 insertions(+), 76 deletions(-) diff --git a/client/src/main/kotlin/org/microg/nlp/client/UnifiedLocationClient.kt b/client/src/main/kotlin/org/microg/nlp/client/UnifiedLocationClient.kt index cea6a99..d6d728e 100644 --- a/client/src/main/kotlin/org/microg/nlp/client/UnifiedLocationClient.kt +++ b/client/src/main/kotlin/org/microg/nlp/client/UnifiedLocationClient.kt @@ -212,11 +212,7 @@ class UnifiedLocationClient private constructor(context: Context) { @Synchronized fun requestLocationUpdates(listener: LocationListener, interval: Long, count: Int) { - for (request in requests) { - if (request.listener === listener) { - requests.remove(request) - } - } + requests.removeAll(requests.filter { it.listener === listener }) requests.add(LocationRequest(listener, interval, count)) updateServiceInterval() updateBinding() @@ -224,13 +220,7 @@ class UnifiedLocationClient private constructor(context: Context) { @Synchronized fun removeLocationUpdates(listener: LocationListener) { - for (request in requests) { - if (request.listener === listener) { - requests.remove(request) - } - } - updateServiceInterval() - updateBinding() + removeRequests(requests.filter { it.listener === listener }) } 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)) configureContinuationTimeout(continuation, timeout) } - } catch (e: RemoteException) { + } catch (e: Exception) { Log.w(TAG, "Failed to request geocode", e) return emptyList() } finally { @@ -368,7 +358,7 @@ class UnifiedLocationClient private constructor(context: Context) { suspend fun setGeocoderBackends(backends: Array) { try { - refAndGetService().locationBackends = backends + refAndGetService().geocoderBackends = backends } catch (e: RemoteException) { Log.w(TAG, "Failed to handle request", e) } finally { @@ -399,10 +389,16 @@ class UnifiedLocationClient private constructor(context: Context) { } @Synchronized - private fun removeRequest(request: LocationRequest) { - requests.remove(request) - updateServiceInterval() - updateBinding() + private fun removeRequestPendingRemoval() { + removeRequests(requests.filter { it.needsRemoval }) + } + + private fun removeRequests(removalNeeded: List) { + if (removalNeeded.isNotEmpty()) { + requests.removeAll(removalNeeded) + updateServiceInterval() + updateBinding() + } } @Synchronized @@ -459,6 +455,7 @@ class UnifiedLocationClient private constructor(context: Context) { for (request in requests) { request.handleLocation(location) } + removeRequestPendingRemoval() } }, options) 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 var lastUpdate: Long = 0 + private var failed: Boolean = false + val needsRemoval: Boolean + get() = pendingCount <= 0 || failed + fun reset(interval: Long, count: Int) { this.interval = interval this.pendingCount = count @@ -508,6 +509,7 @@ class UnifiedLocationClient private constructor(context: Context) { @Synchronized fun handleLocation(location: Location) { + if (needsRemoval) return if (lastUpdate > System.currentTimeMillis()) { lastUpdate = System.currentTimeMillis() } @@ -520,13 +522,10 @@ class UnifiedLocationClient private constructor(context: Context) { listener.onLocation(location) } catch (e: Exception) { Log.w(TAG, "Listener threw uncaught exception, stopping location request", e) - removeRequest(this) + failed = true } } - if (pendingCount == 0) { - removeRequest(this) - } } } diff --git a/service/src/main/kotlin/org/microg/nlp/service/UnifiedLocationServiceRoot.kt b/service/src/main/kotlin/org/microg/nlp/service/UnifiedLocationServiceRoot.kt index f4e99be..cec2f0c 100644 --- a/service/src/main/kotlin/org/microg/nlp/service/UnifiedLocationServiceRoot.kt +++ b/service/src/main/kotlin/org/microg/nlp/service/UnifiedLocationServiceRoot.kt @@ -152,7 +152,7 @@ class UnifiedLocationServiceRoot(private val service: UnifiedLocationServiceEntr override fun setGeocoderBackends(backends: Array) { if (Binder.getCallingUid() != myUid()) throw SecurityException("Only allowed from same UID") - Preferences(service).locationBackends = backends + Preferences(service).geocoderBackends = backends reloadPreferences() } diff --git a/ui/src/main/kotlin/org/microg/nlp/ui/BackendDetailsFragment.kt b/ui/src/main/kotlin/org/microg/nlp/ui/BackendDetailsFragment.kt index 4b661a2..c916ff1 100644 --- a/ui/src/main/kotlin/org/microg/nlp/ui/BackendDetailsFragment.kt +++ b/ui/src/main/kotlin/org/microg/nlp/ui/BackendDetailsFragment.kt @@ -12,7 +12,10 @@ import android.content.Intent.ACTION_VIEW import android.content.pm.PackageManager.GET_META_DATA import android.content.res.ColorStateList import android.graphics.Color +import android.net.Uri import android.os.Bundle +import android.provider.Settings +import android.util.Log import android.util.TypedValue import android.view.LayoutInflater import android.view.View @@ -21,9 +24,10 @@ import androidx.annotation.AttrRes import androidx.annotation.ColorInt import androidx.core.content.ContextCompat import androidx.databinding.Observable +import androidx.databinding.Observable.OnPropertyChangedCallback import androidx.fragment.app.Fragment import androidx.lifecycle.lifecycleScope -import kotlinx.coroutines.launch +import kotlinx.coroutines.delay import org.microg.nlp.client.UnifiedLocationClient import org.microg.nlp.ui.BackendType.GEOCODER import org.microg.nlp.ui.BackendType.LOCATION @@ -73,40 +77,82 @@ class BackendDetailsFragment : Fragment(R.layout.backend_details) { return ColorStateList(arrayOf(emptyArray().toIntArray()), arrayOf(withAlpha).toIntArray()) } + private lateinit var binding: BackendDetailsBinding + 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.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 } + 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) { entry.enabled = !entry.enabled } @@ -130,6 +176,14 @@ class BackendDetailsFragment : Fragment(R.layout.backend_details) { 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? { val activity = activity ?: return null val intent = activity.intent ?: return null @@ -147,9 +201,11 @@ class BackendDetailsFragment : Fragment(R.layout.backend_details) { } companion object { - val ACTION = "org.microg.nlp.ui.BACKEND_DETAILS" - val EXTRA_TYPE = "org.microg.nlp.ui.BackendDetailsFragment.type" - val EXTRA_PACKAGE = "org.microg.nlp.ui.BackendDetailsFragment.package" - val EXTRA_NAME = "org.microg.nlp.ui.BackendDetailsFragment.name" + const val ACTION = "org.microg.nlp.ui.BACKEND_DETAILS" + const val EXTRA_TYPE = "org.microg.nlp.ui.BackendDetailsFragment.type" + const val EXTRA_PACKAGE = "org.microg.nlp.ui.BackendDetailsFragment.package" + const val EXTRA_NAME = "org.microg.nlp.ui.BackendDetailsFragment.name" + private const val TAG = "USettings" + private const val WAIT_FOR_RESULT = 5000L } } \ No newline at end of file diff --git a/ui/src/main/kotlin/org/microg/nlp/ui/BackendInfo.kt b/ui/src/main/kotlin/org/microg/nlp/ui/BackendInfo.kt index f3891a3..3334707 100644 --- a/ui/src/main/kotlin/org/microg/nlp/ui/BackendInfo.kt +++ b/ui/src/main/kotlin/org/microg/nlp/ui/BackendInfo.kt @@ -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 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 } diff --git a/ui/src/main/kotlin/org/microg/nlp/ui/BackendListFragment.kt b/ui/src/main/kotlin/org/microg/nlp/ui/BackendListFragment.kt index c6b63ea..98954c6 100644 --- a/ui/src/main/kotlin/org/microg/nlp/ui/BackendListFragment.kt +++ b/ui/src/main/kotlin/org/microg/nlp/ui/BackendListFragment.kt @@ -16,7 +16,9 @@ import android.view.View import android.view.ViewGroup import androidx.fragment.app.Fragment import androidx.lifecycle.lifecycleScope +import androidx.recyclerview.widget.DefaultItemAnimator import androidx.recyclerview.widget.RecyclerView +import kotlinx.coroutines.delay import org.microg.nlp.api.Constants.* import org.microg.nlp.client.UnifiedLocationClient 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? { val binding = BackendListBinding.inflate(inflater, container, false) binding.fragment = this - lifecycleScope.launchWhenStarted { updateAdapters() } 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) - //intent.`package` = requireContext().packageName + intent.`package` = requireContext().packageName intent.putExtra(EXTRA_TYPE, entry.type.name) intent.putExtra(EXTRA_PACKAGE, entry.serviceInfo.packageName) intent.putExtra(EXTRA_NAME, entry.serviceInfo.name) - context?.packageManager?.queryIntentActivities(intent, 0)?.forEach { - Log.d("USettings", it.activityInfo.name) - } startActivity(intent) } private suspend fun updateAdapters() { val context = requireContext() - locationAdapter.entries = createBackendInfoList(context, Intent(ACTION_LOCATION_BACKEND), UnifiedLocationClient[context].getLocationBackends(), BackendType.LOCATION) - geocoderAdapter.entries = createBackendInfoList(context, Intent(ACTION_GEOCODER_BACKEND), UnifiedLocationClient[context].getGeocoderBackends(), BackendType.GEOCODER) + locationAdapter.setEntries(createBackendInfoList(context, Intent(ACTION_LOCATION_BACKEND), UnifiedLocationClient[context].getLocationBackends(), BackendType.LOCATION)) + geocoderAdapter.setEntries(createBackendInfoList(context, Intent(ACTION_GEOCODER_BACKEND), UnifiedLocationClient[context].getGeocoderBackends(), BackendType.GEOCODER)) } - private fun createBackendInfoList(context: Context, intent: Intent, enabledBackends: Array, type: BackendType): Array { + private fun createBackendInfoList(context: Context, intent: Intent, enabledBackends: Array, type: BackendType): Array { 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() } } 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.entry = entry binding.executePendingBindings() @@ -69,11 +74,47 @@ class BackendSettingsLineViewHolder(val binding: BackendListEntryBinding) : Recy } class BackendSettingsLineAdapter(val fragment: BackendListFragment) : RecyclerView.Adapter() { - var entries: Array = emptyArray() - set(value) { - field = value - notifyDataSetChanged() + private val entries: MutableList = arrayListOf() + + fun addOrUpdateEntry(entry: BackendInfo?) { + 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) { + 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 { return BackendSettingsLineViewHolder(BackendListEntryBinding.inflate(LayoutInflater.from(parent.context), parent, false)) diff --git a/ui/src/main/res/layout/backend_details.xml b/ui/src/main/res/layout/backend_details.xml index 7d8bfc3..c10d1cc 100644 --- a/ui/src/main/res/layout/backend_details.xml +++ b/ui/src/main/res/layout/backend_details.xml @@ -48,6 +48,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerHorizontal="true" + android:onClick='@{() -> fragment.onAppClicked(entry)}' android:gravity="center_horizontal" android:orientation="vertical"> @@ -262,7 +263,7 @@ android:paddingLeft="?attr/listPreferredItemPaddingLeft" android:paddingEnd="?attr/listPreferredItemPaddingEnd" android:paddingRight="?attr/listPreferredItemPaddingRight" - android:visibility='@{lastLocationString == null ? View.GONE : View.VISIBLE}'> + android:visibility='@{lastLocationString == null || lastLocationString == "" ? View.GONE : View.VISIBLE}'> @@ -86,7 +86,8 @@ android:gravity="start|center_vertical" android:orientation="horizontal" android:paddingTop="16dp" - android:paddingBottom="16dp"> + android:paddingBottom="16dp" + android:visibility='@{entry == null ? View.GONE : View.VISIBLE}'> + android:orientation="vertical" + android:visibility='@{entry == null ? View.GONE : View.VISIBLE}'>