Skip to content

Commit

Permalink
Location: Provide "fused" system location provider
Browse files Browse the repository at this point in the history
  • Loading branch information
mar-v-in committed Sep 29, 2024
1 parent 2149212 commit 511afe8
Show file tree
Hide file tree
Showing 6 changed files with 238 additions and 108 deletions.
11 changes: 11 additions & 0 deletions play-services-location/core/provider/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,17 @@
android:name="serviceVersion"
android:value="2" />
</service>
<service
android:name="org.microg.gms.location.provider.FusedLocationProviderService"
android:exported="true"
android:permission="android.permission.WRITE_SECURE_SETTINGS">
<intent-filter>
<action android:name="com.android.location.service.FusedLocationProvider" />
</intent-filter>
<meta-data
android:name="serviceVersion"
android:value="2" />
</service>
<service
android:name="org.microg.gms.location.provider.GeocodeProviderService"
android:exported="true"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*
* SPDX-FileCopyrightText: 2024 microG Project Team
* SPDX-License-Identifier: Apache-2.0
*/

package org.microg.gms.location.provider

import android.app.PendingIntent
import android.content.Intent
import android.location.Criteria
import android.location.Location
import android.os.Build.VERSION.SDK_INT
import android.util.Log
import androidx.core.location.LocationRequestCompat
import com.android.location.provider.ProviderPropertiesUnbundled
import com.android.location.provider.ProviderRequestUnbundled
import com.google.android.gms.location.LocationRequest
import com.google.android.gms.location.LocationResult
import com.google.android.gms.location.LocationServices
import com.google.android.gms.location.Priority
import kotlin.math.max

