Handle unrecoverable location errors and stop sharing

This commit is contained in:
ganfra
2026-06-02 12:23:14 +02:00
parent 1713555a67
commit 1a40e460ae
5 changed files with 36 additions and 12 deletions
@@ -58,7 +58,6 @@ class PlatformLocationProvider(
) { ) {
throw PermissionException() throw PermissionException()
} }
val locationManager = context.getSystemService(LocationManager::class.java) val locationManager = context.getSystemService(LocationManager::class.java)
val provider = PROVIDERS_BY_PRIORITY.firstOrNull { LocationManagerCompat.hasProvider(locationManager, it) } val provider = PROVIDERS_BY_PRIORITY.firstOrNull { LocationManagerCompat.hasProvider(locationManager, it) }
val locationFlow = if (provider != null) { val locationFlow = if (provider != null) {
@@ -133,6 +133,11 @@ class DefaultActiveLiveLocationShareManager(
} }
} }
override suspend fun onUnrecoverableError() {
Timber.d("ActiveLiveLocationShareManager unrecoverable error, stopping all shares")
localSharingRoomIds.value.toList().forEach { stopShare(it) }
}
override suspend fun onLocationUpdate(location: Location) { override suspend fun onLocationUpdate(location: Location) {
val activeSharesCount = localSharingRoomIds.value.size val activeSharesCount = localSharingRoomIds.value.size
Timber.d("ActiveLiveLocationShareManager received location update for $activeSharesCount active share(s)") Timber.d("ActiveLiveLocationShareManager received location update for $activeSharesCount active share(s)")
@@ -9,6 +9,7 @@ package io.element.android.features.location.impl.live.service
import io.element.android.features.location.api.Location import io.element.android.features.location.api.Location
fun interface LiveLocationReceiver { interface LiveLocationReceiver {
suspend fun onLocationUpdate(location: Location) suspend fun onLocationUpdate(location: Location)
suspend fun onUnrecoverableError() {}
} }
@@ -77,6 +77,17 @@ class LiveLocationSharingCoordinator internal constructor(
} }
} }
suspend fun dispatchUnrecoverableError() {
Timber.d("LiveLocationSharingCoordinator dispatching unrecoverable error")
receivers.forEach { (sessionId, receiver) ->
runCatchingExceptions {
receiver.onUnrecoverableError()
}.onFailure {
Timber.e(it, "Failed to dispatch unrecoverable error for session $sessionId")
}
}
}
suspend fun dispatch(location: Location) { suspend fun dispatch(location: Location) {
val currentTimeMillis = nowMillis() val currentTimeMillis = nowMillis()
val millisSincePrevious = currentTimeMillis - lastDispatchMillis.load() val millisSincePrevious = currentTimeMillis - lastDispatchMillis.load()
@@ -30,12 +30,15 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.cancel import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import org.maplibre.compose.location.DesiredAccuracy import org.maplibre.compose.location.DesiredAccuracy
import org.maplibre.compose.location.PermissionException
import org.maplibre.spatialk.units.extensions.inMeters import org.maplibre.spatialk.units.extensions.inMeters
import org.maplibre.spatialk.units.extensions.meters import org.maplibre.spatialk.units.extensions.meters
import timber.log.Timber import timber.log.Timber
@@ -48,7 +51,6 @@ class LiveLocationSharingService : Service() {
@Inject lateinit var coordinator: LiveLocationSharingCoordinator @Inject lateinit var coordinator: LiveLocationSharingCoordinator
@Inject lateinit var notificationCreator: LiveLocationSharingNotificationCreator @Inject lateinit var notificationCreator: LiveLocationSharingNotificationCreator
@Inject lateinit var appPreferencesStore: AppPreferencesStore @Inject lateinit var appPreferencesStore: AppPreferencesStore
@Inject lateinit var appForegroundStateService: AppForegroundStateService @Inject lateinit var appForegroundStateService: AppForegroundStateService
@AppCoroutineScope @AppCoroutineScope
@@ -62,8 +64,8 @@ class LiveLocationSharingService : Service() {
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
Timber.d("LiveLocationSharingService onCreate") Timber.d("LiveLocationSharingService onCreate")
bindings<LocationBindings>().inject(this)
runCatchingExceptions { runCatchingExceptions {
bindings<LocationBindings>().inject(this)
appForegroundStateService.updateIsSharingLiveLocation(true) appForegroundStateService.updateIsSharingLiveLocation(true)
coroutineScope = appCoroutineScope.childScope(Dispatchers.Default, "LiveLocationSharingService") coroutineScope = appCoroutineScope.childScope(Dispatchers.Default, "LiveLocationSharingService")
val notificationId = NotificationIdProvider.getForegroundServiceNotificationId(ForegroundServiceType.LIVE_LOCATION) val notificationId = NotificationIdProvider.getForegroundServiceNotificationId(ForegroundServiceType.LIVE_LOCATION)
@@ -81,6 +83,7 @@ class LiveLocationSharingService : Service() {
startLocationUpdatesListener() startLocationUpdatesListener()
}.onFailure { }.onFailure {
Timber.e(it, "Failed to start live location sharing service") Timber.e(it, "Failed to start live location sharing service")
appCoroutineScope.launch { coordinator.dispatchUnrecoverableError() }
stopSelf() stopSelf()
} }
} }
@@ -90,14 +93,19 @@ class LiveLocationSharingService : Service() {
Timber.d("LiveLocationSharingService listening to location updates") Timber.d("LiveLocationSharingService listening to location updates")
appPreferencesStore.getLiveLocationMinimumDistanceInMetersUpdateFlow() appPreferencesStore.getLiveLocationMinimumDistanceInMetersUpdateFlow()
.flatMapLatest { minDistanceMeters -> .flatMapLatest { minDistanceMeters ->
val locationProvider = PlatformLocationProvider( try {
context = applicationContext, PlatformLocationProvider(
updateInterval = UPDATE_INTERVAL_IN_SECOND.seconds, context = applicationContext,
minDistance = minDistanceMeters.meters, updateInterval = UPDATE_INTERVAL_IN_SECOND.seconds,
desiredAccuracy = DesiredAccuracy.Balanced, minDistance = minDistanceMeters.meters,
coroutineScope = coroutineScope desiredAccuracy = DesiredAccuracy.Balanced,
) coroutineScope = coroutineScope
locationProvider.location ).location
} catch (exception: PermissionException) {
Timber.e(exception, "Failed to create PlatformLocationProvider")
coordinator.dispatchUnrecoverableError()
emptyFlow()
}
} }
.filterNotNull() .filterNotNull()
.map { location -> .map { location ->