Browse Source

Add new split service logic

master
Marvin W 7 months ago
parent
commit
d092182576
No known key found for this signature in database
GPG Key ID: 72E9235DB996F2A
  1. 3
      api/src/main/java/org/microg/nlp/api/Constants.java
  2. 20
      build.gradle
  3. 32
      service-api/build.gradle
  4. 7
      service-api/src/main/AndroidManifest.xml
  5. 8
      service-api/src/main/aidl/org/microg/nlp/service/api/GeocodeRequest.aidl
  6. 12
      service-api/src/main/aidl/org/microg/nlp/service/api/IAddressesCallback.aidl
  7. 24
      service-api/src/main/aidl/org/microg/nlp/service/api/IGeocodeService.aidl
  8. 12
      service-api/src/main/aidl/org/microg/nlp/service/api/ILocationListener.aidl
  9. 26
      service-api/src/main/aidl/org/microg/nlp/service/api/ILocationService.aidl
  10. 10
      service-api/src/main/aidl/org/microg/nlp/service/api/IStatusCallback.aidl
  11. 10
      service-api/src/main/aidl/org/microg/nlp/service/api/IStringsCallback.aidl
  12. 8
      service-api/src/main/aidl/org/microg/nlp/service/api/LocationRequest.aidl
  13. 8
      service-api/src/main/aidl/org/microg/nlp/service/api/ReverseGeocodeRequest.aidl
  14. 20
      service-api/src/main/java/org/microg/nlp/service/api/Constants.java
  15. 45
      service-api/src/main/java/org/microg/nlp/service/api/GeocodeRequest.java
  16. 50
      service-api/src/main/java/org/microg/nlp/service/api/LatLon.java
  17. 22
      service-api/src/main/java/org/microg/nlp/service/api/LatLonBounds.java
  18. 41
      service-api/src/main/java/org/microg/nlp/service/api/LocationRequest.java
  19. 42
      service-api/src/main/java/org/microg/nlp/service/api/ReverseGeocodeRequest.java
  20. 3
      service/build.gradle
  21. 18
      service/src/main/AndroidManifest.xml
  22. 11
      service/src/main/kotlin/org/microg/nlp/service/AbstractBackendHelper.kt
  23. 43
      service/src/main/kotlin/org/microg/nlp/service/AsyncGeocoderBackend.kt
  24. 84
      service/src/main/kotlin/org/microg/nlp/service/GeocodeBackendHelper.kt
  25. 142
      service/src/main/kotlin/org/microg/nlp/service/GeocodeFuser.kt
  26. 196
      service/src/main/kotlin/org/microg/nlp/service/GeocodeService.kt
  27. 134
      service/src/main/kotlin/org/microg/nlp/service/LocationBackendHelper.kt
  28. 147
      service/src/main/kotlin/org/microg/nlp/service/LocationFuser.kt
  29. 348
      service/src/main/kotlin/org/microg/nlp/service/LocationService.kt
  30. 6
      service/src/main/kotlin/org/microg/nlp/service/PackageChangedReceiver.kt
  31. 49
      service/src/main/kotlin/org/microg/nlp/service/UnifiedLocationServiceEntryPoint.kt
  32. 13
      service/src/main/kotlin/org/microg/nlp/service/UnifiedLocationServiceInstance.kt
  33. 44
      service/src/main/kotlin/org/microg/nlp/service/UnifiedLocationServiceRoot.kt
  34. 1
      settings.gradle

3
api/src/main/java/org/microg/nlp/api/Constants.java

