/* * 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() 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
() } 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
() } try { callback.onResult(res) } catch (e: Exception) { Log.w(TAG, e) } } } private fun Set.contentEquals(other: Set): Boolean { if (other.size != size) return false return containsAll(other) } override fun getLocationBackends(): Array { return Preferences(service).locationBackends.toTypedArray() } override fun setLocationBackends(backends: Array) { checkAdminPermission(); if (Preferences(service).locationBackends.contentEquals(backends.toSet())) return Preferences(service).locationBackends = backends.toSet() reloadPreferences() } override fun getGeocoderBackends(): Array { return Preferences(service).geocoderBackends.toTypedArray() } override fun setGeocoderBackends(backends: Array) { 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 } }