From f72e1ad2cceb69012554d387eb294941660e3a33 Mon Sep 17 00:00:00 2001 From: ganfra Date: Mon, 7 Sep 2026 18:11:38 +0200 Subject: [PATCH] Support Google Play Services fused location backend --- app/build.gradle.kts | 4 ++ .../DefaultUserLocationStateFactory.kt | 2 +- ...ocationProvider.kt => LocationProvider.kt} | 42 ++++++++++++++++--- .../NotGrantedLocationProvider.kt | 28 ------------- .../service/LiveLocationSharingService.kt | 4 +- gradle/libs.versions.toml | 1 + 6 files changed, 44 insertions(+), 37 deletions(-) rename features/location/impl/src/main/kotlin/io/element/android/features/location/impl/common/userlocation/{PlatformLocationProvider.kt => LocationProvider.kt} (81%) delete mode 100644 features/location/impl/src/main/kotlin/io/element/android/features/location/impl/common/userlocation/NotGrantedLocationProvider.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 0f5b4864ac..a297ea7702 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -288,6 +288,10 @@ dependencies { implementation(projects.libraries.pushproviders.unifiedpush) } + // Google Play Services fused location backend. Discovered at runtime via ServiceLoader by the + // location feature. + "gplayImplementation"(libs.maplibre.compose.location.runtime.gms) + implementation(libs.appyx.core) implementation(libs.androidx.splash) implementation(libs.androidx.core) diff --git a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/common/userlocation/DefaultUserLocationStateFactory.kt b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/common/userlocation/DefaultUserLocationStateFactory.kt index 266c8f266c..083cd9dd45 100644 --- a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/common/userlocation/DefaultUserLocationStateFactory.kt +++ b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/common/userlocation/DefaultUserLocationStateFactory.kt @@ -25,7 +25,7 @@ class DefaultUserLocationStateFactory : UserLocationState.Factory { @Composable override fun create(hasLocationPermission: Boolean): UserLocationState { val locationProvider = if (hasLocationPermission) { - rememberPlatformLocationProvider() + rememberLocationProvider() } else { rememberNotGrantedLocationProvider() } diff --git a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/common/userlocation/PlatformLocationProvider.kt b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/common/userlocation/LocationProvider.kt similarity index 81% rename from features/location/impl/src/main/kotlin/io/element/android/features/location/impl/common/userlocation/PlatformLocationProvider.kt rename to features/location/impl/src/main/kotlin/io/element/android/features/location/impl/common/userlocation/LocationProvider.kt index 35bf7e67a7..6a2dbe3706 100644 --- a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/common/userlocation/PlatformLocationProvider.kt +++ b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/common/userlocation/LocationProvider.kt @@ -26,9 +26,12 @@ import androidx.core.location.LocationListenerCompat import androidx.core.location.LocationManagerCompat import androidx.core.location.LocationRequestCompat import androidx.core.os.ExecutorCompat +import io.element.android.libraries.core.data.tryOrNull import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.callbackFlow +import kotlinx.coroutines.flow.flowOf +import org.maplibre.compose.location.AndroidLocationBackend import org.maplibre.compose.location.LocationAccuracy import org.maplibre.compose.location.LocationEvent import org.maplibre.compose.location.LocationProvider @@ -36,11 +39,33 @@ import org.maplibre.compose.location.LocationRequest import org.maplibre.compose.location.LocationUnavailableReason import org.maplibre.compose.location.asMapLibreLocation import org.maplibre.spatialk.units.extensions.inMeters +import java.util.ServiceLoader + +/** + * Creates the location provider to use. + * + * Mostly mirrors `createDefaultLocationProvider` from maplibre compose: any [AndroidLocationBackend] + * registered through [ServiceLoader] takes precedence, highest [priority] + * first. When no backend is available it falls back to [PlatformLocationProvider]. + */ +fun createLocationProvider(context: Context): LocationProvider { + val backend = tryOrNull { + ServiceLoader.load(AndroidLocationBackend::class.java) + .filter { it.isAvailable(context) } + .minWithOrNull(compareByDescending { it.priority }.thenBy { it.id }) + } + return backend?.createLocationProvider(context) ?: PlatformLocationProvider(context) +} + +@Composable +fun rememberLocationProvider(context: Context = LocalContext.current): LocationProvider { + return remember(context) { createLocationProvider(context) } +} + class PlatformLocationProvider( private val context: Context, ) : LocationProvider { - @SuppressLint("MissingPermission") override fun updates(request: LocationRequest): Flow = callbackFlow { if (!context.hasLocationPermission()) { @@ -58,6 +83,7 @@ class PlatformLocationProvider( trySend(LocationEvent.Unavailable(LocationUnavailableReason.ServicesDisabled)) } } + var registered = false fun refreshRegistration() { @@ -126,7 +152,7 @@ class PlatformLocationProvider( close() } awaitClose { - runCatching { context.unregisterReceiver(settingsReceiver) } + tryOrNull { context.unregisterReceiver(settingsReceiver) } LocationManagerCompat.removeUpdates(locationManager, listener) } } @@ -155,9 +181,13 @@ private fun LocationAccuracy.toLocationRequestQuality(): Int = when (this) { LocationAccuracy.Low, LocationAccuracy.Lowest -> LocationRequestCompat.QUALITY_LOW_POWER } -@Composable -fun rememberPlatformLocationProvider(context: Context = LocalContext.current): PlatformLocationProvider { - return remember(context) { - PlatformLocationProvider(context = context) +class NotGrantedLocationProvider : LocationProvider { + override fun updates(request: LocationRequest): Flow { + return flowOf(LocationEvent.Unavailable(LocationUnavailableReason.PermissionDenied)) } } +@Composable + +fun rememberNotGrantedLocationProvider(): NotGrantedLocationProvider { + return remember { NotGrantedLocationProvider() } +} diff --git a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/common/userlocation/NotGrantedLocationProvider.kt b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/common/userlocation/NotGrantedLocationProvider.kt deleted file mode 100644 index 4154817bc6..0000000000 --- a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/common/userlocation/NotGrantedLocationProvider.kt +++ /dev/null @@ -1,28 +0,0 @@ -/* - * 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.userlocation - -import androidx.compose.runtime.Composable -import androidx.compose.runtime.remember -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.flowOf -import org.maplibre.compose.location.LocationEvent -import org.maplibre.compose.location.LocationProvider -import org.maplibre.compose.location.LocationRequest -import org.maplibre.compose.location.LocationUnavailableReason - -class NotGrantedLocationProvider : LocationProvider { - override fun updates(request: LocationRequest): Flow { - return flowOf(LocationEvent.Unavailable(LocationUnavailableReason.PermissionDenied)) - } -} - -@Composable -fun rememberNotGrantedLocationProvider(): NotGrantedLocationProvider { - return remember { NotGrantedLocationProvider() } -} 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 b5a966b094..d8868021dc 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 @@ -14,7 +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.userlocation.PlatformLocationProvider +import io.element.android.features.location.impl.common.userlocation.createLocationProvider 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 @@ -90,7 +90,7 @@ class LiveLocationSharingService : Service() { @OptIn(ExperimentalCoroutinesApi::class) private fun startLocationUpdatesListener() { Timber.d("LiveLocationSharingService listening to location updates") - val locationProvider = PlatformLocationProvider(context = applicationContext) + val locationProvider = createLocationProvider(context = applicationContext) appPreferencesStore.getLiveLocationMinimumDistanceInMetersUpdateFlow() .flatMapLatest { minDistanceMeters -> val locationRequest = LocationRequest( diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 14a77b42ca..2dc9c002b5 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -218,6 +218,7 @@ telephoto_flick = { module = "me.saket.telephoto:flick-android", version.ref = " statemachine = "com.freeletics.flowredux:compose:1.2.2" maplibre_compose = { module = "org.maplibre.compose:maplibre-compose", version.ref = "maplibre-compose" } maplibre_compose_runtime_vulkan = { module = "org.maplibre.compose:maplibre-compose-runtime-vulkan-android", version.ref = "maplibre-compose" } +maplibre_compose_location_runtime_gms = { module = "org.maplibre.compose:location-runtime-gms", version.ref = "maplibre-compose" } opusencoder = "io.element.android:opusencoder:1.2.0" zxing_cpp = "io.github.zxing-cpp:android:3.1.1" google_zxing = "com.google.zxing:core:3.5.4"