Small fixes everywhere
This commit is contained in:
parent
484d01fb83
commit
16d8fd5096
|
@ -5,12 +5,13 @@
|
|||
|
||||
buildscript {
|
||||
ext.kotlinVersion = '1.3.72'
|
||||
ext.coroutineVersion = '1.3.3'
|
||||
ext.coroutineVersion = '1.3.7'
|
||||
|
||||
ext.appcompatVersion = '1.1.0'
|
||||
ext.fragmentVersion = '1.2.4'
|
||||
ext.recyclerviewVersion = '1.1.0'
|
||||
ext.fragmentVersion = '1.2.5'
|
||||
ext.lifecycleVersion = '2.2.0'
|
||||
ext.navigationVersion = '2.3.0'
|
||||
ext.recyclerviewVersion = '1.1.0'
|
||||
|
||||
ext.androidBuildGradleVersion = '3.6.3'
|
||||
|
||||
|
|
|
@ -30,6 +30,7 @@ import java.util.concurrent.atomic.AtomicInteger
|
|||
|
||||
import android.content.pm.PackageManager.SIGNATURE_MATCH
|
||||
import kotlinx.coroutines.*
|
||||
import java.lang.RuntimeException
|
||||
import java.util.concurrent.*
|
||||
import kotlin.coroutines.*
|
||||
import kotlin.math.min
|
||||
|
@ -47,6 +48,7 @@ class UnifiedLocationClient private constructor(context: Context) {
|
|||
private var timer: Timer? = null
|
||||
private var reconnectCount = 0
|
||||
private val requests = CopyOnWriteArraySet<LocationRequest>()
|
||||
private val coroutineScope = CoroutineScope(Dispatchers.IO + Job())
|
||||
private val connection = object : ServiceConnection {
|
||||
override fun onServiceConnected(name: ComponentName, service: IBinder) {
|
||||
this@UnifiedLocationClient.onServiceConnected(name, service)
|
||||
|
@ -70,6 +72,10 @@ class UnifiedLocationClient private constructor(context: Context) {
|
|||
get() = options.getBoolean(KEY_FORCE_NEXT_UPDATE, false)
|
||||
set(value) = options.putBoolean(KEY_FORCE_NEXT_UPDATE, value)
|
||||
|
||||
var opPackageName: String?
|
||||
get() = options.getString(KEY_OP_PACKAGE_NAME)
|
||||
set(value) = options.putString(KEY_OP_PACKAGE_NAME, value)
|
||||
|
||||
val isAvailable: Boolean
|
||||
get() = bound || resolve() != null
|
||||
|
||||
|
@ -83,44 +89,40 @@ class UnifiedLocationClient private constructor(context: Context) {
|
|||
val pm = context.packageManager
|
||||
var resolveInfos = pm.queryIntentServices(intent, 0)
|
||||
if (resolveInfos.size > 1) {
|
||||
// Pick own package if possible
|
||||
for (info in resolveInfos) {
|
||||
if (info.serviceInfo.packageName == context.packageName) {
|
||||
intent.setPackage(info.serviceInfo.packageName)
|
||||
Log.d(TAG, "Found more than one active unified service, picked own package " + intent.getPackage()!!)
|
||||
return intent
|
||||
}
|
||||
|
||||
// Restrict to self if possible
|
||||
val isSelf: (it: ResolveInfo) -> Boolean = {
|
||||
it.serviceInfo.packageName == context.packageName
|
||||
}
|
||||
if (resolveInfos.size > 1 && resolveInfos.any(isSelf)) {
|
||||
Log.d(TAG, "Found more than one active unified service, restricted to own package " + context.packageName)
|
||||
resolveInfos = resolveInfos.filter(isSelf)
|
||||
}
|
||||
|
||||
// Pick package with matching signature if possible
|
||||
for (info in resolveInfos) {
|
||||
if (pm.checkSignatures(context.packageName, info.serviceInfo.packageName) == SIGNATURE_MATCH) {
|
||||
intent.setPackage(info.serviceInfo.packageName)
|
||||
Log.d(TAG, "Found more than one active unified service, picked related package " + intent.getPackage()!!)
|
||||
return intent
|
||||
}
|
||||
// Restrict to package with matching signature if possible
|
||||
val isSelfSig: (it: ResolveInfo) -> Boolean = {
|
||||
it.serviceInfo.packageName == context.packageName
|
||||
}
|
||||
if (resolveInfos.size > 1 && resolveInfos.any(isSelfSig)) {
|
||||
Log.d(TAG, "Found more than one active unified service, restricted to related packages")
|
||||
resolveInfos = resolveInfos.filter(isSelfSig)
|
||||
}
|
||||
|
||||
// Restrict to system if single package is system
|
||||
if (resolveInfos.any {
|
||||
(it.serviceInfo?.applicationInfo?.flags
|
||||
?: 0) and ApplicationInfo.FLAG_SYSTEM > 0
|
||||
}) {
|
||||
// Restrict to system if any package is system
|
||||
val isSystem: (it: ResolveInfo) -> Boolean = {
|
||||
(it.serviceInfo?.applicationInfo?.flags ?: 0) and ApplicationInfo.FLAG_SYSTEM > 0
|
||||
}
|
||||
if (resolveInfos.size > 1 && resolveInfos.any(isSystem)) {
|
||||
Log.d(TAG, "Found more than one active unified service, restricted to system packages")
|
||||
resolveInfos = resolveInfos.filter {
|
||||
(it.serviceInfo?.applicationInfo?.flags
|
||||
?: 0) and ApplicationInfo.FLAG_SYSTEM > 0
|
||||
}
|
||||
resolveInfos = resolveInfos.filter(isSystem)
|
||||
}
|
||||
|
||||
var highestPriority: ResolveInfo? = null
|
||||
for (info in resolveInfos) {
|
||||
if (highestPriority == null || highestPriority.priority < info.priority) {
|
||||
highestPriority = info
|
||||
}
|
||||
}
|
||||
val highestPriority: ResolveInfo? = resolveInfos.maxBy { it.priority }
|
||||
intent.setPackage(highestPriority!!.serviceInfo.packageName)
|
||||
Log.d(TAG, "Found more than one active unified service, picked highest priority " + intent.getPackage()!!)
|
||||
intent.setClassName(highestPriority.serviceInfo.packageName, highestPriority.serviceInfo.name)
|
||||
if (resolveInfos.size > 1) {
|
||||
Log.d(TAG, "Found more than one active unified service, picked highest priority " + intent.component)
|
||||
}
|
||||
return intent
|
||||
} else if (!resolveInfos.isEmpty()) {
|
||||
intent.setPackage(resolveInfos[0].serviceInfo.packageName)
|
||||
|
@ -132,17 +134,21 @@ class UnifiedLocationClient private constructor(context: Context) {
|
|||
}
|
||||
|
||||
@Synchronized
|
||||
private fun updateBinding() {
|
||||
private fun updateBinding(): Boolean {
|
||||
Log.d(TAG, "updateBinding - current: $bound, refs: ${serviceReferenceCount.get()}, reqs: ${requests.size}, avail: $isAvailable")
|
||||
if (!bound && (serviceReferenceCount.get() > 0 || !requests.isEmpty()) && isAvailable) {
|
||||
timer = Timer("unified-client")
|
||||
bound = true
|
||||
bind()
|
||||
return true
|
||||
} else if (bound && serviceReferenceCount.get() == 0 && requests.isEmpty()) {
|
||||
timer!!.cancel()
|
||||
timer = null
|
||||
bound = false
|
||||
unbind()
|
||||
return false
|
||||
}
|
||||
return bound
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
|
@ -173,27 +179,30 @@ class UnifiedLocationClient private constructor(context: Context) {
|
|||
val intent = resolve() ?: return
|
||||
unbind()
|
||||
reconnectCount++
|
||||
Log.d(TAG, "Binding to $intent")
|
||||
bound = context.bindService(intent, connection, Context.BIND_AUTO_CREATE)
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
private fun unbind() {
|
||||
try {
|
||||
this.context.get()?.let {
|
||||
it.unbindService(connection)
|
||||
}
|
||||
this.context.get()?.unbindService(connection)
|
||||
} catch (ignored: Exception) {
|
||||
}
|
||||
|
||||
this.service = null
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun ref() {
|
||||
Log.d(TAG, "ref: ${Exception().stackTrace[1]}")
|
||||
serviceReferenceCount.incrementAndGet()
|
||||
updateBinding()
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun unref() {
|
||||
Log.d(TAG, "unref: ${Exception().stackTrace[1]}")
|
||||
serviceReferenceCount.decrementAndGet()
|
||||
updateBinding()
|
||||
}
|
||||
|
@ -210,24 +219,34 @@ class UnifiedLocationClient private constructor(context: Context) {
|
|||
requestLocationUpdates(listener, interval, Integer.MAX_VALUE)
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun requestLocationUpdates(listener: LocationListener, interval: Long, count: Int) {
|
||||
requests.removeAll(requests.filter { it.listener === listener })
|
||||
requests.add(LocationRequest(listener, interval, count))
|
||||
updateServiceInterval()
|
||||
updateBinding()
|
||||
coroutineScope.launch {
|
||||
updateServiceInterval()
|
||||
updateBinding()
|
||||
}
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun removeLocationUpdates(listener: LocationListener) {
|
||||
removeRequests(requests.filter { it.listener === listener })
|
||||
coroutineScope.launch {
|
||||
removeRequests(requests.filter { it.listener === listener })
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun refAndGetService(): UnifiedLocationService = suspendCoroutine { continuation -> refAndGetServiceContinued(continuation) }
|
||||
|
||||
@Synchronized
|
||||
private fun refAndGetServiceContinued(continuation: Continuation<UnifiedLocationService>) {
|
||||
Log.d(TAG, "ref+get: ${Exception().stackTrace[2]}")
|
||||
serviceReferenceCount.incrementAndGet()
|
||||
waitForServiceContinued(continuation)
|
||||
}
|
||||
|
||||
private suspend fun waitForService(): UnifiedLocationService = suspendCoroutine { continuation -> waitForServiceContinued(continuation) }
|
||||
|
||||
@Synchronized
|
||||
private fun waitForServiceContinued(continuation: Continuation<UnifiedLocationService>) {
|
||||
val service = service
|
||||
if (service != null) {
|
||||
continuation.resume(service)
|
||||
|
@ -235,22 +254,30 @@ class UnifiedLocationClient private constructor(context: Context) {
|
|||
synchronized(waitingForService) {
|
||||
waitingForService.add(continuation)
|
||||
}
|
||||
timer?.schedule(object : TimerTask() {
|
||||
override fun run() {
|
||||
try {
|
||||
continuation.resumeWithException(TimeoutException())
|
||||
} catch (e: IllegalStateException) {
|
||||
// Resumed pretty much the same moment as timeout triggered, ignore
|
||||
}
|
||||
}
|
||||
}, CALL_TIMEOUT)
|
||||
updateBinding()
|
||||
val timer = timer
|
||||
if (timer == null) {
|
||||
synchronized(waitingForService) {
|
||||
waitingForService.remove(continuation)
|
||||
}
|
||||
continuation.resumeWithException(RuntimeException("No timer, called waitForService when not connected"))
|
||||
} else {
|
||||
timer.schedule(object : TimerTask() {
|
||||
override fun run() {
|
||||
try {
|
||||
continuation.resumeWithException(TimeoutException())
|
||||
} catch (e: IllegalStateException) {
|
||||
// Resumed pretty much the same moment as timeout triggered, ignore
|
||||
}
|
||||
}
|
||||
}, CALL_TIMEOUT)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun <T> configureContinuationTimeout(continuation: Continuation<T>, timeout: Long) {
|
||||
private fun <T> configureContinuationTimeout(continuation: Continuation<T>, timeout: Long) {
|
||||
if (timeout <= 0 || timeout == Long.MAX_VALUE) return
|
||||
timer?.schedule(object : TimerTask() {
|
||||
timer!!.schedule(object : TimerTask() {
|
||||
override fun run() {
|
||||
try {
|
||||
Log.w(TAG, "Timeout reached")
|
||||
|
@ -262,17 +289,24 @@ class UnifiedLocationClient private constructor(context: Context) {
|
|||
}, timeout)
|
||||
}
|
||||
|
||||
fun <T> executeSyncWithTimeout(timeout: Long = CALL_TIMEOUT, action: suspend () -> T): T {
|
||||
private fun <T> executeSyncWithTimeout(timeout: Long = CALL_TIMEOUT, action: suspend () -> T): T {
|
||||
var result: T? = null
|
||||
val latch = CountDownLatch(1)
|
||||
var err: Exception? = null
|
||||
syncThreads.execute {
|
||||
runBlocking {
|
||||
result = withTimeout(timeout) { action() }
|
||||
try {
|
||||
runBlocking {
|
||||
result = withTimeout(timeout) { action() }
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
err = e
|
||||
} finally {
|
||||
latch.countDown()
|
||||
}
|
||||
}
|
||||
if (!latch.await(timeout, TimeUnit.MILLISECONDS))
|
||||
throw TimeoutException()
|
||||
err?.let { throw it }
|
||||
return result ?: throw NullPointerException()
|
||||
}
|
||||
|
||||
|
@ -302,7 +336,7 @@ class UnifiedLocationClient private constructor(context: Context) {
|
|||
service.getFromLocationNameWithOptions(locationName, maxResults, lowerLeftLatitude, lowerLeftLongitude, upperRightLatitude, upperRightLongitude, locale, options, AddressContinuation(continuation))
|
||||
configureContinuationTimeout(continuation, timeout)
|
||||
}
|
||||
} catch (e: RemoteException) {
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, "Failed to request geocode", e)
|
||||
emptyList()
|
||||
} finally {
|
||||
|
@ -327,7 +361,7 @@ class UnifiedLocationClient private constructor(context: Context) {
|
|||
suspend fun getLocationBackends(): Array<String> {
|
||||
try {
|
||||
return refAndGetService().locationBackends
|
||||
} catch (e: RemoteException) {
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, "Failed to handle request", e)
|
||||
return emptyArray()
|
||||
} finally {
|
||||
|
@ -338,7 +372,7 @@ class UnifiedLocationClient private constructor(context: Context) {
|
|||
suspend fun setLocationBackends(backends: Array<String>) {
|
||||
try {
|
||||
refAndGetService().locationBackends = backends
|
||||
} catch (e: RemoteException) {
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, "Failed to handle request", e)
|
||||
} finally {
|
||||
unref()
|
||||
|
@ -366,6 +400,10 @@ class UnifiedLocationClient private constructor(context: Context) {
|
|||
}
|
||||
}
|
||||
|
||||
fun getLastLocationSync(timeout: Long = CALL_TIMEOUT): Location? = executeSyncWithTimeout(timeout) {
|
||||
getLastLocation()
|
||||
}
|
||||
|
||||
suspend fun getLastLocation(): Location? {
|
||||
return try {
|
||||
refAndGetService().lastLocation
|
||||
|
@ -388,12 +426,11 @@ class UnifiedLocationClient private constructor(context: Context) {
|
|||
}
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
private fun removeRequestPendingRemoval() {
|
||||
private suspend fun removeRequestPendingRemoval() {
|
||||
removeRequests(requests.filter { it.needsRemoval })
|
||||
}
|
||||
|
||||
private fun removeRequests(removalNeeded: List<LocationRequest>) {
|
||||
private suspend fun removeRequests(removalNeeded: List<LocationRequest>) {
|
||||
if (removalNeeded.isNotEmpty()) {
|
||||
requests.removeAll(removalNeeded)
|
||||
updateServiceInterval()
|
||||
|
@ -401,9 +438,7 @@ class UnifiedLocationClient private constructor(context: Context) {
|
|||
}
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
private fun updateServiceInterval() {
|
||||
if (service == null) return
|
||||
private suspend fun updateServiceInterval() {
|
||||
var interval: Long = Long.MAX_VALUE
|
||||
var requestSingle = false
|
||||
for (request in requests) {
|
||||
|
@ -419,11 +454,18 @@ class UnifiedLocationClient private constructor(context: Context) {
|
|||
} else {
|
||||
Log.d(TAG, "Set update interval to $interval")
|
||||
}
|
||||
val service: UnifiedLocationService
|
||||
try {
|
||||
service!!.setUpdateInterval(interval, options)
|
||||
service = waitForService()
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, e)
|
||||
return
|
||||
}
|
||||
try {
|
||||
service.setUpdateInterval(interval, options)
|
||||
if (requestSingle) {
|
||||
Log.d(TAG, "Request single update (force update: $forceNextUpdate)")
|
||||
service!!.requestSingleUpdate(options)
|
||||
service.requestSingleUpdate(options)
|
||||
forceNextUpdate = false
|
||||
}
|
||||
} catch (e: DeadObjectException) {
|
||||
|
@ -434,7 +476,6 @@ class UnifiedLocationClient private constructor(context: Context) {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
@Synchronized
|
||||
private fun onServiceConnected(name: ComponentName, binder: IBinder) {
|
||||
Log.d(TAG, "Connected to $name")
|
||||
|
@ -446,24 +487,40 @@ class UnifiedLocationClient private constructor(context: Context) {
|
|||
continuations.addAll(waitingForService)
|
||||
waitingForService.clear()
|
||||
}
|
||||
for (continuation in continuations) {
|
||||
continuation.resume(service)
|
||||
}
|
||||
try {
|
||||
service.registerLocationCallback(object : LocationCallback.Stub() {
|
||||
override fun onLocationUpdate(location: Location) {
|
||||
for (request in requests) {
|
||||
request.handleLocation(location)
|
||||
coroutineScope.launch {
|
||||
try {
|
||||
Log.d(TAG, "Registering location callback")
|
||||
service.registerLocationCallback(object : LocationCallback.Stub() {
|
||||
override fun onLocationUpdate(location: Location) {
|
||||
coroutineScope.launch {
|
||||
this@UnifiedLocationClient.onLocationUpdate(location)
|
||||
}
|
||||
}
|
||||
removeRequestPendingRemoval()
|
||||
}
|
||||
}, options)
|
||||
}, options)
|
||||
Log.d(TAG, "Registered location callback")
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, "Failed to register location callback", e)
|
||||
}
|
||||
updateServiceInterval()
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, "Failed to register location callback", e)
|
||||
if (continuations.size > 0) {
|
||||
Log.d(TAG, "Resuming ${continuations.size} continuations")
|
||||
}
|
||||
for (continuation in continuations) {
|
||||
try {
|
||||
continuation.resume(service)
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun onLocationUpdate(location: Location) {
|
||||
for (request in requests) {
|
||||
request.handleLocation(location)
|
||||
}
|
||||
removeRequestPendingRemoval()
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
private fun onServiceDisconnected(name: ComponentName) {
|
||||
|
@ -531,7 +588,9 @@ class UnifiedLocationClient private constructor(context: Context) {
|
|||
|
||||
companion object {
|
||||
const val ACTION_UNIFIED_LOCATION_SERVICE = "org.microg.nlp.service.UnifiedLocationService"
|
||||
const val KEY_FORCE_NEXT_UPDATE = "org.microg.nlp.client.FORCE_NEXT_UPDATE"
|
||||
const val PERMISSION_SERVICE_ADMIN = "org.microg.nlp.SERVICE_ADMIN"
|
||||
const val KEY_FORCE_NEXT_UPDATE = "org.microg.nlp.FORCE_NEXT_UPDATE"
|
||||
const val KEY_OP_PACKAGE_NAME = "org.microg.nlp.OP_PACKAGE_NAME"
|
||||
private val TAG = "ULocClient"
|
||||
private var client: UnifiedLocationClient? = null
|
||||
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
<uses-permission android:name="android.permission.ACCESS_COARSE_UPDATES" />
|
||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||
<uses-permission android:name="org.microg.nlp.SERVICE_ADMIN" />
|
||||
|
||||
<application>
|
||||
<uses-library android:name="com.android.location.provider" />
|
||||
|
|
|
@ -18,9 +18,14 @@ import com.android.location.provider.ProviderRequestUnbundled;
|
|||
|
||||
import org.microg.nlp.client.UnifiedLocationClient;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import static android.location.LocationProvider.AVAILABLE;
|
||||
|
||||
public class LocationProvider extends LocationProviderBase implements UnifiedLocationClient.LocationListener {
|
||||
private static final List<String> EXCLUDED_PACKAGES = Arrays.asList("android", "com.android.location.fused", "com.google.android.gms");
|
||||
private static final long FASTEST_REFRESH_INTERVAL = 30000;
|
||||
private static final String TAG = "ULocation";
|
||||
private Context context;
|
||||
|
@ -43,6 +48,21 @@ public class LocationProvider extends LocationProviderBase implements UnifiedLoc
|
|||
@Override
|
||||
public void onSetRequest(ProviderRequestUnbundled requests, WorkSource source) {
|
||||
Log.v(TAG, "onSetRequest: " + requests + " by " + source);
|
||||
String opPackageName = null;
|
||||
try {
|
||||
Field namesField = WorkSource.class.getDeclaredField("mNames");
|
||||
namesField.setAccessible(true);
|
||||
String[] names = (String[]) namesField.get(source);
|
||||
if (names != null) {
|
||||
for (String name : names) {
|
||||
if (!EXCLUDED_PACKAGES.contains(name)) {
|
||||
opPackageName = name;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
|
||||
long autoTime = Math.max(requests.getInterval(), FASTEST_REFRESH_INTERVAL);
|
||||
boolean autoUpdate = requests.getReportLocation();
|
||||
|
@ -50,6 +70,7 @@ public class LocationProvider extends LocationProviderBase implements UnifiedLoc
|
|||
Log.v(TAG, "using autoUpdate=" + autoUpdate + " autoTime=" + autoTime);
|
||||
|
||||
if (autoUpdate) {
|
||||
UnifiedLocationClient.get(context).setOpPackageName(opPackageName);
|
||||
UnifiedLocationClient.get(context).requestLocationUpdates(this, autoTime);
|
||||
} else {
|
||||
UnifiedLocationClient.get(context).removeLocationUpdates(this);
|
||||
|
|
|
@ -34,6 +34,9 @@ dependencies {
|
|||
implementation project(':api')
|
||||
implementation project(':client')
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlinVersion"
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutineVersion"
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutineVersion"
|
||||
implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycleVersion"
|
||||
}
|
||||
|
||||
afterEvaluate {
|
||||
|
|
|
@ -8,7 +8,12 @@
|
|||
xmlns:tools="http://schemas.android.com/tools"
|
||||
package="org.microg.nlp.service">
|
||||
|
||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||
<permission
|
||||
android:name="org.microg.nlp.SERVICE_ADMIN"
|
||||
android:label="Configure UnifiedNlp"
|
||||
android:protectionLevel="privileged|signature" />
|
||||
|
||||
<uses-permission android:name="org.microg.nlp.SERVICE_ADMIN" />
|
||||
|
||||
<application>
|
||||
<service
|
||||
|
|
|
@ -16,6 +16,8 @@ import android.content.pm.Signature
|
|||
import android.os.IBinder
|
||||
import android.os.RemoteException
|
||||
import android.util.Log
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
import java.security.MessageDigest
|
||||
import java.security.NoSuchAlgorithmException
|
||||
|
@ -24,10 +26,10 @@ fun <T> Array<out T>?.isNotNullOrEmpty(): Boolean {
|
|||
return this != null && this.isNotEmpty()
|
||||
}
|
||||
|
||||
abstract class AbstractBackendHelper(private val TAG: String, private val context: Context, val serviceIntent: Intent, val signatureDigest: String?) : ServiceConnection {
|
||||
abstract class AbstractBackendHelper(private val TAG: String, private val context: Context, protected val coroutineScope: CoroutineScope, val serviceIntent: Intent, val signatureDigest: String?) : ServiceConnection {
|
||||
private var bound: Boolean = false
|
||||
|
||||
protected abstract fun close()
|
||||
protected abstract suspend fun close()
|
||||
|
||||
protected abstract fun hasBackend(): Boolean
|
||||
|
||||
|
@ -41,7 +43,7 @@ abstract class AbstractBackendHelper(private val TAG: String, private val contex
|
|||
Log.d(TAG, "Unbound from: $name")
|
||||
}
|
||||
|
||||
fun unbind() {
|
||||
suspend fun unbind() {
|
||||
if (bound) {
|
||||
if (hasBackend()) {
|
||||
try {
|
||||
|
@ -49,7 +51,6 @@ abstract class AbstractBackendHelper(private val TAG: String, private val contex
|
|||
} catch (e: Exception) {
|
||||
Log.w(TAG, e)
|
||||
}
|
||||
|
||||
}
|
||||
try {
|
||||
Log.d(TAG, "Unbinding from: $serviceIntent")
|
||||
|
|
|
@ -0,0 +1,159 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2020, microG Project Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package org.microg.nlp.service
|
||||
|
||||
import android.content.Intent
|
||||
import android.location.Address
|
||||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
import android.os.IBinder
|
||||
import android.os.Looper
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import org.microg.nlp.api.GeocoderBackend
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
|
||||
class AsyncGeocoderBackend(binder: IBinder, name: String = "geocoder-backend-thread") : Thread(name) {
|
||||
private lateinit var looper: Looper
|
||||
private lateinit var handler: Handler
|
||||
private val mutex = Mutex(true)
|
||||
private val backend = GeocoderBackend.Stub.asInterface(binder)
|
||||
|
||||
override fun run() {
|
||||
Looper.prepare()
|
||||
looper = Looper.myLooper()!!
|
||||
handler = Handler(looper)
|
||||
handler.post {
|
||||
mutex.unlock()
|
||||
}
|
||||
Looper.loop()
|
||||
}
|
||||
|
||||
suspend fun open() {
|
||||
start()
|
||||
mutex.withLock {
|
||||
suspendCoroutine<Unit> {
|
||||
handler.post {
|
||||
val result = try {
|
||||
backend.open()
|
||||
Result.success(Unit)
|
||||
} catch (e: Exception) {
|
||||
Result.failure<Unit>(e)
|
||||
}
|
||||
it.resumeWith(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getFromLocation(latitude: Double, longitude: Double, maxResults: Int, locale: String?): List<Address> = mutex.withLock {
|
||||
suspendCoroutine {
|
||||
handler.post {
|
||||
val result = try {
|
||||
Result.success(backend.getFromLocation(latitude, longitude, maxResults, locale))
|
||||
} catch (e: Exception) {
|
||||
Result.failure<List<Address>>(e)
|
||||
}
|
||||
it.resumeWith(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getFromLocationName(locationName: String?, maxResults: Int, lowerLeftLatitude: Double, lowerLeftLongitude: Double, upperRightLatitude: Double, upperRightLongitude: Double, locale: String?): List<Address> = mutex.withLock {
|
||||
suspendCoroutine {
|
||||
handler.post {
|
||||
val result = try {
|
||||
Result.success(backend.getFromLocationName(locationName, maxResults, lowerLeftLatitude, lowerLeftLongitude, upperRightLatitude, upperRightLongitude, locale))
|
||||
} catch (e: Exception) {
|
||||
Result.failure<List<Address>>(e)
|
||||
}
|
||||
it.resumeWith(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun close() {
|
||||
mutex.withLock {
|
||||
suspendCoroutine<Unit> {
|
||||
handler.post {
|
||||
val result = try {
|
||||
backend.close()
|
||||
Result.success(Unit)
|
||||
} catch (e: Exception) {
|
||||
Result.failure<Unit>(e)
|
||||
}
|
||||
it.resumeWith(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
looper.quit()
|
||||
}
|
||||
|
||||
suspend fun getSettingsIntent(): Intent = mutex.withLock {
|
||||
suspendCoroutine {
|
||||
handler.post {
|
||||
val result = try {
|
||||
Result.success(backend.settingsIntent)
|
||||
} catch (e: Exception) {
|
||||
Result.failure<Intent>(e)
|
||||
}
|
||||
it.resumeWith(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getInitIntent(): Intent = mutex.withLock {
|
||||
suspendCoroutine {
|
||||
handler.post {
|
||||
val result = try {
|
||||
Result.success(backend.initIntent)
|
||||
} catch (e: Exception) {
|
||||
Result.failure<Intent>(e)
|
||||
}
|
||||
it.resumeWith(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getAboutIntent(): Intent = mutex.withLock {
|
||||
suspendCoroutine {
|
||||
handler.post {
|
||||
val result = try {
|
||||
Result.success(backend.aboutIntent)
|
||||
} catch (e: Exception) {
|
||||
Result.failure<Intent>(e)
|
||||
}
|
||||
it.resumeWith(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getFromLocationWithOptions(latitude: Double, longitude: Double, maxResults: Int, locale: String?, options: Bundle?): List<Address> = mutex.withLock {
|
||||
suspendCoroutine {
|
||||
handler.post {
|
||||
val result = try {
|
||||
Result.success(backend.getFromLocationWithOptions(latitude, longitude, maxResults, locale, options))
|
||||
} catch (e: Exception) {
|
||||
Result.failure<List<Address>>(e)
|
||||
}
|
||||
it.resumeWith(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getFromLocationNameWithOptions(locationName: String?, maxResults: Int, lowerLeftLatitude: Double, lowerLeftLongitude: Double, upperRightLatitude: Double, upperRightLongitude: Double, locale: String?, options: Bundle?): List<Address> = mutex.withLock {
|
||||
suspendCoroutine {
|
||||
handler.post {
|
||||
val result = try {
|
||||
Result.success(backend.getFromLocationNameWithOptions(locationName, maxResults, lowerLeftLatitude, lowerLeftLongitude, upperRightLatitude, upperRightLongitude, locale, options))
|
||||
} catch (e: Exception) {
|
||||
Result.failure<List<Address>>(e)
|
||||
}
|
||||
it.resumeWith(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,134 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2020, microG Project Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package org.microg.nlp.service
|
||||
|
||||
import android.content.Intent
|
||||
import android.location.Location
|
||||
import android.os.*
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import org.microg.nlp.api.LocationBackend
|
||||
import org.microg.nlp.api.LocationCallback
|
||||
import java.lang.Exception
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
|
||||
class AsyncLocationBackend(binder: IBinder, name: String = "location-backend-thread") : Thread(name) {
|
||||
private lateinit var looper: Looper
|
||||
private lateinit var handler: Handler
|
||||
private val mutex = Mutex(true)
|
||||
private val backend = LocationBackend.Stub.asInterface(binder)
|
||||
|
||||
override fun run() {
|
||||
Looper.prepare()
|
||||
looper = Looper.myLooper()!!
|
||||
handler = Handler(looper)
|
||||
handler.post {
|
||||
mutex.unlock()
|
||||
}
|
||||
Looper.loop()
|
||||
}
|
||||
|
||||
suspend fun updateWithOptions(options: Bundle?): Location = mutex.withLock {
|
||||
suspendCoroutine {
|
||||
handler.post {
|
||||
val result = try {
|
||||
Result.success(backend.updateWithOptions(options))
|
||||
} catch (e: Exception) {
|
||||
Result.failure<Location>(e)
|
||||
}
|
||||
it.resumeWith(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getSettingsIntent(): Intent = mutex.withLock {
|
||||
suspendCoroutine {
|
||||
handler.post {
|
||||
val result = try {
|
||||
Result.success(backend.settingsIntent)
|
||||
} catch (e: Exception) {
|
||||
Result.failure<Intent>(e)
|
||||
}
|
||||
it.resumeWith(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getInitIntent(): Intent = mutex.withLock {
|
||||
suspendCoroutine {
|
||||
handler.post {
|
||||
val result = try {
|
||||
Result.success(backend.initIntent)
|
||||
} catch (e: Exception) {
|
||||
Result.failure<Intent>(e)
|
||||
}
|
||||
it.resumeWith(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun open(callback: LocationCallback) {
|
||||
start()
|
||||
mutex.withLock {
|
||||
suspendCoroutine<Unit> {
|
||||
handler.post {
|
||||
val result = try {
|
||||
backend.open(callback)
|
||||
Result.success(Unit)
|
||||
} catch (e: Exception) {
|
||||
Result.failure<Unit>(e)
|
||||
}
|
||||
it.resumeWith(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getAboutIntent(): Intent = mutex.withLock {
|
||||
suspendCoroutine {
|
||||
handler.post {
|
||||
val result = try {
|
||||
Result.success(backend.aboutIntent)
|
||||
} catch (e: Exception) {
|
||||
Result.failure<Intent>(e)
|
||||
}
|
||||
it.resumeWith(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun update(): Location = mutex.withLock {
|
||||
suspendCoroutine {
|
||||
handler.post {
|
||||
val result = try {
|
||||
Result.success(backend.update())
|
||||
} catch (e: Exception) {
|
||||
Result.failure<Location>(e)
|
||||
}
|
||||
it.resumeWith(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun close() {
|
||||
mutex.withLock {
|
||||
suspendCoroutine<Unit> {
|
||||
handler.post {
|
||||
val result = try {
|
||||
backend.close()
|
||||
Result.success(Unit)
|
||||
} catch (e: Exception) {
|
||||
Result.failure<Unit>(e)
|
||||
}
|
||||
it.resumeWith(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
looper.quit()
|
||||
}
|
||||
}
|
|
@ -12,57 +12,55 @@ import android.location.Address
|
|||
import android.os.IBinder
|
||||
import android.os.RemoteException
|
||||
import android.util.Log
|
||||
import org.microg.nlp.api.GeocoderBackend
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class GeocodeBackendHelper(context: Context, serviceIntent: Intent, signatureDigest: String?) : AbstractBackendHelper(TAG, context, serviceIntent, signatureDigest) {
|
||||
private var backend: GeocoderBackend? = null
|
||||
class GeocodeBackendHelper(context: Context, coroutineScope: CoroutineScope, serviceIntent: Intent, signatureDigest: String?) : AbstractBackendHelper(TAG, context, coroutineScope, serviceIntent, signatureDigest) {
|
||||
private var backend: AsyncGeocoderBackend? = null
|
||||
|
||||
fun getFromLocation(latitude: Double, longitude: Double, maxResults: Int,
|
||||
locale: String): List<Address> {
|
||||
suspend fun getFromLocation(latitude: Double, longitude: Double, maxResults: Int,
|
||||
locale: String): List<Address> {
|
||||
if (backend == null) {
|
||||
Log.d(TAG, "Not (yet) bound.")
|
||||
return emptyList()
|
||||
}
|
||||
try {
|
||||
return backend!!.getFromLocation(latitude, longitude, maxResults, locale) ?: emptyList()
|
||||
return backend!!.getFromLocation(latitude, longitude, maxResults, locale)
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, e)
|
||||
unbind()
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fun getFromLocationName(locationName: String, maxResults: Int,
|
||||
lowerLeftLatitude: Double, lowerLeftLongitude: Double,
|
||||
upperRightLatitude: Double, upperRightLongitude: Double,
|
||||
locale: String): List<Address> {
|
||||
suspend fun getFromLocationName(locationName: String, maxResults: Int,
|
||||
lowerLeftLatitude: Double, lowerLeftLongitude: Double,
|
||||
upperRightLatitude: Double, upperRightLongitude: Double,
|
||||
locale: String): List<Address> {
|
||||
if (backend == null) {
|
||||
Log.d(TAG, "Not (yet) bound.")
|
||||
return emptyList()
|
||||
}
|
||||
try {
|
||||
return backend!!.getFromLocationName(locationName, maxResults, lowerLeftLatitude,
|
||||
lowerLeftLongitude, upperRightLatitude, upperRightLongitude, locale) ?: emptyList()
|
||||
lowerLeftLongitude, upperRightLatitude, upperRightLongitude, locale)
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, e)
|
||||
unbind()
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override fun onServiceConnected(name: ComponentName, service: IBinder) {
|
||||
super.onServiceConnected(name, service)
|
||||
backend = GeocoderBackend.Stub.asInterface(service)
|
||||
if (backend != null) {
|
||||
backend = AsyncGeocoderBackend(service, name.toShortString() + "-geocoder-backend")
|
||||
coroutineScope.launch {
|
||||
try {
|
||||
backend!!.open()
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, e)
|
||||
unbind()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -72,7 +70,7 @@ class GeocodeBackendHelper(context: Context, serviceIntent: Intent, signatureDig
|
|||
}
|
||||
|
||||
@Throws(RemoteException::class)
|
||||
public override fun close() {
|
||||
public override suspend fun close() {
|
||||
backend!!.close()
|
||||
}
|
||||
|
||||
|
|
|
@ -8,17 +8,14 @@ package org.microg.nlp.service
|
|||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.location.Address
|
||||
import kotlinx.coroutines.launch
|
||||
import org.microg.nlp.api.Constants.ACTION_GEOCODER_BACKEND
|
||||
import java.util.ArrayList
|
||||
|
||||
class GeocodeFuser(private val context: Context) {
|
||||
class GeocodeFuser(private val context: Context, private val root: UnifiedLocationServiceRoot) {
|
||||
private val backendHelpers = ArrayList<GeocodeBackendHelper>()
|
||||
|
||||
init {
|
||||
reset()
|
||||
}
|
||||
|
||||
fun reset() {
|
||||
suspend fun reset() {
|
||||
unbind()
|
||||
backendHelpers.clear()
|
||||
for (backend in Preferences(context).geocoderBackends) {
|
||||
|
@ -27,7 +24,7 @@ class GeocodeFuser(private val context: Context) {
|
|||
val intent = Intent(ACTION_GEOCODER_BACKEND)
|
||||
intent.setPackage(parts[0])
|
||||
intent.setClassName(parts[0], parts[1])
|
||||
backendHelpers.add(GeocodeBackendHelper(context, intent, if (parts.size >= 3) parts[2] else null))
|
||||
backendHelpers.add(GeocodeBackendHelper(context, root.coroutineScope, intent, if (parts.size >= 3) parts[2] else null))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -38,18 +35,18 @@ class GeocodeFuser(private val context: Context) {
|
|||
}
|
||||
}
|
||||
|
||||
fun unbind() {
|
||||
suspend fun unbind() {
|
||||
for (backendHelper in backendHelpers) {
|
||||
backendHelper.unbind()
|
||||
}
|
||||
}
|
||||
|
||||
fun destroy() {
|
||||
suspend fun destroy() {
|
||||
unbind()
|
||||
backendHelpers.clear()
|
||||
}
|
||||
|
||||
fun getFromLocation(latitude: Double, longitude: Double, maxResults: Int, locale: String): List<Address>? {
|
||||
suspend fun getFromLocation(latitude: Double, longitude: Double, maxResults: Int, locale: String): List<Address>? {
|
||||
if (backendHelpers.isEmpty())
|
||||
return null
|
||||
val result = ArrayList<Address>()
|
||||
|
@ -60,7 +57,7 @@ class GeocodeFuser(private val context: Context) {
|
|||
return result
|
||||
}
|
||||
|
||||
fun getFromLocationName(locationName: String, maxResults: Int, lowerLeftLatitude: Double, lowerLeftLongitude: Double, upperRightLatitude: Double, upperRightLongitude: Double, locale: String): List<Address>? {
|
||||
suspend fun getFromLocationName(locationName: String, maxResults: Int, lowerLeftLatitude: Double, lowerLeftLongitude: Double, upperRightLatitude: Double, upperRightLongitude: Double, locale: String): List<Address>? {
|
||||
if (backendHelpers.isEmpty())
|
||||
return null
|
||||
val result = ArrayList<Address>()
|
||||
|
|
|
@ -10,17 +10,17 @@ import android.content.ComponentName
|
|||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.location.Location
|
||||
import android.net.wifi.WifiManager
|
||||
import android.os.*
|
||||
import android.util.Log
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
import org.microg.nlp.api.Constants.LOCATION_EXTRA_BACKEND_COMPONENT
|
||||
import org.microg.nlp.api.Constants.LOCATION_EXTRA_BACKEND_PROVIDER
|
||||
import org.microg.nlp.api.LocationBackend
|
||||
import org.microg.nlp.api.LocationCallback
|
||||
|
||||
class LocationBackendHelper(context: Context, private val locationFuser: LocationFuser, serviceIntent: Intent, signatureDigest: String?) : AbstractBackendHelper(TAG, context, serviceIntent, signatureDigest) {
|
||||
class LocationBackendHelper(context: Context, private val locationFuser: LocationFuser, coroutineScope: CoroutineScope, serviceIntent: Intent, signatureDigest: String?) : AbstractBackendHelper(TAG, context, coroutineScope, serviceIntent, signatureDigest) {
|
||||
private val callback = Callback()
|
||||
private var backend: LocationBackend? = null
|
||||
private var backend: AsyncLocationBackend? = null
|
||||
private var updateWaiting: Boolean = false
|
||||
var lastLocation: Location? = null
|
||||
private set(location) {
|
||||
|
@ -52,7 +52,7 @@ class LocationBackendHelper(context: Context, private val locationFuser: Locatio
|
|||
* @return The location reported by the backend. This may be null if a backend cannot determine its
|
||||
* location, or if it is going to return a location asynchronously.
|
||||
*/
|
||||
fun update(): Location? {
|
||||
suspend fun update(): Location? {
|
||||
var result: Location? = null
|
||||
if (backend == null) {
|
||||
Log.d(TAG, "Not (yet) bound.")
|
||||
|
@ -87,7 +87,7 @@ class LocationBackendHelper(context: Context, private val locationFuser: Locatio
|
|||
}
|
||||
|
||||
@Throws(RemoteException::class)
|
||||
public override fun close() {
|
||||
public override suspend fun close() {
|
||||
Log.d(TAG, "Calling close")
|
||||
backend!!.close()
|
||||
}
|
||||
|
@ -98,8 +98,8 @@ class LocationBackendHelper(context: Context, private val locationFuser: Locatio
|
|||
|
||||
override fun onServiceConnected(name: ComponentName, service: IBinder) {
|
||||
super.onServiceConnected(name, service)
|
||||
backend = LocationBackend.Stub.asInterface(service)
|
||||
if (backend != null) {
|
||||
backend = AsyncLocationBackend(service, name.toShortString() + "-location-backend")
|
||||
coroutineScope.launch {
|
||||
try {
|
||||
Log.d(TAG, "Calling open")
|
||||
backend!!.open(callback)
|
||||
|
@ -110,7 +110,6 @@ class LocationBackendHelper(context: Context, private val locationFuser: Locatio
|
|||
Log.w(TAG, e)
|
||||
unbind()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -10,6 +10,8 @@ import android.content.Intent
|
|||
import android.location.Location
|
||||
import android.location.LocationManager
|
||||
import android.util.Log
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
import java.util.ArrayList
|
||||
import java.util.Collections
|
||||
|
@ -24,11 +26,7 @@ class LocationFuser(private val context: Context, private val root: UnifiedLocat
|
|||
private var fusing = false
|
||||
private var lastLocationReportTime: Long = 0
|
||||
|
||||
init {
|
||||
reset()
|
||||
}
|
||||
|
||||
fun reset() {
|
||||
suspend fun reset() {
|
||||
unbind()
|
||||
backendHelpers.clear()
|
||||
lastLocationReportTime = 0
|
||||
|
@ -39,12 +37,12 @@ class LocationFuser(private val context: Context, private val root: UnifiedLocat
|
|||
val intent = Intent(ACTION_LOCATION_BACKEND)
|
||||
intent.setPackage(parts[0])
|
||||
intent.setClassName(parts[0], parts[1])
|
||||
backendHelpers.add(LocationBackendHelper(context, this, intent, if (parts.size >= 3) parts[2] else null))
|
||||
backendHelpers.add(LocationBackendHelper(context, this, root.coroutineScope, intent, if (parts.size >= 3) parts[2] else null))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun unbind() {
|
||||
suspend fun unbind() {
|
||||
for (handler in backendHelpers) {
|
||||
handler.unbind()
|
||||
}
|
||||
|
@ -57,12 +55,12 @@ class LocationFuser(private val context: Context, private val root: UnifiedLocat
|
|||
}
|
||||
}
|
||||
|
||||
fun destroy() {
|
||||
suspend fun destroy() {
|
||||
unbind()
|
||||
backendHelpers.clear()
|
||||
}
|
||||
|
||||
fun update() {
|
||||
suspend fun update() {
|
||||
var hasUpdates = false
|
||||
fusing = true
|
||||
for (handler in backendHelpers) {
|
||||
|
|
|
@ -9,6 +9,10 @@ import android.app.Service
|
|||
import android.content.Intent
|
||||
import android.os.IBinder
|
||||
import android.util.Log
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
|
||||
class UnifiedLocationServiceEntryPoint : Service() {
|
||||
private var root: UnifiedLocationServiceRoot? = null
|
||||
|
@ -31,7 +35,7 @@ class UnifiedLocationServiceEntryPoint : Service() {
|
|||
Log.d(TAG, "onBind: $intent")
|
||||
synchronized(this) {
|
||||
if (root == null) {
|
||||
root = UnifiedLocationServiceRoot(this)
|
||||
root = UnifiedLocationServiceRoot(this, CoroutineScope(Dispatchers.IO + Job()))
|
||||
}
|
||||
return root!!.asBinder()
|
||||
}
|
||||
|
|
|
@ -5,19 +5,44 @@
|
|||
|
||||
package org.microg.nlp.service
|
||||
|
||||
import android.app.ActivityManager
|
||||
import android.content.Context
|
||||
import android.content.pm.PackageManager.PERMISSION_GRANTED
|
||||
import android.location.Location
|
||||
import android.os.Binder.getCallingPid
|
||||
import android.os.Binder.getCallingUid
|
||||
import android.os.Bundle
|
||||
import android.os.RemoteException
|
||||
import android.util.Log
|
||||
|
||||
import android.os.Binder.getCallingUid
|
||||
import org.microg.nlp.client.UnifiedLocationClient
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.launch
|
||||
import org.microg.nlp.client.UnifiedLocationClient.Companion.KEY_FORCE_NEXT_UPDATE
|
||||
import org.microg.nlp.client.UnifiedLocationClient.Companion.KEY_OP_PACKAGE_NAME
|
||||
import org.microg.nlp.client.UnifiedLocationClient.Companion.PERMISSION_SERVICE_ADMIN
|
||||
|
||||
class UnifiedLocationServiceInstance(private val root: UnifiedLocationServiceRoot) : UnifiedLocationService.Default() {
|
||||
private var callback: LocationCallback? = null
|
||||
private var interval: Long = 0
|
||||
private var singleUpdatePending = false
|
||||
private val callingPackage = root.context.packageManager.getNameForUid(getCallingUid())
|
||||
val callingPackage = root.context.getCallingPackage()
|
||||
|
||||
private var opPackage: String? = null
|
||||
private val debugPackageString: String?
|
||||
get() {
|
||||
if (opPackage == callingPackage || opPackage == null) return callingPackage
|
||||
return "$callingPackage for $opPackage"
|
||||
}
|
||||
|
||||
private fun Context.getCallingPackage(): String? {
|
||||
val manager = getSystemService(Context.ACTIVITY_SERVICE) as? ActivityManager
|
||||
val callingPid = getCallingPid()
|
||||
if (manager != null && callingPid > 0) {
|
||||
manager.runningAppProcesses.find { it.pid == callingPid }?.pkgList?.singleOrNull()?.let { return it }
|
||||
}
|
||||
return packageManager.getPackagesForUid(getCallingUid())?.singleOrNull()
|
||||
}
|
||||
|
||||
fun reportLocation(location: Location) {
|
||||
try {
|
||||
|
@ -31,7 +56,6 @@ class UnifiedLocationServiceInstance(private val root: UnifiedLocationServiceRoo
|
|||
} catch (e: RemoteException) {
|
||||
root.onDisconnected(this)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fun getInterval(): Long {
|
||||
|
@ -39,27 +63,37 @@ class UnifiedLocationServiceInstance(private val root: UnifiedLocationServiceRoo
|
|||
return if (singleUpdatePending) UnifiedLocationServiceRoot.MIN_LOCATION_INTERVAL else interval
|
||||
}
|
||||
|
||||
@Throws(RemoteException::class)
|
||||
override fun registerLocationCallback(callback: LocationCallback, options: Bundle) {
|
||||
if (root.context.checkCallingPermission(PERMISSION_SERVICE_ADMIN) == PERMISSION_GRANTED && options.containsKey(KEY_OP_PACKAGE_NAME)) {
|
||||
opPackage = options.getString(KEY_OP_PACKAGE_NAME)
|
||||
}
|
||||
Log.d(TAG, "registerLocationCallback[$callingPackage]")
|
||||
this.callback = callback
|
||||
}
|
||||
|
||||
override fun setUpdateInterval(interval: Long, options: Bundle) {
|
||||
Log.d(TAG, "setUpdateInterval[$callingPackage] interval: $interval")
|
||||
if (root.context.checkCallingPermission(PERMISSION_SERVICE_ADMIN) == PERMISSION_GRANTED && options.containsKey(KEY_OP_PACKAGE_NAME)) {
|
||||
opPackage = options.getString(KEY_OP_PACKAGE_NAME)
|
||||
}
|
||||
Log.d(TAG, "setUpdateInterval[$debugPackageString] interval: $interval")
|
||||
this.interval = interval
|
||||
root.updateLocationInterval()
|
||||
}
|
||||
|
||||
override fun requestSingleUpdate(options: Bundle) {
|
||||
if (root.context.checkCallingPermission(PERMISSION_SERVICE_ADMIN) == PERMISSION_GRANTED && options.containsKey(KEY_OP_PACKAGE_NAME)) {
|
||||
opPackage = options.getString(KEY_OP_PACKAGE_NAME)
|
||||
}
|
||||
val lastLocation = root.lastReportedLocation
|
||||
if (lastLocation == null || lastLocation.time < System.currentTimeMillis() - UnifiedLocationServiceRoot.MAX_LOCATION_AGE || options.getBoolean(UnifiedLocationClient.KEY_FORCE_NEXT_UPDATE, false)) {
|
||||
Log.d(TAG, "requestSingleUpdate[$callingPackage] requesting new location")
|
||||
if (lastLocation == null || lastLocation.time < System.currentTimeMillis() - UnifiedLocationServiceRoot.MAX_LOCATION_AGE || options.getBoolean(KEY_FORCE_NEXT_UPDATE, false)) {
|
||||
Log.d(TAG, "requestSingleUpdate[$debugPackageString] requesting new location")
|
||||
singleUpdatePending = true
|
||||
root.locationFuser.update()
|
||||
root.updateLocationInterval()
|
||||
} else {
|
||||
Log.d(TAG, "requestSingleUpdate[$callingPackage] using last location ")
|
||||
root.coroutineScope.launch {
|
||||
root.locationFuser.update()
|
||||
root.updateLocationInterval()
|
||||
}
|
||||
} else if (callback != null) {
|
||||
Log.d(TAG, "requestSingleUpdate[$debugPackageString] using last location ")
|
||||
try {
|
||||
this.callback!!.onLocationUpdate(lastLocation)
|
||||
} catch (e: RemoteException) {
|
||||
|
|
|
@ -5,13 +5,16 @@
|
|||
|
||||
package org.microg.nlp.service
|
||||
|
||||
import android.Manifest
|
||||
import android.Manifest.permission.ACCESS_COARSE_LOCATION
|
||||
import android.content.Context
|
||||
import android.location.Address
|
||||
import android.location.Location
|
||||
import android.os.Binder
|
||||
import android.os.Bundle
|
||||
import android.os.Process.myUid
|
||||
import android.util.Log
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
import org.microg.nlp.client.UnifiedLocationClient.Companion.PERMISSION_SERVICE_ADMIN
|
||||
import java.util.*
|
||||
import java.util.concurrent.LinkedBlockingQueue
|
||||
import java.util.concurrent.ThreadPoolExecutor
|
||||
|
@ -21,14 +24,14 @@ import kotlin.collections.set
|
|||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
|
||||
class UnifiedLocationServiceRoot(private val service: UnifiedLocationServiceEntryPoint) : UnifiedLocationService.Stub() {
|
||||
class UnifiedLocationServiceRoot(private val service: UnifiedLocationServiceEntryPoint, val coroutineScope: CoroutineScope) : UnifiedLocationService.Stub() {
|
||||
private val instances = HashMap<Int, UnifiedLocationServiceInstance>()
|
||||
private val geocoderThreads: ThreadPoolExecutor = ThreadPoolExecutor(1, Runtime.getRuntime().availableProcessors(), 1, TimeUnit.SECONDS, LinkedBlockingQueue())
|
||||
private val timer: Timer = Timer("location-requests")
|
||||
private var timerTask: TimerTask? = null
|
||||
private var lastTime: Long = 0
|
||||
val locationFuser: LocationFuser = LocationFuser(service, this)
|
||||
val geocodeFuser: GeocodeFuser = GeocodeFuser(service)
|
||||
val geocodeFuser: GeocodeFuser = GeocodeFuser(service, this)
|
||||
var lastReportedLocation: Location? = null
|
||||
private set
|
||||
private var interval: Long = 0
|
||||
|
@ -37,12 +40,7 @@ class UnifiedLocationServiceRoot(private val service: UnifiedLocationServiceEntr
|
|||
get() = service
|
||||
|
||||
init {
|
||||
try {
|
||||
locationFuser.bind()
|
||||
geocodeFuser.bind()
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, "Failed loading preferences", e)
|
||||
}
|
||||
coroutineScope.launch { reset() }
|
||||
}
|
||||
|
||||
val instance: UnifiedLocationServiceInstance
|
||||
|
@ -73,17 +71,22 @@ class UnifiedLocationServiceRoot(private val service: UnifiedLocationServiceEntr
|
|||
}
|
||||
|
||||
fun destroy() {
|
||||
locationFuser.destroy()
|
||||
geocodeFuser.destroy()
|
||||
coroutineScope.launch {
|
||||
locationFuser.destroy()
|
||||
geocodeFuser.destroy()
|
||||
}
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun updateLocationInterval() {
|
||||
var interval: Long = Long.MAX_VALUE
|
||||
val sb = StringBuilder()
|
||||
for (instance in ArrayList(instances.values)) {
|
||||
val implInterval = instance.getInterval()
|
||||
if (implInterval <= 0) continue
|
||||
interval = min(interval, implInterval)
|
||||
if (sb.isNotEmpty()) sb.append(", ")
|
||||
sb.append("${instance.callingPackage}:${implInterval}ms")
|
||||
}
|
||||
interval = max(interval, MIN_LOCATION_INTERVAL)
|
||||
|
||||
|
@ -94,11 +97,14 @@ class UnifiedLocationServiceRoot(private val service: UnifiedLocationServiceEntr
|
|||
timerTask = null
|
||||
|
||||
if (interval < Long.MAX_VALUE) {
|
||||
Log.d(TAG, "Set merged location interval to $interval")
|
||||
Log.d(TAG, "Set merged location interval to $interval ($sb)")
|
||||
|
||||
val timerTask = object : TimerTask() {
|
||||
override fun run() {
|
||||
lastTime = System.currentTimeMillis()
|
||||
locationFuser.update()
|
||||
coroutineScope.launch {
|
||||
lastTime = System.currentTimeMillis()
|
||||
locationFuser.update()
|
||||
}
|
||||
}
|
||||
}
|
||||
timer.scheduleAtFixedRate(timerTask, min(interval, max(0, interval - (System.currentTimeMillis() - lastTime))), interval)
|
||||
|
@ -109,7 +115,11 @@ class UnifiedLocationServiceRoot(private val service: UnifiedLocationServiceEntr
|
|||
}
|
||||
|
||||
private fun checkLocationPermission() {
|
||||
service.enforceCallingPermission(Manifest.permission.ACCESS_COARSE_LOCATION, "coarse location permission required")
|
||||
service.enforceCallingPermission(ACCESS_COARSE_LOCATION, "coarse location permission required")
|
||||
}
|
||||
|
||||
private fun checkAdminPermission() {
|
||||
service.enforceCallingPermission(PERMISSION_SERVICE_ADMIN, "coarse location permission required")
|
||||
}
|
||||
|
||||
override fun registerLocationCallback(callback: LocationCallback, options: Bundle) {
|
||||
|
@ -125,14 +135,34 @@ class UnifiedLocationServiceRoot(private val service: UnifiedLocationServiceEntr
|
|||
}
|
||||
|
||||
override fun getFromLocationWithOptions(latitude: Double, longitude: Double, maxResults: Int, locale: String, options: Bundle, callback: AddressCallback) {
|
||||
geocoderThreads.execute {
|
||||
callback.onResult(geocodeFuser.getFromLocation(latitude, longitude, maxResults, locale))
|
||||
coroutineScope.launch {
|
||||
val res = try {
|
||||
geocodeFuser.getFromLocation(latitude, longitude, maxResults, locale).orEmpty()
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, e)
|
||||
emptyList<Address>()
|
||||
}
|
||||
try {
|
||||
callback.onResult(res)
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getFromLocationNameWithOptions(locationName: String, maxResults: Int, lowerLeftLatitude: Double, lowerLeftLongitude: Double, upperRightLatitude: Double, upperRightLongitude: Double, locale: String, options: Bundle, callback: AddressCallback) {
|
||||
geocoderThreads.execute {
|
||||
callback.onResult(geocodeFuser.getFromLocationName(locationName, maxResults, lowerLeftLatitude, lowerLeftLongitude, upperRightLatitude, upperRightLongitude, locale))
|
||||
coroutineScope.launch {
|
||||
val res = try {
|
||||
geocodeFuser.getFromLocationName(locationName, maxResults, lowerLeftLatitude, lowerLeftLongitude, upperRightLatitude, upperRightLongitude, locale).orEmpty()
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, e)
|
||||
emptyList<Address>()
|
||||
}
|
||||
try {
|
||||
callback.onResult(res)
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -141,7 +171,7 @@ class UnifiedLocationServiceRoot(private val service: UnifiedLocationServiceEntr
|
|||
}
|
||||
|
||||
override fun setLocationBackends(backends: Array<String>) {
|
||||
if (Binder.getCallingUid() != myUid()) throw SecurityException("Only allowed from same UID")
|
||||
checkAdminPermission();
|
||||
Preferences(service).locationBackends = backends
|
||||
reloadPreferences()
|
||||
}
|
||||
|
@ -151,14 +181,16 @@ class UnifiedLocationServiceRoot(private val service: UnifiedLocationServiceEntr
|
|||
}
|
||||
|
||||
override fun setGeocoderBackends(backends: Array<String>) {
|
||||
if (Binder.getCallingUid() != myUid()) throw SecurityException("Only allowed from same UID")
|
||||
checkAdminPermission();
|
||||
Preferences(service).geocoderBackends = backends
|
||||
reloadPreferences()
|
||||
}
|
||||
|
||||
override fun reloadPreferences() {
|
||||
if (Binder.getCallingUid() != myUid()) throw SecurityException("Only allowed from same UID")
|
||||
reset()
|
||||
checkAdminPermission();
|
||||
coroutineScope.launch {
|
||||
reset()
|
||||
}
|
||||
}
|
||||
|
||||
override fun getLastLocation(): Location? {
|
||||
|
@ -172,7 +204,7 @@ class UnifiedLocationServiceRoot(private val service: UnifiedLocationServiceEntr
|
|||
}
|
||||
|
||||
@Synchronized
|
||||
fun reset() {
|
||||
suspend fun reset() {
|
||||
locationFuser.reset()
|
||||
locationFuser.bind()
|
||||
geocodeFuser.reset()
|
||||
|
|
|
@ -32,18 +32,35 @@ android {
|
|||
sourceCompatibility = 1.8
|
||||
targetCompatibility = 1.8
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = "1.8"
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation project(':api')
|
||||
implementation project(':client')
|
||||
|
||||
// Kotlin
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlinVersion"
|
||||
|
||||
// AndroidX UI
|
||||
implementation "androidx.appcompat:appcompat:$appcompatVersion"
|
||||
implementation "androidx.fragment:fragment:$fragmentVersion"
|
||||
implementation "androidx.recyclerview:recyclerview:$recyclerviewVersion"
|
||||
|
||||
// Kotlin coroutine for android
|
||||
implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycleVersion"
|
||||
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycleVersion"
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlinVersion"
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutineVersion"
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutineVersion"
|
||||
|
||||
// Navigation
|
||||
implementation "androidx.navigation:navigation-fragment:$navigationVersion"
|
||||
implementation "androidx.navigation:navigation-ui:$navigationVersion"
|
||||
implementation "androidx.navigation:navigation-fragment-ktx:$navigationVersion"
|
||||
implementation "androidx.navigation:navigation-ui-ktx:$navigationVersion"
|
||||
}
|
||||
|
||||
afterEvaluate {
|
||||
|
|
|
@ -4,7 +4,9 @@
|
|||
~ SPDX-License-Identifier: Apache-2.0
|
||||
-->
|
||||
|
||||
<manifest package="org.microg.nlp.ui">
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="org.microg.nlp.ui">
|
||||
|
||||
<uses-permission android:name="org.microg.nlp.SERVICE_ADMIN" />
|
||||
<application />
|
||||
</manifest>
|
||||
|
|
|
@ -88,9 +88,15 @@ class BackendDetailsFragment : Fragment(R.layout.backend_details) {
|
|||
override fun onResume() {
|
||||
super.onResume()
|
||||
binding.switchWidget.trackTintList = switchBarTrackTintColor
|
||||
UnifiedLocationClient[requireContext()].ref()
|
||||
lifecycleScope.launchWhenStarted { initContent(createBackendInfo()) }
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
UnifiedLocationClient[requireContext()].unref()
|
||||
}
|
||||
|
||||
private suspend fun initContent(entry: BackendInfo?) {
|
||||
binding.entry = entry
|
||||
binding.executePendingBindings()
|
||||
|
@ -185,26 +191,21 @@ class BackendDetailsFragment : Fragment(R.layout.backend_details) {
|
|||
}
|
||||
|
||||
private suspend fun createBackendInfo(): BackendInfo? {
|
||||
val activity = activity ?: return null
|
||||
val intent = activity.intent ?: return null
|
||||
val type = BackendType.values().find { it.name == intent.getStringExtra(EXTRA_TYPE) }
|
||||
val type = BackendType.values().find { it.name == arguments?.getString("type") }
|
||||
?: return null
|
||||
val packageName = intent.getStringExtra(EXTRA_PACKAGE) ?: return null
|
||||
val name = intent.getStringExtra(EXTRA_NAME) ?: return null
|
||||
val serviceInfo = activity.packageManager.getServiceInfo(ComponentName(packageName, name), GET_META_DATA)
|
||||
val packageName = arguments?.getString("package") ?: return null
|
||||
val name = arguments?.getString("name") ?: return null
|
||||
val serviceInfo = context?.packageManager?.getServiceInfo(ComponentName(packageName, name), GET_META_DATA)
|
||||
?: return null
|
||||
val enabledBackends = when (type) {
|
||||
GEOCODER -> UnifiedLocationClient[activity].getGeocoderBackends()
|
||||
LOCATION -> UnifiedLocationClient[activity].getLocationBackends()
|
||||
GEOCODER -> UnifiedLocationClient[requireContext()].getGeocoderBackends()
|
||||
LOCATION -> UnifiedLocationClient[requireContext()].getLocationBackends()
|
||||
}
|
||||
return BackendInfo(activity, serviceInfo, type, lifecycleScope, enabledBackends)
|
||||
return BackendInfo(requireContext(), serviceInfo, type, lifecycleScope, enabledBackends)
|
||||
}
|
||||
|
||||
companion object {
|
||||
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
|
||||
}
|
||||
|
|
|
@ -7,23 +7,21 @@ package org.microg.nlp.ui
|
|||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.content.pm.PackageManager.GET_META_DATA
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageView
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.recyclerview.widget.DefaultItemAnimator
|
||||
import androidx.navigation.fragment.FragmentNavigatorExtras
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import kotlinx.coroutines.delay
|
||||
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.client.UnifiedLocationClient
|
||||
import org.microg.nlp.ui.BackendDetailsFragment.Companion.EXTRA_NAME
|
||||
import org.microg.nlp.ui.BackendDetailsFragment.Companion.EXTRA_PACKAGE
|
||||
import org.microg.nlp.ui.BackendDetailsFragment.Companion.EXTRA_TYPE
|
||||
import org.microg.nlp.ui.databinding.BackendListBinding
|
||||
import org.microg.nlp.ui.databinding.BackendListEntryBinding
|
||||
|
||||
|
@ -39,17 +37,23 @@ class BackendListFragment : Fragment(R.layout.backend_list) {
|
|||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
UnifiedLocationClient[requireContext()].ref()
|
||||
lifecycleScope.launchWhenStarted { updateAdapters() }
|
||||
}
|
||||
|
||||
fun onBackendSelected(entry: BackendInfo?) {
|
||||
if (entry == null) return
|
||||
val intent = Intent(BackendDetailsFragment.ACTION)
|
||||
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)
|
||||
startActivity(intent)
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
UnifiedLocationClient[requireContext()].unref()
|
||||
}
|
||||
|
||||
fun onBackendSelected(tag: Any?) {
|
||||
val binding = tag as? BackendListEntryBinding ?: return
|
||||
val entry = binding.entry ?: return
|
||||
findNavController().navigate(R.id.openDetails, bundleOf(
|
||||
"type" to entry.type.name,
|
||||
"package" to entry.serviceInfo.packageName,
|
||||
"name" to entry.serviceInfo.name
|
||||
))
|
||||
}
|
||||
|
||||
private suspend fun updateAdapters() {
|
||||
|
@ -69,6 +73,7 @@ class BackendSettingsLineViewHolder(val binding: BackendListEntryBinding) : Recy
|
|||
fun bind(fragment: BackendListFragment, entry: BackendInfo?) {
|
||||
binding.fragment = fragment
|
||||
binding.entry = entry
|
||||
binding.tag = binding
|
||||
binding.executePendingBindings()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2020, microG Project Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
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.setupActionBarWithNavController
|
||||
import androidx.navigation.ui.setupWithNavController
|
||||
|
||||
class BackendSettingsActivity : AppCompatActivity() {
|
||||
private lateinit var appBarConfiguration: AppBarConfiguration
|
||||
|
||||
private val navController: NavController
|
||||
get() = (supportFragmentManager.findFragmentById(R.id.navhost) as NavHostFragment).navController
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.backend_settings_activity)
|
||||
|
||||
appBarConfiguration = AppBarConfiguration(navController.graph)
|
||||
setupActionBarWithNavController(navController, appBarConfiguration)
|
||||
}
|
||||
|
||||
override fun onSupportNavigateUp(): Boolean {
|
||||
return navController.navigateUp() || super.onSupportNavigateUp()
|
||||
}
|
||||
}
|
|
@ -48,8 +48,8 @@
|
|||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:onClick='@{() -> fragment.onAppClicked(entry)}'
|
||||
android:gravity="center_horizontal"
|
||||
android:onClick='@{() -> fragment.onAppClicked(entry)}'
|
||||
android:orientation="vertical">
|
||||
|
||||
<ImageView
|
||||
|
|
|
@ -26,10 +26,10 @@
|
|||
android:layout_marginTop="16dp"
|
||||
android:clipToPadding="false"
|
||||
android:gravity="start|center_vertical"
|
||||
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
|
||||
android:paddingLeft="?android:attr/listPreferredItemPaddingLeft"
|
||||
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
|
||||
android:paddingRight="?android:attr/listPreferredItemPaddingRight">
|
||||
android:paddingStart="?attr/listPreferredItemPaddingStart"
|
||||
android:paddingLeft="?attr/listPreferredItemPaddingLeft"
|
||||
android:paddingEnd="?attr/listPreferredItemPaddingEnd"
|
||||
android:paddingRight="?attr/listPreferredItemPaddingRight">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
|
@ -60,7 +60,7 @@
|
|||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:background="?android:attr/dividerHorizontal" />
|
||||
android:background="?attr/dividerHorizontal" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
|
@ -68,15 +68,16 @@
|
|||
android:layout_marginTop="16dp"
|
||||
android:clipToPadding="false"
|
||||
android:gravity="start|center_vertical"
|
||||
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
|
||||
android:paddingLeft="?android:attr/listPreferredItemPaddingLeft"
|
||||
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
|
||||
android:paddingRight="?android:attr/listPreferredItemPaddingRight">
|
||||
android:paddingStart="?attr/listPreferredItemPaddingStart"
|
||||
android:paddingLeft="?attr/listPreferredItemPaddingLeft"
|
||||
android:paddingEnd="?attr/listPreferredItemPaddingEnd"
|
||||
android:paddingRight="?attr/listPreferredItemPaddingRight">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="marquee"
|
||||
android:letterSpacing="0.073"
|
||||
android:paddingStart="56dp"
|
||||
android:paddingLeft="56dp"
|
||||
android:paddingTop="8dp"
|
||||
|
|
|
@ -17,6 +17,10 @@
|
|||
<variable
|
||||
name="entry"
|
||||
type="org.microg.nlp.ui.BackendInfo" />
|
||||
|
||||
<variable
|
||||
name="tag"
|
||||
type="Object" />
|
||||
</data>
|
||||
|
||||
<LinearLayout
|
||||
|
@ -24,23 +28,23 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:baselineAligned="false"
|
||||
android:gravity="center_vertical"
|
||||
android:minHeight="?android:attr/listPreferredItemHeightSmall"
|
||||
android:minHeight="?attr/listPreferredItemHeightSmall"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:clickable='@{entry != null}'
|
||||
android:clipToPadding="false"
|
||||
android:focusable="true"
|
||||
android:gravity="start|center_vertical"
|
||||
android:onClick="@{() -> fragment.onBackendSelected(entry)}"
|
||||
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
|
||||
android:paddingLeft="?android:attr/listPreferredItemPaddingLeft"
|
||||
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
|
||||
android:paddingRight="?android:attr/listPreferredItemPaddingRight">
|
||||
android:onClick="@{() -> fragment.onBackendSelected(tag)}"
|
||||
android:paddingStart="?attr/listPreferredItemPaddingStart"
|
||||
android:paddingLeft="?attr/listPreferredItemPaddingLeft"
|
||||
android:paddingEnd="?attr/listPreferredItemPaddingEnd"
|
||||
android:paddingRight="?attr/listPreferredItemPaddingRight">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/icon_frame"
|
||||
|
@ -74,7 +78,7 @@
|
|||
android:ellipsize="marquee"
|
||||
android:singleLine="true"
|
||||
android:text='@{entry.name ?? "No provider installed"}'
|
||||
android:textAppearance="?android:attr/textAppearanceListItem"
|
||||
android:textAppearance="?attr/textAppearanceListItem"
|
||||
tools:text="Mozilla Location Service" />
|
||||
</RelativeLayout>
|
||||
</LinearLayout>
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ SPDX-FileCopyrightText: 2020, microG Project Team
|
||||
~ SPDX-License-Identifier: Apache-2.0
|
||||
-->
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<androidx.fragment.app.FragmentContainerView
|
||||
android:id="@+id/navhost"
|
||||
android:name="androidx.navigation.fragment.NavHostFragment"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:defaultNavHost="true"
|
||||
app:navGraph="@navigation/nav_unlp" />
|
||||
</ScrollView>
|
|
@ -0,0 +1,37 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ SPDX-FileCopyrightText: 2020, microG Project Team
|
||||
~ SPDX-License-Identifier: Apache-2.0
|
||||
-->
|
||||
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/nav_unlp"
|
||||
app:startDestination="@id/backendListFragment">
|
||||
|
||||
<fragment
|
||||
android:id="@+id/backendListFragment"
|
||||
android:name="org.microg.nlp.ui.BackendListFragment"
|
||||
android:label="Location modules">
|
||||
<action
|
||||
android:id="@+id/openDetails"
|
||||
app:destination="@id/backendDetailsFragment"
|
||||
app:enterAnim="@anim/fragment_open_enter"
|
||||
app:exitAnim="@anim/fragment_close_exit"
|
||||
app:popEnterAnim="@anim/fragment_open_enter"
|
||||
app:popExitAnim="@anim/fragment_close_exit" />
|
||||
</fragment>
|
||||
<fragment
|
||||
android:id="@+id/backendDetailsFragment"
|
||||
android:name="org.microg.nlp.ui.BackendDetailsFragment"
|
||||
android:label="Location module details">
|
||||
<argument
|
||||
android:name="type"
|
||||
app:argType="string" />
|
||||
<argument
|
||||
android:name="package"
|
||||
app:argType="string" />
|
||||
<argument
|
||||
android:name="name"
|
||||
app:argType="string" />
|
||||
</fragment>
|
||||
</navigation>
|
Loading…
Reference in New Issue