diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml index cce83ac87..faa9c06a8 100644 --- a/src/main/AndroidManifest.xml +++ b/src/main/AndroidManifest.xml @@ -59,6 +59,8 @@ limitations under the License. tools:targetApi="s" /> + + Toast.makeText(this, R.string.permission_gps_failed, Toast.LENGTH_SHORT).show()); - } - - if (!PermissionUtils.hasBluetoothPermissions(this)) { - PermissionUtils.requestBluetoothPermission(this, null, null); - } + PermissionRequester.ALL.requestPermissionsIfNeeded(this, this, null, (requester) -> Toast.makeText(this, R.string.permission_gps_failed, Toast.LENGTH_SHORT).show()); } @Override diff --git a/src/main/java/de/dennisguse/opentracks/sensors/BluetoothRemoteSensorManager.java b/src/main/java/de/dennisguse/opentracks/sensors/BluetoothRemoteSensorManager.java index e3bf79e93..a5c5aa388 100644 --- a/src/main/java/de/dennisguse/opentracks/sensors/BluetoothRemoteSensorManager.java +++ b/src/main/java/de/dennisguse/opentracks/sensors/BluetoothRemoteSensorManager.java @@ -35,7 +35,7 @@ import de.dennisguse.opentracks.sensors.sensorData.SensorDataCycling; import de.dennisguse.opentracks.sensors.sensorData.SensorDataRunning; import de.dennisguse.opentracks.sensors.sensorData.SensorDataSet; import de.dennisguse.opentracks.settings.PreferencesUtils; -import de.dennisguse.opentracks.util.PermissionUtils; +import de.dennisguse.opentracks.util.PermissionRequester; /** * Bluetooth LE sensor manager: manages connections to Bluetooth LE sensors. @@ -162,7 +162,7 @@ public class BluetoothRemoteSensorManager implements BluetoothConnectionManager. } else { connectionManager.disconnect(); } - if (!PermissionUtils.hasBluetoothPermissions(context)) { + if (!PermissionRequester.BLUETOOTH.hasPermission(context)) { Log.w(TAG, "BLUETOOTH_SCAN and/or BLUETOOTH_CONNECT not granted; not connecting."); } diff --git a/src/main/java/de/dennisguse/opentracks/services/handlers/GPSHandler.java b/src/main/java/de/dennisguse/opentracks/services/handlers/GPSHandler.java index abf6be108..88a15f4b4 100644 --- a/src/main/java/de/dennisguse/opentracks/services/handlers/GPSHandler.java +++ b/src/main/java/de/dennisguse/opentracks/services/handlers/GPSHandler.java @@ -19,7 +19,7 @@ import de.dennisguse.opentracks.data.models.Distance; import de.dennisguse.opentracks.data.models.TrackPoint; import de.dennisguse.opentracks.settings.PreferencesUtils; import de.dennisguse.opentracks.util.LocationUtils; -import de.dennisguse.opentracks.util.PermissionUtils; +import de.dennisguse.opentracks.util.PermissionRequester; @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) public class GPSHandler implements LocationListener, GpsStatus.GpsStatusListener, SharedPreferences.OnSharedPreferenceChangeListener { @@ -54,7 +54,7 @@ public class GPSHandler implements LocationListener, GpsStatus.GpsStatusListener //TODO upgrade to AGP7.0.0/API31 started complaining about removeUpdates. public void onStop() { if (locationManager != null && context != null) { - if (PermissionUtils.hasGPSPermission(context)) { + if (PermissionRequester.GPS.hasPermission(context)) { locationManager.removeUpdates(this); } locationManager = null; @@ -152,7 +152,7 @@ public class GPSHandler implements LocationListener, GpsStatus.GpsStatusListener Log.e(TAG, "Not started."); return; } - if (PermissionUtils.hasGPSPermission(context)) { + if (PermissionRequester.GPS.hasPermission(context)) { try { locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, gpsInterval.toMillis(), 0, this); } catch (SecurityException e) { diff --git a/src/main/java/de/dennisguse/opentracks/settings/bluetooth/BluetoothLeSensorPreference.java b/src/main/java/de/dennisguse/opentracks/settings/bluetooth/BluetoothLeSensorPreference.java index eb75e5e11..c3d759eb1 100644 --- a/src/main/java/de/dennisguse/opentracks/settings/bluetooth/BluetoothLeSensorPreference.java +++ b/src/main/java/de/dennisguse/opentracks/settings/bluetooth/BluetoothLeSensorPreference.java @@ -29,7 +29,7 @@ import java.util.stream.Collectors; import de.dennisguse.opentracks.R; import de.dennisguse.opentracks.sensors.BluetoothUtils; import de.dennisguse.opentracks.settings.PreferencesUtils; -import de.dennisguse.opentracks.util.PermissionUtils; +import de.dennisguse.opentracks.util.PermissionRequester; /** * Preference to select a discoverable Bluetooth LE device. @@ -145,20 +145,18 @@ public abstract class BluetoothLeSensorPreference extends DialogPreference { bluetoothIcon = AnimatedVectorDrawableCompat.create(getContext(), R.drawable.ic_bluetooth_searching_animated_24dp); bluetoothIcon.start(); - if (!PermissionUtils.hasBluetoothPermissions(getContext())) { - PermissionUtils.requestBluetoothPermission(this, - this::startBluetoothScan, - () -> { - if (PermissionUtils.shouldShowRequestPermissionRationaleBluetooth(this)) { - Toast.makeText(getContext(), R.string.permission_bluetooth_failed_rejected, Toast.LENGTH_LONG).show(); - } else { - Toast.makeText(getContext(), R.string.permission_bluetooth_failed, Toast.LENGTH_SHORT).show(); - } - dismiss(); - }); - } else { - startBluetoothScan(); - } + PermissionRequester.BLUETOOTH.requestPermissionsIfNeeded(getContext(), this, + this::startBluetoothScan, + (requester) -> { + if (requester.shouldShowRequestPermissionRationale(this)) { + Toast.makeText(getContext(), R.string.permission_bluetooth_failed_rejected, Toast.LENGTH_LONG).show(); + } else { + Toast.makeText(getContext(), R.string.permission_bluetooth_failed, Toast.LENGTH_SHORT).show(); + } + dismiss(); + }); + + startBluetoothScan(); } private void startBluetoothScan() { diff --git a/src/main/java/de/dennisguse/opentracks/util/PermissionRequester.java b/src/main/java/de/dennisguse/opentracks/util/PermissionRequester.java new file mode 100644 index 000000000..031b7ab47 --- /dev/null +++ b/src/main/java/de/dennisguse/opentracks/util/PermissionRequester.java @@ -0,0 +1,101 @@ +package de.dennisguse.opentracks.util; + +import android.Manifest; +import android.content.Context; +import android.content.pm.PackageManager; +import android.os.Build; + +import androidx.activity.result.ActivityResultCaller; +import androidx.activity.result.ActivityResultLauncher; +import androidx.activity.result.contract.ActivityResultContracts; +import androidx.annotation.Nullable; +import androidx.core.content.ContextCompat; +import androidx.fragment.app.Fragment; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class PermissionRequester { + + private final List permissions; + + public PermissionRequester(List permissions) { + this.permissions = permissions; + } + + public boolean hasPermission(Context context) { + return permissions.stream() + .map(p -> ContextCompat.checkSelfPermission(context, p)) + .allMatch(r -> r == PackageManager.PERMISSION_GRANTED); + } + + public void requestPermissionsIfNeeded(Context context, ActivityResultCaller caller, @Nullable Runnable onGranted, @Nullable RejectedCallback onRejected) { + if (!hasPermission(context)) { + requestPermission(caller, onGranted, onRejected); + } + } + + public boolean shouldShowRequestPermissionRationale(Fragment context) { + return permissions.stream() + .anyMatch(context::shouldShowRequestPermissionRationale); + } + + private void requestPermission(ActivityResultCaller context, @Nullable Runnable onGranted, @Nullable RejectedCallback onRejected) { + ActivityResultLauncher locationPermissionRequest = context.registerForActivityResult(new ActivityResultContracts.RequestMultiplePermissions(), result -> { + boolean isGranted = permissions.stream() + .allMatch(p -> result.getOrDefault(p, false)); + if (isGranted && onGranted != null) { + onGranted.run(); + } + if (!isGranted && onRejected != null) { + onRejected.rejected(this); + } + } + ); + + locationPermissionRequest.launch(permissions.toArray(permissions.toArray(new String[0]))); + } + + private static final List GPS_PERMISSION = List.of(Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION); + + private static final List BLUETOOTH_PERMISSIONS; + + static { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + BLUETOOTH_PERMISSIONS = List.of(Manifest.permission.BLUETOOTH_SCAN, Manifest.permission.BLUETOOTH_CONNECT); + } else { + BLUETOOTH_PERMISSIONS = Collections.emptyList(); + } + } + + private static final List NOTIFICATION_PERMISSIONS; + + static { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + NOTIFICATION_PERMISSIONS = List.of(Manifest.permission.POST_NOTIFICATIONS); + } else { + NOTIFICATION_PERMISSIONS = Collections.emptyList(); + } + } + + private static final List ALL_PERMISSIONS; + + static { + ArrayList all = new ArrayList<>(GPS_PERMISSION); + all.addAll(BLUETOOTH_PERMISSIONS); + all.addAll(NOTIFICATION_PERMISSIONS); + + ALL_PERMISSIONS = Collections.unmodifiableList(all); + } + + public final static PermissionRequester GPS = new PermissionRequester(GPS_PERMISSION); + public final static PermissionRequester BLUETOOTH = new PermissionRequester(BLUETOOTH_PERMISSIONS); + public final static PermissionRequester NOTIFICATION = new PermissionRequester(NOTIFICATION_PERMISSIONS); + + public final static PermissionRequester ALL = new PermissionRequester(ALL_PERMISSIONS); + + public interface RejectedCallback { + void rejected(PermissionRequester permissionRequester); + } +} diff --git a/src/main/java/de/dennisguse/opentracks/util/PermissionUtils.java b/src/main/java/de/dennisguse/opentracks/util/PermissionUtils.java deleted file mode 100644 index 044dfe07b..000000000 --- a/src/main/java/de/dennisguse/opentracks/util/PermissionUtils.java +++ /dev/null @@ -1,74 +0,0 @@ -package de.dennisguse.opentracks.util; - -import android.Manifest; -import android.content.Context; -import android.content.pm.PackageManager; -import android.os.Build; - -import androidx.activity.result.ActivityResultCaller; -import androidx.activity.result.ActivityResultLauncher; -import androidx.activity.result.contract.ActivityResultContracts; -import androidx.annotation.Nullable; -import androidx.core.content.ContextCompat; -import androidx.fragment.app.Fragment; - -import java.util.Arrays; - -public class PermissionUtils { - - private static final String[] GPS = new String[]{Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION}; - private static final String[] BLUETOOTH; - - static { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) { - BLUETOOTH = new String[]{}; - } else { - BLUETOOTH = new String[]{Manifest.permission.BLUETOOTH_SCAN, Manifest.permission.BLUETOOTH_CONNECT}; - } - } - - private PermissionUtils() { - } - - public static boolean hasGPSPermission(Context context) { - return hasPermissions(context, GPS); - } - - public static void requestGPSPermission(ActivityResultCaller context, @Nullable Runnable onGranted, @Nullable Runnable onRejected) { - requestPermission(context, GPS, onGranted, onRejected); - } - - public static boolean shouldShowRequestPermissionRationaleBluetooth(Fragment context) { - return Arrays.stream(BLUETOOTH).anyMatch(context::shouldShowRequestPermissionRationale); - } - - public static boolean hasBluetoothPermissions(Context context) { - return hasPermissions(context, BLUETOOTH); - } - - public static void requestBluetoothPermission(ActivityResultCaller context, @Nullable Runnable onGranted, @Nullable Runnable onRejected) { - requestPermission(context, BLUETOOTH, onGranted, onRejected); - } - - private static void requestPermission(ActivityResultCaller context, final String[] permissions, @Nullable Runnable onGranted, @Nullable Runnable onRejected) { - ActivityResultLauncher locationPermissionRequest = context.registerForActivityResult(new ActivityResultContracts.RequestMultiplePermissions(), result -> { - boolean isGranted = Arrays.stream(permissions) - .allMatch(p -> result.getOrDefault(p, false)); - if (isGranted && onGranted != null) { - onGranted.run(); - } - if (!isGranted && onRejected != null) { - onRejected.run(); - } - } - ); - - locationPermissionRequest.launch(permissions); - } - - private static boolean hasPermissions(Context context, String[] permissions) { - return Arrays.stream(permissions) - .map(p -> ContextCompat.checkSelfPermission(context, p)) - .allMatch(r -> r == PackageManager.PERMISSION_GRANTED); - } -}