Skip to content

Commit 827a8b9

Browse files
committed
Location: Improve update delivery
1 parent 539dd67 commit 827a8b9

File tree

7 files changed

+116
-35
lines changed

7 files changed

+116
-35
lines changed

play-services-location/core/provider/src/main/kotlin/org/microg/gms/location/network/NetworkLocationService.kt

Lines changed: 35 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ import java.io.PrintWriter
4141
import java.lang.Math.pow
4242
import java.nio.ByteBuffer
4343
import java.util.LinkedList
44+
import kotlin.math.max
4445
import kotlin.math.min
4546

4647
class NetworkLocationService : LifecycleService(), WifiDetailsCallback, CellDetailsCallback {
@@ -271,10 +272,6 @@ class NetworkLocationService : LifecycleService(), WifiDetailsCallback, CellDeta
271272
val scanResultTimestamp = min(wifis.maxOf { it.timestamp ?: Long.MAX_VALUE }, System.currentTimeMillis())
272273
val scanResultRealtimeMillis =
273274
if (SDK_INT >= 17) SystemClock.elapsedRealtime() - (System.currentTimeMillis() - scanResultTimestamp) else scanResultTimestamp
274-
if (scanResultRealtimeMillis < lastWifiDetailsRealtimeMillis + interval / 2 && lastWifiDetailsRealtimeMillis != 0L) {
275-
Log.d(TAG, "Ignoring wifi details, similar age as last ($scanResultRealtimeMillis < $lastWifiDetailsRealtimeMillis + $interval / 2)")
276-
return
277-
}
278275
@Suppress("DEPRECATION")
279276
currentLocalMovingWifi = getSystemService<WifiManager>()?.connectionInfo
280277
?.let { wifiInfo -> wifis.filter { it.macAddress == wifiInfo.bssid && it.isMoving } }
@@ -294,6 +291,10 @@ class NetworkLocationService : LifecycleService(), WifiDetailsCallback, CellDeta
294291
}
295292

296293
private fun updateWifiLocation(requestableWifis: List<WifiDetails>, scanResultRealtimeMillis: Long = 0, scanResultTimestamp: Long = 0) {
294+
if (scanResultRealtimeMillis < lastWifiDetailsRealtimeMillis + interval / 2 && lastWifiDetailsRealtimeMillis != 0L) {
295+
Log.d(TAG, "Ignoring wifi details, similar age as last ($scanResultRealtimeMillis < $lastWifiDetailsRealtimeMillis + $interval / 2)")
296+
return
297+
}
297298
val previousLastRealtimeMillis = lastWifiDetailsRealtimeMillis
298299
if (scanResultRealtimeMillis != 0L) lastWifiDetailsRealtimeMillis = scanResultRealtimeMillis
299300
lifecycleScope.launch {
@@ -302,9 +303,9 @@ class NetworkLocationService : LifecycleService(), WifiDetailsCallback, CellDeta
302303
lastWifiDetailsRealtimeMillis = previousLastRealtimeMillis
303304
return@launch
304305
}
305-
if (scanResultTimestamp != 0L && location.time == 0L) location.time = scanResultTimestamp
306-
if (SDK_INT >= 17 && scanResultRealtimeMillis != 0L && location.elapsedRealtimeNanos == 0L) location.elapsedRealtimeNanos =
307-
scanResultRealtimeMillis * 1_000_000L
306+
if (scanResultTimestamp != 0L) location.time = max(scanResultTimestamp, location.time)
307+
if (SDK_INT >= 17 && scanResultRealtimeMillis != 0L) location.elapsedRealtimeNanos =
308+
max(location.elapsedRealtimeNanos, scanResultRealtimeMillis * 1_000_000L)
308309
synchronized(locationLock) {
309310
lastWifiLocation = location
310311
}
@@ -372,22 +373,43 @@ class NetworkLocationService : LifecycleService(), WifiDetailsCallback, CellDeta
372373
}
373374

