From 1a40e460aea4557659470cf97a3eb55cf953c952 Mon Sep 17 00:00:00 2001 From: ganfra Date: Tue, 2 Jun 2026 12:23:14 +0200 Subject: [PATCH] Handle unrecoverable location errors and stop sharing --- .../impl/common/PlatformLocationProvider.kt | 1 - .../DefaultActiveLiveLocationShareManager.kt | 5 ++++ .../impl/live/service/LiveLocationReceiver.kt | 3 +- .../service/LiveLocationSharingCoordinator.kt | 11 ++++++++ .../service/LiveLocationSharingService.kt | 28 ++++++++++++------- 5 files changed, 36 insertions(+), 12 deletions(-) diff --git a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/common/PlatformLocationProvider.kt b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/common/PlatformLocationProvider.kt index dbc4ac9040..7988c73e68 100644 --- a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/common/PlatformLocationProvider.kt +++ b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/common/PlatformLocationProvider.kt @@ -58,7 +58,6 @@ class PlatformLocationProvider( ) { throw PermissionException() } - val locationManager = context.getSystemService(LocationManager::class.java) val provider = PROVIDERS_BY_PRIORITY.firstOrNull { LocationManagerCompat.hasProvider(locationManager, it) } val locationFlow = if (provider != null) { diff --git a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/live/DefaultActiveLiveLocationShareManager.kt b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/live/DefaultActiveLiveLocationShareManager.kt index fd16bea515..c24d3f9cc2 100644 --- a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/live/DefaultActiveLiveLocationShareManager.kt +++ b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/live/DefaultActiveLiveLocationShareManager.kt @@ -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) { val activeSharesCount = localSharingRoomIds.value.size Timber.d("ActiveLiveLocationShareManager received location update for $activeSharesCount active share(s)") diff --git a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/live/service/LiveLocationReceiver.kt b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/live/service/LiveLocationReceiver.kt index adba75730c..90311e7d4a 100644 --- a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/live/service/LiveLocationReceiver.kt +++ b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/live/service/LiveLocationReceiver.kt @@ -9,6 +9,7 @@ package io.element.android.features.location.impl.live.service import io.element.android.features.location.api.Location -fun interface LiveLocationReceiver { +interface LiveLocationReceiver { suspend fun onLocationUpdate(location: Location) + suspend fun onUnrecoverableError() {} } diff --git a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/live/service/LiveLocationSharingCoordinator.kt b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/live/service/LiveLocationSharingCoordinator.kt index e39acb14e8..63618baa3c 100644 --- a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/live/service/LiveLocationSharingCoordinator.kt +++ b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/live/service/LiveLocationSharingCoordinator.kt @@ -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) { val currentTimeMillis = nowMillis() val millisSincePrevious = currentTimeMillis - lastDispatchMillis.load() diff --git a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/live/service/LiveLocationSharingService.kt b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/live/service/LiveLocationSharingService.kt index b5b8342b96..d682ba0df6 100644 --- a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/live/service/LiveLocationSharingService.kt +++ b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/live/service/LiveLocationSharingService.kt @@ -30,12 +30,15 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.cancel +import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.launch 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.meters import timber.log.Timber @@ -48,7 +51,6 @@ class LiveLocationSharingService : Service() { @Inject lateinit var coordinator: LiveLocationSharingCoordinator @Inject lateinit var notificationCreator: LiveLocationSharingNotificationCreator @Inject lateinit var appPreferencesStore: AppPreferencesStore - @Inject lateinit var appForegroundStateService: AppForegroundStateService @AppCoroutineScope @@ -62,8 +64,8 @@ class LiveLocationSharingService : Service() { override fun onCreate() { super.onCreate() Timber.d("LiveLocationSharingService onCreate") + bindings().inject(this) runCatchingExceptions { - bindings().inject(this) appForegroundStateService.updateIsSharingLiveLocation(true) coroutineScope = appCoroutineScope.childScope(Dispatchers.Default, "LiveLocationSharingService") val notificationId = NotificationIdProvider.getForegroundServiceNotificationId(ForegroundServiceType.LIVE_LOCATION) @@ -81,6 +83,7 @@ class LiveLocationSharingService : Service() { startLocationUpdatesListener() }.onFailure { Timber.e(it, "Failed to start live location sharing service") + appCoroutineScope.launch { coordinator.dispatchUnrecoverableError() } stopSelf() } } @@ -90,14 +93,19 @@ class LiveLocationSharingService : Service() { Timber.d("LiveLocationSharingService listening to location updates") appPreferencesStore.getLiveLocationMinimumDistanceInMetersUpdateFlow() .flatMapLatest { minDistanceMeters -> - val locationProvider = PlatformLocationProvider( - context = applicationContext, - updateInterval = UPDATE_INTERVAL_IN_SECOND.seconds, - minDistance = minDistanceMeters.meters, - desiredAccuracy = DesiredAccuracy.Balanced, - coroutineScope = coroutineScope - ) - locationProvider.location + try { + PlatformLocationProvider( + context = applicationContext, + updateInterval = UPDATE_INTERVAL_IN_SECOND.seconds, + minDistance = minDistanceMeters.meters, + desiredAccuracy = DesiredAccuracy.Balanced, + coroutineScope = coroutineScope + ).location + } catch (exception: PermissionException) { + Timber.e(exception, "Failed to create PlatformLocationProvider") + coordinator.dispatchUnrecoverableError() + emptyFlow() + } } .filterNotNull() .map { location ->