class FusedLocationProviderService : IntentLocationProviderService() {
override fun extractLocation(intent: Intent): Location? = LocationResult.extractResult(intent)?.lastLocation

override fun requestIntentUpdated(currentRequest: ProviderRequestUnbundled?, pendingIntent: PendingIntent?) {
val intervalMillis = if (currentRequest?.reportLocation == true) {
max(currentRequest.interval, minIntervalMillis)
} else {
Long.MAX_VALUE
}
val request = LocationRequest.Builder(intervalMillis)
if (SDK_INT >= 31 && currentRequest != null) {
request.setPriority(when(currentRequest.quality) {
LocationRequestCompat.QUALITY_LOW_POWER -> Priority.PRIORITY_LOW_POWER
LocationRequestCompat.QUALITY_HIGH_ACCURACY -> Priority.PRIORITY_HIGH_ACCURACY
else -> Priority.PRIORITY_BALANCED_POWER_ACCURACY
})
request.setMaxUpdateDelayMillis(currentRequest.maxUpdateDelayMillis)
}
try {
LocationServices.getFusedLocationProviderClient(this).requestLocationUpdates(request.build(), pendingIntent)
} catch (e: SecurityException) {
Log.d(TAG, "Failed requesting location updated", e)
}
}

override fun stopIntentUpdated(pendingIntent: PendingIntent?) {
LocationServices.getFusedLocationProviderClient(this).removeLocationUpdates(pendingIntent)
}

override val minIntervalMillis: Long
get() = MIN_INTERVAL_MILLIS
override val minReportMillis: Long
get() = MIN_REPORT_MILLIS
override val properties: ProviderPropertiesUnbundled
get() = PROPERTIES
override val providerName: String
get() = "fused"

companion object {
private const val MIN_INTERVAL_MILLIS = 20000L
private const val MIN_REPORT_MILLIS = 1000L
private val PROPERTIES = ProviderPropertiesUnbundled.create(false, false, false, false, true, true, true, Criteria.POWER_LOW, Criteria.ACCURACY_COARSE)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,41 +7,36 @@ package org.microg.gms.location.provider

import android.app.PendingIntent
import android.app.PendingIntent.FLAG_UPDATE_CURRENT
import android.content.Context
import android.content.Intent
import android.location.Criteria
import android.location.Location
import android.location.LocationManager
import android.os.Build.VERSION.SDK_INT
import android.os.Handler
import android.os.Looper
import android.os.SystemClock
import android.os.WorkSource
import android.util.Log
import androidx.annotation.RequiresApi
import androidx.core.app.PendingIntentCompat
import androidx.core.content.getSystemService
import com.android.location.provider.ProviderPropertiesUnbundled
import com.android.location.provider.ProviderRequestUnbundled
import org.microg.gms.location.*
import org.microg.gms.location.network.LOCATION_EXTRA_PRECISION
import org.microg.gms.location.network.NetworkLocationService
import org.microg.gms.location.provider.NetworkLocationProviderService.Companion.ACTION_REPORT_LOCATION
import org.microg.gms.location.elapsedMillis
import org.microg.gms.location.formatRealtime
import java.io.PrintWriter
import kotlin.math.max

class NetworkLocationProviderPreTiramisu : AbstractLocationProviderPreTiramisu {
class IntentLocationProviderPreTiramisu : AbstractLocationProviderPreTiramisu {
@Deprecated("Use only with SDK < 31")
constructor(context: Context, legacy: Unit) : super(properties) {
this.context = context
constructor(service: IntentLocationProviderService, properties: ProviderPropertiesUnbundled, legacy: Unit) : super(properties) {
this.service = service
}

@RequiresApi(31)
constructor(context: Context) : super(context, properties) {
this.context = context
constructor(service: IntentLocationProviderService, properties: ProviderPropertiesUnbundled) : super(service, properties) {
this.service = service
}

private val context: Context
private val service: IntentLocationProviderService
private var enabled = false
private var currentRequest: ProviderRequestUnbundled? = null
private var pendingIntent: PendingIntent? = null
Expand All @@ -52,29 +47,7 @@ class NetworkLocationProviderPreTiramisu : AbstractLocationProviderPreTiramisu {

private fun updateRequest() {
if (enabled) {
val forceNow: Boolean
val intervalMillis: Long
if (currentRequest?.reportLocation == true) {
forceNow = true
intervalMillis = max(currentRequest?.interval ?: Long.MAX_VALUE, MIN_INTERVAL_MILLIS)
} else {
forceNow = false
intervalMillis = Long.MAX_VALUE
}
val intent = Intent(ACTION_NETWORK_LOCATION_SERVICE)
intent.`package` = context.packageName
intent.putExtra(EXTRA_PENDING_INTENT, pendingIntent)
intent.putExtra(EXTRA_ENABLE, true)
intent.putExtra(EXTRA_INTERVAL_MILLIS, intervalMillis)
intent.putExtra(EXTRA_FORCE_NOW, forceNow)
if (SDK_INT >= 31) {
intent.putExtra(EXTRA_LOW_POWER, currentRequest?.isLowPower ?: false)
intent.putExtra(EXTRA_WORK_SOURCE, currentRequest?.workSource)
}
if (SDK_INT >= 29) {
intent.putExtra(EXTRA_BYPASS, currentRequest?.isLocationSettingsIgnored ?: false)
}
context.startService(intent)
service.requestIntentUpdated(currentRequest, pendingIntent)
reportAgain()
}
}
Expand All @@ -96,9 +69,9 @@ class NetworkLocationProviderPreTiramisu : AbstractLocationProviderPreTiramisu {
override fun enable() {
synchronized(this) {
if (enabled) throw IllegalStateException()
val intent = Intent(context, NetworkLocationProviderService::class.java)
val intent = Intent(service, service.javaClass)
intent.action = ACTION_REPORT_LOCATION
pendingIntent = PendingIntentCompat.getService(context, 0, intent, FLAG_UPDATE_CURRENT, true)
pendingIntent = PendingIntentCompat.getService(service, 0, intent, FLAG_UPDATE_CURRENT, true)
currentRequest = null
enabled = true
when {
Expand All @@ -107,7 +80,7 @@ class NetworkLocationProviderPreTiramisu : AbstractLocationProviderPreTiramisu {
}
try {
if (lastReportedLocation == null) {
lastReportedLocation = context.getSystemService<LocationManager>()?.getLastKnownLocation(LocationManager.NETWORK_PROVIDER)
lastReportedLocation = service.getSystemService<LocationManager>()?.getLastKnownLocation(service.providerName)
}
} catch (_: SecurityException) {
} catch (_: Exception) {
Expand All @@ -118,10 +91,7 @@ class NetworkLocationProviderPreTiramisu : AbstractLocationProviderPreTiramisu {
override fun disable() {
synchronized(this) {
if (!enabled) throw IllegalStateException()
val intent = Intent(context, NetworkLocationService::class.java)
intent.putExtra(EXTRA_PENDING_INTENT, pendingIntent)
intent.putExtra(EXTRA_ENABLE, false)
context.startService(intent)
service.stopIntentUpdated(pendingIntent)
pendingIntent?.cancel()
pendingIntent = null
currentRequest = null
Expand All @@ -133,28 +103,21 @@ class NetworkLocationProviderPreTiramisu : AbstractLocationProviderPreTiramisu {
private fun reportAgain() {
// Report location again if it's recent enough
lastReportedLocation?.let {
if (it.elapsedMillis + max(currentRequest?.interval ?: 0, MIN_INTERVAL_MILLIS) > SystemClock.elapsedRealtime()) {
if (it.elapsedMillis + max(currentRequest?.interval ?: 0, service.minIntervalMillis) > SystemClock.elapsedRealtime()) {
reportLocationToSystem(it)
}
}
}

override fun reportLocationToSystem(location: Location) {
handler.removeCallbacks(reportAgainRunnable)
location.provider = LocationManager.NETWORK_PROVIDER
location.extras?.remove(LOCATION_EXTRA_PRECISION)
location.provider = service.providerName
lastReportedLocation = location
lastReportTime = SystemClock.elapsedRealtime()
super.reportLocation(location)
val repeatInterval = max(MIN_REPORT_MILLIS, currentRequest?.interval ?: Long.MAX_VALUE)
if (repeatInterval < MIN_INTERVAL_MILLIS) {
val repeatInterval = max(service.minReportMillis, currentRequest?.interval ?: Long.MAX_VALUE)
if (repeatInterval < service.minIntervalMillis) {
handler.postDelayed(reportAgainRunnable, repeatInterval)
}
}

companion object {
private const val MIN_INTERVAL_MILLIS = 20000L
private const val MIN_REPORT_MILLIS = 1000L
private val properties = ProviderPropertiesUnbundled.create(false, false, false, false, true, true, true, Criteria.POWER_LOW, Criteria.ACCURACY_COARSE)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/*
* SPDX-FileCopyrightText: 2024 microG Project Team
* SPDX-License-Identifier: Apache-2.0
*/

package org.microg.gms.location.provider

import android.app.PendingIntent
import android.app.Service
import android.content.Intent
import android.location.Location
import android.os.Binder
import android.os.Build.VERSION.SDK_INT
import android.os.Handler
import android.os.HandlerThread
import android.os.IBinder
import android.os.Process
import android.util.Log
import com.android.location.provider.ProviderPropertiesUnbundled
import com.android.location.provider.ProviderRequestUnbundled
import java.io.FileDescriptor
import java.io.PrintWriter

abstract class IntentLocationProviderService : Service() {
private lateinit var handlerThread: HandlerThread
private lateinit var handler: Handler
private var bound: Boolean = false
private var provider: GenericLocationProvider? = null

override fun onCreate() {
super.onCreate()
handlerThread = HandlerThread(this.javaClass.simpleName)
handlerThread.start()
handler = Handler(handlerThread.looper)
}

abstract fun requestIntentUpdated(currentRequest: ProviderRequestUnbundled?, pendingIntent: PendingIntent?)

abstract fun stopIntentUpdated(pendingIntent: PendingIntent?)

abstract fun extractLocation(intent: Intent): Location?

override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
if (Binder.getCallingUid() == Process.myUid() && intent?.action == ACTION_REPORT_LOCATION) {
handler.post {
val location = extractLocation(intent)
if (location != null) {
provider?.reportLocationToSystem(location)
}
}
}
return START_NOT_STICKY
}

override fun onBind(intent: Intent?): IBinder? {
bound = true
if (provider == null) {
provider = when {
// TODO: Migrate to Tiramisu provider. Not yet required thanks to backwards compat
// SDK_INT >= 33 ->
SDK_INT >= 31 ->
IntentLocationProviderPreTiramisu(this, properties)

else ->
@Suppress("DEPRECATION")
(IntentLocationProviderPreTiramisu(this, properties, Unit))
}
provider?.enable()
}
return provider?.getBinder()
}

override fun dump(fd: FileDescriptor, writer: PrintWriter, args: Array<out String>) {
writer.println("Bound: $bound")
provider?.dump(writer)
}

override fun onDestroy() {
if (SDK_INT >= 18) handlerThread.looper.quitSafely()
else handlerThread.looper.quit()
provider?.disable()
provider = null
bound = false
super.onDestroy()
}

abstract val minIntervalMillis: Long
abstract val minReportMillis: Long
abstract val properties: ProviderPropertiesUnbundled
abstract val providerName: String
}
Loading

0 comments on commit 511afe8

Please sign in to comment.