diff --git a/bramble-android/src/main/java/org/briarproject/bramble/plugin/bluetooth/AndroidBluetoothPlugin.java b/bramble-android/src/main/java/org/briarproject/bramble/plugin/bluetooth/AndroidBluetoothPlugin.java index 1a90232206824a410f3cb0c032b0cc316e6c519b..2c318a6493722db272b75afa698321e128c694c7 100644 --- a/bramble-android/src/main/java/org/briarproject/bramble/plugin/bluetooth/AndroidBluetoothPlugin.java +++ b/bramble-android/src/main/java/org/briarproject/bramble/plugin/bluetooth/AndroidBluetoothPlugin.java @@ -120,6 +120,11 @@ class AndroidBluetoothPlugin extends BluetoothPlugin<BluetoothServerSocket> { } } + @Override + void setEnabledByUs() { + wasEnabledByUs = true; + } + @Override @Nullable String getBluetoothAddress() { diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/event/BluetoothEnabledEvent.java b/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/event/BluetoothEnabledEvent.java new file mode 100644 index 0000000000000000000000000000000000000000..41fa2bdbe16de26720b284a2152bf025f954b9a7 --- /dev/null +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/event/BluetoothEnabledEvent.java @@ -0,0 +1,15 @@ +package org.briarproject.bramble.api.plugin.event; + +import org.briarproject.bramble.api.event.Event; +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; + +import jdk.nashorn.internal.ir.annotations.Immutable; + +/** + * An event that informs the Bluetooth plugin that we have enabled the + * Bluetooth adapter. + */ +@Immutable +@NotNullByDefault +public class BluetoothEnabledEvent extends Event { +} diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/event/EnableBluetoothEvent.java b/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/event/EnableBluetoothEvent.java index 148e55e6e3f2ddf57b40c8aa2f66b2a7bbf82e7c..659b5a16b649cf64a15f9fd48a505ae40748e9a4 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/event/EnableBluetoothEvent.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/event/EnableBluetoothEvent.java @@ -6,7 +6,7 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import javax.annotation.concurrent.Immutable; /** - * An event asks the Bluetooth plugin to enable the Bluetooth adapter. + * An event that asks the Bluetooth plugin to enable the Bluetooth adapter. */ @Immutable @NotNullByDefault diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/bluetooth/BluetoothPlugin.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/bluetooth/BluetoothPlugin.java index bafd0649fcc092b3bc39398d1210e92d4bd708b2..4f7afa0349d3f4f6fa97cecce57f1c5614f97af1 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/plugin/bluetooth/BluetoothPlugin.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/bluetooth/BluetoothPlugin.java @@ -15,6 +15,7 @@ import org.briarproject.bramble.api.plugin.TransportId; import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin; import org.briarproject.bramble.api.plugin.duplex.DuplexPluginCallback; import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection; +import org.briarproject.bramble.api.plugin.event.BluetoothEnabledEvent; import org.briarproject.bramble.api.plugin.event.DisableBluetoothEvent; import org.briarproject.bramble.api.plugin.event.EnableBluetoothEvent; import org.briarproject.bramble.api.properties.TransportProperties; @@ -51,8 +52,7 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener { private static final Logger LOG = Logger.getLogger(BluetoothPlugin.class.getName()); - final Executor ioExecutor; - + private final Executor ioExecutor; private final SecureRandom secureRandom; private final Backoff backoff; private final DuplexPluginCallback callback; @@ -70,6 +70,8 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener { abstract void disableAdapterIfEnabledByUs(); + abstract void setEnabledByUs(); + @Nullable abstract String getBluetoothAddress(); @@ -358,6 +360,8 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener { ioExecutor.execute(this::enableAdapter); } else if (e instanceof DisableBluetoothEvent) { ioExecutor.execute(this::disableAdapterIfEnabledByUs); + } else if (e instanceof BluetoothEnabledEvent) { + setEnabledByUs(); } else if (e instanceof SettingsUpdatedEvent) { SettingsUpdatedEvent s = (SettingsUpdatedEvent) e; if (s.getNamespace().equals(ID.getString())) diff --git a/bramble-j2se/src/main/java/org/briarproject/bramble/plugin/bluetooth/JavaBluetoothPlugin.java b/bramble-j2se/src/main/java/org/briarproject/bramble/plugin/bluetooth/JavaBluetoothPlugin.java index a6086f4c3e0c51bc0405364a6d795342661d5912..487603e2da837b107711c2c5dd02a3a20eea3ba7 100644 --- a/bramble-j2se/src/main/java/org/briarproject/bramble/plugin/bluetooth/JavaBluetoothPlugin.java +++ b/bramble-j2se/src/main/java/org/briarproject/bramble/plugin/bluetooth/JavaBluetoothPlugin.java @@ -61,6 +61,11 @@ class JavaBluetoothPlugin extends BluetoothPlugin<StreamConnectionNotifier> { // We didn't enable it so we don't need to disable it } + @Override + void setEnabledByUs() { + // Irrelevant on this platform + } + @Nullable @Override String getBluetoothAddress() { diff --git a/briar-android/src/main/java/org/briarproject/briar/android/activity/RequestCodes.java b/briar-android/src/main/java/org/briarproject/briar/android/activity/RequestCodes.java index fe771c430c0877a6c6f06ab81bcac7d8e70d3e92..4b8caa3d2004eef63ac34810d9aa0157b291e515 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/activity/RequestCodes.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/activity/RequestCodes.java @@ -11,5 +11,6 @@ public interface RequestCodes { int REQUEST_RINGTONE = 7; int REQUEST_PERMISSION_CAMERA = 8; int REQUEST_DOZE_WHITELISTING = 9; + int REQUEST_ENABLE_BLUETOOTH = 10; } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/KeyAgreementActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/KeyAgreementActivity.java index 040312127b7f850a007d6dd82fc6f27e632a8997..c960ffe9bf06430c71b829f3b626adab5dfbb6f6 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/KeyAgreementActivity.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/KeyAgreementActivity.java @@ -1,6 +1,11 @@ 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; import android.support.annotation.UiThread; import android.support.v4.app.ActivityCompat; @@ -23,6 +28,7 @@ import org.briarproject.bramble.api.keyagreement.KeyAgreementResult; import org.briarproject.bramble.api.keyagreement.event.KeyAgreementFinishedEvent; 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; @@ -39,9 +45,15 @@ import javax.annotation.Nullable; import javax.inject.Inject; import static android.Manifest.permission.CAMERA; +import static android.bluetooth.BluetoothAdapter.ACTION_REQUEST_ENABLE; +import static android.bluetooth.BluetoothAdapter.ACTION_STATE_CHANGED; +import static android.bluetooth.BluetoothAdapter.EXTRA_STATE; +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 java.util.logging.Level.WARNING; +import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_ENABLE_BLUETOOTH; import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_PERMISSION_CAMERA; @MethodsNotNullByDefault @@ -50,6 +62,10 @@ public class KeyAgreementActivity extends BriarActivity implements BaseFragmentListener, IntroScreenSeenListener, EventListener, ContactExchangeListener { + private enum BluetoothState { + UNKNOWN, NO_ADAPTER, WAITING, REFUSED, ENABLED + } + private static final Logger LOG = Logger.getLogger(KeyAgreementActivity.class.getName()); @@ -62,7 +78,10 @@ public class KeyAgreementActivity extends BriarActivity implements @Inject volatile IdentityManager identityManager; + private boolean isResumed = false, enableWasRequested = false; private boolean continueClicked, gotCameraPermission; + private BluetoothState bluetoothState = BluetoothState.UNKNOWN; + private BroadcastReceiver bluetoothReceiver = null; @Override public void injectActivity(ActivityComponent component) { @@ -84,6 +103,15 @@ public class KeyAgreementActivity extends BriarActivity implements if (state == null) { showInitialFragment(IntroFragment.newInstance()); } + IntentFilter filter = new IntentFilter(ACTION_STATE_CHANGED); + bluetoothReceiver = new BluetoothStateReceiver(); + registerReceiver(bluetoothReceiver, filter); + } + + @Override + public void onDestroy() { + super.onDestroy(); + if (bluetoothReceiver != null) unregisterReceiver(bluetoothReceiver); } @Override @@ -112,24 +140,72 @@ public class KeyAgreementActivity extends BriarActivity implements @Override protected void onPostResume() { super.onPostResume(); + isResumed = true; // Workaround for // https://code.google.com/p/android/issues/detail?id=190966 - if (continueClicked && gotCameraPermission) { - showQrCodeFragment(); - } + if (canShowQrCodeFragment()) showQrCodeFragment(); + } + + boolean canShowQrCodeFragment() { + return isResumed && continueClicked + && (SDK_INT < 23 || gotCameraPermission) + && bluetoothState != BluetoothState.UNKNOWN + && bluetoothState != BluetoothState.WAITING; + } + + @Override + protected void onPause() { + super.onPause(); + isResumed = false; } @Override public void showNextScreen() { - // FIXME #824 - // showNextFragment(ShowQrCodeFragment.newInstance()); continueClicked = true; if (checkPermissions()) { - showQrCodeFragment(); + if (shouldRequestEnableBluetooth()) requestEnableBluetooth(); + else if (canShowQrCodeFragment()) showQrCodeFragment(); } } + private boolean shouldRequestEnableBluetooth() { + return bluetoothState == BluetoothState.UNKNOWN + || bluetoothState == BluetoothState.REFUSED; + } + + private void requestEnableBluetooth() { + 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); + } + } + + private void setBluetoothState(BluetoothState bluetoothState) { + LOG.info("Setting Bluetooth state to " + bluetoothState); + this.bluetoothState = bluetoothState; + if (enableWasRequested && bluetoothState == BluetoothState.ENABLED) { + eventBus.broadcast(new BluetoothEnabledEvent()); + enableWasRequested = false; + } + if (canShowQrCodeFragment()) showQrCodeFragment(); + } + + @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); + } + private void showQrCodeFragment() { + // FIXME #824 BaseFragment f = ShowQrCodeFragment.newInstance(); getSupportFragmentManager().beginTransaction() .replace(R.id.fragmentContainer, f, f.getUniqueTag()) @@ -154,8 +230,10 @@ public class KeyAgreementActivity extends BriarActivity implements } else { requestPermission(); } + gotCameraPermission = false; return false; } else { + gotCameraPermission = true; return true; } } @@ -174,6 +252,7 @@ public class KeyAgreementActivity extends BriarActivity implements if (grantResults.length > 0 && grantResults[0] == PERMISSION_GRANTED) { gotCameraPermission = true; + showNextScreen(); } else { if (!ActivityCompat.shouldShowRequestPermissionRationale(this, CAMERA)) { @@ -258,4 +337,14 @@ public class KeyAgreementActivity extends BriarActivity implements finish(); }); } + + 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); + } + } } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/ShowQrCodeFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/ShowQrCodeFragment.java index fd0d46445404da75628165430afafab21f88aec5..1ef7dcd2302956920d6f81f6cc6ce1b98d4af319 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/ShowQrCodeFragment.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/ShowQrCodeFragment.java @@ -1,10 +1,6 @@ package org.briarproject.briar.android.keyagreement; -import android.bluetooth.BluetoothAdapter; -import android.content.BroadcastReceiver; import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; import android.graphics.Bitmap; import android.os.AsyncTask; import android.os.Bundle; @@ -36,7 +32,6 @@ import org.briarproject.bramble.api.keyagreement.event.KeyAgreementWaitingEvent; import org.briarproject.bramble.api.lifecycle.IoExecutor; import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; -import org.briarproject.bramble.api.plugin.event.EnableBluetoothEvent; import org.briarproject.briar.R; import org.briarproject.briar.android.activity.ActivityComponent; import org.briarproject.briar.android.fragment.BaseEventFragment; @@ -49,9 +44,6 @@ import javax.annotation.Nullable; import javax.inject.Inject; import javax.inject.Provider; -import static android.bluetooth.BluetoothAdapter.ACTION_STATE_CHANGED; -import static android.bluetooth.BluetoothAdapter.EXTRA_STATE; -import static android.bluetooth.BluetoothAdapter.STATE_ON; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_NOSENSOR; import static android.view.View.INVISIBLE; import static android.view.View.VISIBLE; @@ -86,8 +78,7 @@ public class ShowQrCodeFragment extends BaseEventFragment private TextView mainProgressTitle; private ViewGroup mainProgressContainer; - private BluetoothStateReceiver receiver; - private boolean gotRemotePayload, waitingForBluetooth; + private boolean gotRemotePayload; private volatile boolean gotLocalPayload; private KeyAgreementTask task; @@ -148,27 +139,13 @@ public class ShowQrCodeFragment extends BaseEventFragment } catch (CameraException e) { logCameraExceptionAndFinish(e); } - // Listen for changes to the Bluetooth state - IntentFilter filter = new IntentFilter(); - filter.addAction(ACTION_STATE_CHANGED); - receiver = new BluetoothStateReceiver(); - getActivity().registerReceiver(receiver, filter); - - // Enable BT adapter if it is not already on. - BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); - if (adapter != null && !adapter.isEnabled()) { - waitingForBluetooth = true; - eventBus.broadcast(new EnableBluetoothEvent()); - } else { - startListening(); - } + startListening(); } @Override public void onStop() { super.onStop(); stopListening(); - if (receiver != null) getActivity().unregisterReceiver(receiver); try { cameraView.stop(); } catch (CameraException e) { @@ -340,16 +317,4 @@ public class ShowQrCodeFragment extends BaseEventFragment protected void finish() { getActivity().getSupportFragmentManager().popBackStack(); } - - private class BluetoothStateReceiver extends BroadcastReceiver { - @UiThread - @Override - public void onReceive(Context ctx, Intent intent) { - int state = intent.getIntExtra(EXTRA_STATE, 0); - if (state == STATE_ON && waitingForBluetooth) { - waitingForBluetooth = false; - startListening(); - } - } - } }