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 67e125020..4f787598f 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 @@ -8,10 +8,20 @@ import android.content.IntentFilter; import android.os.Bundle; import android.view.MenuItem; +import org.briarproject.bramble.api.db.DbException; +import org.briarproject.bramble.api.event.Event; import org.briarproject.bramble.api.event.EventBus; +import org.briarproject.bramble.api.event.EventListener; +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.BluetoothConstants; +import org.briarproject.bramble.api.plugin.Plugin; +import org.briarproject.bramble.api.plugin.PluginManager; import org.briarproject.bramble.api.plugin.event.BluetoothEnabledEvent; +import org.briarproject.bramble.api.plugin.event.TransportActiveEvent; +import org.briarproject.bramble.api.settings.Settings; +import org.briarproject.bramble.api.settings.SettingsManager; import org.briarproject.briar.R; import org.briarproject.briar.android.activity.ActivityComponent; import org.briarproject.briar.android.activity.BriarActivity; @@ -21,6 +31,7 @@ import org.briarproject.briar.android.keyagreement.IntroFragment.IntroScreenSeen import org.briarproject.briar.android.keyagreement.KeyAgreementFragment.KeyAgreementEventListener; import org.briarproject.briar.android.util.UiUtils; +import java.util.concurrent.Executor; import java.util.logging.Logger; import javax.annotation.Nullable; @@ -37,15 +48,15 @@ import static android.Manifest.permission.ACCESS_COARSE_LOCATION; import static android.Manifest.permission.CAMERA; 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 java.util.logging.Level.WARNING; import static java.util.logging.Logger.getLogger; import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull; +import static org.briarproject.bramble.api.plugin.BluetoothConstants.PREF_BT_ENABLE; +import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE; +import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_BLUETOOTH_DISCOVERABLE; import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_PERMISSION_CAMERA_LOCATION; @@ -53,10 +64,41 @@ import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_PERMI @ParametersNotNullByDefault public abstract class KeyAgreementActivity extends BriarActivity implements BaseFragmentListener, IntroScreenSeenListener, - KeyAgreementEventListener { + KeyAgreementEventListener, EventListener { private enum BluetoothState { - UNKNOWN, NO_ADAPTER, WAITING, REFUSED, ENABLED, DISCOVERABLE + /** + * We don't yet know the state of the Bluetooth adapter or plugin. + */ + UNKNOWN, + + /** + * The device doesn't have a Bluetooth adapter. + */ + NO_ADAPTER, + + /** + * We're waiting for the user to decide whether to allow + * discoverability, or the user has allowed discoverability and we're + * waiting for the adapter to become discoverable. + */ + WAITING, + + /** + * The user has refused discoverability. + */ + REFUSED, + + /** + * The Bluetooth adapter is discoverable. + */ + DISCOVERABLE, + + /** + * The Bluetooth adapter is discoverable and we're waiting for the + * Bluetooth plugin to become active. + */ + ACTIVATING_PLUGIN } private enum Permission { @@ -69,6 +111,16 @@ public abstract class KeyAgreementActivity extends BriarActivity implements @Inject EventBus eventBus; + @Inject + PluginManager pluginManager; + + @Inject + @IoExecutor + Executor ioExecutor; + + @Inject + SettingsManager settingsManager; + /** * Set to true in onPostResume() and false in onPause(). This prevents the * QR code fragment from being shown if onRequestPermissionsResult() is @@ -91,6 +143,13 @@ public abstract class KeyAgreementActivity extends BriarActivity implements */ private boolean wasAdapterEnabled = false; + /** + * Records whether the user accepted the most recent request (if any) to + * make the Bluetooth adapter discoverable, so we know whether to broadcast + * a {@link BluetoothEnabledEvent}. + */ + private boolean wasRequestAccepted = false; + private Permission cameraPermission = Permission.UNKNOWN; private Permission locationPermission = Permission.UNKNOWN; private BluetoothState bluetoothState = BluetoothState.UNKNOWN; @@ -111,9 +170,7 @@ public abstract class KeyAgreementActivity extends BriarActivity implements if (state == null) { showInitialFragment(IntroFragment.newInstance()); } - IntentFilter filter = new IntentFilter(); - filter.addAction(ACTION_STATE_CHANGED); - filter.addAction(ACTION_SCAN_MODE_CHANGED); + IntentFilter filter = new IntentFilter(ACTION_SCAN_MODE_CHANGED); bluetoothReceiver = new BluetoothStateReceiver(); registerReceiver(bluetoothReceiver, filter); } @@ -136,6 +193,7 @@ public abstract class KeyAgreementActivity extends BriarActivity implements @Override public void onStart() { super.onStart(); + eventBus.addListener(this); // Permissions may have been granted manually while we were stopped cameraPermission = Permission.UNKNOWN; locationPermission = Permission.UNKNOWN; @@ -152,11 +210,22 @@ public abstract class KeyAgreementActivity extends BriarActivity implements private void showQrCodeFragmentIfAllowed() { if (isResumed && continueClicked && areEssentialPermissionsGranted()) { - if (bluetoothState == BluetoothState.UNKNOWN || - bluetoothState == BluetoothState.ENABLED) { + if (bluetoothState == BluetoothState.UNKNOWN) { requestBluetoothDiscoverable(); - } else if (bluetoothState != BluetoothState.WAITING) { + } else if (bluetoothState == BluetoothState.NO_ADAPTER || + bluetoothState == BluetoothState.REFUSED) { + LOG.info("Continuing without Bluetooth"); showQrCodeFragment(); + } else if (bluetoothState == BluetoothState.DISCOVERABLE || + bluetoothState == BluetoothState.ACTIVATING_PLUGIN) { + if (isBluetoothPluginActive()) { + LOG.info("Bluetooth plugin is active"); + showQrCodeFragment(); + } else { + LOG.info("Enabling Bluetooth plugin"); + bluetoothState = BluetoothState.ACTIVATING_PLUGIN; + enableBluetoothPlugin(); + } } } } @@ -169,12 +238,36 @@ public abstract class KeyAgreementActivity extends BriarActivity implements locationPermission == Permission.PERMANENTLY_DENIED); } + private boolean isBluetoothPluginActive() { + Plugin p = pluginManager.getPlugin(BluetoothConstants.ID); + return p != null && p.getState() == ACTIVE; + } + + private void enableBluetoothPlugin() { + ioExecutor.execute(() -> { + try { + Settings s = new Settings(); + s.putBoolean(PREF_BT_ENABLE, true); + String namespace = BluetoothConstants.ID.getString(); + settingsManager.mergeSettings(s, namespace); + } catch (DbException e) { + logException(LOG, WARNING, e); + } + }); + } + @Override protected void onPause() { super.onPause(); isResumed = false; } + @Override + protected void onStop() { + super.onStop(); + eventBus.removeListener(this); + } + @Override public void showNextScreen() { continueClicked = true; @@ -200,8 +293,11 @@ public abstract class KeyAgreementActivity extends BriarActivity implements private void setBluetoothState(BluetoothState bluetoothState) { LOG.info("Setting Bluetooth state to " + bluetoothState); this.bluetoothState = bluetoothState; - if (!wasAdapterEnabled && bluetoothState == BluetoothState.ENABLED) { + if (!wasAdapterEnabled && wasRequestAccepted && + bluetoothState == BluetoothState.DISCOVERABLE) { + LOG.info("Bluetooth adapter was enabled by us"); eventBus.broadcast(new BluetoothEnabledEvent()); + wasRequestAccepted = false; wasAdapterEnabled = true; } showQrCodeFragmentIfAllowed(); @@ -214,12 +310,14 @@ public abstract class KeyAgreementActivity extends BriarActivity implements if (result == RESULT_CANCELED) { setBluetoothState(BluetoothState.REFUSED); } else { + wasRequestAccepted = true; // 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) + if (bt.getScanMode() == SCAN_MODE_CONNECTABLE_DISCOVERABLE) { setBluetoothState(BluetoothState.DISCOVERABLE); + } } } else super.onActivityResult(request, result, data); } @@ -332,23 +430,30 @@ public abstract class KeyAgreementActivity extends BriarActivity implements permission); } + @Override + public void eventOccurred(Event e) { + if (e instanceof TransportActiveEvent) { + TransportActiveEvent t = (TransportActiveEvent) e; + if (t.getTransportId().equals(BluetoothConstants.ID)) { + showQrCodeFragmentIfAllowed(); + } + } + } + private class BluetoothStateReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { 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)) { + if (ACTION_SCAN_MODE_CHANGED.equals(action)) { int scanMode = intent.getIntExtra(EXTRA_SCAN_MODE, 0); - if (scanMode == SCAN_MODE_CONNECTABLE_DISCOVERABLE) + if (scanMode == SCAN_MODE_CONNECTABLE_DISCOVERABLE) { + LOG.info("Bluetooth adapter is discoverable"); setBluetoothState(BluetoothState.DISCOVERABLE); - else if (scanMode == SCAN_MODE_CONNECTABLE) - setBluetoothState(BluetoothState.ENABLED); - else setBluetoothState(BluetoothState.UNKNOWN); + } else if (bluetoothState == BluetoothState.DISCOVERABLE) { + LOG.info("Bluetooth adapter is not discoverable"); + setBluetoothState(BluetoothState.UNKNOWN); + } } } }