parent
d092182576
commit
3845b43f07
@ -0,0 +1,166 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020, microG Project Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package org.microg.nlp.client
|
||||
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Context.BIND_ABOVE_CLIENT
|
||||
import android.content.Context.BIND_AUTO_CREATE
|
||||
import android.content.Intent
|
||||
import android.content.ServiceConnection
|
||||
import android.location.Location
|
||||
import android.os.Bundle
|
||||
import android.os.IBinder
|
||||
import android.util.Log
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import org.microg.nlp.service.api.Constants
|
||||
import org.microg.nlp.service.api.ILocationListener
|
||||
import org.microg.nlp.service.api.IStatusCallback
|
||||
import org.microg.nlp.service.api.IStringsCallback
|
||||
import java.util.concurrent.LinkedBlockingQueue
|
||||
import java.util.concurrent.ThreadPoolExecutor
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
import kotlin.coroutines.Continuation
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.resumeWithException
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
|
||||
private const val TAG = "BaseClient"
|
||||
|
||||
abstract class BaseClient<I>(val context: Context, private val lifecycle: Lifecycle, val asInterface: (IBinder) -> I) : LifecycleOwner {
|
||||
private val callbackThreads: ThreadPoolExecutor = ThreadPoolExecutor(1, Runtime.getRuntime().availableProcessors(), 1, TimeUnit.SECONDS, LinkedBlockingQueue())
|
||||
private var persistedConnectionCounter = AtomicInteger(0)
|
||||
private val serviceConnectionMutex = Mutex()
|
||||
private var persistedServiceConnection: ContinuedServiceConnection? = null
|
||||
val defaultOptions = Bundle()
|
||||
|
||||
abstract val action: String
|
||||
|
||||
val intent: Intent?
|
||||
get() = resolveIntent(context, action)
|
||||
|
||||
val isAvailable: Boolean
|
||||
get() = intent != null
|
||||
|
||||
var packageName: String
|
||||
get() = defaultOptions.getString("packageName") ?: context.packageName
|
||||
set(value) = defaultOptions.putString("packageName", value)
|
||||
|
||||
init {
|
||||
packageName = context.packageName
|
||||
}
|
||||
|
||||
val isConnectedUnsafe: Boolean
|
||||
get() = persistedServiceConnection != null && persistedConnectionCounter.get() > 0
|
||||
|
||||
suspend fun isConnected(): Boolean = serviceConnectionMutex.withLock {
|
||||
return persistedServiceConnection != null && persistedConnectionCounter.get() > 0
|
||||
}
|
||||
|
||||
suspend fun connect() {
|
||||
serviceConnectionMutex.withLock {
|
||||
if (persistedServiceConnection == null) {
|
||||
val intent = intent ?: throw IllegalStateException("$action service is not available")
|
||||
persistedServiceConnection = suspendCoroutine<ContinuedServiceConnection> { continuation ->
|
||||
context.bindService(intent, ContinuedServiceConnection(continuation), BIND_AUTO_CREATE or BIND_ABOVE_CLIENT)
|
||||
}
|
||||
}
|
||||
persistedConnectionCounter.incrementAndGet()
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun disconnect() {
|
||||
serviceConnectionMutex.withLock {
|
||||
if (persistedConnectionCounter.decrementAndGet() <= 0) {
|
||||
persistedServiceConnection?.let { context.unbindService(it) }
|
||||
persistedServiceConnection = null
|
||||
persistedConnectionCounter.set(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun <T> withConnectedServiceSync(v: (I) -> T): T {
|
||||
try {
|
||||
if (persistedConnectionCounter.incrementAndGet() <= 1) {
|
||||
throw IllegalStateException("Service not connected")
|
||||
}
|
||||
val service = persistedServiceConnection?.service ?: throw RuntimeException("No binder returned")
|
||||
return v(asInterface(service))
|
||||
} finally {
|
||||
persistedConnectionCounter.decrementAndGet()
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun <T> withService(v: suspend (I) -> T): T {
|
||||
connect()
|
||||
val service = persistedServiceConnection?.service ?: throw RuntimeException("No binder returned")
|
||||
return try {
|
||||
v(asInterface(service))
|
||||
} finally {
|
||||
disconnect()
|
||||
}
|
||||
}
|
||||
|
||||
override fun getLifecycle(): Lifecycle = lifecycle
|
||||
}
|
||||
|
||||
internal class ContinuedServiceConnection(private val continuation: Continuation<ContinuedServiceConnection>) : ServiceConnection {
|
||||
var service: IBinder? = null
|
||||
private var continued: Boolean = false
|
||||
|
||||
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
|
||||
this.service = service
|
||||
if (!continued) {
|
||||
continued = true
|
||||
continuation.resume(this)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onServiceDisconnected(name: ComponentName?) {
|
||||
if (!continued) {
|
||||
continued = true
|
||||
continuation.resumeWithException(RuntimeException("Disconnected"))
|
||||
}
|
||||
}
|
||||
|
||||
override fun onBindingDied(name: ComponentName?) {
|
||||
if (!continued) {
|
||||
continued = true
|
||||
continuation.resumeWithException(RuntimeException("Binding diead"))
|
||||
}
|
||||
}
|
||||
|
||||
override fun onNullBinding(name: ComponentName?) {
|
||||
if (!continued) {
|
||||
continued = true
|
||||
continuation.resume(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal class StringsCallback(private val continuation: Continuation<List<String>>) : IStringsCallback.Stub() {
|
||||
override fun onStrings(statusCode: Int, strings: MutableList<String>) {
|
||||
if (statusCode == Constants.STATUS_OK) {
|
||||
continuation.resume(strings)
|
||||
} else {
|
||||
continuation.resumeWithException(RuntimeException("Status: $statusCode"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal class StatusCallback(private val continuation: Continuation<Unit>) : IStatusCallback.Stub() {
|
||||
override fun onStatus(statusCode: Int) {
|
||||
if (statusCode == Constants.STATUS_OK) {
|
||||
continuation.resume(Unit)
|
||||
} else {
|
||||
continuation.resumeWithException(RuntimeException("Status: $statusCode"))
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,93 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020, microG Project Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package org.microg.nlp.client
|
||||
|
||||
import android.content.Context
|
||||
import android.location.Address
|
||||
import android.os.Bundle
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import org.microg.nlp.service.api.*
|
||||
import org.microg.nlp.service.api.Constants.STATUS_OK
|
||||
import java.util.concurrent.*
|
||||
import kotlin.coroutines.Continuation
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.resumeWithException
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
|
||||
class GeocodeClient(context: Context, lifecycle: Lifecycle) : BaseClient<IGeocodeService>(context, lifecycle, { IGeocodeService.Stub.asInterface(it) }) {
|
||||
private val syncThreads: ThreadPoolExecutor = ThreadPoolExecutor(1, Runtime.getRuntime().availableProcessors(), 1, TimeUnit.SECONDS, LinkedBlockingQueue())
|
||||
override val action: String
|
||||
get() = Constants.ACTION_GEOCODE
|
||||
|
||||
fun requestGeocodeSync(request: GeocodeRequest, options: Bundle = defaultOptions): List<Address> = executeWithTimeout {
|
||||
withConnectedServiceSync { service ->
|
||||
service.requestGeocodeSync(request, options)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun requestGeocode(request: GeocodeRequest, options: Bundle = defaultOptions): List<Address> = withService { service ->
|
||||
suspendCoroutine {
|
||||
service.requestGeocode(request, AddressesCallback(it), options)
|
||||
}
|
||||
}
|
||||
|
||||
fun requestReverseGeocodeSync(request: ReverseGeocodeRequest, options: Bundle = defaultOptions): List<Address> = executeWithTimeout {
|
||||
withConnectedServiceSync { service ->
|
||||
service.requestReverseGeocodeSync(request, options)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun requestReverseGeocode(request: ReverseGeocodeRequest, options: Bundle = defaultOptions): List<Address> = withService { service ->
|
||||
suspendCoroutine {
|
||||
service.requestReverseGeocode(request, AddressesCallback(it), options)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getGeocodeBackends(options: Bundle = defaultOptions): List<String> = withService { service ->
|
||||
suspendCoroutine {
|
||||
service.getGeocodeBackends(StringsCallback(it), options)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun setGeocodeBackends(backends: List<String>, options: Bundle = defaultOptions): Unit = withService { service ->
|
||||
suspendCoroutine {
|
||||
service.setGeocodeBackends(backends, StatusCallback(it), options)
|
||||
}
|
||||
}
|
||||
|
||||
private fun <T> executeWithTimeout(timeout: Long = CALL_TIMEOUT, action: () -> T): T {
|
||||
var result: T? = null
|
||||
val latch = CountDownLatch(1)
|
||||
var err: Exception? = null
|
||||
syncThreads.execute {
|
||||
try {
|
||||
result = action()
|
||||
} catch (e: Exception) {
|
||||
err = e
|
||||
} finally {
|
||||
latch.countDown()
|
||||
}
|
||||
}
|
||||
if (!latch.await(timeout, TimeUnit.MILLISECONDS))
|
||||
throw TimeoutException()
|
||||
err?.let { throw it }
|
||||
return result ?: throw NullPointerException()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val CALL_TIMEOUT = 10000L
|
||||
}
|
||||
}
|
||||
|
||||
private class AddressesCallback(private val continuation: Continuation<List<Address>>) : IAddressesCallback.Stub() {
|
||||
override fun onAddresses(statusCode: Int, addresses: List<Address>?) {
|
||||
if (statusCode == STATUS_OK) {
|
||||
continuation.resume(addresses.orEmpty())
|
||||
} else {
|
||||
continuation.resumeWithException(RuntimeException("Status: $statusCode"))
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,62 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020, microG Project Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package org.microg.nlp.client
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.ApplicationInfo
|
||||
import android.content.pm.ResolveInfo
|
||||
import android.util.Log
|
||||
|
||||
internal fun resolveIntent(context: Context, action: String): Intent? {
|
||||
val intent = Intent(action)
|
||||
|
||||
val pm = context.packageManager
|
||||
var resolveInfos = pm.queryIntentServices(intent, 0)
|
||||
if (resolveInfos.size > 1) {
|
||||
|
||||
// Restrict to self if possible
|
||||
val isSelf: (it: ResolveInfo) -> Boolean = {
|
||||
it.serviceInfo.packageName == context.packageName
|
||||
}
|
||||
if (resolveInfos.size > 1 && resolveInfos.any(isSelf)) {
|
||||
Log.d("IntentResolver", "Found more than one service for $action, restricted to own package " + context.packageName)
|
||||
resolveInfos = resolveInfos.filter(isSelf)
|
||||
}
|
||||
|
||||
// Restrict to package with matching signature if possible
|
||||
val isSelfSig: (it: ResolveInfo) -> Boolean = {
|
||||
it.serviceInfo.packageName == context.packageName
|
||||
}
|
||||
if (resolveInfos.size > 1 && resolveInfos.any(isSelfSig)) {
|
||||
Log.d("IntentResolver", "Found more than one service for $action, restricted to related packages")
|
||||
resolveInfos = resolveInfos.filter(isSelfSig)
|
||||
}
|
||||
|
||||
// Restrict to system if any package is system
|
||||
val isSystem: (it: ResolveInfo) -> Boolean = {
|
||||
(it.serviceInfo?.applicationInfo?.flags ?: 0) and ApplicationInfo.FLAG_SYSTEM > 0
|
||||
}
|
||||
if (resolveInfos.size > 1 && resolveInfos.any(isSystem)) {
|
||||
Log.d("IntentResolver", "Found more than one service for $action, restricted to system packages")
|
||||
resolveInfos = resolveInfos.filter(isSystem)
|
||||
}
|
||||
|
||||
val highestPriority: ResolveInfo? = resolveInfos.maxByOrNull { it.priority }
|
||||
intent.setPackage(highestPriority!!.serviceInfo.packageName)
|
||||
intent.setClassName(highestPriority.serviceInfo.packageName, highestPriority.serviceInfo.name)
|
||||
if (resolveInfos.size > 1) {
|
||||
Log.d("IntentResolver", "Found more than one service for $action, picked highest priority " + intent.component)
|
||||
}
|
||||
return intent
|
||||
} else if (!resolveInfos.isEmpty()) {
|
||||
intent.setPackage(resolveInfos[0].serviceInfo.packageName)
|
||||
return intent
|
||||
} else {
|
||||
Log.w("IntentResolver", "No service to bind to, your system does not support unified service")
|
||||
return null
|
||||
}
|
||||
}
|
@ -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"))
|
||||
}
|
||||
}
|
||||
}
|
@ -1,59 +0,0 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2019, microG Project Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package org.microg.nlp.geocode.v1;
|
||||
|
||||
import android.content.Context;
|
||||
import android.location.Address;
|
||||
import android.location.GeocoderParams;
|
||||
import android.util.Log;
|
||||
|
||||
import org.microg.nlp.client.UnifiedLocationClient;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class GeocodeProvider extends com.android.location.provider.GeocodeProvider {
|
||||
private static final String TAG = "UGeocode";
|
||||
private Context context;
|
||||
private static final long TIMEOUT = 10000;
|
||||
|
||||
public GeocodeProvider(Context context) {
|
||||
this.context = context;
|
||||
UnifiedLocationClient.get(context).ref();
|
||||
}
|
||||
|
||||
public void onDisable() {
|
||||
UnifiedLocationClient.get(context).unref();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String onGetFromLocation(double latitude, double longitude, int maxResults, GeocoderParams params, List<Address> addrs) {
|
||||
try {
|
||||
return handleResult(addrs, UnifiedLocationClient.get(context).getFromLocationSync(latitude, longitude, maxResults, params.getLocale().toString(), TIMEOUT));
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, e);
|
||||
return e.getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String onGetFromLocationName(String locationName, double lowerLeftLatitude, double lowerLeftLongitude, double upperRightLatitude, double upperRightLongitude, int maxResults, GeocoderParams params, List<Address> addrs) {
|
||||
try {
|
||||
return handleResult(addrs, UnifiedLocationClient.get(context).getFromLocationNameSync(locationName, maxResults, lowerLeftLatitude, lowerLeftLongitude, upperRightLatitude, upperRightLongitude, params.getLocale().toString(), TIMEOUT));
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, e);
|
||||
return e.getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
private String handleResult(List<Address> realResult, List<Address> fuserResult) {
|
||||
if (fuserResult.isEmpty()) {
|
||||
return "no result";
|
||||
} else {
|
||||
realResult.addAll(fuserResult);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,64 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2019, microG Project Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
package org.microg.nlp.geocode.v1
|
||||
|
||||
import android.content.Context
|
||||
import android.location.Address
|
||||
import android.location.GeocoderParams
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import com.android.location.provider.GeocodeProvider
|
||||
import org.microg.nlp.client.GeocodeClient
|
||||
import org.microg.nlp.service.api.GeocodeRequest
|
||||
import org.microg.nlp.service.api.LatLon
|
||||
import org.microg.nlp.service.api.LatLonBounds
|
||||
import org.microg.nlp.service.api.ReverseGeocodeRequest
|
||||
|
||||
class GeocodeProvider(context: Context, lifecycle: Lifecycle) : GeocodeProvider() {
|
||||
private val client: GeocodeClient = GeocodeClient(context, lifecycle)
|
||||
|
||||
override fun onGetFromLocation(latitude: Double, longitude: Double, maxResults: Int, params: GeocoderParams, addrs: MutableList<Address>): String? {
|
||||
return try {
|
||||
handleResult(addrs, client.requestReverseGeocodeSync(
|
||||
ReverseGeocodeRequest(LatLon(latitude, longitude), maxResults, params.locale),
|
||||
Bundle().apply { putString("packageName", params.clientPackage) }
|
||||
))
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, e)
|
||||
e.message
|
||||
}
|
||||
}
|
||||
|
||||
override fun onGetFromLocationName(locationName: String?, lowerLeftLatitude: Double, lowerLeftLongitude: Double, upperRightLatitude: Double, upperRightLongitude: Double, maxResults: Int, params: GeocoderParams, addrs: MutableList<Address>): String? {
|
||||
return try {
|
||||
handleResult(addrs, client.requestGeocodeSync(
|
||||
GeocodeRequest(locationName!!, LatLonBounds(LatLon(lowerLeftLatitude, lowerLeftLongitude), LatLon(upperRightLatitude, upperRightLongitude)), maxResults, params.locale),
|
||||
Bundle().apply { putString("packageName", params.clientPackage) }
|
||||
))
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, e)
|
||||
e.message
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleResult(realResult: MutableList<Address>, fuserResult: List<Address>): String? {
|
||||
return if (fuserResult.isEmpty()) {
|
||||
"no result"
|
||||
} else {
|
||||
realResult.addAll(fuserResult)
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun connect() = client.connect()
|
||||
suspend fun disconnect() = client.disconnect()
|
||||
|
||||
companion object {
|
||||
private const val TAG = "GeocodeProvider"
|
||||
private const val TIMEOUT: Long = 10000
|
||||
}
|
||||
}
|
@ -1,33 +0,0 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2019, microG Project Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package org.microg.nlp.geocode.v1;
|
||||
|
||||
import android.app.Service;
|
||||
import android.content.Intent;
|
||||
import android.os.IBinder;
|
||||
import android.util.Log;
|
||||
|
||||
public class GeocodeService extends Service {
|
||||
private static final String TAG = "UnifiedGeocode";
|
||||
private GeocodeProvider provider;
|
||||
|
||||
@Override
|
||||
public synchronized IBinder onBind(Intent intent) {
|
||||
Log.d(TAG, "onBind: "+intent);
|
||||
if (provider == null) {
|
||||
provider = new GeocodeProvider(this);
|
||||
}
|
||||
return provider.getBinder();
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized boolean onUnbind(Intent intent) {
|
||||
if (provider != null) {
|
||||
provider.onDisable();
|
||||
}
|
||||
return super.onUnbind(intent);
|
||||
}
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2019, microG Project Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
package org.microg.nlp.geocode.v1
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.IBinder
|
||||
import android.util.Log
|
||||
import androidx.lifecycle.LifecycleService
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import kotlinx.coroutines.runBlocking
|
||||
|
||||
class GeocodeService : LifecycleService() {
|
||||
private lateinit var provider: GeocodeProvider
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
Log.d(TAG, "Creating system service...")
|
||||
provider = GeocodeProvider(this, lifecycle)
|
||||
lifecycleScope.launchWhenStarted { provider.connect() }
|
||||
Log.d(TAG, "Created system service.")
|
||||
}
|
||||
|
||||
override fun onBind(intent: Intent): IBinder? {
|
||||
super.onBind(intent)
|
||||
Log.d(TAG, "onBind: $intent")
|
||||
return provider.binder
|
||||
}
|
||||
|
||||
override fun onUnbind(intent: Intent): Boolean {
|
||||
return super.onUnbind(intent)
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
runBlocking { provider.disconnect() }
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "GeocodeService"
|
||||
}
|
||||
}
|
@ -1,99 +0,0 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2019, microG Project Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package org.microg.nlp.location.v2;
|
||||
|
||||
import android.content.Context;
|
||||
import android.location.Criteria;
|
||||
import android.location.Location;
|
||||
import android.os.Bundle;
|
||||
import android.os.WorkSource;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.location.provider.LocationProviderBase;
|
||||
import com.android.location.provider.ProviderPropertiesUnbundled;
|
||||
import com.android.location.provider.ProviderRequestUnbundled;
|
||||
|
||||
import org.microg.nlp.client.UnifiedLocationClient;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import static android.location.LocationProvider.AVAILABLE;
|
||||
|
||||
public class LocationProvider extends LocationProviderBase implements UnifiedLocationClient.LocationListener {
|
||||
private static final List<String> EXCLUDED_PACKAGES = Arrays.asList("android", "com.android.location.fused", "com.google.android.gms");
|
||||
private static final long FASTEST_REFRESH_INTERVAL = 30000;
|
||||
private static final String TAG = "ULocation";
|
||||
private Context context;
|
||||
|
||||
public LocationProvider(Context context) {
|
||||
super(TAG, ProviderPropertiesUnbundled.create(false, false, false, false, true, true, true, Criteria.POWER_LOW, Criteria.ACCURACY_COARSE));
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
UnifiedLocationClient.get(context).ref();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisable() {
|
||||
UnifiedLocationClient.get(context).unref();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSetRequest(ProviderRequestUnbundled requests, WorkSource source) {
|
||||
Log.v(TAG, "onSetRequest: " + requests + " by " + source);
|
||||
String opPackageName = null;
|
||||
try {
|
||||
Field namesField = WorkSource.class.getDeclaredField("mNames");
|
||||
namesField.setAccessible(true);
|
||||
String[] names = (String[]) namesField.get(source);
|
||||
if (names != null) {
|
||||
for (String name : names) {
|
||||
if (!EXCLUDED_PACKAGES.contains(name)) {
|
||||
opPackageName = name;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
|
||||
long autoTime = Math.max(requests.getInterval(), FASTEST_REFRESH_INTERVAL);
|
||||
boolean autoUpdate = requests.getReportLocation();
|
||||
|
||||
Log.v(TAG, "using autoUpdate=" + autoUpdate + " autoTime=" + autoTime);
|
||||
|
||||
if (autoUpdate) {
|
||||
UnifiedLocationClient.get(context).setOpPackageName(opPackageName);
|
||||
UnifiedLocationClient.get(context).requestLocationUpdates(this, autoTime);
|
||||
} else {
|
||||
UnifiedLocationClient.get(context).removeLocationUpdates(this);
|
||||
}
|
||||
}
|
||||
|
||||
public void unsetRequest() {
|
||||
UnifiedLocationClient.get(context).removeLocationUpdates(this);
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
public int onGetStatus(Bundle extras) {
|
||||
return AVAILABLE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long onGetStatusUpdateTime() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLocation(Location location) {
|
||||
reportLocation(location);
|
||||
}
|
||||
}
|
@ -0,0 +1,149 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2019, microG Project Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
package org.microg.nlp.location.v2
|
||||
|
||||
import android.content.Context
|
||||
import android.location.Criteria
|
||||
import android.location.Location
|
||||
import android.location.LocationProvider
|
||||
import android.os.Bundle
|
||||
import android.os.SystemClock
|
||||
import android.os.WorkSource
|
||||
import android.util.Log
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.android.location.provider.LocationProviderBase
|
||||
import com.android.location.provider.ProviderPropertiesUnbundled
|
||||
import com.android.location.provider.ProviderRequestUnbundled
|
||||
import kotlinx.coroutines.launch
|
||||
import org.microg.nlp.client.LocationClient
|
||||
import org.microg.nlp.service.api.Constants.STATUS_OK
|
||||
import org.microg.nlp.service.api.ILocationListener
|
||||
import org.microg.nlp.service.api.LocationRequest
|
||||
import java.io.FileDescriptor
|
||||
import java.io.PrintWriter
|
||||
import java.util.*
|
||||
|
||||
class LocationProvider(private val context: Context, private val lifecycle: Lifecycle) : LocationProviderBase(TAG, ProviderPropertiesUnbundled.create(false, false, false, false, true, true, true, Criteria.POWER_LOW, Criteria.ACCURACY_COARSE)), LifecycleOwner {
|
||||
private val client: LocationClient = LocationClient(context, lifecycle)
|
||||
private val id = UUID.randomUUID().toString()
|
||||
private var statusUpdateTime = SystemClock.elapsedRealtime()
|
||||
private val listener = object : ILocationListener.Stub() {
|
||||
override fun onLocation(statusCode: Int, location: Location?) {
|
||||
if (statusCode == STATUS_OK && location != null) {
|
||||
val reportableLocation = Location(location)
|
||||
for (key in reportableLocation.extras.keySet().toList()) {
|
||||
if (key?.startsWith("org.microg.nlp.") == true) {
|
||||
reportableLocation.extras.remove(key)
|
||||
}
|
||||
}
|
||||
Log.d(TAG, "reportLocation: $reportableLocation")
|
||||
reportLocation(reportableLocation)
|
||||
}
|
||||
}
|
||||
}
|
||||
private var opPackageName: String? = null
|
||||
private var autoTime = Long.MAX_VALUE
|
||||
private var autoUpdate = false
|
||||
|
||||
init {
|
||||
client.defaultOptions.putString("source", "LocationProvider")
|
||||
client.defaultOptions.putString("requestId", id)
|
||||
}
|
||||
|
||||
override fun onEnable() {
|
||||
Log.d(TAG, "onEnable")
|
||||
statusUpdateTime = SystemClock.elapsedRealtime()
|
||||
}
|
||||
|
||||
override fun onDisable() {
|
||||
Log.d(TAG, "onDisable")
|
||||
unsetRequest()
|
||||
statusUpdateTime = SystemClock.elapsedRealtime()
|
||||
}
|
||||
|
||||
override fun onSetRequest(requests: ProviderRequestUnbundled, source: WorkSource) {
|
||||
Log.v(TAG, "onSetRequest: $requests by $source")
|
||||
opPackageName = null
|
||||
try {
|
||||
val namesField = WorkSource::class.java.getDeclaredField("mNames")
|
||||
namesField.isAccessible = true
|
||||
val names = namesField[source] as Array<String>
|
||||
if (names != null) {
|
||||
for (name in names) {
|
||||
if (!EXCLUDED_PACKAGES.contains(name)) {
|
||||
opPackageName = name
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (ignored: Exception) {
|
||||
}
|
||||
autoTime = requests.interval.coerceAtLeast(FASTEST_REFRESH_INTERVAL)
|
||||
autoUpdate = requests.reportLocation
|
||||
Log.v(TAG, "using autoUpdate=$autoUpdate autoTime=$autoTime")
|
||||
lifecycleScope.launch {
|
||||
updateRequest()
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun updateRequest() {
|
||||
if (client.isConnected()) {
|
||||
if (autoUpdate) {
|
||||
client.packageName = opPackageName ?: context.packageName
|
||||
client.updateLocationRequest(LocationRequest(listener, autoTime, Int.MAX_VALUE, id))
|
||||
} else {
|
||||
client.cancelLocationRequestById(id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun unsetRequest() {
|
||||
lifecycleScope.launch {
|
||||
client.cancelLocationRequestById(id)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onGetStatus(extras: Bundle?): Int {
|
||||
return LocationProvider.AVAILABLE
|
||||
}
|
||||
|
||||
override fun onGetStatusUpdateTime(): Long {
|
||||
return statusUpdateTime
|
||||
}
|
||||
|
||||
override fun onSendExtraCommand(command: String?, extras: Bundle?): Boolean {
|
||||
Log.d(TAG, "onSendExtraCommand: $command, $extras")
|
||||
return false
|
||||
}
|
||||
|
||||
override fun onDump(fd: FileDescriptor?, pw: PrintWriter?, args: Array<out String>?) {
|
||||
dump(pw)
|
||||
}
|
||||
|
||||
fun dump(writer: PrintWriter?) {
|
||||
writer?.println("ID: $id")
|
||||
writer?.println("connected: ${client.isConnectedUnsafe}")
|
||||
writer?.println("active: $autoUpdate")
|
||||
writer?.println("interval: $autoTime")
|
||||
}
|
||||
|
||||
suspend fun connect() {
|
||||
Log.d(TAG, "Connecting to userspace service...")
|
||||
client.connect()
|
||||
updateRequest()
|
||||
Log.d(TAG, "Connected to userspace service.")
|
||||
}
|
||||
suspend fun disconnect() = client.disconnect()
|
||||
|
||||
override fun getLifecycle(): Lifecycle = lifecycle
|
||||
|
||||
companion object {
|
||||
private val EXCLUDED_PACKAGES = listOf("android", "com.android.location.fused", "com.google.android.gms")
|
||||
private const val FASTEST_REFRESH_INTERVAL: Long = 2500
|
||||
private const val TAG = "LocationProvider"
|
||||
}
|
||||
}
|
@ -1,33 +0,0 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2019, microG Project Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package org.microg.nlp.location.v2;
|
||||
|
||||
import android.app.Service;
|
||||
import android.content.Intent;
|
||||
import android.os.IBinder;
|
||||
import android.util.Log;
|
||||
|
||||
public class LocationService extends Service {
|
||||
private static final String TAG = "ULocation";
|
||||
private LocationProvider provider;
|
||||
|
||||
@Override
|
||||
public synchronized IBinder onBind(Intent intent) {
|
||||
Log.d(TAG, "onBind: "+intent);
|
||||
if (provider == null) {
|
||||
provider = new LocationProvider(this);
|
||||
}
|
||||
return provider.getBinder();
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized boolean onUnbind(Intent intent) {
|
||||
if (provider != null) {
|
||||
provider.unsetRequest();
|
||||
}
|
||||
return super.onUnbind(intent);
|
||||
}
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2019, microG Project Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
package org.microg.nlp.location.v2
|
||||
|
||||
import androidx.lifecycle.LifecycleService
|
||||
import android.content.Intent
|
||||
import android.os.IBinder
|
||||
import android.util.Log
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import java.io.FileDescriptor
|
||||
import java.io.PrintWriter
|
||||
|
||||
open class LocationService : LifecycleService() {
|
||||
private lateinit var provider: LocationProvider
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
Log.d(TAG, "Creating system service...")
|
||||
provider = LocationProvider(this, lifecycle)
|
||||
lifecycleScope.launchWhenStarted { provider.connect() }
|
||||
Log.d(TAG, "Created system service.")
|
||||
}
|
||||
|
||||
override fun onBind(intent: Intent): IBinder? {
|
||||
super.onBind(intent)
|
||||
Log.d(TAG, "onBind: $intent")
|
||||
return provider.binder
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
runBlocking { provider.disconnect() }
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
override fun dump(fd: FileDescriptor?, writer: PrintWriter?, args: Array<out String>?) {
|
||||
if (!this::provider.isInitialized) {
|
||||
writer?.println("Not yet initialized")
|
||||
}
|
||||
provider.dump(writer)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "LocationService"
|
||||
}
|
||||
}
|