374375
private fun sendLocationUpdate(now: Boolean = false) {
376+
fun cliffLocations(old: Location?, new: Location?): Location? {
377+
// We move from wifi towards cell with accuracy
378+
if (old == null) return new
379+
if (new == null) return old
380+
val diff = new.elapsedMillis - old.elapsedMillis
381+
if (diff < LOCATION_TIME_CLIFF_START_MS) return old
382+
if (diff > LOCATION_TIME_CLIFF_END_MS) return new
383+
val pct = (diff - LOCATION_TIME_CLIFF_START_MS).toDouble() / (LOCATION_TIME_CLIFF_END_MS - LOCATION_TIME_CLIFF_START_MS).toDouble()
384+
return Location(old).apply {
385+
provider = "cliff"
386+
latitude = old.latitude * (1.0-pct) + new.latitude * pct
387+
longitude = old.longitude * (1.0-pct) + new.longitude * pct
388+
accuracy = (old.accuracy * (1.0-pct) + new.accuracy * pct).toFloat()
389+
altitude = old.altitude * (1.0-pct) + new.altitude * pct
390+
time = (old.time.toDouble() * (1.0-pct) + new.time.toDouble() * pct).toLong()
391+
elapsedRealtimeNanos = (old.elapsedRealtimeNanos.toDouble() * (1.0-pct) + new.elapsedRealtimeNanos.toDouble() * pct).toLong()
392+
}
393+
}
375394
val location = synchronized(locationLock) {
376395
if (lastCellLocation == null && lastWifiLocation == null) return
377396
when {
378397
// Only non-null
379398
lastCellLocation == null -> lastWifiLocation
380399
lastWifiLocation == null -> lastCellLocation
381-
// Consider cliff
382-
lastCellLocation!!.elapsedMillis > lastWifiLocation!!.elapsedMillis + LOCATION_TIME_CLIFF_MS -> lastCellLocation
383-
lastWifiLocation!!.elapsedMillis > lastCellLocation!!.elapsedMillis + LOCATION_TIME_CLIFF_MS -> lastWifiLocation
400+
// Consider cliff end
401+
lastCellLocation!!.elapsedMillis > lastWifiLocation!!.elapsedMillis + LOCATION_TIME_CLIFF_END_MS -> lastCellLocation
402+
lastWifiLocation!!.elapsedMillis > lastCellLocation!!.elapsedMillis + LOCATION_TIME_CLIFF_START_MS -> lastWifiLocation
384403
// Wifi out of cell range with higher precision
385404
lastCellLocation!!.precision > lastWifiLocation!!.precision && lastWifiLocation!!.distanceTo(lastCellLocation!!) > 2 * lastCellLocation!!.accuracy -> lastCellLocation
405+
// Consider cliff start
406+
lastCellLocation!!.elapsedMillis > lastWifiLocation!!.elapsedMillis + LOCATION_TIME_CLIFF_START_MS -> cliffLocations(lastWifiLocation, lastCellLocation)
386407
else -> lastWifiLocation
387408
}
388409
} ?: return
389410
if (location == lastLocation) return
390-
if (lastLocation == lastWifiLocation && location == lastCellLocation && !now) {
411+
if (lastLocation == lastWifiLocation && lastLocation.let { it != null && location.accuracy > it.accuracy } && !now) {
412+
Log.d(TAG, "Debounce inaccurate location update")
391413
handler.postDelayed({
392414
sendLocationUpdate(true)
393415
}, DEBOUNCE_DELAY_MS)
@@ -456,7 +478,8 @@ class NetworkLocationService : LifecycleService(), WifiDetailsCallback, CellDeta
456478
const val GPS_BUFFER_SIZE = 60
457479
const val GPS_PASSIVE_INTERVAL = 1000L
458480
const val GPS_PASSIVE_MIN_ACCURACY = 25f
459-
const val LOCATION_TIME_CLIFF_MS = 30000L
481+
const val LOCATION_TIME_CLIFF_START_MS = 30000L
482+
const val LOCATION_TIME_CLIFF_END_MS = 60000L
460483
const val DEBOUNCE_DELAY_MS = 5000L
461484
const val MAX_WIFI_SCAN_CACHE_AGE = 1000L * 60 * 60 * 24 // 1 day
462485
const val MAX_LOCAL_WIFI_AGE_NS = 60_000_000_000L // 1 minute

play-services-location/core/provider/src/main/kotlin/org/microg/gms/location/network/ichnaea/GeolocateResponse.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66
package org.microg.gms.location.network.ichnaea
77

88
data class GeolocateResponse(
9-
val location: ResponseLocation?,
10-
val accuracy: Double?,
11-
val fallback: String?,
12-
val error: ResponseError?
9+
val location: ResponseLocation? = null,
10+
val accuracy: Double? = null,
11+
val fallback: String? = null,
12+
val error: ResponseError? = null
1313
)

play-services-location/core/provider/src/main/kotlin/org/microg/gms/location/network/ichnaea/IchnaeaServiceClient.kt

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,18 @@ import android.net.Uri
1111
import android.os.Bundle
1212
import android.util.Log
1313
import com.android.volley.Request.Method
14+
import com.android.volley.VolleyError
1415
import com.android.volley.toolbox.JsonObjectRequest
1516
import com.android.volley.toolbox.Volley
17+
import org.json.JSONObject
1618
import org.microg.gms.location.LocationSettings
1719
import org.microg.gms.location.network.cell.CellDetails
1820
import org.microg.gms.location.network.precision
1921
import org.microg.gms.location.network.wifi.WifiDetails
2022
import org.microg.gms.location.network.wifi.isMoving
2123
import org.microg.gms.location.provider.BuildConfig
2224
import org.microg.gms.utils.singleInstanceOf
25+
import kotlin.coroutines.Continuation
2326
import kotlin.coroutines.resume
2427
import kotlin.coroutines.resumeWithException
2528
import kotlin.coroutines.suspendCoroutine
@@ -75,12 +78,32 @@ class IchnaeaServiceClient(private val context: Context) {
7578
}
7679
}
7780

81+
private fun continueError(continuation: Continuation<GeolocateResponse>, error: VolleyError) {
82+
try {
83+
val response = JSONObject(error.networkResponse.data.decodeToString()).toGeolocateResponse()
84+
if (response.error != null) {
85+
continuation.resume(response)
86+
return
87+
} else if (response.location?.lat != null){
88+
Log.w(TAG, "Received location in response with error code")
89+
} else {
90+
Log.w(TAG, "Received valid json without error in response with error code")
91+
}
92+
} catch (_: Exception) {
93+
}
94+
if (error.networkResponse != null) {
95+
continuation.resume(GeolocateResponse(error = ResponseError(error.networkResponse.statusCode, error.message)))
96+
return
97+
}
98+
continuation.resumeWithException(error)
99+
}
100+
78101
private suspend fun rawGeoLocate(request: GeolocateRequest): GeolocateResponse = suspendCoroutine { continuation ->
79102
val url = Uri.parse(settings.ichneaeEndpoint).buildUpon().appendPath("v1").appendPath("geolocate").build().toString()
80103
queue.add(object : JsonObjectRequest(Method.POST, url, request.toJson(), {
81104
continuation.resume(it.toGeolocateResponse())
82105
}, {
83-
continuation.resumeWithException(it)
106+
continueError(continuation, it)
84107
}) {
85108
override fun getHeaders(): Map<String, String> = getRequestHeaders()
86109
})

play-services-location/core/provider/src/main/kotlin/org/microg/gms/location/network/ichnaea/ResponseError.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,6 @@
66
package org.microg.gms.location.network.ichnaea
77

88
data class ResponseError(
9-
val code: Int,
10-
val message: String
9+
val code: Int? = null,
10+
val message: String? = null
1111
)

play-services-location/core/provider/src/main/kotlin/org/microg/gms/location/network/wifi/MovingWifiHelper.kt

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -299,13 +299,9 @@ class MovingWifiHelper(private val context: Context) {
299299
"CDWiFi", "MAVSTART-WIFI" -> parsePassengera(location, data)
300300
"AegeanWiFi" -> parseDisplayUgo(location, data)
301301
"Cathay Pacific", "Telekom_FlyNet", "KrisWorld", "SWISS Connect", "Edelweiss Entertainment" -> parsePanasonic(location, data)
302-
"FlyNet" -> parseBoardConnect(location, data)
302+
"FlyNet", "Austrian FlyNet" -> parseBoardConnect(location, data)
303303
"ACWiFi" -> parseAirCanada(location, data)
304-
"OUIFI" -> parseSncf(location, data)
305-
"_SNCF_WIFI_INOUI" -> parseSncf(location, data)
306-
"_SNCF_WIFI_INTERCITES" -> parseSncf(location, data)
307-
"_WIFI_LYRIA" -> parseSncf(location, data)
308-
"NormandieTrainConnecte" -> parseSncf(location, data)
304+
"OUIFI", "_SNCF_WIFI_INOUI", "_SNCF_WIFI_INTERCITES", "_WIFI_LYRIA", "NormandieTrainConnecte" -> parseSncf(location, data)
309305
"agilis-Wifi" -> parseHotsplots(location, data)
310306
else -> throw UnsupportedOperationException()
311307
}
@@ -337,6 +333,7 @@ class MovingWifiHelper(private val context: Context) {
337333
"_WIFI_LYRIA" to "https://wifi.tgv-lyria.com/router/api/train/gps",
338334
"NormandieTrainConnecte" to "https://wifi.normandie.fr/router/api/train/gps",
339335
"agilis-Wifi" to "http://hsp.hotsplots.net/status.json",
336+
"Austrian FlyNet" to "https://www.austrian-flynet.com/map/api/flightData",
340337
)
341338
}
342339
}

play-services-location/core/provider/src/main/kotlin/org/microg/gms/location/provider/NetworkLocationProviderPreTiramisu.kt

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,15 @@ import android.content.Context
1111
import android.content.Intent
1212
import android.location.Criteria
1313
import android.location.Location
14+
import android.location.LocationManager
1415
import android.os.Build.VERSION.SDK_INT
16+
import android.os.Handler
17+
import android.os.Looper
18+
import android.os.SystemClock
1519
import android.os.WorkSource
1620
import androidx.annotation.RequiresApi
1721
import androidx.core.app.PendingIntentCompat
22+
import androidx.core.content.getSystemService
1823
import com.android.location.provider.ProviderPropertiesUnbundled
1924
import com.android.location.provider.ProviderRequestUnbundled
2025
import org.microg.gms.location.*
@@ -40,6 +45,8 @@ class NetworkLocationProviderPreTiramisu : AbstractLocationProviderPreTiramisu {
4045
private var currentRequest: ProviderRequestUnbundled? = null
4146
private var pendingIntent: PendingIntent? = null
4247
private var lastReportedLocation: Location? = null
48+
private val handler = Handler(Looper.getMainLooper())
49+
private val reportAgainRunnable = Runnable { reportAgain() }
4350

4451
private fun updateRequest() {
4552
if (enabled) {
@@ -65,6 +72,7 @@ class NetworkLocationProviderPreTiramisu : AbstractLocationProviderPreTiramisu {
6572
intent.putExtra(EXTRA_BYPASS, currentRequest?.isLocationSettingsIgnored ?: false)
6673
}
6774
context.startService(intent)
75+
reportAgain()
6876
}
6977
}
7078

@@ -93,6 +101,13 @@ class NetworkLocationProviderPreTiramisu : AbstractLocationProviderPreTiramisu {
93101
SDK_INT >= 30 -> isAllowed = true
94102
SDK_INT >= 29 -> isEnabled = true
95103
}
104+
try {
105+
if (lastReportedLocation == null) {
106+
lastReportedLocation = context.getSystemService<LocationManager>()?.getLastKnownLocation(LocationManager.NETWORK_PROVIDER)
107+
}
108+
} catch (_: SecurityException) {
109+
} catch (_: Exception) {
110+
}
96111
}
97112
}
98113

