Refactor more code into AddNearbyContactViewModel

thus concentrating the logic there needing less back and forth with the activity
This commit is contained in:
Torsten Grote
2021-03-23 17:00:36 -03:00
parent bcc0442add
commit 7f486eef4c
8 changed files with 170 additions and 129 deletions

View File

@@ -11,7 +11,6 @@ public interface RequestCodes {
int REQUEST_RINGTONE = 7; int REQUEST_RINGTONE = 7;
int REQUEST_PERMISSION_CAMERA_LOCATION = 8; int REQUEST_PERMISSION_CAMERA_LOCATION = 8;
int REQUEST_DOZE_WHITELISTING = 9; int REQUEST_DOZE_WHITELISTING = 9;
int REQUEST_BLUETOOTH_DISCOVERABLE = 10;
int REQUEST_UNLOCK = 11; int REQUEST_UNLOCK = 11;
int REQUEST_KEYGUARD_UNLOCK = 12; int REQUEST_KEYGUARD_UNLOCK = 12;
int REQUEST_ATTACH_IMAGE = 13; int REQUEST_ATTACH_IMAGE = 13;

View File

@@ -1,16 +1,12 @@
package org.briarproject.briar.android.contact.add.nearby; package org.briarproject.briar.android.contact.add.nearby;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle; import android.os.Bundle;
import android.view.MenuItem; import android.view.MenuItem;
import android.widget.Toast; import android.widget.Toast;
import org.briarproject.bramble.api.identity.Author; import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.NullSafety;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.briar.R; import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.ActivityComponent; import org.briarproject.briar.android.activity.ActivityComponent;
@@ -21,25 +17,25 @@ import org.briarproject.briar.android.contact.add.nearby.AddContactState.Failed;
import org.briarproject.briar.android.contact.add.nearby.AddNearbyContactViewModel.BluetoothDecision; import org.briarproject.briar.android.contact.add.nearby.AddNearbyContactViewModel.BluetoothDecision;
import org.briarproject.briar.android.fragment.BaseFragment; import org.briarproject.briar.android.fragment.BaseFragment;
import org.briarproject.briar.android.fragment.BaseFragment.BaseFragmentListener; import org.briarproject.briar.android.fragment.BaseFragment.BaseFragmentListener;
import org.briarproject.briar.android.util.RequestBluetoothDiscoverable;
import java.util.logging.Logger; import java.util.logging.Logger;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.inject.Inject; import javax.inject.Inject;
import androidx.activity.result.ActivityResultLauncher;
import androidx.appcompat.widget.Toolbar; import androidx.appcompat.widget.Toolbar;
import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentManager;
import androidx.lifecycle.ViewModelProvider; import androidx.lifecycle.ViewModelProvider;
import static android.bluetooth.BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE; import static android.bluetooth.BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE;
import static android.bluetooth.BluetoothAdapter.ACTION_SCAN_MODE_CHANGED; import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
import static android.widget.Toast.LENGTH_LONG; import static android.widget.Toast.LENGTH_LONG;
import static java.util.Objects.requireNonNull; import static java.util.Objects.requireNonNull;
import static java.util.logging.Logger.getLogger; import static java.util.logging.Logger.getLogger;
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_BLUETOOTH_DISCOVERABLE;
import static org.briarproject.briar.android.contact.add.nearby.AddNearbyContactViewModel.BluetoothDecision.ACCEPTED; import static org.briarproject.briar.android.contact.add.nearby.AddNearbyContactViewModel.BluetoothDecision.ACCEPTED;
import static org.briarproject.briar.android.contact.add.nearby.AddNearbyContactViewModel.BluetoothDecision.REFUSED; import static org.briarproject.briar.android.contact.add.nearby.AddNearbyContactViewModel.BluetoothDecision.REFUSED;
import static org.briarproject.briar.android.contact.add.nearby.AddNearbyContactViewModel.BluetoothDecision.UNKNOWN;
@MethodsNotNullByDefault @MethodsNotNullByDefault
@ParametersNotNullByDefault @ParametersNotNullByDefault
@@ -55,14 +51,9 @@ public class AddNearbyContactActivity extends BriarActivity
private AddNearbyContactViewModel viewModel; private AddNearbyContactViewModel viewModel;
private AddNearbyContactPermissionManager permissionManager; private AddNearbyContactPermissionManager permissionManager;
/** private final ActivityResultLauncher<Integer> bluetoothLauncher =
* Set to true in onPostResume() and false in onPause(). This prevents the registerForActivityResult(new RequestBluetoothDiscoverable(),
* QR code fragment from being shown if onRequestPermissionsResult() is this::onBluetoothDiscoverableResult);
* called while the activity is paused, which could cause a crash due to
* https://issuetracker.google.com/issues/37067655.
*/
private boolean isResumed = false;
private BroadcastReceiver bluetoothReceiver = null;
@Override @Override
public void injectActivity(ActivityComponent component) { public void injectActivity(ActivityComponent component) {
@@ -79,21 +70,14 @@ public class AddNearbyContactActivity extends BriarActivity
setContentView(R.layout.activity_fragment_container_toolbar); setContentView(R.layout.activity_fragment_container_toolbar);
Toolbar toolbar = findViewById(R.id.toolbar); Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar); setSupportActionBar(toolbar);
NullSafety.requireNonNull(getSupportActionBar()) requireNonNull(getSupportActionBar()).setDisplayHomeAsUpEnabled(true);
.setDisplayHomeAsUpEnabled(true);
if (state == null) { if (state == null) {
showInitialFragment(AddNearbyContactIntroFragment.newInstance()); showInitialFragment(AddNearbyContactIntroFragment.newInstance());
} }
IntentFilter filter = new IntentFilter(ACTION_SCAN_MODE_CHANGED); viewModel.getCheckPermissions().observeEvent(this, check ->
bluetoothReceiver = new BluetoothStateReceiver(); permissionManager.checkPermissions()); // never false
registerReceiver(bluetoothReceiver, filter); viewModel.getRequestBluetoothDiscoverable().observeEvent(this, r ->
viewModel.getWasContinueClicked().observe(this, clicked -> { requestBluetoothDiscoverable()); // never false
if (clicked && permissionManager.checkPermissions()) {
showQrCodeFragmentIfAllowed();
}
});
viewModel.getTransportStateChanged().observeEvent(this,
t -> showQrCodeFragmentIfAllowed());
viewModel.getShowQrCodeFragment().observeEvent(this, show -> { viewModel.getShowQrCodeFragment().observeEvent(this, show -> {
if (show) showQrCodeFragment(); if (show) showQrCodeFragment();
}); });
@@ -113,37 +97,23 @@ public class AddNearbyContactActivity extends BriarActivity
@Override @Override
protected void onPostResume() { protected void onPostResume() {
super.onPostResume(); super.onPostResume();
isResumed = true; viewModel.setIsActivityResumed(true);
// Workaround for
// https://code.google.com/p/android/issues/detail?id=190966
showQrCodeFragmentIfAllowed();
} }
@Override @Override
protected void onPause() { protected void onPause() {
super.onPause(); super.onPause();
isResumed = false; viewModel.setIsActivityResumed(false);
} }
@Override private void onBluetoothDiscoverableResult(boolean discoverable) {
public void onDestroy() { if (discoverable) {
super.onDestroy(); LOG.info("Bluetooth discoverability was accepted");
if (bluetoothReceiver != null) unregisterReceiver(bluetoothReceiver); viewModel.setBluetoothDecision(ACCEPTED);
} } else {
LOG.info("Bluetooth discoverability was refused");
@Override viewModel.setBluetoothDecision(REFUSED);
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");
viewModel.bluetoothDecision = REFUSED;
} else {
LOG.info("Bluetooth discoverability was accepted");
viewModel.bluetoothDecision = ACCEPTED;
}
showQrCodeFragmentIfAllowed();
} else super.onActivityResult(request, result, data);
} }
@Override @Override
@@ -161,14 +131,16 @@ public class AddNearbyContactActivity extends BriarActivity
super.onRequestPermissionsResult(requestCode, permissions, super.onRequestPermissionsResult(requestCode, permissions,
grantResults); grantResults);
permissionManager.onRequestPermissionsResult(requestCode, permissions, permissionManager.onRequestPermissionsResult(requestCode, permissions,
grantResults, this::showQrCodeFragmentIfAllowed); grantResults, viewModel::showQrCodeFragmentIfAllowed);
} }
@Override @Override
public void onBackPressed() { public void onBackPressed() {
if (viewModel.getState().getValue() instanceof Failed) { if (viewModel.getState().getValue() instanceof Failed) {
// finish this activity when going back in failed state // re-create this activity when going back in failed state
supportFinishAfterTransition(); Intent i = new Intent(this, AddNearbyContactActivity.class);
i.setFlags(FLAG_ACTIVITY_CLEAR_TOP);
startActivity(i);
} else { } else {
super.onBackPressed(); super.onBackPressed();
} }
@@ -176,41 +148,15 @@ public class AddNearbyContactActivity extends BriarActivity
private void requestBluetoothDiscoverable() { private void requestBluetoothDiscoverable() {
if (!viewModel.isBluetoothSupported()) { if (!viewModel.isBluetoothSupported()) {
viewModel.bluetoothDecision = BluetoothDecision.NO_ADAPTER; viewModel.setBluetoothDecision(BluetoothDecision.NO_ADAPTER);
showQrCodeFragmentIfAllowed();
} else { } else {
Intent i = new Intent(ACTION_REQUEST_DISCOVERABLE); Intent i = new Intent(ACTION_REQUEST_DISCOVERABLE);
if (i.resolveActivity(getPackageManager()) != null) { if (i.resolveActivity(getPackageManager()) != null) {
LOG.info("Asking for Bluetooth discoverability"); LOG.info("Asking for Bluetooth discoverability");
viewModel.bluetoothDecision = BluetoothDecision.WAITING; viewModel.setBluetoothDecision(BluetoothDecision.WAITING);
startActivityForResult(i, REQUEST_BLUETOOTH_DISCOVERABLE); bluetoothLauncher.launch(120); // 2min discoverable
} else { } else {
viewModel.bluetoothDecision = BluetoothDecision.NO_ADAPTER; viewModel.setBluetoothDecision(BluetoothDecision.NO_ADAPTER);
showQrCodeFragmentIfAllowed();
}
}
}
@SuppressWarnings("StatementWithEmptyBody")
private void showQrCodeFragmentIfAllowed() {
boolean continueClicked = // never set to null
NullSafety.requireNonNull(
viewModel.getWasContinueClicked().getValue());
boolean permissionsGranted =
permissionManager.areEssentialPermissionsGranted();
if (isResumed && continueClicked && permissionsGranted) {
if (viewModel.isWifiReady() && viewModel.isBluetoothReady()) {
LOG.info("Wifi and Bluetooth are ready");
viewModel.startAddingContact();
} else {
viewModel.enableWifiIfWeShould();
if (viewModel.bluetoothDecision == UNKNOWN) {
requestBluetoothDiscoverable();
} else if (viewModel.bluetoothDecision == REFUSED) {
// Ask again when the user clicks "continue"
} else {
viewModel.enableBluetoothIfWeShould();
}
} }
} }
} }
@@ -233,11 +179,6 @@ public class AddNearbyContactActivity extends BriarActivity
((ContactExchangeFinished) state).result; ((ContactExchangeFinished) state).result;
onContactExchangeResult(result); onContactExchangeResult(result);
} else if (state instanceof Failed) { } else if (state instanceof Failed) {
// Remove navigation icon, so user can't go back when failed
// ErrorFragment will finish or relaunch this activity
Toolbar toolbar = findViewById(R.id.toolbar);
toolbar.setNavigationIcon(null);
Boolean qrCodeTooOld = ((Failed) state).qrCodeTooOld; Boolean qrCodeTooOld = ((Failed) state).qrCodeTooOld;
onAddingContactFailed(qrCodeTooOld); onAddingContactFailed(qrCodeTooOld);
} }
@@ -286,11 +227,4 @@ public class AddNearbyContactActivity extends BriarActivity
showNextFragment(new AddNearbyContactErrorFragment()); showNextFragment(new AddNearbyContactErrorFragment());
} }
private class BluetoothStateReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
LOG.info("Bluetooth scan mode changed");
showQrCodeFragmentIfAllowed();
}
}
} }

