mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-18 13:49:53 +01:00
Merge branch 'add-contacts-via-bluetooth' into 'master'
Add contacts via Bluetooth if possible See merge request briar/briar!1292
This commit is contained in:
@@ -41,12 +41,10 @@ public interface DuplexPlugin extends Plugin {
|
|||||||
/**
|
/**
|
||||||
* Attempts to connect to the remote peer specified in the given descriptor.
|
* Attempts to connect to the remote peer specified in the given descriptor.
|
||||||
* Returns null if no connection can be established.
|
* Returns null if no connection can be established.
|
||||||
*
|
|
||||||
* @param alice True if the local party is Alice
|
|
||||||
*/
|
*/
|
||||||
@Nullable
|
@Nullable
|
||||||
DuplexTransportConnection createKeyAgreementConnection(
|
DuplexTransportConnection createKeyAgreementConnection(
|
||||||
byte[] remoteCommitment, BdfList descriptor, boolean alice);
|
byte[] remoteCommitment, BdfList descriptor);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if the plugin supports rendezvous connections.
|
* Returns true if the plugin supports rendezvous connections.
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package org.briarproject.bramble.keyagreement;
|
package org.briarproject.bramble.keyagreement;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.Pair;
|
||||||
import org.briarproject.bramble.api.crypto.KeyAgreementCrypto;
|
import org.briarproject.bramble.api.crypto.KeyAgreementCrypto;
|
||||||
import org.briarproject.bramble.api.crypto.KeyPair;
|
import org.briarproject.bramble.api.crypto.KeyPair;
|
||||||
import org.briarproject.bramble.api.data.BdfList;
|
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.Payload;
|
||||||
import org.briarproject.bramble.api.keyagreement.TransportDescriptor;
|
import org.briarproject.bramble.api.keyagreement.TransportDescriptor;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
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;
|
||||||
import org.briarproject.bramble.api.plugin.PluginManager;
|
import org.briarproject.bramble.api.plugin.PluginManager;
|
||||||
import org.briarproject.bramble.api.plugin.TransportId;
|
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.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.concurrent.Callable;
|
import java.util.concurrent.Callable;
|
||||||
import java.util.concurrent.CopyOnWriteArrayList;
|
import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.concurrent.CountDownLatch;
|
||||||
@@ -28,8 +33,10 @@ import java.util.logging.Logger;
|
|||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
import static java.util.Arrays.asList;
|
||||||
import static java.util.logging.Level.INFO;
|
import static java.util.logging.Level.INFO;
|
||||||
import static java.util.logging.Level.WARNING;
|
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.api.keyagreement.KeyAgreementConstants.CONNECTION_TIMEOUT;
|
||||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||||
|
|
||||||
@@ -41,7 +48,10 @@ class KeyAgreementConnector {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static final Logger LOG =
|
private static final Logger LOG =
|
||||||
Logger.getLogger(KeyAgreementConnector.class.getName());
|
getLogger(KeyAgreementConnector.class.getName());
|
||||||
|
|
||||||
|
private static final List<TransportId> PREFERRED_TRANSPORTS =
|
||||||
|
asList(BluetoothConstants.ID, LanTcpConstants.ID);
|
||||||
|
|
||||||
private final Callbacks callbacks;
|
private final Callbacks callbacks;
|
||||||
private final KeyAgreementCrypto keyAgreementCrypto;
|
private final KeyAgreementCrypto keyAgreementCrypto;
|
||||||
@@ -105,24 +115,35 @@ class KeyAgreementConnector {
|
|||||||
this.alice = alice;
|
this.alice = alice;
|
||||||
aliceLatch.countDown();
|
aliceLatch.countDown();
|
||||||
|
|
||||||
// Start connecting over supported transports
|
// Start connecting over supported transports in order of preference
|
||||||
if (LOG.isLoggable(INFO)) {
|
if (LOG.isLoggable(INFO)) {
|
||||||
LOG.info("Starting outgoing BQP connections as "
|
LOG.info("Starting outgoing BQP connections as "
|
||||||
+ (alice ? "Alice" : "Bob"));
|
+ (alice ? "Alice" : "Bob"));
|
||||||
}
|
}
|
||||||
|
Map<TransportId, TransportDescriptor> descriptors = new HashMap<>();
|
||||||
for (TransportDescriptor d : remotePayload.getTransportDescriptors()) {
|
for (TransportDescriptor d : remotePayload.getTransportDescriptors()) {
|
||||||
Plugin p = pluginManager.getPlugin(d.getId());
|
descriptors.put(d.getId(), d);
|
||||||
if (p instanceof DuplexPlugin) {
|
}
|
||||||
|
List<Pair<DuplexPlugin, BdfList>> 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))
|
if (LOG.isLoggable(INFO))
|
||||||
LOG.info("Connecting via " + d.getId());
|
LOG.info("Connecting via " + id);
|
||||||
DuplexPlugin plugin = (DuplexPlugin) p;
|
transports.add(new Pair<>((DuplexPlugin) p, d.getDescriptor()));
|
||||||
byte[] commitment = remotePayload.getCommitment();
|
|
||||||
BdfList descriptor = d.getDescriptor();
|
|
||||||
connectionChooser.submit(new ReadableTask(new ConnectorTask(
|
|
||||||
plugin, commitment, descriptor, alice)));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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
|
// Get chosen connection
|
||||||
try {
|
try {
|
||||||
KeyAgreementConnection chosen =
|
KeyAgreementConnection chosen =
|
||||||
@@ -148,17 +169,13 @@ class KeyAgreementConnector {
|
|||||||
|
|
||||||
private class ConnectorTask implements Callable<KeyAgreementConnection> {
|
private class ConnectorTask implements Callable<KeyAgreementConnection> {
|
||||||
|
|
||||||
private final DuplexPlugin plugin;
|
private final List<Pair<DuplexPlugin, BdfList>> transports;
|
||||||
private final byte[] commitment;
|
private final byte[] commitment;
|
||||||
private final BdfList descriptor;
|
|
||||||
private final boolean alice;
|
|
||||||
|
|
||||||
private ConnectorTask(DuplexPlugin plugin, byte[] commitment,
|
private ConnectorTask(List<Pair<DuplexPlugin, BdfList>> transports,
|
||||||
BdfList descriptor, boolean alice) {
|
byte[] commitment) {
|
||||||
this.plugin = plugin;
|
this.transports = transports;
|
||||||
this.commitment = commitment;
|
this.commitment = commitment;
|
||||||
this.descriptor = descriptor;
|
|
||||||
this.alice = alice;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@@ -166,13 +183,18 @@ class KeyAgreementConnector {
|
|||||||
public KeyAgreementConnection call() throws Exception {
|
public KeyAgreementConnection call() throws Exception {
|
||||||
// Repeat attempts until we connect, get stopped, or get interrupted
|
// Repeat attempts until we connect, get stopped, or get interrupted
|
||||||
while (!stopped) {
|
while (!stopped) {
|
||||||
DuplexTransportConnection conn =
|
for (Pair<DuplexPlugin, BdfList> pair : transports) {
|
||||||
plugin.createKeyAgreementConnection(commitment,
|
if (stopped) return null;
|
||||||
descriptor, alice);
|
DuplexPlugin plugin = pair.getFirst();
|
||||||
if (conn != null) {
|
BdfList descriptor = pair.getSecond();
|
||||||
if (LOG.isLoggable(INFO))
|
DuplexTransportConnection conn =
|
||||||
LOG.info(plugin.getId() + ": Outgoing connection");
|
plugin.createKeyAgreementConnection(commitment,
|
||||||
return new KeyAgreementConnection(conn, plugin.getId());
|
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)
|
// Wait 2s before retry (to circumvent transient failures)
|
||||||
Thread.sleep(2000);
|
Thread.sleep(2000);
|
||||||
|
|||||||
@@ -430,22 +430,17 @@ abstract class BluetoothPlugin<S, SS> implements DuplexPlugin, EventListener {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public DuplexTransportConnection createKeyAgreementConnection(
|
public DuplexTransportConnection createKeyAgreementConnection(
|
||||||
byte[] commitment, BdfList descriptor, boolean alice) {
|
byte[] commitment, BdfList descriptor) {
|
||||||
if (getState() != ACTIVE) return null;
|
if (getState() != ACTIVE) return null;
|
||||||
// No truncation necessary because COMMIT_LENGTH = 16
|
// No truncation necessary because COMMIT_LENGTH = 16
|
||||||
String uuid = UUID.nameUUIDFromBytes(commitment).toString();
|
String uuid = UUID.nameUUIDFromBytes(commitment).toString();
|
||||||
DuplexTransportConnection conn;
|
DuplexTransportConnection conn;
|
||||||
if (descriptor.size() == 1) {
|
if (descriptor.size() == 1) {
|
||||||
if (alice) {
|
if (LOG.isLoggable(INFO)) {
|
||||||
if (LOG.isLoggable(INFO)) {
|
LOG.info("Discovering address for key agreement UUID " +
|
||||||
LOG.info("Discovering address for key agreement UUID " +
|
uuid);
|
||||||
uuid);
|
|
||||||
}
|
|
||||||
conn = discoverAndConnect(uuid);
|
|
||||||
} else {
|
|
||||||
LOG.info("No address in key agreement descriptor");
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
conn = discoverAndConnect(uuid);
|
||||||
} else {
|
} else {
|
||||||
String address;
|
String address;
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -376,7 +376,7 @@ class LanTcpPlugin extends TcpPlugin {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public DuplexTransportConnection createKeyAgreementConnection(
|
public DuplexTransportConnection createKeyAgreementConnection(
|
||||||
byte[] commitment, BdfList descriptor, boolean alice) {
|
byte[] commitment, BdfList descriptor) {
|
||||||
ServerSocket ss = state.getServerSocket(true);
|
ServerSocket ss = state.getServerSocket(true);
|
||||||
if (ss == null) return null;
|
if (ss == null) return null;
|
||||||
InterfaceAddress local = getLocalInterfaceAddress(ss.getInetAddress());
|
InterfaceAddress local = getLocalInterfaceAddress(ss.getInetAddress());
|
||||||
|
|||||||
@@ -367,7 +367,7 @@ abstract class TcpPlugin implements DuplexPlugin, EventListener {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public DuplexTransportConnection createKeyAgreementConnection(
|
public DuplexTransportConnection createKeyAgreementConnection(
|
||||||
byte[] commitment, BdfList descriptor, boolean alice) {
|
byte[] commitment, BdfList descriptor) {
|
||||||
throw new UnsupportedOperationException();
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -708,7 +708,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public DuplexTransportConnection createKeyAgreementConnection(
|
public DuplexTransportConnection createKeyAgreementConnection(
|
||||||
byte[] commitment, BdfList descriptor, boolean alice) {
|
byte[] commitment, BdfList descriptor) {
|
||||||
throw new UnsupportedOperationException();
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -276,7 +276,7 @@ public class LanTcpPluginTest extends BrambleTestCase {
|
|||||||
descriptor.add(local.getPort());
|
descriptor.add(local.getPort());
|
||||||
// Connect to the port
|
// Connect to the port
|
||||||
DuplexTransportConnection d = plugin.createKeyAgreementConnection(
|
DuplexTransportConnection d = plugin.createKeyAgreementConnection(
|
||||||
new byte[COMMIT_LENGTH], descriptor, true);
|
new byte[COMMIT_LENGTH], descriptor);
|
||||||
assertNotNull(d);
|
assertNotNull(d);
|
||||||
// Check that the connection was accepted
|
// Check that the connection was accepted
|
||||||
assertTrue(latch.await(5, SECONDS));
|
assertTrue(latch.await(5, SECONDS));
|
||||||
|
|||||||
@@ -198,7 +198,7 @@ class ModemPlugin implements DuplexPlugin, Modem.Callback {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public DuplexTransportConnection createKeyAgreementConnection(
|
public DuplexTransportConnection createKeyAgreementConnection(
|
||||||
byte[] commitment, BdfList descriptor, boolean alice) {
|
byte[] commitment, BdfList descriptor) {
|
||||||
throw new UnsupportedOperationException();
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
|||||||
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;
|
||||||
import org.briarproject.briar.android.fragment.BaseFragment;
|
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
@@ -114,9 +113,7 @@ public class ContactExchangeActivity extends KeyAgreementActivity {
|
|||||||
return getString(R.string.exchanging_contact_details);
|
return getString(R.string.exchanging_contact_details);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void showErrorFragment() {
|
private void showErrorFragment() {
|
||||||
String errorMsg = getString(R.string.connection_error_explanation);
|
showNextFragment(new ContactExchangeErrorFragment());
|
||||||
BaseFragment f = ContactExchangeErrorFragment.newInstance(errorMsg);
|
|
||||||
showNextFragment(f);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package org.briarproject.briar.android.keyagreement;
|
package org.briarproject.briar.android.keyagreement;
|
||||||
|
|
||||||
|
import android.content.Intent;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
@@ -18,7 +19,10 @@ import org.briarproject.briar.android.util.UiUtils;
|
|||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
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;
|
import static org.briarproject.briar.android.util.UiUtils.onSingleLinkClick;
|
||||||
|
|
||||||
@MethodsNotNullByDefault
|
@MethodsNotNullByDefault
|
||||||
@@ -58,13 +62,12 @@ public class ContactExchangeErrorFragment extends BaseFragment {
|
|||||||
View v = inflater.inflate(R.layout.fragment_error_contact_exchange,
|
View v = inflater.inflate(R.layout.fragment_error_contact_exchange,
|
||||||
container, false);
|
container, false);
|
||||||
|
|
||||||
// set humanized error message
|
// set optional error message
|
||||||
TextView explanation = v.findViewById(R.id.errorMessage);
|
TextView explanation = v.findViewById(R.id.errorMessage);
|
||||||
Bundle args = getArguments();
|
Bundle args = getArguments();
|
||||||
if (args == null) {
|
String errorMessage = args == null ? null : args.getString(ERROR_MSG);
|
||||||
throw new IllegalArgumentException("Use newInstance()");
|
if (errorMessage == null) explanation.setVisibility(GONE);
|
||||||
}
|
else explanation.setText(args.getString(ERROR_MSG));
|
||||||
explanation.setText(args.getString(ERROR_MSG));
|
|
||||||
|
|
||||||
// make feedback link clickable
|
// make feedback link clickable
|
||||||
TextView sendFeedback = v.findViewById(R.id.sendFeedback);
|
TextView sendFeedback = v.findViewById(R.id.sendFeedback);
|
||||||
@@ -73,7 +76,11 @@ public class ContactExchangeErrorFragment extends BaseFragment {
|
|||||||
// buttons
|
// buttons
|
||||||
Button tryAgain = v.findViewById(R.id.tryAgainButton);
|
Button tryAgain = v.findViewById(R.id.tryAgainButton);
|
||||||
tryAgain.setOnClickListener(view -> {
|
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);
|
Button cancel = v.findViewById(R.id.cancelButton);
|
||||||
cancel.setOnClickListener(view -> finish());
|
cancel.setOnClickListener(view -> finish());
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ 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.keyagreement.IntroFragment.IntroScreenSeenListener;
|
import org.briarproject.briar.android.keyagreement.IntroFragment.IntroScreenSeenListener;
|
||||||
import org.briarproject.briar.android.keyagreement.KeyAgreementFragment.KeyAgreementEventListener;
|
import org.briarproject.briar.android.keyagreement.KeyAgreementFragment.KeyAgreementEventListener;
|
||||||
import org.briarproject.briar.android.util.UiUtils;
|
|
||||||
|
|
||||||
import java.util.logging.Logger;
|
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.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.content.pm.PackageManager.PERMISSION_GRANTED;
|
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.Level.INFO;
|
||||||
import static java.util.logging.Logger.getLogger;
|
import static java.util.logging.Logger.getLogger;
|
||||||
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
|
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.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_BLUETOOTH_DISCOVERABLE;
|
||||||
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;
|
||||||
|
|
||||||
@MethodsNotNullByDefault
|
@MethodsNotNullByDefault
|
||||||
@ParametersNotNullByDefault
|
@ParametersNotNullByDefault
|
||||||
@@ -133,6 +134,8 @@ public abstract class KeyAgreementActivity extends BriarActivity implements
|
|||||||
private Permission locationPermission = Permission.UNKNOWN;
|
private Permission locationPermission = Permission.UNKNOWN;
|
||||||
private BluetoothDecision bluetoothDecision = BluetoothDecision.UNKNOWN;
|
private BluetoothDecision bluetoothDecision = BluetoothDecision.UNKNOWN;
|
||||||
private BroadcastReceiver bluetoothReceiver = null;
|
private BroadcastReceiver bluetoothReceiver = null;
|
||||||
|
private Plugin wifiPlugin = null, bluetoothPlugin = null;
|
||||||
|
private BluetoothAdapter bt = null;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void injectActivity(ActivityComponent component) {
|
public void injectActivity(ActivityComponent component) {
|
||||||
@@ -152,6 +155,9 @@ public abstract class KeyAgreementActivity extends BriarActivity implements
|
|||||||
IntentFilter filter = new IntentFilter(ACTION_SCAN_MODE_CHANGED);
|
IntentFilter filter = new IntentFilter(ACTION_SCAN_MODE_CHANGED);
|
||||||
bluetoothReceiver = new BluetoothStateReceiver();
|
bluetoothReceiver = new BluetoothStateReceiver();
|
||||||
registerReceiver(bluetoothReceiver, filter);
|
registerReceiver(bluetoothReceiver, filter);
|
||||||
|
wifiPlugin = pluginManager.getPlugin(LanTcpConstants.ID);
|
||||||
|
bluetoothPlugin = pluginManager.getPlugin(BluetoothConstants.ID);
|
||||||
|
bt = BluetoothAdapter.getDefaultAdapter();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -187,6 +193,7 @@ public abstract class KeyAgreementActivity extends BriarActivity implements
|
|||||||
showQrCodeFragmentIfAllowed();
|
showQrCodeFragmentIfAllowed();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("StatementWithEmptyBody")
|
||||||
private void showQrCodeFragmentIfAllowed() {
|
private void showQrCodeFragmentIfAllowed() {
|
||||||
if (isResumed && continueClicked && areEssentialPermissionsGranted()) {
|
if (isResumed && continueClicked && areEssentialPermissionsGranted()) {
|
||||||
if (isWifiReady() && isBluetoothReady()) {
|
if (isWifiReady() && isBluetoothReady()) {
|
||||||
@@ -200,6 +207,8 @@ public abstract class KeyAgreementActivity extends BriarActivity implements
|
|||||||
}
|
}
|
||||||
if (bluetoothDecision == BluetoothDecision.UNKNOWN) {
|
if (bluetoothDecision == BluetoothDecision.UNKNOWN) {
|
||||||
requestBluetoothDiscoverable();
|
requestBluetoothDiscoverable();
|
||||||
|
} else if (bluetoothDecision == BluetoothDecision.REFUSED) {
|
||||||
|
// Ask again when the user clicks "continue"
|
||||||
} else if (shouldEnableBluetooth()) {
|
} else if (shouldEnableBluetooth()) {
|
||||||
LOG.info("Enabling Bluetooth plugin");
|
LOG.info("Enabling Bluetooth plugin");
|
||||||
hasEnabledBluetooth = true;
|
hasEnabledBluetooth = true;
|
||||||
@@ -210,55 +219,50 @@ public abstract class KeyAgreementActivity extends BriarActivity implements
|
|||||||
}
|
}
|
||||||
|
|
||||||
private boolean areEssentialPermissionsGranted() {
|
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 &&
|
return cameraPermission == Permission.GRANTED &&
|
||||||
(locationPermission == Permission.GRANTED ||
|
(SDK_INT < 23 || locationPermission == Permission.GRANTED ||
|
||||||
locationPermission == Permission.PERMANENTLY_DENIED);
|
!isBluetoothSupported());
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isBluetoothSupported() {
|
||||||
|
return bt != null && bluetoothPlugin != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isWifiReady() {
|
private boolean isWifiReady() {
|
||||||
Plugin p = pluginManager.getPlugin(LanTcpConstants.ID);
|
if (wifiPlugin == null) return true; // Continue without wifi
|
||||||
if (p == null) return true; // Continue without wifi
|
State state = wifiPlugin.getState();
|
||||||
State state = p.getState();
|
|
||||||
// Wait for plugin to become enabled
|
// Wait for plugin to become enabled
|
||||||
return state == ACTIVE || state == INACTIVE;
|
return state == ACTIVE || state == INACTIVE;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isBluetoothReady() {
|
private boolean isBluetoothReady() {
|
||||||
if (bluetoothDecision == BluetoothDecision.UNKNOWN ||
|
if (!isBluetoothSupported()) {
|
||||||
bluetoothDecision == BluetoothDecision.WAITING) {
|
|
||||||
// Wait for decision
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (bluetoothDecision == BluetoothDecision.NO_ADAPTER
|
|
||||||
|| bluetoothDecision == BluetoothDecision.REFUSED) {
|
|
||||||
// Continue without Bluetooth
|
// Continue without Bluetooth
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
BluetoothAdapter bt = BluetoothAdapter.getDefaultAdapter();
|
if (bluetoothDecision == BluetoothDecision.UNKNOWN ||
|
||||||
if (bt == null) return true; // Continue without Bluetooth
|
bluetoothDecision == BluetoothDecision.WAITING ||
|
||||||
|
bluetoothDecision == BluetoothDecision.REFUSED) {
|
||||||
|
// Wait for user to accept
|
||||||
|
return false;
|
||||||
|
}
|
||||||
if (bt.getScanMode() != SCAN_MODE_CONNECTABLE_DISCOVERABLE) {
|
if (bt.getScanMode() != SCAN_MODE_CONNECTABLE_DISCOVERABLE) {
|
||||||
// Wait for adapter to become discoverable
|
// Wait for adapter to become discoverable
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
Plugin p = pluginManager.getPlugin(BluetoothConstants.ID);
|
|
||||||
if (p == null) return true; // Continue without Bluetooth
|
|
||||||
// Wait for plugin to become active
|
// Wait for plugin to become active
|
||||||
return p.getState() == ACTIVE;
|
return bluetoothPlugin.getState() == ACTIVE;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean shouldEnableWifi() {
|
private boolean shouldEnableWifi() {
|
||||||
if (hasEnabledWifi) return false;
|
if (hasEnabledWifi) return false;
|
||||||
Plugin p = pluginManager.getPlugin(LanTcpConstants.ID);
|
if (wifiPlugin == null) return false;
|
||||||
if (p == null) return false;
|
State state = wifiPlugin.getState();
|
||||||
State state = p.getState();
|
|
||||||
return state == STARTING_STOPPING || state == DISABLED;
|
return state == STARTING_STOPPING || state == DISABLED;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void requestBluetoothDiscoverable() {
|
private void requestBluetoothDiscoverable() {
|
||||||
BluetoothAdapter bt = BluetoothAdapter.getDefaultAdapter();
|
if (!isBluetoothSupported()) {
|
||||||
if (bt == null) {
|
|
||||||
bluetoothDecision = BluetoothDecision.NO_ADAPTER;
|
bluetoothDecision = BluetoothDecision.NO_ADAPTER;
|
||||||
showQrCodeFragmentIfAllowed();
|
showQrCodeFragmentIfAllowed();
|
||||||
} else {
|
} else {
|
||||||
@@ -277,9 +281,8 @@ public abstract class KeyAgreementActivity extends BriarActivity implements
|
|||||||
private boolean shouldEnableBluetooth() {
|
private boolean shouldEnableBluetooth() {
|
||||||
if (bluetoothDecision != BluetoothDecision.ACCEPTED) return false;
|
if (bluetoothDecision != BluetoothDecision.ACCEPTED) return false;
|
||||||
if (hasEnabledBluetooth) return false;
|
if (hasEnabledBluetooth) return false;
|
||||||
Plugin p = pluginManager.getPlugin(BluetoothConstants.ID);
|
if (!isBluetoothSupported()) return false;
|
||||||
if (p == null) return false;
|
State state = bluetoothPlugin.getState();
|
||||||
State state = p.getState();
|
|
||||||
return state == STARTING_STOPPING || state == DISABLED;
|
return state == STARTING_STOPPING || state == DISABLED;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -298,6 +301,9 @@ public abstract class KeyAgreementActivity extends BriarActivity implements
|
|||||||
@Override
|
@Override
|
||||||
public void showNextScreen() {
|
public void showNextScreen() {
|
||||||
continueClicked = true;
|
continueClicked = true;
|
||||||
|
if (bluetoothDecision == BluetoothDecision.REFUSED) {
|
||||||
|
bluetoothDecision = BluetoothDecision.UNKNOWN; // Ask again
|
||||||
|
}
|
||||||
if (checkPermissions()) showQrCodeFragmentIfAllowed();
|
if (checkPermissions()) showQrCodeFragmentIfAllowed();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -341,17 +347,17 @@ public abstract class KeyAgreementActivity extends BriarActivity implements
|
|||||||
|
|
||||||
private boolean checkPermissions() {
|
private boolean checkPermissions() {
|
||||||
if (areEssentialPermissionsGranted()) return true;
|
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
|
// user to change the setting
|
||||||
if (cameraPermission == Permission.PERMANENTLY_DENIED) {
|
if (cameraPermission == Permission.PERMANENTLY_DENIED) {
|
||||||
Builder builder = new Builder(this, R.style.BriarDialogTheme);
|
showDenialDialog(R.string.permission_camera_title,
|
||||||
builder.setTitle(R.string.permission_camera_title);
|
R.string.permission_camera_denied_body);
|
||||||
builder.setMessage(R.string.permission_camera_denied_body);
|
return false;
|
||||||
builder.setPositiveButton(R.string.ok,
|
}
|
||||||
UiUtils.getGoToSettingsListener(this));
|
if (isBluetoothSupported() &&
|
||||||
builder.setNegativeButton(R.string.cancel,
|
locationPermission == Permission.PERMANENTLY_DENIED) {
|
||||||
(dialog, which) -> supportFinishAfterTransition());
|
showDenialDialog(R.string.permission_location_title,
|
||||||
builder.show();
|
R.string.permission_location_denied_body);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// Should we show the rationale for one or both permissions?
|
// Should we show the rationale for one or both permissions?
|
||||||
@@ -371,6 +377,16 @@ public abstract class KeyAgreementActivity extends BriarActivity implements
|
|||||||
return false;
|
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) {
|
private void showRationale(@StringRes int title, @StringRes int body) {
|
||||||
Builder builder = new Builder(this, R.style.BriarDialogTheme);
|
Builder builder = new Builder(this, R.style.BriarDialogTheme);
|
||||||
builder.setTitle(title);
|
builder.setTitle(title);
|
||||||
@@ -381,8 +397,13 @@ public abstract class KeyAgreementActivity extends BriarActivity implements
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void requestPermissions() {
|
private void requestPermissions() {
|
||||||
ActivityCompat.requestPermissions(this,
|
String[] permissions;
|
||||||
new String[] {CAMERA, ACCESS_FINE_LOCATION},
|
if (isBluetoothSupported()) {
|
||||||
|
permissions = new String[] {CAMERA, ACCESS_FINE_LOCATION};
|
||||||
|
} else {
|
||||||
|
permissions = new String[] {CAMERA};
|
||||||
|
}
|
||||||
|
ActivityCompat.requestPermissions(this, permissions,
|
||||||
REQUEST_PERMISSION_CAMERA_LOCATION);
|
REQUEST_PERMISSION_CAMERA_LOCATION);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -399,12 +420,15 @@ public abstract class KeyAgreementActivity extends BriarActivity implements
|
|||||||
} else {
|
} else {
|
||||||
cameraPermission = Permission.PERMANENTLY_DENIED;
|
cameraPermission = Permission.PERMANENTLY_DENIED;
|
||||||
}
|
}
|
||||||
if (gotPermission(ACCESS_FINE_LOCATION, permissions, grantResults)) {
|
if (isBluetoothSupported()) {
|
||||||
locationPermission = Permission.GRANTED;
|
if (gotPermission(ACCESS_FINE_LOCATION, permissions,
|
||||||
} else if (shouldShowRationale(ACCESS_FINE_LOCATION)) {
|
grantResults)) {
|
||||||
locationPermission = Permission.SHOW_RATIONALE;
|
locationPermission = Permission.GRANTED;
|
||||||
} else {
|
} else if (shouldShowRationale(ACCESS_FINE_LOCATION)) {
|
||||||
locationPermission = Permission.PERMANENTLY_DENIED;
|
locationPermission = Permission.SHOW_RATIONALE;
|
||||||
|
} else {
|
||||||
|
locationPermission = Permission.PERMANENTLY_DENIED;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// If a permission dialog has been shown, showing the QR code fragment
|
// If a permission dialog has been shown, showing the QR code fragment
|
||||||
// on this call path would cause a crash due to
|
// on this call path would cause a crash due to
|
||||||
|
|||||||
@@ -210,7 +210,6 @@
|
|||||||
<string name="connecting_to_device">Connecting to device\u2026</string>
|
<string name="connecting_to_device">Connecting to device\u2026</string>
|
||||||
<string name="authenticating_with_device">Authenticating with device\u2026</string>
|
<string name="authenticating_with_device">Authenticating with device\u2026</string>
|
||||||
<string name="connection_error_title">Could not connect to your contact</string>
|
<string name="connection_error_title">Could not connect to your contact</string>
|
||||||
<string name="connection_error_explanation">Please check that you\'re both connected to the same Wi-Fi network.</string>
|
|
||||||
<string name="connection_error_feedback">If this problem persists, please <a href="feedback">send feedback</a> to help us improve the app.</string>
|
<string name="connection_error_feedback">If this problem persists, please <a href="feedback">send feedback</a> to help us improve the app.</string>
|
||||||
|
|
||||||
<!-- Adding Contacts Remotely -->
|
<!-- Adding Contacts Remotely -->
|
||||||
@@ -589,6 +588,7 @@
|
|||||||
<string name="permission_camera_location_title">Camera and location</string>
|
<string name="permission_camera_location_title">Camera and location</string>
|
||||||
<string name="permission_camera_location_request_body">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.</string>
|
<string name="permission_camera_location_request_body">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.</string>
|
||||||
<string name="permission_camera_denied_body">You have denied access to the camera, but adding contacts requires using the camera.\n\nPlease consider granting access.</string>
|
<string name="permission_camera_denied_body">You have denied access to the camera, but adding contacts requires using the camera.\n\nPlease consider granting access.</string>
|
||||||
|
<string name="permission_location_denied_body">You have denied access to your location, but Briar needs this permission to discover Bluetooth devices.\n\nPlease consider granting access.</string>
|
||||||
<string name="qr_code">QR code</string>
|
<string name="qr_code">QR code</string>
|
||||||
<string name="show_qr_code_fullscreen">Show QR code fullscreen</string>
|
<string name="show_qr_code_fullscreen">Show QR code fullscreen</string>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user