Adjust code for new service API
This commit is contained in:
parent
d092182576
commit
3845b43f07
|
@ -5,7 +5,6 @@
|
||||||
|
|
||||||
apply plugin: 'com.android.library'
|
apply plugin: 'com.android.library'
|
||||||
apply plugin: 'kotlin-android'
|
apply plugin: 'kotlin-android'
|
||||||
apply plugin: 'kotlin-android-extensions'
|
|
||||||
apply plugin: 'maven-publish'
|
apply plugin: 'maven-publish'
|
||||||
apply plugin: 'signing'
|
apply plugin: 'signing'
|
||||||
|
|
||||||
|
@ -34,6 +33,9 @@ apply from: '../gradle/publish.gradle'
|
||||||
description = 'UnifiedNlp client library'
|
description = 'UnifiedNlp client library'
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
api project(":service-api")
|
||||||
|
|
||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlinVersion"
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlinVersion"
|
||||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutineVersion"
|
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.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.ServiceConnection
|
import android.content.ServiceConnection
|
||||||
import android.content.pm.ApplicationInfo
|
|
||||||
import android.content.pm.ResolveInfo
|
|
||||||
import android.location.Address
|
import android.location.Address
|
||||||
import android.location.Location
|
import android.location.Location
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
@ -18,37 +16,31 @@ import android.os.DeadObjectException
|
||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
import android.os.RemoteException
|
import android.os.RemoteException
|
||||||
import android.util.Log
|
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.AddressCallback
|
||||||
import org.microg.nlp.service.LocationCallback
|
import org.microg.nlp.service.LocationCallback
|
||||||
import org.microg.nlp.service.UnifiedLocationService
|
import org.microg.nlp.service.UnifiedLocationService
|
||||||
|
import java.util.*
|
||||||
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.concurrent.*
|
import java.util.concurrent.*
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger
|
||||||
import kotlin.coroutines.*
|
import kotlin.coroutines.*
|
||||||
import kotlin.math.min
|
|
||||||
|
|
||||||
private const val CALL_TIMEOUT = 10000L
|
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 var bound = false
|
||||||
private val serviceReferenceCount = AtomicInteger(0)
|
private val serviceReferenceCount = AtomicInteger(0)
|
||||||
private val options = Bundle()
|
private val options = Bundle()
|
||||||
private var context: WeakReference<Context> = WeakReference(context)
|
|
||||||
private var service: UnifiedLocationService? = null
|
private var service: UnifiedLocationService? = null
|
||||||
private val syncThreads: ThreadPoolExecutor = ThreadPoolExecutor(1, Runtime.getRuntime().availableProcessors(), 1, TimeUnit.SECONDS, LinkedBlockingQueue())
|
private val syncThreads: ThreadPoolExecutor = ThreadPoolExecutor(1, Runtime.getRuntime().availableProcessors(), 1, TimeUnit.SECONDS, LinkedBlockingQueue())
|
||||||
private val waitingForService = arrayListOf<Continuation<UnifiedLocationService>>()
|
private val waitingForService = arrayListOf<Continuation<UnifiedLocationService>>()
|
||||||
private var timer: Timer? = null
|
private var timer: Timer? = null
|
||||||
private var reconnectCount = 0
|
private var reconnectCount = 0
|
||||||
private val requests = CopyOnWriteArraySet<LocationRequest>()
|
private val requests = CopyOnWriteArraySet<LocationRequest>()
|
||||||
private val coroutineScope = CoroutineScope(Dispatchers.IO + Job())
|
|
||||||
private val connection = object : ServiceConnection {
|
private val connection = object : ServiceConnection {
|
||||||
override fun onServiceConnected(name: ComponentName, service: IBinder) {
|
override fun onServiceConnected(name: ComponentName, service: IBinder) {
|
||||||
this@UnifiedLocationClient.onServiceConnected(name, service)
|
this@UnifiedLocationClient.onServiceConnected(name, service)
|
||||||
|
@ -82,56 +74,7 @@ class UnifiedLocationClient private constructor(context: Context) {
|
||||||
val targetPackage: String?
|
val targetPackage: String?
|
||||||
get() = resolve()?.`package`
|
get() = resolve()?.`package`
|
||||||
|
|
||||||
private fun resolve(): Intent? {
|
private fun resolve(): Intent? = resolveIntent(context, ACTION_UNIFIED_LOCATION_SERVICE)
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
private fun updateBinding(): Boolean {
|
private fun updateBinding(): Boolean {
|
||||||
|
@ -167,7 +110,6 @@ class UnifiedLocationClient private constructor(context: Context) {
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
private fun bind() {
|
private fun bind() {
|
||||||
val context = this.context.get() ?: return
|
|
||||||
if (!bound) {
|
if (!bound) {
|
||||||
Log.w(TAG, "Tried to bind while not being bound!")
|
Log.w(TAG, "Tried to bind while not being bound!")
|
||||||
return
|
return
|
||||||
|
@ -186,7 +128,7 @@ class UnifiedLocationClient private constructor(context: Context) {
|
||||||
@Synchronized
|
@Synchronized
|
||||||
private fun unbind() {
|
private fun unbind() {
|
||||||
try {
|
try {
|
||||||
this.context.get()?.unbindService(connection)
|
this.context.unbindService(connection)
|
||||||
} catch (ignored: Exception) {
|
} catch (ignored: Exception) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -207,29 +149,34 @@ class UnifiedLocationClient private constructor(context: Context) {
|
||||||
updateBinding()
|
updateBinding()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated("Use LocationClient")
|
||||||
suspend fun getSingleLocation(): Location = suspendCoroutine { continuation ->
|
suspend fun getSingleLocation(): Location = suspendCoroutine { continuation ->
|
||||||
requestSingleLocation(LocationListener.wrap { continuation.resume(it) })
|
requestSingleLocation(LocationListener.wrap { continuation.resume(it) })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated("Use LocationClient")
|
||||||
fun requestSingleLocation(listener: LocationListener) {
|
fun requestSingleLocation(listener: LocationListener) {
|
||||||
requestLocationUpdates(listener, 0, 1)
|
requestLocationUpdates(listener, 0, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated("Use LocationClient")
|
||||||
fun requestLocationUpdates(listener: LocationListener, interval: Long) {
|
fun requestLocationUpdates(listener: LocationListener, interval: Long) {
|
||||||
requestLocationUpdates(listener, interval, Integer.MAX_VALUE)
|
requestLocationUpdates(listener, interval, Integer.MAX_VALUE)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated("Use LocationClient")
|
||||||
fun requestLocationUpdates(listener: LocationListener, interval: Long, count: Int) {
|
fun requestLocationUpdates(listener: LocationListener, interval: Long, count: Int) {
|
||||||
requests.removeAll(requests.filter { it.listener === listener })
|
requests.removeAll(requests.filter { it.listener === listener })
|
||||||
requests.add(LocationRequest(listener, interval, count))
|
requests.add(LocationRequest(listener, interval, count))
|
||||||
coroutineScope.launch {
|
lifecycleScope.launchWhenStarted {
|
||||||
updateServiceInterval()
|
updateServiceInterval()
|
||||||
updateBinding()
|
updateBinding()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated("Use LocationClient")
|
||||||
fun removeLocationUpdates(listener: LocationListener) {
|
fun removeLocationUpdates(listener: LocationListener) {
|
||||||
coroutineScope.launch {
|
lifecycleScope.launchWhenStarted {
|
||||||
removeRequests(requests.filter { it.listener === listener })
|
removeRequests(requests.filter { it.listener === listener })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -310,6 +257,7 @@ class UnifiedLocationClient private constructor(context: Context) {
|
||||||
return result ?: throw NullPointerException()
|
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> {
|
suspend fun getFromLocation(latitude: Double, longitude: Double, maxResults: Int, locale: String, timeout: Long = Long.MAX_VALUE): List<Address> {
|
||||||
try {
|
try {
|
||||||
val service = refAndGetService()
|
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) {
|
fun getFromLocationSync(latitude: Double, longitude: Double, maxResults: Int, locale: String, timeout: Long = CALL_TIMEOUT): List<Address> = executeSyncWithTimeout(timeout) {
|
||||||
getFromLocation(latitude, longitude, maxResults, locale, 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> {
|
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 {
|
return try {
|
||||||
val service = refAndGetService()
|
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) {
|
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)
|
getFromLocationName(locationName, maxResults, lowerLeftLatitude, lowerLeftLongitude, upperRightLatitude, upperRightLongitude, locale, timeout)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun reloadPreferences() {
|
@Deprecated("Use LocationClient")
|
||||||
try {
|
|
||||||
refAndGetService().reloadPreferences()
|
|
||||||
} catch (e: RemoteException) {
|
|
||||||
Log.w(TAG, "Failed to handle request", e)
|
|
||||||
} finally {
|
|
||||||
unref()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun getLocationBackends(): Array<String> {
|
suspend fun getLocationBackends(): Array<String> {
|
||||||
try {
|
try {
|
||||||
return refAndGetService().locationBackends
|
return refAndGetService().locationBackends
|
||||||
|
@ -369,6 +311,7 @@ class UnifiedLocationClient private constructor(context: Context) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated("Use LocationClient")
|
||||||
suspend fun setLocationBackends(backends: Array<String>) {
|
suspend fun setLocationBackends(backends: Array<String>) {
|
||||||
try {
|
try {
|
||||||
refAndGetService().locationBackends = backends
|
refAndGetService().locationBackends = backends
|
||||||
|
@ -379,6 +322,7 @@ class UnifiedLocationClient private constructor(context: Context) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated("Use GeocoderClient")
|
||||||
suspend fun getGeocoderBackends(): Array<String> {
|
suspend fun getGeocoderBackends(): Array<String> {
|
||||||
try {
|
try {
|
||||||
return refAndGetService().geocoderBackends
|
return refAndGetService().geocoderBackends
|
||||||
|
@ -390,6 +334,7 @@ class UnifiedLocationClient private constructor(context: Context) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated("Use GeocoderClient")
|
||||||
suspend fun setGeocoderBackends(backends: Array<String>) {
|
suspend fun setGeocoderBackends(backends: Array<String>) {
|
||||||
try {
|
try {
|
||||||
refAndGetService().geocoderBackends = backends
|
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) {
|
fun getLastLocationSync(timeout: Long = CALL_TIMEOUT): Location? = executeSyncWithTimeout(timeout) {
|
||||||
getLastLocation()
|
getLastLocation()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated("Use LocationClient")
|
||||||
suspend fun getLastLocation(): Location? {
|
suspend fun getLastLocation(): Location? {
|
||||||
return try {
|
return try {
|
||||||
refAndGetService().lastLocation
|
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? {
|
suspend fun getLastLocationForBackend(packageName: String, className: String, signatureDigest: String? = null): Location? {
|
||||||
return try {
|
return try {
|
||||||
refAndGetService().getLastLocationForBackend(packageName, className, signatureDigest)
|
refAndGetService().getLastLocationForBackend(packageName, className, signatureDigest)
|
||||||
|
@ -439,30 +387,32 @@ class UnifiedLocationClient private constructor(context: Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun updateServiceInterval() {
|
private suspend fun updateServiceInterval() {
|
||||||
var interval: Long = Long.MAX_VALUE
|
var minTime = Long.MAX_VALUE
|
||||||
var requestSingle = false
|
var requestSingle = false
|
||||||
for (request in requests) {
|
for (request in requests) {
|
||||||
if (request.interval <= 0) {
|
if (request.interval <= 0) {
|
||||||
requestSingle = true
|
requestSingle = true
|
||||||
|
forceNextUpdate = true
|
||||||
continue
|
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")
|
Log.d(TAG, "Disable automatic updates")
|
||||||
interval = 0
|
minTime = 0
|
||||||
} else {
|
} else {
|
||||||
Log.d(TAG, "Set update interval to $interval")
|
Log.d(TAG, "Set update interval to $minTime")
|
||||||
}
|
}
|
||||||
val service: UnifiedLocationService
|
val service = try {
|
||||||
try {
|
waitForService()
|
||||||
service = waitForService()
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.w(TAG, e)
|
Log.w(TAG, e)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
service.setUpdateInterval(interval, options)
|
service.setUpdateInterval(minTime, options)
|
||||||
if (requestSingle) {
|
if (requestSingle) {
|
||||||
Log.d(TAG, "Request single update (force update: $forceNextUpdate)")
|
Log.d(TAG, "Request single update (force update: $forceNextUpdate)")
|
||||||
service.requestSingleUpdate(options)
|
service.requestSingleUpdate(options)
|
||||||
|
@ -487,12 +437,12 @@ class UnifiedLocationClient private constructor(context: Context) {
|
||||||
continuations.addAll(waitingForService)
|
continuations.addAll(waitingForService)
|
||||||
waitingForService.clear()
|
waitingForService.clear()
|
||||||
}
|
}
|
||||||
coroutineScope.launch {
|
lifecycleScope.launchWhenStarted {
|
||||||
try {
|
try {
|
||||||
Log.d(TAG, "Registering location callback")
|
Log.d(TAG, "Registering location callback")
|
||||||
service.registerLocationCallback(object : LocationCallback.Stub() {
|
service.registerLocationCallback(object : LocationCallback.Stub() {
|
||||||
override fun onLocationUpdate(location: Location) {
|
override fun onLocationUpdate(location: Location) {
|
||||||
coroutineScope.launch {
|
lifecycleScope.launchWhenStarted {
|
||||||
this@UnifiedLocationClient.onLocationUpdate(location)
|
this@UnifiedLocationClient.onLocationUpdate(location)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -538,6 +488,8 @@ class UnifiedLocationClient private constructor(context: Context) {
|
||||||
bindLater()
|
bindLater()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun getLifecycle(): Lifecycle = lifecycle
|
||||||
|
|
||||||
interface LocationListener {
|
interface LocationListener {
|
||||||
fun onLocation(location: Location)
|
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_FORCE_NEXT_UPDATE = "org.microg.nlp.FORCE_NEXT_UPDATE"
|
||||||
const val KEY_OP_PACKAGE_NAME = "org.microg.nlp.OP_PACKAGE_NAME"
|
const val KEY_OP_PACKAGE_NAME = "org.microg.nlp.OP_PACKAGE_NAME"
|
||||||
private val TAG = "ULocClient"
|
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,15 @@ if (!sdkDir) {
|
||||||
sdkDir = properties.getProperty('sdk.dir')
|
sdkDir = properties.getProperty('sdk.dir')
|
||||||
}
|
}
|
||||||
|
|
||||||
sourceSets.main {
|
sourceSets {
|
||||||
java.srcDirs = ['src/current/java', 'src/v9/java']
|
main {
|
||||||
compileClasspath += project.rootProject.files("$sdkDir/platforms/android-$androidCompileSdk/android.jar")
|
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: 'com.android.library'
|
||||||
|
apply plugin: 'kotlin-android'
|
||||||
apply plugin: 'maven-publish'
|
apply plugin: 'maven-publish'
|
||||||
apply plugin: 'signing'
|
apply plugin: 'signing'
|
||||||
|
|
||||||
|
@ -30,4 +31,9 @@ description = 'UnifiedNlp service to implement Geocode API v1'
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation project(':client')
|
implementation project(':client')
|
||||||
compileOnly project(':compat')
|
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: 'com.android.library'
|
||||||
|
apply plugin: 'kotlin-android'
|
||||||
apply plugin: 'maven-publish'
|
apply plugin: 'maven-publish'
|
||||||
apply plugin: 'signing'
|
apply plugin: 'signing'
|
||||||
|
|
||||||
|
@ -30,4 +31,9 @@ description = 'UnifiedNlp service to implement Location API v2'
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation project(':client')
|
implementation project(':client')
|
||||||
compileOnly project(':compat')
|
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: 'com.android.library'
|
||||||
|
apply plugin: 'kotlin-android'
|
||||||
apply plugin: 'maven-publish'
|
apply plugin: 'maven-publish'
|
||||||
apply plugin: 'signing'
|
apply plugin: 'signing'
|
||||||
|
|
||||||
|
@ -29,4 +30,9 @@ description = 'UnifiedNlp service to implement Location API v3'
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation project(':location-v2')
|
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 {
|
android {
|
||||||
compileSdkVersion androidCompileSdk
|
compileSdkVersion androidCompileSdk
|
||||||
buildToolsVersion "$androidBuildVersionTools"
|
buildToolsVersion "$androidBuildVersionTools"
|
||||||
dataBinding {
|
|
||||||
enabled = true
|
buildFeatures {
|
||||||
|
dataBinding = true
|
||||||
}
|
}
|
||||||
|
|
||||||
defaultConfig {
|
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.Constants.ACTION_LOCATION_BACKEND
|
||||||
import org.microg.nlp.api.GeocoderBackend
|
import org.microg.nlp.api.GeocoderBackend
|
||||||
import org.microg.nlp.api.LocationBackend
|
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.BackendInfo
|
||||||
import org.microg.nlp.ui.model.BackendType
|
import org.microg.nlp.ui.model.BackendType
|
||||||
import java.security.MessageDigest
|
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 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) {
|
suspend fun BackendInfo.updateEnabled(fragment: Fragment, newValue: Boolean) {
|
||||||
Log.d("USettings", "updateEnabled $signedComponent = $newValue")
|
Log.d("USettings", "updateEnabled $signedComponent = $newValue")
|
||||||
|
@ -107,20 +109,31 @@ private suspend fun BackendInfo.enable(fragment: Fragment): Boolean {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val client = UnifiedLocationClient[activity]
|
when(type) {
|
||||||
when (type) {
|
BackendType.LOCATION -> {
|
||||||
BackendType.LOCATION -> client.setLocationBackends(client.getLocationBackends().without(this) + signedComponent)
|
val client = LocationClient(activity, activity.lifecycle)
|
||||||
BackendType.GEOCODER -> client.setGeocoderBackends(client.getGeocoderBackends().without(this) + signedComponent)
|
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")
|
Log.w("USettings", "Enabled backend $signedComponent")
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun BackendInfo.disable(fragment: Fragment): Boolean {
|
private suspend fun BackendInfo.disable(fragment: Fragment): Boolean {
|
||||||
val client = UnifiedLocationClient[fragment.requireContext()]
|
val activity = fragment.requireActivity() as AppCompatActivity
|
||||||
when (type) {
|
when(type) {
|
||||||
BackendType.LOCATION -> client.setLocationBackends(client.getLocationBackends().without(this))
|
BackendType.LOCATION -> {
|
||||||
BackendType.GEOCODER -> client.setGeocoderBackends(client.getGeocoderBackends().without(this))
|
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
|
return true
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,14 +26,16 @@ import androidx.core.content.ContextCompat
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import org.microg.nlp.client.GeocodeClient
|
||||||
import org.microg.nlp.client.UnifiedLocationClient
|
import org.microg.nlp.client.LocationClient
|
||||||
import org.microg.nlp.ui.model.BackendType.GEOCODER
|
import org.microg.nlp.service.api.LatLon
|
||||||
import org.microg.nlp.ui.model.BackendType.LOCATION
|
import org.microg.nlp.service.api.ReverseGeocodeRequest
|
||||||
import org.microg.nlp.ui.databinding.BackendDetailsBinding
|
import org.microg.nlp.ui.databinding.BackendDetailsBinding
|
||||||
import org.microg.nlp.ui.model.BackendDetailsCallback
|
import org.microg.nlp.ui.model.BackendDetailsCallback
|
||||||
import org.microg.nlp.ui.model.BackendInfo
|
import org.microg.nlp.ui.model.BackendInfo
|
||||||
import org.microg.nlp.ui.model.BackendType
|
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.*
|
import java.util.*
|
||||||
|
|
||||||
class BackendDetailsFragment : Fragment(R.layout.backend_details), BackendDetailsCallback {
|
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 binding: BackendDetailsBinding
|
||||||
|
private lateinit var locationClient: LocationClient
|
||||||
|
private lateinit var geocodeClient: GeocodeClient
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||||
binding = BackendDetailsBinding.inflate(inflater, container, false)
|
binding = BackendDetailsBinding.inflate(inflater, container, false)
|
||||||
|
@ -90,13 +94,13 @@ class BackendDetailsFragment : Fragment(R.layout.backend_details), BackendDetail
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
super.onResume()
|
super.onResume()
|
||||||
binding.switchWidget.trackTintList = switchBarTrackTintColor
|
binding.switchWidget.trackTintList = switchBarTrackTintColor
|
||||||
UnifiedLocationClient[requireContext()].ref()
|
locationClient = LocationClient(requireContext(), lifecycle)
|
||||||
|
geocodeClient = GeocodeClient(requireContext(), lifecycle)
|
||||||
lifecycleScope.launchWhenStarted { initContent(createBackendInfo()) }
|
lifecycleScope.launchWhenStarted { initContent(createBackendInfo()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPause() {
|
override fun onPause() {
|
||||||
super.onPause()
|
super.onPause()
|
||||||
UnifiedLocationClient[requireContext()].unref()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun initContent(entry: BackendInfo?) {
|
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 (entry.type == LOCATION && entry.enabled.get()) {
|
||||||
if (updateInProgress) return
|
if (updateInProgress) return
|
||||||
updateInProgress = true
|
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) {
|
val location = when (locationTemp) {
|
||||||
null -> {
|
null -> {
|
||||||
delay(500L) // Wait short time to ensure backend was activated
|
delay(500L) // Wait short time to ensure backend was activated
|
||||||
Log.d(TAG, "Location was not available, requesting once")
|
Log.d(TAG, "Location was not available, requesting once")
|
||||||
client.forceNextUpdate = true
|
locationClient.forceLocationUpdate()
|
||||||
client.getSingleLocation()
|
val secondAttempt = locationClient.getLastLocationForBackend(entry.serviceInfo.packageName, entry.serviceInfo.name, entry.firstSignatureDigest)
|
||||||
val secondAttempt = client.getLastLocationForBackend(entry.serviceInfo.packageName, entry.serviceInfo.name, entry.firstSignatureDigest)
|
|
||||||
if (secondAttempt == null) {
|
if (secondAttempt == null) {
|
||||||
Log.d(TAG, "Location still not available, waiting or giving up")
|
Log.d(TAG, "Location still not available, waiting or giving up")
|
||||||
delay(WAIT_FOR_RESULT)
|
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 {
|
} else {
|
||||||
secondAttempt
|
secondAttempt
|
||||||
}
|
}
|
||||||
|
@ -137,7 +139,7 @@ class BackendDetailsFragment : Fragment(R.layout.backend_details), BackendDetail
|
||||||
} ?: return
|
} ?: return
|
||||||
var locationString = "${location.latitude.toStringWithDigits(6)}, ${location.longitude.toStringWithDigits(6)}"
|
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) {
|
if (address != null) {
|
||||||
val addressLine = StringBuilder()
|
val addressLine = StringBuilder()
|
||||||
var i = 0
|
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)
|
val serviceInfo = context?.packageManager?.getServiceInfo(ComponentName(packageName, name), GET_META_DATA)
|
||||||
?: return null
|
?: return null
|
||||||
val enabledBackends = when (type) {
|
val enabledBackends = when (type) {
|
||||||
GEOCODER -> UnifiedLocationClient[requireContext()].getGeocoderBackends()
|
LOCATION -> locationClient.getLocationBackends()
|
||||||
LOCATION -> UnifiedLocationClient[requireContext()].getLocationBackends()
|
GEOCODER -> geocodeClient.getGeocodeBackends()
|
||||||
}
|
}
|
||||||
val info = BackendInfo(serviceInfo, type, firstSignatureDigest(requireContext(), packageName))
|
val info = BackendInfo(serviceInfo, type, firstSignatureDigest(requireContext(), packageName))
|
||||||
info.enabled.set(enabledBackends.contains(info.signedComponent) || enabledBackends.contains(info.unsignedComponent))
|
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) {
|
override fun onEnabledChange(entry: BackendInfo?, newValue: Boolean) {
|
||||||
if (entry?.enabled?.get() == newValue) return
|
if (entry?.enabled?.get() == newValue) return
|
||||||
Log.d(TAG, "onEnabledChange: ${entry?.signedComponent} = $newValue")
|
Log.d(TAG, "onEnabledChange: ${entry?.signedComponent} = $newValue")
|
||||||
val activity = requireActivity() as AppCompatActivity
|
|
||||||
entry?.enabled?.set(newValue)
|
entry?.enabled?.set(newValue)
|
||||||
activity.lifecycleScope.launch {
|
lifecycleScope.launchWhenStarted {
|
||||||
entry?.updateEnabled(this@BackendDetailsFragment, newValue)
|
entry?.updateEnabled(this@BackendDetailsFragment, newValue)
|
||||||
initContent(entry)
|
initContent(entry)
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,10 +23,10 @@ import androidx.navigation.NavController
|
||||||
import androidx.navigation.fragment.findNavController
|
import androidx.navigation.fragment.findNavController
|
||||||
import androidx.navigation.navOptions
|
import androidx.navigation.navOptions
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
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_GEOCODER_BACKEND
|
||||||
import org.microg.nlp.api.Constants.ACTION_LOCATION_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.BackendListBinding
|
||||||
import org.microg.nlp.ui.databinding.BackendListEntryBinding
|
import org.microg.nlp.ui.databinding.BackendListEntryBinding
|
||||||
import org.microg.nlp.ui.model.BackendInfo
|
import org.microg.nlp.ui.model.BackendInfo
|
||||||
|
@ -45,13 +45,11 @@ class BackendListFragment : Fragment(R.layout.backend_list), BackendListEntryCal
|
||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
super.onResume()
|
super.onResume()
|
||||||
UnifiedLocationClient[requireContext()].ref()
|
|
||||||
lifecycleScope.launchWhenStarted { updateAdapters() }
|
lifecycleScope.launchWhenStarted { updateAdapters() }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPause() {
|
override fun onPause() {
|
||||||
super.onPause()
|
super.onPause()
|
||||||
UnifiedLocationClient[requireContext()].unref()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onOpenDetails(entry: BackendInfo?) {
|
override fun onOpenDetails(entry: BackendInfo?) {
|
||||||
|
@ -65,25 +63,25 @@ class BackendListFragment : Fragment(R.layout.backend_list), BackendListEntryCal
|
||||||
|
|
||||||
override fun onEnabledChange(entry: BackendInfo?, newValue: Boolean) {
|
override fun onEnabledChange(entry: BackendInfo?, newValue: Boolean) {
|
||||||
val activity = requireActivity() as AppCompatActivity
|
val activity = requireActivity() as AppCompatActivity
|
||||||
activity.lifecycleScope.launch {
|
lifecycleScope.launchWhenStarted {
|
||||||
entry?.updateEnabled(this@BackendListFragment, newValue)
|
entry?.updateEnabled(this@BackendListFragment, newValue)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun updateAdapters() {
|
private suspend fun updateAdapters() {
|
||||||
val activity = requireActivity() as AppCompatActivity
|
val activity = requireActivity() as AppCompatActivity
|
||||||
locationAdapter.setEntries(createBackendInfoList(activity, Intent(ACTION_LOCATION_BACKEND), UnifiedLocationClient[activity].getLocationBackends(), BackendType.LOCATION))
|
locationAdapter.setEntries(createBackendInfoList(activity, Intent(ACTION_LOCATION_BACKEND), LocationClient(activity, lifecycle).getLocationBackends(), BackendType.LOCATION))
|
||||||
geocoderAdapter.setEntries(createBackendInfoList(activity, Intent(ACTION_GEOCODER_BACKEND), UnifiedLocationClient[activity].getGeocoderBackends(), BackendType.GEOCODER))
|
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 backends = activity.packageManager.queryIntentServices(intent, GET_META_DATA).map {
|
||||||
val info = BackendInfo(it.serviceInfo, type, firstSignatureDigest(activity, it.serviceInfo.packageName))
|
val info = BackendInfo(it.serviceInfo, type, firstSignatureDigest(activity, it.serviceInfo.packageName))
|
||||||
if (enabledBackends.contains(info.signedComponent) || enabledBackends.contains(info.unsignedComponent)) {
|
if (enabledBackends.contains(info.signedComponent) || enabledBackends.contains(info.unsignedComponent)) {
|
||||||
info.enabled.set(true)
|
info.enabled.set(true)
|
||||||
}
|
}
|
||||||
info.fillDetails(activity)
|
info.fillDetails(activity)
|
||||||
activity.lifecycleScope.launch {
|
lifecycleScope.launchWhenStarted {
|
||||||
info.loadIntents(activity)
|
info.loadIntents(activity)
|
||||||
}
|
}
|
||||||
info
|
info
|
||||||
|
|
Loading…
Reference in a new issue