UnifiedNlp/service/src/main/kotlin/org/microg/nlp/service/UnifiedLocationServiceRoot.kt

240 lines
8.4 KiB
Kotlin

/*
* SPDX-FileCopyrightText: 2019, microG Project Team
* SPDX-License-Identifier: Apache-2.0
*/
package org.microg.nlp.service
import android.Manifest.permission.ACCESS_COARSE_LOCATION
import android.content.Context
import android.location.Address
import android.location.Location
import android.os.Binder
import android.os.Bundle
import android.os.Process
import android.util.Log
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.lifecycleScope
import org.microg.nlp.client.UnifiedLocationClient.Companion.PERMISSION_SERVICE_ADMIN
import java.io.PrintWriter
import java.util.*
import java.util.concurrent.LinkedBlockingQueue
import java.util.concurrent.ThreadPoolExecutor
import java.util.concurrent.TimeUnit
import kotlin.collections.ArrayList
import kotlin.collections.set
import kotlin.math.max
import kotlin.math.min
@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 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, lifecycle, this)
val geocodeFuser: GeocodeFuser = GeocodeFuser(service, lifecycle)
var lastReportedLocation: Location? = null
private set
private var interval: Long = 0
val context: Context
get() = service
val instance: UnifiedLocationServiceInstance
@Synchronized get() {
checkLocationPermission()
val instance = instances[Binder.getCallingPid()] ?: UnifiedLocationServiceInstance(this)
instances[Binder.getCallingPid()] = instance
return instance
}
@Synchronized
override 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() {
lifecycleScope.launchWhenStarted {
locationFuser.destroy()
geocodeFuser.destroy()
}
}
@Synchronized
fun updateLocationInterval() {
var interval: Long = Long.MAX_VALUE
val sb = StringBuilder()
for (instance in ArrayList(instances.values)) {
val implInterval = instance.getInterval()
if (implInterval <= 0) continue
interval = min(interval, implInterval)
if (sb.isNotEmpty()) sb.append(", ")
sb.append("${instance.callingPackage}:${implInterval}ms")
}
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 ($sb)")
val timerTask = object : TimerTask() {
override fun run() {
lifecycleScope.launchWhenStarted {
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() {
if (Binder.getCallingUid() == Process.myUid()) return // Always except self
service.enforceCallingPermission(ACCESS_COARSE_LOCATION, "coarse location permission required")
}
private fun checkAdminPermission() {
if (Binder.getCallingUid() == Process.myUid()) return // Always except self
service.enforceCallingPermission(PERMISSION_SERVICE_ADMIN, "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) {
lifecycleScope.launchWhenStarted {
val res = try {
geocodeFuser.getFromLocation(latitude, longitude, maxResults, locale).orEmpty()
} catch (e: Exception) {
Log.w(TAG, e)
emptyList<Address>()
}
try {
callback.onResult(res)
} catch (e: Exception) {
Log.w(TAG, e)
}
}
}
override fun getFromLocationNameWithOptions(locationName: String, maxResults: Int, lowerLeftLatitude: Double, lowerLeftLongitude: Double, upperRightLatitude: Double, upperRightLongitude: Double, locale: String, options: Bundle, callback: AddressCallback) {
lifecycleScope.launchWhenStarted {
val res = try {
geocodeFuser.getFromLocationName(locationName, maxResults, lowerLeftLatitude, lowerLeftLongitude, upperRightLatitude, upperRightLongitude, locale).orEmpty()
} catch (e: Exception) {
Log.w(TAG, e)
emptyList<Address>()
}
try {
callback.onResult(res)
} catch (e: Exception) {
Log.w(TAG, e)
}
}
}
private fun <E> Set<E>.contentEquals(other: Set<E>): Boolean {
if (other.size != size) return false
return containsAll(other)
}
override fun getLocationBackends(): Array<String> {
return Preferences(service).locationBackends.toTypedArray()
}
override fun setLocationBackends(backends: Array<String>) {
checkAdminPermission();
if (Preferences(service).locationBackends.contentEquals(backends.toSet())) return
Preferences(service).locationBackends = backends.toSet()
reloadPreferences()
}
override fun getGeocoderBackends(): Array<String> {
return Preferences(service).geocoderBackends.toTypedArray()
}
override fun setGeocoderBackends(backends: Array<String>) {
checkAdminPermission();
if (Preferences(service).geocoderBackends.contentEquals(backends.toSet())) return
Preferences(service).geocoderBackends = backends.toSet()
reloadPreferences()
}
override fun reloadPreferences() {
checkAdminPermission();
lifecycleScope.launchWhenStarted {
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)
}
override fun getLifecycle(): Lifecycle = lifecycle
suspend fun reset() {
locationFuser.reset()
locationFuser.bind()
geocodeFuser.reset()
geocodeFuser.bind()
}
fun dump(writer: PrintWriter?) {
writer?.println("Last reported location: $lastReportedLocation")
writer?.println("Current location interval: $interval")
for (instance in instances.values) {
instance.dump(writer)
}
locationFuser.dump(writer)
geocodeFuser.dump(writer)
}
companion object {
private val TAG = "ULocService"
val MIN_LOCATION_INTERVAL = 2500L
val MAX_LOCATION_AGE = 300000L
}
}