Add service
This commit is contained in:
parent
75d87e1d49
commit
aff82ab98c
73
service/build.gradle
Normal file
73
service/build.gradle
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2019, microG Project Team
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
apply plugin: 'com.android.library'
|
||||||
|
apply plugin: 'kotlin-android'
|
||||||
|
apply plugin: 'kotlin-android-extensions'
|
||||||
|
apply plugin: 'maven-publish'
|
||||||
|
|
||||||
|
android {
|
||||||
|
compileSdkVersion androidCompileSdk
|
||||||
|
buildToolsVersion "$androidBuildVersionTools"
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
versionName version
|
||||||
|
minSdkVersion androidMinSdk
|
||||||
|
targetSdkVersion androidTargetSdk
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceSets {
|
||||||
|
main.java.srcDirs += 'src/main/kotlin'
|
||||||
|
}
|
||||||
|
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility = 1.8
|
||||||
|
targetCompatibility = 1.8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation project(':api')
|
||||||
|
implementation project(':client')
|
||||||
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlinVersion"
|
||||||
|
}
|
||||||
|
|
||||||
|
afterEvaluate {
|
||||||
|
publishing {
|
||||||
|
publications {
|
||||||
|
release(MavenPublication) {
|
||||||
|
pom {
|
||||||
|
name = 'UnifiedNlp Service'
|
||||||
|
description = 'UnifiedNlp service library'
|
||||||
|
url = 'https://github.com/microg/UnifiedNlp'
|
||||||
|
licenses {
|
||||||
|
license {
|
||||||
|
name = 'The Apache Software License, Version 2.0'
|
||||||
|
url = 'http://www.apache.org/licenses/LICENSE-2.0.txt'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
developers {
|
||||||
|
developer {
|
||||||
|
id = 'microg'
|
||||||
|
name = 'microG Team'
|
||||||
|
}
|
||||||
|
developer {
|
||||||
|
id = 'mar-v-in'
|
||||||
|
name = 'Marvin W.'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
scm {
|
||||||
|
url = 'https://github.com/microg/UnifiedNlp'
|
||||||
|
connection = 'scm:git:https://github.com/microg/UnifiedNlp.git'
|
||||||
|
developerConnection = 'scm:git:ssh://github.com/microg/UnifiedNlp.git'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
from components.release
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
45
service/src/main/AndroidManifest.xml
Normal file
45
service/src/main/AndroidManifest.xml
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?><!--
|
||||||
|
~ Copyright (C) 2013-2019 microG Project Team
|
||||||
|
~
|
||||||
|
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
~ you may not use this file except in compliance with the License.
|
||||||
|
~ You may obtain a copy of the License at
|
||||||
|
~
|
||||||
|
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
~
|
||||||
|
~ Unless required by applicable law or agreed to in writing, software
|
||||||
|
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
~ See the License for the specific language governing permissions and
|
||||||
|
~ limitations under the License.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
package="org.microg.nlp.service">
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||||
|
|
||||||
|
<application>
|
||||||
|
<service
|
||||||
|
android:name=".UnifiedLocationServiceEntryPoint"
|
||||||
|
android:exported="true"
|
||||||
|
android:process=":ulocservice"
|
||||||
|
tools:ignore="ExportedService">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="org.microg.nlp.service.UnifiedLocationService" />
|
||||||
|
</intent-filter>
|
||||||
|
</service>
|
||||||
|
|
||||||
|
<receiver android:name=".PackageChangedReceiver">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.PACKAGE_CHANGED" />
|
||||||
|
<action android:name="android.intent.action.PACKAGE_REMOVED" />
|
||||||
|
<action android:name="android.intent.action.PACKAGE_REPLACED" />
|
||||||
|
<action android:name="android.intent.action.PACKAGE_RESTARTED" />
|
||||||
|
|
||||||
|
<data android:scheme="package" />
|
||||||
|
</intent-filter>
|
||||||
|
</receiver>
|
||||||
|
</application>
|
||||||
|
</manifest>
|
|
@ -0,0 +1,124 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2014, microG Project Team
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.microg.nlp.service
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.content.ComponentName
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.ServiceConnection
|
||||||
|
import android.content.pm.PackageInfo
|
||||||
|
import android.content.pm.PackageManager
|
||||||
|
import android.content.pm.Signature
|
||||||
|
import android.os.IBinder
|
||||||
|
import android.os.RemoteException
|
||||||
|
import android.util.Log
|
||||||
|
|
||||||
|
import java.security.MessageDigest
|
||||||
|
import java.security.NoSuchAlgorithmException
|
||||||
|
|
||||||
|
fun <T> Array<out T>?.isNotNullOrEmpty(): Boolean {
|
||||||
|
return this != null && this.isNotEmpty()
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class AbstractBackendHelper(private val TAG: String, private val context: Context, val serviceIntent: Intent, val signatureDigest: String?) : ServiceConnection {
|
||||||
|
private var bound: Boolean = false
|
||||||
|
|
||||||
|
protected abstract fun close()
|
||||||
|
|
||||||
|
protected abstract fun hasBackend(): Boolean
|
||||||
|
|
||||||
|
override fun onServiceConnected(name: ComponentName, service: IBinder) {
|
||||||
|
bound = true
|
||||||
|
Log.d(TAG, "Bound to: $name")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onServiceDisconnected(name: ComponentName) {
|
||||||
|
bound = false
|
||||||
|
Log.d(TAG, "Unbound from: $name")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun unbind() {
|
||||||
|
if (bound) {
|
||||||
|
if (hasBackend()) {
|
||||||
|
try {
|
||||||
|
close()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.w(TAG, e)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
Log.d(TAG, "Unbinding from: $serviceIntent")
|
||||||
|
context.unbindService(this)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.w(TAG, e)
|
||||||
|
}
|
||||||
|
|
||||||
|
bound = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun bind() {
|
||||||
|
if (!bound) {
|
||||||
|
Log.d(TAG, "Binding to: $serviceIntent sig: $signatureDigest")
|
||||||
|
if (signatureDigest == null) {
|
||||||
|
Log.w(TAG, "No signature digest provided. Aborting.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (serviceIntent.getPackage() == null) {
|
||||||
|
Log.w(TAG, "Intent is not properly resolved, can't verify signature. Aborting.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (signatureDigest != firstSignatureDigest(context, serviceIntent.getPackage())) {
|
||||||
|
Log.w(TAG, "Target signature does not match selected package (" + signatureDigest + " = " + firstSignatureDigest(context, serviceIntent.getPackage()) + "). Aborting.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
context.bindService(serviceIntent, this, Context.BIND_AUTO_CREATE)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.w(TAG, e)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
|
@SuppressLint("PackageManagerGetSignatures")
|
||||||
|
fun firstSignatureDigest(context: Context, packageName: String?): String? {
|
||||||
|
val packageManager = context.packageManager
|
||||||
|
val info: PackageInfo?
|
||||||
|
try {
|
||||||
|
info = packageManager.getPackageInfo(packageName!!, PackageManager.GET_SIGNATURES)
|
||||||
|
} catch (e: PackageManager.NameNotFoundException) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (info?.signatures.isNotNullOrEmpty()) {
|
||||||
|
for (sig in info.signatures) {
|
||||||
|
sha256sum(sig.toByteArray())?.let { return it }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun sha256sum(bytes: ByteArray): String? {
|
||||||
|
try {
|
||||||
|
val md = MessageDigest.getInstance("SHA-256")
|
||||||
|
val digest = md.digest(bytes)
|
||||||
|
val sb = StringBuilder(2 * digest.size)
|
||||||
|
for (b in digest) {
|
||||||
|
sb.append(String.format("%02x", b))
|
||||||
|
}
|
||||||
|
return sb.toString()
|
||||||
|
} catch (e: NoSuchAlgorithmException) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,86 @@
|
||||||
|
/*
|
||||||
|
* 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 org.microg.nlp.api.GeocoderBackend
|
||||||
|
|
||||||
|
class GeocodeBackendHelper(context: Context, serviceIntent: Intent, signatureDigest: String?) : AbstractBackendHelper(TAG, context, serviceIntent, signatureDigest) {
|
||||||
|
private var backend: GeocoderBackend? = null
|
||||||
|
|
||||||
|
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) ?: emptyList()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.w(TAG, e)
|
||||||
|
unbind()
|
||||||
|
return emptyList()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
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) ?: emptyList()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.w(TAG, e)
|
||||||
|
unbind()
|
||||||
|
return emptyList()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onServiceConnected(name: ComponentName, service: IBinder) {
|
||||||
|
super.onServiceConnected(name, service)
|
||||||
|
backend = GeocoderBackend.Stub.asInterface(service)
|
||||||
|
if (backend != null) {
|
||||||
|
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 fun close() {
|
||||||
|
backend!!.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
public override fun hasBackend(): Boolean {
|
||||||
|
return backend != null
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val TAG = "UnifiedGeocoder"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,73 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2014, microG Project Team
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.microg.nlp.service
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.location.Address
|
||||||
|
import org.microg.nlp.api.Constants.ACTION_GEOCODER_BACKEND
|
||||||
|
import java.util.ArrayList
|
||||||
|
|
||||||
|
class GeocodeFuser(private val context: Context) {
|
||||||
|
private val backendHelpers = ArrayList<GeocodeBackendHelper>()
|
||||||
|
|
||||||
|
init {
|
||||||
|
reset()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun reset() {
|
||||||
|
unbind()
|
||||||
|
backendHelpers.clear()
|
||||||
|
for (backend in Preferences(context).geocoderBackends) {
|
||||||
|
val parts = backend.split("/".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
|
||||||
|
if (parts.size >= 2) {
|
||||||
|
val intent = Intent(ACTION_GEOCODER_BACKEND)
|
||||||
|
intent.setPackage(parts[0])
|
||||||
|
intent.setClassName(parts[0], parts[1])
|
||||||
|
backendHelpers.add(GeocodeBackendHelper(context, intent, if (parts.size >= 3) parts[2] else null))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun bind() {
|
||||||
|
for (backendHelper in backendHelpers) {
|
||||||
|
backendHelper.bind()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun unbind() {
|
||||||
|
for (backendHelper in backendHelpers) {
|
||||||
|
backendHelper.unbind()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun destroy() {
|
||||||
|
unbind()
|
||||||
|
backendHelpers.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getFromLocation(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.getFromLocation(latitude, longitude, maxResults, locale)
|
||||||
|
result.addAll(backendResult)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getFromLocationName(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.getFromLocationName(locationName, maxResults, lowerLeftLatitude, lowerLeftLongitude, upperRightLatitude, upperRightLongitude, locale)
|
||||||
|
result.addAll(backendResult)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,135 @@
|
||||||
|
/*
|
||||||
|
* 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.net.wifi.WifiManager
|
||||||
|
import android.os.*
|
||||||
|
import android.util.Log
|
||||||
|
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.LocationBackend
|
||||||
|
import org.microg.nlp.api.LocationCallback
|
||||||
|
|
||||||
|
class LocationBackendHelper(context: Context, private val locationFuser: LocationFuser, serviceIntent: Intent, signatureDigest: String?) : AbstractBackendHelper(TAG, context, serviceIntent, signatureDigest) {
|
||||||
|
private val callback = Callback()
|
||||||
|
private var backend: LocationBackend? = 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.
|
||||||
|
*/
|
||||||
|
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 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 = LocationBackend.Stub.asInterface(service)
|
||||||
|
if (backend != null) {
|
||||||
|
try {
|
||||||
|
Log.d(TAG, "Calling open")
|
||||||
|
backend!!.open(callback)
|
||||||
|
if (updateWaiting) {
|
||||||
|
update()
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.w(TAG, e)
|
||||||
|
unbind()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onServiceDisconnected(name: ComponentName) {
|
||||||
|
super.onServiceDisconnected(name)
|
||||||
|
backend = null
|
||||||
|
}
|
||||||
|
|
||||||
|
private inner class Callback : LocationCallback.Stub() {
|
||||||
|
override fun report(location: Location?) {
|
||||||
|
val lastLocation = lastLocation
|
||||||
|
if (location == null || lastLocation != null && location.time > 0 && location.time <= lastLocation.getTime())
|
||||||
|
return
|
||||||
|
this@LocationBackendHelper.lastLocation = location
|
||||||
|
locationFuser.reportLocation()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val TAG = "UnifiedLocation"
|
||||||
|
}
|
||||||
|
}
|
147
service/src/main/kotlin/org/microg/nlp/service/LocationFuser.kt
Normal file
147
service/src/main/kotlin/org/microg/nlp/service/LocationFuser.kt
Normal file
|
@ -0,0 +1,147 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2014, microG Project Team
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.microg.nlp.service
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.location.Location
|
||||||
|
import android.location.LocationManager
|
||||||
|
import android.util.Log
|
||||||
|
|
||||||
|
import java.util.ArrayList
|
||||||
|
import java.util.Collections
|
||||||
|
import java.util.Comparator
|
||||||
|
|
||||||
|
import org.microg.nlp.api.Constants.ACTION_LOCATION_BACKEND
|
||||||
|
import org.microg.nlp.api.Constants.LOCATION_EXTRA_OTHER_BACKENDS
|
||||||
|
|
||||||
|
class LocationFuser(private val context: Context, private val root: UnifiedLocationServiceRoot) {
|
||||||
|
|
||||||
|
private val backendHelpers = ArrayList<LocationBackendHelper>()
|
||||||
|
private var fusing = false
|
||||||
|
private var lastLocationReportTime: Long = 0
|
||||||
|
|
||||||
|
init {
|
||||||
|
reset()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun reset() {
|
||||||
|
unbind()
|
||||||
|
backendHelpers.clear()
|
||||||
|
lastLocationReportTime = 0
|
||||||
|
for (backend in Preferences(context).locationBackends) {
|
||||||
|
Log.d(TAG, "Backend: $backend")
|
||||||
|
val parts = backend.split("/".toRegex()).dropLastWhile(String::isEmpty).toTypedArray()
|
||||||
|
if (parts.size >= 2) {
|
||||||
|
val intent = Intent(ACTION_LOCATION_BACKEND)
|
||||||
|
intent.setPackage(parts[0])
|
||||||
|
intent.setClassName(parts[0], parts[1])
|
||||||
|
backendHelpers.add(LocationBackendHelper(context, this, intent, if (parts.size >= 3) parts[2] else null))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun unbind() {
|
||||||
|
for (handler in backendHelpers) {
|
||||||
|
handler.unbind()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun bind() {
|
||||||
|
fusing = false
|
||||||
|
for (handler in backendHelpers) {
|
||||||
|
handler.bind()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun destroy() {
|
||||||
|
unbind()
|
||||||
|
backendHelpers.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun update() {
|
||||||
|
var hasUpdates = false
|
||||||
|
fusing = true
|
||||||
|
for (handler in backendHelpers) {
|
||||||
|
if (handler.update() != null)
|
||||||
|
hasUpdates = true
|
||||||
|
}
|
||||||
|
fusing = false
|
||||||
|
if (hasUpdates)
|
||||||
|
updateLocation()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateLocation() {
|
||||||
|
val locations = ArrayList<Location>()
|
||||||
|
for (handler in backendHelpers) {
|
||||||
|
handler.lastLocation?.let { locations.add(it) }
|
||||||
|
}
|
||||||
|
val location = mergeLocations(locations)
|
||||||
|
if (location != null) {
|
||||||
|
location.provider = LocationManager.NETWORK_PROVIDER
|
||||||
|
if (lastLocationReportTime < location.time) {
|
||||||
|
lastLocationReportTime = location.time
|
||||||
|
Log.v(TAG, "Fused location: $location")
|
||||||
|
root.reportLocation(location)
|
||||||
|
} else {
|
||||||
|
Log.v(TAG, "Ignoring location update as it's older than other provider.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun mergeLocations(locations: List<Location>): Location? {
|
||||||
|
Collections.sort(locations, LocationComparator.INSTANCE)
|
||||||
|
if (locations.isEmpty()) return null
|
||||||
|
if (locations.size == 1) return locations[0]
|
||||||
|
val location = Location(locations[0])
|
||||||
|
val backendResults = ArrayList<Location>()
|
||||||
|
for (backendResult in locations) {
|
||||||
|
if (locations[0] == backendResult) continue
|
||||||
|
backendResults.add(backendResult)
|
||||||
|
}
|
||||||
|
if (!backendResults.isEmpty()) {
|
||||||
|
location.extras.putParcelableArrayList(LOCATION_EXTRA_OTHER_BACKENDS, backendResults)
|
||||||
|
}
|
||||||
|
return location
|
||||||
|
}
|
||||||
|
|
||||||
|
fun reportLocation() {
|
||||||
|
if (fusing)
|
||||||
|
return
|
||||||
|
updateLocation()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getLastLocationForBackend(packageName: String, className: String, signatureDigest: String?): Location? =
|
||||||
|
backendHelpers.find {
|
||||||
|
it.serviceIntent.`package` == packageName && it.serviceIntent.component?.className == className && (signatureDigest == null || it.signatureDigest == null || it.signatureDigest == signatureDigest)
|
||||||
|
}?.lastLocation
|
||||||
|
|
||||||
|
class LocationComparator : Comparator<Location> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return whether {@param lhs} is better than {@param rhs}
|
||||||
|
*/
|
||||||
|
override fun compare(lhs: Location?, rhs: Location?): Int {
|
||||||
|
if (lhs === rhs) return 0
|
||||||
|
if (lhs == null) return 1
|
||||||
|
if (rhs == null) return -1
|
||||||
|
if (!lhs.hasAccuracy()) return 1
|
||||||
|
if (!rhs.hasAccuracy()) return -1
|
||||||
|
if (rhs.time > lhs.time + SWITCH_ON_FRESHNESS_CLIFF_MS) return 1
|
||||||
|
if (lhs.time > rhs.time + SWITCH_ON_FRESHNESS_CLIFF_MS) return -1
|
||||||
|
return (lhs.accuracy - rhs.accuracy).toInt()
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val INSTANCE = LocationComparator()
|
||||||
|
val SWITCH_ON_FRESHNESS_CLIFF_MS: Long = 30000 // 30 seconds
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val TAG = "UnifiedLocation"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,48 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2019, microG Project Team
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.microg.nlp.service
|
||||||
|
|
||||||
|
import android.content.BroadcastReceiver
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.Intent.*
|
||||||
|
import android.util.Log
|
||||||
|
|
||||||
|
import org.microg.nlp.client.UnifiedLocationClient
|
||||||
|
|
||||||
|
class PackageChangedReceiver : BroadcastReceiver() {
|
||||||
|
|
||||||
|
private fun isProtectedAction(action: String) = when (action) {
|
||||||
|
ACTION_PACKAGE_CHANGED, ACTION_PACKAGE_REMOVED, ACTION_PACKAGE_REPLACED, ACTION_PACKAGE_RESTARTED -> true
|
||||||
|
else -> false
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onReceive(context: Context, intent: Intent) {
|
||||||
|
Log.d(TAG, "Intent received: $intent")
|
||||||
|
if (intent.action?.let { isProtectedAction(it) } != true) return
|
||||||
|
|
||||||
|
val packageName = intent.data!!.schemeSpecificPart
|
||||||
|
val preferences = Preferences(context)
|
||||||
|
for (backend in preferences.locationBackends) {
|
||||||
|
if (backend.startsWith("$packageName/")) {
|
||||||
|
Log.d(TAG, "Reloading location service for $packageName")
|
||||||
|
suspend { UnifiedLocationClient[context].reloadPreferences() }
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (backend in preferences.geocoderBackends) {
|
||||||
|
if (backend.startsWith("$packageName/")) {
|
||||||
|
Log.d(TAG, "Reloading geocoding service for $packageName")
|
||||||
|
suspend { UnifiedLocationClient[context].reloadPreferences() }
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val TAG = "UnifiedService"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2014, microG Project Team
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.microg.nlp.service
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.SharedPreferences
|
||||||
|
|
||||||
|
class Preferences(private val context: Context) {
|
||||||
|
|
||||||
|
private val sharedPreferences: SharedPreferences
|
||||||
|
get() = context.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE)
|
||||||
|
|
||||||
|
var locationBackends: Array<String>
|
||||||
|
get() = splitBackendString(sharedPreferences.getString(PREF_LOCATION_BACKENDS, null))
|
||||||
|
set(backends) {
|
||||||
|
sharedPreferences.edit().putString(PREF_LOCATION_BACKENDS, backends.joinToString("|")).apply()
|
||||||
|
}
|
||||||
|
|
||||||
|
var geocoderBackends: Array<String>
|
||||||
|
get() = splitBackendString(sharedPreferences.getString(PREF_GEOCODER_BACKENDS, null))
|
||||||
|
set(backends) {
|
||||||
|
sharedPreferences.edit().putString(PREF_GEOCODER_BACKENDS, backends.joinToString("|")).apply()
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val PREFERENCES_NAME = "unified_nlp"
|
||||||
|
private val PREF_LOCATION_BACKENDS = "location_backends"
|
||||||
|
private val PREF_GEOCODER_BACKENDS = "geocoder_backends"
|
||||||
|
|
||||||
|
private fun splitBackendString(backendString: String?): Array<String> {
|
||||||
|
return backendString?.split("\\|".toRegex())?.dropLastWhile(String::isEmpty)?.toTypedArray()
|
||||||
|
?: emptyArray()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,48 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2019, microG Project Team
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.microg.nlp.service
|
||||||
|
|
||||||
|
import android.app.Service
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.IBinder
|
||||||
|
import android.util.Log
|
||||||
|
|
||||||
|
class UnifiedLocationServiceEntryPoint : Service() {
|
||||||
|
private var root: UnifiedLocationServiceRoot? = null
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
fun destroy() {
|
||||||
|
if (root != null) {
|
||||||
|
root!!.destroy()
|
||||||
|
root = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreate() {
|
||||||
|
super.onCreate()
|
||||||
|
Log.d(TAG, "onCreate")
|
||||||
|
destroy()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBind(intent: Intent): IBinder? {
|
||||||
|
Log.d(TAG, "onBind: $intent")
|
||||||
|
synchronized(this) {
|
||||||
|
if (root == null) {
|
||||||
|
root = UnifiedLocationServiceRoot(this)
|
||||||
|
}
|
||||||
|
return root!!.asBinder()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroy() {
|
||||||
|
Log.d(TAG, "onDestroy")
|
||||||
|
destroy()
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val TAG = "ULocService"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,75 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2019, microG Project Team
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.microg.nlp.service
|
||||||
|
|
||||||
|
import android.location.Location
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.os.RemoteException
|
||||||
|
import android.util.Log
|
||||||
|
|
||||||
|
import android.os.Binder.getCallingUid
|
||||||
|
import org.microg.nlp.client.UnifiedLocationClient
|
||||||
|
|
||||||
|
class UnifiedLocationServiceInstance(private val root: UnifiedLocationServiceRoot) : UnifiedLocationService.Default() {
|
||||||
|
private var callback: LocationCallback? = null
|
||||||
|
private var interval: Long = 0
|
||||||
|
private var singleUpdatePending = false
|
||||||
|
private val callingPackage = root.context.packageManager.getNameForUid(getCallingUid())
|
||||||
|
|
||||||
|
fun reportLocation(location: Location) {
|
||||||
|
try {
|
||||||
|
if (callback != null) {
|
||||||
|
callback!!.onLocationUpdate(location)
|
||||||
|
}
|
||||||
|
if (singleUpdatePending) {
|
||||||
|
singleUpdatePending = false
|
||||||
|
root.updateLocationInterval()
|
||||||
|
}
|
||||||
|
} catch (e: RemoteException) {
|
||||||
|
root.onDisconnected(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getInterval(): Long {
|
||||||
|
// TODO: Do not report interval if client should no longer receive
|
||||||
|
return if (singleUpdatePending) UnifiedLocationServiceRoot.MIN_LOCATION_INTERVAL else interval
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(RemoteException::class)
|
||||||
|
override fun registerLocationCallback(callback: LocationCallback, options: Bundle) {
|
||||||
|
Log.d(TAG, "registerLocationCallback[$callingPackage]")
|
||||||
|
this.callback = callback
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setUpdateInterval(interval: Long, options: Bundle) {
|
||||||
|
Log.d(TAG, "setUpdateInterval[$callingPackage] interval: $interval")
|
||||||
|
this.interval = interval
|
||||||
|
root.updateLocationInterval()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun requestSingleUpdate(options: Bundle) {
|
||||||
|
val lastLocation = root.lastReportedLocation
|
||||||
|
if (lastLocation == null || lastLocation.time < System.currentTimeMillis() - UnifiedLocationServiceRoot.MAX_LOCATION_AGE || options.getBoolean(UnifiedLocationClient.KEY_FORCE_NEXT_UPDATE, false)) {
|
||||||
|
Log.d(TAG, "requestSingleUpdate[$callingPackage] requesting new location")
|
||||||
|
singleUpdatePending = true
|
||||||
|
root.locationFuser.update()
|
||||||
|
root.updateLocationInterval()
|
||||||
|
} else {
|
||||||
|
Log.d(TAG, "requestSingleUpdate[$callingPackage] using last location ")
|
||||||
|
try {
|
||||||
|
this.callback!!.onLocationUpdate(lastLocation)
|
||||||
|
} catch (e: RemoteException) {
|
||||||
|
root.onDisconnected(this)
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val TAG = "ULocService"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,187 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2019, microG Project Team
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.microg.nlp.service
|
||||||
|
|
||||||
|
import android.Manifest
|
||||||
|
import android.content.Context
|
||||||
|
import android.location.Location
|
||||||
|
import android.os.Binder
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.os.Process.myUid
|
||||||
|
import android.util.Log
|
||||||
|
import java.util.*
|
||||||
|
import java.util.concurrent.LinkedBlockingQueue
|
||||||
|
import java.util.concurrent.ThreadPoolExecutor
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
import kotlin.collections.ArrayList
|
||||||
|
import kotlin.collections.set
|
||||||
|
import kotlin.math.max
|
||||||
|
import kotlin.math.min
|
||||||
|
|
||||||
|
class UnifiedLocationServiceRoot(private val service: UnifiedLocationServiceEntryPoint) : UnifiedLocationService.Stub() {
|
||||||
|
private val instances = HashMap<Int, UnifiedLocationServiceInstance>()
|
||||||
|
private val geocoderThreads: ThreadPoolExecutor = ThreadPoolExecutor(1, Runtime.getRuntime().availableProcessors(), 1, TimeUnit.SECONDS, LinkedBlockingQueue())
|
||||||
|
private val timer: Timer = Timer("location-requests")
|
||||||
|
private var timerTask: TimerTask? = null
|
||||||
|
private var lastTime: Long = 0
|
||||||
|
val locationFuser: LocationFuser = LocationFuser(service, this)
|
||||||
|
val geocodeFuser: GeocodeFuser = GeocodeFuser(service)
|
||||||
|
var lastReportedLocation: Location? = null
|
||||||
|
private set
|
||||||
|
private var interval: Long = 0
|
||||||
|
|
||||||
|
val context: Context
|
||||||
|
get() = service
|
||||||
|
|
||||||
|
init {
|
||||||
|
try {
|
||||||
|
locationFuser.bind()
|
||||||
|
geocodeFuser.bind()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.w(TAG, "Failed loading preferences", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val instance: UnifiedLocationServiceInstance
|
||||||
|
@Synchronized get() {
|
||||||
|
checkLocationPermission()
|
||||||
|
val instance = instances[Binder.getCallingPid()] ?: UnifiedLocationServiceInstance(this)
|
||||||
|
instances[Binder.getCallingPid()] = instance
|
||||||
|
return instance
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
fun reportLocation(location: Location) {
|
||||||
|
for (instance in ArrayList(instances.values)) {
|
||||||
|
instance.reportLocation(location)
|
||||||
|
}
|
||||||
|
lastReportedLocation = location
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
fun onDisconnected(instance: UnifiedLocationServiceInstance) {
|
||||||
|
var instancePid: Int? = null
|
||||||
|
for (pid in instances.keys) {
|
||||||
|
if (instances[pid] === instance) instancePid = pid
|
||||||
|
}
|
||||||
|
if (instancePid != null) {
|
||||||
|
instances.remove(instancePid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun destroy() {
|
||||||
|
locationFuser.destroy()
|
||||||
|
geocodeFuser.destroy()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
fun updateLocationInterval() {
|
||||||
|
var interval: Long = Long.MAX_VALUE
|
||||||
|
for (instance in ArrayList(instances.values)) {
|
||||||
|
val implInterval = instance.getInterval()
|
||||||
|
if (implInterval <= 0) continue
|
||||||
|
interval = min(interval, implInterval)
|
||||||
|
}
|
||||||
|
interval = max(interval, MIN_LOCATION_INTERVAL)
|
||||||
|
|
||||||
|
if (this.interval == interval) return
|
||||||
|
this.interval = interval
|
||||||
|
|
||||||
|
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() {
|
||||||
|
lastTime = System.currentTimeMillis()
|
||||||
|
locationFuser.update()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
timer.scheduleAtFixedRate(timerTask, min(interval, max(0, interval - (System.currentTimeMillis() - lastTime))), interval)
|
||||||
|
this.timerTask = timerTask
|
||||||
|
} else {
|
||||||
|
Log.d(TAG, "Disable location updates")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun checkLocationPermission() {
|
||||||
|
service.enforceCallingPermission(Manifest.permission.ACCESS_COARSE_LOCATION, "coarse location permission required")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun registerLocationCallback(callback: LocationCallback, options: Bundle) {
|
||||||
|
instance.registerLocationCallback(callback, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setUpdateInterval(interval: Long, options: Bundle) {
|
||||||
|
instance.setUpdateInterval(interval, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun requestSingleUpdate(options: Bundle) {
|
||||||
|
instance.requestSingleUpdate(options)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getFromLocationWithOptions(latitude: Double, longitude: Double, maxResults: Int, locale: String, options: Bundle, callback: AddressCallback) {
|
||||||
|
geocoderThreads.execute {
|
||||||
|
callback.onResult(geocodeFuser.getFromLocation(latitude, longitude, maxResults, locale))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getFromLocationNameWithOptions(locationName: String, maxResults: Int, lowerLeftLatitude: Double, lowerLeftLongitude: Double, upperRightLatitude: Double, upperRightLongitude: Double, locale: String, options: Bundle, callback: AddressCallback) {
|
||||||
|
geocoderThreads.execute {
|
||||||
|
callback.onResult(geocodeFuser.getFromLocationName(locationName, maxResults, lowerLeftLatitude, lowerLeftLongitude, upperRightLatitude, upperRightLongitude, locale))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getLocationBackends(): Array<String> {
|
||||||
|
return Preferences(service).locationBackends
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setLocationBackends(backends: Array<String>) {
|
||||||
|
if (Binder.getCallingUid() != myUid()) throw SecurityException("Only allowed from same UID")
|
||||||
|
Preferences(service).locationBackends = backends
|
||||||
|
reloadPreferences()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getGeocoderBackends(): Array<String> {
|
||||||
|
return Preferences(service).geocoderBackends
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setGeocoderBackends(backends: Array<String>) {
|
||||||
|
if (Binder.getCallingUid() != myUid()) throw SecurityException("Only allowed from same UID")
|
||||||
|
Preferences(service).locationBackends = backends
|
||||||
|
reloadPreferences()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun reloadPreferences() {
|
||||||
|
if (Binder.getCallingUid() != myUid()) throw SecurityException("Only allowed from same UID")
|
||||||
|
reset()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getLastLocation(): Location? {
|
||||||
|
checkLocationPermission()
|
||||||
|
return lastReportedLocation
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getLastLocationForBackend(packageName: String, className: String, signatureDigest: String?): Location? {
|
||||||
|
checkLocationPermission()
|
||||||
|
return locationFuser.getLastLocationForBackend(packageName, className, signatureDigest)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
fun reset() {
|
||||||
|
locationFuser.reset()
|
||||||
|
locationFuser.bind()
|
||||||
|
geocodeFuser.reset()
|
||||||
|
geocodeFuser.bind()
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val TAG = "ULocService"
|
||||||
|
val MIN_LOCATION_INTERVAL = 2500L
|
||||||
|
val MAX_LOCATION_AGE = 3600000L
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue