Rework settings bits
This commit is contained in:
parent
9d2d56ab03
commit
ecf1784629
|
@ -14,6 +14,6 @@
|
|||
<activity
|
||||
android:name=".MPermissionHelperActivity"
|
||||
android:exported="true"
|
||||
android:theme="@android:style/Theme.Translucent.NoTitleBar"/>
|
||||
android:theme="@style/HiddenActivity"/>
|
||||
</application>
|
||||
</manifest>
|
||||
|
|
|
@ -76,7 +76,7 @@ public abstract class HelperLocationBackendService extends LocationBackendServic
|
|||
perms.addAll(Arrays.asList(helper.getRequiredPermissions()));
|
||||
}
|
||||
// Request background location permission if needed as we are likely to run in background
|
||||
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.Q && (perms.contains(ACCESS_COARSE_LOCATION) || perms.contains(ACCESS_FINE_LOCATION))) {
|
||||
if (Build.VERSION.SDK_INT >= 29 && (perms.contains(ACCESS_COARSE_LOCATION) || perms.contains(ACCESS_FINE_LOCATION))) {
|
||||
perms.add(ACCESS_BACKGROUND_LOCATION);
|
||||
}
|
||||
for (Iterator<String> iterator = perms.iterator(); iterator.hasNext(); ) {
|
||||
|
|
15
api/src/main/res/values/themes.xml
Normal file
15
api/src/main/res/values/themes.xml
Normal file
|
@ -0,0 +1,15 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ SPDX-FileCopyrightText: 2020, microG Project Team
|
||||
~ SPDX-License-Identifier: Apache-2.0
|
||||
-->
|
||||
|
||||
<resources>
|
||||
|
||||
<style name="HiddenActivity" parent="android:Theme.Translucent.NoTitleBar">
|
||||
<item name="android:windowAnimationStyle">@null</item>
|
||||
<item name="android:windowDisablePreview">true</item>
|
||||
<item name="android:windowFrame">@null</item>
|
||||
<item name="android:windowIsFloating">true</item>
|
||||
</style>
|
||||
</resources>
|
|
@ -66,15 +66,11 @@ abstract class AbstractBackendHelper(private val TAG: String, private val contex
|
|||
fun bind() {
|
||||
if (!bound) {
|
||||
Log.d(TAG, "Binding to: $serviceIntent sig: $signatureDigest")
|
||||
if (signatureDigest == null) {
|
||||
Log.w(TAG, "No signature digest provided. Aborting.")
|
||||
return
|
||||
}
|
||||
if (serviceIntent.getPackage() == null) {
|
||||
Log.w(TAG, "Intent is not properly resolved, can't verify signature. Aborting.")
|
||||
return
|
||||
}
|
||||
if (signatureDigest != firstSignatureDigest(context, serviceIntent.getPackage())) {
|
||||
if (signatureDigest != null && signatureDigest != firstSignatureDigest(context, serviceIntent.getPackage())) {
|
||||
Log.w(TAG, "Target signature does not match selected package (" + signatureDigest + " = " + firstSignatureDigest(context, serviceIntent.getPackage()) + "). Aborting.")
|
||||
return
|
||||
}
|
||||
|
|
|
@ -7,32 +7,57 @@ package org.microg.nlp.service
|
|||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.os.Build
|
||||
|
||||
|
||||
class Preferences(private val context: Context) {
|
||||
|
||||
private val sharedPreferences: SharedPreferences
|
||||
get() = context.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE)
|
||||
|
||||
var locationBackends: Array<String>
|
||||
get() = splitBackendString(sharedPreferences.getString(PREF_LOCATION_BACKENDS, null))
|
||||
private fun SharedPreferences.getStringSetCompat(key: String, defValues: Set<String>? = null): Set<String>? {
|
||||
if (Build.VERSION.SDK_INT >= 11) {
|
||||
try {
|
||||
val res = getStringSet(key, null)
|
||||
if (res != null) return res.filter { it.isNotEmpty() }.toSet()
|
||||
} catch (ignored: Exception) {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
try {
|
||||
val str = getString(key, null)
|
||||
if (str != null) return str.split("\\|".toRegex()).filter { it.isNotEmpty() }.toSet()
|
||||
} catch (ignored: Exception) {
|
||||
// Ignore
|
||||
}
|
||||
return defValues
|
||||
}
|
||||
|
||||
private fun SharedPreferences.Editor.putStringSetCompat(key: String, values: Set<String>): SharedPreferences.Editor {
|
||||
return if (Build.VERSION.SDK_INT >= 11) {
|
||||
putStringSet(key, values.filter { it.isNotEmpty() }.toSet())
|
||||
} else {
|
||||
putString(key, values.filter { it.isNotEmpty() }.joinToString("|"))
|
||||
}
|
||||
}
|
||||
|
||||
var locationBackends: Set<String>
|
||||
get() =
|
||||
sharedPreferences.getStringSetCompat(PREF_LOCATION_BACKENDS) ?: emptySet()
|
||||
set(backends) {
|
||||
sharedPreferences.edit().putString(PREF_LOCATION_BACKENDS, backends.joinToString("|")).apply()
|
||||
sharedPreferences.edit().putStringSetCompat(PREF_LOCATION_BACKENDS, backends).apply()
|
||||
}
|
||||
|
||||
var geocoderBackends: Array<String>
|
||||
get() = splitBackendString(sharedPreferences.getString(PREF_GEOCODER_BACKENDS, null))
|
||||
var geocoderBackends: Set<String>
|
||||
get() =
|
||||
sharedPreferences.getStringSetCompat(PREF_GEOCODER_BACKENDS) ?: emptySet()
|
||||
set(backends) {
|
||||
sharedPreferences.edit().putString(PREF_GEOCODER_BACKENDS, backends.joinToString("|")).apply()
|
||||
sharedPreferences.edit().putStringSetCompat(PREF_GEOCODER_BACKENDS, backends).apply()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val PREFERENCES_NAME = "unified_nlp"
|
||||
private val PREF_LOCATION_BACKENDS = "location_backends"
|
||||
private val PREF_GEOCODER_BACKENDS = "geocoder_backends"
|
||||
|
||||
private fun splitBackendString(backendString: String?): Array<String> {
|
||||
return backendString?.split("\\|".toRegex())?.dropLastWhile(String::isEmpty)?.toTypedArray()
|
||||
?: emptyArray()
|
||||
}
|
||||
private const val PREFERENCES_NAME = "unified_nlp"
|
||||
private const val PREF_LOCATION_BACKENDS = "location_backends"
|
||||
private const val PREF_GEOCODER_BACKENDS = "geocoder_backends"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -167,22 +167,22 @@ class UnifiedLocationServiceRoot(private val service: UnifiedLocationServiceEntr
|
|||
}
|
||||
|
||||
override fun getLocationBackends(): Array<String> {
|
||||
return Preferences(service).locationBackends
|
||||
return Preferences(service).locationBackends.toTypedArray()
|
||||
}
|
||||
|
||||
override fun setLocationBackends(backends: Array<String>) {
|
||||
checkAdminPermission();
|
||||
Preferences(service).locationBackends = backends
|
||||
Preferences(service).locationBackends = backends.toSet()
|
||||
reloadPreferences()
|
||||
}
|
||||
|
||||
override fun getGeocoderBackends(): Array<String> {
|
||||
return Preferences(service).geocoderBackends
|
||||
return Preferences(service).geocoderBackends.toTypedArray()
|
||||
}
|
||||
|
||||
override fun setGeocoderBackends(backends: Array<String>) {
|
||||
checkAdminPermission();
|
||||
Preferences(service).geocoderBackends = backends
|
||||
Preferences(service).geocoderBackends = backends.toSet()
|
||||
reloadPreferences()
|
||||
}
|
||||
|
||||
|
|
|
@ -58,6 +58,10 @@ dependencies {
|
|||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutineVersion"
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutineVersion"
|
||||
|
||||
// Activity result
|
||||
implementation "androidx.activity:activity:1.1.0"
|
||||
implementation "androidx.activity:activity-ktx:1.1.0"
|
||||
|
||||
// Navigation
|
||||
implementation "androidx.navigation:navigation-fragment:$navigationVersion"
|
||||
implementation "androidx.navigation:navigation-ui:$navigationVersion"
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2020, microG Project Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package org.microg.nlp.ui
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.util.SparseArray
|
||||
import androidx.core.util.set
|
||||
import androidx.fragment.app.Fragment
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
|
||||
private val requestCodeCounter = AtomicInteger(1)
|
||||
private val continuationMap = SparseArray<(Int, Intent?) -> Unit>()
|
||||
|
||||
fun Fragment.startActivityForResult(intent: Intent, options: Bundle? = null, callback: (Int, Intent?) -> Unit) {
|
||||
val requestCode = requestCodeCounter.getAndIncrement()
|
||||
continuationMap[requestCode] = callback
|
||||
startActivityForResult(intent, requestCode, options)
|
||||
}
|
||||
|
||||
suspend fun Fragment.startActivityForResultCode(intent: Intent, options: Bundle? = null): Int = suspendCoroutine { continuation ->
|
||||
startActivityForResult(intent, options) { responseCode, _ ->
|
||||
continuation.resume(responseCode)
|
||||
}
|
||||
}
|
||||
|
||||
fun handleActivityResult(requestCode: Int, responseCode: Int, data: Intent?) {
|
||||
Log.d("ActivityResultProc", "handleActivityResult: $requestCode, $responseCode")
|
||||
try {
|
||||
continuationMap[requestCode]?.let { it(responseCode, data) }
|
||||
} catch (e: Exception) {
|
||||
Log.w("ActivityResultProc", "Error while handling activity result", e)
|
||||
}
|
||||
continuationMap.remove(requestCode)
|
||||
}
|
160
ui/src/main/kotlin/org/microg/nlp/ui/BackendConfiguration.kt
Normal file
160
ui/src/main/kotlin/org/microg/nlp/ui/BackendConfiguration.kt
Normal file
|
@ -0,0 +1,160 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2020, microG Project Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package org.microg.nlp.ui
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity.RESULT_OK
|
||||
import android.app.Service.BIND_AUTO_CREATE
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.ServiceConnection
|
||||
import android.content.pm.PackageInfo
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.IBinder
|
||||
import android.util.Log
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.fragment.app.Fragment
|
||||
import org.microg.nlp.api.Constants
|
||||
import org.microg.nlp.api.Constants.ACTION_GEOCODER_BACKEND
|
||||
import org.microg.nlp.api.Constants.ACTION_LOCATION_BACKEND
|
||||
import org.microg.nlp.api.GeocoderBackend
|
||||
import org.microg.nlp.api.LocationBackend
|
||||
import org.microg.nlp.client.UnifiedLocationClient
|
||||
import org.microg.nlp.ui.viewmodel.BackendInfo
|
||||
import org.microg.nlp.ui.viewmodel.BackendType
|
||||
import java.security.MessageDigest
|
||||
import java.security.NoSuchAlgorithmException
|
||||
|
||||
|
||||
private fun Array<String>.without(entry: BackendInfo): Array<String> = filterNot { it == entry.unsignedComponent || it.startsWith("${entry.unsignedComponent}/") }.toTypedArray()
|
||||
|
||||
suspend fun BackendInfo.updateEnabled(fragment: Fragment, newValue: Boolean) {
|
||||
Log.d("USettings", "updateEnabled $signedComponent = $newValue")
|
||||
val success = try {
|
||||
if (newValue) enable(fragment) else disable(fragment)
|
||||
} catch (e: Exception) {
|
||||
false
|
||||
}
|
||||
enabled.set(if (success) newValue else false)
|
||||
}
|
||||
|
||||
fun BackendInfo.fillDetails(context: Context) {
|
||||
appIcon.set(serviceInfo.loadIcon(context.packageManager))
|
||||
name.set(serviceInfo.loadLabel(context.packageManager).toString())
|
||||
appName.set(serviceInfo.applicationInfo.loadLabel(context.packageManager).toString())
|
||||
summary.set(serviceInfo.metaData?.getString(Constants.METADATA_BACKEND_SUMMARY))
|
||||
aboutIntent.set(serviceInfo.metaData?.getString(Constants.METADATA_BACKEND_ABOUT_ACTIVITY)?.let { createExternalIntent(serviceInfo.packageName, it) })
|
||||
settingsIntent.set(serviceInfo.metaData?.getString(Constants.METADATA_BACKEND_SETTINGS_ACTIVITY)?.let { createExternalIntent(serviceInfo.packageName, it) })
|
||||
initIntent.set(serviceInfo.metaData?.getString(Constants.METADATA_BACKEND_INIT_ACTIVITY)?.let { createExternalIntent(serviceInfo.packageName, it) })
|
||||
}
|
||||
|
||||
fun BackendInfo.loadIntents(activity: AppCompatActivity) {
|
||||
if (aboutIntent.get() == null || settingsIntent.get() == null || initIntent.get() == null) {
|
||||
val intent = when (type) {
|
||||
BackendType.LOCATION -> Intent(ACTION_LOCATION_BACKEND)
|
||||
BackendType.GEOCODER -> Intent(ACTION_GEOCODER_BACKEND)
|
||||
}
|
||||
intent.setPackage(serviceInfo.packageName);
|
||||
intent.setClassName(serviceInfo.packageName, serviceInfo.name);
|
||||
activity.bindService(intent, object : ServiceConnection {
|
||||
|
||||
override fun onServiceConnected(name: ComponentName, service: IBinder) {
|
||||
if (aboutIntent.get() == null) {
|
||||
aboutIntent.set(when (type) {
|
||||
BackendType.LOCATION -> LocationBackend.Stub.asInterface(service).aboutIntent
|
||||
BackendType.GEOCODER -> GeocoderBackend.Stub.asInterface(service).aboutIntent
|
||||
})
|
||||
}
|
||||
if (settingsIntent.get() == null) {
|
||||
settingsIntent.set(when (type) {
|
||||
BackendType.LOCATION -> LocationBackend.Stub.asInterface(service).settingsIntent
|
||||
BackendType.GEOCODER -> GeocoderBackend.Stub.asInterface(service).settingsIntent
|
||||
})
|
||||
}
|
||||
if (initIntent.get() == null) {
|
||||
initIntent.set(when (type) {
|
||||
BackendType.LOCATION -> LocationBackend.Stub.asInterface(service).initIntent
|
||||
BackendType.GEOCODER -> GeocoderBackend.Stub.asInterface(service).initIntent
|
||||
})
|
||||
}
|
||||
activity.unbindService(this)
|
||||
loaded.set(true)
|
||||
}
|
||||
|
||||
override fun onServiceDisconnected(name: ComponentName) {}
|
||||
}, BIND_AUTO_CREATE)
|
||||
}
|
||||
}
|
||||
|
||||
private fun createExternalIntent(packageName: String, activityName: String): Intent {
|
||||
val intent = Intent(Intent.ACTION_VIEW);
|
||||
intent.setPackage(packageName);
|
||||
intent.setClassName(packageName, activityName);
|
||||
return intent;
|
||||
}
|
||||
|
||||
private suspend fun BackendInfo.enable(fragment: Fragment): Boolean {
|
||||
val initIntent = initIntent.get()
|
||||
val activity = fragment.requireActivity() as AppCompatActivity
|
||||
if (initIntent != null) {
|
||||
val success = fragment.startActivityForResultCode(initIntent) == RESULT_OK
|
||||
if (!success) {
|
||||
Log.w("USettings", "Failed to init backend $signedComponent")
|
||||
return false
|
||||
}
|
||||
}
|
||||
val client = UnifiedLocationClient[activity]
|
||||
when (type) {
|
||||
BackendType.LOCATION -> client.setLocationBackends(client.getLocationBackends().without(this) + signedComponent)
|
||||
BackendType.GEOCODER -> client.setGeocoderBackends(client.getGeocoderBackends().without(this) + signedComponent)
|
||||
}
|
||||
Log.w("USettings", "Enabled backend $signedComponent")
|
||||
return true
|
||||
}
|
||||
|
||||
private suspend fun BackendInfo.disable(fragment: Fragment): Boolean {
|
||||
val client = UnifiedLocationClient[fragment.requireContext()]
|
||||
when (type) {
|
||||
BackendType.LOCATION -> client.setLocationBackends(client.getLocationBackends().without(this))
|
||||
BackendType.GEOCODER -> client.setGeocoderBackends(client.getGeocoderBackends().without(this))
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
@SuppressLint("PackageManagerGetSignatures")
|
||||
fun firstSignatureDigest(context: Context, packageName: String?): String? {
|
||||
val packageManager = context.packageManager
|
||||
val info: PackageInfo?
|
||||
try {
|
||||
info = packageManager.getPackageInfo(packageName!!, PackageManager.GET_SIGNATURES)
|
||||
} catch (e: PackageManager.NameNotFoundException) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (info?.signatures?.isNotEmpty() == true) {
|
||||
for (sig in info.signatures) {
|
||||
sha256sum(sig.toByteArray())?.let { return it }
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun sha256sum(bytes: ByteArray): String? {
|
||||
try {
|
||||
val md = MessageDigest.getInstance("SHA-256")
|
||||
val digest = md.digest(bytes)
|
||||
val sb = StringBuilder(2 * digest.size)
|
||||
for (b in digest) {
|
||||
sb.append(String.format("%02x", b))
|
||||
}
|
||||
return sb.toString()
|
||||
} catch (e: NoSuchAlgorithmException) {
|
||||
return null
|
||||
}
|
||||
}
|
|
@ -8,7 +8,6 @@ package org.microg.nlp.ui
|
|||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.Intent.ACTION_VIEW
|
||||
import android.content.pm.PackageManager.GET_META_DATA
|
||||
import android.content.res.ColorStateList
|
||||
import android.graphics.Color
|
||||
|
@ -22,19 +21,22 @@ import android.view.View
|
|||
import android.view.ViewGroup
|
||||
import androidx.annotation.AttrRes
|
||||
import androidx.annotation.ColorInt
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
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.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import org.microg.nlp.client.UnifiedLocationClient
|
||||
import org.microg.nlp.ui.BackendType.GEOCODER
|
||||
import org.microg.nlp.ui.BackendType.LOCATION
|
||||
import org.microg.nlp.ui.viewmodel.BackendType.GEOCODER
|
||||
import org.microg.nlp.ui.viewmodel.BackendType.LOCATION
|
||||
import org.microg.nlp.ui.databinding.BackendDetailsBinding
|
||||
import org.microg.nlp.ui.viewmodel.BackendDetailsCallback
|
||||
import org.microg.nlp.ui.viewmodel.BackendInfo
|
||||
import org.microg.nlp.ui.viewmodel.BackendType
|
||||
import java.util.*
|
||||
|
||||
class BackendDetailsFragment : Fragment(R.layout.backend_details) {
|
||||
class BackendDetailsFragment : Fragment(R.layout.backend_details), BackendDetailsCallback {
|
||||
|
||||
fun Double.toStringWithDigits(digits: Int): String {
|
||||
val s = this.toString()
|
||||
|
@ -82,6 +84,7 @@ class BackendDetailsFragment : Fragment(R.layout.backend_details) {
|
|||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
binding = BackendDetailsBinding.inflate(inflater, container, false)
|
||||
binding.fragment = this
|
||||
binding.callbacks = this
|
||||
return binding.root
|
||||
}
|
||||
|
||||
|
@ -101,21 +104,19 @@ class BackendDetailsFragment : Fragment(R.layout.backend_details) {
|
|||
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 (entry == null) return
|
||||
if (!entry.loaded.get()) {
|
||||
entry.fillDetails(requireContext())
|
||||
entry.loadIntents(requireActivity() as AppCompatActivity)
|
||||
}
|
||||
if (entry.type == LOCATION && entry.enabled.get()) {
|
||||
if (updateInProgress) return
|
||||
updateInProgress = true
|
||||
val client = UnifiedLocationClient[entry.context]
|
||||
val client = UnifiedLocationClient[requireContext()]
|
||||
|
||||
val locationTemp = client.getLastLocationForBackend(entry.serviceInfo.packageName, entry.serviceInfo.name, entry.firstSignatureDigest)
|
||||
val location = when (locationTemp) {
|
||||
|
@ -153,41 +154,18 @@ class BackendDetailsFragment : Fragment(R.layout.backend_details) {
|
|||
binding.lastLocationString = locationString
|
||||
binding.executePendingBindings()
|
||||
} else {
|
||||
Log.d(TAG, "Location is not available for this backend (type: ${entry?.type}, enabled ${entry?.enabled}")
|
||||
Log.d(TAG, "Location is not available for this backend (type: ${entry.type}, enabled ${entry.enabled.get()}")
|
||||
binding.lastLocationString = ""
|
||||
binding.executePendingBindings()
|
||||
}
|
||||
}
|
||||
|
||||
fun onBackendEnabledChanged(entry: BackendInfo) {
|
||||
entry.enabled = !entry.enabled
|
||||
override fun onAboutClicked(entry: BackendInfo?) {
|
||||
entry?.aboutIntent?.get()?.let { requireContext().startActivity(it) }
|
||||
}
|
||||
|
||||
private fun createExternalIntent(packageName: String, activityName: String): Intent {
|
||||
val intent = Intent(ACTION_VIEW);
|
||||
intent.setPackage(packageName);
|
||||
intent.setClassName(packageName, activityName);
|
||||
return intent;
|
||||
}
|
||||
|
||||
private fun startExternalActivity(packageName: String, activityName: String) {
|
||||
requireContext().startActivity(createExternalIntent(packageName, activityName))
|
||||
}
|
||||
|
||||
fun onAboutClicked(entry: BackendInfo) {
|
||||
entry.aboutActivity?.let { activityName -> startExternalActivity(entry.serviceInfo.packageName, activityName) }
|
||||
}
|
||||
|
||||
fun onConfigureClicked(entry: BackendInfo) {
|
||||
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)
|
||||
override fun onConfigureClicked(entry: BackendInfo?) {
|
||||
entry?.settingsIntent?.get()?.let { requireContext().startActivity(it) }
|
||||
}
|
||||
|
||||
private suspend fun createBackendInfo(): BackendInfo? {
|
||||
|
@ -201,11 +179,35 @@ class BackendDetailsFragment : Fragment(R.layout.backend_details) {
|
|||
GEOCODER -> UnifiedLocationClient[requireContext()].getGeocoderBackends()
|
||||
LOCATION -> UnifiedLocationClient[requireContext()].getLocationBackends()
|
||||
}
|
||||
return BackendInfo(requireContext(), serviceInfo, type, lifecycleScope, enabledBackends)
|
||||
val info = BackendInfo(serviceInfo, type, firstSignatureDigest(requireContext(), packageName))
|
||||
info.enabled.set(enabledBackends.contains(info.signedComponent) || enabledBackends.contains(info.unsignedComponent))
|
||||
return info
|
||||
}
|
||||
|
||||
override fun onEnabledChange(entry: BackendInfo?, newValue: Boolean) {
|
||||
Log.d(TAG, "onEnabledChange: ${entry?.signedComponent} = $newValue")
|
||||
val activity = requireActivity() as AppCompatActivity
|
||||
entry?.enabled?.set(newValue)
|
||||
activity.lifecycleScope.launch {
|
||||
entry?.updateEnabled(this@BackendDetailsFragment, newValue)
|
||||
initContent(entry)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onAppClicked(entry: BackendInfo?) {
|
||||
if (entry == null) return
|
||||
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)
|
||||
}
|
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
handleActivityResult(requestCode, resultCode, data)
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val ACTION = "org.microg.nlp.ui.BACKEND_DETAILS"
|
||||
private const val TAG = "USettings"
|
||||
private const val WAIT_FOR_RESULT = 5000L
|
||||
}
|
||||
|
|
|
@ -1,103 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2020, microG Project Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package org.microg.nlp.ui
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.content.pm.PackageInfo
|
||||
import android.content.pm.PackageManager
|
||||
import android.content.pm.ServiceInfo
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.util.Log
|
||||
import androidx.databinding.BaseObservable
|
||||
import androidx.databinding.Bindable
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
import org.microg.nlp.api.Constants
|
||||
import org.microg.nlp.client.UnifiedLocationClient
|
||||
import java.security.MessageDigest
|
||||
import java.security.NoSuchAlgorithmException
|
||||
|
||||
private val TAG: String = "ULocUI"
|
||||
|
||||
class BackendInfo(val context: Context, val serviceInfo: ServiceInfo, val type: BackendType, val coroutineScope: CoroutineScope, enabledBackends: Array<String>) : BaseObservable() {
|
||||
val firstSignatureDigest = firstSignatureDigest(context, serviceInfo.packageName)
|
||||
val unsignedComponent: String = "${serviceInfo.packageName}/${serviceInfo.name}"
|
||||
val signedComponent: String = "${serviceInfo.packageName}/${serviceInfo.name}/$firstSignatureDigest"
|
||||
|
||||
var enabled: Boolean = enabledBackends.contains(signedComponent) || enabledBackends.contains(unsignedComponent)
|
||||
@Bindable get
|
||||
set(value) {
|
||||
if (field == value) return
|
||||
field = value
|
||||
notifyPropertyChanged(BR.enabled)
|
||||
coroutineScope.launch {
|
||||
val client = UnifiedLocationClient[context]
|
||||
val withoutSelf = when (type) {
|
||||
BackendType.LOCATION -> client.getLocationBackends()
|
||||
BackendType.GEOCODER -> client.getGeocoderBackends()
|
||||
}.filterNot { it == unsignedComponent || it.startsWith("$unsignedComponent/") }.toTypedArray()
|
||||
val new = if (value) withoutSelf + signedComponent else withoutSelf
|
||||
try {
|
||||
when (type) {
|
||||
BackendType.LOCATION -> client.setLocationBackends(new)
|
||||
BackendType.GEOCODER -> client.setGeocoderBackends(new)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, "Failed to change backend state", e)
|
||||
field = !value
|
||||
notifyPropertyChanged(BR.enabled)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val appIcon: Drawable by lazy { serviceInfo.loadIcon(context.packageManager) }
|
||||
val name: CharSequence by lazy { serviceInfo.loadLabel(context.packageManager) }
|
||||
val appName: CharSequence by lazy { serviceInfo.applicationInfo.loadLabel(context.packageManager) }
|
||||
|
||||
val backendSummary: String? by lazy { serviceInfo.metaData?.getString(Constants.METADATA_BACKEND_SUMMARY) }
|
||||
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 }
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
@SuppressLint("PackageManagerGetSignatures")
|
||||
fun firstSignatureDigest(context: Context, packageName: String?): String? {
|
||||
val packageManager = context.packageManager
|
||||
val info: PackageInfo?
|
||||
try {
|
||||
info = packageManager.getPackageInfo(packageName!!, PackageManager.GET_SIGNATURES)
|
||||
} catch (e: PackageManager.NameNotFoundException) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (info?.signatures?.isNotEmpty() == true) {
|
||||
for (sig in info.signatures) {
|
||||
sha256sum(sig.toByteArray())?.let { return it }
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun sha256sum(bytes: ByteArray): String? {
|
||||
try {
|
||||
val md = MessageDigest.getInstance("SHA-256")
|
||||
val digest = md.digest(bytes)
|
||||
val sb = StringBuilder(2 * digest.size)
|
||||
for (b in digest) {
|
||||
sb.append(String.format("%02x", b))
|
||||
}
|
||||
return sb.toString()
|
||||
} catch (e: NoSuchAlgorithmException) {
|
||||
return null
|
||||
}
|
||||
}
|
|
@ -5,27 +5,29 @@
|
|||
|
||||
package org.microg.nlp.ui
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager.GET_META_DATA
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageView
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.navigation.fragment.FragmentNavigatorExtras
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import kotlinx.coroutines.launch
|
||||
import org.microg.nlp.api.Constants.ACTION_GEOCODER_BACKEND
|
||||
import org.microg.nlp.api.Constants.ACTION_LOCATION_BACKEND
|
||||
import org.microg.nlp.client.UnifiedLocationClient
|
||||
import org.microg.nlp.ui.databinding.BackendListBinding
|
||||
import org.microg.nlp.ui.databinding.BackendListEntryBinding
|
||||
import org.microg.nlp.ui.viewmodel.BackendInfo
|
||||
import org.microg.nlp.ui.viewmodel.BackendListEntryCallback
|
||||
import org.microg.nlp.ui.viewmodel.BackendType
|
||||
|
||||
class BackendListFragment : Fragment(R.layout.backend_list) {
|
||||
class BackendListFragment : Fragment(R.layout.backend_list), BackendListEntryCallback {
|
||||
val locationAdapter: BackendSettingsLineAdapter = BackendSettingsLineAdapter(this)
|
||||
val geocoderAdapter: BackendSettingsLineAdapter = BackendSettingsLineAdapter(this)
|
||||
|
||||
|
@ -46,34 +48,53 @@ class BackendListFragment : Fragment(R.layout.backend_list) {
|
|||
UnifiedLocationClient[requireContext()].unref()
|
||||
}
|
||||
|
||||
fun onBackendSelected(tag: Any?) {
|
||||
val binding = tag as? BackendListEntryBinding ?: return
|
||||
val entry = binding.entry ?: return
|
||||
findNavController().navigate(R.id.openDetails, bundleOf(
|
||||
override fun onOpenDetails(entry: BackendInfo?) {
|
||||
if (entry == null) return
|
||||
findNavController().navigate(R.id.openBackendDetails, bundleOf(
|
||||
"type" to entry.type.name,
|
||||
"package" to entry.serviceInfo.packageName,
|
||||
"name" to entry.serviceInfo.name
|
||||
))
|
||||
}
|
||||
|
||||
private suspend fun updateAdapters() {
|
||||
val context = requireContext()
|
||||
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))
|
||||
override fun onEnabledChange(entry: BackendInfo?, newValue: Boolean) {
|
||||
val activity = requireActivity() as AppCompatActivity
|
||||
activity.lifecycleScope.launch {
|
||||
entry?.updateEnabled(this@BackendListFragment, newValue)
|
||||
}
|
||||
}
|
||||
|
||||
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) }
|
||||
private suspend fun updateAdapters() {
|
||||
val activity = requireActivity() as AppCompatActivity
|
||||
locationAdapter.setEntries(createBackendInfoList(activity, Intent(ACTION_LOCATION_BACKEND), UnifiedLocationClient[activity].getLocationBackends(), BackendType.LOCATION))
|
||||
geocoderAdapter.setEntries(createBackendInfoList(activity, Intent(ACTION_GEOCODER_BACKEND), UnifiedLocationClient[activity].getGeocoderBackends(), BackendType.GEOCODER))
|
||||
}
|
||||
|
||||
private fun createBackendInfoList(activity: AppCompatActivity, intent: Intent, enabledBackends: Array<String>, type: BackendType): Array<BackendInfo?> {
|
||||
val backends = activity.packageManager.queryIntentServices(intent, GET_META_DATA).map {
|
||||
val info = BackendInfo(it.serviceInfo, type, firstSignatureDigest(activity, it.serviceInfo.packageName))
|
||||
if (enabledBackends.contains(info.signedComponent) || enabledBackends.contains(info.unsignedComponent)) {
|
||||
info.enabled.set(true)
|
||||
}
|
||||
info.fillDetails(activity)
|
||||
activity.lifecycleScope.launch {
|
||||
info.loadIntents(activity)
|
||||
}
|
||||
info
|
||||
}.sortedBy { it.name.get() }
|
||||
if (backends.isEmpty()) return arrayOf(null)
|
||||
return backends.toTypedArray()
|
||||
}
|
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
handleActivityResult(requestCode, resultCode, data)
|
||||
}
|
||||
}
|
||||
|
||||
class BackendSettingsLineViewHolder(val binding: BackendListEntryBinding) : RecyclerView.ViewHolder(binding.root) {
|
||||
fun bind(fragment: BackendListFragment, entry: BackendInfo?) {
|
||||
binding.fragment = fragment
|
||||
binding.callbacks = fragment
|
||||
binding.entry = entry
|
||||
binding.tag = binding
|
||||
binding.executePendingBindings()
|
||||
}
|
||||
}
|
||||
|
@ -92,7 +113,7 @@ class BackendSettingsLineAdapter(val fragment: BackendListFragment) : RecyclerVi
|
|||
if (entries[oldIndex] == entry) return
|
||||
entries.removeAt(oldIndex)
|
||||
}
|
||||
val targetIndex = when (val i = entries.indexOfFirst { it == null || it.name.toString() > entry.name.toString() }) {
|
||||
val targetIndex = when (val i = entries.indexOfFirst { it == null || it.name.get().toString() > entry.name.get().toString() }) {
|
||||
-1 -> entries.size
|
||||
else -> i
|
||||
}
|
||||
|
|
|
@ -6,17 +6,12 @@
|
|||
package org.microg.nlp.ui
|
||||
|
||||
import android.os.Bundle
|
||||
import android.os.PersistableBundle
|
||||
import android.util.Log
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.appcompat.widget.Toolbar
|
||||
import androidx.navigation.NavController
|
||||
import androidx.navigation.NavHostController
|
||||
import androidx.navigation.findNavController
|
||||
import androidx.navigation.fragment.NavHostFragment
|
||||
import androidx.navigation.ui.AppBarConfiguration
|
||||
import androidx.navigation.ui.navigateUp
|
||||
import androidx.navigation.ui.setupActionBarWithNavController
|
||||
import androidx.navigation.ui.setupWithNavController
|
||||
|
||||
class BackendSettingsActivity : AppCompatActivity() {
|
||||
private lateinit var appBarConfiguration: AppBarConfiguration
|
||||
|
@ -33,6 +28,6 @@ class BackendSettingsActivity : AppCompatActivity() {
|
|||
}
|
||||
|
||||
override fun onSupportNavigateUp(): Boolean {
|
||||
return navController.navigateUp() || super.onSupportNavigateUp()
|
||||
return navController.navigateUp(appBarConfiguration) || super.onSupportNavigateUp()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2020, microG Project Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package org.microg.nlp.ui.viewmodel
|
||||
|
||||
interface BackendDetailsCallback {
|
||||
fun onEnabledChange(entry: BackendInfo?, newValue: Boolean)
|
||||
fun onAppClicked(entry: BackendInfo?)
|
||||
fun onAboutClicked(entry: BackendInfo?)
|
||||
fun onConfigureClicked(entry: BackendInfo?)
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2020, microG Project Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package org.microg.nlp.ui.viewmodel
|
||||
|
||||
import android.content.Intent
|
||||
import android.content.pm.ServiceInfo
|
||||
import android.graphics.drawable.Drawable
|
||||
import androidx.databinding.ObservableBoolean
|
||||
import androidx.databinding.ObservableField
|
||||
|
||||
class BackendInfo(val serviceInfo: ServiceInfo, val type: BackendType, val firstSignatureDigest: String?) {
|
||||
val enabled = ObservableBoolean()
|
||||
val appIcon = ObservableField<Drawable>()
|
||||
val name = ObservableField<String>()
|
||||
val appName = ObservableField<String>()
|
||||
val summary = ObservableField<String>()
|
||||
|
||||
val loaded = ObservableBoolean()
|
||||
|
||||
val initIntent = ObservableField<Intent>()
|
||||
val aboutIntent = ObservableField<Intent>()
|
||||
val settingsIntent = ObservableField<Intent>()
|
||||
|
||||
val unsignedComponent: String = "${serviceInfo.packageName}/${serviceInfo.name}"
|
||||
val signedComponent: String = "${serviceInfo.packageName}/${serviceInfo.name}/$firstSignatureDigest"
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
return other is BackendInfo && other.name == name && other.enabled == enabled && other.appName == appName && other.unsignedComponent == unsignedComponent && other.summary == summary
|
||||
}
|
||||
}
|
||||
|
||||
enum class BackendType { LOCATION, GEOCODER }
|
|
@ -0,0 +1,11 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2020, microG Project Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package org.microg.nlp.ui.viewmodel
|
||||
|
||||
interface BackendListEntryCallback {
|
||||
fun onEnabledChange(entry: BackendInfo?, newValue: Boolean)
|
||||
fun onOpenDetails(entry: BackendInfo?)
|
||||
}
|
|
@ -12,15 +12,19 @@
|
|||
|
||||
<import type="android.view.View" />
|
||||
|
||||
<import type="org.microg.nlp.ui.BackendType" />
|
||||
<import type="org.microg.nlp.ui.viewmodel.BackendType" />
|
||||
|
||||
<variable
|
||||
name="fragment"
|
||||
type="org.microg.nlp.ui.BackendDetailsFragment" />
|
||||
|
||||
<variable
|
||||
name="callbacks"
|
||||
type="org.microg.nlp.ui.viewmodel.BackendDetailsCallback" />
|
||||
|
||||
<variable
|
||||
name="entry"
|
||||
type="org.microg.nlp.ui.BackendInfo" />
|
||||
type="org.microg.nlp.ui.viewmodel.BackendInfo" />
|
||||
|
||||
<variable
|
||||
name="lastLocationString"
|
||||
|
@ -49,7 +53,7 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:gravity="center_horizontal"
|
||||
android:onClick='@{() -> fragment.onAppClicked(entry)}'
|
||||
android:onClick='@{() -> callbacks.onAppClicked(entry)}'
|
||||
android:orientation="vertical">
|
||||
|
||||
<ImageView
|
||||
|
@ -93,9 +97,10 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:clickable="true"
|
||||
android:enabled="@{entry.loaded}"
|
||||
android:focusable="true"
|
||||
android:gravity="center"
|
||||
android:onClick="@{() -> fragment.onBackendEnabledChanged(entry)}"
|
||||
android:onClick="@{() -> callbacks.onEnabledChange(entry, !entry.enabled)}"
|
||||
android:orientation="horizontal"
|
||||
android:paddingStart="?attr/listPreferredItemPaddingStart"
|
||||
android:paddingLeft="?attr/listPreferredItemPaddingLeft"
|
||||
|
@ -124,7 +129,9 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:background="@null"
|
||||
android:checked="@={entry.enabled}"
|
||||
android:checked="@{entry.enabled}"
|
||||
android:enabled="@{entry.loaded || entry.enabled}"
|
||||
app:onCheckedChangeListener="@{(view, checked) -> callbacks.onEnabledChange(entry, checked)}"
|
||||
app:thumbTint="?android:attr/textColorPrimaryInverse"
|
||||
tools:checked="true" />
|
||||
</LinearLayout>
|
||||
|
@ -139,12 +146,12 @@
|
|||
android:focusable="true"
|
||||
android:gravity="start|center_vertical"
|
||||
android:minHeight="?attr/listPreferredItemHeightSmall"
|
||||
android:onClick="@{() -> fragment.onConfigureClicked(entry)}"
|
||||
android:onClick="@{() -> callbacks.onConfigureClicked(entry)}"
|
||||
android:paddingStart="?attr/listPreferredItemPaddingStart"
|
||||
android:paddingLeft="?attr/listPreferredItemPaddingLeft"
|
||||
android:paddingEnd="?attr/listPreferredItemPaddingEnd"
|
||||
android:paddingRight="?attr/listPreferredItemPaddingRight"
|
||||
android:visibility='@{entry == null || entry.settingsActivity == null ? View.GONE : View.VISIBLE}'>
|
||||
android:visibility='@{entry == null || entry.settingsIntent == null ? View.GONE : View.VISIBLE}'>
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="wrap_content"
|
||||
|
@ -174,12 +181,12 @@
|
|||
android:focusable="true"
|
||||
android:gravity="start|center_vertical"
|
||||
android:minHeight="?attr/listPreferredItemHeightSmall"
|
||||
android:onClick="@{() -> fragment.onAboutClicked(entry)}"
|
||||
android:onClick="@{() -> callbacks.onAboutClicked(entry)}"
|
||||
android:paddingStart="?attr/listPreferredItemPaddingStart"
|
||||
android:paddingLeft="?attr/listPreferredItemPaddingLeft"
|
||||
android:paddingEnd="?attr/listPreferredItemPaddingEnd"
|
||||
android:paddingRight="?attr/listPreferredItemPaddingRight"
|
||||
android:visibility='@{entry == null || entry.aboutActivity == null ? View.GONE : View.VISIBLE}'>
|
||||
android:visibility='@{entry == null || entry.aboutIntent == null ? View.GONE : View.VISIBLE}'>
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="wrap_content"
|
||||
|
@ -205,7 +212,7 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:background="?attr/dividerHorizontal"
|
||||
android:visibility='@{entry == null || (entry.backendSummary == null || entry.type != BackendType.LOCATION) || (entry.settingsActivity == null && entry.aboutActivity == null) ? View.GONE : View.VISIBLE}' />
|
||||
android:visibility='@{entry == null || (entry.summary == null || entry.type != BackendType.LOCATION) || (entry.settingsIntent == null && entry.aboutIntent == null) ? View.GONE : View.VISIBLE}' />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
|
@ -217,7 +224,7 @@
|
|||
android:paddingLeft="?attr/listPreferredItemPaddingLeft"
|
||||
android:paddingEnd="?attr/listPreferredItemPaddingEnd"
|
||||
android:paddingRight="?attr/listPreferredItemPaddingRight"
|
||||
android:visibility='@{entry == null || entry.backendSummary == null ? View.GONE : View.VISIBLE}'>
|
||||
android:visibility='@{entry == null || entry.summary == null ? View.GONE : View.VISIBLE}'>
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="wrap_content"
|
||||
|
@ -246,7 +253,7 @@
|
|||
android:layout_alignStart="@id/description_title"
|
||||
android:layout_alignLeft="@id/description_title"
|
||||
android:maxLines="10"
|
||||
android:text='@{entry.backendSummary ?? ""}'
|
||||
android:text='@{entry.summary ?? ""}'
|
||||
android:textAppearance="?attr/textAppearanceListItemSecondary"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
tools:text="Locate using Mozilla\'s online database" />
|
||||
|
|
|
@ -41,7 +41,7 @@
|
|||
android:paddingTop="8dp"
|
||||
android:paddingBottom="8dp"
|
||||
android:singleLine="true"
|
||||
android:text="@string/network_location"
|
||||
android:text="Network-based Geolocation modules"
|
||||
android:textAllCaps="true"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Body2"
|
||||
android:textColor="?attr/colorAccent"
|
||||
|
@ -83,7 +83,7 @@
|
|||
android:paddingTop="8dp"
|
||||
android:paddingBottom="8dp"
|
||||
android:singleLine="true"
|
||||
android:text="@string/geocoding"
|
||||
android:text="Address lookup modules"
|
||||
android:textAllCaps="true"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Body2"
|
||||
android:textColor="?attr/colorAccent"
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
~ SPDX-License-Identifier: Apache-2.0
|
||||
-->
|
||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<data>
|
||||
|
@ -11,16 +12,12 @@
|
|||
<import type="android.view.View" />
|
||||
|
||||
<variable
|
||||
name="fragment"
|
||||
type="org.microg.nlp.ui.BackendListFragment" />
|
||||
name="callbacks"
|
||||
type="org.microg.nlp.ui.viewmodel.BackendListEntryCallback" />
|
||||
|
||||
<variable
|
||||
name="entry"
|
||||
type="org.microg.nlp.ui.BackendInfo" />
|
||||
|
||||
<variable
|
||||
name="tag"
|
||||
type="Object" />
|
||||
type="org.microg.nlp.ui.viewmodel.BackendInfo" />
|
||||
</data>
|
||||
|
||||
<LinearLayout
|
||||
|
@ -40,7 +37,7 @@
|
|||
android:clipToPadding="false"
|
||||
android:focusable="true"
|
||||
android:gravity="start|center_vertical"
|
||||
android:onClick="@{() -> fragment.onBackendSelected(tag)}"
|
||||
android:onClick="@{() -> callbacks.onOpenDetails(entry)}"
|
||||
android:paddingStart="?attr/listPreferredItemPaddingStart"
|
||||
android:paddingLeft="?attr/listPreferredItemPaddingLeft"
|
||||
android:paddingEnd="?attr/listPreferredItemPaddingEnd"
|
||||
|
@ -110,10 +107,12 @@
|
|||
<androidx.appcompat.widget.SwitchCompat
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:checked="@={entry.enabled}"
|
||||
android:checked="@{entry.enabled}"
|
||||
android:enabled="@{entry.loaded || entry.enabled}"
|
||||
android:minWidth="80dp"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingRight="16dp" />
|
||||
android:paddingRight="16dp"
|
||||
app:onCheckedChangeListener="@{(view, checked) -> callbacks.onEnabledChange(entry, checked)}" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</layout>
|
|
@ -13,11 +13,11 @@
|
|||
android:name="org.microg.nlp.ui.BackendListFragment"
|
||||
android:label="Location modules">
|
||||
<action
|
||||
android:id="@+id/openDetails"
|
||||
android:id="@+id/openBackendDetails"
|
||||
app:destination="@id/backendDetailsFragment"
|
||||
app:enterAnim="@anim/fragment_open_enter"
|
||||
app:exitAnim="@anim/fragment_close_exit"
|
||||
app:popEnterAnim="@anim/fragment_open_enter"
|
||||
app:exitAnim="@anim/fragment_open_exit"
|
||||
app:popEnterAnim="@anim/fragment_close_enter"
|
||||
app:popExitAnim="@anim/fragment_close_exit" />
|
||||
</fragment>
|
||||
<fragment
|
||||
|
|
Loading…
Reference in a new issue