From 5a55b3d7e3ade3acfcbb1c024a19f145e01ec615 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Tue, 2 Feb 2021 17:47:47 -0300 Subject: [PATCH] Move Plugin related code from activity to ViewModel --- .../android/activity/ActivityComponent.java | 11 +- .../add/nearby/ContactExchangeViewModel.java | 195 ++++++++++++- .../contact/add/nearby/IntroFragment.java | 36 +-- .../add/nearby/KeyAgreementActivity.java | 276 ++++-------------- 4 files changed, 280 insertions(+), 238 deletions(-) diff --git a/briar-android/src/main/java/org/briarproject/briar/android/activity/ActivityComponent.java b/briar-android/src/main/java/org/briarproject/briar/android/activity/ActivityComponent.java index c8bf4b403..18a9d26f8 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/activity/ActivityComponent.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/activity/ActivityComponent.java @@ -21,6 +21,11 @@ import org.briarproject.briar.android.blog.RssFeedImportActivity; import org.briarproject.briar.android.blog.RssFeedManageActivity; import org.briarproject.briar.android.blog.WriteBlogPostActivity; import org.briarproject.briar.android.contact.ContactListFragment; +import org.briarproject.briar.android.contact.add.nearby.ContactExchangeActivity; +import org.briarproject.briar.android.contact.add.nearby.ContactExchangeErrorFragment; +import org.briarproject.briar.android.contact.add.nearby.IntroFragment; +import org.briarproject.briar.android.contact.add.nearby.KeyAgreementActivity; +import org.briarproject.briar.android.contact.add.nearby.KeyAgreementFragment; import org.briarproject.briar.android.contact.add.remote.AddContactActivity; import org.briarproject.briar.android.contact.add.remote.LinkExchangeFragment; import org.briarproject.briar.android.contact.add.remote.NicknameFragment; @@ -36,10 +41,6 @@ import org.briarproject.briar.android.fragment.ScreenFilterDialogFragment; import org.briarproject.briar.android.introduction.ContactChooserFragment; import org.briarproject.briar.android.introduction.IntroductionActivity; import org.briarproject.briar.android.introduction.IntroductionMessageFragment; -import org.briarproject.briar.android.contact.add.nearby.ContactExchangeActivity; -import org.briarproject.briar.android.contact.add.nearby.ContactExchangeErrorFragment; -import org.briarproject.briar.android.contact.add.nearby.KeyAgreementActivity; -import org.briarproject.briar.android.contact.add.nearby.KeyAgreementFragment; import org.briarproject.briar.android.login.ChangePasswordActivity; import org.briarproject.briar.android.login.OpenDatabaseFragment; import org.briarproject.briar.android.login.PasswordFragment; @@ -208,6 +209,8 @@ public interface ActivityComponent { void inject(FeedFragment fragment); + void inject(IntroFragment fragment); + void inject(KeyAgreementFragment fragment); void inject(LinkExchangeFragment fragment); diff --git a/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/ContactExchangeViewModel.java b/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/ContactExchangeViewModel.java index 241e665ec..0b0d11ec2 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/ContactExchangeViewModel.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/ContactExchangeViewModel.java @@ -1,6 +1,7 @@ package org.briarproject.briar.android.contact.add.nearby; import android.app.Application; +import android.bluetooth.BluetoothAdapter; import android.graphics.Bitmap; import android.util.DisplayMetrics; import android.widget.Toast; @@ -30,8 +31,14 @@ import org.briarproject.bramble.api.keyagreement.event.KeyAgreementStartedEvent; import org.briarproject.bramble.api.keyagreement.event.KeyAgreementWaitingEvent; import org.briarproject.bramble.api.lifecycle.IoExecutor; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.bramble.api.plugin.BluetoothConstants; +import org.briarproject.bramble.api.plugin.LanTcpConstants; +import org.briarproject.bramble.api.plugin.Plugin; +import org.briarproject.bramble.api.plugin.Plugin.State; +import org.briarproject.bramble.api.plugin.PluginManager; import org.briarproject.bramble.api.plugin.TransportId; import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection; +import org.briarproject.bramble.api.plugin.event.TransportStateEvent; import org.briarproject.briar.R; import org.briarproject.briar.android.contact.add.nearby.ContactAddingState.ContactExchangeFinished; import org.briarproject.briar.android.contact.add.nearby.ContactAddingState.ContactExchangeStarted; @@ -40,6 +47,8 @@ import org.briarproject.briar.android.contact.add.nearby.ContactAddingState.KeyA import org.briarproject.briar.android.contact.add.nearby.ContactAddingState.KeyAgreementWaiting; import org.briarproject.briar.android.contact.add.nearby.ContactExchangeResult.Error; import org.briarproject.briar.android.contact.add.nearby.ContactExchangeResult.Success; +import org.briarproject.briar.android.viewmodel.LiveEvent; +import org.briarproject.briar.android.viewmodel.MutableLiveEvent; import java.io.IOException; import java.nio.charset.Charset; @@ -55,12 +64,19 @@ import androidx.lifecycle.AndroidViewModel; import androidx.lifecycle.LiveData; import androidx.lifecycle.MutableLiveData; +import static android.bluetooth.BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE; import static android.widget.Toast.LENGTH_LONG; import static java.util.Objects.requireNonNull; import static java.util.logging.Level.INFO; import static java.util.logging.Level.WARNING; import static java.util.logging.Logger.getLogger; +import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE; +import static org.briarproject.bramble.api.plugin.Plugin.State.DISABLED; +import static org.briarproject.bramble.api.plugin.Plugin.State.INACTIVE; +import static org.briarproject.bramble.api.plugin.Plugin.State.STARTING_STOPPING; import static org.briarproject.bramble.util.LogUtils.logException; +import static org.briarproject.briar.android.contact.add.nearby.ContactExchangeViewModel.BluetoothDecision.REFUSED; +import static org.briarproject.briar.android.contact.add.nearby.ContactExchangeViewModel.BluetoothDecision.UNKNOWN; @NotNullByDefault class ContactExchangeViewModel extends AndroidViewModel @@ -69,22 +85,80 @@ class ContactExchangeViewModel extends AndroidViewModel private static final Logger LOG = getLogger(ContactExchangeViewModel.class.getName()); + enum BluetoothDecision { + /** + * We haven't asked the user about Bluetooth discoverability. + */ + UNKNOWN, + + /** + * The device doesn't have a Bluetooth adapter. + */ + NO_ADAPTER, + + /** + * We're waiting for the user to accept or refuse discoverability. + */ + WAITING, + + /** + * The user has accepted discoverability. + */ + ACCEPTED, + + /** + * The user has refused discoverability. + */ + REFUSED + } + @SuppressWarnings("CharsetObjectCanBeUsed") // Requires minSdkVersion >= 19 private static final Charset ISO_8859_1 = Charset.forName("ISO-8859-1"); private final EventBus eventBus; private final Executor ioExecutor; + private final PluginManager pluginManager; private final PayloadEncoder payloadEncoder; private final PayloadParser payloadParser; private final Provider keyAgreementTaskProvider; private final ContactExchangeManager contactExchangeManager; private final ConnectionManager connectionManager; + /** + * 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 final MutableLiveData wasContinueClicked = + new MutableLiveData<>(false); + private final MutableLiveEvent showQrCodeFragment = + new MutableLiveEvent<>(); + private final MutableLiveEvent transportStateChanged = + new MutableLiveEvent<>(); private final MutableLiveData state = new MutableLiveData<>(); final QrCodeDecoder qrCodeDecoder; + @Nullable + private final BluetoothAdapter bt; + @Nullable + private final Plugin wifiPlugin, bluetoothPlugin; + // UiThread + BluetoothDecision bluetoothDecision = BluetoothDecision.UNKNOWN; + + /** + * Records whether we've enabled the wifi plugin so we don't enable it more + * than once. + */ + private boolean hasEnabledWifi = false; + + /** + * Records whether we've enabled the Bluetooth plugin so we don't enable it + * more than once. + */ + private boolean hasEnabledBluetooth = false; + @Nullable private KeyAgreementTask task; private volatile boolean gotLocalPayload = false, gotRemotePayload = false; @@ -93,6 +167,7 @@ class ContactExchangeViewModel extends AndroidViewModel ContactExchangeViewModel(Application app, EventBus eventBus, @IoExecutor Executor ioExecutor, + PluginManager pluginManager, PayloadEncoder payloadEncoder, PayloadParser payloadParser, Provider keyAgreementTaskProvider, @@ -101,11 +176,15 @@ class ContactExchangeViewModel extends AndroidViewModel super(app); this.eventBus = eventBus; this.ioExecutor = ioExecutor; + this.pluginManager = pluginManager; this.payloadEncoder = payloadEncoder; this.payloadParser = payloadParser; this.keyAgreementTaskProvider = keyAgreementTaskProvider; this.contactExchangeManager = contactExchangeManager; this.connectionManager = connectionManager; + bt = BluetoothAdapter.getDefaultAdapter(); + wifiPlugin = pluginManager.getPlugin(LanTcpConstants.ID); + bluetoothPlugin = pluginManager.getPlugin(BluetoothConstants.ID); qrCodeDecoder = new QrCodeDecoder(ioExecutor, this); eventBus.addListener(this); } @@ -117,12 +196,95 @@ class ContactExchangeViewModel extends AndroidViewModel stopListening(); } + @UiThread + void onContinueClicked() { + if (bluetoothDecision == REFUSED) { + bluetoothDecision = UNKNOWN; // Ask again + } + wasContinueClicked.setValue(true); + } + + @UiThread + boolean isBluetoothSupported() { + return bt != null && bluetoothPlugin != null; + } + + @UiThread + boolean isWifiReady() { + if (wifiPlugin == null) return true; // Continue without wifi + State state = wifiPlugin.getState(); + // Wait for plugin to become enabled + return state == ACTIVE || state == INACTIVE; + } + + @UiThread + boolean isBluetoothReady() { + if (bt == null || bluetoothPlugin == null) { + // Continue without Bluetooth + return true; + } + if (bluetoothDecision == BluetoothDecision.UNKNOWN || + bluetoothDecision == BluetoothDecision.WAITING || + bluetoothDecision == BluetoothDecision.REFUSED) { + // Wait for user to accept + return false; + } + if (bt.getScanMode() != SCAN_MODE_CONNECTABLE_DISCOVERABLE) { + // Wait for adapter to become discoverable + return false; + } + // Wait for plugin to become active + return bluetoothPlugin.getState() == ACTIVE; + } + + @UiThread + void enableWifiIfWeShould() { + if (hasEnabledWifi) return; + if (wifiPlugin == null) return; + State state = wifiPlugin.getState(); + if (state == STARTING_STOPPING || state == DISABLED) { + LOG.info("Enabling wifi plugin"); + hasEnabledWifi = true; + pluginManager.setPluginEnabled(LanTcpConstants.ID, true); + } + } + + @UiThread + void enableBluetoothIfWeShould() { + if (bluetoothDecision != BluetoothDecision.ACCEPTED) return; + if (hasEnabledBluetooth) return; + if (bluetoothPlugin == null || !isBluetoothSupported()) return; + State state = bluetoothPlugin.getState(); + if (state == STARTING_STOPPING || state == DISABLED) { + LOG.info("Enabling Bluetooth plugin"); + hasEnabledBluetooth = true; + pluginManager.setPluginEnabled(BluetoothConstants.ID, true); + } + } + + @UiThread + void startAddingContact() { + // If we return to the intro fragment, the continue button needs to be + // clicked again before showing the QR code fragment + wasContinueClicked.setValue(false); + // If we return to the intro fragment, ask for Bluetooth + // discoverability again before showing the QR code fragment + bluetoothDecision = UNKNOWN; + // If we return to the intro fragment, we may need to enable wifi and + // Bluetooth again + hasEnabledWifi = false; + hasEnabledBluetooth = false; + // start to listen with a KeyAgreementTask + startListening(); + showQrCodeFragment.setEvent(true); + } + /** * Call this once Bluetooth and Wi-Fi are ready to be used. * It is possible to call this more than once over the ViewModel's lifetime. */ @UiThread - void startListening() { + private void startListening() { KeyAgreementTask oldTask = task; KeyAgreementTask newTask = keyAgreementTaskProvider.get(); task = newTask; @@ -142,7 +304,20 @@ class ContactExchangeViewModel extends AndroidViewModel @Override public void eventOccurred(Event e) { - if (e instanceof KeyAgreementListeningEvent) { + if (e instanceof TransportStateEvent) { + TransportStateEvent t = (TransportStateEvent) e; + if (t.getTransportId().equals(BluetoothConstants.ID)) { + if (LOG.isLoggable(INFO)) { + LOG.info("Bluetooth state changed to " + t.getState()); + } + transportStateChanged.setEvent(t.getTransportId()); + } else if (t.getTransportId().equals(LanTcpConstants.ID)) { + if (LOG.isLoggable(INFO)) { + LOG.info("Wifi state changed to " + t.getState()); + } + transportStateChanged.setEvent(t.getTransportId()); + } + } else if (e instanceof KeyAgreementListeningEvent) { LOG.info("KeyAgreementListeningEvent received"); KeyAgreementListeningEvent event = (KeyAgreementListeningEvent) e; onLocalPayloadReceived(event.getLocalPayload()); @@ -257,6 +432,22 @@ class ContactExchangeViewModel extends AndroidViewModel } } + LiveData getWasContinueClicked() { + return wasContinueClicked; + } + + /** + * Receives an event when the transport state of the WiFi or Bluetooth + * plugins changes. + */ + LiveEvent getTransportStateChanged() { + return transportStateChanged; + } + + LiveEvent getShowQrCodeFragment() { + return showQrCodeFragment; + } + LiveData getState() { return state; } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/IntroFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/IntroFragment.java index f0ffe62c6..4b7a6201f 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/IntroFragment.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/IntroFragment.java @@ -1,6 +1,5 @@ package org.briarproject.briar.android.contact.add.nearby; -import android.content.Context; import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; @@ -10,9 +9,13 @@ import android.widget.ScrollView; import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.briar.R; +import org.briarproject.briar.android.activity.ActivityComponent; import org.briarproject.briar.android.fragment.BaseFragment; import javax.annotation.Nullable; +import javax.inject.Inject; + +import androidx.lifecycle.ViewModelProvider; import static android.view.View.FOCUS_DOWN; @@ -20,33 +23,27 @@ import static android.view.View.FOCUS_DOWN; @ParametersNotNullByDefault public class IntroFragment extends BaseFragment { - interface IntroScreenSeenListener { - void showNextScreen(); - } - public static final String TAG = IntroFragment.class.getName(); - private IntroScreenSeenListener screenSeenListener; + @Inject + ViewModelProvider.Factory viewModelFactory; + + private ContactExchangeViewModel viewModel; + private ScrollView scrollView; public static IntroFragment newInstance() { - Bundle args = new Bundle(); - IntroFragment fragment = new IntroFragment(); fragment.setArguments(args); return fragment; } @Override - public void onAttach(Context context) { - super.onAttach(context); - screenSeenListener = (IntroScreenSeenListener) context; - } - - @Override - public String getUniqueTag() { - return TAG; + public void injectFragment(ActivityComponent component) { + component.inject(this); + viewModel = new ViewModelProvider(requireActivity(), viewModelFactory) + .get(ContactExchangeViewModel.class); } @Nullable @@ -59,7 +56,7 @@ public class IntroFragment extends BaseFragment { false); scrollView = v.findViewById(R.id.scrollView); View button = v.findViewById(R.id.continueButton); - button.setOnClickListener(view -> screenSeenListener.showNextScreen()); + button.setOnClickListener(view -> viewModel.onContinueClicked()); return v; } @@ -69,4 +66,9 @@ public class IntroFragment extends BaseFragment { scrollView.post(() -> scrollView.fullScroll(FOCUS_DOWN)); } + @Override + public String getUniqueTag() { + return TAG; + } + } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/KeyAgreementActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/KeyAgreementActivity.java index 39f7d168b..6eb87f38b 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/KeyAgreementActivity.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/KeyAgreementActivity.java @@ -1,6 +1,5 @@ package org.briarproject.briar.android.contact.add.nearby; -import android.bluetooth.BluetoothAdapter; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -8,21 +7,12 @@ import android.content.IntentFilter; import android.os.Bundle; import android.view.MenuItem; -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.nullsafety.MethodsNotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; -import org.briarproject.bramble.api.plugin.BluetoothConstants; -import org.briarproject.bramble.api.plugin.LanTcpConstants; -import org.briarproject.bramble.api.plugin.Plugin; -import org.briarproject.bramble.api.plugin.Plugin.State; -import org.briarproject.bramble.api.plugin.PluginManager; -import org.briarproject.bramble.api.plugin.event.TransportStateEvent; import org.briarproject.briar.R; import org.briarproject.briar.android.activity.ActivityComponent; import org.briarproject.briar.android.activity.BriarActivity; -import org.briarproject.briar.android.contact.add.nearby.IntroFragment.IntroScreenSeenListener; +import org.briarproject.briar.android.contact.add.nearby.ContactExchangeViewModel.BluetoothDecision; import org.briarproject.briar.android.fragment.BaseFragment; import org.briarproject.briar.android.fragment.BaseFragment.BaseFragmentListener; @@ -43,51 +33,21 @@ import static android.Manifest.permission.ACCESS_FINE_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.SCAN_MODE_CONNECTABLE_DISCOVERABLE; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.os.Build.VERSION.SDK_INT; -import static java.util.logging.Level.INFO; import static java.util.logging.Logger.getLogger; import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull; -import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE; -import static org.briarproject.bramble.api.plugin.Plugin.State.DISABLED; -import static org.briarproject.bramble.api.plugin.Plugin.State.INACTIVE; -import static org.briarproject.bramble.api.plugin.Plugin.State.STARTING_STOPPING; import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_BLUETOOTH_DISCOVERABLE; import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_PERMISSION_CAMERA_LOCATION; +import static org.briarproject.briar.android.contact.add.nearby.ContactExchangeViewModel.BluetoothDecision.ACCEPTED; +import static org.briarproject.briar.android.contact.add.nearby.ContactExchangeViewModel.BluetoothDecision.REFUSED; +import static org.briarproject.briar.android.contact.add.nearby.ContactExchangeViewModel.BluetoothDecision.UNKNOWN; import static org.briarproject.briar.android.util.UiUtils.getGoToSettingsListener; @MethodsNotNullByDefault @ParametersNotNullByDefault -public abstract class KeyAgreementActivity extends BriarActivity implements - BaseFragmentListener, IntroScreenSeenListener, EventListener { - - private enum BluetoothDecision { - /** - * We haven't asked the user about Bluetooth discoverability. - */ - UNKNOWN, - - /** - * The device doesn't have a Bluetooth adapter. - */ - NO_ADAPTER, - - /** - * We're waiting for the user to accept or refuse discoverability. - */ - WAITING, - - /** - * The user has accepted discoverability. - */ - ACCEPTED, - - /** - * The user has refused discoverability. - */ - REFUSED - } +public abstract class KeyAgreementActivity extends BriarActivity + implements BaseFragmentListener { private enum Permission { UNKNOWN, GRANTED, SHOW_RATIONALE, PERMANENTLY_DENIED @@ -96,16 +56,9 @@ public abstract class KeyAgreementActivity extends BriarActivity implements private static final Logger LOG = getLogger(KeyAgreementActivity.class.getName()); - @Inject ViewModelProvider.Factory viewModelFactory; - @Inject - EventBus eventBus; - - @Inject - PluginManager pluginManager; - protected ContactExchangeViewModel viewModel; /** @@ -116,31 +69,9 @@ public abstract class KeyAgreementActivity extends BriarActivity implements */ 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 we've enabled the wifi plugin so we don't enable it more - * than once. - */ - private boolean hasEnabledWifi = false; - - /** - * Records whether we've enabled the Bluetooth plugin so we don't enable it - * more than once. - */ - private boolean hasEnabledBluetooth = false; - private Permission cameraPermission = Permission.UNKNOWN; private Permission locationPermission = Permission.UNKNOWN; - private BluetoothDecision bluetoothDecision = BluetoothDecision.UNKNOWN; private BroadcastReceiver bluetoothReceiver = null; - private Plugin wifiPlugin = null, bluetoothPlugin = null; - private BluetoothAdapter bt = null; @Override public void injectActivity(ActivityComponent component) { @@ -162,9 +93,42 @@ public abstract class KeyAgreementActivity extends BriarActivity implements IntentFilter filter = new IntentFilter(ACTION_SCAN_MODE_CHANGED); bluetoothReceiver = new BluetoothStateReceiver(); registerReceiver(bluetoothReceiver, filter); - wifiPlugin = pluginManager.getPlugin(LanTcpConstants.ID); - bluetoothPlugin = pluginManager.getPlugin(BluetoothConstants.ID); - bt = BluetoothAdapter.getDefaultAdapter(); + viewModel.getWasContinueClicked().observe(this, clicked -> { + if (clicked && checkPermissions()) showQrCodeFragmentIfAllowed(); + }); + viewModel.getTransportStateChanged().observeEvent(this, + t -> showQrCodeFragmentIfAllowed()); + viewModel.getShowQrCodeFragment().observeEvent(this, show -> { + if (show) showQrCodeFragment(); + }); + } + + @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 + showQrCodeFragmentIfAllowed(); + } + + @Override + protected void onPause() { + super.onPause(); + isResumed = false; + } + + @Override + protected void onStop() { + super.onStop(); } @Override @@ -182,45 +146,22 @@ public abstract class KeyAgreementActivity extends BriarActivity implements return super.onOptionsItemSelected(item); } - @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; - } - - @Override - protected void onPostResume() { - super.onPostResume(); - isResumed = true; - // Workaround for - // https://code.google.com/p/android/issues/detail?id=190966 - showQrCodeFragmentIfAllowed(); - } - @SuppressWarnings("StatementWithEmptyBody") private void showQrCodeFragmentIfAllowed() { + boolean continueClicked = // never set to null + requireNonNull(viewModel.getWasContinueClicked().getValue()); if (isResumed && continueClicked && areEssentialPermissionsGranted()) { - if (isWifiReady() && isBluetoothReady()) { + if (viewModel.isWifiReady() && viewModel.isBluetoothReady()) { LOG.info("Wifi and Bluetooth are ready"); - viewModel.startListening(); - showQrCodeFragment(); + viewModel.startAddingContact(); } else { - if (shouldEnableWifi()) { - LOG.info("Enabling wifi plugin"); - hasEnabledWifi = true; - pluginManager.setPluginEnabled(LanTcpConstants.ID, true); - } - if (bluetoothDecision == BluetoothDecision.UNKNOWN) { + viewModel.enableWifiIfWeShould(); + if (viewModel.bluetoothDecision == UNKNOWN) { requestBluetoothDiscoverable(); - } else if (bluetoothDecision == BluetoothDecision.REFUSED) { + } else if (viewModel.bluetoothDecision == REFUSED) { // Ask again when the user clicks "continue" - } else if (shouldEnableBluetooth()) { - LOG.info("Enabling Bluetooth plugin"); - hasEnabledBluetooth = true; - pluginManager.setPluginEnabled(BluetoothConstants.ID, true); + } else { + viewModel.enableBluetoothIfWeShould(); } } } @@ -229,119 +170,42 @@ public abstract class KeyAgreementActivity extends BriarActivity implements private boolean areEssentialPermissionsGranted() { return cameraPermission == Permission.GRANTED && (SDK_INT < 23 || locationPermission == Permission.GRANTED || - !isBluetoothSupported()); - } - - private boolean isBluetoothSupported() { - return bt != null && bluetoothPlugin != null; - } - - private boolean isWifiReady() { - if (wifiPlugin == null) return true; // Continue without wifi - State state = wifiPlugin.getState(); - // Wait for plugin to become enabled - return state == ACTIVE || state == INACTIVE; - } - - private boolean isBluetoothReady() { - if (!isBluetoothSupported()) { - // Continue without Bluetooth - return true; - } - if (bluetoothDecision == BluetoothDecision.UNKNOWN || - bluetoothDecision == BluetoothDecision.WAITING || - bluetoothDecision == BluetoothDecision.REFUSED) { - // Wait for user to accept - return false; - } - if (bt.getScanMode() != SCAN_MODE_CONNECTABLE_DISCOVERABLE) { - // Wait for adapter to become discoverable - return false; - } - // Wait for plugin to become active - return bluetoothPlugin.getState() == ACTIVE; - } - - private boolean shouldEnableWifi() { - if (hasEnabledWifi) return false; - if (wifiPlugin == null) return false; - State state = wifiPlugin.getState(); - return state == STARTING_STOPPING || state == DISABLED; + !viewModel.isBluetoothSupported()); } private void requestBluetoothDiscoverable() { - if (!isBluetoothSupported()) { - bluetoothDecision = BluetoothDecision.NO_ADAPTER; + if (!viewModel.isBluetoothSupported()) { + viewModel.bluetoothDecision = BluetoothDecision.NO_ADAPTER; showQrCodeFragmentIfAllowed(); } else { Intent i = new Intent(ACTION_REQUEST_DISCOVERABLE); if (i.resolveActivity(getPackageManager()) != null) { LOG.info("Asking for Bluetooth discoverability"); - bluetoothDecision = BluetoothDecision.WAITING; + viewModel.bluetoothDecision = BluetoothDecision.WAITING; startActivityForResult(i, REQUEST_BLUETOOTH_DISCOVERABLE); } else { - bluetoothDecision = BluetoothDecision.NO_ADAPTER; + viewModel.bluetoothDecision = BluetoothDecision.NO_ADAPTER; showQrCodeFragmentIfAllowed(); } } } - private boolean shouldEnableBluetooth() { - if (bluetoothDecision != BluetoothDecision.ACCEPTED) return false; - if (hasEnabledBluetooth) return false; - if (!isBluetoothSupported()) return false; - State state = bluetoothPlugin.getState(); - return state == STARTING_STOPPING || state == DISABLED; - } - - @Override - protected void onPause() { - super.onPause(); - isResumed = false; - } - - @Override - protected void onStop() { - super.onStop(); - eventBus.removeListener(this); - } - - @Override - public void showNextScreen() { - continueClicked = true; - if (bluetoothDecision == BluetoothDecision.REFUSED) { - bluetoothDecision = BluetoothDecision.UNKNOWN; // Ask again - } - if (checkPermissions()) showQrCodeFragmentIfAllowed(); - } - @Override public void onActivityResult(int request, int result, @Nullable Intent data) { if (request == REQUEST_BLUETOOTH_DISCOVERABLE) { if (result == RESULT_CANCELED) { LOG.info("Bluetooth discoverability was refused"); - bluetoothDecision = BluetoothDecision.REFUSED; + viewModel.bluetoothDecision = REFUSED; } else { LOG.info("Bluetooth discoverability was accepted"); - bluetoothDecision = BluetoothDecision.ACCEPTED; + viewModel.bluetoothDecision = ACCEPTED; } showQrCodeFragmentIfAllowed(); } else super.onActivityResult(request, result, data); } 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 - bluetoothDecision = BluetoothDecision.UNKNOWN; - // If we return to the intro fragment, we may need to enable wifi and - // Bluetooth again - hasEnabledWifi = false; - hasEnabledBluetooth = false; - // FIXME #824 FragmentManager fm = getSupportFragmentManager(); if (fm.findFragmentByTag(KeyAgreementFragment.TAG) == null) { @@ -362,7 +226,7 @@ public abstract class KeyAgreementActivity extends BriarActivity implements R.string.permission_camera_denied_body); return false; } - if (isBluetoothSupported() && + if (viewModel.isBluetoothSupported() && locationPermission == Permission.PERMANENTLY_DENIED) { showDenialDialog(R.string.permission_location_title, R.string.permission_location_denied_body); @@ -406,7 +270,7 @@ public abstract class KeyAgreementActivity extends BriarActivity implements private void requestPermissions() { String[] permissions; - if (isBluetoothSupported()) { + if (viewModel.isBluetoothSupported()) { permissions = new String[] {CAMERA, ACCESS_FINE_LOCATION}; } else { permissions = new String[] {CAMERA}; @@ -430,7 +294,7 @@ public abstract class KeyAgreementActivity extends BriarActivity implements } else { cameraPermission = Permission.PERMANENTLY_DENIED; } - if (isBluetoothSupported()) { + if (viewModel.isBluetoothSupported()) { if (gotPermission(ACCESS_FINE_LOCATION, permissions, grantResults)) { locationPermission = Permission.GRANTED; @@ -463,24 +327,6 @@ public abstract class KeyAgreementActivity extends BriarActivity implements permission); } - @Override - public void eventOccurred(Event e) { - if (e instanceof TransportStateEvent) { - TransportStateEvent t = (TransportStateEvent) e; - if (t.getTransportId().equals(BluetoothConstants.ID)) { - if (LOG.isLoggable(INFO)) { - LOG.info("Bluetooth state changed to " + t.getState()); - } - showQrCodeFragmentIfAllowed(); - } else if (t.getTransportId().equals(LanTcpConstants.ID)) { - if (LOG.isLoggable(INFO)) { - LOG.info("Wifi state changed to " + t.getState()); - } - showQrCodeFragmentIfAllowed(); - } - } - } - private class BluetoothStateReceiver extends BroadcastReceiver { @Override