mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-12 18:59:06 +01:00
Handle new BLUETOOTH_SCAN and BLUETOOTH_CONNECT permission
We need to have those permissions before doing things like accessing the Bluetooth address. So we force-disable the Bluetooth plugin if the permission is not granted. The UI then forces the permission before allowing to enable the plugin.
This commit is contained in:
@@ -1,15 +1,25 @@
|
||||
<manifest
|
||||
package="org.briarproject.bramble"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="org.briarproject.bramble">
|
||||
|
||||
<uses-feature android:name="android.hardware.bluetooth" android:required="false"/>
|
||||
<uses-feature
|
||||
android:name="android.hardware.bluetooth"
|
||||
android:required="false" />
|
||||
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
|
||||
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
|
||||
<uses-permission android:name="android.permission.BLUETOOTH"/>
|
||||
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK"/>
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
||||
<uses-permission
|
||||
android:name="android.permission.BLUETOOTH"
|
||||
android:maxSdkVersion="30" />
|
||||
<uses-permission
|
||||
android:name="android.permission.BLUETOOTH_ADMIN"
|
||||
android:maxSdkVersion="30" />
|
||||
<uses-permission
|
||||
android:name="android.permission.BLUETOOTH_SCAN"
|
||||
android:usesPermissionFlags="neverForLocation" />
|
||||
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
|
||||
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
|
||||
<application
|
||||
android:allowBackup="false"
|
||||
|
||||
@@ -55,6 +55,7 @@ import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static java.util.logging.Logger.getLogger;
|
||||
import static org.briarproject.bramble.util.AndroidUtils.hasBtConnectPermission;
|
||||
import static org.briarproject.bramble.util.PrivacyUtils.scrubMacAddress;
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@@ -97,6 +98,11 @@ class AndroidBluetoothPlugin extends
|
||||
this.clock = clock;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isBluetoothAccessible() {
|
||||
return hasBtConnectPermission(app);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() throws PluginException {
|
||||
super.start();
|
||||
|
||||
@@ -6,7 +6,6 @@ import android.bluetooth.BluetoothAdapter;
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.os.Parcel;
|
||||
import android.os.StrictMode;
|
||||
import android.provider.Settings;
|
||||
@@ -15,12 +14,19 @@ import org.briarproject.nullsafety.NotNullByDefault;
|
||||
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static android.os.Build.FINGERPRINT;
|
||||
import static android.os.Build.SERIAL;
|
||||
import static android.os.Build.VERSION.SDK_INT;
|
||||
import static android.os.Process.myPid;
|
||||
import static android.os.Process.myTid;
|
||||
import static android.os.Process.myUid;
|
||||
import static android.provider.Settings.Secure.ANDROID_ID;
|
||||
import static org.briarproject.bramble.util.AndroidUtils.hasBtConnectPermission;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
@@ -39,22 +45,27 @@ class AndroidSecureRandomProvider extends UnixSecureRandomProvider {
|
||||
@Override
|
||||
protected void writeToEntropyPool(DataOutputStream out) throws IOException {
|
||||
super.writeToEntropyPool(out);
|
||||
out.writeInt(android.os.Process.myPid());
|
||||
out.writeInt(android.os.Process.myTid());
|
||||
out.writeInt(android.os.Process.myUid());
|
||||
if (Build.FINGERPRINT != null) out.writeUTF(Build.FINGERPRINT);
|
||||
if (Build.SERIAL != null) out.writeUTF(Build.SERIAL);
|
||||
out.writeInt(myPid());
|
||||
out.writeInt(myTid());
|
||||
out.writeInt(myUid());
|
||||
if (FINGERPRINT != null) out.writeUTF(FINGERPRINT);
|
||||
if (SERIAL != null) out.writeUTF(SERIAL);
|
||||
ContentResolver contentResolver = appContext.getContentResolver();
|
||||
String id = Settings.Secure.getString(contentResolver, ANDROID_ID);
|
||||
if (id != null) out.writeUTF(id);
|
||||
Parcel parcel = Parcel.obtain();
|
||||
BluetoothAdapter bt = BluetoothAdapter.getDefaultAdapter();
|
||||
if (bt != null) {
|
||||
for (BluetoothDevice device : bt.getBondedDevices())
|
||||
parcel.writeParcelable(device, 0);
|
||||
// use bluetooth paired devices as well, if allowed
|
||||
if (hasBtConnectPermission(appContext)) {
|
||||
Parcel parcel = Parcel.obtain();
|
||||
BluetoothAdapter bt = BluetoothAdapter.getDefaultAdapter();
|
||||
if (bt != null) {
|
||||
@SuppressLint("MissingPermission")
|
||||
Set<BluetoothDevice> deviceSet = bt.getBondedDevices();
|
||||
for (BluetoothDevice device : deviceSet)
|
||||
parcel.writeParcelable(device, 0);
|
||||
}
|
||||
out.write(parcel.marshall());
|
||||
parcel.recycle();
|
||||
}
|
||||
out.write(parcel.marshall());
|
||||
parcel.recycle();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -77,7 +88,7 @@ class AndroidSecureRandomProvider extends UnixSecureRandomProvider {
|
||||
.invoke(null, (Object) seed);
|
||||
// Mix the output of the Linux PRNG into the OpenSSL PRNG
|
||||
int bytesRead = (Integer) Class.forName(
|
||||
"org.apache.harmony.xnet.provider.jsse.NativeCrypto")
|
||||
"org.apache.harmony.xnet.provider.jsse.NativeCrypto")
|
||||
.getMethod("RAND_load_file", String.class, long.class)
|
||||
.invoke(null, "/dev/urandom", 1024);
|
||||
if (bytesRead != 1024) throw new IOException();
|
||||
|
||||
@@ -22,9 +22,14 @@ import java.util.Scanner;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static android.Manifest.permission.BLUETOOTH_CONNECT;
|
||||
import static android.Manifest.permission.BLUETOOTH_SCAN;
|
||||
import static android.app.PendingIntent.FLAG_IMMUTABLE;
|
||||
import static android.content.Context.MODE_PRIVATE;
|
||||
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
|
||||
import static android.os.Build.VERSION.SDK_INT;
|
||||
import static android.os.Process.myPid;
|
||||
import static android.os.Process.myUid;
|
||||
import static java.lang.Runtime.getRuntime;
|
||||
import static java.util.Arrays.asList;
|
||||
import static org.briarproject.nullsafety.NullSafety.requireNonNull;
|
||||
@@ -49,6 +54,16 @@ public class AndroidUtils {
|
||||
return abis;
|
||||
}
|
||||
|
||||
public static boolean hasBtScanPermission(Context ctx) {
|
||||
return SDK_INT < 31 || ctx.checkPermission(BLUETOOTH_SCAN, myPid(),
|
||||
myUid()) == PERMISSION_GRANTED;
|
||||
}
|
||||
|
||||
public static boolean hasBtConnectPermission(Context ctx) {
|
||||
return SDK_INT < 31 || ctx.checkPermission(BLUETOOTH_CONNECT, myPid(),
|
||||
myUid()) == PERMISSION_GRANTED;
|
||||
}
|
||||
|
||||
public static String getBluetoothAddress(Context ctx,
|
||||
BluetoothAdapter adapter) {
|
||||
return getBluetoothAddressAndMethod(ctx, adapter).getFirst();
|
||||
|
||||
@@ -89,6 +89,16 @@ abstract class AbstractBluetoothPlugin<S, SS> implements BluetoothPlugin,
|
||||
|
||||
private volatile String contactConnectionsUuid = null;
|
||||
|
||||
/**
|
||||
* Override and return true, if the plugin is now allowed to access the
|
||||
* Bluetooth hardware, so it must be
|
||||
* {@link org.briarproject.bramble.api.plugin.Plugin.State#DISABLED}
|
||||
* in {@link #start()}.
|
||||
*/
|
||||
protected boolean isBluetoothAccessible() {
|
||||
return true;
|
||||
}
|
||||
|
||||
abstract void initialiseAdapter() throws IOException;
|
||||
|
||||
abstract boolean isAdapterEnabled();
|
||||
@@ -176,19 +186,28 @@ abstract class AbstractBluetoothPlugin<S, SS> implements BluetoothPlugin,
|
||||
DEFAULT_PREF_PLUGIN_ENABLE);
|
||||
everConnected.set(settings.getBoolean(PREF_EVER_CONNECTED,
|
||||
DEFAULT_PREF_EVER_CONNECTED));
|
||||
// disable plugin, if conditions for enabling are not met
|
||||
if (enabledByUser && !isBluetoothAccessible()) {
|
||||
enabledByUser = false;
|
||||
settings.putBoolean(PREF_PLUGIN_ENABLE, false);
|
||||
callback.mergeSettings(settings);
|
||||
}
|
||||
state.setStarted(enabledByUser);
|
||||
try {
|
||||
initialiseAdapter();
|
||||
} catch (IOException e) {
|
||||
throw new PluginException(e);
|
||||
}
|
||||
updateProperties();
|
||||
if (enabledByUser && isAdapterEnabled()) bind();
|
||||
if (enabledByUser) {
|
||||
updateProperties();
|
||||
if (isAdapterEnabled()) bind();
|
||||
}
|
||||
}
|
||||
|
||||
private void bind() {
|
||||
ioExecutor.execute(() -> {
|
||||
if (getState() != INACTIVE) return;
|
||||
if (contactConnectionsUuid == null) updateProperties();
|
||||
// Bind a server socket to accept connections from contacts
|
||||
SS ss;
|
||||
try {
|
||||
|
||||
@@ -19,8 +19,6 @@
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
||||
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
|
||||
<uses-permission android:name="android.permission.BLUETOOTH" />
|
||||
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||
|
||||
@@ -11,20 +11,31 @@ import androidx.core.util.Consumer;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
|
||||
import static android.Manifest.permission.ACCESS_FINE_LOCATION;
|
||||
import static android.Manifest.permission.BLUETOOTH_ADVERTISE;
|
||||
import static android.Manifest.permission.BLUETOOTH_CONNECT;
|
||||
import static android.Manifest.permission.BLUETOOTH_SCAN;
|
||||
import static android.Manifest.permission.CAMERA;
|
||||
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
|
||||
import static android.os.Build.VERSION.SDK_INT;
|
||||
import static androidx.core.app.ActivityCompat.shouldShowRequestPermissionRationale;
|
||||
import static androidx.core.content.ContextCompat.checkSelfPermission;
|
||||
import static org.briarproject.bramble.util.AndroidUtils.hasBtConnectPermission;
|
||||
import static org.briarproject.bramble.util.AndroidUtils.hasBtScanPermission;
|
||||
import static org.briarproject.briar.android.util.Permission.GRANTED;
|
||||
import static org.briarproject.briar.android.util.Permission.PERMANENTLY_DENIED;
|
||||
import static org.briarproject.briar.android.util.Permission.SHOW_RATIONALE;
|
||||
import static org.briarproject.briar.android.util.Permission.UNKNOWN;
|
||||
import static org.briarproject.briar.android.util.UiUtils.isLocationEnabled;
|
||||
import static org.briarproject.briar.android.util.UiUtils.showDenialDialog;
|
||||
import static org.briarproject.briar.android.util.UiUtils.showLocationDialog;
|
||||
import static org.briarproject.briar.android.util.UiUtils.showRationale;
|
||||
import static org.briarproject.briar.android.util.UiUtils.wasGrantedBluetoothPermissions;
|
||||
|
||||
class AddNearbyContactPermissionManager {
|
||||
|
||||
private Permission cameraPermission = Permission.UNKNOWN;
|
||||
private Permission locationPermission = Permission.UNKNOWN;
|
||||
private Permission cameraPermission = UNKNOWN;
|
||||
private Permission locationPermission = SDK_INT < 31 ? UNKNOWN : GRANTED;
|
||||
private Permission bluetoothPermissions = SDK_INT < 31 ? GRANTED : UNKNOWN;
|
||||
|
||||
private final FragmentActivity ctx;
|
||||
private final Consumer<String[]> requestPermissions;
|
||||
@@ -39,23 +50,32 @@ class AddNearbyContactPermissionManager {
|
||||
}
|
||||
|
||||
void resetPermissions() {
|
||||
cameraPermission = Permission.UNKNOWN;
|
||||
locationPermission = Permission.UNKNOWN;
|
||||
cameraPermission = UNKNOWN;
|
||||
locationPermission = SDK_INT < 31 ? UNKNOWN : GRANTED;
|
||||
bluetoothPermissions = SDK_INT < 31 ? GRANTED : UNKNOWN;
|
||||
}
|
||||
|
||||
static boolean areEssentialPermissionsGranted(Context ctx,
|
||||
boolean isBluetoothSupported) {
|
||||
int ok = PERMISSION_GRANTED;
|
||||
return checkSelfPermission(ctx, CAMERA) == ok &&
|
||||
(SDK_INT < 23 ||
|
||||
checkSelfPermission(ctx, ACCESS_FINE_LOCATION) == ok ||
|
||||
!isBluetoothSupported);
|
||||
boolean bluetoothOk;
|
||||
if (!isBluetoothSupported || SDK_INT < 23) {
|
||||
bluetoothOk = true;
|
||||
} else if (SDK_INT < 31) {
|
||||
bluetoothOk = checkSelfPermission(ctx, ACCESS_FINE_LOCATION) == ok;
|
||||
} else {
|
||||
bluetoothOk = hasBtConnectPermission(ctx) &&
|
||||
hasBtScanPermission(ctx) &&
|
||||
checkSelfPermission(ctx, BLUETOOTH_ADVERTISE) == ok;
|
||||
}
|
||||
return bluetoothOk && checkSelfPermission(ctx, CAMERA) == ok;
|
||||
}
|
||||
|
||||
private boolean areEssentialPermissionsGranted() {
|
||||
return cameraPermission == Permission.GRANTED &&
|
||||
(SDK_INT < 23 || locationPermission == Permission.GRANTED ||
|
||||
!isBluetoothSupported);
|
||||
boolean bluetoothGranted = locationPermission == GRANTED &&
|
||||
bluetoothPermissions == GRANTED;
|
||||
return cameraPermission == GRANTED &&
|
||||
(SDK_INT < 23 || !isBluetoothSupported || bluetoothGranted);
|
||||
}
|
||||
|
||||
boolean checkPermissions() {
|
||||
@@ -63,31 +83,40 @@ class AddNearbyContactPermissionManager {
|
||||
if (locationEnabled && areEssentialPermissionsGranted()) return true;
|
||||
// If an essential permission has been permanently denied, ask the
|
||||
// user to change the setting
|
||||
if (cameraPermission == Permission.PERMANENTLY_DENIED) {
|
||||
if (cameraPermission == PERMANENTLY_DENIED) {
|
||||
showDenialDialog(ctx, R.string.permission_camera_title,
|
||||
R.string.permission_camera_denied_body);
|
||||
return false;
|
||||
}
|
||||
if (isBluetoothSupported &&
|
||||
locationPermission == Permission.PERMANENTLY_DENIED) {
|
||||
if (isBluetoothSupported && locationPermission == PERMANENTLY_DENIED) {
|
||||
showDenialDialog(ctx, R.string.permission_location_title,
|
||||
R.string.permission_location_denied_body);
|
||||
return false;
|
||||
}
|
||||
if (isBluetoothSupported &&
|
||||
bluetoothPermissions == PERMANENTLY_DENIED) {
|
||||
showDenialDialog(ctx, R.string.permission_bluetooth_title,
|
||||
R.string.permission_bluetooth_denied_body);
|
||||
return false;
|
||||
}
|
||||
// Should we show the rationale for one or both permissions?
|
||||
if (cameraPermission == Permission.SHOW_RATIONALE &&
|
||||
locationPermission == Permission.SHOW_RATIONALE) {
|
||||
if (cameraPermission == SHOW_RATIONALE &&
|
||||
locationPermission == SHOW_RATIONALE) {
|
||||
showRationale(ctx, R.string.permission_camera_location_title,
|
||||
R.string.permission_camera_location_request_body,
|
||||
this::requestPermissions);
|
||||
} else if (cameraPermission == Permission.SHOW_RATIONALE) {
|
||||
} else if (cameraPermission == SHOW_RATIONALE) {
|
||||
showRationale(ctx, R.string.permission_camera_title,
|
||||
R.string.permission_camera_request_body,
|
||||
this::requestPermissions);
|
||||
} else if (locationPermission == Permission.SHOW_RATIONALE) {
|
||||
} else if (locationPermission == SHOW_RATIONALE) {
|
||||
showRationale(ctx, R.string.permission_location_title,
|
||||
R.string.permission_location_request_body,
|
||||
this::requestPermissions);
|
||||
} else if (bluetoothPermissions == SHOW_RATIONALE) {
|
||||
showRationale(ctx, R.string.permission_bluetooth_title,
|
||||
R.string.permission_bluetooth_body,
|
||||
this::requestPermissions);
|
||||
} else if (locationEnabled) {
|
||||
requestPermissions();
|
||||
} else {
|
||||
@@ -99,7 +128,12 @@ class AddNearbyContactPermissionManager {
|
||||
private void requestPermissions() {
|
||||
String[] permissions;
|
||||
if (isBluetoothSupported) {
|
||||
permissions = new String[] {CAMERA, ACCESS_FINE_LOCATION};
|
||||
if (SDK_INT < 31) {
|
||||
permissions = new String[] {CAMERA, ACCESS_FINE_LOCATION};
|
||||
} else {
|
||||
permissions = new String[] {CAMERA, BLUETOOTH_ADVERTISE,
|
||||
BLUETOOTH_CONNECT, BLUETOOTH_SCAN};
|
||||
}
|
||||
} else {
|
||||
permissions = new String[] {CAMERA};
|
||||
}
|
||||
@@ -108,19 +142,29 @@ class AddNearbyContactPermissionManager {
|
||||
|
||||
void onRequestPermissionResult(Map<String, Boolean> result) {
|
||||
if (gotPermission(CAMERA, result)) {
|
||||
cameraPermission = Permission.GRANTED;
|
||||
cameraPermission = GRANTED;
|
||||
} else if (shouldShowRationale(CAMERA)) {
|
||||
cameraPermission = Permission.SHOW_RATIONALE;
|
||||
cameraPermission = SHOW_RATIONALE;
|
||||
} else {
|
||||
cameraPermission = Permission.PERMANENTLY_DENIED;
|
||||
cameraPermission = PERMANENTLY_DENIED;
|
||||
}
|
||||
if (isBluetoothSupported) {
|
||||
if (gotPermission(ACCESS_FINE_LOCATION, result)) {
|
||||
locationPermission = Permission.GRANTED;
|
||||
} else if (shouldShowRationale(ACCESS_FINE_LOCATION)) {
|
||||
locationPermission = Permission.SHOW_RATIONALE;
|
||||
if (SDK_INT < 31) {
|
||||
if (gotPermission(ACCESS_FINE_LOCATION, result)) {
|
||||
locationPermission = GRANTED;
|
||||
} else if (shouldShowRationale(ACCESS_FINE_LOCATION)) {
|
||||
locationPermission = SHOW_RATIONALE;
|
||||
} else {
|
||||
locationPermission = PERMANENTLY_DENIED;
|
||||
}
|
||||
} else {
|
||||
locationPermission = Permission.PERMANENTLY_DENIED;
|
||||
if (wasGrantedBluetoothPermissions(result)) {
|
||||
bluetoothPermissions = GRANTED;
|
||||
} else if (shouldShowRationale(BLUETOOTH_CONNECT)) {
|
||||
bluetoothPermissions = SHOW_RATIONALE;
|
||||
} else {
|
||||
bluetoothPermissions = PERMANENTLY_DENIED;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.briarproject.briar.android.contact.add.nearby;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Application;
|
||||
import android.bluetooth.BluetoothAdapter;
|
||||
import android.content.BroadcastReceiver;
|
||||
@@ -250,6 +251,7 @@ class AddNearbyContactViewModel extends AndroidViewModel
|
||||
}
|
||||
|
||||
@UiThread
|
||||
@SuppressLint("MissingPermission") // we check permissions before
|
||||
private boolean isBluetoothReady() {
|
||||
if (bt == null || bluetoothPlugin == null) {
|
||||
// Continue without Bluetooth
|
||||
|
||||
@@ -5,58 +5,101 @@ import android.content.Context;
|
||||
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.util.Permission;
|
||||
import org.briarproject.briar.android.util.UiUtils;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import androidx.activity.result.ActivityResultLauncher;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.UiThread;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
|
||||
import static android.Manifest.permission.ACCESS_FINE_LOCATION;
|
||||
import static android.Manifest.permission.BLUETOOTH_CONNECT;
|
||||
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
|
||||
import static android.os.Build.VERSION.SDK_INT;
|
||||
import static androidx.core.app.ActivityCompat.shouldShowRequestPermissionRationale;
|
||||
import static androidx.core.content.ContextCompat.checkSelfPermission;
|
||||
import static org.briarproject.briar.android.util.Permission.GRANTED;
|
||||
import static org.briarproject.briar.android.util.Permission.PERMANENTLY_DENIED;
|
||||
import static org.briarproject.briar.android.util.Permission.SHOW_RATIONALE;
|
||||
import static org.briarproject.briar.android.util.Permission.UNKNOWN;
|
||||
import static org.briarproject.briar.android.util.UiUtils.getGoToSettingsListener;
|
||||
import static org.briarproject.briar.android.util.UiUtils.isLocationEnabled;
|
||||
import static org.briarproject.briar.android.util.UiUtils.requestBluetoothPermissions;
|
||||
import static org.briarproject.briar.android.util.UiUtils.showLocationDialog;
|
||||
import static org.briarproject.briar.android.util.UiUtils.wasGrantedBluetoothPermissions;
|
||||
|
||||
class BluetoothConditionManager {
|
||||
|
||||
private Permission locationPermission = Permission.UNKNOWN;
|
||||
private Permission locationPermission = SDK_INT < 31 ? UNKNOWN : GRANTED;
|
||||
private Permission bluetoothPermissions = SDK_INT < 31 ? GRANTED : UNKNOWN;
|
||||
|
||||
/**
|
||||
* Call this when the using activity or fragment starts,
|
||||
* because permissions might have changed while it was stopped.
|
||||
*/
|
||||
void reset() {
|
||||
locationPermission = Permission.UNKNOWN;
|
||||
locationPermission = SDK_INT < 31 ? UNKNOWN : GRANTED;
|
||||
bluetoothPermissions = SDK_INT < 31 ? GRANTED : UNKNOWN;
|
||||
}
|
||||
|
||||
@UiThread
|
||||
void requestPermissions(ActivityResultLauncher<String[]> launcher) {
|
||||
if (SDK_INT < 31) {
|
||||
launcher.launch(new String[] {ACCESS_FINE_LOCATION});
|
||||
} else {
|
||||
requestBluetoothPermissions(launcher);
|
||||
}
|
||||
}
|
||||
|
||||
@UiThread
|
||||
void onLocationPermissionResult(Activity activity,
|
||||
@Nullable Boolean result) {
|
||||
if (result != null && result) {
|
||||
locationPermission = Permission.GRANTED;
|
||||
} else if (shouldShowRequestPermissionRationale(activity,
|
||||
ACCESS_FINE_LOCATION)) {
|
||||
locationPermission = Permission.SHOW_RATIONALE;
|
||||
@Nullable Map<String, Boolean> result) {
|
||||
if (SDK_INT < 31) {
|
||||
if (gotPermission(activity, result)) {
|
||||
locationPermission = GRANTED;
|
||||
} else if (shouldShowRequestPermissionRationale(activity,
|
||||
ACCESS_FINE_LOCATION)) {
|
||||
locationPermission = SHOW_RATIONALE;
|
||||
} else {
|
||||
locationPermission = PERMANENTLY_DENIED;
|
||||
}
|
||||
} else {
|
||||
locationPermission = Permission.PERMANENTLY_DENIED;
|
||||
if (wasGrantedBluetoothPermissions(result)) {
|
||||
bluetoothPermissions = GRANTED;
|
||||
} else if (shouldShowRequestPermissionRationale(activity,
|
||||
BLUETOOTH_CONNECT)) {
|
||||
bluetoothPermissions = SHOW_RATIONALE;
|
||||
} else {
|
||||
bluetoothPermissions = PERMANENTLY_DENIED;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
boolean areRequirementsFulfilled(Context ctx,
|
||||
ActivityResultLauncher<String> permissionRequest,
|
||||
boolean areRequirementsFulfilled(FragmentActivity ctx,
|
||||
ActivityResultLauncher<String[]> permissionRequest,
|
||||
Runnable onLocationDenied) {
|
||||
boolean permissionGranted =
|
||||
SDK_INT < 23 || locationPermission == Permission.GRANTED;
|
||||
(SDK_INT < 23 || locationPermission == GRANTED) &&
|
||||
bluetoothPermissions == GRANTED;
|
||||
boolean locationEnabled = isLocationEnabled(ctx);
|
||||
if (permissionGranted && locationEnabled) return true;
|
||||
|
||||
if (locationPermission == Permission.PERMANENTLY_DENIED) {
|
||||
if (locationPermission == PERMANENTLY_DENIED) {
|
||||
showDenialDialog(ctx, onLocationDenied);
|
||||
} else if (locationPermission == Permission.SHOW_RATIONALE) {
|
||||
} else if (locationPermission == SHOW_RATIONALE) {
|
||||
showRationale(ctx, permissionRequest);
|
||||
} else if (!locationEnabled) {
|
||||
showLocationDialog(ctx);
|
||||
} else if (bluetoothPermissions == PERMANENTLY_DENIED) {
|
||||
UiUtils.showDenialDialog(ctx, R.string.permission_bluetooth_title,
|
||||
R.string.permission_bluetooth_denied_body);
|
||||
} else if (bluetoothPermissions == SHOW_RATIONALE && SDK_INT >= 31) {
|
||||
UiUtils.showRationale(ctx, R.string.permission_bluetooth_title,
|
||||
R.string.permission_bluetooth_body, () ->
|
||||
requestBluetoothPermissions(permissionRequest));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -72,13 +115,27 @@ class BluetoothConditionManager {
|
||||
}
|
||||
|
||||
private void showRationale(Context ctx,
|
||||
ActivityResultLauncher<String> permissionRequest) {
|
||||
ActivityResultLauncher<String[]> permissionRequest) {
|
||||
new AlertDialog.Builder(ctx, R.style.BriarDialogTheme)
|
||||
.setTitle(R.string.permission_location_title)
|
||||
.setMessage(R.string.permission_location_request_body)
|
||||
.setPositiveButton(R.string.ok, (dialog, which) ->
|
||||
permissionRequest.launch(ACCESS_FINE_LOCATION))
|
||||
permissionRequest.launch(
|
||||
new String[] {ACCESS_FINE_LOCATION}))
|
||||
.show();
|
||||
}
|
||||
|
||||
private boolean gotPermission(Context ctx,
|
||||
@Nullable Map<String, Boolean> result) {
|
||||
Boolean permissionResult =
|
||||
result == null ? null : result.get(ACCESS_FINE_LOCATION);
|
||||
return permissionResult == null ? isLocationPermissionGranted(ctx) :
|
||||
permissionResult;
|
||||
}
|
||||
|
||||
private boolean isLocationPermissionGranted(Context ctx) {
|
||||
return checkSelfPermission(ctx, ACCESS_FINE_LOCATION) ==
|
||||
PERMISSION_GRANTED;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package org.briarproject.briar.android.contact.connect;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
@@ -14,15 +13,17 @@ import org.briarproject.briar.android.util.ActivityLaunchers.RequestBluetoothDis
|
||||
import org.briarproject.nullsafety.MethodsNotNullByDefault;
|
||||
import org.briarproject.nullsafety.ParametersNotNullByDefault;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import androidx.activity.result.ActivityResultLauncher;
|
||||
import androidx.activity.result.contract.ActivityResultContracts.RequestPermission;
|
||||
import androidx.activity.result.contract.ActivityResultContracts.RequestMultiplePermissions;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
|
||||
import static android.Manifest.permission.ACCESS_FINE_LOCATION;
|
||||
import static android.widget.Toast.LENGTH_LONG;
|
||||
import static org.briarproject.briar.android.AppModule.getAndroidComponent;
|
||||
|
||||
@@ -42,8 +43,8 @@ public class BluetoothIntroFragment extends Fragment {
|
||||
private final ActivityResultLauncher<Integer> bluetoothDiscoverableRequest =
|
||||
registerForActivityResult(new RequestBluetoothDiscoverable(),
|
||||
this::onBluetoothDiscoverable);
|
||||
private final ActivityResultLauncher<String> permissionRequest =
|
||||
registerForActivityResult(new RequestPermission(),
|
||||
private final ActivityResultLauncher<String[]> permissionRequest =
|
||||
registerForActivityResult(new RequestMultiplePermissions(),
|
||||
this::onPermissionRequestResult);
|
||||
|
||||
@Override
|
||||
@@ -80,12 +81,13 @@ public class BluetoothIntroFragment extends Fragment {
|
||||
// if the permission is already granted.
|
||||
// So we can use the request as a generic entry point
|
||||
// to the whole flow.
|
||||
permissionRequest.launch(ACCESS_FINE_LOCATION);
|
||||
conditionManager.requestPermissions(permissionRequest);
|
||||
}
|
||||
}
|
||||
|
||||
private void onPermissionRequestResult(@Nullable Boolean result) {
|
||||
Activity a = requireActivity();
|
||||
private void onPermissionRequestResult(
|
||||
@Nullable Map<String, Boolean> result) {
|
||||
FragmentActivity a = requireActivity();
|
||||
// update permission result in BluetoothConnecter
|
||||
conditionManager.onLocationPermissionResult(a, result);
|
||||
// what to do when the user denies granting the location permission
|
||||
|
||||
@@ -26,18 +26,24 @@ import org.briarproject.nullsafety.ParametersNotNullByDefault;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import androidx.activity.result.ActivityResultLauncher;
|
||||
import androidx.activity.result.contract.ActivityResultContracts.RequestMultiplePermissions;
|
||||
import androidx.annotation.ColorRes;
|
||||
import androidx.annotation.DrawableRes;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RequiresApi;
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
import androidx.appcompat.widget.SwitchCompat;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
|
||||
import static android.Manifest.permission.BLUETOOTH_CONNECT;
|
||||
import static android.os.Build.VERSION.SDK_INT;
|
||||
import static android.view.View.GONE;
|
||||
import static android.view.View.VISIBLE;
|
||||
import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE;
|
||||
@@ -47,7 +53,13 @@ import static org.briarproject.bramble.api.plugin.Plugin.State.STARTING_STOPPING
|
||||
import static org.briarproject.bramble.api.plugin.TorConstants.REASON_BATTERY;
|
||||
import static org.briarproject.bramble.api.plugin.TorConstants.REASON_COUNTRY_BLOCKED;
|
||||
import static org.briarproject.bramble.api.plugin.TorConstants.REASON_MOBILE_DATA;
|
||||
import static org.briarproject.bramble.util.AndroidUtils.hasBtConnectPermission;
|
||||
import static org.briarproject.bramble.util.AndroidUtils.hasBtScanPermission;
|
||||
import static org.briarproject.briar.android.util.UiUtils.requestBluetoothPermissions;
|
||||
import static org.briarproject.briar.android.util.UiUtils.showDenialDialog;
|
||||
import static org.briarproject.briar.android.util.UiUtils.showOnboardingDialog;
|
||||
import static org.briarproject.briar.android.util.UiUtils.showRationale;
|
||||
import static org.briarproject.briar.android.util.UiUtils.wasGrantedBluetoothPermissions;
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
@@ -61,6 +73,11 @@ public class TransportsActivity extends BriarActivity {
|
||||
private PluginViewModel viewModel;
|
||||
private BaseAdapter transportsAdapter;
|
||||
|
||||
@RequiresApi(31)
|
||||
private final ActivityResultLauncher<String[]> requestPermissionLauncher =
|
||||
registerForActivityResult(new RequestMultiplePermissions(),
|
||||
this::handleBtPermissionResult);
|
||||
|
||||
@Override
|
||||
public void injectActivity(ActivityComponent component) {
|
||||
component.inject(this);
|
||||
@@ -149,8 +166,7 @@ public class TransportsActivity extends BriarActivity {
|
||||
view.findViewById(R.id.switchCompat);
|
||||
switchCompat.setText(getString(t.switchLabel));
|
||||
switchCompat.setOnClickListener(v ->
|
||||
viewModel.enableTransport(t.id,
|
||||
switchCompat.isChecked()));
|
||||
onClicked(t.id, switchCompat.isChecked()));
|
||||
switchCompat.setChecked(t.isSwitchChecked);
|
||||
|
||||
TextView summary = view.findViewById(R.id.summary);
|
||||
@@ -203,6 +219,21 @@ public class TransportsActivity extends BriarActivity {
|
||||
});
|
||||
}
|
||||
|
||||
private void onClicked(TransportId transportId, boolean enable) {
|
||||
if (enable && SDK_INT >= 31 &&
|
||||
(!hasBtConnectPermission(this) || !hasBtScanPermission(this))) {
|
||||
if (shouldShowRequestPermissionRationale(BLUETOOTH_CONNECT)) {
|
||||
showRationale(this, R.string.permission_bluetooth_title,
|
||||
R.string.permission_bluetooth_body,
|
||||
this::requestBtPermissions);
|
||||
} else {
|
||||
requestBtPermissions();
|
||||
}
|
||||
} else {
|
||||
viewModel.enableTransport(transportId, enable);
|
||||
}
|
||||
}
|
||||
|
||||
private String getBulletString(@StringRes int resId) {
|
||||
return "\u2022 " + getString(resId);
|
||||
}
|
||||
@@ -316,6 +347,23 @@ public class TransportsActivity extends BriarActivity {
|
||||
return transport;
|
||||
}
|
||||
|
||||
@RequiresApi(31)
|
||||
private void requestBtPermissions() {
|
||||
requestBluetoothPermissions(requestPermissionLauncher);
|
||||
}
|
||||
|
||||
@RequiresApi(31)
|
||||
private void handleBtPermissionResult(Map<String, Boolean> grantedMap) {
|
||||
if (wasGrantedBluetoothPermissions(grantedMap)) {
|
||||
viewModel.enableTransport(BluetoothConstants.ID, true);
|
||||
} else {
|
||||
transportsAdapter.notifyDataSetChanged();
|
||||
showDenialDialog(this,
|
||||
R.string.permission_bluetooth_title,
|
||||
R.string.permission_bluetooth_denied_body);
|
||||
}
|
||||
}
|
||||
|
||||
private static class Transport {
|
||||
|
||||
private final TransportId id;
|
||||
|
||||
@@ -58,6 +58,8 @@ import static java.util.Locale.US;
|
||||
import static java.util.Objects.requireNonNull;
|
||||
import static java.util.TimeZone.getTimeZone;
|
||||
import static org.briarproject.bramble.util.AndroidUtils.getBluetoothAddressAndMethod;
|
||||
import static org.briarproject.bramble.util.AndroidUtils.hasBtConnectPermission;
|
||||
import static org.briarproject.bramble.util.AndroidUtils.hasBtScanPermission;
|
||||
import static org.briarproject.bramble.util.PrivacyUtils.scrubInetAddress;
|
||||
import static org.briarproject.bramble.util.PrivacyUtils.scrubMacAddress;
|
||||
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
|
||||
@@ -273,12 +275,13 @@ class BriarReportCollector {
|
||||
|
||||
// Is Bluetooth enabled?
|
||||
@SuppressLint("HardwareIds")
|
||||
boolean btEnabled = bt.isEnabled()
|
||||
boolean btEnabled = hasBtConnectPermission(ctx) && bt.isEnabled()
|
||||
&& !isNullOrEmpty(bt.getAddress());
|
||||
connectivityInfo.add("BluetoothEnabled", btEnabled);
|
||||
|
||||
// Is Bluetooth connectable?
|
||||
int scanMode = bt.getScanMode();
|
||||
@SuppressLint("MissingPermission")
|
||||
int scanMode = hasBtScanPermission(ctx) ? bt.getScanMode() : -1;
|
||||
boolean btConnectable = scanMode == SCAN_MODE_CONNECTABLE ||
|
||||
scanMode == SCAN_MODE_CONNECTABLE_DISCOVERABLE;
|
||||
connectivityInfo.add("BluetoothConnectable", btConnectable);
|
||||
@@ -298,11 +301,14 @@ class BriarReportCollector {
|
||||
btLeAdvertise);
|
||||
}
|
||||
|
||||
Pair<String, String> p = getBluetoothAddressAndMethod(ctx, bt);
|
||||
String address = p.getFirst();
|
||||
String method = p.getSecond();
|
||||
connectivityInfo.add("BluetoothAddress", scrubMacAddress(address));
|
||||
connectivityInfo.add("BluetoothAddressMethod", method);
|
||||
if (hasBtConnectPermission(ctx)) {
|
||||
Pair<String, String> p = getBluetoothAddressAndMethod(ctx, bt);
|
||||
String address = p.getFirst();
|
||||
String method = p.getSecond();
|
||||
connectivityInfo.add("BluetoothAddress",
|
||||
scrubMacAddress(address));
|
||||
connectivityInfo.add("BluetoothAddressMethod", method);
|
||||
}
|
||||
}
|
||||
return new ReportItem("Connectivity", R.string.dev_report_connectivity,
|
||||
connectivityInfo);
|
||||
|
||||
@@ -8,18 +8,32 @@ import org.briarproject.briar.R;
|
||||
import org.briarproject.nullsafety.MethodsNotNullByDefault;
|
||||
import org.briarproject.nullsafety.ParametersNotNullByDefault;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import androidx.activity.result.ActivityResultLauncher;
|
||||
import androidx.activity.result.contract.ActivityResultContracts.RequestMultiplePermissions;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RequiresApi;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
import androidx.lifecycle.LifecycleOwner;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.preference.ListPreference;
|
||||
import androidx.preference.PreferenceFragmentCompat;
|
||||
import androidx.preference.SwitchPreferenceCompat;
|
||||
|
||||
import static android.Manifest.permission.BLUETOOTH_CONNECT;
|
||||
import static android.os.Build.VERSION.SDK_INT;
|
||||
import static org.briarproject.bramble.util.AndroidUtils.hasBtConnectPermission;
|
||||
import static org.briarproject.bramble.util.AndroidUtils.hasBtScanPermission;
|
||||
import static org.briarproject.briar.android.AppModule.getAndroidComponent;
|
||||
import static org.briarproject.briar.android.settings.SettingsActivity.enableAndPersist;
|
||||
import static org.briarproject.briar.android.util.UiUtils.requestBluetoothPermissions;
|
||||
import static org.briarproject.briar.android.util.UiUtils.showDenialDialog;
|
||||
import static org.briarproject.briar.android.util.UiUtils.showRationale;
|
||||
import static org.briarproject.briar.android.util.UiUtils.wasGrantedBluetoothPermissions;
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
@@ -47,6 +61,11 @@ public class ConnectionsFragment extends PreferenceFragmentCompat {
|
||||
private SwitchPreferenceCompat torMobile;
|
||||
private SwitchPreferenceCompat torOnlyWhenCharging;
|
||||
|
||||
@RequiresApi(31)
|
||||
private final ActivityResultLauncher<String[]> requestPermissionLauncher =
|
||||
registerForActivityResult(new RequestMultiplePermissions(),
|
||||
this::handleBtPermissionResult);
|
||||
|
||||
@Override
|
||||
public void onAttach(@NonNull Context context) {
|
||||
super.onAttach(context);
|
||||
@@ -69,6 +88,25 @@ public class ConnectionsFragment extends PreferenceFragmentCompat {
|
||||
|
||||
torNetwork.setSummaryProvider(viewModel.torSummaryProvider);
|
||||
|
||||
if (SDK_INT >= 31) {
|
||||
enableBluetooth.setOnPreferenceChangeListener((p, value) -> {
|
||||
FragmentActivity ctx = requireActivity();
|
||||
if (hasBtConnectPermission(ctx) && hasBtScanPermission(ctx)) {
|
||||
return true;
|
||||
} else if (shouldShowRequestPermissionRationale(
|
||||
BLUETOOTH_CONNECT)) {
|
||||
showRationale(ctx, R.string.permission_bluetooth_title,
|
||||
R.string.permission_bluetooth_body,
|
||||
this::requestBtPermissions);
|
||||
// we don't update the preference directly,
|
||||
// but do it via the launcher, if we got the permissions
|
||||
return false;
|
||||
} else {
|
||||
requestBtPermissions();
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
enableBluetooth.setPreferenceDataStore(connectionsManager.btStore);
|
||||
enableWifi.setPreferenceDataStore(connectionsManager.wifiStore);
|
||||
enableTor.setPreferenceDataStore(connectionsManager.torStore);
|
||||
@@ -115,4 +153,19 @@ public class ConnectionsFragment extends PreferenceFragmentCompat {
|
||||
requireActivity().setTitle(R.string.network_settings_title);
|
||||
}
|
||||
|
||||
@RequiresApi(31)
|
||||
private void requestBtPermissions() {
|
||||
requestBluetoothPermissions(requestPermissionLauncher);
|
||||
}
|
||||
|
||||
@RequiresApi(31)
|
||||
private void handleBtPermissionResult(Map<String, Boolean> grantedMap) {
|
||||
if (wasGrantedBluetoothPermissions(grantedMap)) {
|
||||
enableBluetooth.setChecked(true);
|
||||
} else {
|
||||
showDenialDialog(requireActivity(),
|
||||
R.string.permission_bluetooth_title,
|
||||
R.string.permission_bluetooth_denied_body);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,6 +44,7 @@ import org.briarproject.nullsafety.MethodsNotNullByDefault;
|
||||
import org.briarproject.nullsafety.ParametersNotNullByDefault;
|
||||
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import androidx.activity.result.ActivityResultLauncher;
|
||||
@@ -70,6 +71,9 @@ import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.Observer;
|
||||
import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat;
|
||||
|
||||
import static android.Manifest.permission.BLUETOOTH_ADVERTISE;
|
||||
import static android.Manifest.permission.BLUETOOTH_CONNECT;
|
||||
import static android.Manifest.permission.BLUETOOTH_SCAN;
|
||||
import static android.content.Context.KEYGUARD_SERVICE;
|
||||
import static android.content.Intent.CATEGORY_DEFAULT;
|
||||
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
|
||||
@@ -105,6 +109,7 @@ import static androidx.core.content.ContextCompat.getColor;
|
||||
import static androidx.core.content.ContextCompat.getSystemService;
|
||||
import static androidx.core.graphics.drawable.DrawableCompat.setTint;
|
||||
import static androidx.core.view.ViewCompat.LAYOUT_DIRECTION_RTL;
|
||||
import static java.lang.Boolean.TRUE;
|
||||
import static java.util.Objects.requireNonNull;
|
||||
import static java.util.concurrent.TimeUnit.DAYS;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
@@ -346,10 +351,10 @@ public class UiUtils {
|
||||
|
||||
/**
|
||||
* @return true if location is enabled,
|
||||
* or it isn't required due to this being a SDK < 28 device.
|
||||
* or it isn't required due to this being a device with SDK < 28 or >= 31.
|
||||
*/
|
||||
public static boolean isLocationEnabled(Context ctx) {
|
||||
if (SDK_INT >= 28) {
|
||||
if (SDK_INT >= 28 && SDK_INT < 31) {
|
||||
LocationManager lm = ctx.getSystemService(LocationManager.class);
|
||||
return lm.isLocationEnabled();
|
||||
} else {
|
||||
@@ -625,4 +630,21 @@ public class UiUtils {
|
||||
}
|
||||
Toast.makeText(ctx, R.string.error_start_activity, LENGTH_LONG).show();
|
||||
}
|
||||
|
||||
@RequiresApi(31)
|
||||
public static void requestBluetoothPermissions(
|
||||
ActivityResultLauncher<String[]> launcher) {
|
||||
String[] perms = new String[] {BLUETOOTH_ADVERTISE, BLUETOOTH_CONNECT,
|
||||
BLUETOOTH_SCAN};
|
||||
launcher.launch(perms);
|
||||
}
|
||||
|
||||
@RequiresApi(31)
|
||||
public static boolean wasGrantedBluetoothPermissions(
|
||||
@Nullable Map<String, Boolean> grantedMap) {
|
||||
return grantedMap != null &&
|
||||
TRUE.equals(grantedMap.get(BLUETOOTH_ADVERTISE)) &&
|
||||
TRUE.equals(grantedMap.get(BLUETOOTH_CONNECT)) &&
|
||||
TRUE.equals(grantedMap.get(BLUETOOTH_SCAN));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -782,6 +782,10 @@
|
||||
<string name="permission_location_setting_title">Location setting</string>
|
||||
<string name="permission_location_setting_body">Your device\'s location setting must be turned on to find other devices via Bluetooth. Please enable location to continue. You can disable it again afterwards.</string>
|
||||
<string name="permission_location_setting_button">Enable location</string>
|
||||
<string name="permission_bluetooth_title">Nearby devices permission</string>
|
||||
<string name="permission_bluetooth_body">To use Bluetooth communication, Briar needs permission to find and connect to nearby devices.</string>
|
||||
<string name="permission_bluetooth_denied_body">You have denied access to nearby devices, but Briar needs this permission to use Bluetooth.\n\nPlease consider granting access.</string>
|
||||
|
||||
<string name="qr_code">QR code</string>
|
||||
<string name="show_qr_code_fullscreen">Show QR code fullscreen</string>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user