Adjust code for new service API

This commit is contained in:
Marvin W 2022-01-18 13:46:39 +01:00
parent d092182576
commit 3845b43f07
No known key found for this signature in database
GPG Key ID: 072E9235DB996F2A
24 changed files with 863 additions and 380 deletions

View File

@ -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"
}

View 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"))
}
}
}

View File

@ -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"))
}
}
}

View File

@ -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
}
}

View 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"))
}
}
}

View File

@ -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
}
}
}
}

View File

@ -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")
}

View File

@ -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"
}

View File

@ -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;
}
}
}

View File

@ -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
}
}

View File

@ -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);
}
}

View File

@ -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"
}
}

View File

@ -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"
}

View File

@ -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);
}
}

View File

@ -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"
}
}

View File

@ -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);
}
}

View File

@ -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"
}
}

View File

@ -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"
}

View File

@ -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 {
}

View File

@ -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()

View File

@ -14,8 +14,9 @@ apply plugin: 'signing'
android {
compileSdkVersion androidCompileSdk
buildToolsVersion "$androidBuildVersionTools"
dataBinding {
enabled = true
buildFeatures {
dataBinding = true
}
defaultConfig {

View File

@ -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
}

View File

@ -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)
}

View File

@ -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