diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 0b878ee71..5e08f29db 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -29,7 +29,6 @@ - @@ -48,6 +47,7 @@ + @@ -104,7 +104,7 @@ diff --git a/app/src/main/java/com/geeksville/mesh/android/ContextServices.kt b/app/src/main/java/com/geeksville/mesh/android/ContextServices.kt index 7565da079..9232bfbac 100644 --- a/app/src/main/java/com/geeksville/mesh/android/ContextServices.kt +++ b/app/src/main/java/com/geeksville/mesh/android/ContextServices.kt @@ -145,21 +145,6 @@ fun Context.getLocationPermissions(): Array { /** @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 { - 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) */ diff --git a/app/src/main/java/com/geeksville/mesh/repository/location/LocationRepository.kt b/app/src/main/java/com/geeksville/mesh/repository/location/LocationRepository.kt index 23504fba5..9cd064814 100644 --- a/app/src/main/java/com/geeksville/mesh/repository/location/LocationRepository.kt +++ b/app/src/main/java/com/geeksville/mesh/repository/location/LocationRepository.kt @@ -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 = MutableStateFlow(false) val receivingLocationUpdates: StateFlow 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() } diff --git a/app/src/main/java/com/geeksville/mesh/service/MeshService.kt b/app/src/main/java/com/geeksville/mesh/service/MeshService.kt index 20782fd6b..737e7ac4d 100644 --- a/app/src/main/java/com/geeksville/mesh/service/MeshService.kt +++ b/app/src/main/java/com/geeksville/mesh/service/MeshService.kt @@ -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 }, diff --git a/app/src/main/java/com/geeksville/mesh/ui/SettingsFragment.kt b/app/src/main/java/com/geeksville/mesh/ui/SettingsFragment.kt index 7b253b7fc..5911c91d8 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/SettingsFragment.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/SettingsFragment.kt @@ -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() diff --git a/app/src/main/java/com/geeksville/mesh/ui/map/MapFragment.kt b/app/src/main/java/com/geeksville/mesh/ui/map/MapFragment.kt index 801819870..cf0308afa 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/map/MapFragment.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/map/MapFragment.kt @@ -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