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