diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/duplex/DuplexPlugin.java b/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/duplex/DuplexPlugin.java index f37a8b5fe..9e14e7ab5 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/duplex/DuplexPlugin.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/duplex/DuplexPlugin.java @@ -41,12 +41,10 @@ public interface DuplexPlugin extends Plugin { /** * Attempts to connect to the remote peer specified in the given descriptor. * Returns null if no connection can be established. - * - * @param alice True if the local party is Alice */ @Nullable DuplexTransportConnection createKeyAgreementConnection( - byte[] remoteCommitment, BdfList descriptor, boolean alice); + byte[] remoteCommitment, BdfList descriptor); /** * Returns true if the plugin supports rendezvous connections. diff --git a/bramble-core/src/main/java/org/briarproject/bramble/keyagreement/KeyAgreementConnector.java b/bramble-core/src/main/java/org/briarproject/bramble/keyagreement/KeyAgreementConnector.java index 79bb700aa..23af3589e 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/keyagreement/KeyAgreementConnector.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/keyagreement/KeyAgreementConnector.java @@ -1,5 +1,6 @@ package org.briarproject.bramble.keyagreement; +import org.briarproject.bramble.api.Pair; import org.briarproject.bramble.api.crypto.KeyAgreementCrypto; import org.briarproject.bramble.api.crypto.KeyPair; import org.briarproject.bramble.api.data.BdfList; @@ -8,6 +9,8 @@ import org.briarproject.bramble.api.keyagreement.KeyAgreementListener; import org.briarproject.bramble.api.keyagreement.Payload; import org.briarproject.bramble.api.keyagreement.TransportDescriptor; 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.PluginManager; import org.briarproject.bramble.api.plugin.TransportId; @@ -19,7 +22,9 @@ import org.briarproject.bramble.api.record.RecordWriterFactory; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.concurrent.Callable; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CountDownLatch; @@ -28,8 +33,10 @@ import java.util.logging.Logger; import javax.annotation.Nullable; +import static java.util.Arrays.asList; 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.keyagreement.KeyAgreementConstants.CONNECTION_TIMEOUT; import static org.briarproject.bramble.util.LogUtils.logException; @@ -41,7 +48,10 @@ class KeyAgreementConnector { } private static final Logger LOG = - Logger.getLogger(KeyAgreementConnector.class.getName()); + getLogger(KeyAgreementConnector.class.getName()); + + private static final List PREFERRED_TRANSPORTS = + asList(BluetoothConstants.ID, LanTcpConstants.ID); private final Callbacks callbacks; private final KeyAgreementCrypto keyAgreementCrypto; @@ -105,24 +115,35 @@ class KeyAgreementConnector { this.alice = alice; aliceLatch.countDown(); - // Start connecting over supported transports + // Start connecting over supported transports in order of preference if (LOG.isLoggable(INFO)) { LOG.info("Starting outgoing BQP connections as " + (alice ? "Alice" : "Bob")); } + Map descriptors = new HashMap<>(); for (TransportDescriptor d : remotePayload.getTransportDescriptors()) { - Plugin p = pluginManager.getPlugin(d.getId()); - if (p instanceof DuplexPlugin) { + descriptors.put(d.getId(), d); + } + List> transports = new ArrayList<>(); + for (TransportId id : PREFERRED_TRANSPORTS) { + TransportDescriptor d = descriptors.get(id); + Plugin p = pluginManager.getPlugin(id); + if (d != null && p instanceof DuplexPlugin) { if (LOG.isLoggable(INFO)) - LOG.info("Connecting via " + d.getId()); - DuplexPlugin plugin = (DuplexPlugin) p; - byte[] commitment = remotePayload.getCommitment(); - BdfList descriptor = d.getDescriptor(); - connectionChooser.submit(new ReadableTask(new ConnectorTask( - plugin, commitment, descriptor, alice))); + LOG.info("Connecting via " + id); + transports.add(new Pair<>((DuplexPlugin) p, d.getDescriptor())); } } + // TODO: If we don't have any transports in common with the peer, + // warn the user and give up (#1224) + + if (!transports.isEmpty()) { + byte[] commitment = remotePayload.getCommitment(); + connectionChooser.submit(new ReadableTask(new ConnectorTask( + transports, commitment))); + } + // Get chosen connection try { KeyAgreementConnection chosen = @@ -148,17 +169,13 @@ class KeyAgreementConnector { private class ConnectorTask implements Callable { - private final DuplexPlugin plugin; + private final List> transports; private final byte[] commitment; - private final BdfList descriptor; - private final boolean alice; - private ConnectorTask(DuplexPlugin plugin, byte[] commitment, - BdfList descriptor, boolean alice) { - this.plugin = plugin; + private ConnectorTask(List> transports, + byte[] commitment) { + this.transports = transports; this.commitment = commitment; - this.descriptor = descriptor; - this.alice = alice; } @Nullable @@ -166,13 +183,18 @@ class KeyAgreementConnector { public KeyAgreementConnection call() throws Exception { // Repeat attempts until we connect, get stopped, or get interrupted while (!stopped) { - DuplexTransportConnection conn = - plugin.createKeyAgreementConnection(commitment, - descriptor, alice); - if (conn != null) { - if (LOG.isLoggable(INFO)) - LOG.info(plugin.getId() + ": Outgoing connection"); - return new KeyAgreementConnection(conn, plugin.getId()); + for (Pair pair : transports) { + if (stopped) return null; + DuplexPlugin plugin = pair.getFirst(); + BdfList descriptor = pair.getSecond(); + DuplexTransportConnection conn = + plugin.createKeyAgreementConnection(commitment, + descriptor); + if (conn != null) { + if (LOG.isLoggable(INFO)) + LOG.info(plugin.getId() + ": Outgoing connection"); + return new KeyAgreementConnection(conn, plugin.getId()); + } } // Wait 2s before retry (to circumvent transient failures) Thread.sleep(2000); 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 7c1ee4b59..f90469d56 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 @@ -430,22 +430,17 @@ abstract class BluetoothPlugin implements DuplexPlugin, EventListener { @Override public DuplexTransportConnection createKeyAgreementConnection( - byte[] commitment, BdfList descriptor, boolean alice) { + byte[] commitment, BdfList descriptor) { if (getState() != ACTIVE) return null; // No truncation necessary because COMMIT_LENGTH = 16 String uuid = UUID.nameUUIDFromBytes(commitment).toString(); DuplexTransportConnection conn; if (descriptor.size() == 1) { - if (alice) { - if (LOG.isLoggable(INFO)) { - LOG.info("Discovering address for key agreement UUID " + - uuid); - } - conn = discoverAndConnect(uuid); - } else { - LOG.info("No address in key agreement descriptor"); - return null; + if (LOG.isLoggable(INFO)) { + LOG.info("Discovering address for key agreement UUID " + + uuid); } + conn = discoverAndConnect(uuid); } else { String address; try { diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/tcp/LanTcpPlugin.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/tcp/LanTcpPlugin.java index 235ac5cb9..87706b7e5 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/plugin/tcp/LanTcpPlugin.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/tcp/LanTcpPlugin.java @@ -376,7 +376,7 @@ class LanTcpPlugin extends TcpPlugin { @Override public DuplexTransportConnection createKeyAgreementConnection( - byte[] commitment, BdfList descriptor, boolean alice) { + byte[] commitment, BdfList descriptor) { ServerSocket ss = state.getServerSocket(true); if (ss == null) return null; InterfaceAddress local = getLocalInterfaceAddress(ss.getInetAddress()); diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/tcp/TcpPlugin.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/tcp/TcpPlugin.java index 53ada12c1..f67121685 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/plugin/tcp/TcpPlugin.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/tcp/TcpPlugin.java @@ -367,7 +367,7 @@ abstract class TcpPlugin implements DuplexPlugin, EventListener { @Override public DuplexTransportConnection createKeyAgreementConnection( - byte[] commitment, BdfList descriptor, boolean alice) { + byte[] commitment, BdfList descriptor) { throw new UnsupportedOperationException(); } diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/tor/TorPlugin.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/tor/TorPlugin.java index a2ba04ed2..5fc93f885 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/plugin/tor/TorPlugin.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/tor/TorPlugin.java @@ -708,7 +708,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener { @Override public DuplexTransportConnection createKeyAgreementConnection( - byte[] commitment, BdfList descriptor, boolean alice) { + byte[] commitment, BdfList descriptor) { throw new UnsupportedOperationException(); } diff --git a/bramble-core/src/test/java/org/briarproject/bramble/plugin/tcp/LanTcpPluginTest.java b/bramble-core/src/test/java/org/briarproject/bramble/plugin/tcp/LanTcpPluginTest.java index 59f3b1923..cd560e3a6 100644 --- a/bramble-core/src/test/java/org/briarproject/bramble/plugin/tcp/LanTcpPluginTest.java +++ b/bramble-core/src/test/java/org/briarproject/bramble/plugin/tcp/LanTcpPluginTest.java @@ -276,7 +276,7 @@ public class LanTcpPluginTest extends BrambleTestCase { descriptor.add(local.getPort()); // Connect to the port DuplexTransportConnection d = plugin.createKeyAgreementConnection( - new byte[COMMIT_LENGTH], descriptor, true); + new byte[COMMIT_LENGTH], descriptor); assertNotNull(d); // Check that the connection was accepted assertTrue(latch.await(5, SECONDS)); diff --git a/bramble-java/src/main/java/org/briarproject/bramble/plugin/modem/ModemPlugin.java b/bramble-java/src/main/java/org/briarproject/bramble/plugin/modem/ModemPlugin.java index 4d2ee0e1e..53a668f20 100644 --- a/bramble-java/src/main/java/org/briarproject/bramble/plugin/modem/ModemPlugin.java +++ b/bramble-java/src/main/java/org/briarproject/bramble/plugin/modem/ModemPlugin.java @@ -198,7 +198,7 @@ class ModemPlugin implements DuplexPlugin, Modem.Callback { @Override public DuplexTransportConnection createKeyAgreementConnection( - byte[] commitment, BdfList descriptor, boolean alice) { + byte[] commitment, BdfList descriptor) { throw new UnsupportedOperationException(); } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/ContactExchangeActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/ContactExchangeActivity.java index f18c41f75..222fba77b 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/ContactExchangeActivity.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/ContactExchangeActivity.java @@ -9,7 +9,6 @@ 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; @@ -114,9 +113,7 @@ public class ContactExchangeActivity extends KeyAgreementActivity { return getString(R.string.exchanging_contact_details); } - protected void showErrorFragment() { - String errorMsg = getString(R.string.connection_error_explanation); - BaseFragment f = ContactExchangeErrorFragment.newInstance(errorMsg); - showNextFragment(f); + private void showErrorFragment() { + showNextFragment(new ContactExchangeErrorFragment()); } } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/ContactExchangeErrorFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/ContactExchangeErrorFragment.java index 733b50a6a..2ed8ce6e4 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/ContactExchangeErrorFragment.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/ContactExchangeErrorFragment.java @@ -1,5 +1,6 @@ package org.briarproject.briar.android.keyagreement; +import android.content.Intent; import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; @@ -18,7 +19,10 @@ import org.briarproject.briar.android.util.UiUtils; import javax.inject.Inject; import androidx.annotation.Nullable; +import androidx.fragment.app.FragmentActivity; +import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP; +import static android.view.View.GONE; import static org.briarproject.briar.android.util.UiUtils.onSingleLinkClick; @MethodsNotNullByDefault @@ -58,13 +62,12 @@ public class ContactExchangeErrorFragment extends BaseFragment { View v = inflater.inflate(R.layout.fragment_error_contact_exchange, container, false); - // set humanized error message + // set optional error message TextView explanation = v.findViewById(R.id.errorMessage); Bundle args = getArguments(); - if (args == null) { - throw new IllegalArgumentException("Use newInstance()"); - } - explanation.setText(args.getString(ERROR_MSG)); + String errorMessage = args == null ? null : args.getString(ERROR_MSG); + if (errorMessage == null) explanation.setVisibility(GONE); + else explanation.setText(args.getString(ERROR_MSG)); // make feedback link clickable TextView sendFeedback = v.findViewById(R.id.sendFeedback); @@ -73,7 +76,11 @@ public class ContactExchangeErrorFragment extends BaseFragment { // buttons Button tryAgain = v.findViewById(R.id.tryAgainButton); tryAgain.setOnClickListener(view -> { - if (getActivity() != null) getActivity().onBackPressed(); + // Recreate the activity so we return to the intro fragment + FragmentActivity activity = requireActivity(); + Intent i = new Intent(activity, ContactExchangeActivity.class); + i.setFlags(FLAG_ACTIVITY_CLEAR_TOP); + activity.startActivity(i); }); Button cancel = v.findViewById(R.id.cancelButton); cancel.setOnClickListener(view -> finish()); 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 568ed219b..a2795b2a0 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 @@ -26,7 +26,6 @@ import org.briarproject.briar.android.fragment.BaseFragment; import org.briarproject.briar.android.fragment.BaseFragment.BaseFragmentListener; import org.briarproject.briar.android.keyagreement.IntroFragment.IntroScreenSeenListener; import org.briarproject.briar.android.keyagreement.KeyAgreementFragment.KeyAgreementEventListener; -import org.briarproject.briar.android.util.UiUtils; import java.util.logging.Logger; @@ -46,6 +45,7 @@ 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; @@ -55,6 +55,7 @@ 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.util.UiUtils.getGoToSettingsListener; @MethodsNotNullByDefault @ParametersNotNullByDefault @@ -133,6 +134,8 @@ public abstract class KeyAgreementActivity extends BriarActivity implements 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) { @@ -152,6 +155,9 @@ 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(); } @Override @@ -187,6 +193,7 @@ public abstract class KeyAgreementActivity extends BriarActivity implements showQrCodeFragmentIfAllowed(); } + @SuppressWarnings("StatementWithEmptyBody") private void showQrCodeFragmentIfAllowed() { if (isResumed && continueClicked && areEssentialPermissionsGranted()) { if (isWifiReady() && isBluetoothReady()) { @@ -200,6 +207,8 @@ public abstract class KeyAgreementActivity extends BriarActivity implements } if (bluetoothDecision == BluetoothDecision.UNKNOWN) { requestBluetoothDiscoverable(); + } else if (bluetoothDecision == BluetoothDecision.REFUSED) { + // Ask again when the user clicks "continue" } else if (shouldEnableBluetooth()) { LOG.info("Enabling Bluetooth plugin"); hasEnabledBluetooth = true; @@ -210,55 +219,50 @@ public abstract class KeyAgreementActivity extends BriarActivity implements } 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); + (SDK_INT < 23 || locationPermission == Permission.GRANTED || + !isBluetoothSupported()); + } + + private boolean isBluetoothSupported() { + return bt != null && bluetoothPlugin != null; } private boolean isWifiReady() { - Plugin p = pluginManager.getPlugin(LanTcpConstants.ID); - if (p == null) return true; // Continue without wifi - State state = p.getState(); + 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 (bluetoothDecision == BluetoothDecision.UNKNOWN || - bluetoothDecision == BluetoothDecision.WAITING) { - // Wait for decision - return false; - } - if (bluetoothDecision == BluetoothDecision.NO_ADAPTER - || bluetoothDecision == BluetoothDecision.REFUSED) { + if (!isBluetoothSupported()) { // Continue without Bluetooth return true; } - BluetoothAdapter bt = BluetoothAdapter.getDefaultAdapter(); - if (bt == null) return true; // Continue without Bluetooth + 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; } - Plugin p = pluginManager.getPlugin(BluetoothConstants.ID); - if (p == null) return true; // Continue without Bluetooth // Wait for plugin to become active - return p.getState() == ACTIVE; + return bluetoothPlugin.getState() == ACTIVE; } private boolean shouldEnableWifi() { if (hasEnabledWifi) return false; - Plugin p = pluginManager.getPlugin(LanTcpConstants.ID); - if (p == null) return false; - State state = p.getState(); + if (wifiPlugin == null) return false; + State state = wifiPlugin.getState(); return state == STARTING_STOPPING || state == DISABLED; } private void requestBluetoothDiscoverable() { - BluetoothAdapter bt = BluetoothAdapter.getDefaultAdapter(); - if (bt == null) { + if (!isBluetoothSupported()) { bluetoothDecision = BluetoothDecision.NO_ADAPTER; showQrCodeFragmentIfAllowed(); } else { @@ -277,9 +281,8 @@ public abstract class KeyAgreementActivity extends BriarActivity implements private boolean shouldEnableBluetooth() { if (bluetoothDecision != BluetoothDecision.ACCEPTED) return false; if (hasEnabledBluetooth) return false; - Plugin p = pluginManager.getPlugin(BluetoothConstants.ID); - if (p == null) return false; - State state = p.getState(); + if (!isBluetoothSupported()) return false; + State state = bluetoothPlugin.getState(); return state == STARTING_STOPPING || state == DISABLED; } @@ -298,6 +301,9 @@ public abstract class KeyAgreementActivity extends BriarActivity implements @Override public void showNextScreen() { continueClicked = true; + if (bluetoothDecision == BluetoothDecision.REFUSED) { + bluetoothDecision = BluetoothDecision.UNKNOWN; // Ask again + } if (checkPermissions()) showQrCodeFragmentIfAllowed(); } @@ -341,17 +347,17 @@ public abstract class KeyAgreementActivity extends BriarActivity implements private boolean checkPermissions() { if (areEssentialPermissionsGranted()) return true; - // If the camera permission has been permanently denied, ask the + // If an essential 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(); + showDenialDialog(R.string.permission_camera_title, + R.string.permission_camera_denied_body); + return false; + } + if (isBluetoothSupported() && + locationPermission == Permission.PERMANENTLY_DENIED) { + showDenialDialog(R.string.permission_location_title, + R.string.permission_location_denied_body); return false; } // Should we show the rationale for one or both permissions? @@ -371,6 +377,16 @@ public abstract class KeyAgreementActivity extends BriarActivity implements return false; } + private void showDenialDialog(@StringRes int title, @StringRes int body) { + Builder builder = new Builder(this, R.style.BriarDialogTheme); + builder.setTitle(title); + builder.setMessage(body); + builder.setPositiveButton(R.string.ok, getGoToSettingsListener(this)); + builder.setNegativeButton(R.string.cancel, + (dialog, which) -> supportFinishAfterTransition()); + builder.show(); + } + private void showRationale(@StringRes int title, @StringRes int body) { Builder builder = new Builder(this, R.style.BriarDialogTheme); builder.setTitle(title); @@ -381,8 +397,13 @@ public abstract class KeyAgreementActivity extends BriarActivity implements } private void requestPermissions() { - ActivityCompat.requestPermissions(this, - new String[] {CAMERA, ACCESS_FINE_LOCATION}, + String[] permissions; + if (isBluetoothSupported()) { + permissions = new String[] {CAMERA, ACCESS_FINE_LOCATION}; + } else { + permissions = new String[] {CAMERA}; + } + ActivityCompat.requestPermissions(this, permissions, REQUEST_PERMISSION_CAMERA_LOCATION); } @@ -399,12 +420,15 @@ public abstract class KeyAgreementActivity extends BriarActivity implements } else { cameraPermission = Permission.PERMANENTLY_DENIED; } - if (gotPermission(ACCESS_FINE_LOCATION, permissions, grantResults)) { - locationPermission = Permission.GRANTED; - } else if (shouldShowRationale(ACCESS_FINE_LOCATION)) { - locationPermission = Permission.SHOW_RATIONALE; - } else { - locationPermission = Permission.PERMANENTLY_DENIED; + if (isBluetoothSupported()) { + if (gotPermission(ACCESS_FINE_LOCATION, permissions, + grantResults)) { + locationPermission = Permission.GRANTED; + } else if (shouldShowRationale(ACCESS_FINE_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 diff --git a/briar-android/src/main/res/values/strings.xml b/briar-android/src/main/res/values/strings.xml index 8951beb2a..66cd51313 100644 --- a/briar-android/src/main/res/values/strings.xml +++ b/briar-android/src/main/res/values/strings.xml @@ -210,7 +210,6 @@ Connecting to device\u2026 Authenticating with device\u2026 Could not connect to your contact - Please check that you\'re both connected to the same Wi-Fi network. If this problem persists, please send feedback to help us improve the app. @@ -589,6 +588,7 @@ Camera and location To scan the QR code, Briar needs access to the camera.\n\nTo discover Bluetooth devices, Briar needs permission to access your location.\n\nBriar does not store your location or share it with anyone. You have denied access to the camera, but adding contacts requires using the camera.\n\nPlease consider granting access. + You have denied access to your location, but Briar needs this permission to discover Bluetooth devices.\n\nPlease consider granting access. QR code Show QR code fullscreen