mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-13 19:29:06 +01:00
Merge branch '1147-bluetooth-discovery' into 'master'
Support Bluetooth discovery for adding contacts See merge request briar/briar!954
This commit is contained in:
@@ -107,7 +107,7 @@ public class AppModule {
|
||||
Context appContext = app.getApplicationContext();
|
||||
DuplexPluginFactory bluetooth =
|
||||
new AndroidBluetoothPluginFactory(ioExecutor, androidExecutor,
|
||||
appContext, random, eventBus, backoffFactory);
|
||||
appContext, random, eventBus, clock, backoffFactory);
|
||||
DuplexPluginFactory tor = new AndroidTorPluginFactory(ioExecutor,
|
||||
scheduler, appContext, networkManager, locationUtils, eventBus,
|
||||
torSocketFactory, backoffFactory, resourceProvider,
|
||||
|
||||
@@ -9,9 +9,9 @@ public interface RequestCodes {
|
||||
int REQUEST_WRITE_BLOG_POST = 5;
|
||||
int REQUEST_SHARE_BLOG = 6;
|
||||
int REQUEST_RINGTONE = 7;
|
||||
int REQUEST_PERMISSION_CAMERA = 8;
|
||||
int REQUEST_PERMISSION_CAMERA_LOCATION = 8;
|
||||
int REQUEST_DOZE_WHITELISTING = 9;
|
||||
int REQUEST_ENABLE_BLUETOOTH = 10;
|
||||
int REQUEST_BLUETOOTH_DISCOVERABLE = 10;
|
||||
int REQUEST_UNLOCK = 11;
|
||||
int REQUEST_KEYGUARD_UNLOCK = 12;
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ package org.briarproject.briar.android.keyagreement;
|
||||
import android.bluetooth.BluetoothAdapter;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface.OnClickListener;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.os.Bundle;
|
||||
@@ -11,19 +10,15 @@ import android.support.annotation.StringRes;
|
||||
import android.support.annotation.UiThread;
|
||||
import android.support.v4.app.ActivityCompat;
|
||||
import android.support.v4.app.FragmentManager;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.support.v7.app.AlertDialog.Builder;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import android.view.MenuItem;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.briarproject.bramble.api.event.EventBus;
|
||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.event.BluetoothEnabledEvent;
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.R.string;
|
||||
import org.briarproject.briar.R.style;
|
||||
import org.briarproject.briar.android.activity.ActivityComponent;
|
||||
import org.briarproject.briar.android.activity.BriarActivity;
|
||||
import org.briarproject.briar.android.fragment.BaseFragment;
|
||||
@@ -37,16 +32,19 @@ import java.util.logging.Logger;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static android.Manifest.permission.ACCESS_COARSE_LOCATION;
|
||||
import static android.Manifest.permission.CAMERA;
|
||||
import static android.bluetooth.BluetoothAdapter.ACTION_REQUEST_ENABLE;
|
||||
import static android.bluetooth.BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE;
|
||||
import static android.bluetooth.BluetoothAdapter.ACTION_SCAN_MODE_CHANGED;
|
||||
import static android.bluetooth.BluetoothAdapter.ACTION_STATE_CHANGED;
|
||||
import static android.bluetooth.BluetoothAdapter.EXTRA_SCAN_MODE;
|
||||
import static android.bluetooth.BluetoothAdapter.EXTRA_STATE;
|
||||
import static android.bluetooth.BluetoothAdapter.SCAN_MODE_CONNECTABLE;
|
||||
import static android.bluetooth.BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE;
|
||||
import static android.bluetooth.BluetoothAdapter.STATE_ON;
|
||||
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
|
||||
import static android.os.Build.VERSION.SDK_INT;
|
||||
import static android.widget.Toast.LENGTH_LONG;
|
||||
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_ENABLE_BLUETOOTH;
|
||||
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_PERMISSION_CAMERA;
|
||||
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_BLUETOOTH_DISCOVERABLE;
|
||||
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_PERMISSION_CAMERA_LOCATION;
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
@@ -55,7 +53,11 @@ public abstract class KeyAgreementActivity extends BriarActivity implements
|
||||
KeyAgreementEventListener {
|
||||
|
||||
private enum BluetoothState {
|
||||
UNKNOWN, NO_ADAPTER, WAITING, REFUSED, ENABLED
|
||||
UNKNOWN, NO_ADAPTER, WAITING, REFUSED, ENABLED, DISCOVERABLE
|
||||
}
|
||||
|
||||
private enum Permission {
|
||||
UNKNOWN, GRANTED, SHOW_RATIONALE, PERMANENTLY_DENIED
|
||||
}
|
||||
|
||||
private static final Logger LOG =
|
||||
@@ -64,8 +66,27 @@ public abstract class KeyAgreementActivity extends BriarActivity implements
|
||||
@Inject
|
||||
EventBus eventBus;
|
||||
|
||||
private boolean isResumed = false, enableWasRequested = false;
|
||||
private boolean continueClicked, gotCameraPermission;
|
||||
/**
|
||||
* Set to true in onPostResume() and false in onPause(). This prevents the
|
||||
* QR code fragment from being shown if onRequestPermissionsResult() is
|
||||
* called while the activity is paused, which could cause a crash due to
|
||||
* https://issuetracker.google.com/issues/37067655.
|
||||
*/
|
||||
private boolean isResumed = false;
|
||||
/**
|
||||
* Set to true when the continue button is clicked, and false when the QR
|
||||
* code fragment is shown. This prevents the QR code fragment from being
|
||||
* shown automatically before the continue button has been clicked.
|
||||
*/
|
||||
private boolean continueClicked = false;
|
||||
/**
|
||||
* Records whether the Bluetooth adapter was already enabled before we
|
||||
* asked for Bluetooth discoverability, so we know whether to broadcast a
|
||||
* {@link BluetoothEnabledEvent}.
|
||||
*/
|
||||
private boolean wasAdapterEnabled = false;
|
||||
private Permission cameraPermission = Permission.UNKNOWN;
|
||||
private Permission locationPermission = Permission.UNKNOWN;
|
||||
private BluetoothState bluetoothState = BluetoothState.UNKNOWN;
|
||||
private BroadcastReceiver bluetoothReceiver = null;
|
||||
|
||||
@@ -85,7 +106,9 @@ public abstract class KeyAgreementActivity extends BriarActivity implements
|
||||
if (state == null) {
|
||||
showInitialFragment(IntroFragment.newInstance());
|
||||
}
|
||||
IntentFilter filter = new IntentFilter(ACTION_STATE_CHANGED);
|
||||
IntentFilter filter = new IntentFilter();
|
||||
filter.addAction(ACTION_STATE_CHANGED);
|
||||
filter.addAction(ACTION_SCAN_MODE_CHANGED);
|
||||
bluetoothReceiver = new BluetoothStateReceiver();
|
||||
registerReceiver(bluetoothReceiver, filter);
|
||||
}
|
||||
@@ -107,20 +130,40 @@ public abstract class KeyAgreementActivity extends BriarActivity implements
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
// Permissions may have been granted manually while we were stopped
|
||||
cameraPermission = Permission.UNKNOWN;
|
||||
locationPermission = Permission.UNKNOWN;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostResume() {
|
||||
super.onPostResume();
|
||||
isResumed = true;
|
||||
// Workaround for
|
||||
// https://code.google.com/p/android/issues/detail?id=190966
|
||||
if (canShowQrCodeFragment()) showQrCodeFragment();
|
||||
showQrCodeFragmentIfAllowed();
|
||||
}
|
||||
|
||||
private boolean canShowQrCodeFragment() {
|
||||
return isResumed && continueClicked
|
||||
&& (SDK_INT < 23 || gotCameraPermission)
|
||||
&& bluetoothState != BluetoothState.UNKNOWN
|
||||
&& bluetoothState != BluetoothState.WAITING;
|
||||
private void showQrCodeFragmentIfAllowed() {
|
||||
if (isResumed && continueClicked && areEssentialPermissionsGranted()) {
|
||||
if (bluetoothState == BluetoothState.UNKNOWN ||
|
||||
bluetoothState == BluetoothState.ENABLED) {
|
||||
requestBluetoothDiscoverable();
|
||||
} else if (bluetoothState != BluetoothState.WAITING) {
|
||||
showQrCodeFragment();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean areEssentialPermissionsGranted() {
|
||||
// If the camera permission has been granted, and the location
|
||||
// permission has been granted or permanently denied, we can continue
|
||||
return cameraPermission == Permission.GRANTED &&
|
||||
(locationPermission == Permission.GRANTED ||
|
||||
locationPermission == Permission.PERMANENTLY_DENIED);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -132,50 +175,54 @@ public abstract class KeyAgreementActivity extends BriarActivity implements
|
||||
@Override
|
||||
public void showNextScreen() {
|
||||
continueClicked = true;
|
||||
if (checkPermissions()) {
|
||||
if (shouldRequestEnableBluetooth()) requestEnableBluetooth();
|
||||
else if (canShowQrCodeFragment()) showQrCodeFragment();
|
||||
}
|
||||
if (checkPermissions()) showQrCodeFragmentIfAllowed();
|
||||
}
|
||||
|
||||
private boolean shouldRequestEnableBluetooth() {
|
||||
return bluetoothState == BluetoothState.UNKNOWN
|
||||
|| bluetoothState == BluetoothState.REFUSED;
|
||||
}
|
||||
|
||||
private void requestEnableBluetooth() {
|
||||
private void requestBluetoothDiscoverable() {
|
||||
BluetoothAdapter bt = BluetoothAdapter.getDefaultAdapter();
|
||||
if (bt == null) {
|
||||
setBluetoothState(BluetoothState.NO_ADAPTER);
|
||||
} else if (bt.isEnabled()) {
|
||||
setBluetoothState(BluetoothState.ENABLED);
|
||||
} else {
|
||||
enableWasRequested = true;
|
||||
setBluetoothState(BluetoothState.WAITING);
|
||||
Intent i = new Intent(ACTION_REQUEST_ENABLE);
|
||||
startActivityForResult(i, REQUEST_ENABLE_BLUETOOTH);
|
||||
wasAdapterEnabled = bt.isEnabled();
|
||||
Intent i = new Intent(ACTION_REQUEST_DISCOVERABLE);
|
||||
startActivityForResult(i, REQUEST_BLUETOOTH_DISCOVERABLE);
|
||||
}
|
||||
}
|
||||
|
||||
private void setBluetoothState(BluetoothState bluetoothState) {
|
||||
LOG.info("Setting Bluetooth state to " + bluetoothState);
|
||||
this.bluetoothState = bluetoothState;
|
||||
if (enableWasRequested && bluetoothState == BluetoothState.ENABLED) {
|
||||
if (!wasAdapterEnabled && bluetoothState == BluetoothState.ENABLED) {
|
||||
eventBus.broadcast(new BluetoothEnabledEvent());
|
||||
enableWasRequested = false;
|
||||
wasAdapterEnabled = true;
|
||||
}
|
||||
if (canShowQrCodeFragment()) showQrCodeFragment();
|
||||
showQrCodeFragmentIfAllowed();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int request, int result, Intent data) {
|
||||
// If the request was granted we'll catch the state change event
|
||||
if (request == REQUEST_ENABLE_BLUETOOTH && result == RESULT_CANCELED)
|
||||
setBluetoothState(BluetoothState.REFUSED);
|
||||
if (request == REQUEST_BLUETOOTH_DISCOVERABLE) {
|
||||
if (result == RESULT_CANCELED) {
|
||||
setBluetoothState(BluetoothState.REFUSED);
|
||||
} else {
|
||||
// If Bluetooth is already discoverable, show the QR code -
|
||||
// otherwise wait for the state or scan mode to change
|
||||
BluetoothAdapter bt = BluetoothAdapter.getDefaultAdapter();
|
||||
if (bt == null) throw new AssertionError();
|
||||
if (bt.getScanMode() == SCAN_MODE_CONNECTABLE_DISCOVERABLE)
|
||||
setBluetoothState(BluetoothState.DISCOVERABLE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void showQrCodeFragment() {
|
||||
// If we return to the intro fragment, the continue button needs to be
|
||||
// clicked again before showing the QR code fragment
|
||||
continueClicked = false;
|
||||
// If we return to the intro fragment, ask for Bluetooth
|
||||
// discoverability again before showing the QR code fragment
|
||||
bluetoothState = BluetoothState.UNKNOWN;
|
||||
// FIXME #824
|
||||
FragmentManager fm = getSupportFragmentManager();
|
||||
if (fm.findFragmentByTag(KeyAgreementFragment.TAG) == null) {
|
||||
@@ -194,74 +241,113 @@ public abstract class KeyAgreementActivity extends BriarActivity implements
|
||||
}
|
||||
|
||||
private boolean checkPermissions() {
|
||||
if (ContextCompat.checkSelfPermission(this, CAMERA) !=
|
||||
PERMISSION_GRANTED) {
|
||||
// Should we show an explanation?
|
||||
if (ActivityCompat.shouldShowRequestPermissionRationale(this,
|
||||
CAMERA)) {
|
||||
OnClickListener continueListener =
|
||||
(dialog, which) -> requestPermission();
|
||||
Builder builder = new Builder(this, style.BriarDialogTheme);
|
||||
builder.setTitle(string.permission_camera_title);
|
||||
builder.setMessage(string.permission_camera_request_body);
|
||||
builder.setNeutralButton(string.continue_button,
|
||||
continueListener);
|
||||
builder.show();
|
||||
} else {
|
||||
requestPermission();
|
||||
}
|
||||
gotCameraPermission = false;
|
||||
if (areEssentialPermissionsGranted()) return true;
|
||||
// If the camera permission has been permanently denied, ask the
|
||||
// user to change the setting
|
||||
if (cameraPermission == Permission.PERMANENTLY_DENIED) {
|
||||
Builder builder = new Builder(this, R.style.BriarDialogTheme);
|
||||
builder.setTitle(R.string.permission_camera_title);
|
||||
builder.setMessage(R.string.permission_camera_denied_body);
|
||||
builder.setPositiveButton(R.string.ok,
|
||||
UiUtils.getGoToSettingsListener(this));
|
||||
builder.setNegativeButton(R.string.cancel,
|
||||
(dialog, which) -> supportFinishAfterTransition());
|
||||
builder.show();
|
||||
return false;
|
||||
} else {
|
||||
gotCameraPermission = true;
|
||||
return true;
|
||||
}
|
||||
// Should we show the rationale for one or both permissions?
|
||||
if (cameraPermission == Permission.SHOW_RATIONALE &&
|
||||
locationPermission == Permission.SHOW_RATIONALE) {
|
||||
showRationale(R.string.permission_camera_location_title,
|
||||
R.string.permission_camera_location_request_body);
|
||||
} else if (cameraPermission == Permission.SHOW_RATIONALE) {
|
||||
showRationale(R.string.permission_camera_title,
|
||||
R.string.permission_camera_request_body);
|
||||
} else if (locationPermission == Permission.SHOW_RATIONALE) {
|
||||
showRationale(R.string.permission_location_title,
|
||||
R.string.permission_location_request_body);
|
||||
} else {
|
||||
requestPermissions();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void requestPermission() {
|
||||
ActivityCompat.requestPermissions(this, new String[] {CAMERA},
|
||||
REQUEST_PERMISSION_CAMERA);
|
||||
private void showRationale(@StringRes int title, @StringRes int body) {
|
||||
Builder builder = new Builder(this, R.style.BriarDialogTheme);
|
||||
builder.setTitle(title);
|
||||
builder.setMessage(body);
|
||||
builder.setNeutralButton(R.string.continue_button,
|
||||
(dialog, which) -> requestPermissions());
|
||||
builder.show();
|
||||
}
|
||||
|
||||
private void requestPermissions() {
|
||||
ActivityCompat.requestPermissions(this,
|
||||
new String[] {CAMERA, ACCESS_COARSE_LOCATION},
|
||||
REQUEST_PERMISSION_CAMERA_LOCATION);
|
||||
}
|
||||
|
||||
@Override
|
||||
@UiThread
|
||||
public void onRequestPermissionsResult(int requestCode,
|
||||
String permissions[], int[] grantResults) {
|
||||
if (requestCode == REQUEST_PERMISSION_CAMERA) {
|
||||
// If request is cancelled, the result arrays are empty.
|
||||
if (grantResults.length > 0 &&
|
||||
grantResults[0] == PERMISSION_GRANTED) {
|
||||
gotCameraPermission = true;
|
||||
showNextScreen();
|
||||
} else {
|
||||
if (!ActivityCompat.shouldShowRequestPermissionRationale(this,
|
||||
CAMERA)) {
|
||||
// The user has permanently denied the request
|
||||
OnClickListener cancelListener =
|
||||
(dialog, which) -> supportFinishAfterTransition();
|
||||
Builder builder = new Builder(this, style.BriarDialogTheme);
|
||||
builder.setTitle(string.permission_camera_title);
|
||||
builder.setMessage(string.permission_camera_denied_body);
|
||||
builder.setPositiveButton(string.ok,
|
||||
UiUtils.getGoToSettingsListener(this));
|
||||
builder.setNegativeButton(string.cancel, cancelListener);
|
||||
builder.show();
|
||||
} else {
|
||||
Toast.makeText(this, string.permission_camera_denied_toast,
|
||||
LENGTH_LONG).show();
|
||||
supportFinishAfterTransition();
|
||||
}
|
||||
}
|
||||
String[] permissions, int[] grantResults) {
|
||||
if (requestCode != REQUEST_PERMISSION_CAMERA_LOCATION)
|
||||
throw new AssertionError();
|
||||
if (gotPermission(CAMERA, permissions, grantResults)) {
|
||||
cameraPermission = Permission.GRANTED;
|
||||
} else if (shouldShowRationale(CAMERA)) {
|
||||
cameraPermission = Permission.SHOW_RATIONALE;
|
||||
} else {
|
||||
cameraPermission = Permission.PERMANENTLY_DENIED;
|
||||
}
|
||||
if (gotPermission(ACCESS_COARSE_LOCATION, permissions, grantResults)) {
|
||||
locationPermission = Permission.GRANTED;
|
||||
} else if (shouldShowRationale(ACCESS_COARSE_LOCATION)) {
|
||||
locationPermission = Permission.SHOW_RATIONALE;
|
||||
} else {
|
||||
locationPermission = Permission.PERMANENTLY_DENIED;
|
||||
}
|
||||
// If a permission dialog has been shown, showing the QR code fragment
|
||||
// on this call path would cause a crash due to
|
||||
// https://code.google.com/p/android/issues/detail?id=190966.
|
||||
// In that case the isResumed flag prevents the fragment from being
|
||||
// shown here, and showQrCodeFragmentIfAllowed() will be called again
|
||||
// from onPostResume().
|
||||
if (checkPermissions()) showQrCodeFragmentIfAllowed();
|
||||
}
|
||||
|
||||
private boolean gotPermission(String permission, String[] permissions,
|
||||
int[] grantResults) {
|
||||
for (int i = 0; i < permissions.length; i++) {
|
||||
if (permission.equals(permissions[i]))
|
||||
return grantResults[i] == PERMISSION_GRANTED;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean shouldShowRationale(String permission) {
|
||||
return ActivityCompat.shouldShowRequestPermissionRationale(this,
|
||||
permission);
|
||||
}
|
||||
|
||||
private class BluetoothStateReceiver extends BroadcastReceiver {
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
int state = intent.getIntExtra(EXTRA_STATE, 0);
|
||||
if (state == STATE_ON) setBluetoothState(BluetoothState.ENABLED);
|
||||
else setBluetoothState(BluetoothState.UNKNOWN);
|
||||
String action = intent.getAction();
|
||||
if (ACTION_STATE_CHANGED.equals(action)) {
|
||||
int state = intent.getIntExtra(EXTRA_STATE, 0);
|
||||
if (state == STATE_ON)
|
||||
setBluetoothState(BluetoothState.ENABLED);
|
||||
else setBluetoothState(BluetoothState.UNKNOWN);
|
||||
} else if (ACTION_SCAN_MODE_CHANGED.equals(action)) {
|
||||
int scanMode = intent.getIntExtra(EXTRA_SCAN_MODE, 0);
|
||||
if (scanMode == SCAN_MODE_CONNECTABLE_DISCOVERABLE)
|
||||
setBluetoothState(BluetoothState.DISCOVERABLE);
|
||||
else if (scanMode == SCAN_MODE_CONNECTABLE)
|
||||
setBluetoothState(BluetoothState.ENABLED);
|
||||
else setBluetoothState(BluetoothState.UNKNOWN);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user