@@ -107,18 +122,35 @@ class NetworkLocationProviderPreTiramisu : AbstractLocationProviderPreTiramisu {
107122
pendingIntent = null
108123
currentRequest = null
109124
enabled = false
125+
handler.removeCallbacks(reportAgainRunnable)
126+
}
127+
}
128+
129+
private fun reportAgain() {
130+
// Report location again if it's recent enough
131+
lastReportedLocation?.let {
132+
if (it.elapsedMillis + MIN_INTERVAL_MILLIS < SystemClock.elapsedRealtime() ||
133+
it.elapsedMillis + (currentRequest?.interval ?: 0) < SystemClock.elapsedRealtime()) {
134+
reportLocationToSystem(it)
135+
}
110136
}
111137
}
112138

113139
override fun reportLocationToSystem(location: Location) {
114-
location.provider = "network"
140+
handler.removeCallbacks(reportAgainRunnable)
141+
location.provider = LocationManager.NETWORK_PROVIDER
115142
location.extras?.remove(LOCATION_EXTRA_PRECISION)
116143
lastReportedLocation = location
117144
super.reportLocation(location)
145+
val repeatInterval = max(MIN_REPORT_MILLIS, currentRequest?.interval ?: Long.MAX_VALUE)
146+
if (repeatInterval < MIN_INTERVAL_MILLIS) {
147+
handler.postDelayed(reportAgainRunnable, repeatInterval)
148+
}
118149
}
119150