View File

@@ -171,10 +171,8 @@ public class AddNearbyContactFragment extends BaseFragment
status.setText(R.string.waiting_for_contact_to_scan); status.setText(R.string.waiting_for_contact_to_scan);
} else if (state instanceof KeyAgreementStarted) { } else if (state instanceof KeyAgreementStarted) {
qrCodeView.setVisibility(INVISIBLE); qrCodeView.setVisibility(INVISIBLE);
statusView.setVisibility(VISIBLE);
status.setText(R.string.authenticating_with_device); status.setText(R.string.authenticating_with_device);
} else if (state instanceof ContactExchangeStarted) { } else if (state instanceof ContactExchangeStarted) {
statusView.setVisibility(VISIBLE);
status.setText(R.string.exchanging_contact_details); status.setText(R.string.exchanging_contact_details);
} else if (state instanceof Failed) { } else if (state instanceof Failed) {
// the activity will replace this fragment with an error fragment // the activity will replace this fragment with an error fragment

View File

@@ -1,5 +1,7 @@
package org.briarproject.briar.android.contact.add.nearby; package org.briarproject.briar.android.contact.add.nearby;
import android.content.Context;
import org.briarproject.briar.R; import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.BaseActivity; import org.briarproject.briar.android.activity.BaseActivity;
@@ -11,6 +13,8 @@ import static android.Manifest.permission.ACCESS_FINE_LOCATION;
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.content.ContextCompat.checkSelfPermission;
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_PERMISSION_CAMERA_LOCATION; import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_PERMISSION_CAMERA_LOCATION;
import static org.briarproject.briar.android.util.UiUtils.getGoToSettingsListener; import static org.briarproject.briar.android.util.UiUtils.getGoToSettingsListener;
@@ -37,6 +41,15 @@ class AddNearbyContactPermissionManager {
locationPermission = Permission.UNKNOWN; locationPermission = Permission.UNKNOWN;
} }
static boolean areEssentialPermissionsGranted(Context ctx,
boolean isBluetoothSupported) {
int ok = PERMISSION_GRANTED;
return checkSelfPermission(ctx, CAMERA) == ok &&
(SDK_INT < 23 ||
checkSelfPermission(ctx, ACCESS_FINE_LOCATION) == ok ||
!isBluetoothSupported);
}
boolean areEssentialPermissionsGranted() { boolean areEssentialPermissionsGranted() {
return cameraPermission == Permission.GRANTED && return cameraPermission == Permission.GRANTED &&
(SDK_INT < 23 || locationPermission == Permission.GRANTED || (SDK_INT < 23 || locationPermission == Permission.GRANTED ||
@@ -147,8 +160,7 @@ class AddNearbyContactPermissionManager {
} }
private boolean shouldShowRationale(String permission) { private boolean shouldShowRationale(String permission) {
return ActivityCompat return shouldShowRequestPermissionRationale(ctx, permission);
.shouldShowRequestPermissionRationale(ctx, permission);
} }
} }

View File

@@ -2,6 +2,10 @@ package org.briarproject.briar.android.contact.add.nearby;
import android.app.Application; import android.app.Application;
import android.bluetooth.BluetoothAdapter; 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.graphics.Bitmap;
import android.util.DisplayMetrics; import android.util.DisplayMetrics;
import android.widget.Toast; import android.widget.Toast;
@@ -39,6 +43,7 @@ import org.briarproject.bramble.api.plugin.PluginManager;
import org.briarproject.bramble.api.plugin.TransportId; import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection; import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
import org.briarproject.bramble.api.plugin.event.TransportStateEvent; import org.briarproject.bramble.api.plugin.event.TransportStateEvent;
import org.briarproject.bramble.api.system.AndroidExecutor;
import org.briarproject.briar.R; import org.briarproject.briar.R;
import org.briarproject.briar.android.contact.add.nearby.AddContactState.ContactExchangeFinished; import org.briarproject.briar.android.contact.add.nearby.AddContactState.ContactExchangeFinished;
import org.briarproject.briar.android.contact.add.nearby.AddContactState.ContactExchangeResult.Error; import org.briarproject.briar.android.contact.add.nearby.AddContactState.ContactExchangeResult.Error;
@@ -64,6 +69,7 @@ import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LiveData; import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData; import androidx.lifecycle.MutableLiveData;
import static android.bluetooth.BluetoothAdapter.ACTION_SCAN_MODE_CHANGED;
import static android.bluetooth.BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE; import static android.bluetooth.BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE;
import static android.widget.Toast.LENGTH_LONG; import static android.widget.Toast.LENGTH_LONG;
import static java.util.Objects.requireNonNull; import static java.util.Objects.requireNonNull;
@@ -75,6 +81,7 @@ 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.INACTIVE;
import static org.briarproject.bramble.api.plugin.Plugin.State.STARTING_STOPPING; import static org.briarproject.bramble.api.plugin.Plugin.State.STARTING_STOPPING;
import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.briar.android.contact.add.nearby.AddNearbyContactPermissionManager.areEssentialPermissionsGranted;
import static org.briarproject.briar.android.contact.add.nearby.AddNearbyContactViewModel.BluetoothDecision.REFUSED; import static org.briarproject.briar.android.contact.add.nearby.AddNearbyContactViewModel.BluetoothDecision.REFUSED;
import static org.briarproject.briar.android.contact.add.nearby.AddNearbyContactViewModel.BluetoothDecision.UNKNOWN; import static org.briarproject.briar.android.contact.add.nearby.AddNearbyContactViewModel.BluetoothDecision.UNKNOWN;
@@ -116,6 +123,7 @@ class AddNearbyContactViewModel extends AndroidViewModel
private static final Charset ISO_8859_1 = Charset.forName("ISO-8859-1"); private static final Charset ISO_8859_1 = Charset.forName("ISO-8859-1");
private final EventBus eventBus; private final EventBus eventBus;
private final AndroidExecutor androidExecutor;
private final Executor ioExecutor; private final Executor ioExecutor;
private final PluginManager pluginManager; private final PluginManager pluginManager;
private final PayloadEncoder payloadEncoder; private final PayloadEncoder payloadEncoder;
@@ -124,28 +132,28 @@ class AddNearbyContactViewModel extends AndroidViewModel
private final ContactExchangeManager contactExchangeManager; private final ContactExchangeManager contactExchangeManager;
private final ConnectionManager connectionManager; private final ConnectionManager connectionManager;
/** private final MutableLiveEvent<Boolean> checkPermissions =
* 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<Boolean> wasContinueClicked =
new MutableLiveData<>(false);
private final MutableLiveEvent<Boolean> showQrCodeFragment =
new MutableLiveEvent<>(); new MutableLiveEvent<>();
private final MutableLiveEvent<TransportId> transportStateChanged = private final MutableLiveEvent<Boolean> requestBluetoothDiscoverable =
new MutableLiveEvent<>();
private final MutableLiveEvent<Boolean> showQrCodeFragment =
new MutableLiveEvent<>(); new MutableLiveEvent<>();
private final MutableLiveData<AddContactState> state = private final MutableLiveData<AddContactState> state =
new MutableLiveData<>(); new MutableLiveData<>();
final QrCodeDecoder qrCodeDecoder; final QrCodeDecoder qrCodeDecoder;
final BroadcastReceiver bluetoothReceiver = new BluetoothStateReceiver();
@Nullable @Nullable
private final BluetoothAdapter bt; private final BluetoothAdapter bt;
@Nullable @Nullable
private final Plugin wifiPlugin, bluetoothPlugin; private final Plugin wifiPlugin, bluetoothPlugin;
// UiThread // UiThread
BluetoothDecision bluetoothDecision = BluetoothDecision.UNKNOWN; private BluetoothDecision bluetoothDecision = BluetoothDecision.UNKNOWN;
private boolean wasContinueClicked = false;
private boolean isActivityResumed = false;
/** /**
* Records whether we've enabled the wifi plugin so we don't enable it more * Records whether we've enabled the wifi plugin so we don't enable it more
@@ -166,6 +174,7 @@ class AddNearbyContactViewModel extends AndroidViewModel
@Inject @Inject
AddNearbyContactViewModel(Application app, AddNearbyContactViewModel(Application app,
EventBus eventBus, EventBus eventBus,
AndroidExecutor androidExecutor,
@IoExecutor Executor ioExecutor, @IoExecutor Executor ioExecutor,
PluginManager pluginManager, PluginManager pluginManager,
PayloadEncoder payloadEncoder, PayloadEncoder payloadEncoder,
@@ -175,6 +184,7 @@ class AddNearbyContactViewModel extends AndroidViewModel
ConnectionManager connectionManager) { ConnectionManager connectionManager) {
super(app); super(app);
this.eventBus = eventBus; this.eventBus = eventBus;
this.androidExecutor = androidExecutor;
this.ioExecutor = ioExecutor; this.ioExecutor = ioExecutor;
this.pluginManager = pluginManager; this.pluginManager = pluginManager;
this.payloadEncoder = payloadEncoder; this.payloadEncoder = payloadEncoder;
@@ -185,13 +195,16 @@ class AddNearbyContactViewModel extends AndroidViewModel
bt = BluetoothAdapter.getDefaultAdapter(); bt = BluetoothAdapter.getDefaultAdapter();
wifiPlugin = pluginManager.getPlugin(LanTcpConstants.ID); wifiPlugin = pluginManager.getPlugin(LanTcpConstants.ID);
bluetoothPlugin = pluginManager.getPlugin(BluetoothConstants.ID); bluetoothPlugin = pluginManager.getPlugin(BluetoothConstants.ID);
qrCodeDecoder = new QrCodeDecoder(ioExecutor, this); qrCodeDecoder = new QrCodeDecoder(androidExecutor, ioExecutor, this);
eventBus.addListener(this); eventBus.addListener(this);
IntentFilter filter = new IntentFilter(ACTION_SCAN_MODE_CHANGED);
getApplication().registerReceiver(bluetoothReceiver, filter);
} }
@Override @Override
protected void onCleared() { protected void onCleared() {
super.onCleared(); super.onCleared();
getApplication().unregisterReceiver(bluetoothReceiver);
eventBus.removeListener(this); eventBus.removeListener(this);
stopListening(); stopListening();
} }
@@ -201,7 +214,9 @@ class AddNearbyContactViewModel extends AndroidViewModel
if (bluetoothDecision == REFUSED) { if (bluetoothDecision == REFUSED) {
bluetoothDecision = UNKNOWN; // Ask again bluetoothDecision = UNKNOWN; // Ask again
} }
wasContinueClicked.setValue(true); wasContinueClicked = true;
checkPermissions.setEvent(true);
showQrCodeFragmentIfAllowed();
} }
@UiThread @UiThread
@@ -266,7 +281,7 @@ class AddNearbyContactViewModel extends AndroidViewModel
void startAddingContact() { void startAddingContact() {
// If we return to the intro fragment, the continue button needs to be // If we return to the intro fragment, the continue button needs to be
// clicked again before showing the QR code fragment // clicked again before showing the QR code fragment
wasContinueClicked.setValue(false); wasContinueClicked = false;
// If we return to the intro fragment, ask for Bluetooth // If we return to the intro fragment, ask for Bluetooth
// discoverability again before showing the QR code fragment // discoverability again before showing the QR code fragment
bluetoothDecision = UNKNOWN; bluetoothDecision = UNKNOWN;
@@ -310,12 +325,12 @@ class AddNearbyContactViewModel extends AndroidViewModel
if (LOG.isLoggable(INFO)) { if (LOG.isLoggable(INFO)) {
LOG.info("Bluetooth state changed to " + t.getState()); LOG.info("Bluetooth state changed to " + t.getState());
} }
transportStateChanged.setEvent(t.getTransportId()); showQrCodeFragmentIfAllowed();
} else if (t.getTransportId().equals(LanTcpConstants.ID)) { } else if (t.getTransportId().equals(LanTcpConstants.ID)) {
if (LOG.isLoggable(INFO)) { if (LOG.isLoggable(INFO)) {
LOG.info("Wifi state changed to " + t.getState()); LOG.info("Wifi state changed to " + t.getState());
} }
transportStateChanged.setEvent(t.getTransportId()); showQrCodeFragmentIfAllowed();
} }
} else if (e instanceof KeyAgreementListeningEvent) { } else if (e instanceof KeyAgreementListeningEvent) {
LOG.info("KeyAgreementListeningEvent received"); LOG.info("KeyAgreementListeningEvent received");
@@ -344,6 +359,28 @@ class AddNearbyContactViewModel extends AndroidViewModel
} }
} }
@SuppressWarnings("StatementWithEmptyBody")
@UiThread
void showQrCodeFragmentIfAllowed() {
boolean permissionsGranted = areEssentialPermissionsGranted(
getApplication(), isBluetoothSupported());
if (isActivityResumed && wasContinueClicked && permissionsGranted) {
if (isWifiReady() && isBluetoothReady()) {
LOG.info("Wifi and Bluetooth are ready");
startAddingContact();
} else {
enableWifiIfWeShould();
if (bluetoothDecision == UNKNOWN) {
requestBluetoothDiscoverable.setEvent(true);
} else if (bluetoothDecision == REFUSED) {
// Ask again when the user clicks "continue"
} else {
enableBluetoothIfWeShould();
}
}
}
}
/** /**
* This sets the QR code by setting the state to KeyAgreementListening. * This sets the QR code by setting the state to KeyAgreementListening.
*/ */
@@ -383,8 +420,8 @@ class AddNearbyContactViewModel extends AndroidViewModel
state.postValue(new AddContactState.Failed(e.isTooOld())); state.postValue(new AddContactState.Failed(e.isTooOld()));
} catch (IOException | IllegalArgumentException e) { } catch (IOException | IllegalArgumentException e) {
LOG.log(WARNING, "QR Code Invalid", e); LOG.log(WARNING, "QR Code Invalid", e);
Toast.makeText(getApplication(), R.string.qr_code_invalid, androidExecutor.runOnUiThread(() -> Toast.makeText(getApplication(),
LENGTH_LONG).show(); R.string.qr_code_invalid, LENGTH_LONG).show());
resetPayloadFlags(); resetPayloadFlags();
state.postValue(new AddContactState.Failed()); state.postValue(new AddContactState.Failed());
} }
@@ -423,6 +460,15 @@ class AddNearbyContactViewModel extends AndroidViewModel
}); });
} }
private class BluetoothStateReceiver extends BroadcastReceiver {
@UiThread
@Override
public void onReceive(Context context, Intent intent) {
LOG.info("Bluetooth scan mode changed");
showQrCodeFragmentIfAllowed();
}
}
private void tryToClose(DuplexTransportConnection conn) { private void tryToClose(DuplexTransportConnection conn) {
try { try {
conn.getReader().dispose(true, true); conn.getReader().dispose(true, true);
@@ -432,16 +478,33 @@ class AddNearbyContactViewModel extends AndroidViewModel
} }
} }
LiveData<Boolean> getWasContinueClicked() { /**
return wasContinueClicked; * 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.
* TODO check if this is still happening when using new permission requesting
*/
@UiThread
void setIsActivityResumed(boolean resumed) {
isActivityResumed = resumed;
// Workaround for
// https://code.google.com/p/android/issues/detail?id=190966
showQrCodeFragmentIfAllowed();
} }
/** @UiThread
* Receives an event when the transport state of the WiFi or Bluetooth void setBluetoothDecision(BluetoothDecision decision) {
* plugins changes. bluetoothDecision = decision;
*/ showQrCodeFragmentIfAllowed();
LiveEvent<TransportId> getTransportStateChanged() { }
return transportStateChanged;
LiveEvent<Boolean> getCheckPermissions() {
return checkPermissions;
}
LiveEvent<Boolean> getRequestBluetoothDiscoverable() {
return requestBluetoothDiscoverable;
} }
LiveEvent<Boolean> getShowQrCodeFragment() { LiveEvent<Boolean> getShowQrCodeFragment() {

View File

@@ -18,6 +18,7 @@ import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.system.AndroidExecutor;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.logging.Logger; import java.util.logging.Logger;
@@ -35,6 +36,7 @@ class QrCodeDecoder implements PreviewConsumer, PreviewCallback {
private static final Logger LOG = getLogger(QrCodeDecoder.class.getName()); private static final Logger LOG = getLogger(QrCodeDecoder.class.getName());
private final AndroidExecutor androidExecutor;
private final Executor ioExecutor; private final Executor ioExecutor;
private final Reader reader = new QRCodeReader(); private final Reader reader = new QRCodeReader();
private final ResultCallback callback; private final ResultCallback callback;
@@ -42,7 +44,9 @@ class QrCodeDecoder implements PreviewConsumer, PreviewCallback {
private Camera camera = null; private Camera camera = null;
private int cameraIndex = 0; private int cameraIndex = 0;
QrCodeDecoder(@IoExecutor Executor ioExecutor, ResultCallback callback) { QrCodeDecoder(AndroidExecutor androidExecutor,
@IoExecutor Executor ioExecutor, ResultCallback callback) {
this.androidExecutor = androidExecutor;
this.ioExecutor = ioExecutor; this.ioExecutor = ioExecutor;
this.callback = callback; this.callback = callback;
} }
@@ -104,9 +108,9 @@ class QrCodeDecoder implements PreviewConsumer, PreviewCallback {
LOG.warning("Invalid preview frame"); LOG.warning("Invalid preview frame");
} finally { } finally {
reader.reset(); reader.reset();
androidExecutor.runOnUiThread(this::askForPreviewFrame);
} }
}); });
askForPreviewFrame();
} }
private static BinaryBitmap binarize(byte[] data, int width, int height, private static BinaryBitmap binarize(byte[] data, int width, int height,

View File

@@ -0,0 +1,31 @@
package org.briarproject.briar.android.util;
import android.content.Context;
import android.content.Intent;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import androidx.activity.result.contract.ActivityResultContract;
import androidx.annotation.Nullable;
import static android.app.Activity.RESULT_CANCELED;
import static android.bluetooth.BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE;
import static android.bluetooth.BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION;
@NotNullByDefault
public class RequestBluetoothDiscoverable
extends ActivityResultContract<Integer, Boolean> {
@Override
public Intent createIntent(Context context, Integer duration) {
Intent i = new Intent(ACTION_REQUEST_DISCOVERABLE);
i.putExtra(EXTRA_DISCOVERABLE_DURATION, duration);
return i;
}
@Override
public Boolean parseResult(int resultCode, @Nullable Intent intent) {
return resultCode != RESULT_CANCELED;
}
}

View File

@@ -4,7 +4,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:orientation="vertical" android:orientation="vertical"
tools:context=".android.contact.add.nearby.KeyAgreementActivity"> tools:context=".android.contact.add.nearby.AddNearbyContactActivity">
<include layout="@layout/toolbar" /> <include layout="@layout/toolbar" />