diff --git a/api/src/main/java/org/microg/nlp/api/Constants.java b/api/src/main/java/org/microg/nlp/api/Constants.java
index 1c352d8..21d8e45 100644
--- a/api/src/main/java/org/microg/nlp/api/Constants.java
+++ b/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";
diff --git a/build.gradle b/build.gradle
index ce495fb..f16d931 100644
--- a/build.gradle
+++ b/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()
diff --git a/service-api/build.gradle b/service-api/build.gradle
new file mode 100644
index 0000000..c0cf2c0
--- /dev/null
+++ b/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'
diff --git a/service-api/src/main/AndroidManifest.xml b/service-api/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..11dbdf5
--- /dev/null
+++ b/service-api/src/main/AndroidManifest.xml
@@ -0,0 +1,7 @@
+
+
+
+
diff --git a/service-api/src/main/aidl/org/microg/nlp/service/api/GeocodeRequest.aidl b/service-api/src/main/aidl/org/microg/nlp/service/api/GeocodeRequest.aidl
new file mode 100644
index 0000000..3dd7272
--- /dev/null
+++ b/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;
diff --git a/service-api/src/main/aidl/org/microg/nlp/service/api/IAddressesCallback.aidl b/service-api/src/main/aidl/org/microg/nlp/service/api/IAddressesCallback.aidl
new file mode 100644
index 0000000..6973b4f
--- /dev/null
+++ b/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
location) ;
+}
diff --git a/service-api/src/main/aidl/org/microg/nlp/service/api/IGeocodeService.aidl b/service-api/src/main/aidl/org/microg/nlp/service/api/IGeocodeService.aidl
new file mode 100644
index 0000000..825bf4a
--- /dev/null
+++ b/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 requestGeocodeSync(in GeocodeRequest request, in Bundle options) = 2;
+ List 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 backends, IStatusCallback callback, in Bundle options) = 22;
+}
diff --git a/service-api/src/main/aidl/org/microg/nlp/service/api/ILocationListener.aidl b/service-api/src/main/aidl/org/microg/nlp/service/api/ILocationListener.aidl
new file mode 100644
index 0000000..cf28ff9
--- /dev/null
+++ b/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);
+}
diff --git a/service-api/src/main/aidl/org/microg/nlp/service/api/ILocationService.aidl b/service-api/src/main/aidl/org/microg/nlp/service/api/ILocationService.aidl
new file mode 100644
index 0000000..31a2f50
--- /dev/null
+++ b/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 backends, IStatusCallback callback, in Bundle options) = 22;
+}
diff --git a/service-api/src/main/aidl/org/microg/nlp/service/api/IStatusCallback.aidl b/service-api/src/main/aidl/org/microg/nlp/service/api/IStatusCallback.aidl
new file mode 100644
index 0000000..9f45bfb
--- /dev/null
+++ b/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);
+}
diff --git a/service-api/src/main/aidl/org/microg/nlp/service/api/IStringsCallback.aidl b/service-api/src/main/aidl/org/microg/nlp/service/api/IStringsCallback.aidl
new file mode 100644
index 0000000..3f34b50
--- /dev/null
+++ b/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 strings);
+}
diff --git a/service-api/src/main/aidl/org/microg/nlp/service/api/LocationRequest.aidl b/service-api/src/main/aidl/org/microg/nlp/service/api/LocationRequest.aidl
new file mode 100644
index 0000000..cb77e43
--- /dev/null
+++ b/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;
diff --git a/service-api/src/main/aidl/org/microg/nlp/service/api/ReverseGeocodeRequest.aidl b/service-api/src/main/aidl/org/microg/nlp/service/api/ReverseGeocodeRequest.aidl
new file mode 100644
index 0000000..e05bb88
--- /dev/null
+++ b/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;
diff --git a/service-api/src/main/java/org/microg/nlp/service/api/Constants.java b/service-api/src/main/java/org/microg/nlp/service/api/Constants.java
new file mode 100644
index 0000000..6bf0b0f
--- /dev/null
+++ b/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";
+}
diff --git a/service-api/src/main/java/org/microg/nlp/service/api/GeocodeRequest.java b/service-api/src/main/java/org/microg/nlp/service/api/GeocodeRequest.java
new file mode 100644
index 0000000..a35733e
--- /dev/null
+++ b/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 CREATOR = new AutoCreator<>(GeocodeRequest.class);
+}
diff --git a/service-api/src/main/java/org/microg/nlp/service/api/LatLon.java b/service-api/src/main/java/org/microg/nlp/service/api/LatLon.java
new file mode 100644
index 0000000..881ca7e
--- /dev/null
+++ b/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 CREATOR = new Creator() {
+ @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);
+ }
+}
diff --git a/service-api/src/main/java/org/microg/nlp/service/api/LatLonBounds.java b/service-api/src/main/java/org/microg/nlp/service/api/LatLonBounds.java
new file mode 100644
index 0000000..fe52834
--- /dev/null
+++ b/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 CREATOR = new AutoCreator<>(LatLonBounds.class);
+}
diff --git a/service-api/src/main/java/org/microg/nlp/service/api/LocationRequest.java b/service-api/src/main/java/org/microg/nlp/service/api/LocationRequest.java
new file mode 100644
index 0000000..1213582
--- /dev/null
+++ b/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 CREATOR = new AutoCreator<>(LocationRequest.class);
+}
diff --git a/service-api/src/main/java/org/microg/nlp/service/api/ReverseGeocodeRequest.java b/service-api/src/main/java/org/microg/nlp/service/api/ReverseGeocodeRequest.java
new file mode 100644
index 0000000..030250c
--- /dev/null
+++ b/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 CREATOR = new AutoCreator<>(ReverseGeocodeRequest.class);
+}
diff --git a/service/build.gradle b/service/build.gradle
index 28327ef..bf9580a 100644
--- a/service/build.gradle
+++ b/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"
}
diff --git a/service/src/main/AndroidManifest.xml b/service/src/main/AndroidManifest.xml
index 8ebf360..e128fb5 100644
--- a/service/src/main/AndroidManifest.xml
+++ b/service/src/main/AndroidManifest.xml
@@ -18,13 +18,29 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/service/src/main/kotlin/org/microg/nlp/service/AbstractBackendHelper.kt b/service/src/main/kotlin/org/microg/nlp/service/AbstractBackendHelper.kt
index ad00475..e8828b2 100644
--- a/service/src/main/kotlin/org/microg/nlp/service/AbstractBackendHelper.kt
+++ b/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 Array?.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")
diff --git a/service/src/main/kotlin/org/microg/nlp/service/AsyncGeocoderBackend.kt b/service/src/main/kotlin/org/microg/nlp/service/AsyncGeocoderBackend.kt
index 86b6406..e8efe93 100644
--- a/service/src/main/kotlin/org/microg/nlp/service/AsyncGeocoderBackend.kt
+++ b/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 = 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 = 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 = executeWithTimeout {
+ backend.getFromLocationName(locationName, maxResults, lowerLeftLatitude, lowerLeftLongitude, upperRightLatitude, upperRightLongitude, locale)
+ }
+
suspend fun close() {
mutex.withLock {
suspendCoroutine {
@@ -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 = 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 = mutex.withLock {
suspendCoroutine {
handler.post {
@@ -156,4 +170,31 @@ class AsyncGeocoderBackend(binder: IBinder, name: String = "geocoder-backend-thr
}
}
}
-}
\ No newline at end of file
+
+ fun getFromLocationNameWithOptionsSync(locationName: String?, maxResults: Int, lowerLeftLatitude: Double, lowerLeftLongitude: Double, upperRightLatitude: Double, upperRightLongitude: Double, locale: String?, options: Bundle?): List = executeWithTimeout {
+ backend.getFromLocationNameWithOptions(locationName, maxResults, lowerLeftLatitude, lowerLeftLongitude, upperRightLatitude, upperRightLongitude, locale, options)
+ }
+
+ private fun 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
+ }
+}
diff --git a/service/src/main/kotlin/org/microg/nlp/service/GeocodeBackendHelper.kt b/service/src/main/kotlin/org/microg/nlp/service/GeocodeBackendHelper.kt
deleted file mode 100644
index aabc3cd..0000000
--- a/service/src/main/kotlin/org/microg/nlp/service/GeocodeBackendHelper.kt
+++ /dev/null
@@ -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 {
- 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 {
- 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"
- }
-}
\ No newline at end of file
diff --git a/service/src/main/kotlin/org/microg/nlp/service/GeocodeFuser.kt b/service/src/main/kotlin/org/microg/nlp/service/GeocodeFuser.kt
index 0568ade..32d73cb 100644
--- a/service/src/main/kotlin/org/microg/nlp/service/GeocodeFuser.kt
+++ b/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()
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? {
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? {
+ if (backendHelpers.isEmpty())
+ return null
+ val result = ArrayList()
+ 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? {
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? {
+ if (backendHelpers.isEmpty())
+ return null
+ val result = ArrayList()
+ 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 {
+ 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 {
+ 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 {
+ 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 {
+ 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
+ }
}
diff --git a/service/src/main/kotlin/org/microg/nlp/service/GeocodeService.kt b/service/src/main/kotlin/org/microg/nlp/service/GeocodeService.kt
new file mode 100644
index 0000000..e49324b
--- /dev/null
+++ b/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?) {
+ 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 {
+ 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 {
+ 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?, 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
+}
+
diff --git a/service/src/main/kotlin/org/microg/nlp/service/LocationBackendHelper.kt b/service/src/main/kotlin/org/microg/nlp/service/LocationBackendHelper.kt
deleted file mode 100644
index 359a8bb..0000000
--- a/service/src/main/kotlin/org/microg/nlp/service/LocationBackendHelper.kt
+++ /dev/null
@@ -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"
- }
-}
diff --git a/service/src/main/kotlin/org/microg/nlp/service/LocationFuser.kt b/service/src/main/kotlin/org/microg/nlp/service/LocationFuser.kt
index b5add73..6717518 100644
--- a/service/src/main/kotlin/org/microg/nlp/service/LocationFuser.kt
+++ b/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()
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
+
class LocationComparator : Comparator {
/**
@@ -139,8 +156,116 @@ class LocationFuser(private val context: Context, private val root: UnifiedLocat
val SWITCH_ON_FRESHNESS_CLIFF_MS: Long = 30000 // 30 seconds
}
}
+}
- companion object {
- private val TAG = "UnifiedLocation"
+
+class LocationBackendHelper(context: Context, private val locationFuser: LocationFuser, lifecycle: Lifecycle, serviceIntent: Intent, signatureDigest: String?) : AbstractBackendHelper(TAG, context, lifecycle, 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(Constants.LOCATION_EXTRA_BACKEND_PROVIDER, location.provider)
+ location.extras.putString(Constants.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")
+ lifecycleScope.launchWhenStarted {
+ 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()
+ }
}
}
diff --git a/service/src/main/kotlin/org/microg/nlp/service/LocationService.kt b/service/src/main/kotlin/org/microg/nlp/service/LocationService.kt
new file mode 100644
index 0000000..5676083
--- /dev/null
+++ b/service/src/main/kotlin/org/microg/nlp/service/LocationService.kt
@@ -0,0 +1,348 @@
+/*
+ * 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.Location
+import android.os.Bundle
+import android.os.IBinder
+import android.os.SystemClock
+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
+import java.util.*
+import kotlin.math.max
+import kotlin.math.min
+
+private const val TAG = "LocationService"
+private const val MIN_LOCATION_INTERVAL = 2500L
+
+class LocationService : LifecycleService() {
+ private lateinit var service: LocationServiceImpl
+
+ override fun onCreate() {
+ super.onCreate()
+ Log.d(TAG, "Creating userspace service...")
+ service = LocationServiceImpl(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?) {
+ service.dump(writer)
+ }
+}
+
+interface LocationReceiver {
+ fun reportLocation(location: Location)
+}
+
+class LocationRequestInternal(private var request: LocationRequest, private val extras: Bundle) {
+ val id: String
+ get() = request.id
+ val callingUid: Int
+ get() = extras.getInt("callingUid")
+ val callingPid: Int
+ get() = extras.getInt("callingPid")
+ val callingPackage: String?
+ get() = extras.getString("callingPackage")
+ val packageName: String
+ get() = extras.getString("packageName")!!
+ val interval: Long
+ get() = request.interval
+ val numUpdates: Int
+ get() = request.numUpdates
+ var updatesDelivered: Int = 0
+ private set
+ val updatesPending: Int
+ get() = (numUpdates - updatesDelivered).coerceAtLeast(0)
+ val listener: ILocationListener
+ get() = request.listener
+ val source: String
+ get() = extras.getString("source") ?: ""
+
+ fun report(context: Context, location: Location) {
+ if (updatesPending <= 0) throw IllegalStateException("Not waiting for updates")
+ if (context.checkPermission("android.permission.ACCESS_COARSE_LOCATION", callingPid, callingUid) != PERMISSION_GRANTED) throw SecurityException("No permission to access location")
+ listener.onLocation(STATUS_OK, location)
+ updatesDelivered++
+ }
+
+ fun matches(other: LocationRequestInternal): Boolean {
+ if (id == other.id && callingPid == other.callingPid) return true
+ return false
+ }
+
+ fun adopt(requestInternal: LocationRequestInternal) {
+ updatesDelivered = 0
+ request = requestInternal.request
+ extras.putAll(requestInternal.extras)
+ }
+}
+
+class LocationServiceImpl(private val context: Context, private val lifecycle: Lifecycle) : ILocationService.Stub(), LifecycleOwner, LocationReceiver {
+ private val requests = arrayListOf()
+ private val fuser = LocationFuser(context, lifecycle, this)
+ private var lastLocation: Location? = null
+ private var interval: Long = 0
+ private val timer: Timer = Timer("location-requests")
+ private var timerTask: TimerTask? = null
+ private var lastTime: Long = 0
+
+ init {
+ lifecycleScope.launchWhenStarted {
+ Log.d(TAG, "Preparing LocationFuser...")
+ fuser.reset()
+ fuser.bind()
+ fuser.update()
+ Log.d(TAG, "Finished preparing LocationFuser")
+ }
+ }
+
+ private fun updateLocationInterval() {
+ var interval: Long = Long.MAX_VALUE
+ var requestNow = false
+ synchronized(requests) {
+ for (request in requests) {
+ if (request.interval == 0L && request.updatesPending == 1) requestNow = true
+ if (request.interval <= 0 || request.updatesPending <= 0) continue
+ interval = min(interval, request.interval)
+ }
+ }
+ interval = max(interval, MIN_LOCATION_INTERVAL)
+
+ if (this.interval == interval) return
+ this.interval = interval
+
+ synchronized(timer) {
+ timerTask?.cancel()
+ timerTask = null
+
+ if (interval < Long.MAX_VALUE) {
+ Log.d(TAG, "Set merged location interval to $interval")
+
+ val timerTask = object : TimerTask() {
+ override fun run() {
+ lifecycleScope.launchWhenStarted {
+ lastTime = SystemClock.elapsedRealtime()
+ fuser.update()
+ Log.d(TAG, "Triggered update")
+ }
+ }
+ }
+ val delay = if (requestNow) {
+ 0
+ } else {
+ (interval - (SystemClock.elapsedRealtime() - lastTime)).coerceIn(0, interval)
+ }
+ timer.scheduleAtFixedRate(timerTask, delay, interval)
+ this.timerTask = timerTask
+ } else {
+ Log.d(TAG, "Disable location updates")
+ }
+ }
+ }
+
+ 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()
+ val callingPackage = getCallingPackage()
+ options.putString("callingPackage", callingPackage)
+ if (!options.containsKey("packageName")) {
+ options.putString("packageName", callingPackage)
+ } else if (context.checkCallingPermission("org.microg.nlp.SERVICE_ADMIN") != PERMISSION_GRANTED && context.packageName != callingPackage) {
+ val claimedPackageName = options.getString("packageName")
+ if (context.packageManager.getPackagesForUid(getCallingUid())?.any { it == claimedPackageName } != true) {
+ Log.d(TAG, "$callingPackage invalidly claimed package name $claimedPackageName, ignoring")
+ options.putString("packageName", callingPackage)
+ }
+ }
+ 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 getLastLocation(listener: ILocationListener?, options: Bundle?) {
+ val extras = processOptions(options)
+ if (listener == null || extras.getString("packageName") == null) return
+ lifecycleScope.launchWhenStarted {
+ if (extras.checkPermission("android.permission.ACCESS_COARSE_LOCATION") != PERMISSION_GRANTED)
+ return@launchWhenStarted listener.onLocation(STATUS_PERMISSION_ERROR, null)
+ listener.onLocation(STATUS_OK, lastLocation)
+ }
+ }
+
+ override fun getLastLocationForBackend(packageName: String?, className: String?, signatureDigest: String?, listener: ILocationListener?, options: Bundle?) {
+ val extras = processOptions(options)
+ if (listener == null || extras.getString("packageName") == null) return
+ lifecycleScope.launchWhenStarted {
+ if (extras.checkPermission("android.permission.ACCESS_COARSE_LOCATION") != PERMISSION_GRANTED)
+ return@launchWhenStarted listener.onLocation(STATUS_PERMISSION_ERROR, null)
+ if (extras.checkPermission("org.microg.nlp.SERVICE_ADMIN") != PERMISSION_GRANTED)
+ return@launchWhenStarted listener.onLocation(STATUS_PERMISSION_ERROR, null)
+ listener.onLocation(STATUS_OK, fuser.getLastLocationForBackend(packageName, className, signatureDigest))
+ }
+ }
+
+ override fun updateLocationRequest(request: LocationRequest?, callback: IStatusCallback?, options: Bundle?) {
+ val extras = processOptions(options)
+ if (callback == null || extras.getString("packageName") == null) return
+ lifecycleScope.launchWhenStarted {
+ if (extras.checkPermission("android.permission.ACCESS_COARSE_LOCATION") != PERMISSION_GRANTED)
+ return@launchWhenStarted callback.onStatus(STATUS_PERMISSION_ERROR)
+ if (request == null)
+ return@launchWhenStarted callback.onStatus(STATUS_INVALID_ARGS)
+ val requestInternal = LocationRequestInternal(request, extras)
+ synchronized(requests) {
+ requests.find { it.matches(requestInternal) }?.adopt(requestInternal) ?: requests.add(requestInternal)
+ }
+ updateLocationInterval()
+ callback.onStatus(STATUS_OK)
+ }
+ }
+
+ override fun cancelLocationRequestByListener(listener: ILocationListener?, callback: IStatusCallback?, options: Bundle?) {
+ val extras = processOptions(options)
+ if (callback == null || extras.getString("packageName") == null) return
+ lifecycleScope.launchWhenStarted {
+ if (listener == null)
+ return@launchWhenStarted callback.onStatus(STATUS_INVALID_ARGS)
+ synchronized(requests) {
+ requests.removeAll { it.listener == listener }
+ }
+ updateLocationInterval()
+ callback.onStatus(STATUS_OK)
+ }
+ }
+
+ override fun cancelLocationRequestById(id: String?, callback: IStatusCallback?, options: Bundle?) {
+ val extras = processOptions(options)
+ if (callback == null || extras.getString("packageName") == null) return
+ lifecycleScope.launchWhenStarted {
+ if (id == null)
+ return@launchWhenStarted callback.onStatus(STATUS_INVALID_ARGS)
+ synchronized(requests) {
+ requests.removeAll { it.id == id && it.callingPid == extras.getInt("callingPid") }
+ }
+ updateLocationInterval()
+ callback.onStatus(STATUS_OK)
+ }
+ }
+
+ override fun forceLocationUpdate(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.update()
+ callback.onStatus(STATUS_OK)
+ }
+ }
+
+ 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(STATUS_OK)
+ }
+ }
+
+ override fun getLocationBackends(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(STATUS_OK, Preferences(context).locationBackends.toList())
+ }
+ }
+
+ override fun setLocationBackends(backends: MutableList?, 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).locationBackends = backends.toSet()
+ callback.onStatus(STATUS_OK)
+ }
+ }
+
+ override fun reportLocation(location: Location) {
+ this.lastLocation = location
+ val requestsToDelete = hashSetOf()
+ synchronized(requests) {
+ for (request in requests) {
+ try {
+ request.report(context, location)
+ if (request.updatesPending <= 0) requestsToDelete.add(request)
+ } catch (e: Exception) {
+ Log.w(TAG, "Removing request due to error: ", e)
+ requestsToDelete.add(request)
+ }
+ }
+ requests.removeAll(requestsToDelete)
+ }
+ updateLocationInterval()
+ }
+
+ fun dump(writer: PrintWriter?) {
+ writer?.println("Last reported location: $lastLocation")
+ writer?.println("Current location interval: $interval")
+ writer?.println("${requests.size} requests:")
+ for (request in requests) {
+ writer?.println(" ${request.id} package=${request.packageName} source=${request.source} interval=${request.interval} pending=${request.updatesPending}")
+ }
+ fuser.dump(writer)
+ }
+
+ suspend fun destroy() {
+ fuser.destroy()
+ }
+
+ override fun getLifecycle(): Lifecycle = lifecycle
+}
diff --git a/service/src/main/kotlin/org/microg/nlp/service/PackageChangedReceiver.kt b/service/src/main/kotlin/org/microg/nlp/service/PackageChangedReceiver.kt
index ca8c2ad..fcfc5c9 100644
--- a/service/src/main/kotlin/org/microg/nlp/service/PackageChangedReceiver.kt
+++ b/service/src/main/kotlin/org/microg/nlp/service/PackageChangedReceiver.kt
@@ -11,8 +11,6 @@ import android.content.Intent
import android.content.Intent.*
import android.util.Log
-import org.microg.nlp.client.UnifiedLocationClient
-
class PackageChangedReceiver : BroadcastReceiver() {
private fun isProtectedAction(action: String) = when (action) {
@@ -29,14 +27,14 @@ class PackageChangedReceiver : BroadcastReceiver() {
for (backend in preferences.locationBackends) {
if (backend.startsWith("$packageName/")) {
Log.d(TAG, "Reloading location service for $packageName")
- suspend { UnifiedLocationClient[context].reloadPreferences() }
+ UnifiedLocationServiceEntryPoint.reloadPreferences()
return
}
}
for (backend in preferences.geocoderBackends) {
if (backend.startsWith("$packageName/")) {
Log.d(TAG, "Reloading geocoding service for $packageName")
- suspend { UnifiedLocationClient[context].reloadPreferences() }
+ UnifiedLocationServiceEntryPoint.reloadPreferences()
return
}
}
diff --git a/service/src/main/kotlin/org/microg/nlp/service/UnifiedLocationServiceEntryPoint.kt b/service/src/main/kotlin/org/microg/nlp/service/UnifiedLocationServiceEntryPoint.kt
index 789c088..9823ad2 100644
--- a/service/src/main/kotlin/org/microg/nlp/service/UnifiedLocationServiceEntryPoint.kt
+++ b/service/src/main/kotlin/org/microg/nlp/service/UnifiedLocationServiceEntryPoint.kt
@@ -5,48 +5,49 @@
package org.microg.nlp.service
-import android.app.Service
import android.content.Intent
import android.os.IBinder
import android.util.Log
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.Job
-import kotlinx.coroutines.coroutineScope
+import androidx.lifecycle.LifecycleService
+import androidx.lifecycle.lifecycleScope
+import java.io.FileDescriptor
+import java.io.PrintWriter
-class UnifiedLocationServiceEntryPoint : Service() {
- private var root: UnifiedLocationServiceRoot? = null
-
- @Synchronized
- fun destroy() {
- if (root != null) {
- root!!.destroy()
- root = null
- }
- }
+@Deprecated("Use LocationService or GeocodeService")
+class UnifiedLocationServiceEntryPoint : LifecycleService() {
+ private var root: UnifiedLocationServiceRoot = UnifiedLocationServiceRoot(this, lifecycle)
override fun onCreate() {
+ singleton = this
super.onCreate()
Log.d(TAG, "onCreate")
- destroy()
+ lifecycleScope.launchWhenStarted { root.reset() }
}
override fun onBind(intent: Intent): IBinder? {
+ super.onBind(intent)
Log.d(TAG, "onBind: $intent")
- synchronized(this) {
- if (root == null) {
- root = UnifiedLocationServiceRoot(this, CoroutineScope(Dispatchers.IO + Job()))
- }
- return root!!.asBinder()
- }
+ return root.asBinder()
}
override fun onDestroy() {
+ super.onDestroy()
Log.d(TAG, "onDestroy")
- destroy()
+ root.destroy()
+ singleton = null
+ }
+
+ override fun dump(fd: FileDescriptor?, writer: PrintWriter?, args: Array?) {
+ writer?.println("Singleton: ${singleton != null}")
+ root.dump(writer)
}
companion object {
private val TAG = "ULocService"
+ private var singleton: UnifiedLocationServiceEntryPoint? = null
+
+ fun reloadPreferences() {
+ singleton?.root?.reloadPreferences()
+ }
}
}
diff --git a/service/src/main/kotlin/org/microg/nlp/service/UnifiedLocationServiceInstance.kt b/service/src/main/kotlin/org/microg/nlp/service/UnifiedLocationServiceInstance.kt
index 64c3050..d0457c3 100644
--- a/service/src/main/kotlin/org/microg/nlp/service/UnifiedLocationServiceInstance.kt
+++ b/service/src/main/kotlin/org/microg/nlp/service/UnifiedLocationServiceInstance.kt
@@ -14,14 +14,13 @@ import android.os.Binder.getCallingUid
import android.os.Bundle
import android.os.RemoteException
import android.util.Log
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.Job
-import kotlinx.coroutines.launch
+import androidx.lifecycle.lifecycleScope
import org.microg.nlp.client.UnifiedLocationClient.Companion.KEY_FORCE_NEXT_UPDATE
import org.microg.nlp.client.UnifiedLocationClient.Companion.KEY_OP_PACKAGE_NAME
import org.microg.nlp.client.UnifiedLocationClient.Companion.PERMISSION_SERVICE_ADMIN
+import java.io.PrintWriter
+@Deprecated("Use LocationService or GeocodeService")
class UnifiedLocationServiceInstance(private val root: UnifiedLocationServiceRoot) : UnifiedLocationService.Default() {
private var callback: LocationCallback? = null
private var interval: Long = 0
@@ -88,7 +87,7 @@ class UnifiedLocationServiceInstance(private val root: UnifiedLocationServiceRoo
if (lastLocation == null || lastLocation.time < System.currentTimeMillis() - UnifiedLocationServiceRoot.MAX_LOCATION_AGE || options.getBoolean(KEY_FORCE_NEXT_UPDATE, false)) {
Log.d(TAG, "requestSingleUpdate[$debugPackageString] requesting new location")
singleUpdatePending = true
- root.coroutineScope.launch {
+ root.lifecycleScope.launchWhenStarted {
root.locationFuser.update()
root.updateLocationInterval()
}
@@ -103,6 +102,10 @@ class UnifiedLocationServiceInstance(private val root: UnifiedLocationServiceRoo
}
}
+ fun dump(writer: PrintWriter?) {
+ writer?.println("$debugPackageString: interval $interval, single $singleUpdatePending")
+ }
+
companion object {
private val TAG = "ULocService"
}
diff --git a/service/src/main/kotlin/org/microg/nlp/service/UnifiedLocationServiceRoot.kt b/service/src/main/kotlin/org/microg/nlp/service/UnifiedLocationServiceRoot.kt
index adb14ee..0c7ed52 100644
--- a/service/src/main/kotlin/org/microg/nlp/service/UnifiedLocationServiceRoot.kt
+++ b/service/src/main/kotlin/org/microg/nlp/service/UnifiedLocationServiceRoot.kt
@@ -13,9 +13,11 @@ import android.os.Binder
import android.os.Bundle
import android.os.Process
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 org.microg.nlp.client.UnifiedLocationClient.Companion.PERMISSION_SERVICE_ADMIN
+import java.io.PrintWriter
import java.util.*
import java.util.concurrent.LinkedBlockingQueue
import java.util.concurrent.ThreadPoolExecutor
@@ -25,14 +27,15 @@ import kotlin.collections.set
import kotlin.math.max
import kotlin.math.min
-class UnifiedLocationServiceRoot(private val service: UnifiedLocationServiceEntryPoint, val coroutineScope: CoroutineScope) : UnifiedLocationService.Stub() {
+@Deprecated("Use LocationService or GeocodeService")
+class UnifiedLocationServiceRoot(private val service: UnifiedLocationServiceEntryPoint, private val lifecycle: Lifecycle) : UnifiedLocationService.Stub(), LifecycleOwner, LocationReceiver {
private val instances = HashMap()
private val geocoderThreads: ThreadPoolExecutor = ThreadPoolExecutor(1, Runtime.getRuntime().availableProcessors(), 1, TimeUnit.SECONDS, LinkedBlockingQueue())
private val timer: Timer = Timer("location-requests")
private var timerTask: TimerTask? = null
private var lastTime: Long = 0
- val locationFuser: LocationFuser = LocationFuser(service, this)
- val geocodeFuser: GeocodeFuser = GeocodeFuser(service, this)
+ val locationFuser: LocationFuser = LocationFuser(service, lifecycle, this)
+ val geocodeFuser: GeocodeFuser = GeocodeFuser(service, lifecycle)
var lastReportedLocation: Location? = null
private set
private var interval: Long = 0
@@ -40,10 +43,6 @@ class UnifiedLocationServiceRoot(private val service: UnifiedLocationServiceEntr
val context: Context
get() = service
- init {
- coroutineScope.launch { reset() }
- }
-
val instance: UnifiedLocationServiceInstance
@Synchronized get() {
checkLocationPermission()
@@ -53,7 +52,7 @@ class UnifiedLocationServiceRoot(private val service: UnifiedLocationServiceEntr
}
@Synchronized
- fun reportLocation(location: Location) {
+ override fun reportLocation(location: Location) {
for (instance in ArrayList(instances.values)) {
instance.reportLocation(location)
}
@@ -72,7 +71,7 @@ class UnifiedLocationServiceRoot(private val service: UnifiedLocationServiceEntr
}
fun destroy() {
- coroutineScope.launch {
+ lifecycleScope.launchWhenStarted {
locationFuser.destroy()
geocodeFuser.destroy()
}
@@ -102,7 +101,7 @@ class UnifiedLocationServiceRoot(private val service: UnifiedLocationServiceEntr
val timerTask = object : TimerTask() {
override fun run() {
- coroutineScope.launch {
+ lifecycleScope.launchWhenStarted {
lastTime = System.currentTimeMillis()
locationFuser.update()
}
@@ -138,7 +137,7 @@ class UnifiedLocationServiceRoot(private val service: UnifiedLocationServiceEntr
}
override fun getFromLocationWithOptions(latitude: Double, longitude: Double, maxResults: Int, locale: String, options: Bundle, callback: AddressCallback) {
- coroutineScope.launch {
+ lifecycleScope.launchWhenStarted {
val res = try {
geocodeFuser.getFromLocation(latitude, longitude, maxResults, locale).orEmpty()
} catch (e: Exception) {
@@ -154,7 +153,7 @@ class UnifiedLocationServiceRoot(private val service: UnifiedLocationServiceEntr
}
override fun getFromLocationNameWithOptions(locationName: String, maxResults: Int, lowerLeftLatitude: Double, lowerLeftLongitude: Double, upperRightLatitude: Double, upperRightLongitude: Double, locale: String, options: Bundle, callback: AddressCallback) {
- coroutineScope.launch {
+ lifecycleScope.launchWhenStarted {
val res = try {
geocodeFuser.getFromLocationName(locationName, maxResults, lowerLeftLatitude, lowerLeftLongitude, upperRightLatitude, upperRightLongitude, locale).orEmpty()
} catch (e: Exception) {
@@ -198,7 +197,7 @@ class UnifiedLocationServiceRoot(private val service: UnifiedLocationServiceEntr
override fun reloadPreferences() {
checkAdminPermission();
- coroutineScope.launch {
+ lifecycleScope.launchWhenStarted {
reset()
}
}
@@ -213,7 +212,8 @@ class UnifiedLocationServiceRoot(private val service: UnifiedLocationServiceEntr
return locationFuser.getLastLocationForBackend(packageName, className, signatureDigest)
}
- @Synchronized
+ override fun getLifecycle(): Lifecycle = lifecycle
+
suspend fun reset() {
locationFuser.reset()
locationFuser.bind()
@@ -221,9 +221,19 @@ class UnifiedLocationServiceRoot(private val service: UnifiedLocationServiceEntr
geocodeFuser.bind()
}
+ fun dump(writer: PrintWriter?) {
+ writer?.println("Last reported location: $lastReportedLocation")
+ writer?.println("Current location interval: $interval")
+ for (instance in instances.values) {
+ instance.dump(writer)
+ }
+ locationFuser.dump(writer)
+ geocodeFuser.dump(writer)
+ }
+
companion object {
private val TAG = "ULocService"
val MIN_LOCATION_INTERVAL = 2500L
- val MAX_LOCATION_AGE = 3600000L
+ val MAX_LOCATION_AGE = 300000L
}
}
diff --git a/settings.gradle b/settings.gradle
index 9581e9d..34ec6bb 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -5,6 +5,7 @@
include ':api'
include ':service'
+include ':service-api'
include ':compat'
include ':client'
include ':location-v1'