120151
companion object {
121152
private const val MIN_INTERVAL_MILLIS = 20000L
153+
private const val MIN_REPORT_MILLIS = 1000L
122154
private val properties = ProviderPropertiesUnbundled.create(false, false, false, false, true, true, true, Criteria.POWER_LOW, Criteria.ACCURACY_COARSE)
123155
}
124156
}

play-services-location/core/src/main/kotlin/org/microg/gms/location/manager/LastLocationCapsule.kt

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ package org.microg.gms.location.manager
88
import android.content.Context
99
import android.location.Location
1010
import android.location.LocationManager
11-
import android.os.Build.VERSION.SDK_INT
1211
import android.os.SystemClock
1312
import android.util.Log
1413
import androidx.core.content.getSystemService
@@ -24,6 +23,7 @@ import org.microg.safeparcel.AutoSafeParcelable
2423
import java.io.File
2524
import java.lang.Long.max
2625
import java.util.concurrent.TimeUnit
26+
import kotlin.math.min
2727

2828
class LastLocationCapsule(private val context: Context) {
2929
private var lastFineLocation: Location? = null
@@ -44,15 +44,13 @@ class LastLocationCapsule(private val context: Context) {
4444
else -> return null
4545
} ?: return null
4646
val cliff = if (effectiveGranularity == GRANULARITY_COARSE) max(maxUpdateAgeMillis, TIME_COARSE_CLIFF) else maxUpdateAgeMillis
47-
val elapsedRealtimeDiff = SystemClock.elapsedRealtime() - LocationCompat.getElapsedRealtimeMillis(location)
47+
val elapsedRealtimeDiff = SystemClock.elapsedRealtime() - location.elapsedMillis
4848
if (elapsedRealtimeDiff > cliff) return null
4949
if (elapsedRealtimeDiff <= maxUpdateAgeMillis) return location
5050
// Location is too old according to maxUpdateAgeMillis, but still in scope due to time coarsing. Adjust time
5151
val locationUpdated = Location(location)
5252
val timeAdjustment = elapsedRealtimeDiff - maxUpdateAgeMillis
53-
if (SDK_INT >= 17) {
54-
locationUpdated.elapsedRealtimeNanos = location.elapsedRealtimeNanos + TimeUnit.MILLISECONDS.toNanos(timeAdjustment)
55-
}
53+
locationUpdated.elapsedRealtimeNanos = location.elapsedRealtimeNanos + TimeUnit.MILLISECONDS.toNanos(timeAdjustment)
5654
locationUpdated.time = location.time + timeAdjustment
5755
return locationUpdated
5856
}
@@ -66,6 +64,8 @@ class LastLocationCapsule(private val context: Context) {
6664
}
6765

