refactor: remove BACKGROUND_LOCATION permission

This commit is contained in:
andrekir
2024-08-26 19:31:41 -03:00
committed by Andre K
parent b4cdbf0617
commit 3a97e6dbcb
6 changed files with 19 additions and 56 deletions

View File

@@ -29,7 +29,6 @@
<!-- Permissions required for providing location (from phone GPS) to mesh -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
<!-- This permission is required for analytics - and soon the MQTT gateway -->
<uses-permission android:name="android.permission.INTERNET" />
@@ -48,6 +47,7 @@
<!-- We run our mesh code as a foreground service - FIXME, find a way to stop doing this -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />
<uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE" />
<!-- Needed to open our bluetooth connection to our paired device (after reboot) -->
@@ -104,7 +104,7 @@
<service
android:name="com.geeksville.mesh.service.MeshService"
android:enabled="true"
android:foregroundServiceType="connectedDevice"
android:foregroundServiceType="connectedDevice|location"
android:exported="true" tools:ignore="ExportedActivity">
<intent-filter>
<action android:name="com.geeksville.mesh.Service" />

View File

@@ -145,21 +145,6 @@ fun Context.getLocationPermissions(): Array<String> {
/** @return true if the user already has location permission */
fun Context.hasLocationPermission() = getLocationPermissions().isEmpty()
/**
* A list of missing background location permissions (or empty if we already have what we need)
*/
fun Context.getBackgroundPermissions(): Array<String> {
val perms = mutableListOf(Manifest.permission.ACCESS_FINE_LOCATION)
if (android.os.Build.VERSION.SDK_INT >= 29) // only added later
perms.add(Manifest.permission.ACCESS_BACKGROUND_LOCATION)
return getMissingPermissions(perms)
}
/** @return true if the user already has background location permission */
fun Context.hasBackgroundPermission() = getBackgroundPermissions().isEmpty()
/**
* Notification permission (or empty if we already have what we need)
*/

View File

@@ -1,8 +1,10 @@
package com.geeksville.mesh.repository.location
import android.annotation.SuppressLint
import android.Manifest.permission.ACCESS_COARSE_LOCATION
import android.Manifest.permission.ACCESS_FINE_LOCATION
import android.app.Application
import android.location.LocationManager
import androidx.annotation.RequiresPermission
import androidx.core.location.LocationCompat
import androidx.core.location.LocationListenerCompat
import androidx.core.location.LocationManagerCompat
@@ -10,7 +12,6 @@ import androidx.core.location.LocationRequestCompat
import androidx.core.location.altitude.AltitudeConverterCompat
import com.geeksville.mesh.android.GeeksvilleApplication
import com.geeksville.mesh.android.Logging
import com.geeksville.mesh.android.hasBackgroundPermission
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.asExecutor
import kotlinx.coroutines.channels.awaitClose
@@ -32,9 +33,8 @@ class LocationRepository @Inject constructor(
private val _receivingLocationUpdates: MutableStateFlow<Boolean> = MutableStateFlow(false)
val receivingLocationUpdates: StateFlow<Boolean> get() = _receivingLocationUpdates
@SuppressLint("MissingPermission")
@RequiresPermission(anyOf = [ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION])
private fun LocationManager.requestLocationUpdates() = callbackFlow {
if (!context.hasBackgroundPermission()) close()
val intervalMs = 30 * 1000L // 30 seconds
val minDistanceM = 0f
@@ -96,5 +96,6 @@ class LocationRepository @Inject constructor(
/**
* Observable flow for location updates
*/
@RequiresPermission(anyOf = [ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION])
fun getLocations() = locationManager.get().requestLocationUpdates()
}

View File

@@ -1,5 +1,6 @@
package com.geeksville.mesh.service
import android.annotation.SuppressLint
import android.app.Service
import android.content.Context
import android.content.Intent
@@ -17,7 +18,7 @@ import com.geeksville.mesh.LocalOnlyProtos.LocalConfig
import com.geeksville.mesh.LocalOnlyProtos.LocalModuleConfig
import com.geeksville.mesh.MeshProtos.MeshPacket
import com.geeksville.mesh.MeshProtos.ToRadio
import com.geeksville.mesh.android.hasBackgroundPermission
import com.geeksville.mesh.android.hasLocationPermission
import com.geeksville.mesh.database.MeshLogRepository
import com.geeksville.mesh.database.PacketRepository
import com.geeksville.mesh.database.entity.MeshLog
@@ -175,7 +176,8 @@ class MeshService : Service(), Logging {
// If we're already observing updates, don't register again
if (locationFlow?.isActive == true) return
if (hasBackgroundPermission()) {
@SuppressLint("MissingPermission")
if (hasLocationPermission()) {
locationFlow = locationRepository.getLocations().onEach { location ->
sendPosition(
position {
@@ -299,7 +301,7 @@ class MeshService : Service(), Logging {
serviceNotifications.notifyId,
notification,
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q) {
ServiceInfo.FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE
ServiceInfo.FOREGROUND_SERVICE_TYPE_MANIFEST
} else {
0
},

View File

@@ -138,24 +138,16 @@ class SettingsFragment : ScreenFragment("Settings"), Logging {
private fun initCommonUI() {
val requestBackgroundAndCheckLauncher =
registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { permissions ->
if (permissions.entries.any { !it.value }) {
debug("User denied background permission")
model.showSnackbar(getString(R.string.why_background_required))
}
}
val requestLocationAndBackgroundLauncher =
val requestLocationPermissionLauncher =
registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { permissions ->
if (permissions.entries.all { it.value }) {
// Older versions of android only need Location permission
if (!requireContext().hasBackgroundPermission())
requestBackgroundAndCheckLauncher.launch(requireContext().getBackgroundPermissions())
model.provideLocation.value = true
model.meshService?.startProvideLocation()
} else {
debug("User denied location permission")
model.showSnackbar(getString(R.string.why_background_required))
}
bluetoothViewModel.permissionsUpdated()
}
// init our region spinner
@@ -240,7 +232,7 @@ class SettingsFragment : ScreenFragment("Settings"), Logging {
binding.provideLocationCheckbox.setOnCheckedChangeListener { view, isChecked ->
// Don't check the box until the system setting changes
view.isChecked = isChecked && requireContext().hasBackgroundPermission()
view.isChecked = isChecked && requireContext().hasLocationPermission()
if (view.isPressed) { // We want to ignore changes caused by code (as opposed to the user)
debug("User changed location tracking to $isChecked")
@@ -255,9 +247,7 @@ class SettingsFragment : ScreenFragment("Settings"), Logging {
.setPositiveButton(getString(R.string.accept)) { _, _ ->
// Make sure we have location permission (prerequisite)
if (!requireContext().hasLocationPermission()) {
requestLocationAndBackgroundLauncher.launch(requireContext().getLocationPermissions())
} else {
requestBackgroundAndCheckLauncher.launch(requireContext().getBackgroundPermissions())
requestLocationPermissionLauncher.launch(requireContext().getLocationPermissions())
}
}
.show()

View File

@@ -182,21 +182,6 @@ fun MapView(
if (permissions.entries.all { it.value }) map.toggleMyLocation()
}
fun requestPermissionAndToggle() {
// Google rejects releases claiming this requires BACKGROUND_LOCATION prominent
// disclosure. Adding to comply even though it does not use background location.
MaterialAlertDialogBuilder(context)
.setTitle(R.string.background_required)
.setMessage(R.string.why_background_required)
.setNeutralButton(R.string.cancel) { _, _ ->
debug("User denied location permission")
}
.setPositiveButton(R.string.accept) { _, _ ->
requestPermissionAndToggleLauncher.launch(context.getLocationPermissions())
}
.show()
}
val nodes by model.nodeList.collectAsStateWithLifecycle()
val waypoints by model.waypoints.collectAsStateWithLifecycle(emptyMap())
@@ -667,7 +652,7 @@ fun MapView(
IconButton(
onClick = {
if (context.hasLocationPermission()) map.toggleMyLocation()
else requestPermissionAndToggle()
else requestPermissionAndToggleLauncher.launch(context.getLocationPermissions())
},
enabled = hasGps,
drawableRes = if (myLocationOverlay == null) R.drawable.ic_twotone_my_location_24