UnifiedNlp/location-v2/src/main/java/org/microg/nlp/location/v2/LocationProvider.kt

150 lines
5.2 KiB
Kotlin

/*
* SPDX-FileCopyrightText: 2019, microG Project Team
* SPDX-License-Identifier: Apache-2.0
*/
package org.microg.nlp.location.v2
import android.content.Context
import android.location.Criteria
import android.location.Location
import android.location.LocationProvider
import android.os.Bundle
import android.os.SystemClock
import android.os.WorkSource
import android.util.Log
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.lifecycleScope
import com.android.location.provider.LocationProviderBase
import com.android.location.provider.ProviderPropertiesUnbundled
import com.android.location.provider.ProviderRequestUnbundled
import kotlinx.coroutines.launch
import org.microg.nlp.client.LocationClient
import org.microg.nlp.service.api.Constants.STATUS_OK
import org.microg.nlp.service.api.ILocationListener
import org.microg.nlp.service.api.LocationRequest
import java.io.FileDescriptor
import java.io.PrintWriter
import java.util.*
class LocationProvider(private val context: Context, private val lifecycle: Lifecycle) : LocationProviderBase(TAG, ProviderPropertiesUnbundled.create(false, false, false, false, true, true, true, Criteria.POWER_LOW, Criteria.ACCURACY_COARSE)), LifecycleOwner {
private val client: LocationClient = LocationClient(context, lifecycle)
private val id = UUID.randomUUID().toString()
private var statusUpdateTime = SystemClock.elapsedRealtime()
private val listener = object : ILocationListener.Stub() {
override fun onLocation(statusCode: Int, location: Location?) {
if (statusCode == STATUS_OK && location != null) {
val reportableLocation = Location(location)
for (key in reportableLocation.extras.keySet().toList()) {
if (key?.startsWith("org.microg.nlp.") == true) {
reportableLocation.extras.remove(key)
}
}
Log.d(TAG, "reportLocation: $reportableLocation")
reportLocation(reportableLocation)
}
}
}
private var opPackageName: String? = null
private var autoTime = Long.MAX_VALUE
private var autoUpdate = false
init {
client.defaultOptions.putString("source", "LocationProvider")
client.defaultOptions.putString("requestId", id)
}
override fun onEnable() {
Log.d(TAG, "onEnable")
statusUpdateTime = SystemClock.elapsedRealtime()
}
override fun onDisable() {
Log.d(TAG, "onDisable")
unsetRequest()
statusUpdateTime = SystemClock.elapsedRealtime()
}
override fun onSetRequest(requests: ProviderRequestUnbundled, source: WorkSource) {
Log.v(TAG, "onSetRequest: $requests by $source")
opPackageName = null
try {
val namesField = WorkSource::class.java.getDeclaredField("mNames")
namesField.isAccessible = true
val names = namesField[source] as Array<String>
if (names != null) {
for (name in names) {
if (!EXCLUDED_PACKAGES.contains(name)) {
opPackageName = name
break
}
}
}
} catch (ignored: Exception) {
}
autoTime = requests.interval.coerceAtLeast(FASTEST_REFRESH_INTERVAL)
autoUpdate = requests.reportLocation
Log.v(TAG, "using autoUpdate=$autoUpdate autoTime=$autoTime")
lifecycleScope.launch {
updateRequest()
}
}
suspend fun updateRequest() {
if (client.isConnected()) {
if (autoUpdate) {
client.packageName = opPackageName ?: context.packageName
client.updateLocationRequest(LocationRequest(listener, autoTime, Int.MAX_VALUE, id))
} else {
client.cancelLocationRequestById(id)
}
}
}
fun unsetRequest() {
lifecycleScope.launch {
client.cancelLocationRequestById(id)
}
}
override fun onGetStatus(extras: Bundle?): Int {
return LocationProvider.AVAILABLE
}
override fun onGetStatusUpdateTime(): Long {
return statusUpdateTime
}
override fun onSendExtraCommand(command: String?, extras: Bundle?): Boolean {
Log.d(TAG, "onSendExtraCommand: $command, $extras")
return false
}
override fun onDump(fd: FileDescriptor?, pw: PrintWriter?, args: Array<out String>?) {
dump(pw)
}
fun dump(writer: PrintWriter?) {
writer?.println("ID: $id")
writer?.println("connected: ${client.isConnectedUnsafe}")
writer?.println("active: $autoUpdate")
writer?.println("interval: $autoTime")
}
suspend fun connect() {
Log.d(TAG, "Connecting to userspace service...")
client.connect()
updateRequest()
Log.d(TAG, "Connected to userspace service.")
}
suspend fun disconnect() = client.disconnect()
override fun getLifecycle(): Lifecycle = lifecycle
companion object {
private val EXCLUDED_PACKAGES = listOf("android", "com.android.location.fused", "com.google.android.gms")
private const val FASTEST_REFRESH_INTERVAL: Long = 2500
private const val TAG = "LocationProvider"
}
}