Introduce PlatformLocationProvider to replace the maplibre one.
This commit is contained in:
+130
@@ -0,0 +1,130 @@
|
||||
/*
|
||||
* Copyright (c) 2026 Element Creations Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.location.impl.common
|
||||
|
||||
import android.Manifest
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.content.pm.PackageManager
|
||||
import android.location.LocationManager
|
||||
import android.os.Handler
|
||||
import android.os.HandlerThread
|
||||
import androidx.annotation.RequiresPermission
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.core.location.LocationListenerCompat
|
||||
import androidx.core.location.LocationManagerCompat
|
||||
import androidx.core.location.LocationRequestCompat
|
||||
import androidx.core.os.ExecutorCompat
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.channels.awaitClose
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.callbackFlow
|
||||
import kotlinx.coroutines.flow.emptyFlow
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import org.maplibre.compose.location.DesiredAccuracy
|
||||
import org.maplibre.compose.location.Location
|
||||
import org.maplibre.compose.location.LocationProvider
|
||||
import org.maplibre.compose.location.PermissionException
|
||||
import org.maplibre.compose.location.asMapLibreLocation
|
||||
import org.maplibre.spatialk.units.Length
|
||||
import org.maplibre.spatialk.units.extensions.inMeters
|
||||
import kotlin.time.Duration
|
||||
|
||||
@SuppressLint("InlinedApi")
|
||||
class PlatformLocationProvider(
|
||||
context: Context,
|
||||
private val updateInterval: Duration,
|
||||
private val minDistance: Length,
|
||||
private val desiredAccuracy: DesiredAccuracy = DesiredAccuracy.High,
|
||||
coroutineScope: CoroutineScope,
|
||||
sharingStarted: SharingStarted = SharingStarted.WhileSubscribed(stopTimeoutMillis = 1000),
|
||||
) : LocationProvider {
|
||||
override val location: StateFlow<Location?>
|
||||
|
||||
init {
|
||||
if (!handlerThread.isAlive) handlerThread.start()
|
||||
if (
|
||||
context.checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED &&
|
||||
context.checkSelfPermission(Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED
|
||||
) {
|
||||
throw PermissionException()
|
||||
}
|
||||
|
||||
val locationManager = context.getSystemService(LocationManager::class.java)
|
||||
val provider = PROVIDERS_BY_PRIORITY.firstOrNull { LocationManagerCompat.hasProvider(locationManager, it) }
|
||||
val locationFlow = if (provider != null) {
|
||||
createProviderFlow(locationManager, provider)
|
||||
} else {
|
||||
emptyFlow()
|
||||
}
|
||||
location = locationFlow.stateIn(coroutineScope, sharingStarted, null)
|
||||
}
|
||||
|
||||
@RequiresPermission(
|
||||
anyOf = [Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION]
|
||||
)
|
||||
private fun createProviderFlow(locationManager: LocationManager, provider: String) = callbackFlow {
|
||||
send(locationManager.getLastKnownLocation(provider)?.asMapLibreLocation())
|
||||
val listener = LocationListenerCompat { trySend(it.asMapLibreLocation()) }
|
||||
val request = LocationRequestCompat.Builder(updateInterval.inWholeMilliseconds)
|
||||
.setQuality(desiredAccuracy.toLocationRequestQuality())
|
||||
.setMinUpdateDistanceMeters(minDistance.inMeters.toFloat())
|
||||
.build()
|
||||
LocationManagerCompat.requestLocationUpdates(
|
||||
locationManager,
|
||||
provider,
|
||||
request,
|
||||
ExecutorCompat.create(Handler(handlerThread.looper)),
|
||||
listener,
|
||||
)
|
||||
awaitClose { LocationManagerCompat.removeUpdates(locationManager, listener) }
|
||||
}
|
||||
|
||||
private companion object {
|
||||
private val PROVIDERS_BY_PRIORITY = listOf(
|
||||
LocationManager.FUSED_PROVIDER,
|
||||
LocationManager.GPS_PROVIDER,
|
||||
LocationManager.NETWORK_PROVIDER,
|
||||
)
|
||||
private val handlerThread by lazy { HandlerThread("PlatformLocationProvider") }
|
||||
}
|
||||
}
|
||||
|
||||
private fun DesiredAccuracy.toLocationRequestQuality(): Int = when (this) {
|
||||
DesiredAccuracy.Highest, DesiredAccuracy.High -> LocationRequestCompat.QUALITY_HIGH_ACCURACY
|
||||
DesiredAccuracy.Balanced -> LocationRequestCompat.QUALITY_BALANCED_POWER_ACCURACY
|
||||
DesiredAccuracy.Low, DesiredAccuracy.Lowest -> LocationRequestCompat.QUALITY_LOW_POWER
|
||||
}
|
||||
|
||||
@Composable
|
||||
@RequiresPermission(
|
||||
anyOf = [Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION]
|
||||
)
|
||||
fun rememberPlatformLocationProvider(
|
||||
updateInterval: Duration,
|
||||
minDistance: Length,
|
||||
desiredAccuracy: DesiredAccuracy = DesiredAccuracy.High,
|
||||
context: Context = LocalContext.current,
|
||||
coroutineScope: CoroutineScope = rememberCoroutineScope(),
|
||||
sharingStarted: SharingStarted = SharingStarted.WhileSubscribed(stopTimeoutMillis = 1000),
|
||||
): PlatformLocationProvider {
|
||||
return remember(context, updateInterval, minDistance, desiredAccuracy, coroutineScope, sharingStarted) {
|
||||
PlatformLocationProvider(
|
||||
context = context,
|
||||
updateInterval = updateInterval,
|
||||
minDistance = minDistance,
|
||||
desiredAccuracy = desiredAccuracy,
|
||||
coroutineScope = coroutineScope,
|
||||
sharingStarted = sharingStarted,
|
||||
)
|
||||
}
|
||||
}
|
||||
+1
-2
@@ -16,7 +16,6 @@ import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.platform.LocalInspectionMode
|
||||
import org.maplibre.compose.location.DesiredAccuracy
|
||||
import org.maplibre.compose.location.Location
|
||||
import org.maplibre.compose.location.rememberAndroidLocationProvider
|
||||
import org.maplibre.compose.location.rememberNullLocationProvider
|
||||
import org.maplibre.spatialk.units.extensions.meters
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
@@ -32,7 +31,7 @@ fun rememberUserLocationState(hasLocationPermission: Boolean): UserLocationState
|
||||
val locationProvider = if (isPreview || !hasLocationPermission) {
|
||||
rememberNullLocationProvider()
|
||||
} else {
|
||||
rememberAndroidLocationProvider(
|
||||
rememberPlatformLocationProvider(
|
||||
updateInterval = 5.seconds,
|
||||
desiredAccuracy = DesiredAccuracy.High,
|
||||
minDistance = 5.meters,
|
||||
|
||||
+2
-2
@@ -14,6 +14,7 @@ import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION
|
||||
import android.os.IBinder
|
||||
import androidx.core.app.ServiceCompat
|
||||
import dev.zacsweers.metro.Inject
|
||||
import io.element.android.features.location.impl.common.PlatformLocationProvider
|
||||
import io.element.android.features.location.impl.di.LocationBindings
|
||||
import io.element.android.features.location.impl.live.notification.LiveLocationSharingNotificationCreator
|
||||
import io.element.android.libraries.architecture.bindings
|
||||
@@ -34,7 +35,6 @@ import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import org.maplibre.compose.location.AndroidLocationProvider
|
||||
import org.maplibre.compose.location.DesiredAccuracy
|
||||
import org.maplibre.spatialk.units.extensions.inMeters
|
||||
import org.maplibre.spatialk.units.extensions.meters
|
||||
@@ -90,7 +90,7 @@ class LiveLocationSharingService : Service() {
|
||||
Timber.d("LiveLocationSharingService listening to location updates")
|
||||
appPreferencesStore.getLiveLocationMinimumDistanceInMetersUpdateFlow()
|
||||
.flatMapLatest { minDistanceMeters ->
|
||||
val locationProvider = AndroidLocationProvider(
|
||||
val locationProvider = PlatformLocationProvider(
|
||||
context = applicationContext,
|
||||
updateInterval = UPDATE_INTERVAL_IN_SECOND.seconds,
|
||||
minDistance = minDistanceMeters.meters,
|
||||
|
||||
Reference in New Issue
Block a user