Browse Source

Adjust code for new service API

master
Marvin W 7 months ago
parent
commit
3845b43f07
No known key found for this signature in database
GPG Key ID: 72E9235DB996F2A
  1. 4
      client/build.gradle
  2. 166
      client/src/main/kotlin/org/microg/nlp/client/BaseClient.kt
  3. 93
      client/src/main/kotlin/org/microg/nlp/client/GeocodeClient.kt
  4. 62
      client/src/main/kotlin/org/microg/nlp/client/IntentResolver.kt
  5. 103
      client/src/main/kotlin/org/microg/nlp/client/LocationClient.kt
  6. 150
      client/src/main/kotlin/org/microg/nlp/client/UnifiedLocationClient.kt
  7. 14
      compat/build.gradle
  8. 6
      geocode-v1/build.gradle
  9. 59
      geocode-v1/src/main/java/org/microg/nlp/geocode/v1/GeocodeProvider.java
  10. 64
      geocode-v1/src/main/java/org/microg/nlp/geocode/v1/GeocodeProvider.kt
  11. 33
      geocode-v1/src/main/java/org/microg/nlp/geocode/v1/GeocodeService.java
  12. 43
      geocode-v1/src/main/java/org/microg/nlp/geocode/v1/GeocodeService.kt
  13. 6
      location-v2/build.gradle
  14. 99
      location-v2/src/main/java/org/microg/nlp/location/v2/LocationProvider.java
  15. 149
      location-v2/src/main/java/org/microg/nlp/location/v2/LocationProvider.kt
  16. 33
      location-v2/src/main/java/org/microg/nlp/location/v2/LocationService.java
  17. 48
      location-v2/src/main/java/org/microg/nlp/location/v2/LocationService.kt
  18. 6
      location-v3/build.gradle
  19. 9
      location-v3/src/main/java/org/microg/nlp/location/v3/LocationService.java
  20. 9
      location-v3/src/main/java/org/microg/nlp/location/v3/LocationService.kt
  21. 5
      ui/build.gradle
  22. 31
      ui/src/main/kotlin/org/microg/nlp/ui/BackendConfiguration.kt
  23. 35
      ui/src/main/kotlin/org/microg/nlp/ui/BackendDetailsFragment.kt
  24. 16
      ui/src/main/kotlin/org/microg/nlp/ui/BackendListFragment.kt

4
client/build.gradle

@ -5,7 +5,6 @@
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'maven-publish'
apply plugin: 'signing'
@ -34,6 +33,9 @@ apply from: '../gradle/publish.gradle'
description = 'UnifiedNlp client library'
dependencies {
api project(":service-api")
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlinVersion"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutineVersion"
implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycleVersion"
}

166
client/src/main/kotlin/org/microg/nlp/client/BaseClient.kt

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

93
client/src/main/kotlin/org/microg/nlp/client/GeocodeClient.kt

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

62
client/src/main/kotlin/org/microg/nlp/client/IntentResolver.kt

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

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

150
client/src/main/kotlin/org/microg/nlp/client/UnifiedLocationClient.kt

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

14
compat/build.gradle

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

6
geocode-v1/build.gradle

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

59
geocode-v1/src/main/java/org/microg/nlp/geocode/v1/GeocodeProvider.java

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

64
geocode-v1/src/main/java/org/microg/nlp/geocode/v1/GeocodeProvider.kt

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

33
geocode-v1/src/main/java/org/microg/nlp/geocode/v1/GeocodeService.java

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

43
geocode-v1/src/main/java/org/microg/nlp/geocode/v1/GeocodeService.kt

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

6
location-v2/build.gradle

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

99
location-v2/src/main/java/org/microg/nlp/location/v2/LocationProvider.java

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

149
location-v2/src/main/java/org/microg/nlp/location/v2/LocationProvider.kt

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

33
location-v2/src/main/java/org/microg/nlp/location/v2/LocationService.java

@ -1,33 +0,0 @@
/*
* SPDX-FileCopyrightText: 2019, microG Project Team
* SPDX-License-Identifier: Apache-2.0