@ -12,8 +12,11 @@ public class Constants {
public static final String ACTION_FORCE_LOCATION = "org.microg.nlp.FORCE_LOCATION";
public static final String PERMISSION_FORCE_LOCATION = "org.microg.permission.FORCE_COARSE_LOCATION";
public static final String INTENT_EXTRA_LOCATION = "location";
@Deprecated
public static final String LOCATION_EXTRA_BACKEND_PROVIDER = "SERVICE_BACKEND_PROVIDER";
@Deprecated
public static final String LOCATION_EXTRA_BACKEND_COMPONENT = "SERVICE_BACKEND_COMPONENT";
@Deprecated
public static final String LOCATION_EXTRA_OTHER_BACKENDS = "OTHER_BACKEND_RESULTS";
public static final String METADATA_BACKEND_SETTINGS_ACTIVITY = "org.microg.nlp.BACKEND_SETTINGS_ACTIVITY";
public static final String METADATA_BACKEND_ABOUT_ACTIVITY = "org.microg.nlp.BACKEND_ABOUT_ACTIVITY";

20
build.gradle

@ -4,23 +4,23 @@
*/
buildscript {
ext.kotlinVersion = '1.3.72'
ext.coroutineVersion = '1.3.7'
ext.kotlinVersion = '1.6.10'
ext.coroutineVersion = '1.5.2'
ext.appcompatVersion = '1.1.0'
ext.fragmentVersion = '1.2.5'
ext.lifecycleVersion = '2.2.0'
ext.navigationVersion = '2.3.0'
ext.appcompatVersion = '1.4.0'
ext.fragmentVersion = '1.4.0'
ext.lifecycleVersion = '2.4.0'
ext.navigationVersion = '2.3.5'
ext.preferenceVersion = '1.1.1'
ext.recyclerviewVersion = '1.1.0'
ext.recyclerviewVersion = '1.2.0'
ext.androidBuildGradleVersion = '3.6.3'
ext.androidBuildGradleVersion = '7.0.4'
ext.androidBuildVersionTools = '29.0.3'
ext.androidBuildVersionTools = '30.0.2'
ext.androidMinSdk = 9
ext.androidTargetSdk = 29
ext.androidCompileSdk = 29
ext.androidCompileSdk = 31
repositories {
jcenter()

32
service-api/build.gradle

@ -0,0 +1,32 @@
/*
* SPDX-FileCopyrightText: 2022, microG Project Team
* SPDX-License-Identifier: Apache-2.0
*/
apply plugin: 'com.android.library'
apply plugin: 'maven-publish'
apply plugin: 'signing'
android {
compileSdkVersion androidCompileSdk
buildToolsVersion "$androidBuildVersionTools"
defaultConfig {
versionName version
minSdkVersion androidMinSdk
targetSdkVersion androidTargetSdk
}
compileOptions {
sourceCompatibility = 1.8
targetCompatibility = 1.8
}
}
dependencies {
api "org.microg:safe-parcel:1.7.0"
}
apply from: '../gradle/publish.gradle'
description = 'API interfaces and helpers to access UnifiedNlp service'

7
service-api/src/main/AndroidManifest.xml

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ SPDX-FileCopyrightText: 2022, microG Project Team
~ SPDX-License-Identifier: Apache-2.0
-->
<manifest package="org.microg.nlp.service.api" />

8
service-api/src/main/aidl/org/microg/nlp/service/api/GeocodeRequest.aidl

@ -0,0 +1,8 @@
/*
* SPDX-FileCopyrightText: 2022, microG Project Team
* SPDX-License-Identifier: Apache-2.0
*/
package org.microg.nlp.service.api;
parcelable GeocodeRequest;

12
service-api/src/main/aidl/org/microg/nlp/service/api/IAddressesCallback.aidl

@ -0,0 +1,12 @@
/*
* SPDX-FileCopyrightText: 2022, microG Project Team
* SPDX-License-Identifier: Apache-2.0
*/
package org.microg.nlp.service.api;
import android.location.Address;
interface IAddressesCallback {
oneway void onAddresses(int statusCode, in List<Address> location) ;
}

24
service-api/src/main/aidl/org/microg/nlp/service/api/IGeocodeService.aidl

@ -0,0 +1,24 @@
/*
* SPDX-FileCopyrightText: 2022, microG Project Team
* SPDX-License-Identifier: Apache-2.0
*/
package org.microg.nlp.service.api;
import android.location.Address;
import org.microg.nlp.service.api.GeocodeRequest;
import org.microg.nlp.service.api.IAddressesCallback;
import org.microg.nlp.service.api.IStatusCallback;
import org.microg.nlp.service.api.IStringsCallback;
import org.microg.nlp.service.api.ReverseGeocodeRequest;
interface IGeocodeService {
oneway void requestGeocode(in GeocodeRequest request, IAddressesCallback callback, in Bundle options) = 0;
oneway void requestReverseGeocode(in ReverseGeocodeRequest request, IAddressesCallback callback, in Bundle options) = 1;
List<Address> requestGeocodeSync(in GeocodeRequest request, in Bundle options) = 2;
List<Address> requestReverseGeocodeSync(in ReverseGeocodeRequest request, in Bundle options) = 3;
oneway void reloadPreferences(IStatusCallback callback, in Bundle options) = 20;
oneway void getGeocodeBackends(IStringsCallback callback, in Bundle options) = 21;
oneway void setGeocodeBackends(in List<String> backends, IStatusCallback callback, in Bundle options) = 22;
}

12
service-api/src/main/aidl/org/microg/nlp/service/api/ILocationListener.aidl

@ -0,0 +1,12 @@
/*
* SPDX-FileCopyrightText: 2022, microG Project Team
* SPDX-License-Identifier: Apache-2.0
*/
package org.microg.nlp.service.api;
import android.location.Location;
interface ILocationListener {
oneway void onLocation(int statusCode, in Location location);
}

26
service-api/src/main/aidl/org/microg/nlp/service/api/ILocationService.aidl

@ -0,0 +1,26 @@
/*
* SPDX-FileCopyrightText: 2022, microG Project Team
* SPDX-License-Identifier: Apache-2.0
*/
package org.microg.nlp.service.api;
import android.location.Location;
import org.microg.nlp.service.api.ILocationListener;
import org.microg.nlp.service.api.IStatusCallback;
import org.microg.nlp.service.api.IStringsCallback;
import org.microg.nlp.service.api.LocationRequest;
interface ILocationService {
oneway void getLastLocation(ILocationListener listener, in Bundle options) = 0;
oneway void getLastLocationForBackend(String packageName, String className, String signatureDigest, ILocationListener listener, in Bundle options) = 1;
oneway void updateLocationRequest(in LocationRequest request, IStatusCallback callback, in Bundle options) = 10;
oneway void cancelLocationRequestByListener(ILocationListener listener, IStatusCallback callback, in Bundle options) = 11;
oneway void cancelLocationRequestById(String id, IStatusCallback callback, in Bundle options) = 12;
oneway void forceLocationUpdate(IStatusCallback callback, in Bundle options) = 13;
oneway void reloadPreferences(IStatusCallback callback, in Bundle options) = 20;
oneway void getLocationBackends(IStringsCallback callback, in Bundle options) = 21;
oneway void setLocationBackends(in List<String> backends, IStatusCallback callback, in Bundle options) = 22;
}

10
service-api/src/main/aidl/org/microg/nlp/service/api/IStatusCallback.aidl

@ -0,0 +1,10 @@
/*
* SPDX-FileCopyrightText: 2022, microG Project Team
* SPDX-License-Identifier: Apache-2.0
*/
package org.microg.nlp.service.api;
interface IStatusCallback {
oneway void onStatus(int statusCode);
}

10
service-api/src/main/aidl/org/microg/nlp/service/api/IStringsCallback.aidl

@ -0,0 +1,10 @@
/*
* SPDX-FileCopyrightText: 2022, microG Project Team
* SPDX-License-Identifier: Apache-2.0
*/
package org.microg.nlp.service.api;
interface IStringsCallback {
oneway void onStrings(int statusCode, in List<String> strings);
}

8
service-api/src/main/aidl/org/microg/nlp/service/api/LocationRequest.aidl

@ -0,0 +1,8 @@
/*
* SPDX-FileCopyrightText: 2022, microG Project Team
* SPDX-License-Identifier: Apache-2.0
*/
package org.microg.nlp.service.api;
parcelable LocationRequest;

8
service-api/src/main/aidl/org/microg/nlp/service/api/ReverseGeocodeRequest.aidl

@ -0,0 +1,8 @@
/*
* SPDX-FileCopyrightText: 2022, microG Project Team
* SPDX-License-Identifier: Apache-2.0
*/
package org.microg.nlp.service.api;
parcelable ReverseGeocodeRequest;

20
service-api/src/main/java/org/microg/nlp/service/api/Constants.java

@ -0,0 +1,20 @@
/*
* SPDX-FileCopyrightText: 2020, microG Project Team
* SPDX-License-Identifier: Apache-2.0
*/
package org.microg.nlp.service.api;
public final class Constants {
public static final int STATUS_OK = 0;
public static final int STATUS_NOT_IMPLEMENTED = 1;
public static final int STATUS_PERMISSION_ERROR = 2;
public static final int STATUS_INVALID_ARGS = 3;
public static final String ACTION_LOCATION = "org.microg.nlp.service.LOCATION";
public static final String ACTION_GEOCODE = "org.microg.nlp.service.GEOCODE";
public static final String LOCATION_EXTRA_BACKEND_PROVIDER = "org.microg.nlp.extra.SERVICE_BACKEND_PROVIDER";
public static final String LOCATION_EXTRA_BACKEND_COMPONENT = "org.microg.nlp.extra.SERVICE_BACKEND_COMPONENT";
public static final String LOCATION_EXTRA_OTHER_BACKENDS = "org.microg.nlp.extra.OTHER_BACKEND_RESULTS";
}

45
service-api/src/main/java/org/microg/nlp/service/api/GeocodeRequest.java

@ -0,0 +1,45 @@
/*
* SPDX-FileCopyrightText: 2020, microG Project Team
* SPDX-License-Identifier: Apache-2.0
*/
package org.microg.nlp.service.api;
import org.microg.safeparcel.AutoSafeParcelable;
import java.util.Locale;
public class GeocodeRequest extends AutoSafeParcelable {
@Field(1)
public String locationName;
@Field(2)
public LatLonBounds bounds;
@Field(3)
public int maxResults;
@Field(4)
public String locale;
private GeocodeRequest() {
}
public GeocodeRequest(String locationName, LatLonBounds bounds) {
this(locationName, bounds, 1);
}
public GeocodeRequest(String locationName, LatLonBounds bounds, int maxResults) {
this(locationName, bounds, maxResults, Locale.getDefault());
}
public GeocodeRequest(String locationName, LatLonBounds bounds, int maxResults, Locale locale) {
this(locationName, bounds, maxResults, locale.toString());
}
public GeocodeRequest(String locationName, LatLonBounds bounds, int maxResults, String locale) {
this.locationName = locationName;
this.bounds = bounds;
this.maxResults = maxResults;
this.locale = locale;
}
public static final Creator<GeocodeRequest> CREATOR = new AutoCreator<>(GeocodeRequest.class);
}

50
service-api/src/main/java/org/microg/nlp/service/api/LatLon.java

@ -0,0 +1,50 @@
/*
* SPDX-FileCopyrightText: 2020, microG Project Team
* SPDX-License-Identifier: Apache-2.0
*/
package org.microg.nlp.service.api;
import android.os.Parcel;
import android.os.Parcelable;
public class LatLon implements Parcelable {
private double latitude;
private double longitude;
public LatLon(double latitude, double longitude) {
this.latitude = latitude;
this.longitude = longitude;
}
public double getLatitude() {
return latitude;
}
public double getLongitude() {
return longitude;
}
public static final Creator<LatLon> CREATOR = new Creator<LatLon>() {
@Override
public LatLon createFromParcel(Parcel source) {
return new LatLon(source.readDouble(), source.readDouble());
}
@Override
public LatLon[] newArray(int size) {
return new LatLon[size];
}
};
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeDouble(latitude);
dest.writeDouble(longitude);
}
}

22
service-api/src/main/java/org/microg/nlp/service/api/LatLonBounds.java

@ -0,0 +1,22 @@
/*
* SPDX-FileCopyrightText: 2020, microG Project Team
* SPDX-License-Identifier: Apache-2.0
*/
package org.microg.nlp.service.api;
import org.microg.safeparcel.AutoSafeParcelable;
public class LatLonBounds extends AutoSafeParcelable {
@Field(1)
public LatLon lowerLeft;
@Field(2)
public LatLon upperRight;
public LatLonBounds(LatLon lowerLeft, LatLon upperRight) {
this.lowerLeft = lowerLeft;
this.upperRight = upperRight;
}
public static final Creator<LatLonBounds> CREATOR = new AutoCreator<>(LatLonBounds.class);
}

41
service-api/src/main/java/org/microg/nlp/service/api/LocationRequest.java

@ -0,0 +1,41 @@
/*
* SPDX-FileCopyrightText: 2020, microG Project Team
* SPDX-License-Identifier: Apache-2.0
*/
package org.microg.nlp.service.api;
import org.microg.safeparcel.AutoSafeParcelable;
import java.util.UUID;
public class LocationRequest extends AutoSafeParcelable {
@Field(1)
public ILocationListener listener;
@Field(2)
public long interval;
@Field(3)
public int numUpdates;
@Field(4)
public String id;
private LocationRequest() {
}
public LocationRequest(ILocationListener listener, long interval) {
this(listener, interval, Integer.MAX_VALUE, UUID.randomUUID().toString());
}
public LocationRequest(ILocationListener listener, long interval, int numUpdates) {
this(listener, interval, numUpdates, UUID.randomUUID().toString());
}
public LocationRequest(ILocationListener listener, long interval, int numUpdates, String id) {
this.listener = listener;
this.interval = interval;
this.numUpdates = numUpdates;
this.id = id;
}
public static final Creator<LocationRequest> CREATOR = new AutoCreator<>(LocationRequest.class);
}

42
service-api/src/main/java/org/microg/nlp/service/api/ReverseGeocodeRequest.java

@ -0,0 +1,42 @@
/*
* SPDX-FileCopyrightText: 2020, microG Project Team
* SPDX-License-Identifier: Apache-2.0
*/
package org.microg.nlp.service.api;
import org.microg.safeparcel.AutoSafeParcelable;
import java.util.Locale;
public class ReverseGeocodeRequest extends AutoSafeParcelable {
@Field(1)
public LatLon location;
@Field(2)
public int maxResults;
@Field(3)
public String locale;
private ReverseGeocodeRequest() {
}
public ReverseGeocodeRequest(LatLon location) {
this(location, 1);
}
public ReverseGeocodeRequest(LatLon location, int maxResults) {
this(location, maxResults, Locale.getDefault());
}
public ReverseGeocodeRequest(LatLon location, int maxResults, Locale locale) {
this(location, maxResults, locale.toString());
}
public ReverseGeocodeRequest(LatLon location, int maxResults, String locale) {
this.location = location;
this.maxResults = maxResults;
this.locale = locale;
}
public static final Creator<ReverseGeocodeRequest> CREATOR = new AutoCreator<>(ReverseGeocodeRequest.class);
}

3
service/build.gradle

@ -6,7 +6,6 @@
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'maven-publish'
apply plugin: 'signing'
@ -36,9 +35,11 @@ description = 'UnifiedNlp service library'
dependencies {
implementation project(':api')
implementation project(':service-api')
implementation project(':client')
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"
}

18
service/src/main/AndroidManifest.xml

@ -18,13 +18,29 @@
<application>
<service
android:name=".UnifiedLocationServiceEntryPoint"
android:exported="true"
android:exported="false"
tools:ignore="ExportedService">
<intent-filter>
<action android:name="org.microg.nlp.service.UnifiedLocationService" />
</intent-filter>
</service>
<service
android:name=".LocationService"
android:exported="true">
<intent-filter>
<action android:name="org.microg.nlp.service.LOCATION" />
</intent-filter>
</service>
<service
android:name=".GeocodeService"
android:exported="true">
<intent-filter>
<action android:name="org.microg.nlp.service.GEOCODE" />
</intent-filter>
</service>
<receiver android:name=".PackageChangedReceiver">
<intent-filter>
<action android:name="android.intent.action.PACKAGE_CHANGED" />

11
service/src/main/kotlin/org/microg/nlp/service/AbstractBackendHelper.kt

@ -14,7 +14,10 @@ import android.content.pm.PackageInfo
import android.content.pm.PackageManager
import android.os.IBinder
import android.util.Log
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import kotlinx.coroutines.CoroutineScope
import java.io.PrintWriter
import java.security.MessageDigest
import java.security.NoSuchAlgorithmException
@ -23,11 +26,13 @@ fun <T> Array<out T>?.isNotNullOrEmpty(): Boolean {
return this != null && this.isNotEmpty()
}
abstract class AbstractBackendHelper(private val TAG: String, private val context: Context, protected val coroutineScope: CoroutineScope, val serviceIntent: Intent, val signatureDigest: String?) : ServiceConnection {
abstract class AbstractBackendHelper(private val TAG: String, private val context: Context, private val lifecycle: Lifecycle, val serviceIntent: Intent, val signatureDigest: String?) : ServiceConnection, LifecycleOwner {
private var bound: Boolean = false
protected abstract suspend fun close()
override fun getLifecycle(): Lifecycle = lifecycle
protected abstract fun hasBackend(): Boolean
override fun onServiceConnected(name: ComponentName, service: IBinder) {
@ -81,6 +86,10 @@ abstract class AbstractBackendHelper(private val TAG: String, private val contex
}
}
fun dump(writer: PrintWriter?) {
writer?.println(" ${javaClass.simpleName} $serviceIntent bound=$bound")
}
companion object {
@Suppress("DEPRECATION")
@SuppressLint("PackageManagerGetSignatures")

43
service/src/main/kotlin/org/microg/nlp/service/AsyncGeocoderBackend.kt

@ -14,9 +14,11 @@ import android.os.Looper
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import org.microg.nlp.api.GeocoderBackend
import java.util.concurrent.*
import kotlin.coroutines.suspendCoroutine
class AsyncGeocoderBackend(binder: IBinder, name: String = "geocoder-backend-thread") : Thread(name) {
private val syncThreads: ThreadPoolExecutor = ThreadPoolExecutor(1, Runtime.getRuntime().availableProcessors(), 1, TimeUnit.SECONDS, LinkedBlockingQueue())
private lateinit var looper: Looper
private lateinit var handler: Handler
private val mutex = Mutex(true)
@ -62,6 +64,10 @@ class AsyncGeocoderBackend(binder: IBinder, name: String = "geocoder-backend-thr
}
}
fun getFromLocationSync(latitude: Double, longitude: Double, maxResults: Int, locale: String?): List<Address> = executeWithTimeout {
backend.getFromLocation(latitude, longitude, maxResults, locale);
}
suspend fun getFromLocationName(locationName: String?, maxResults: Int, lowerLeftLatitude: Double, lowerLeftLongitude: Double, upperRightLatitude: Double, upperRightLongitude: Double, locale: String?): List<Address> = mutex.withLock {
suspendCoroutine {
handler.post {
@ -75,6 +81,10 @@ class AsyncGeocoderBackend(binder: IBinder, name: String = "geocoder-backend-thr
}
}
fun getFromLocationNameSync(locationName: String?, maxResults: Int, lowerLeftLatitude: Double, lowerLeftLongitude: Double, upperRightLatitude: Double, upperRightLongitude: Double, locale: String?): List<Address> = executeWithTimeout {
backend.getFromLocationName(locationName, maxResults, lowerLeftLatitude, lowerLeftLongitude, upperRightLatitude, upperRightLongitude, locale)
}
suspend fun close() {
mutex.withLock {
suspendCoroutine<Unit> {
@ -144,6 +154,10 @@ class AsyncGeocoderBackend(binder: IBinder, name: String = "geocoder-backend-thr
}
}
fun getFromLocationWithOptionsSync(latitude: Double, longitude: Double, maxResults: Int, locale: String?, options: Bundle?): List<Address> = executeWithTimeout {
backend.getFromLocationWithOptions(latitude, longitude, maxResults, locale, options)
}
suspend fun getFromLocationNameWithOptions(locationName: String?, maxResults: Int, lowerLeftLatitude: Double, lowerLeftLongitude: Double, upperRightLatitude: Double, upperRightLongitude: Double, locale: String?, options: Bundle?): List<Address> = mutex.withLock {
suspendCoroutine {
handler.post {
@ -156,4 +170,31 @@ class AsyncGeocoderBackend(binder: IBinder, name: String = "geocoder-backend-thr
}
}
}
}
fun getFromLocationNameWithOptionsSync(locationName: String?, maxResults: Int, lowerLeftLatitude: Double, lowerLeftLongitude: Double, upperRightLatitude: Double, upperRightLongitude: Double, locale: String?, options: Bundle?): List<Address> = executeWithTimeout {
backend.getFromLocationNameWithOptions(locationName, maxResults, lowerLeftLatitude, lowerLeftLongitude, upperRightLatitude, upperRightLongitude, locale, 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
}
}

84
service/src/main/kotlin/org/microg/nlp/service/GeocodeBackendHelper.kt

@ -1,84 +0,0 @@
/*
* SPDX-FileCopyrightText: 2014, microG Project Team
* SPDX-License-Identifier: Apache-2.0
*/
package org.microg.nlp.service
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.location.Address
import android.os.IBinder
import android.os.RemoteException
import android.util.Log
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
class GeocodeBackendHelper(context: Context, coroutineScope: CoroutineScope, serviceIntent: Intent, signatureDigest: String?) : AbstractBackendHelper(TAG, context, coroutineScope, serviceIntent, signatureDigest) {
private var backend: AsyncGeocoderBackend? = null
suspend fun getFromLocation(latitude: Double, longitude: Double, maxResults: Int,
locale: String): List<Address> {
if (backend == null) {
Log.d(TAG, "Not (yet) bound.")
return emptyList()
}
try {
return backend!!.getFromLocation(latitude, longitude, maxResults, locale)
} catch (e: Exception) {
Log.w(TAG, e)
unbind()
return emptyList()
}
}
suspend fun getFromLocationName(locationName: String, maxResults: Int,
lowerLeftLatitude: Double, lowerLeftLongitude: Double,
upperRightLatitude: Double, upperRightLongitude: Double,
locale: String): List<Address> {
if (backend == null) {
Log.d(TAG, "Not (yet) bound.")
return emptyList()
}
try {
return backend!!.getFromLocationName(locationName, maxResults, lowerLeftLatitude,
lowerLeftLongitude, upperRightLatitude, upperRightLongitude, locale)
} catch (e: Exception) {
Log.w(TAG, e)
unbind()
return emptyList()
}
}
override fun onServiceConnected(name: ComponentName, service: IBinder) {
super.onServiceConnected(name, service)
backend = AsyncGeocoderBackend(service, name.toShortString() + "-geocoder-backend")
coroutineScope.launch {
try {
backend!!.open()
} catch (e: Exception) {
Log.w(TAG, e)
unbind()
}
}
}
override fun onServiceDisconnected(name: ComponentName) {
super.onServiceDisconnected(name)
backend = null
}
@Throws(RemoteException::class)
public override suspend fun close() {
backend!!.close()
}
public override fun hasBackend(): Boolean {
return backend != null
}
companion object {
private val TAG = "UnifiedGeocoder"
}
}

142
service/src/main/kotlin/org/microg/nlp/service/GeocodeFuser.kt

@ -5,15 +5,25 @@
package org.microg.nlp.service
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.location.Address
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.launch
import org.microg.nlp.api.Constants.ACTION_GEOCODER_BACKEND
import java.io.PrintWriter
import java.util.ArrayList
import java.util.concurrent.CopyOnWriteArrayList
class GeocodeFuser(private val context: Context, private val root: UnifiedLocationServiceRoot) {
private const val TAG = "GeocodeFuser"
class GeocodeFuser(private val context: Context, private val lifecycle: Lifecycle) : LifecycleOwner {
private val backendHelpers = CopyOnWriteArrayList<GeocodeBackendHelper>()
suspend fun reset() {
@ -25,7 +35,7 @@ class GeocodeFuser(private val context: Context, private val root: UnifiedLocati
val intent = Intent(ACTION_GEOCODER_BACKEND)
intent.setPackage(parts[0])
intent.setClassName(parts[0], parts[1])
backendHelpers.add(GeocodeBackendHelper(context, root.coroutineScope, intent, if (parts.size >= 3) parts[2] else null))
backendHelpers.add(GeocodeBackendHelper(context, lifecycle, intent, if (parts.size >= 3) parts[2] else null))
}
}
}
@ -47,6 +57,13 @@ class GeocodeFuser(private val context: Context, private val root: UnifiedLocati
backendHelpers.clear()
}
fun dump(writer: PrintWriter?) {
writer?.println("${backendHelpers.size} backends:")
for (helper in backendHelpers) {
helper?.dump(writer)
}
}
suspend fun getFromLocation(latitude: Double, longitude: Double, maxResults: Int, locale: String): List<Address>? {
if (backendHelpers.isEmpty())
return null
@ -58,6 +75,17 @@ class GeocodeFuser(private val context: Context, private val root: UnifiedLocati
return result
}
fun getFromLocationSync(latitude: Double, longitude: Double, maxResults: Int, locale: String): List<Address>? {
if (backendHelpers.isEmpty())
return null
val result = ArrayList<Address>()
for (backendHelper in backendHelpers) {
val backendResult = backendHelper.getFromLocationSync(latitude, longitude, maxResults, locale)
result.addAll(backendResult)
}
return result
}
suspend fun getFromLocationName(locationName: String, maxResults: Int, lowerLeftLatitude: Double, lowerLeftLongitude: Double, upperRightLatitude: Double, upperRightLongitude: Double, locale: String): List<Address>? {
if (backendHelpers.isEmpty())
return null
@ -68,4 +96,114 @@ class GeocodeFuser(private val context: Context, private val root: UnifiedLocati
}
return result
}
fun getFromLocationNameSync(locationName: String, maxResults: Int, lowerLeftLatitude: Double, lowerLeftLongitude: Double, upperRightLatitude: Double, upperRightLongitude: Double, locale: String): List<Address>? {
if (backendHelpers.isEmpty())
return null
val result = ArrayList<Address>()
for (backendHelper in backendHelpers) {
val backendResult = backendHelper.getFromLocationNameSync(locationName, maxResults, lowerLeftLatitude, lowerLeftLongitude, upperRightLatitude, upperRightLongitude, locale)
result.addAll(backendResult)
}
return result
}
override fun getLifecycle(): Lifecycle = lifecycle
}
class GeocodeBackendHelper(context: Context, lifecycle: Lifecycle, serviceIntent: Intent, signatureDigest: String?) : AbstractBackendHelper(TAG, context, lifecycle, serviceIntent, signatureDigest) {
private var backend: AsyncGeocoderBackend? = null
suspend fun getFromLocation(latitude: Double, longitude: Double, maxResults: Int,
locale: String): List<Address> {
if (backend == null) {
Log.d(TAG, "Not (yet) bound.")
return emptyList()
}
try {
return backend!!.getFromLocation(latitude, longitude, maxResults, locale)
} catch (e: Exception) {
Log.w(TAG, e)
unbind()
return emptyList()
}
}
fun getFromLocationSync(latitude: Double, longitude: Double, maxResults: Int,
locale: String): List<Address> {
if (backend == null) {
Log.d(TAG, "Not (yet) bound.")
return emptyList()
}
try {
return backend!!.getFromLocationSync(latitude, longitude, maxResults, locale)
} catch (e: Exception) {
Log.w(TAG, e)
lifecycleScope.launch { unbind() }
return emptyList()
}
}
suspend fun getFromLocationName(locationName: String, maxResults: Int,
lowerLeftLatitude: Double, lowerLeftLongitude: Double,
upperRightLatitude: Double, upperRightLongitude: Double,
locale: String): List<Address> {
if (backend == null) {
Log.d(TAG, "Not (yet) bound.")
return emptyList()
}
try {
return backend!!.getFromLocationName(locationName, maxResults, lowerLeftLatitude,
lowerLeftLongitude, upperRightLatitude, upperRightLongitude, locale)
} catch (e: Exception) {
Log.w(TAG, e)
unbind()
return emptyList()
}
}
fun getFromLocationNameSync(locationName: String, maxResults: Int,
lowerLeftLatitude: Double, lowerLeftLongitude: Double,
upperRightLatitude: Double, upperRightLongitude: Double,
locale: String): List<Address> {
if (backend == null) {
Log.d(TAG, "Not (yet) bound.")
return emptyList()
}
try {
return backend!!.getFromLocationNameSync(locationName, maxResults, lowerLeftLatitude,
lowerLeftLongitude, upperRightLatitude, upperRightLongitude, locale)
} catch (e: Exception) {
Log.w(TAG, e)
lifecycleScope.launch { unbind() }
return emptyList()
}
}
override fun onServiceConnected(name: ComponentName, service: IBinder) {
super.onServiceConnected(name, service)
backend = AsyncGeocoderBackend(service, name.toShortString() + "-geocoder-backend")
lifecycleScope.launchWhenStarted {
try {
backend!!.open()
} catch (e: Exception) {
Log.w(TAG, e)
unbind()
}
}
}
override fun onServiceDisconnected(name: ComponentName) {
super.onServiceDisconnected(name)
backend = null
}
@Throws(RemoteException::class)
public override suspend fun close() {
backend!!.close()
}
public override fun hasBackend(): Boolean {
return backend != null
}
}

196
service/src/main/kotlin/org/microg/nlp/service/GeocodeService.kt

@ -0,0 +1,196 @@
/*
* SPDX-FileCopyrightText: 2020, microG Project Team
* SPDX-License-Identifier: Apache-2.0
*/
package org.microg.nlp.service
import android.app.ActivityManager
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager.PERMISSION_GRANTED
import android.location.Address
import android.os.Bundle
import android.os.IBinder
import android.util.Log
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LifecycleService
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.launch
import org.microg.nlp.service.api.*
import org.microg.nlp.service.api.Constants.*
import java.io.FileDescriptor
import java.io.PrintWriter
private const val TAG = "GeocodeService"
class GeocodeService : LifecycleService() {
private lateinit var service: GeocodeServiceImpl
override fun onCreate() {
super.onCreate()
Log.d(TAG, "Creating userspace service...")
service = GeocodeServiceImpl(this, lifecycle)
Log.d(TAG, "Created userspace service.")
}
override fun onBind(intent: Intent): IBinder? {
super.onBind(intent)
return service.asBinder()
}
override fun onDestroy() {
lifecycleScope.launch {
service.destroy()
}
super.onDestroy()
Log.d(TAG, "Destroyed")
}
override fun dump(fd: FileDescriptor?, writer: PrintWriter?, args: Array<out String>?) {
service.dump(writer)
}
}
class GeocodeServiceImpl(private val context: Context, private val lifecycle: Lifecycle) : IGeocodeService.Stub(), LifecycleOwner {
private val fuser = GeocodeFuser(context, lifecycle)
init {
lifecycleScope.launchWhenStarted {
Log.d(TAG, "Preparing GeocodeFuser...")
fuser.reset()
fuser.bind()
Log.d(TAG, "Finished preparing GeocodeFuser")
}
}
private fun getCallingPackage(): String? {
val manager = context.getSystemService(Context.ACTIVITY_SERVICE) as? ActivityManager
val callingPid = getCallingPid()
if (manager != null && callingPid > 0) {
manager.runningAppProcesses.find { it.pid == callingPid }?.pkgList?.firstOrNull()?.let { return it }
}
return context.packageManager.getPackagesForUid(getCallingUid())?.firstOrNull()
}
private fun processOptions(options: Bundle?): Bundle {
val options = options ?: Bundle()
options.putString("callingPackage", getCallingPackage())
if (!options.containsKey("packageName")) {
options.putString("packageName", options.getString("packageName"))
} else if (context.checkCallingPermission("org.microg.nlp.SERVICE_ADMIN") != PERMISSION_GRANTED) {
val claimedPackageName = options.getString("packageName")
if (context.packageManager.getPackagesForUid(getCallingUid())?.any { it == claimedPackageName } != true) {
options.putString("packageName", options.getString("packageName"))
}
}
options.putInt("callingUid", getCallingUid())
options.putInt("callingPid", getCallingPid())
return options
}
private fun Bundle.checkPermission(permission: String): Int {
return context.checkPermission(permission, getInt("callingPid"), getInt("callingUid"))
}
override fun requestGeocode(request: GeocodeRequest?, callback: IAddressesCallback?, options: Bundle?) {
val extras = processOptions(options)
if (callback == null || extras.getString("packageName") == null) return
lifecycleScope.launchWhenStarted {
if (request == null)
return@launchWhenStarted callback.onAddresses(STATUS_INVALID_ARGS, null)
if (extras.checkPermission("android.permission.INTERNET") != PERMISSION_GRANTED)
return@launchWhenStarted callback.onAddresses(STATUS_PERMISSION_ERROR, null)
callback.onAddresses(STATUS_OK, fuser.getFromLocationName(
request.locationName,
request.maxResults,
request.bounds.lowerLeft.latitude,
request.bounds.lowerLeft.longitude,
request.bounds.upperRight.latitude,
request.bounds.upperRight.longitude,
request.locale
))
}
}
override fun requestGeocodeSync(request: GeocodeRequest?, options: Bundle?): List<Address> {
val extras = processOptions(options)
if (request == null || extras.getString("packageName") == null) throw IllegalArgumentException()
if (extras.checkPermission("android.permission.INTERNET") != PERMISSION_GRANTED) throw SecurityException("Missing INTERNET permission")
return fuser.getFromLocationNameSync(
request.locationName,
request.maxResults,
request.bounds.lowerLeft.latitude,
request.bounds.lowerLeft.longitude,
request.bounds.upperRight.latitude,
request.bounds.upperRight.longitude,
request.locale
).orEmpty()
}
override fun requestReverseGeocode(request: ReverseGeocodeRequest?, callback: IAddressesCallback?, options: Bundle?) {
val extras = processOptions(options)
if (callback == null || extras.getString("packageName") == null) return
lifecycleScope.launchWhenStarted {
if (request == null)
return@launchWhenStarted callback.onAddresses(STATUS_INVALID_ARGS, null)
if (extras.checkPermission("android.permission.INTERNET") != PERMISSION_GRANTED)
return@launchWhenStarted callback.onAddresses(STATUS_PERMISSION_ERROR, null)
callback.onAddresses(STATUS_OK, fuser.getFromLocation(request.location.latitude, request.location.longitude, request.maxResults, request.locale))
}
}
override fun requestReverseGeocodeSync(request: ReverseGeocodeRequest?, options: Bundle?): List<Address> {
val extras = processOptions(options)
if (request == null || extras.getString("packageName") == null) throw IllegalArgumentException()
if (extras.checkPermission("android.permission.INTERNET") != PERMISSION_GRANTED) throw SecurityException("Missing INTERNET permission")
return fuser.getFromLocationSync(request.location.latitude, request.location.longitude, request.maxResults, request.locale).orEmpty()
}
override fun reloadPreferences(callback: IStatusCallback?, options: Bundle?) {
val extras = processOptions(options)
if (callback == null || extras.getString("packageName") == null) return
lifecycleScope.launchWhenStarted {
if (extras.checkPermission("org.microg.nlp.SERVICE_ADMIN") != PERMISSION_GRANTED)
return@launchWhenStarted callback.onStatus(STATUS_PERMISSION_ERROR)
fuser.reset()
fuser.bind()
callback.onStatus(Constants.STATUS_OK)
}
}
override fun getGeocodeBackends(callback: IStringsCallback?, options: Bundle?) {
val extras = processOptions(options)
if (callback == null || extras.getString("packageName") == null) return
lifecycleScope.launchWhenStarted {
if (extras.checkPermission("org.microg.nlp.SERVICE_ADMIN") != PERMISSION_GRANTED)
return@launchWhenStarted callback.onStrings(STATUS_PERMISSION_ERROR, null)
callback.onStrings(Constants.STATUS_OK, Preferences(context).geocoderBackends.toList())
}
}
override fun setGeocodeBackends(backends: MutableList<String>?, callback: IStatusCallback?, options: Bundle?) {
val extras = processOptions(options)
if (callback == null || extras.getString("packageName") == null) return
lifecycleScope.launchWhenStarted {
if (extras.checkPermission("org.microg.nlp.SERVICE_ADMIN") != PERMISSION_GRANTED)
return@launchWhenStarted callback.onStatus(STATUS_PERMISSION_ERROR)
if (backends == null)
return@launchWhenStarted callback.onStatus(STATUS_INVALID_ARGS)
Preferences(context).geocoderBackends = backends.toSet()
callback.onStatus(Constants.STATUS_OK)
}
}
fun dump(writer: PrintWriter?) {
fuser.dump(writer)
}
suspend fun destroy() {
fuser.destroy()
}
override fun getLifecycle(): Lifecycle = lifecycle
}

134
service/src/main/kotlin/org/microg/nlp/service/LocationBackendHelper.kt

@ -1,134 +0,0 @@
/*
* SPDX-FileCopyrightText: 2014, microG Project Team
* SPDX-License-Identifier: Apache-2.0
*/
package org.microg.nlp.service
import android.annotation.TargetApi
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.location.Location
import android.os.*
import android.util.Log
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import org.microg.nlp.api.Constants.LOCATION_EXTRA_BACKEND_COMPONENT
import org.microg.nlp.api.Constants.LOCATION_EXTRA_BACKEND_PROVIDER
import org.microg.nlp.api.LocationCallback
class LocationBackendHelper(context: Context, private val locationFuser: LocationFuser, coroutineScope: CoroutineScope, serviceIntent: Intent, signatureDigest: String?) : AbstractBackendHelper(TAG, context, coroutineScope, serviceIntent, signatureDigest) {
private val callback = Callback()
private var backend: AsyncLocationBackend? = null
private var updateWaiting: Boolean = false
var lastLocation: Location? = null
private set(location) {
if (location == null || !location.hasAccuracy()) {
return
}
if (location.extras == null) {
location.extras = Bundle()
}
location.extras.putString(LOCATION_EXTRA_BACKEND_PROVIDER, location.provider)
location.extras.putString(LOCATION_EXTRA_BACKEND_COMPONENT,
serviceIntent.component!!.flattenToShortString())
location.provider = "network"
if (!location.hasAccuracy()) {
location.accuracy = 50000f
}
if (location.time <= 0) {
location.time = System.currentTimeMillis()
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
updateElapsedRealtimeNanos(location)
}
field = location
}
/**
* Requests a location update from the backend.
*
* @return The location reported by the backend. This may be null if a backend cannot determine its
* location, or if it is going to return a location asynchronously.
*/
suspend fun update(): Location? {
var result: Location? = null
if (backend == null) {
Log.d(TAG, "Not (yet) bound.")
updateWaiting = true
} else {
updateWaiting = false
try {
result = backend?.update()
if (result == null) {
Log.d(TAG, "Received no location from ${serviceIntent.component!!.flattenToShortString()}")
} else {
Log.d(TAG, "Received location from ${serviceIntent.component!!.flattenToShortString()} with time ${result.time} (last was ${lastLocation?.time ?: 0})")
if (this.lastLocation == null || result.time > this.lastLocation!!.time) {
lastLocation = result
locationFuser.reportLocation()
}
}
} catch (e: Exception) {
Log.w(TAG, e)
unbind()
}
}
return result
}
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
private fun updateElapsedRealtimeNanos(location: Location) {
if (location.elapsedRealtimeNanos <= 0) {
location.elapsedRealtimeNanos = SystemClock.elapsedRealtimeNanos()
}
}
@Throws(RemoteException::class)
public override suspend fun close() {
Log.d(TAG, "Calling close")
backend!!.close()
}
public override fun hasBackend(): Boolean {
return backend != null
}
override fun onServiceConnected(name: ComponentName, service: IBinder) {
super.onServiceConnected(name, service)
backend = AsyncLocationBackend(service, name.toShortString() + "-location-backend")
coroutineScope.launch {
try {
Log.d(TAG, "Calling open")
backend!!.open(callback)
if (updateWaiting) {
update()
}
} catch (e: Exception) {
Log.w(TAG, e)
unbind()
}
}
}
override fun onServiceDisconnected(name: ComponentName) {
super.onServiceDisconnected(name)
backend = null
}
private inner class Callback : LocationCallback.Stub() {
override fun report(location: Location?) {
val lastLocation = lastLocation
if (location == null || lastLocation != null && location.time > 0 && location.time <= lastLocation.getTime())
return
this@LocationBackendHelper.lastLocation = location
locationFuser.reportLocation()
}
}
companion object {
private val TAG = "UnifiedLocation"
}
}

147
service/src/main/kotlin/org/microg/nlp/service/LocationFuser.kt

@ -5,23 +5,31 @@
package org.microg.nlp.service
import android.annotation.TargetApi
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.location.Location
import android.location.LocationManager
import android.os.*
import android.util.Log
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.lifecycleScope
import java.util.ArrayList
import java.util.Collections
import java.util.Comparator
import org.microg.nlp.api.Constants.ACTION_LOCATION_BACKEND
import org.microg.nlp.api.Constants.LOCATION_EXTRA_OTHER_BACKENDS
import org.microg.nlp.api.LocationCallback
import org.microg.nlp.service.api.Constants
import java.io.PrintWriter
import java.util.concurrent.CopyOnWriteArrayList
class LocationFuser(private val context: Context, private val root: UnifiedLocationServiceRoot) {
private const val TAG = "LocationFuser"
class LocationFuser(private val context: Context, private val lifecycle: Lifecycle, private val receiver: LocationReceiver) : LifecycleOwner {
private val backendHelpers = CopyOnWriteArrayList<LocationBackendHelper>()
private var fusing = false
@ -38,7 +46,7 @@ class LocationFuser(private val context: Context, private val root: UnifiedLocat
val intent = Intent(ACTION_LOCATION_BACKEND)
intent.setPackage(parts[0])
intent.setClassName(parts[0], parts[1])
backendHelpers.add(LocationBackendHelper(context, this, root.coroutineScope, intent, if (parts.size >= 3) parts[2] else null))
backendHelpers.add(LocationBackendHelper(context, this, lifecycle, intent, if (parts.size >= 3) parts[2] else null))
}
}
}
@ -84,7 +92,7 @@ class LocationFuser(private val context: Context, private val root: UnifiedLocat
if (lastLocationReportTime < location.time) {
lastLocationReportTime = location.time
Log.v(TAG, "Fused location: $location")
root.reportLocation(location)
receiver.reportLocation(location)
} else {
Log.v(TAG, "Ignoring location update as it's older than other provider.")
}
@ -101,8 +109,8 @@ class LocationFuser(private val context: Context, private val root: UnifiedLocat
if (locations[0] == backendResult) continue
backendResults.add(backendResult)
}
if (!backendResults.isEmpty()) {
location.extras.putParcelableArrayList(LOCATION_EXTRA_OTHER_BACKENDS, backendResults)
if (backendResults.isNotEmpty()) {
location.extras.putParcelableArrayList(Constants.LOCATION_EXTRA_OTHER_BACKENDS, backendResults)
}
return location
}
@ -113,11 +121,20 @@ class LocationFuser(private val context: Context, private val root: UnifiedLocat
updateLocation()
}
fun getLastLocationForBackend(packageName: String, className: String, signatureDigest: String?): Location? =
fun getLastLocationForBackend(packageName: String?, className: String?, signatureDigest: String?): Location? =
backendHelpers.find {
it.serviceIntent.`package` == packageName && it.serviceIntent.component?.className == className && (signatureDigest == null || it.signatureDigest == null || it.signatureDigest == signatureDigest)
}?.lastLocation
fun dump(writer: PrintWriter?) {
writer?.println("${backendHelpers.size} backends:")
for (helper in backendHelpers) {
helper?.dump(writer)
}
}
override fun getLifecycle(): Lifecycle = lifecycle