DevicePoseProvider: be picky about locations (#1344)

* Only take updated location when it is of better quality

* Rename to AndroidLocation for clarification

* be pedantic about licenses
This commit is contained in:
shtrophic 2025-06-02 20:33:30 +02:00 committed by GitHub
parent 0be42610c6
commit 4bb092d98b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 65 additions and 17 deletions

View File

@ -18,6 +18,8 @@ import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.channelFlow
import de.mm20.launcher2.ktx.foldOrNull
import de.mm20.launcher2.ktx.isBetterThan
import kotlinx.coroutines.flow.combine
class DevicePoseProvider internal constructor(
@ -37,10 +39,16 @@ class DevicePoseProvider internal constructor(
}
fun getLocation(minTimeMs: Long = 1000, minDistanceM: Float = 1f) = channelFlow {
fun updateLocation(update: Location?) {
if (update == null) return
if (!update.isBetterThan(lastLocation)) return
lastLocation = update
updateDeclination(update)
trySend(update)
}
val locationCallback = LocationListenerCompat {
lastLocation = it
updateDeclination(it)
trySend(it)
updateLocation(it)
}
context.getSystemService<LocationManager>()
@ -50,20 +58,14 @@ class DevicePoseProvider internal constructor(
val hasCoarseAccess =
context.checkPermission(Manifest.permission.ACCESS_COARSE_LOCATION)
val location =
(if (hasFineAccess) this@runCatching.getLastKnownLocation(LocationManager.GPS_PROVIDER) else null)
?: if (hasCoarseAccess) this@runCatching.getLastKnownLocation(
LocationManager.NETWORK_PROVIDER
) else null
val previousLocation =
hasFineAccess.foldOrNull { getLastKnownLocation(LocationManager.GPS_PROVIDER) } ?:
hasCoarseAccess.foldOrNull { getLastKnownLocation(LocationManager.NETWORK_PROVIDER) }
if (location != null) {
lastLocation = location
updateDeclination(location)
trySend(location)
}
updateLocation(previousLocation)
if (hasFineAccess) {
this@runCatching.requestLocationUpdates(
requestLocationUpdates(
LocationManager.GPS_PROVIDER,
minTimeMs,
minDistanceM,
@ -71,7 +73,7 @@ class DevicePoseProvider internal constructor(
)
}
if (hasCoarseAccess) {
this@runCatching.requestLocationUpdates(
requestLocationUpdates(
LocationManager.NETWORK_PROVIDER,
minTimeMs,
minDistanceM,

View File

@ -0,0 +1,37 @@
package de.mm20.launcher2.ktx
import kotlin.time.Duration.Companion.minutes
import kotlin.time.Duration.Companion.nanoseconds
import android.location.Location as AndroidLocation
/* https://github.com/streetcomplete/StreetComplete/blob/master/app/src/main/java/de/westnordost/streetcomplete/util/location/LocationUtils.kt
* GPL-3.0-or-later
*/
fun AndroidLocation.isBetterThan(previous: AndroidLocation?): Boolean {
if (longitude.isNaN() || latitude.isNaN()) return false
if (previous == null) return true
val locationTimeDiff = elapsedRealtimeNanos.nanoseconds - previous.elapsedRealtimeNanos.nanoseconds
val isMuchNewer = locationTimeDiff > 2.minutes
val isMuchOlder = locationTimeDiff < (-2).minutes
val isNewer = locationTimeDiff.isPositive()
val accuracyDelta = accuracy - previous.accuracy
val isLessAccurate = accuracyDelta > 0f
val isMoreAccurate = accuracyDelta < 0f
val isMuchLessAccurate = accuracyDelta > 200f
val isFromSameProvider = provider == previous.provider
return when {
// the user has likely moved
isMuchNewer -> true
// If the new location is more than two minutes older, it must be worse
isMuchOlder -> false
isMoreAccurate -> true
isNewer && !isLessAccurate -> true
isNewer && !isMuchLessAccurate && isFromSameProvider -> true
else -> false
}
}

View File

@ -1,5 +1,14 @@
package de.mm20.launcher2.ktx
inline fun Boolean.toInt(): Int {
fun Boolean.toInt(): Int {
return if (this) 1 else 0
}
fun <T> Boolean.fold(
whenTrue: () -> T,
otherwise: () -> T
): T = if (this) whenTrue() else otherwise()
fun <T> Boolean.foldOrNull(
whenTrue: () -> T
): T? = fold(whenTrue) { null }