Add new split service logic

This commit is contained in:
Marvin W 2022-01-18 13:44:56 +01:00
parent 768f30c7ae
commit d092182576
No known key found for this signature in database
GPG Key ID: 072E9235DB996F2A
34 changed files with 1343 additions and 294 deletions

View File

@ -12,8 +12,11 @@ public class Constants {
public static final String ACTION_FORCE_LOCATION = "org.microg.nlp.FORCE_LOCATION"; 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 PERMISSION_FORCE_LOCATION = "org.microg.permission.FORCE_COARSE_LOCATION";
public static final String INTENT_EXTRA_LOCATION = "location"; public static final String INTENT_EXTRA_LOCATION = "location";
@Deprecated
public static final String LOCATION_EXTRA_BACKEND_PROVIDER = "SERVICE_BACKEND_PROVIDER"; public static final String LOCATION_EXTRA_BACKEND_PROVIDER = "SERVICE_BACKEND_PROVIDER";
@Deprecated
public static final String LOCATION_EXTRA_BACKEND_COMPONENT = "SERVICE_BACKEND_COMPONENT"; 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 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_SETTINGS_ACTIVITY = "org.microg.nlp.BACKEND_SETTINGS_ACTIVITY";
public static final String METADATA_BACKEND_ABOUT_ACTIVITY = "org.microg.nlp.BACKEND_ABOUT_ACTIVITY"; public static final String METADATA_BACKEND_ABOUT_ACTIVITY = "org.microg.nlp.BACKEND_ABOUT_ACTIVITY";

View File

@ -4,23 +4,23 @@
*/ */
buildscript { buildscript {
ext.kotlinVersion = '1.3.72' ext.kotlinVersion = '1.6.10'
ext.coroutineVersion = '1.3.7' ext.coroutineVersion = '1.5.2'
ext.appcompatVersion = '1.1.0' ext.appcompatVersion = '1.4.0'
ext.fragmentVersion = '1.2.5' ext.fragmentVersion = '1.4.0'
ext.lifecycleVersion = '2.2.0' ext.lifecycleVersion = '2.4.0'
ext.navigationVersion = '2.3.0' ext.navigationVersion = '2.3.5'
ext.preferenceVersion = '1.1.1' 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.androidMinSdk = 9
ext.androidTargetSdk = 29 ext.androidTargetSdk = 29
ext.androidCompileSdk = 29 ext.androidCompileSdk = 31
repositories { repositories {
jcenter() jcenter()

32
service-api/build.gradle Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -18,13 +18,29 @@
<application> <application>
<service <service
android:name=".UnifiedLocationServiceEntryPoint" android:name=".UnifiedLocationServiceEntryPoint"
android:exported="true" android:exported="false"
tools:ignore="ExportedService"> tools:ignore="ExportedService">
<intent-filter> <intent-filter>
<action android:name="org.microg.nlp.service.UnifiedLocationService" /> <action android:name="org.microg.nlp.service.UnifiedLocationService" />
</intent-filter> </intent-filter>
</service> </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"> <receiver android:name=".PackageChangedReceiver">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.PACKAGE_CHANGED" /> <action android:name="android.intent.action.PACKAGE_CHANGED" />

View File

@ -14,7 +14,10 @@ import android.content.pm.PackageInfo
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.os.IBinder import android.os.IBinder
import android.util.Log import android.util.Log
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import java.io.PrintWriter
import java.security.MessageDigest import java.security.MessageDigest
import java.security.NoSuchAlgorithmException import java.security.NoSuchAlgorithmException
@ -23,11 +26,13 @@ fun <T> Array<out T>?.isNotNullOrEmpty(): Boolean {
return this != null && this.isNotEmpty() 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 private var bound: Boolean = false
protected abstract suspend fun close() protected abstract suspend fun close()
override fun getLifecycle(): Lifecycle = lifecycle
protected abstract fun hasBackend(): Boolean protected abstract fun hasBackend(): Boolean
override fun onServiceConnected(name: ComponentName, service: IBinder) { 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 { companion object {
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
@SuppressLint("PackageManagerGetSignatures") @SuppressLint("PackageManagerGetSignatures")

View File

@ -14,9 +14,11 @@ import android.os.Looper
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.sync.withLock
import org.microg.nlp.api.GeocoderBackend import org.microg.nlp.api.GeocoderBackend
import java.util.concurrent.*
import kotlin.coroutines.suspendCoroutine import kotlin.coroutines.suspendCoroutine
class AsyncGeocoderBackend(binder: IBinder, name: String = "geocoder-backend-thread") : Thread(name) { 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 looper: Looper
private lateinit var handler: Handler private lateinit var handler: Handler
private val mutex = Mutex(true) 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 { suspend fun getFromLocationName(locationName: String?, maxResults: Int, lowerLeftLatitude: Double, lowerLeftLongitude: Double, upperRightLatitude: Double, upperRightLongitude: Double, locale: String?): List<Address> = mutex.withLock {
suspendCoroutine { suspendCoroutine {
handler.post { 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() { suspend fun close() {
mutex.withLock { mutex.withLock {
suspendCoroutine<Unit> { 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 { suspend fun getFromLocationNameWithOptions(locationName: String?, maxResults: Int, lowerLeftLatitude: Double, lowerLeftLongitude: Double, upperRightLatitude: Double, upperRightLongitude: Double, locale: String?, options: Bundle?): List<Address> = mutex.withLock {
suspendCoroutine { suspendCoroutine {
handler.post { 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
}
}

View File

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

View File

@ -5,15 +5,25 @@
package org.microg.nlp.service package org.microg.nlp.service
import android.content.ComponentName
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.location.Address 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 kotlinx.coroutines.launch
import org.microg.nlp.api.Constants.ACTION_GEOCODER_BACKEND import org.microg.nlp.api.Constants.ACTION_GEOCODER_BACKEND
import java.io.PrintWriter
import java.util.ArrayList import java.util.ArrayList
import java.util.concurrent.CopyOnWriteArrayList 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>() private val backendHelpers = CopyOnWriteArrayList<GeocodeBackendHelper>()
suspend fun reset() { suspend fun reset() {
@ -25,7 +35,7 @@ class GeocodeFuser(private val context: Context, private val root: UnifiedLocati
val intent = Intent(ACTION_GEOCODER_BACKEND) val intent = Intent(ACTION_GEOCODER_BACKEND)
intent.setPackage(parts[0]) intent.setPackage(parts[0])
intent.setClassName(parts[0], parts[1]) 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() 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>? { suspend fun getFromLocation(latitude: Double, longitude: Double, maxResults: Int, locale: String): List<Address>? {
if (backendHelpers.isEmpty()) if (backendHelpers.isEmpty())
return null return null
@ -58,6 +75,17 @@ class GeocodeFuser(private val context: Context, private val root: UnifiedLocati
return result 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>? { suspend fun getFromLocationName(locationName: String, maxResults: Int, lowerLeftLatitude: Double, lowerLeftLongitude: Double, upperRightLatitude: Double, upperRightLongitude: Double, locale: String): List<Address>? {
if (backendHelpers.isEmpty()) if (backendHelpers.isEmpty())
return null return null
@ -68,4 +96,114 @@ class GeocodeFuser(private val context: Context, private val root: UnifiedLocati
} }
return result 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
}
} }

View File

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

View File

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

View File

@ -5,23 +5,31 @@
package org.microg.nlp.service package org.microg.nlp.service
import android.annotation.TargetApi
import android.content.ComponentName
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.location.Location import android.location.Location
import android.location.LocationManager import android.location.LocationManager
import android.os.*
import android.util.Log import android.util.Log
import kotlinx.coroutines.CoroutineScope import androidx.lifecycle.Lifecycle
import kotlinx.coroutines.launch import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.lifecycleScope
import java.util.ArrayList import java.util.ArrayList
import java.util.Collections import java.util.Collections
import java.util.Comparator import java.util.Comparator
import org.microg.nlp.api.Constants.ACTION_LOCATION_BACKEND 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 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 val backendHelpers = CopyOnWriteArrayList<LocationBackendHelper>()
private var fusing = false private var fusing = false
@ -38,7 +46,7 @@ class LocationFuser(private val context: Context, private val root: UnifiedLocat
val intent = Intent(ACTION_LOCATION_BACKEND) val intent = Intent(ACTION_LOCATION_BACKEND)
intent.setPackage(parts[0]) intent.setPackage(parts[0])
intent.setClassName(parts[0], parts[1]) 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) { if (lastLocationReportTime < location.time) {
lastLocationReportTime = location.time lastLocationReportTime = location.time
Log.v(TAG, "Fused location: $location") Log.v(TAG, "Fused location: $location")
root.reportLocation(location) receiver.reportLocation(location)
} else { } else {
Log.v(TAG, "Ignoring location update as it's older than other provider.") 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 if (locations[0] == backendResult) continue
backendResults.add(backendResult) backendResults.add(backendResult)
} }
if (!backendResults.isEmpty()) { if (backendResults.isNotEmpty()) {
location.extras.putParcelableArrayList(LOCATION_EXTRA_OTHER_BACKENDS, backendResults) location.extras.putParcelableArrayList(Constants.LOCATION_EXTRA_OTHER_BACKENDS, backendResults)
} }
return location return location
} }
@ -113,11 +121,20 @@ class LocationFuser(private val context: Context, private val root: UnifiedLocat
updateLocation() updateLocation()
} }
fun getLastLocationForBackend(packageName: String, className: String, signatureDigest: String?): Location? = fun getLastLocationForBackend(packageName: String?, className: String?, signatureDigest: String?): Location? =
backendHelpers.find { backendHelpers.find {
it.serviceIntent.`package` == packageName && it.serviceIntent.component?.className == className && (signatureDigest == null || it.signatureDigest == null || it.signatureDigest == signatureDigest) it.serviceIntent.`package` == packageName && it.serviceIntent.component?.className == className && (signatureDigest == null || it.signatureDigest == null || it.signatureDigest == signatureDigest)
}?.lastLocation }?.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<Location> { class LocationComparator : Comparator<Location> {
/** /**
@ -139,8 +156,116 @@ class LocationFuser(private val context: Context, private val root: UnifiedLocat
val SWITCH_ON_FRESHNESS_CLIFF_MS: Long = 30000 // 30 seconds 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()
}
} }
} }

View File

@ -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<out String>?) {
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") ?: "<none>"
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<LocationRequestInternal>()
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<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).locationBackends = backends.toSet()
callback.onStatus(STATUS_OK)
}
}
override fun reportLocation(location: Location) {
this.lastLocation = location
val requestsToDelete = hashSetOf<LocationRequestInternal>()
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
}

View File

@ -11,8 +11,6 @@ import android.content.Intent
import android.content.Intent.* import android.content.Intent.*
import android.util.Log import android.util.Log
import org.microg.nlp.client.UnifiedLocationClient
class PackageChangedReceiver : BroadcastReceiver() { class PackageChangedReceiver : BroadcastReceiver() {
private fun isProtectedAction(action: String) = when (action) { private fun isProtectedAction(action: String) = when (action) {
@ -29,14 +27,14 @@ class PackageChangedReceiver : BroadcastReceiver() {
for (backend in preferences.locationBackends) { for (backend in preferences.locationBackends) {
if (backend.startsWith("$packageName/")) { if (backend.startsWith("$packageName/")) {
Log.d(TAG, "Reloading location service for $packageName") Log.d(TAG, "Reloading location service for $packageName")
suspend { UnifiedLocationClient[context].reloadPreferences() } UnifiedLocationServiceEntryPoint.reloadPreferences()
return return
} }
} }
for (backend in preferences.geocoderBackends) { for (backend in preferences.geocoderBackends) {
if (backend.startsWith("$packageName/")) { if (backend.startsWith("$packageName/")) {
Log.d(TAG, "Reloading geocoding service for $packageName") Log.d(TAG, "Reloading geocoding service for $packageName")
suspend { UnifiedLocationClient[context].reloadPreferences() } UnifiedLocationServiceEntryPoint.reloadPreferences()
return return
} }
} }

View File

@ -5,48 +5,49 @@
package org.microg.nlp.service package org.microg.nlp.service
import android.app.Service
import android.content.Intent import android.content.Intent
import android.os.IBinder import android.os.IBinder
import android.util.Log import android.util.Log
import kotlinx.coroutines.CoroutineScope import androidx.lifecycle.LifecycleService
import kotlinx.coroutines.Dispatchers import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.Job import java.io.FileDescriptor
import kotlinx.coroutines.coroutineScope import java.io.PrintWriter
class UnifiedLocationServiceEntryPoint : Service() { @Deprecated("Use LocationService or GeocodeService")
private var root: UnifiedLocationServiceRoot? = null class UnifiedLocationServiceEntryPoint : LifecycleService() {
private var root: UnifiedLocationServiceRoot = UnifiedLocationServiceRoot(this, lifecycle)
@Synchronized
fun destroy() {
if (root != null) {
root!!.destroy()
root = null
}
}
override fun onCreate() { override fun onCreate() {
singleton = this
super.onCreate() super.onCreate()
Log.d(TAG, "onCreate") Log.d(TAG, "onCreate")
destroy() lifecycleScope.launchWhenStarted { root.reset() }
} }
override fun onBind(intent: Intent): IBinder? { override fun onBind(intent: Intent): IBinder? {
super.onBind(intent)
Log.d(TAG, "onBind: $intent") Log.d(TAG, "onBind: $intent")
synchronized(this) { return root.asBinder()
if (root == null) {
root = UnifiedLocationServiceRoot(this, CoroutineScope(Dispatchers.IO + Job()))
}
return root!!.asBinder()
}
} }
override fun onDestroy() { override fun onDestroy() {
super.onDestroy()
Log.d(TAG, "onDestroy") Log.d(TAG, "onDestroy")
destroy() root.destroy()
singleton = null
}
override fun dump(fd: FileDescriptor?, writer: PrintWriter?, args: Array<out String>?) {
writer?.println("Singleton: ${singleton != null}")
root.dump(writer)
} }
companion object { companion object {
private val TAG = "ULocService" private val TAG = "ULocService"
private var singleton: UnifiedLocationServiceEntryPoint? = null
fun reloadPreferences() {
singleton?.root?.reloadPreferences()
}
} }
} }

View File

@ -14,14 +14,13 @@ import android.os.Binder.getCallingUid
import android.os.Bundle import android.os.Bundle
import android.os.RemoteException import android.os.RemoteException
import android.util.Log import android.util.Log
import kotlinx.coroutines.CoroutineScope import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import org.microg.nlp.client.UnifiedLocationClient.Companion.KEY_FORCE_NEXT_UPDATE 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.KEY_OP_PACKAGE_NAME
import org.microg.nlp.client.UnifiedLocationClient.Companion.PERMISSION_SERVICE_ADMIN 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() { class UnifiedLocationServiceInstance(private val root: UnifiedLocationServiceRoot) : UnifiedLocationService.Default() {
private var callback: LocationCallback? = null private var callback: LocationCallback? = null
private var interval: Long = 0 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)) { 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") Log.d(TAG, "requestSingleUpdate[$debugPackageString] requesting new location")
singleUpdatePending = true singleUpdatePending = true
root.coroutineScope.launch { root.lifecycleScope.launchWhenStarted {
root.locationFuser.update() root.locationFuser.update()
root.updateLocationInterval() 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 { companion object {
private val TAG = "ULocService" private val TAG = "ULocService"
} }

View File

@ -13,9 +13,11 @@ import android.os.Binder
import android.os.Bundle import android.os.Bundle
import android.os.Process import android.os.Process
import android.util.Log import android.util.Log
import kotlinx.coroutines.CoroutineScope import androidx.lifecycle.Lifecycle
import kotlinx.coroutines.launch import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.lifecycleScope
import org.microg.nlp.client.UnifiedLocationClient.Companion.PERMISSION_SERVICE_ADMIN import org.microg.nlp.client.UnifiedLocationClient.Companion.PERMISSION_SERVICE_ADMIN
import java.io.PrintWriter
import java.util.* import java.util.*
import java.util.concurrent.LinkedBlockingQueue import java.util.concurrent.LinkedBlockingQueue
import java.util.concurrent.ThreadPoolExecutor import java.util.concurrent.ThreadPoolExecutor
@ -25,14 +27,15 @@ import kotlin.collections.set
import kotlin.math.max import kotlin.math.max
import kotlin.math.min 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<Int, UnifiedLocationServiceInstance>() private val instances = HashMap<Int, UnifiedLocationServiceInstance>()
private val geocoderThreads: ThreadPoolExecutor = ThreadPoolExecutor(1, Runtime.getRuntime().availableProcessors(), 1, TimeUnit.SECONDS, LinkedBlockingQueue()) private val geocoderThreads: ThreadPoolExecutor = ThreadPoolExecutor(1, Runtime.getRuntime().availableProcessors(), 1, TimeUnit.SECONDS, LinkedBlockingQueue())
private val timer: Timer = Timer("location-requests") private val timer: Timer = Timer("location-requests")
private var timerTask: TimerTask? = null private var timerTask: TimerTask? = null
private var lastTime: Long = 0 private var lastTime: Long = 0
val locationFuser: LocationFuser = LocationFuser(service, this) val locationFuser: LocationFuser = LocationFuser(service, lifecycle, this)
val geocodeFuser: GeocodeFuser = GeocodeFuser(service, this) val geocodeFuser: GeocodeFuser = GeocodeFuser(service, lifecycle)
var lastReportedLocation: Location? = null var lastReportedLocation: Location? = null
private set private set
private var interval: Long = 0 private var interval: Long = 0
@ -40,10 +43,6 @@ class UnifiedLocationServiceRoot(private val service: UnifiedLocationServiceEntr
val context: Context val context: Context
get() = service get() = service
init {
coroutineScope.launch { reset() }
}
val instance: UnifiedLocationServiceInstance val instance: UnifiedLocationServiceInstance
@Synchronized get() { @Synchronized get() {
checkLocationPermission() checkLocationPermission()
@ -53,7 +52,7 @@ class UnifiedLocationServiceRoot(private val service: UnifiedLocationServiceEntr
} }
@Synchronized @Synchronized
fun reportLocation(location: Location) { override fun reportLocation(location: Location) {
for (instance in ArrayList(instances.values)) { for (instance in ArrayList(instances.values)) {
instance.reportLocation(location) instance.reportLocation(location)
} }
@ -72,7 +71,7 @@ class UnifiedLocationServiceRoot(private val service: UnifiedLocationServiceEntr
} }
fun destroy() { fun destroy() {
coroutineScope.launch { lifecycleScope.launchWhenStarted {
locationFuser.destroy() locationFuser.destroy()
geocodeFuser.destroy() geocodeFuser.destroy()
} }
@ -102,7 +101,7 @@ class UnifiedLocationServiceRoot(private val service: UnifiedLocationServiceEntr
val timerTask = object : TimerTask() { val timerTask = object : TimerTask() {
override fun run() { override fun run() {
coroutineScope.launch { lifecycleScope.launchWhenStarted {
lastTime = System.currentTimeMillis() lastTime = System.currentTimeMillis()
locationFuser.update() 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) { override fun getFromLocationWithOptions(latitude: Double, longitude: Double, maxResults: Int, locale: String, options: Bundle, callback: AddressCallback) {
coroutineScope.launch { lifecycleScope.launchWhenStarted {
val res = try { val res = try {
geocodeFuser.getFromLocation(latitude, longitude, maxResults, locale).orEmpty() geocodeFuser.getFromLocation(latitude, longitude, maxResults, locale).orEmpty()
} catch (e: Exception) { } 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) { 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 { val res = try {
geocodeFuser.getFromLocationName(locationName, maxResults, lowerLeftLatitude, lowerLeftLongitude, upperRightLatitude, upperRightLongitude, locale).orEmpty() geocodeFuser.getFromLocationName(locationName, maxResults, lowerLeftLatitude, lowerLeftLongitude, upperRightLatitude, upperRightLongitude, locale).orEmpty()
} catch (e: Exception) { } catch (e: Exception) {
@ -198,7 +197,7 @@ class UnifiedLocationServiceRoot(private val service: UnifiedLocationServiceEntr
override fun reloadPreferences() { override fun reloadPreferences() {
checkAdminPermission(); checkAdminPermission();
coroutineScope.launch { lifecycleScope.launchWhenStarted {
reset() reset()
} }
} }
@ -213,7 +212,8 @@ class UnifiedLocationServiceRoot(private val service: UnifiedLocationServiceEntr
return locationFuser.getLastLocationForBackend(packageName, className, signatureDigest) return locationFuser.getLastLocationForBackend(packageName, className, signatureDigest)
} }
@Synchronized override fun getLifecycle(): Lifecycle = lifecycle
suspend fun reset() { suspend fun reset() {
locationFuser.reset() locationFuser.reset()
locationFuser.bind() locationFuser.bind()
@ -221,9 +221,19 @@ class UnifiedLocationServiceRoot(private val service: UnifiedLocationServiceEntr
geocodeFuser.bind() 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 { companion object {
private val TAG = "ULocService" private val TAG = "ULocService"
val MIN_LOCATION_INTERVAL = 2500L val MIN_LOCATION_INTERVAL = 2500L
val MAX_LOCATION_AGE = 3600000L val MAX_LOCATION_AGE = 300000L
} }
} }

View File

@ -5,6 +5,7 @@
include ':api' include ':api'
include ':service' include ':service'
include ':service-api'
include ':compat' include ':compat'
include ':client' include ':client'
include ':location-v1' include ':location-v1'