6866
fun updateCoarseLocation(location: Location) {
67+
location.elapsedRealtimeNanos = min(location.elapsedRealtimeNanos, SystemClock.elapsedRealtimeNanos())
68+
location.time = min(location.time, System.currentTimeMillis())
6969
if (lastCoarseLocation != null && lastCoarseLocation!!.elapsedMillis + EXTENSION_CLIFF > location.elapsedMillis) {
7070
if (!location.hasSpeed()) {
7171
location.speed = lastCoarseLocation!!.distanceTo(location) / ((location.elapsedMillis - lastCoarseLocation!!.elapsedMillis) / 1000)
@@ -81,6 +81,8 @@ class LastLocationCapsule(private val context: Context) {
8181
}
8282

8383
fun updateFineLocation(location: Location) {
84+
location.elapsedRealtimeNanos = min(location.elapsedRealtimeNanos, SystemClock.elapsedRealtimeNanos())
85+
location.time = min(location.time, System.currentTimeMillis())
8486
lastFineLocation = newest(lastFineLocation, location)
8587
lastFineLocationTimeCoarsed = newest(lastFineLocationTimeCoarsed, location, TIME_COARSE_CLIFF)
8688
updateCoarseLocation(location)
@@ -89,15 +91,19 @@ class LastLocationCapsule(private val context: Context) {
8991
private fun newest(oldLocation: Location?, newLocation: Location, cliff: Long = 0): Location {
9092
if (oldLocation == null) return newLocation
9193
if (LocationCompat.isMock(oldLocation) && !LocationCompat.isMock(newLocation)) return newLocation
92-
if (LocationCompat.getElapsedRealtimeNanos(newLocation) >= LocationCompat.getElapsedRealtimeNanos(oldLocation) + TimeUnit.MILLISECONDS.toNanos(cliff)) return newLocation
94+
oldLocation.elapsedRealtimeNanos = min(oldLocation.elapsedRealtimeNanos, SystemClock.elapsedRealtimeNanos())
95+
oldLocation.time = min(oldLocation.time, System.currentTimeMillis())
96+
if (newLocation.elapsedRealtimeNanos >= oldLocation.elapsedRealtimeNanos + TimeUnit.MILLISECONDS.toNanos(cliff)) return newLocation
9397
return oldLocation
9498
}
9599

96100
fun start() {
97101
fun Location.adjustRealtime() = apply {
98-
if (SDK_INT >= 17) {
99-
elapsedRealtimeNanos = SystemClock.elapsedRealtimeNanos() - TimeUnit.MILLISECONDS.toNanos((System.currentTimeMillis() - time))
100-
}
102+
time = min(time, System.currentTimeMillis())
103+
elapsedRealtimeNanos = min(
104+
SystemClock.elapsedRealtimeNanos() - TimeUnit.MILLISECONDS.toNanos((System.currentTimeMillis() - time)),
105+
SystemClock.elapsedRealtimeNanos()
106+
)
101107
}
102108
try {
103109
if (file.exists()) {

0 commit comments

Comments
 (0)