Skip to content

Commit

Permalink
Merge branch 'feature/check_permissions' of github.com:nrbrook/RxAndr…
Browse files Browse the repository at this point in the history
…oidBle into gradle-7-permissions
  • Loading branch information
nrbrook committed Feb 28, 2022
2 parents 5df8f26 + 88ccb00 commit f30abcc
Show file tree
Hide file tree
Showing 11 changed files with 175 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import com.polidea.rxandroidble2.internal.scan.ScanPreconditionsVerifier;
import com.polidea.rxandroidble2.internal.scan.ScanPreconditionsVerifierApi18;
import com.polidea.rxandroidble2.internal.scan.ScanPreconditionsVerifierApi24;
import com.polidea.rxandroidble2.internal.scan.ScanPreconditionsVerifierApi31;
import com.polidea.rxandroidble2.internal.scan.ScanSetupBuilder;
import com.polidea.rxandroidble2.internal.scan.ScanSetupBuilderImplApi18;
import com.polidea.rxandroidble2.internal.scan.ScanSetupBuilderImplApi21;
Expand Down Expand Up @@ -89,6 +90,7 @@ class PlatformConstants {
public static final String BOOL_IS_ANDROID_WEAR = "android-wear";
public static final String BOOL_IS_NEARBY_PERMISSION_NEVER_FOR_LOCATION = "nearby-permission-never-for-location";
public static final String STRING_ARRAY_SCAN_PERMISSIONS = "scan-permissions";
public static final String PACKAGE_INFO = "package-info";

private PlatformConstants() {

Expand Down Expand Up @@ -187,6 +189,18 @@ static String[][] provideRecommendedScanRuntimePermissionNames(
};
}

@Provides
@Named(PlatformConstants.PACKAGE_INFO)
static PackageInfo providePackageInfo(
Context context
) {
try {
return context.getPackageManager().getPackageInfo(context.getPackageName(), PackageManager.GET_PERMISSIONS);
} catch (Exception e) {
return new PackageInfo();
}
}

@Provides
static ContentResolver provideContentResolver(Context context) {
return context.getContentResolver();
Expand Down Expand Up @@ -347,12 +361,15 @@ static byte[] provideDisableNotificationValue() {
static ScanPreconditionsVerifier provideScanPreconditionVerifier(
@Named(PlatformConstants.INT_DEVICE_SDK) int deviceSdk,
Provider<ScanPreconditionsVerifierApi18> scanPreconditionVerifierForApi18,
Provider<ScanPreconditionsVerifierApi24> scanPreconditionVerifierForApi24
Provider<ScanPreconditionsVerifierApi24> scanPreconditionVerifierForApi24,
Provider<ScanPreconditionsVerifierApi31> scanPreconditionVerifierForApi31
) {
if (deviceSdk < Build.VERSION_CODES.N) {
return scanPreconditionVerifierForApi18.get();
} else {
} else if (deviceSdk < Build.VERSION_CODES.S) {
return scanPreconditionVerifierForApi24.get();
} else {
return scanPreconditionVerifierForApi31.get();
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ public RxBleDevice getBleDevice(@NonNull String macAddress) {
public Set<RxBleDevice> getBondedDevices() {
guardBluetoothAdapterAvailable();
Set<RxBleDevice> rxBleDevices = new HashSet<>();
// TODO: check BLUETOOTH_CONNECT permission
Set<BluetoothDevice> bluetoothDevices = rxBleAdapterWrapper.getBondedDevices();
for (BluetoothDevice bluetoothDevice : bluetoothDevices) {
rxBleDevices.add(getBleDevice(bluetoothDevice.getAddress()));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ public class BleScanException extends BleException {

@IntDef({BLUETOOTH_CANNOT_START, BLUETOOTH_DISABLED, BLUETOOTH_NOT_AVAILABLE, LOCATION_PERMISSION_MISSING, LOCATION_SERVICES_DISABLED,
SCAN_FAILED_ALREADY_STARTED, SCAN_FAILED_APPLICATION_REGISTRATION_FAILED, SCAN_FAILED_INTERNAL_ERROR,
SCAN_FAILED_FEATURE_UNSUPPORTED, SCAN_FAILED_OUT_OF_HARDWARE_RESOURCES, UNDOCUMENTED_SCAN_THROTTLE, UNKNOWN_ERROR_CODE})
SCAN_FAILED_FEATURE_UNSUPPORTED, SCAN_FAILED_OUT_OF_HARDWARE_RESOURCES, SCAN_PERMISSION_MISSING, UNDOCUMENTED_SCAN_THROTTLE,
UNKNOWN_ERROR_CODE})
@Retention(RetentionPolicy.SOURCE)
public @interface Reason {

Expand Down Expand Up @@ -76,6 +77,11 @@ public class BleScanException extends BleException {
*/
public static final int SCAN_FAILED_OUT_OF_HARDWARE_RESOURCES = 9;

/**
* The BLUETOOTH_SCAN permission has not been granted. Only on API >=31.
*/
public static final int SCAN_PERMISSION_MISSING = 10;

/**
* On API >=25 there is an undocumented scan throttling mechanism. If 5 scans were started by the app during a 30 second window
* the next scan in that window will be silently skipped with only a log warning. In this situation there should be
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ public Observable<RxBleConnection> establishConnection(final ConnectionSetup opt
return Observable.defer(new Callable<ObservableSource<RxBleConnection>>() {
@Override
public ObservableSource<RxBleConnection> call() {
// TODO: Check BLUETOOTH_CONNECT permission
if (isConnected.compareAndSet(false, true)) {
return connector.prepareConnection(options)
.doFinally(new Action() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package com.polidea.rxandroidble2.internal.scan;


import android.Manifest;
import android.content.pm.PackageInfo;

import com.polidea.rxandroidble2.ClientComponent;
import com.polidea.rxandroidble2.exceptions.BleScanException;
import com.polidea.rxandroidble2.internal.util.LocationServicesStatus;
import com.polidea.rxandroidble2.internal.util.RxBleAdapterWrapper;

import java.util.Date;
import java.util.concurrent.TimeUnit;

import bleshadow.javax.inject.Inject;
import bleshadow.javax.inject.Named;
import io.reactivex.Scheduler;

public class ScanPreconditionsVerifierApi31 implements ScanPreconditionsVerifier {

/*
* default values taken from
* https://android.googlesource.com/platform/packages/apps/Bluetooth/+/android-7.0.0_r1/src/com/android/bluetooth/gatt/AppScanStats.java
*/
private static final int SCANS_LENGTH = 5;
private static final long EXCESSIVE_SCANNING_PERIOD = TimeUnit.SECONDS.toMillis(30);
private final long[] previousChecks = new long[SCANS_LENGTH];
private final RxBleAdapterWrapper rxBleAdapterWrapper;
private final LocationServicesStatus locationServicesStatus;
private final Scheduler timeScheduler;
private final PackageInfo packageInfo;

@Inject
public ScanPreconditionsVerifierApi31(
RxBleAdapterWrapper rxBleAdapterWrapper,
LocationServicesStatus locationServicesStatus,
@Named(ClientComponent.NamedSchedulers.COMPUTATION) Scheduler timeScheduler,
@Named(ClientComponent.PlatformConstants.PACKAGE_INFO) PackageInfo packageInfo
) {
this.rxBleAdapterWrapper = rxBleAdapterWrapper;
this.locationServicesStatus = locationServicesStatus;
this.timeScheduler = timeScheduler;
this.packageInfo = packageInfo;
}

@Override
public void verify(boolean checkLocationProviderState) {
// determine if we really need to check location
if (checkLocationProviderState
&& this.packageInfo != null
&& this.packageInfo.requestedPermissions != null
&& this.packageInfo.requestedPermissionsFlags != null) {
// On API 31 we only need to check location here if the scan permission requests it
for (int i = 0; i < this.packageInfo.requestedPermissions.length; i++) {
if (Manifest.permission.BLUETOOTH_SCAN.equals(this.packageInfo.requestedPermissions[i])) {
if ((this.packageInfo.requestedPermissionsFlags[i] & PackageInfo.REQUESTED_PERMISSION_NEVER_FOR_LOCATION) != 0) {
// BLUETOOTH_SCAN is neverForLocation
checkLocationProviderState = false;
}
break;
}
}
}

if (!rxBleAdapterWrapper.hasBluetoothAdapter()) {
throw new BleScanException(BleScanException.BLUETOOTH_NOT_AVAILABLE);
} else if (!rxBleAdapterWrapper.isBluetoothEnabled()) {
throw new BleScanException(BleScanException.BLUETOOTH_DISABLED);
} else if (checkLocationProviderState && !locationServicesStatus.isLocationPermissionOk()) {
throw new BleScanException(BleScanException.LOCATION_PERMISSION_MISSING);
} else if (checkLocationProviderState && !locationServicesStatus.isLocationProviderOk()) {
throw new BleScanException(BleScanException.LOCATION_SERVICES_DISABLED);
} else if (!locationServicesStatus.isScanPermissionOk()) {
throw new BleScanException(BleScanException.SCAN_PERMISSION_MISSING);
}

/*
* Android 7.0 (API 24) introduces an undocumented scan throttle for applications that try to scan more than 5 times during
* a 30 second window. More on the topic: https://blog.classycode.com/undocumented-android-7-ble-behavior-changes-d1a9bd87d983
*/

// TODO: [DS] 27.06.2017 Think if persisting this information through Application close is needed
final int oldestCheckTimestampIndex = getOldestCheckTimestampIndex();
final long oldestCheckTimestamp = previousChecks[oldestCheckTimestampIndex];
final long currentCheckTimestamp = timeScheduler.now(TimeUnit.MILLISECONDS);

if (currentCheckTimestamp - oldestCheckTimestamp < EXCESSIVE_SCANNING_PERIOD) {
throw new BleScanException(
BleScanException.UNDOCUMENTED_SCAN_THROTTLE,
new Date(oldestCheckTimestamp + EXCESSIVE_SCANNING_PERIOD)
);
}
previousChecks[oldestCheckTimestampIndex] = currentCheckTimestamp;
}

private int getOldestCheckTimestampIndex() {
long oldestTimestamp = Long.MAX_VALUE;
int index = -1;
for (int i = 0; i < SCANS_LENGTH; i++) {
final long previousCheckTimestamp = previousChecks[i];
if (previousCheckTimestamp < oldestTimestamp) {
index = i;
oldestTimestamp = previousCheckTimestamp;
}
}
return index;
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.polidea.rxandroidble2.internal.util;


import android.Manifest;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Process;
Expand Down Expand Up @@ -34,6 +35,15 @@ public boolean isScanRuntimePermissionGranted() {
return allNeededPermissionsGranted;
}

public boolean isLocationRuntimePermissionGranted() {
return isPermissionGranted(Manifest.permission.ACCESS_COARSE_LOCATION)
|| isPermissionGranted(Manifest.permission.ACCESS_FINE_LOCATION);
}

public boolean isConnectRuntimePermissionGranted() {
return isPermissionGranted(Manifest.permission.BLUETOOTH_CONNECT);
}

private boolean isAnyPermissionGranted(String[] acceptablePermissions) {
for (String acceptablePermission : acceptablePermissions) {
if (isPermissionGranted(acceptablePermission)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,6 @@ public interface LocationServicesStatus {

boolean isLocationPermissionOk();
boolean isLocationProviderOk();
boolean isScanPermissionOk();
boolean isConnectPermissionOk();
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,12 @@ public boolean isLocationPermissionOk() {
public boolean isLocationProviderOk() {
return true;
}

public boolean isScanPermissionOk() {
return true;
}

public boolean isConnectPermissionOk() {
return true;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,14 @@ public boolean isLocationProviderOk() {
return !isLocationProviderEnabledRequired() || checkerLocationProvider.isLocationProviderEnabled();
}

public boolean isScanPermissionOk() {
return true;
}

public boolean isConnectPermissionOk() {
return true;
}

/**
* A function that returns true if the location services may be needed to be turned ON. Since there are no official guidelines
* for Android Wear check is disabled.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,22 @@ public class LocationServicesStatusApi31 implements LocationServicesStatus {
}

public boolean isLocationPermissionOk() {
return checkerScanPermission.isScanRuntimePermissionGranted();
return checkerScanPermission.isLocationRuntimePermissionGranted();
}

public boolean isLocationProviderOk() {
return !isLocationProviderEnabledRequired() || checkerLocationProvider.isLocationProviderEnabled();
}

@Override
public boolean isScanPermissionOk() {
return checkerScanPermission.isScanRuntimePermissionGranted();
}

public boolean isConnectPermissionOk() {
return checkerScanPermission.isConnectRuntimePermissionGranted();
}

/**
* A function that returns true if the location services may be needed to be turned ON. Since there are no official guidelines
* for Android Wear check is disabled.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ public Set<BluetoothDevice> getBondedDevices() {
if (bluetoothAdapter == null) {
throw nullBluetoothAdapter;
}
// TODO: check BLUETOOTH_CONNECT permission
return bluetoothAdapter.getBondedDevices();
}
}

0 comments on commit f30abcc

Please sign in to comment.