Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 18df2ca3 authored by Ellen Poe's avatar Ellen Poe
Browse files

fix: location permission grant not being received on e/OS

parent f534501b
Loading
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -108,6 +108,7 @@ android {
    }
    buildFeatures {
        compose = true
        buildConfig = true
    }

    // Define a single UniFFI binding generation task outside of applicationVariants.all to avoid duplication
+19 −18
Original line number Diff line number Diff line
@@ -129,11 +129,7 @@ class MainActivity : ComponentActivity() {
    }

    private fun requestLocationPermission() {
        requestPermissions(
            arrayOf(
                Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION
            ), LOCATION_PERMISSION_REQUEST_CODE
        )
        locationPermissionLauncher.launch(Manifest.permission.ACCESS_FINE_LOCATION)
    }

    // Permission request launcher for notification permission (Android 13+)
@@ -154,6 +150,24 @@ class MainActivity : ComponentActivity() {
        }
    }

    // Permission request launcher for location permission (Android 13+)
    private val locationPermissionLauncher = registerForActivityResult(
        ActivityResultContracts.RequestPermission()
    ) { isGranted ->
        hasLocationPermission = isGranted
        if (isGranted) {
            Log.d(TAG, "Location permission granted")
            lifecycleScope.launch {
                permissionRequestManager.onPermissionGranted(PermissionRequest.NotificationPermission)
            }
        } else {
            Log.d(TAG, "Location permission denied")
            lifecycleScope.launch {
                permissionRequestManager.onPermissionDenied(PermissionRequest.NotificationPermission)
            }
        }
    }

    private val connection = object : ServiceConnection {
        override fun onServiceConnected(className: ComponentName, service: IBinder) {
            // We've bound to LocalMapServerService, cast the IBinder and get LocalMapServerService instance
@@ -289,19 +303,6 @@ class MainActivity : ComponentActivity() {
        ferrostarWrapperRepository.androidTtsObserver.stopAndClearQueue()
    }

    override fun onRequestPermissionsResult(
        requestCode: Int, permissions: Array<out String?>, grantResults: IntArray, deviceId: Int
    ) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults, deviceId)
        when (requestCode) {
            LOCATION_PERMISSION_REQUEST_CODE -> {
                // Check if all permissions were granted
                hasLocationPermission =
                    grantResults.isNotEmpty() && grantResults.all { it == android.content.pm.PackageManager.PERMISSION_GRANTED }
            }
        }
    }

    override fun onDestroy() {
        super.onDestroy()
        ferrostarWrapperRepository.androidTtsObserver.shutdown()
+112 −22
Original line number Diff line number Diff line
@@ -23,6 +23,7 @@ import android.content.Context
import android.location.Location
import android.location.LocationListener
import android.location.LocationManager
import android.os.Build
import android.os.Bundle
import android.util.Log
import dagger.hilt.android.qualifiers.ApplicationContext
@@ -55,6 +56,8 @@ class LocationRepository @Inject constructor(
        private const val LOCATION_REQUEST_TIMEOUT_MS = 10000L // 10 seconds
        private const val CONTINUOUS_LOCATION_UPDATE_INTERVAL_MS = 5000L // 5 seconds
        private const val CONTINUOUS_LOCATION_UPDATE_DISTANCE_M = 5f // 5 meters
        private const val FUSED_PROVIDER_TIMEOUT_MS =
            5000L // 5 seconds - discard GPS if fused hasn't updated
    }

    // Location caching with thread safety
@@ -69,8 +72,12 @@ class LocationRepository @Inject constructor(
    private val _locationFlow: MutableStateFlow<Location?> = MutableStateFlow(null)
    val locationFlow: StateFlow<Location?> = _locationFlow.asStateFlow()

    // Location listener for continuous updates
    private var locationListener: LocationListener? = null
    // Location listeners for continuous updates
    private var gpsLocationListener: LocationListener? = null
    private var fusedLocationListener: LocationListener? = null

    // Track last fused provider update time
    private var lastFusedLocationTime: Long = 0L

    /**
     * Gets the current location, either from cache or by requesting a fresh one.
@@ -132,6 +139,10 @@ class LocationRepository @Inject constructor(
    /**
     * Starts continuous location updates and orientation tracking.
     * This should be called from the UI when the map is ready.
     *
     * Subscribes to both GPS and fused providers to handle unreliable fused location
     * on some microG devices. GPS updates are discarded unless fused hasn't provided
     * a location for 5 seconds.
     */
    @SuppressLint("MissingPermission")
    fun startContinuousLocationUpdates(context: Context) {
@@ -146,37 +157,95 @@ class LocationRepository @Inject constructor(

        try {
            val locationManager = getLocationManager(context)
            val availableProviders = locationManager.getProviders(true)

            @Suppress("DEPRECATION") val bestProvider = locationManager.getBestProvider(
                android.location.Criteria().apply {
                    accuracy = android.location.Criteria.ACCURACY_FINE
                }, true
            // Subscribe to fused provider if available
            if (Build.VERSION.SDK_INT >= 31 && availableProviders.contains(LocationManager.FUSED_PROVIDER)) {
                fusedLocationListener = object : LocationListener {
                    override fun onLocationChanged(location: Location) {
                        Log.d("LocationRepository", "Fused location update received")
                        lastFusedLocationTime = System.currentTimeMillis()

                        // Always use fused provider updates
                        _locationFlow.value = location
                        orientationRepository.setDeclination(location)

                        synchronized(locationLock) {
                            lastRequestedLocation = location
                        }
                    }

                    override fun onProviderDisabled(provider: String) {
                        Log.d("LocationRepository", "Fused provider disabled")
                        try {
                            locationManager.removeUpdates(this)
                        } catch (_: Exception) {
                            // Ignore exceptions during cleanup
                        }
                        fusedLocationListener = null
                    }

                    override fun onProviderEnabled(provider: String) {
                        Log.d("LocationRepository", "Fused provider enabled")
                    }

                    override fun onStatusChanged(
                        provider: String?,
                        status: Int,
                        extras: Bundle?
                    ) {
                    }
                }

                locationManager.requestLocationUpdates(
                    LocationManager.FUSED_PROVIDER,
                    CONTINUOUS_LOCATION_UPDATE_INTERVAL_MS,
                    CONTINUOUS_LOCATION_UPDATE_DISTANCE_M,
                    fusedLocationListener!!
                )
                Log.d("LocationRepository", "Subscribed to fused provider")
            }

            bestProvider?.let { provider ->
                locationListener = object : LocationListener {
            // Subscribe to GPS provider if available
            if (availableProviders.contains(LocationManager.GPS_PROVIDER)) {
                gpsLocationListener = object : LocationListener {
                    override fun onLocationChanged(location: Location) {
                        // Update the location flow with the new location
                        val currentTime = System.currentTimeMillis()
                        val timeSinceLastFused = currentTime - lastFusedLocationTime

                        // Only use GPS if fused hasn't updated in 5 seconds
                        if (timeSinceLastFused >= FUSED_PROVIDER_TIMEOUT_MS) {
                            Log.d(
                                "LocationRepository",
                                "Using GPS location (fused timeout: ${timeSinceLastFused}ms)"
                            )
                            _locationFlow.value = location
                            orientationRepository.setDeclination(location)

                            synchronized(locationLock) {
                                lastRequestedLocation = location
                            }
                        } else {
                            Log.d(
                                "LocationRepository",
                                "Discarding GPS update (fused active: ${timeSinceLastFused}ms ago)"
                            )
                        }
                    }

                    override fun onProviderDisabled(provider: String) {
                        // Stop location updates if provider is disabled
                        Log.d("LocationRepository", "GPS provider disabled")
                        try {
                            locationManager.removeUpdates(this)
                        } catch (_: Exception) {
                            // Ignore exceptions during cleanup
                        }
                        locationListener = null
                        _locationFlow.value = null
                        gpsLocationListener = null
                    }

                    override fun onProviderEnabled(provider: String) {}
                    override fun onProviderEnabled(provider: String) {
                        Log.d("LocationRepository", "GPS provider enabled")
                    }

                    override fun onStatusChanged(
                        provider: String?,
@@ -187,15 +256,17 @@ class LocationRepository @Inject constructor(
                }

                locationManager.requestLocationUpdates(
                    provider,
                    LocationManager.GPS_PROVIDER,
                    CONTINUOUS_LOCATION_UPDATE_INTERVAL_MS,
                    CONTINUOUS_LOCATION_UPDATE_DISTANCE_M,
                    locationListener!!
                    gpsLocationListener!!
                )
                Log.d("LocationRepository", "Subscribed to GPS provider")
            }
        } catch (_: Exception) {
            // Handle exceptions during setup
            locationListener = null
            gpsLocationListener = null
            fusedLocationListener = null
            _locationFlow.value = null
        }
    }
@@ -208,15 +279,34 @@ class LocationRepository @Inject constructor(
        orientationRepository.stop()
        Log.d("LocationRepository", "Orientation sensor stopped")

        locationListener?.let { listener ->
        val locationManager = try {
            getLocationManager(context)
        } catch (_: Exception) {
            null
        }

        // Remove GPS listener
        gpsLocationListener?.let { listener ->
            try {
                val locationManager = getLocationManager(context)
                locationManager.removeUpdates(listener)
                locationManager?.removeUpdates(listener)
            } catch (_: Exception) {
                // Ignore exceptions during cleanup
            }
            locationListener = null
            gpsLocationListener = null
        }

        // Remove fused listener
        fusedLocationListener?.let { listener ->
            try {
                locationManager?.removeUpdates(listener)
            } catch (_: Exception) {
                // Ignore exceptions during cleanup
            }
            fusedLocationListener = null
        }

        // Reset fused location timestamp
        lastFusedLocationTime = 0L
    }

    /**
+0 −11
Original line number Diff line number Diff line
@@ -20,7 +20,6 @@ package earth.maps.cardinal.ui.core

import android.content.Context
import android.location.Location
import android.location.LocationListener
import android.util.Log
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.ui.unit.Dp
@@ -72,15 +71,8 @@ class MapViewModel @Inject constructor(

    private companion object {
        const val TAG = "MapViewModel"
        private const val LOCATION_REQUEST_TIMEOUT_MS = 10000L // 10 seconds
        private const val CONTINUOUS_LOCATION_UPDATE_INTERVAL_MS = 5000L // 5 seconds
        private const val CONTINUOUS_LOCATION_UPDATE_DISTANCE_M = 5f // 5 meters
    }

    // Location caching with thread safety
    private var lastRequestedLocation: Location? = null
    private val locationLock = Any()

    // State flows for UI components - delegate to repository
    val isLocating: StateFlow<Boolean> = locationRepository.isLocating

@@ -91,9 +83,6 @@ class MapViewModel @Inject constructor(

    val heading: StateFlow<Float?> = orientationRepository.azimuth

    // Location listener for continuous updates
    private var locationListener: LocationListener? = null

    // Permission tracking
    private val previousPermissionState = AtomicBoolean(false)