Add new split service logic
This commit is contained in:
parent
768f30c7ae
commit
d092182576
|
@ -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";
|
||||||
|
|
20
build.gradle
20
build.gradle
|
@ -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()
|
||||||
|
|
|
@ -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'
|
|
@ -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" />
|
|
@ -0,0 +1,8 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2022, microG Project Team
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.microg.nlp.service.api;
|
||||||
|
|
||||||
|
parcelable GeocodeRequest;
|
|
@ -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) ;
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2022, microG Project Team
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.microg.nlp.service.api;
|
||||||
|
|
||||||
|
parcelable LocationRequest;
|
|
@ -0,0 +1,8 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2022, microG Project Team
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.microg.nlp.service.api;
|
||||||
|
|
||||||
|
parcelable ReverseGeocodeRequest;
|
|
@ -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";
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
|
@ -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"
|
||||||
}
|
}
|
||||||
|
|
|
@ -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" />
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
|
@ -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"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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"
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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'
|
||||||
|
|
Loading…
Reference in New Issue