Adjust code for new service API
This commit is contained in:
parent
d092182576
commit
3845b43f07
24 changed files with 863 additions and 380 deletions
|
@ -5,7 +5,6 @@
|
|||
|
||||
apply plugin: 'com.android.library'
|
||||
apply plugin: 'kotlin-android'
|
||||
apply plugin: 'kotlin-android-extensions'
|
||||
apply plugin: 'maven-publish'
|
||||
apply plugin: 'signing'
|
||||
|
||||
|
@ -34,6 +33,9 @@ apply from: '../gradle/publish.gradle'
|
|||
description = 'UnifiedNlp client library'
|
||||
|
||||
dependencies {
|
||||
api project(":service-api")
|
||||
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlinVersion"
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutineVersion"
|
||||
implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycleVersion"
|
||||
}
|
||||
|
|
166
client/src/main/kotlin/org/microg/nlp/client/BaseClient.kt
Normal file
166
client/src/main/kotlin/org/microg/nlp/client/BaseClient.kt
Normal file
|
@ -0,0 +1,166 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2020, microG Project Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package org.microg.nlp.client
|
||||
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Context.BIND_ABOVE_CLIENT
|
||||
import android.content.Context.BIND_AUTO_CREATE
|
||||
import android.content.Intent
|
||||
import android.content.ServiceConnection
|
||||
import android.location.Location
|
||||
import android.os.Bundle
|
||||
import android.os.IBinder
|
||||
import android.util.Log
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import org.microg.nlp.service.api.Constants
|
||||
import org.microg.nlp.service.api.ILocationListener
|
||||
import org.microg.nlp.service.api.IStatusCallback
|
||||
import org.microg.nlp.service.api.IStringsCallback
|
||||
import java.util.concurrent.LinkedBlockingQueue
|
||||
import java.util.concurrent.ThreadPoolExecutor
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
import kotlin.coroutines.Continuation
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.resumeWithException
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
|
||||
private const val TAG = "BaseClient"
|
||||
|
||||
abstract class BaseClient<I>(val context: Context, private val lifecycle: Lifecycle, val asInterface: (IBinder) -> I) : LifecycleOwner {
|
||||
private val callbackThreads: ThreadPoolExecutor = ThreadPoolExecutor(1, Runtime.getRuntime().availableProcessors(), 1, TimeUnit.SECONDS, LinkedBlockingQueue())
|
||||
private var persistedConnectionCounter = AtomicInteger(0)
|
||||
private val serviceConnectionMutex = Mutex()
|
||||
private var persistedServiceConnection: ContinuedServiceConnection? = null
|
||||
val defaultOptions = Bundle()
|
||||
|
||||
abstract val action: String
|
||||
|
||||
val intent: Intent?
|
||||
get() = resolveIntent(context, action)
|
||||
|
||||
val isAvailable: Boolean
|
||||
get() = intent != null
|
||||
|
||||
var packageName: String
|
||||
get() = defaultOptions.getString("packageName") ?: context.packageName
|
||||
set(value) = defaultOptions.putString("packageName", value)
|
||||
|
||||
init {
|
||||
packageName = context.packageName
|
||||
}
|
||||
|
||||
val isConnectedUnsafe: Boolean
|
||||
get() = persistedServiceConnection != null && persistedConnectionCounter.get() > 0
|
||||
|
||||
suspend fun isConnected(): Boolean = serviceConnectionMutex.withLock {
|
||||
return persistedServiceConnection != null && persistedConnectionCounter.get() > 0
|
||||
}
|
||||
|
||||
suspend fun connect() {
|
||||
serviceConnectionMutex.withLock {
|
||||
if (persistedServiceConnection == null) {
|
||||
val intent = intent ?: throw IllegalStateException("$action service is not available")
|
||||
persistedServiceConnection = suspendCoroutine<ContinuedServiceConnection> { continuation ->
|
||||
context.bindService(intent, ContinuedServiceConnection(continuation), BIND_AUTO_CREATE or BIND_ABOVE_CLIENT)
|
||||
}
|
||||
}
|
||||
persistedConnectionCounter.incrementAndGet()
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun disconnect() {
|
||||
serviceConnectionMutex.withLock {
|
||||
if (persistedConnectionCounter.decrementAndGet() <= 0) {
|
||||
persistedServiceConnection?.let { context.unbindService(it) }
|
||||
persistedServiceConnection = null
|
||||
persistedConnectionCounter.set(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun <T> withConnectedServiceSync(v: (I) -> T): T {
|
||||
try {
|
||||
if (persistedConnectionCounter.incrementAndGet() <= 1) {
|
||||
throw IllegalStateException("Service not connected")
|
||||
}
|
||||
val service = persistedServiceConnection?.service ?: throw RuntimeException("No binder returned")
|
||||
return v(asInterface(service))
|
||||
} finally {
|
||||
persistedConnectionCounter.decrementAndGet()
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun <T> withService(v: suspend (I) -> T): T {
|
||||
connect()
|
||||
val service = persistedServiceConnection?.service ?: throw RuntimeException("No binder returned")
|
||||
return try {
|
||||
v(asInterface(service))
|
||||
} finally {
|
||||
disconnect()
|
||||
}
|
||||
}
|
||||
|
||||
override fun getLifecycle(): Lifecycle = lifecycle
|
||||
}
|
||||
|
||||
internal class ContinuedServiceConnection(private val continuation: Continuation<ContinuedServiceConnection>) : ServiceConnection {
|
||||
var service: IBinder? = null
|
||||
private var continued: Boolean = false
|
||||
|
||||
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
|
||||
this.service = service
|
||||
if (!continued) {
|
||||
continued = true
|
||||
continuation.resume(this)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onServiceDisconnected(name: ComponentName?) {
|
||||
if (!continued) {
|
||||
continued = true
|
||||
continuation.resumeWithException(RuntimeException("Disconnected"))
|
||||
}
|
||||
}
|
||||
|
||||
override fun onBindingDied(name: ComponentName?) {
|
||||
if (!continued) {
|
||||
continued = true
|
||||
continuation.resumeWithException(RuntimeException("Binding diead"))
|
||||
}
|
||||
}
|
||||
|
||||
override fun onNullBinding(name: ComponentName?) {
|
||||
if (!continued) {
|
||||
continued = true
|
||||
continuation.resume(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal class StringsCallback(private val continuation: Continuation<List<String>>) : IStringsCallback.Stub() {
|
||||
override fun onStrings(statusCode: Int, strings: MutableList<String>) {
|
||||
if (statusCode == Constants.STATUS_OK) {
|
||||
continuation.resume(strings)
|
||||
} else {
|
||||
continuation.resumeWithException(RuntimeException("Status: $statusCode"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal class StatusCallback(private val continuation: Continuation<Unit>) : IStatusCallback.Stub() {
|
||||
override fun onStatus(statusCode: Int) {
|
||||
if (statusCode == Constants.STATUS_OK) {
|
||||
continuation.resume(Unit)
|
||||
} else {
|
||||
continuation.resumeWithException(RuntimeException("Status: $statusCode"))
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2020, microG Project Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package org.microg.nlp.client
|
||||
|
||||
import android.content.Context
|
||||
import android.location.Address
|
||||
import android.os.Bundle
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import org.microg.nlp.service.api.*
|
||||
import org.microg.nlp.service.api.Constants.STATUS_OK
|
||||
import java.util.concurrent.*
|
||||
import kotlin.coroutines.Continuation
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.resumeWithException
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
|
||||
class GeocodeClient(context: Context, lifecycle: Lifecycle) : BaseClient<IGeocodeService>(context, lifecycle, { IGeocodeService.Stub.asInterface(it) }) {
|
||||
private val syncThreads: ThreadPoolExecutor = ThreadPoolExecutor(1, Runtime.getRuntime().availableProcessors(), 1, TimeUnit.SECONDS, LinkedBlockingQueue())
|
||||
override val action: String
|
||||
get() = Constants.ACTION_GEOCODE
|
||||
|
||||
fun requestGeocodeSync(request: GeocodeRequest, options: Bundle = defaultOptions): List<Address> = executeWithTimeout {
|
||||
withConnectedServiceSync { service ->
|
||||
service.requestGeocodeSync(request, options)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun requestGeocode(request: GeocodeRequest, options: Bundle = defaultOptions): List<Address> = withService { service ->
|
||||
suspendCoroutine {
|
||||
service.requestGeocode(request, AddressesCallback(it), options)
|
||||
}
|
||||
}
|
||||
|
||||
fun requestReverseGeocodeSync(request: ReverseGeocodeRequest, options: Bundle = defaultOptions): List<Address> = executeWithTimeout {
|
||||
withConnectedServiceSync { service ->
|
||||
service.requestReverseGeocodeSync(request, options)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun requestReverseGeocode(request: ReverseGeocodeRequest, options: Bundle = defaultOptions): List<Address> = withService { service ->
|
||||
suspendCoroutine {
|
||||
service.requestReverseGeocode(request, AddressesCallback(it), options)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getGeocodeBackends(options: Bundle = defaultOptions): List<String> = withService { service ->
|
||||
suspendCoroutine {
|
||||
service.getGeocodeBackends(StringsCallback(it), options)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun setGeocodeBackends(backends: List<String>, options: Bundle = defaultOptions): Unit = withService { service ->
|
||||
suspendCoroutine {
|
||||
service.setGeocodeBackends(backends, StatusCallback(it), options)
|
||||
}
|
||||
}
|
||||
|
||||
private fun <T> executeWithTimeout(timeout: Long = CALL_TIMEOUT, action: () -> T): T {
|
||||
var result: T? = null
|
||||
val latch = CountDownLatch(1)
|
||||
var err: Exception? = null
|
||||
syncThreads.execute {
|
||||
try {
|
||||
result = 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()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val CALL_TIMEOUT = 10000L
|
||||
}
|
||||
}
|
||||
|
||||
private class AddressesCallback(private val continuation: Continuation<List<Address>>) : IAddressesCallback.Stub() {
|
||||
override fun onAddresses(statusCode: Int, addresses: List<Address>?) {
|
||||
if (statusCode == STATUS_OK) {
|
||||
continuation.resume(addresses.orEmpty())
|
||||
} else {
|
||||
continuation.resumeWithException(RuntimeException("Status: $statusCode"))
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2020, microG Project Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package org.microg.nlp.client
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.ApplicationInfo
|
||||
import android.content.pm.ResolveInfo
|
||||
import android.util.Log
|
||||
|
||||
internal fun resolveIntent(context: Context, action: String): Intent? {
|
||||
val intent = Intent(action)
|
||||
|
||||
val pm = context.packageManager
|
||||
var resolveInfos = pm.queryIntentServices(intent, 0)
|
||||
if (resolveInfos.size > 1) {
|
||||
|
||||
// Restrict to self if possible
|
||||
val isSelf: (it: ResolveInfo) -> Boolean = {
|
||||
it.serviceInfo.packageName == context.packageName
|
||||
}
|
||||
if (resolveInfos.size > 1 && resolveInfos.any(isSelf)) {
|
||||
Log.d("IntentResolver", "Found more than one service for $action, restricted to own package " + context.packageName)
|
||||
resolveInfos = resolveInfos.filter(isSelf)
|
||||
}
|
||||
|
||||
// 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("IntentResolver", "Found more than one service for $action, restricted to related packages")
|
||||
resolveInfos = resolveInfos.filter(isSelfSig)
|
||||
}
|
||||
|
||||
// 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("IntentResolver", "Found more than one service for $action, restricted to system packages")
|
||||
resolveInfos = resolveInfos.filter(isSystem)
|
||||
}
|
||||
|
||||
val highestPriority: ResolveInfo? = resolveInfos.maxByOrNull { it.priority }
|
||||
intent.setPackage(highestPriority!!.serviceInfo.packageName)
|
||||
intent.setClassName(highestPriority.serviceInfo.packageName, highestPriority.serviceInfo.name)
|
||||
if (resolveInfos.size > 1) {
|
||||
Log.d("IntentResolver", "Found more than one service for $action, picked highest priority " + intent.component)
|
||||
}
|
||||
return intent
|
||||
} else if (!resolveInfos.isEmpty()) {
|
||||
intent.setPackage(resolveInfos[0].serviceInfo.packageName)
|
||||
return intent
|
||||
} else {
|
||||
Log.w("IntentResolver", "No service to bind to, your system does not support unified service")
|
||||
return null
|
||||
}
|
||||
}
|
103
client/src/main/kotlin/org/microg/nlp/client/LocationClient.kt
Normal file
103
client/src/main/kotlin/org/microg/nlp/client/LocationClient.kt
Normal file
|
@ -0,0 +1,103 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2020, microG Project Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package org.microg.nlp.client
|
||||
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.ServiceConnection
|
||||
import android.location.Location
|
||||
import android.os.Bundle
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import org.microg.nlp.service.api.*
|
||||
import kotlin.coroutines.Continuation
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.resumeWithException
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
|
||||
class LocationClient(context: Context, lifecycle: Lifecycle) : BaseClient<ILocationService>(context, lifecycle, { ILocationService.Stub.asInterface(it) }) {
|
||||
private val requests = hashSetOf<LocationRequest>()
|
||||
private val requestsMutex = Mutex(false)
|
||||
|
||||
override val action: String
|
||||
get() = Constants.ACTION_LOCATION
|
||||
|
||||
suspend fun getLastLocation(options: Bundle = defaultOptions): Location? = withService { service ->
|
||||
suspendCoroutine {
|
||||
service.getLastLocation(SingleLocationListener(it), options)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getLastLocationForBackend(componentName: ComponentName, options: Bundle = defaultOptions) = getLastLocationForBackend(componentName.packageName, componentName.className, null, options)
|
||||
|
||||
suspend fun getLastLocationForBackend(packageName: String, className: String, signatureDigest: String? = null, options: Bundle = defaultOptions): Location? = withService { service ->
|
||||
suspendCoroutine {
|
||||
service.getLastLocationForBackend(packageName, className, signatureDigest, SingleLocationListener(it), options)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun <T> withRequestService(v: suspend (ILocationService) -> T): T {
|
||||
return requestsMutex.withLock {
|
||||
try {
|
||||
if (requests.isEmpty()) connect()
|
||||
withService(v)
|
||||
} finally {
|
||||
if (requests.isEmpty()) disconnect()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun updateLocationRequest(request: LocationRequest, options: Bundle = defaultOptions): Unit = withRequestService { service ->
|
||||
suspendCoroutine<Unit> {
|
||||
service.updateLocationRequest(request, StatusCallback(it), options)
|
||||
}
|
||||
requests.removeAll { it.id == request.id }
|
||||
requests.add(request)
|
||||
}
|
||||
|
||||
suspend fun cancelLocationRequestByListener(listener: ILocationListener, options: Bundle = defaultOptions): Unit = withRequestService { service ->
|
||||
suspendCoroutine<Unit> {
|
||||
service.cancelLocationRequestByListener(listener, StatusCallback(it), options)
|
||||
}
|
||||
requests.removeAll { it.listener == listener }
|
||||
}
|
||||
|
||||
suspend fun cancelLocationRequestById(id: String, options: Bundle = defaultOptions): Unit = withRequestService { service ->
|
||||
suspendCoroutine<Unit> {
|
||||
service.cancelLocationRequestById(id, StatusCallback(it), options)
|
||||
}
|
||||
requests.removeAll { it.id == id }
|
||||
}
|
||||
|
||||
suspend fun forceLocationUpdate(options: Bundle = defaultOptions): Unit = withService { service ->
|
||||
suspendCoroutine {
|
||||
service.forceLocationUpdate(StatusCallback(it), options)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getLocationBackends(options: Bundle = defaultOptions): List<String> = withService { service ->
|
||||
suspendCoroutine {
|
||||
service.getLocationBackends(StringsCallback(it), options)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun setLocationBackends(backends: List<String>, options: Bundle = defaultOptions): Unit = withService { service ->
|
||||
suspendCoroutine {
|
||||
service.setLocationBackends(backends, StatusCallback(it), options)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class SingleLocationListener(private val continuation: Continuation<Location?>) : ILocationListener.Stub() {
|
||||
override fun onLocation(statusCode: Int, location: Location?) {
|
||||
if (statusCode == Constants.STATUS_OK) {
|
||||
continuation.resume(location)
|
||||
} else {
|
||||
continuation.resumeWithException(RuntimeException("Status: $statusCode"))
|
||||
}
|
||||
}
|
||||
}
|
|
@ -9,8 +9,6 @@ import android.content.ComponentName
|
|||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.ServiceConnection
|
||||
import android.content.pm.ApplicationInfo
|
||||
import android.content.pm.ResolveInfo
|
||||
import android.location.Address
|
||||
import android.location.Location
|
||||
import android.os.Bundle
|
||||
|
@ -18,37 +16,31 @@ import android.os.DeadObjectException
|
|||
import android.os.IBinder
|
||||
import android.os.RemoteException
|
||||
import android.util.Log
|
||||
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import kotlinx.coroutines.*
|
||||
import org.microg.nlp.service.AddressCallback
|
||||
import org.microg.nlp.service.LocationCallback
|
||||
import org.microg.nlp.service.UnifiedLocationService
|
||||
|
||||
import java.lang.ref.WeakReference
|
||||
import java.util.Timer
|
||||
import java.util.TimerTask
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
|
||||
import android.content.pm.PackageManager.SIGNATURE_MATCH
|
||||
import kotlinx.coroutines.*
|
||||
import java.lang.RuntimeException
|
||||
import java.util.*
|
||||
import java.util.concurrent.*
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
import kotlin.coroutines.*
|
||||
import kotlin.math.min
|
||||
|
||||
private const val CALL_TIMEOUT = 10000L
|
||||
|
||||
class UnifiedLocationClient private constructor(context: Context) {
|
||||
@Deprecated("Use LocationClient or GeocodeClient")
|
||||
class UnifiedLocationClient(private val context: Context, private val lifecycle: Lifecycle) : LifecycleOwner {
|
||||
private var bound = false
|
||||
private val serviceReferenceCount = AtomicInteger(0)
|
||||
private val options = Bundle()
|
||||
private var context: WeakReference<Context> = WeakReference(context)
|
||||
private var service: UnifiedLocationService? = null
|
||||
private val syncThreads: ThreadPoolExecutor = ThreadPoolExecutor(1, Runtime.getRuntime().availableProcessors(), 1, TimeUnit.SECONDS, LinkedBlockingQueue())
|
||||
private val waitingForService = arrayListOf<Continuation<UnifiedLocationService>>()
|
||||
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)
|
||||
|
@ -82,56 +74,7 @@ class UnifiedLocationClient private constructor(context: Context) {
|
|||
val targetPackage: String?
|
||||
get() = resolve()?.`package`
|
||||
|
||||
private fun resolve(): Intent? {
|
||||
val context = this.context.get() ?: return null
|
||||
val intent = Intent(ACTION_UNIFIED_LOCATION_SERVICE)
|
||||
|
||||
val pm = context.packageManager
|
||||
var resolveInfos = pm.queryIntentServices(intent, 0)
|
||||
if (resolveInfos.size > 1) {
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// 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 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(isSystem)
|
||||
}
|
||||
|
||||
val highestPriority: ResolveInfo? = resolveInfos.maxBy { it.priority }
|
||||
intent.setPackage(highestPriority!!.serviceInfo.packageName)
|
||||
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)
|
||||
return intent
|
||||
} else {
|
||||
Log.w(TAG, "No service to bind to, your system does not support unified service")
|
||||
return null
|
||||
}
|
||||
}
|
||||
private fun resolve(): Intent? = resolveIntent(context, ACTION_UNIFIED_LOCATION_SERVICE)
|
||||
|
||||
@Synchronized
|
||||
private fun updateBinding(): Boolean {
|
||||
|
@ -167,7 +110,6 @@ class UnifiedLocationClient private constructor(context: Context) {
|
|||
|
||||
@Synchronized
|
||||
private fun bind() {
|
||||
val context = this.context.get() ?: return
|
||||
if (!bound) {
|
||||
Log.w(TAG, "Tried to bind while not being bound!")
|
||||
return
|
||||
|
@ -186,7 +128,7 @@ class UnifiedLocationClient private constructor(context: Context) {
|
|||
@Synchronized
|
||||
private fun unbind() {
|
||||
try {
|
||||
this.context.get()?.unbindService(connection)
|
||||
this.context.unbindService(connection)
|
||||
} catch (ignored: Exception) {
|
||||
}
|
||||
|
||||
|
@ -207,29 +149,34 @@ class UnifiedLocationClient private constructor(context: Context) {
|
|||
updateBinding()
|
||||
}
|
||||
|
||||
@Deprecated("Use LocationClient")
|
||||
suspend fun getSingleLocation(): Location = suspendCoroutine { continuation ->
|
||||
requestSingleLocation(LocationListener.wrap { continuation.resume(it) })
|
||||
}
|
||||
|
||||
@Deprecated("Use LocationClient")
|
||||
fun requestSingleLocation(listener: LocationListener) {
|
||||
requestLocationUpdates(listener, 0, 1)
|
||||
}
|
||||
|
||||
@Deprecated("Use LocationClient")
|
||||
fun requestLocationUpdates(listener: LocationListener, interval: Long) {
|
||||
requestLocationUpdates(listener, interval, Integer.MAX_VALUE)
|
||||
}
|
||||
|
||||
@Deprecated("Use LocationClient")
|
||||
fun requestLocationUpdates(listener: LocationListener, interval: Long, count: Int) {
|
||||
requests.removeAll(requests.filter { it.listener === listener })
|
||||
requests.add(LocationRequest(listener, interval, count))
|
||||
coroutineScope.launch {
|
||||
lifecycleScope.launchWhenStarted {
|
||||
updateServiceInterval()
|
||||
updateBinding()
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated("Use LocationClient")
|
||||
fun removeLocationUpdates(listener: LocationListener) {
|
||||
coroutineScope.launch {
|
||||
lifecycleScope.launchWhenStarted {
|
||||
removeRequests(requests.filter { it.listener === listener })
|
||||
}
|
||||
}
|
||||
|
@ -310,6 +257,7 @@ class UnifiedLocationClient private constructor(context: Context) {
|
|||
return result ?: throw NullPointerException()
|
||||
}
|
||||
|
||||
@Deprecated("Use GeocoderClient")
|
||||
suspend fun getFromLocation(latitude: Double, longitude: Double, maxResults: Int, locale: String, timeout: Long = Long.MAX_VALUE): List<Address> {
|
||||
try {
|
||||
val service = refAndGetService()
|
||||
|
@ -325,10 +273,12 @@ class UnifiedLocationClient private constructor(context: Context) {
|
|||
}
|
||||
}
|
||||
|
||||
@Deprecated("Use GeocoderClient")
|
||||
fun getFromLocationSync(latitude: Double, longitude: Double, maxResults: Int, locale: String, timeout: Long = CALL_TIMEOUT): List<Address> = executeSyncWithTimeout(timeout) {
|
||||
getFromLocation(latitude, longitude, maxResults, locale, timeout)
|
||||
}
|
||||
|
||||
@Deprecated("Use GeocoderClient")
|
||||
suspend fun getFromLocationName(locationName: String, maxResults: Int, lowerLeftLatitude: Double, lowerLeftLongitude: Double, upperRightLatitude: Double, upperRightLongitude: Double, locale: String, timeout: Long = Long.MAX_VALUE): List<Address> {
|
||||
return try {
|
||||
val service = refAndGetService()
|
||||
|
@ -344,20 +294,12 @@ class UnifiedLocationClient private constructor(context: Context) {
|
|||
}
|
||||
}
|
||||
|
||||
@Deprecated("Use GeocoderClient")
|
||||
fun getFromLocationNameSync(locationName: String, maxResults: Int, lowerLeftLatitude: Double, lowerLeftLongitude: Double, upperRightLatitude: Double, upperRightLongitude: Double, locale: String, timeout: Long = CALL_TIMEOUT): List<Address> = executeSyncWithTimeout(timeout) {
|
||||
getFromLocationName(locationName, maxResults, lowerLeftLatitude, lowerLeftLongitude, upperRightLatitude, upperRightLongitude, locale, timeout)
|
||||
}
|
||||
|
||||
suspend fun reloadPreferences() {
|
||||
try {
|
||||
refAndGetService().reloadPreferences()
|
||||
} catch (e: RemoteException) {
|
||||
Log.w(TAG, "Failed to handle request", e)
|
||||
} finally {
|
||||
unref()
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated("Use LocationClient")
|
||||
suspend fun getLocationBackends(): Array<String> {
|
||||
try {
|
||||
return refAndGetService().locationBackends
|
||||
|
@ -369,6 +311,7 @@ class UnifiedLocationClient private constructor(context: Context) {
|
|||
}
|
||||
}
|
||||
|
||||
@Deprecated("Use LocationClient")
|
||||
suspend fun setLocationBackends(backends: Array<String>) {
|
||||
try {
|
||||
refAndGetService().locationBackends = backends
|
||||
|
@ -379,6 +322,7 @@ class UnifiedLocationClient private constructor(context: Context) {
|
|||
}
|
||||
}
|
||||
|
||||
@Deprecated("Use GeocoderClient")
|
||||
suspend fun getGeocoderBackends(): Array<String> {
|
||||
try {
|
||||
return refAndGetService().geocoderBackends
|
||||
|
@ -390,6 +334,7 @@ class UnifiedLocationClient private constructor(context: Context) {
|
|||
}
|
||||
}
|
||||
|
||||
@Deprecated("Use GeocoderClient")
|
||||
suspend fun setGeocoderBackends(backends: Array<String>) {
|
||||
try {
|
||||
refAndGetService().geocoderBackends = backends
|
||||
|
@ -400,10 +345,12 @@ class UnifiedLocationClient private constructor(context: Context) {
|
|||
}
|
||||
}
|
||||
|
||||
@Deprecated("Use LocationClient")
|
||||
fun getLastLocationSync(timeout: Long = CALL_TIMEOUT): Location? = executeSyncWithTimeout(timeout) {
|
||||
getLastLocation()
|
||||
}
|
||||
|
||||
@Deprecated("Use LocationClient")
|
||||
suspend fun getLastLocation(): Location? {
|
||||
return try {
|
||||
refAndGetService().lastLocation
|
||||
|
@ -415,6 +362,7 @@ class UnifiedLocationClient private constructor(context: Context) {
|
|||
}
|
||||
}
|
||||
|
||||
@Deprecated("Use LocationClient")
|
||||
suspend fun getLastLocationForBackend(packageName: String, className: String, signatureDigest: String? = null): Location? {
|
||||
return try {
|
||||
refAndGetService().getLastLocationForBackend(packageName, className, signatureDigest)
|
||||
|
@ -439,30 +387,32 @@ class UnifiedLocationClient private constructor(context: Context) {
|
|||
}
|
||||
|
||||
private suspend fun updateServiceInterval() {
|
||||
var interval: Long = Long.MAX_VALUE
|
||||
var minTime = Long.MAX_VALUE
|
||||
var requestSingle = false
|
||||
for (request in requests) {
|
||||
if (request.interval <= 0) {
|
||||
requestSingle = true
|
||||
forceNextUpdate = true
|
||||
continue
|
||||
}
|
||||
interval = min(interval, request.interval)
|
||||
if (request.interval <= minTime) {
|
||||
minTime = request.interval
|
||||
}
|
||||
}
|
||||
if (interval == Long.MAX_VALUE) {
|
||||
if (minTime == Long.MAX_VALUE) {
|
||||
Log.d(TAG, "Disable automatic updates")
|
||||
interval = 0
|
||||
minTime = 0
|
||||
} else {
|
||||
Log.d(TAG, "Set update interval to $interval")
|
||||
Log.d(TAG, "Set update interval to $minTime")
|
||||
}
|
||||
val service: UnifiedLocationService
|
||||
try {
|
||||
service = waitForService()
|
||||
val service = try {
|
||||
waitForService()
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, e)
|
||||
return
|
||||
}
|
||||
try {
|
||||
service.setUpdateInterval(interval, options)
|
||||
service.setUpdateInterval(minTime, options)
|
||||
if (requestSingle) {
|
||||
Log.d(TAG, "Request single update (force update: $forceNextUpdate)")
|
||||
service.requestSingleUpdate(options)
|
||||
|
@ -487,12 +437,12 @@ class UnifiedLocationClient private constructor(context: Context) {
|
|||
continuations.addAll(waitingForService)
|
||||
waitingForService.clear()
|
||||
}
|
||||
coroutineScope.launch {
|
||||
lifecycleScope.launchWhenStarted {
|
||||
try {
|
||||
Log.d(TAG, "Registering location callback")
|
||||
service.registerLocationCallback(object : LocationCallback.Stub() {
|
||||
override fun onLocationUpdate(location: Location) {
|
||||
coroutineScope.launch {
|
||||
lifecycleScope.launchWhenStarted {
|
||||
this@UnifiedLocationClient.onLocationUpdate(location)
|
||||
}
|
||||
}
|
||||
|
@ -538,6 +488,8 @@ class UnifiedLocationClient private constructor(context: Context) {
|
|||
bindLater()
|
||||
}
|
||||
|
||||
override fun getLifecycle(): Lifecycle = lifecycle
|
||||
|
||||
interface LocationListener {
|
||||
fun onLocation(location: Location)
|
||||
|
||||
|
@ -592,20 +544,6 @@ class UnifiedLocationClient private constructor(context: Context) {
|
|||
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
|
||||
|
||||
@JvmStatic
|
||||
@Synchronized
|
||||
operator fun get(context: Context): UnifiedLocationClient {
|
||||
var client = client
|
||||
if (client == null) {
|
||||
client = UnifiedLocationClient(context)
|
||||
this.client = client
|
||||
} else {
|
||||
client.context = WeakReference(context)
|
||||
}
|
||||
return client
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -622,4 +560,4 @@ class AddressContinuation(private val continuation: Continuation<List<Address>>)
|
|||
// Ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,7 +12,15 @@ if (!sdkDir) {
|
|||
sdkDir = properties.getProperty('sdk.dir')
|
||||
}
|
||||
|
||||
sourceSets.main {
|
||||
java.srcDirs = ['src/current/java', 'src/v9/java']
|
||||
compileClasspath += project.rootProject.files("$sdkDir/platforms/android-$androidCompileSdk/android.jar")
|
||||
sourceSets {
|
||||
main {
|
||||
java {
|
||||
srcDir 'src/current/java'
|
||||
srcDir 'src/v9/java'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly files("$sdkDir/platforms/android-$androidCompileSdk/android.jar")
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
*/
|
||||
|
||||
apply plugin: 'com.android.library'
|
||||
apply plugin: 'kotlin-android'
|
||||
apply plugin: 'maven-publish'
|
||||
apply plugin: 'signing'
|
||||
|
||||
|
@ -30,4 +31,9 @@ description = 'UnifiedNlp service to implement Geocode API v1'
|
|||
dependencies {
|
||||
implementation project(':client')
|
||||
compileOnly project(':compat')
|
||||
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"
|
||||
implementation "androidx.lifecycle:lifecycle-service:$lifecycleVersion"
|
||||
}
|
||||
|
|
|
@ -1,59 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2019, microG Project Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package org.microg.nlp.geocode.v1;
|
||||
|
||||
import android.content.Context;
|
||||
import android.location.Address;
|
||||
import android.location.GeocoderParams;
|
||||
import android.util.Log;
|
||||
|
||||
import org.microg.nlp.client.UnifiedLocationClient;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class GeocodeProvider extends com.android.location.provider.GeocodeProvider {
|
||||
private static final String TAG = "UGeocode";
|
||||
private Context context;
|
||||
private static final long TIMEOUT = 10000;
|
||||
|
||||
public GeocodeProvider(Context context) {
|
||||
this.context = context;
|
||||
UnifiedLocationClient.get(context).ref();
|
||||
}
|
||||
|
||||
public void onDisable() {
|
||||
UnifiedLocationClient.get(context).unref();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String onGetFromLocation(double latitude, double longitude, int maxResults, GeocoderParams params, List<Address> addrs) {
|
||||
try {
|
||||
return handleResult(addrs, UnifiedLocationClient.get(context).getFromLocationSync(latitude, longitude, maxResults, params.getLocale().toString(), TIMEOUT));
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, e);
|
||||
return e.getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String onGetFromLocationName(String locationName, double lowerLeftLatitude, double lowerLeftLongitude, double upperRightLatitude, double upperRightLongitude, int maxResults, GeocoderParams params, List<Address> addrs) {
|
||||
try {
|
||||
return handleResult(addrs, UnifiedLocationClient.get(context).getFromLocationNameSync(locationName, maxResults, lowerLeftLatitude, lowerLeftLongitude, upperRightLatitude, upperRightLongitude, params.getLocale().toString(), TIMEOUT));
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, e);
|
||||
return e.getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
private String handleResult(List<Address> realResult, List<Address> fuserResult) {
|
||||
if (fuserResult.isEmpty()) {
|
||||
return "no result";
|
||||
} else {
|
||||
realResult.addAll(fuserResult);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2019, microG Project Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
package org.microg.nlp.geocode.v1
|
||||
|
||||
import android.content.Context
|
||||
import android.location.Address
|
||||
import android.location.GeocoderParams
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import com.android.location.provider.GeocodeProvider
|
||||
import org.microg.nlp.client.GeocodeClient
|
||||
import org.microg.nlp.service.api.GeocodeRequest
|
||||
import org.microg.nlp.service.api.LatLon
|
||||
import org.microg.nlp.service.api.LatLonBounds
|
||||
import org.microg.nlp.service.api.ReverseGeocodeRequest
|
||||
|
||||
class GeocodeProvider(context: Context, lifecycle: Lifecycle) : GeocodeProvider() {
|
||||
private val client: GeocodeClient = GeocodeClient(context, lifecycle)
|
||||
|
||||
override fun onGetFromLocation(latitude: Double, longitude: Double, maxResults: Int, params: GeocoderParams, addrs: MutableList<Address>): String? {
|
||||
return try {
|
||||
handleResult(addrs, client.requestReverseGeocodeSync(
|
||||
ReverseGeocodeRequest(LatLon(latitude, longitude), maxResults, params.locale),
|
||||
Bundle().apply { putString("packageName", params.clientPackage) }
|
||||
))
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, e)
|
||||
e.message
|
||||
}
|
||||
}
|
||||
|
||||
override fun onGetFromLocationName(locationName: String?, lowerLeftLatitude: Double, lowerLeftLongitude: Double, upperRightLatitude: Double, upperRightLongitude: Double, maxResults: Int, params: GeocoderParams, addrs: MutableList<Address>): String? {
|
||||
return try {
|
||||
handleResult(addrs, client.requestGeocodeSync(
|
||||
GeocodeRequest(locationName!!, LatLonBounds(LatLon(lowerLeftLatitude, lowerLeftLongitude), LatLon(upperRightLatitude, upperRightLongitude)), maxResults, params.locale),
|
||||
Bundle().apply { putString("packageName", params.clientPackage) }
|
||||
))
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, e)
|
||||
e.message
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleResult(realResult: MutableList<Address>, fuserResult: List<Address>): String? {
|
||||
return if (fuserResult.isEmpty()) {
|
||||
"no result"
|
||||
} else {
|
||||
realResult.addAll(fuserResult)
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun connect() = client.connect()
|
||||
suspend fun disconnect() = client.disconnect()
|
||||
|
||||
companion object {
|
||||
private const val TAG = "GeocodeProvider"
|
||||
private const val TIMEOUT: Long = 10000
|
||||
}
|
||||
}
|
|
@ -1,33 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2019, microG Project Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package org.microg.nlp.geocode.v1;
|
||||
|
||||
import android.app.Service;
|
||||
import android.content.Intent;
|
||||
import android.os.IBinder;
|
||||
import android.util.Log;
|
||||
|
||||
public class GeocodeService extends Service {
|
||||
private static final String TAG = "UnifiedGeocode";
|
||||
private GeocodeProvider provider;
|
||||
|
||||
@Override
|
||||
public synchronized IBinder onBind(Intent intent) {
|
||||
Log.d(TAG, "onBind: "+intent);
|
||||
if (provider == null) {
|
||||
provider = new GeocodeProvider(this);
|
||||
}
|
||||
return provider.getBinder();
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized boolean onUnbind(Intent intent) {
|
||||
if (provider != null) {
|
||||
provider.onDisable();
|
||||
}
|
||||
return super.onUnbind(intent);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2019, microG Project Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
package org.microg.nlp.geocode.v1
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.IBinder
|
||||
import android.util.Log
|
||||
import androidx.lifecycle.LifecycleService
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import kotlinx.coroutines.runBlocking
|
||||
|
||||
class GeocodeService : LifecycleService() {
|
||||
private lateinit var provider: GeocodeProvider
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
Log.d(TAG, "Creating system service...")
|
||||
provider = GeocodeProvider(this, lifecycle)
|
||||
lifecycleScope.launchWhenStarted { provider.connect() }
|
||||
Log.d(TAG, "Created system service.")
|
||||
}
|
||||
|
||||
override fun onBind(intent: Intent): IBinder? {
|
||||
super.onBind(intent)
|
||||
Log.d(TAG, "onBind: $intent")
|
||||
return provider.binder
|
||||
}
|
||||
|
||||
override fun onUnbind(intent: Intent): Boolean {
|
||||
return super.onUnbind(intent)
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
runBlocking { provider.disconnect() }
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "GeocodeService"
|
||||
}
|
||||
}
|
|
@ -4,6 +4,7 @@
|
|||
*/
|
||||
|
||||
apply plugin: 'com.android.library'
|
||||
apply plugin: 'kotlin-android'
|
||||
apply plugin: 'maven-publish'
|
||||
apply plugin: 'signing'
|
||||
|
||||
|
@ -30,4 +31,9 @@ description = 'UnifiedNlp service to implement Location API v2'
|
|||
dependencies {
|
||||
implementation project(':client')
|
||||
compileOnly project(':compat')
|
||||
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"
|
||||
implementation "androidx.lifecycle:lifecycle-service:$lifecycleVersion"
|
||||
}
|
||||
|
|
|
@ -1,99 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2019, microG Project Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package org.microg.nlp.location.v2;
|
||||
|
||||
import android.content.Context;
|
||||
import android.location.Criteria;
|
||||
import android.location.Location;
|
||||
import android.os.Bundle;
|
||||
import android.os.WorkSource;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.location.provider.LocationProviderBase;
|
||||
import com.android.location.provider.ProviderPropertiesUnbundled;
|
||||
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;
|
||||
|
||||
public LocationProvider(Context context) {
|
||||
super(TAG, ProviderPropertiesUnbundled.create(false, false, false, false, true, true, true, Criteria.POWER_LOW, Criteria.ACCURACY_COARSE));
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
UnifiedLocationClient.get(context).ref();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisable() {
|
||||
UnifiedLocationClient.get(context).unref();
|
||||
}
|
||||
|
||||
@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();
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
public void unsetRequest() {
|
||||
UnifiedLocationClient.get(context).removeLocationUpdates(this);
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
public int onGetStatus(Bundle extras) {
|
||||
return AVAILABLE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long onGetStatusUpdateTime() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLocation(Location location) {
|
||||
reportLocation(location);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,149 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2019, microG Project Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
package org.microg.nlp.location.v2
|
||||
|
||||
import android.content.Context
|
||||
import android.location.Criteria
|
||||
import android.location.Location
|
||||
import android.location.LocationProvider
|
||||
import android.os.Bundle
|
||||
import android.os.SystemClock
|
||||
import android.os.WorkSource
|
||||
import android.util.Log
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.android.location.provider.LocationProviderBase
|
||||
import com.android.location.provider.ProviderPropertiesUnbundled
|
||||
import com.android.location.provider.ProviderRequestUnbundled
|
||||
import kotlinx.coroutines.launch
|
||||
import org.microg.nlp.client.LocationClient
|
||||
import org.microg.nlp.service.api.Constants.STATUS_OK
|
||||
import org.microg.nlp.service.api.ILocationListener
|
||||
import org.microg.nlp.service.api.LocationRequest
|
||||
import java.io.FileDescriptor
|
||||
import java.io.PrintWriter
|
||||
import java.util.*
|
||||
|
||||
class LocationProvider(private val context: Context, private val lifecycle: Lifecycle) : LocationProviderBase(TAG, ProviderPropertiesUnbundled.create(false, false, false, false, true, true, true, Criteria.POWER_LOW, Criteria.ACCURACY_COARSE)), LifecycleOwner {
|
||||
private val client: LocationClient = LocationClient(context, lifecycle)
|
||||
private val id = UUID.randomUUID().toString()
|
||||
private var statusUpdateTime = SystemClock.elapsedRealtime()
|
||||
private val listener = object : ILocationListener.Stub() {
|
||||
override fun onLocation(statusCode: Int, location: Location?) {
|
||||
if (statusCode == STATUS_OK && location != null) {
|
||||
val reportableLocation = Location(location)
|
||||
for (key in reportableLocation.extras.keySet().toList()) {
|
||||
if (key?.startsWith("org.microg.nlp.") == true) {
|
||||
reportableLocation.extras.remove(key)
|
||||
}
|
||||
}
|
||||
Log.d(TAG, "reportLocation: $reportableLocation")
|
||||
reportLocation(reportableLocation)
|
||||
}
|
||||
}
|
||||
}
|
||||
private var opPackageName: String? = null
|
||||
private var autoTime = Long.MAX_VALUE
|
||||
private var autoUpdate = false
|
||||
|
||||
init {
|
||||
client.defaultOptions.putString("source", "LocationProvider")
|
||||
client.defaultOptions.putString("requestId", id)
|
||||
}
|
||||
|
||||
override fun onEnable() {
|
||||
Log.d(TAG, "onEnable")
|
||||
statusUpdateTime = SystemClock.elapsedRealtime()
|
||||
}
|
||||
|
||||
override fun onDisable() {
|
||||
Log.d(TAG, "onDisable")
|
||||
unsetRequest()
|
||||
statusUpdateTime = SystemClock.elapsedRealtime()
|
||||
}
|
||||
|
||||
override fun onSetRequest(requests: ProviderRequestUnbundled, source: WorkSource) {
|
||||
Log.v(TAG, "onSetRequest: $requests by $source")
|
||||
opPackageName = null
|
||||
try {
|
||||
val namesField = WorkSource::class.java.getDeclaredField("mNames")
|
||||
namesField.isAccessible = true
|
||||
val names = namesField[source] as Array<String>
|
||||
if (names != null) {
|
||||
for (name in names) {
|
||||
if (!EXCLUDED_PACKAGES.contains(name)) {
|
||||
opPackageName = name
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (ignored: Exception) {
|
||||
}
|
||||
autoTime = requests.interval.coerceAtLeast(FASTEST_REFRESH_INTERVAL)
|
||||
autoUpdate = requests.reportLocation
|
||||
Log.v(TAG, "using autoUpdate=$autoUpdate autoTime=$autoTime")
|
||||
lifecycleScope.launch {
|
||||
updateRequest()
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun updateRequest() {
|
||||
if (client.isConnected()) {
|
||||
if (autoUpdate) {
|
||||
client.packageName = opPackageName ?: context.packageName
|
||||
client.updateLocationRequest(LocationRequest(listener, autoTime, Int.MAX_VALUE, id))
|
||||
} else {
|
||||
client.cancelLocationRequestById(id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun unsetRequest() {
|
||||
lifecycleScope.launch {
|
||||
client.cancelLocationRequestById(id)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onGetStatus(extras: Bundle?): Int {
|
||||
return LocationProvider.AVAILABLE
|
||||
}
|
||||
|
||||
override fun onGetStatusUpdateTime(): Long {
|
||||
return statusUpdateTime
|
||||
}
|
||||
|
||||
override fun onSendExtraCommand(command: String?, extras: Bundle?): Boolean {
|
||||
Log.d(TAG, "onSendExtraCommand: $command, $extras")
|
||||
return false
|
||||
}
|
||||
|
||||
override fun onDump(fd: FileDescriptor?, pw: PrintWriter?, args: Array<out String>?) {
|
||||
dump(pw)
|
||||
}
|
||||
|
||||
fun dump(writer: PrintWriter?) {
|
||||
writer?.println("ID: $id")
|
||||
writer?.println("connected: ${client.isConnectedUnsafe}")
|
||||
writer?.println("active: $autoUpdate")
|
||||
writer?.println("interval: $autoTime")
|
||||
}
|
||||
|
||||
suspend fun connect() {
|
||||
Log.d(TAG, "Connecting to userspace service...")
|
||||
client.connect()
|
||||
updateRequest()
|
||||
Log.d(TAG, "Connected to userspace service.")
|
||||
}
|
||||
suspend fun disconnect() = client.disconnect()
|
||||
|
||||
override fun getLifecycle(): Lifecycle = lifecycle
|
||||
|
||||
companion object {
|
||||
private val EXCLUDED_PACKAGES = listOf("android", "com.android.location.fused", "com.google.android.gms")
|
||||
private const val FASTEST_REFRESH_INTERVAL: Long = 2500
|
||||
private const val TAG = "LocationProvider"
|
||||
}
|
||||
}
|
|
@ -1,33 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2019, microG Project Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package org.microg.nlp.location.v2;
|
||||
|
||||
import android.app.Service;
|
||||
import android.content.Intent;
|
||||
import android.os.IBinder;
|
||||
import android.util.Log;
|
||||
|
||||
public class LocationService extends Service {
|
||||
private static final String TAG = "ULocation";
|
||||
private LocationProvider provider;
|
||||
|
||||
@Override
|
||||
public synchronized IBinder onBind(Intent intent) {
|
||||
Log.d(TAG, "onBind: "+intent);
|
||||
if (provider == null) {
|
||||
provider = new LocationProvider(this);
|
||||
}
|
||||
return provider.getBinder();
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized boolean onUnbind(Intent intent) {
|
||||
if (provider != null) {
|
||||
provider.unsetRequest();
|
||||
}
|
||||
return super.onUnbind(intent);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2019, microG Project Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
package org.microg.nlp.location.v2
|
||||
|
||||
import androidx.lifecycle.LifecycleService
|
||||
import android.content.Intent
|
||||
import android.os.IBinder
|
||||
import android.util.Log
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import java.io.FileDescriptor
|
||||
import java.io.PrintWriter
|
||||
|
||||
open class LocationService : LifecycleService() {
|
||||
private lateinit var provider: LocationProvider
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
Log.d(TAG, "Creating system service...")
|
||||
provider = LocationProvider(this, lifecycle)
|
||||
lifecycleScope.launchWhenStarted { provider.connect() }
|
||||
Log.d(TAG, "Created system service.")
|
||||
}
|
||||
|
||||
override fun onBind(intent: Intent): IBinder? {
|
||||
super.onBind(intent)
|
||||
Log.d(TAG, "onBind: $intent")
|
||||
return provider.binder
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
runBlocking { provider.disconnect() }
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
override fun dump(fd: FileDescriptor?, writer: PrintWriter?, args: Array<out String>?) {
|
||||
if (!this::provider.isInitialized) {
|
||||
writer?.println("Not yet initialized")
|
||||
}
|
||||
provider.dump(writer)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "LocationService"
|
||||
}
|
||||
}
|
|
@ -4,6 +4,7 @@
|
|||
*/
|
||||
|
||||
apply plugin: 'com.android.library'
|
||||
apply plugin: 'kotlin-android'
|
||||
apply plugin: 'maven-publish'
|
||||
apply plugin: 'signing'
|
||||
|
||||
|
@ -29,4 +30,9 @@ description = 'UnifiedNlp service to implement Location API v3'
|
|||
|
||||
dependencies {
|
||||
implementation project(':location-v2')
|
||||
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"
|
||||
implementation "androidx.lifecycle:lifecycle-service:$lifecycleVersion"
|
||||
}
|
||||
|
|
|
@ -1,9 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2019, microG Project Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package org.microg.nlp.location.v3;
|
||||
|
||||
public class LocationService extends org.microg.nlp.location.v2.LocationService {
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2019, microG Project Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
package org.microg.nlp.location.v3
|
||||
|
||||
import org.microg.nlp.location.v2.LocationService
|
||||
|
||||
class LocationService : LocationService()
|
|
@ -14,8 +14,9 @@ apply plugin: 'signing'
|
|||
android {
|
||||
compileSdkVersion androidCompileSdk
|
||||
buildToolsVersion "$androidBuildVersionTools"
|
||||
dataBinding {
|
||||
enabled = true
|
||||
|
||||
buildFeatures {
|
||||
dataBinding = true
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
|
|
|
@ -23,7 +23,8 @@ 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.client.GeocodeClient
|
||||
import org.microg.nlp.client.LocationClient
|
||||
import org.microg.nlp.ui.model.BackendInfo
|
||||
import org.microg.nlp.ui.model.BackendType
|
||||
import java.security.MessageDigest
|
||||
|
@ -31,6 +32,7 @@ import java.security.NoSuchAlgorithmException
|
|||
|
||||
|
||||
private fun Array<String>.without(entry: BackendInfo): Array<String> = filterNot { it == entry.unsignedComponent || it.startsWith("${entry.unsignedComponent}/") }.toTypedArray()
|
||||
private fun List<String>.without(entry: BackendInfo): List<String> = filterNot { it == entry.unsignedComponent || it.startsWith("${entry.unsignedComponent}/") }
|
||||
|
||||
suspend fun BackendInfo.updateEnabled(fragment: Fragment, newValue: Boolean) {
|
||||
Log.d("USettings", "updateEnabled $signedComponent = $newValue")
|
||||
|
@ -107,20 +109,31 @@ private suspend fun BackendInfo.enable(fragment: Fragment): Boolean {
|
|||
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)
|
||||
when(type) {
|
||||
BackendType.LOCATION -> {
|
||||
val client = LocationClient(activity, activity.lifecycle)
|
||||
client.setLocationBackends(client.getLocationBackends().without(this) + signedComponent)
|
||||
}
|
||||
BackendType.GEOCODER -> {
|
||||
val client = GeocodeClient(activity, activity.lifecycle)
|
||||
client.setGeocodeBackends(client.getGeocodeBackends().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))
|
||||
val activity = fragment.requireActivity() as AppCompatActivity
|
||||
when(type) {
|
||||
BackendType.LOCATION -> {
|
||||
val client = LocationClient(activity, activity.lifecycle)
|
||||
client.setLocationBackends(client.getLocationBackends().without(this))
|
||||
}
|
||||
BackendType.GEOCODER -> {
|
||||
val client = GeocodeClient(activity, activity.lifecycle)
|
||||
client.setGeocodeBackends(client.getGeocodeBackends().without(this))
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
|
|
@ -26,14 +26,16 @@ import androidx.core.content.ContextCompat
|
|||
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.model.BackendType.GEOCODER
|
||||
import org.microg.nlp.ui.model.BackendType.LOCATION
|
||||
import org.microg.nlp.client.GeocodeClient
|
||||
import org.microg.nlp.client.LocationClient
|
||||
import org.microg.nlp.service.api.LatLon
|
||||
import org.microg.nlp.service.api.ReverseGeocodeRequest
|
||||
import org.microg.nlp.ui.databinding.BackendDetailsBinding
|
||||
import org.microg.nlp.ui.model.BackendDetailsCallback
|
||||
import org.microg.nlp.ui.model.BackendInfo
|
||||
import org.microg.nlp.ui.model.BackendType
|
||||
import org.microg.nlp.ui.model.BackendType.GEOCODER
|
||||
import org.microg.nlp.ui.model.BackendType.LOCATION
|
||||
import java.util.*
|
||||
|
||||
class BackendDetailsFragment : Fragment(R.layout.backend_details), BackendDetailsCallback {
|
||||
|
@ -80,6 +82,8 @@ class BackendDetailsFragment : Fragment(R.layout.backend_details), BackendDetail
|
|||
}
|
||||
|
||||
private lateinit var binding: BackendDetailsBinding
|
||||
private lateinit var locationClient: LocationClient
|
||||
private lateinit var geocodeClient: GeocodeClient
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
binding = BackendDetailsBinding.inflate(inflater, container, false)
|
||||
|
@ -90,13 +94,13 @@ class BackendDetailsFragment : Fragment(R.layout.backend_details), BackendDetail
|
|||
override fun onResume() {
|
||||
super.onResume()
|
||||
binding.switchWidget.trackTintList = switchBarTrackTintColor
|
||||
UnifiedLocationClient[requireContext()].ref()
|
||||
locationClient = LocationClient(requireContext(), lifecycle)
|
||||
geocodeClient = GeocodeClient(requireContext(), lifecycle)
|
||||
lifecycleScope.launchWhenStarted { initContent(createBackendInfo()) }
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
UnifiedLocationClient[requireContext()].unref()
|
||||
}
|
||||
|
||||
private suspend fun initContent(entry: BackendInfo?) {
|
||||
|
@ -115,20 +119,18 @@ class BackendDetailsFragment : Fragment(R.layout.backend_details), BackendDetail
|
|||
if (entry.type == LOCATION && entry.enabled.get()) {
|
||||
if (updateInProgress) return
|
||||
updateInProgress = true
|
||||
val client = UnifiedLocationClient[requireContext()]
|
||||
|
||||
val locationTemp = client.getLastLocationForBackend(entry.serviceInfo.packageName, entry.serviceInfo.name, entry.firstSignatureDigest)
|
||||
val locationTemp = locationClient.getLastLocationForBackend(entry.serviceInfo.packageName, entry.serviceInfo.name, entry.firstSignatureDigest)
|
||||
val location = when (locationTemp) {
|
||||
null -> {
|
||||
delay(500L) // Wait short time to ensure backend was activated
|
||||
Log.d(TAG, "Location was not available, requesting once")
|
||||
client.forceNextUpdate = true
|
||||
client.getSingleLocation()
|
||||
val secondAttempt = client.getLastLocationForBackend(entry.serviceInfo.packageName, entry.serviceInfo.name, entry.firstSignatureDigest)
|
||||
locationClient.forceLocationUpdate()
|
||||
val secondAttempt = locationClient.getLastLocationForBackend(entry.serviceInfo.packageName, entry.serviceInfo.name, entry.firstSignatureDigest)
|
||||
if (secondAttempt == null) {
|
||||
Log.d(TAG, "Location still not available, waiting or giving up")
|
||||
delay(WAIT_FOR_RESULT)
|
||||
client.getLastLocationForBackend(entry.serviceInfo.packageName, entry.serviceInfo.name, entry.firstSignatureDigest)
|
||||
locationClient.getLastLocationForBackend(entry.serviceInfo.packageName, entry.serviceInfo.name, entry.firstSignatureDigest)
|
||||
} else {
|
||||
secondAttempt
|
||||
}
|
||||
|
@ -137,7 +139,7 @@ class BackendDetailsFragment : Fragment(R.layout.backend_details), BackendDetail
|
|||
} ?: return
|
||||
var locationString = "${location.latitude.toStringWithDigits(6)}, ${location.longitude.toStringWithDigits(6)}"
|
||||
|
||||
val address = client.getFromLocation(location.latitude, location.longitude, 1, Locale.getDefault().toString()).singleOrNull()
|
||||
val address = geocodeClient.requestReverseGeocode(ReverseGeocodeRequest(LatLon(location.latitude, location.longitude))).singleOrNull()
|
||||
if (address != null) {
|
||||
val addressLine = StringBuilder()
|
||||
var i = 0
|
||||
|
@ -175,8 +177,8 @@ class BackendDetailsFragment : Fragment(R.layout.backend_details), BackendDetail
|
|||
val serviceInfo = context?.packageManager?.getServiceInfo(ComponentName(packageName, name), GET_META_DATA)
|
||||
?: return null
|
||||
val enabledBackends = when (type) {
|
||||
GEOCODER -> UnifiedLocationClient[requireContext()].getGeocoderBackends()
|
||||
LOCATION -> UnifiedLocationClient[requireContext()].getLocationBackends()
|
||||
LOCATION -> locationClient.getLocationBackends()
|
||||
GEOCODER -> geocodeClient.getGeocodeBackends()
|
||||
}
|
||||
val info = BackendInfo(serviceInfo, type, firstSignatureDigest(requireContext(), packageName))
|
||||
info.enabled.set(enabledBackends.contains(info.signedComponent) || enabledBackends.contains(info.unsignedComponent))
|
||||
|
@ -186,9 +188,8 @@ class BackendDetailsFragment : Fragment(R.layout.backend_details), BackendDetail
|
|||
override fun onEnabledChange(entry: BackendInfo?, newValue: Boolean) {
|
||||
if (entry?.enabled?.get() == newValue) return
|
||||
Log.d(TAG, "onEnabledChange: ${entry?.signedComponent} = $newValue")
|
||||
val activity = requireActivity() as AppCompatActivity
|
||||
entry?.enabled?.set(newValue)
|
||||
activity.lifecycleScope.launch {
|
||||
lifecycleScope.launchWhenStarted {
|
||||
entry?.updateEnabled(this@BackendDetailsFragment, newValue)
|
||||
initContent(entry)
|
||||
}
|
||||
|
|
|
@ -23,10 +23,10 @@ import androidx.navigation.NavController
|
|||
import androidx.navigation.fragment.findNavController
|
||||
import androidx.navigation.navOptions
|
||||
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.client.GeocodeClient
|
||||
import org.microg.nlp.client.LocationClient
|
||||
import org.microg.nlp.ui.databinding.BackendListBinding
|
||||
import org.microg.nlp.ui.databinding.BackendListEntryBinding
|
||||
import org.microg.nlp.ui.model.BackendInfo
|
||||
|
@ -45,13 +45,11 @@ class BackendListFragment : Fragment(R.layout.backend_list), BackendListEntryCal
|
|||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
UnifiedLocationClient[requireContext()].ref()
|
||||
lifecycleScope.launchWhenStarted { updateAdapters() }
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
UnifiedLocationClient[requireContext()].unref()
|
||||
}
|
||||
|
||||
override fun onOpenDetails(entry: BackendInfo?) {
|
||||
|
@ -65,25 +63,25 @@ class BackendListFragment : Fragment(R.layout.backend_list), BackendListEntryCal
|
|||
|
||||
override fun onEnabledChange(entry: BackendInfo?, newValue: Boolean) {
|
||||
val activity = requireActivity() as AppCompatActivity
|
||||
activity.lifecycleScope.launch {
|
||||
lifecycleScope.launchWhenStarted {
|
||||
entry?.updateEnabled(this@BackendListFragment, newValue)
|
||||
}
|
||||
}
|
||||
|
||||
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))
|
||||
locationAdapter.setEntries(createBackendInfoList(activity, Intent(ACTION_LOCATION_BACKEND), LocationClient(activity, lifecycle).getLocationBackends(), BackendType.LOCATION))
|
||||
geocoderAdapter.setEntries(createBackendInfoList(activity, Intent(ACTION_GEOCODER_BACKEND), GeocodeClient(activity, lifecycle).getGeocodeBackends(), BackendType.GEOCODER))
|
||||
}
|
||||
|
||||
private fun createBackendInfoList(activity: AppCompatActivity, intent: Intent, enabledBackends: Array<String>, type: BackendType): Array<BackendInfo?> {
|
||||
private fun createBackendInfoList(activity: AppCompatActivity, intent: Intent, enabledBackends: List<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 {
|
||||
lifecycleScope.launchWhenStarted {
|
||||
info.loadIntents(activity)
|
||||
}
|
||||
info
|
||||
|
|
Loading…
Add table
Reference in a new issue