mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-18 21:59:54 +01:00
Merge branch '1147-bluetooth-discovery' into 'master'
Support Bluetooth discovery for adding contacts See merge request briar/briar!954
This commit is contained in:
@@ -16,18 +16,27 @@ import org.briarproject.bramble.api.plugin.PluginException;
|
|||||||
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginCallback;
|
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginCallback;
|
||||||
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
||||||
import org.briarproject.bramble.api.system.AndroidExecutor;
|
import org.briarproject.bramble.api.system.AndroidExecutor;
|
||||||
|
import org.briarproject.bramble.api.system.Clock;
|
||||||
import org.briarproject.bramble.util.AndroidUtils;
|
import org.briarproject.bramble.util.AndroidUtils;
|
||||||
|
|
||||||
import java.io.Closeable;
|
import java.io.Closeable;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.BlockingQueue;
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
|
import java.util.concurrent.LinkedBlockingQueue;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
import static android.bluetooth.BluetoothAdapter.ACTION_DISCOVERY_FINISHED;
|
||||||
|
import static android.bluetooth.BluetoothAdapter.ACTION_DISCOVERY_STARTED;
|
||||||
import static android.bluetooth.BluetoothAdapter.ACTION_SCAN_MODE_CHANGED;
|
import static android.bluetooth.BluetoothAdapter.ACTION_SCAN_MODE_CHANGED;
|
||||||
import static android.bluetooth.BluetoothAdapter.ACTION_STATE_CHANGED;
|
import static android.bluetooth.BluetoothAdapter.ACTION_STATE_CHANGED;
|
||||||
import static android.bluetooth.BluetoothAdapter.EXTRA_SCAN_MODE;
|
import static android.bluetooth.BluetoothAdapter.EXTRA_SCAN_MODE;
|
||||||
@@ -37,8 +46,13 @@ import static android.bluetooth.BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERA
|
|||||||
import static android.bluetooth.BluetoothAdapter.SCAN_MODE_NONE;
|
import static android.bluetooth.BluetoothAdapter.SCAN_MODE_NONE;
|
||||||
import static android.bluetooth.BluetoothAdapter.STATE_OFF;
|
import static android.bluetooth.BluetoothAdapter.STATE_OFF;
|
||||||
import static android.bluetooth.BluetoothAdapter.STATE_ON;
|
import static android.bluetooth.BluetoothAdapter.STATE_ON;
|
||||||
|
import static android.bluetooth.BluetoothDevice.ACTION_FOUND;
|
||||||
|
import static android.bluetooth.BluetoothDevice.EXTRA_DEVICE;
|
||||||
|
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||||
|
import static java.util.logging.Level.INFO;
|
||||||
import static java.util.logging.Level.WARNING;
|
import static java.util.logging.Level.WARNING;
|
||||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||||
|
import static org.briarproject.bramble.util.PrivacyUtils.scrubMacAddress;
|
||||||
|
|
||||||
@MethodsNotNullByDefault
|
@MethodsNotNullByDefault
|
||||||
@ParametersNotNullByDefault
|
@ParametersNotNullByDefault
|
||||||
@@ -47,8 +61,11 @@ class AndroidBluetoothPlugin extends BluetoothPlugin<BluetoothServerSocket> {
|
|||||||
private static final Logger LOG =
|
private static final Logger LOG =
|
||||||
Logger.getLogger(AndroidBluetoothPlugin.class.getName());
|
Logger.getLogger(AndroidBluetoothPlugin.class.getName());
|
||||||
|
|
||||||
|
private static final int MAX_DISCOVERY_MS = 10_000;
|
||||||
|
|
||||||
private final AndroidExecutor androidExecutor;
|
private final AndroidExecutor androidExecutor;
|
||||||
private final Context appContext;
|
private final Context appContext;
|
||||||
|
private final Clock clock;
|
||||||
|
|
||||||
private volatile boolean wasEnabledByUs = false;
|
private volatile boolean wasEnabledByUs = false;
|
||||||
private volatile BluetoothStateReceiver receiver = null;
|
private volatile BluetoothStateReceiver receiver = null;
|
||||||
@@ -58,12 +75,13 @@ class AndroidBluetoothPlugin extends BluetoothPlugin<BluetoothServerSocket> {
|
|||||||
|
|
||||||
AndroidBluetoothPlugin(BluetoothConnectionLimiter connectionLimiter,
|
AndroidBluetoothPlugin(BluetoothConnectionLimiter connectionLimiter,
|
||||||
Executor ioExecutor, AndroidExecutor androidExecutor,
|
Executor ioExecutor, AndroidExecutor androidExecutor,
|
||||||
Context appContext, SecureRandom secureRandom, Backoff backoff,
|
Context appContext, SecureRandom secureRandom, Clock clock,
|
||||||
DuplexPluginCallback callback, int maxLatency) {
|
Backoff backoff, DuplexPluginCallback callback, int maxLatency) {
|
||||||
super(connectionLimiter, ioExecutor, secureRandom, backoff, callback,
|
super(connectionLimiter, ioExecutor, secureRandom, backoff, callback,
|
||||||
maxLatency);
|
maxLatency);
|
||||||
this.androidExecutor = androidExecutor;
|
this.androidExecutor = androidExecutor;
|
||||||
this.appContext = appContext;
|
this.appContext = appContext;
|
||||||
|
this.clock = clock;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -182,6 +200,74 @@ class AndroidBluetoothPlugin extends BluetoothPlugin<BluetoothServerSocket> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Nullable
|
||||||
|
DuplexTransportConnection discoverAndConnect(String uuid) {
|
||||||
|
if (adapter == null) return null;
|
||||||
|
for (String address : discoverDevices()) {
|
||||||
|
try {
|
||||||
|
if (LOG.isLoggable(INFO))
|
||||||
|
LOG.info("Connecting to " + scrubMacAddress(address));
|
||||||
|
return connectTo(address, uuid);
|
||||||
|
} catch (IOException e) {
|
||||||
|
if (LOG.isLoggable(INFO)) {
|
||||||
|
LOG.info("Could not connect to "
|
||||||
|
+ scrubMacAddress(address));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LOG.info("Could not connect to any devices");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Collection<String> discoverDevices() {
|
||||||
|
List<String> addresses = new ArrayList<>();
|
||||||
|
BlockingQueue<Intent> intents = new LinkedBlockingQueue<>();
|
||||||
|
DiscoveryReceiver receiver = new DiscoveryReceiver(intents);
|
||||||
|
IntentFilter filter = new IntentFilter();
|
||||||
|
filter.addAction(ACTION_DISCOVERY_STARTED);
|
||||||
|
filter.addAction(ACTION_DISCOVERY_FINISHED);
|
||||||
|
filter.addAction(ACTION_FOUND);
|
||||||
|
appContext.registerReceiver(receiver, filter);
|
||||||
|
try {
|
||||||
|
if (adapter.startDiscovery()) {
|
||||||
|
long now = clock.currentTimeMillis();
|
||||||
|
long end = now + MAX_DISCOVERY_MS;
|
||||||
|
while (now < end) {
|
||||||
|
Intent i = intents.poll(end - now, MILLISECONDS);
|
||||||
|
if (i == null) break;
|
||||||
|
String action = i.getAction();
|
||||||
|
if (ACTION_DISCOVERY_STARTED.equals(action)) {
|
||||||
|
LOG.info("Discovery started");
|
||||||
|
} else if (ACTION_DISCOVERY_FINISHED.equals(action)) {
|
||||||
|
LOG.info("Discovery finished");
|
||||||
|
break;
|
||||||
|
} else if (ACTION_FOUND.equals(action)) {
|
||||||
|
BluetoothDevice d = i.getParcelableExtra(EXTRA_DEVICE);
|
||||||
|
String address = d.getAddress();
|
||||||
|
if (LOG.isLoggable(INFO))
|
||||||
|
LOG.info("Discovered " + scrubMacAddress(address));
|
||||||
|
if (!addresses.contains(address))
|
||||||
|
addresses.add(address);
|
||||||
|
}
|
||||||
|
now = clock.currentTimeMillis();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
LOG.info("Could not start discovery");
|
||||||
|
}
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
LOG.info("Interrupted while discovering devices");
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
} finally {
|
||||||
|
LOG.info("Cancelling discovery");
|
||||||
|
adapter.cancelDiscovery();
|
||||||
|
appContext.unregisterReceiver(receiver);
|
||||||
|
}
|
||||||
|
// Shuffle the addresses so we don't always try the same one first
|
||||||
|
Collections.shuffle(addresses);
|
||||||
|
return addresses;
|
||||||
|
}
|
||||||
|
|
||||||
private void tryToClose(@Nullable Closeable c) {
|
private void tryToClose(@Nullable Closeable c) {
|
||||||
try {
|
try {
|
||||||
if (c != null) c.close();
|
if (c != null) c.close();
|
||||||
@@ -207,4 +293,18 @@ class AndroidBluetoothPlugin extends BluetoothPlugin<BluetoothServerSocket> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static class DiscoveryReceiver extends BroadcastReceiver {
|
||||||
|
|
||||||
|
private final BlockingQueue<Intent> intents;
|
||||||
|
|
||||||
|
private DiscoveryReceiver(BlockingQueue<Intent> intents) {
|
||||||
|
this.intents = intents;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onReceive(Context ctx, Intent intent) {
|
||||||
|
intents.add(intent);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
|
|||||||
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginCallback;
|
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginCallback;
|
||||||
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginFactory;
|
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginFactory;
|
||||||
import org.briarproject.bramble.api.system.AndroidExecutor;
|
import org.briarproject.bramble.api.system.AndroidExecutor;
|
||||||
|
import org.briarproject.bramble.api.system.Clock;
|
||||||
|
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
@@ -33,17 +34,19 @@ public class AndroidBluetoothPluginFactory implements DuplexPluginFactory {
|
|||||||
private final Context appContext;
|
private final Context appContext;
|
||||||
private final SecureRandom secureRandom;
|
private final SecureRandom secureRandom;
|
||||||
private final EventBus eventBus;
|
private final EventBus eventBus;
|
||||||
|
private final Clock clock;
|
||||||
private final BackoffFactory backoffFactory;
|
private final BackoffFactory backoffFactory;
|
||||||
|
|
||||||
public AndroidBluetoothPluginFactory(Executor ioExecutor,
|
public AndroidBluetoothPluginFactory(Executor ioExecutor,
|
||||||
AndroidExecutor androidExecutor, Context appContext,
|
AndroidExecutor androidExecutor, Context appContext,
|
||||||
SecureRandom secureRandom, EventBus eventBus,
|
SecureRandom secureRandom, EventBus eventBus, Clock clock,
|
||||||
BackoffFactory backoffFactory) {
|
BackoffFactory backoffFactory) {
|
||||||
this.ioExecutor = ioExecutor;
|
this.ioExecutor = ioExecutor;
|
||||||
this.androidExecutor = androidExecutor;
|
this.androidExecutor = androidExecutor;
|
||||||
this.appContext = appContext;
|
this.appContext = appContext;
|
||||||
this.secureRandom = secureRandom;
|
this.secureRandom = secureRandom;
|
||||||
this.eventBus = eventBus;
|
this.eventBus = eventBus;
|
||||||
|
this.clock = clock;
|
||||||
this.backoffFactory = backoffFactory;
|
this.backoffFactory = backoffFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,7 +68,7 @@ public class AndroidBluetoothPluginFactory implements DuplexPluginFactory {
|
|||||||
MAX_POLLING_INTERVAL, BACKOFF_BASE);
|
MAX_POLLING_INTERVAL, BACKOFF_BASE);
|
||||||
AndroidBluetoothPlugin plugin = new AndroidBluetoothPlugin(
|
AndroidBluetoothPlugin plugin = new AndroidBluetoothPlugin(
|
||||||
connectionLimiter, ioExecutor, androidExecutor, appContext,
|
connectionLimiter, ioExecutor, androidExecutor, appContext,
|
||||||
secureRandom, backoff, callback, MAX_LATENCY);
|
secureRandom, clock, backoff, callback, MAX_LATENCY);
|
||||||
eventBus.addListener(plugin);
|
eventBus.addListener(plugin);
|
||||||
return plugin;
|
return plugin;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ public interface KeyAgreementConstants {
|
|||||||
/**
|
/**
|
||||||
* The connection timeout in milliseconds.
|
* The connection timeout in milliseconds.
|
||||||
*/
|
*/
|
||||||
long CONNECTION_TIMEOUT = 20 * 1000;
|
long CONNECTION_TIMEOUT = 60_000;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The transport identifier for Bluetooth.
|
* The transport identifier for Bluetooth.
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ import org.briarproject.bramble.api.plugin.event.EnableBluetoothEvent;
|
|||||||
import org.briarproject.bramble.api.properties.TransportProperties;
|
import org.briarproject.bramble.api.properties.TransportProperties;
|
||||||
import org.briarproject.bramble.api.settings.Settings;
|
import org.briarproject.bramble.api.settings.Settings;
|
||||||
import org.briarproject.bramble.api.settings.event.SettingsUpdatedEvent;
|
import org.briarproject.bramble.api.settings.event.SettingsUpdatedEvent;
|
||||||
import org.briarproject.bramble.util.StringUtils;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
@@ -46,6 +45,9 @@ import static org.briarproject.bramble.api.plugin.BluetoothConstants.PROP_UUID;
|
|||||||
import static org.briarproject.bramble.api.plugin.BluetoothConstants.UUID_BYTES;
|
import static org.briarproject.bramble.api.plugin.BluetoothConstants.UUID_BYTES;
|
||||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||||
import static org.briarproject.bramble.util.PrivacyUtils.scrubMacAddress;
|
import static org.briarproject.bramble.util.PrivacyUtils.scrubMacAddress;
|
||||||
|
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
|
||||||
|
import static org.briarproject.bramble.util.StringUtils.macToBytes;
|
||||||
|
import static org.briarproject.bramble.util.StringUtils.macToString;
|
||||||
|
|
||||||
@MethodsNotNullByDefault
|
@MethodsNotNullByDefault
|
||||||
@ParametersNotNullByDefault
|
@ParametersNotNullByDefault
|
||||||
@@ -96,6 +98,9 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
|
|||||||
abstract DuplexTransportConnection connectTo(String address, String uuid)
|
abstract DuplexTransportConnection connectTo(String address, String uuid)
|
||||||
throws IOException;
|
throws IOException;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
abstract DuplexTransportConnection discoverAndConnect(String uuid);
|
||||||
|
|
||||||
BluetoothPlugin(BluetoothConnectionLimiter connectionLimiter,
|
BluetoothPlugin(BluetoothConnectionLimiter connectionLimiter,
|
||||||
Executor ioExecutor, SecureRandom secureRandom,
|
Executor ioExecutor, SecureRandom secureRandom,
|
||||||
Backoff backoff, DuplexPluginCallback callback, int maxLatency) {
|
Backoff backoff, DuplexPluginCallback callback, int maxLatency) {
|
||||||
@@ -193,7 +198,7 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
|
|||||||
address = getBluetoothAddress();
|
address = getBluetoothAddress();
|
||||||
if (LOG.isLoggable(INFO))
|
if (LOG.isLoggable(INFO))
|
||||||
LOG.info("Local address " + scrubMacAddress(address));
|
LOG.info("Local address " + scrubMacAddress(address));
|
||||||
if (!StringUtils.isNullOrEmpty(address)) {
|
if (!isNullOrEmpty(address)) {
|
||||||
p.put(PROP_ADDRESS, address);
|
p.put(PROP_ADDRESS, address);
|
||||||
changed = true;
|
changed = true;
|
||||||
}
|
}
|
||||||
@@ -256,9 +261,9 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
|
|||||||
// Try to connect to known devices in parallel
|
// Try to connect to known devices in parallel
|
||||||
for (Entry<ContactId, TransportProperties> e : contacts.entrySet()) {
|
for (Entry<ContactId, TransportProperties> e : contacts.entrySet()) {
|
||||||
String address = e.getValue().get(PROP_ADDRESS);
|
String address = e.getValue().get(PROP_ADDRESS);
|
||||||
if (StringUtils.isNullOrEmpty(address)) continue;
|
if (isNullOrEmpty(address)) continue;
|
||||||
String uuid = e.getValue().get(PROP_UUID);
|
String uuid = e.getValue().get(PROP_UUID);
|
||||||
if (StringUtils.isNullOrEmpty(uuid)) continue;
|
if (isNullOrEmpty(uuid)) continue;
|
||||||
ContactId c = e.getKey();
|
ContactId c = e.getKey();
|
||||||
ioExecutor.execute(() -> {
|
ioExecutor.execute(() -> {
|
||||||
if (!isRunning() || !shouldAllowContactConnections()) return;
|
if (!isRunning() || !shouldAllowContactConnections()) return;
|
||||||
@@ -309,9 +314,9 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
|
|||||||
if (!isRunning() || !shouldAllowContactConnections()) return null;
|
if (!isRunning() || !shouldAllowContactConnections()) return null;
|
||||||
if (!connectionLimiter.canOpenContactConnection()) return null;
|
if (!connectionLimiter.canOpenContactConnection()) return null;
|
||||||
String address = p.get(PROP_ADDRESS);
|
String address = p.get(PROP_ADDRESS);
|
||||||
if (StringUtils.isNullOrEmpty(address)) return null;
|
if (isNullOrEmpty(address)) return null;
|
||||||
String uuid = p.get(PROP_UUID);
|
String uuid = p.get(PROP_UUID);
|
||||||
if (StringUtils.isNullOrEmpty(uuid)) return null;
|
if (isNullOrEmpty(uuid)) return null;
|
||||||
DuplexTransportConnection conn = connect(address, uuid);
|
DuplexTransportConnection conn = connect(address, uuid);
|
||||||
if (conn == null) return null;
|
if (conn == null) return null;
|
||||||
// TODO: Why don't we reset the backoff here?
|
// TODO: Why don't we reset the backoff here?
|
||||||
@@ -326,9 +331,6 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
|
|||||||
@Override
|
@Override
|
||||||
public KeyAgreementListener createKeyAgreementListener(byte[] commitment) {
|
public KeyAgreementListener createKeyAgreementListener(byte[] commitment) {
|
||||||
if (!isRunning()) return null;
|
if (!isRunning()) return null;
|
||||||
// There's no point listening if we can't discover our own address
|
|
||||||
String address = getBluetoothAddress();
|
|
||||||
if (address == null) 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();
|
||||||
if (LOG.isLoggable(INFO)) LOG.info("Key agreement UUID " + uuid);
|
if (LOG.isLoggable(INFO)) LOG.info("Key agreement UUID " + uuid);
|
||||||
@@ -346,7 +348,8 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
|
|||||||
}
|
}
|
||||||
BdfList descriptor = new BdfList();
|
BdfList descriptor = new BdfList();
|
||||||
descriptor.add(TRANSPORT_ID_BLUETOOTH);
|
descriptor.add(TRANSPORT_ID_BLUETOOTH);
|
||||||
descriptor.add(StringUtils.macToBytes(address));
|
String address = getBluetoothAddress();
|
||||||
|
if (address != null) descriptor.add(macToBytes(address));
|
||||||
return new BluetoothKeyAgreementListener(descriptor, ss);
|
return new BluetoothKeyAgreementListener(descriptor, ss);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -354,18 +357,25 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
|
|||||||
public DuplexTransportConnection createKeyAgreementConnection(
|
public DuplexTransportConnection createKeyAgreementConnection(
|
||||||
byte[] commitment, BdfList descriptor) {
|
byte[] commitment, BdfList descriptor) {
|
||||||
if (!isRunning()) return null;
|
if (!isRunning()) return null;
|
||||||
String address;
|
|
||||||
try {
|
|
||||||
address = parseAddress(descriptor);
|
|
||||||
} catch (FormatException e) {
|
|
||||||
LOG.info("Invalid address in key agreement descriptor");
|
|
||||||
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();
|
||||||
if (LOG.isLoggable(INFO))
|
DuplexTransportConnection conn;
|
||||||
LOG.info("Connecting to key agreement UUID " + uuid);
|
if (descriptor.size() == 1) {
|
||||||
DuplexTransportConnection conn = connect(address, uuid);
|
if (LOG.isLoggable(INFO))
|
||||||
|
LOG.info("Discovering address for key agreement UUID " + uuid);
|
||||||
|
conn = discoverAndConnect(uuid);
|
||||||
|
} else {
|
||||||
|
String address;
|
||||||
|
try {
|
||||||
|
address = parseAddress(descriptor);
|
||||||
|
} catch (FormatException e) {
|
||||||
|
LOG.info("Invalid address in key agreement descriptor");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (LOG.isLoggable(INFO))
|
||||||
|
LOG.info("Connecting to key agreement UUID " + uuid);
|
||||||
|
conn = connect(address, uuid);
|
||||||
|
}
|
||||||
if (conn != null) connectionLimiter.keyAgreementConnectionOpened(conn);
|
if (conn != null) connectionLimiter.keyAgreementConnectionOpened(conn);
|
||||||
return conn;
|
return conn;
|
||||||
}
|
}
|
||||||
@@ -373,7 +383,7 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
|
|||||||
private String parseAddress(BdfList descriptor) throws FormatException {
|
private String parseAddress(BdfList descriptor) throws FormatException {
|
||||||
byte[] mac = descriptor.getRaw(1);
|
byte[] mac = descriptor.getRaw(1);
|
||||||
if (mac.length != 6) throw new FormatException();
|
if (mac.length != 6) throw new FormatException();
|
||||||
return StringUtils.macToString(mac);
|
return macToString(mac);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -108,6 +108,12 @@ class JavaBluetoothPlugin extends BluetoothPlugin<StreamConnectionNotifier> {
|
|||||||
return wrapSocket((StreamConnection) Connector.open(url));
|
return wrapSocket((StreamConnection) Connector.open(url));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Nullable
|
||||||
|
DuplexTransportConnection discoverAndConnect(String uuid) {
|
||||||
|
return null; // TODO
|
||||||
|
}
|
||||||
|
|
||||||
private String makeUrl(String address, String uuid) {
|
private String makeUrl(String address, String uuid) {
|
||||||
return "btspp://" + address + ":" + uuid + ";name=RFCOMM";
|
return "btspp://" + address + ":" + uuid + ";name=RFCOMM";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
<uses-feature android:name="android.hardware.camera" android:required="false"/>
|
<uses-feature android:name="android.hardware.camera" android:required="false"/>
|
||||||
<uses-feature android:name="android.hardware.touchscreen" android:required="false" />
|
<uses-feature android:name="android.hardware.touchscreen" android:required="false" />
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||||
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
||||||
<uses-permission android:name="android.permission.BLUETOOTH" />
|
<uses-permission android:name="android.permission.BLUETOOTH" />
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ public class AppModule {
|
|||||||
Context appContext = app.getApplicationContext();
|
Context appContext = app.getApplicationContext();
|
||||||
DuplexPluginFactory bluetooth =
|
DuplexPluginFactory bluetooth =
|
||||||
new AndroidBluetoothPluginFactory(ioExecutor, androidExecutor,
|
new AndroidBluetoothPluginFactory(ioExecutor, androidExecutor,
|
||||||
appContext, random, eventBus, backoffFactory);
|
appContext, random, eventBus, clock, backoffFactory);
|
||||||
DuplexPluginFactory tor = new AndroidTorPluginFactory(ioExecutor,
|
DuplexPluginFactory tor = new AndroidTorPluginFactory(ioExecutor,
|
||||||
scheduler, appContext, networkManager, locationUtils, eventBus,
|
scheduler, appContext, networkManager, locationUtils, eventBus,
|
||||||
torSocketFactory, backoffFactory, resourceProvider,
|
torSocketFactory, backoffFactory, resourceProvider,
|
||||||
|
|||||||
@@ -9,9 +9,9 @@ public interface RequestCodes {
|
|||||||
int REQUEST_WRITE_BLOG_POST = 5;
|
int REQUEST_WRITE_BLOG_POST = 5;
|
||||||
int REQUEST_SHARE_BLOG = 6;
|
int REQUEST_SHARE_BLOG = 6;
|
||||||
int REQUEST_RINGTONE = 7;
|
int REQUEST_RINGTONE = 7;
|
||||||
int REQUEST_PERMISSION_CAMERA = 8;
|
int REQUEST_PERMISSION_CAMERA_LOCATION = 8;
|
||||||
int REQUEST_DOZE_WHITELISTING = 9;
|
int REQUEST_DOZE_WHITELISTING = 9;
|
||||||
int REQUEST_ENABLE_BLUETOOTH = 10;
|
int REQUEST_BLUETOOTH_DISCOVERABLE = 10;
|
||||||
int REQUEST_UNLOCK = 11;
|
int REQUEST_UNLOCK = 11;
|
||||||
int REQUEST_KEYGUARD_UNLOCK = 12;
|
int REQUEST_KEYGUARD_UNLOCK = 12;
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package org.briarproject.briar.android.keyagreement;
|
|||||||
import android.bluetooth.BluetoothAdapter;
|
import android.bluetooth.BluetoothAdapter;
|
||||||
import android.content.BroadcastReceiver;
|
import android.content.BroadcastReceiver;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.DialogInterface.OnClickListener;
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.IntentFilter;
|
import android.content.IntentFilter;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
@@ -11,19 +10,15 @@ import android.support.annotation.StringRes;
|
|||||||
import android.support.annotation.UiThread;
|
import android.support.annotation.UiThread;
|
||||||
import android.support.v4.app.ActivityCompat;
|
import android.support.v4.app.ActivityCompat;
|
||||||
import android.support.v4.app.FragmentManager;
|
import android.support.v4.app.FragmentManager;
|
||||||
import android.support.v4.content.ContextCompat;
|
|
||||||
import android.support.v7.app.AlertDialog.Builder;
|
import android.support.v7.app.AlertDialog.Builder;
|
||||||
import android.support.v7.widget.Toolbar;
|
import android.support.v7.widget.Toolbar;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.widget.Toast;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.event.EventBus;
|
import org.briarproject.bramble.api.event.EventBus;
|
||||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||||
import org.briarproject.bramble.api.plugin.event.BluetoothEnabledEvent;
|
import org.briarproject.bramble.api.plugin.event.BluetoothEnabledEvent;
|
||||||
import org.briarproject.briar.R;
|
import org.briarproject.briar.R;
|
||||||
import org.briarproject.briar.R.string;
|
|
||||||
import org.briarproject.briar.R.style;
|
|
||||||
import org.briarproject.briar.android.activity.ActivityComponent;
|
import org.briarproject.briar.android.activity.ActivityComponent;
|
||||||
import org.briarproject.briar.android.activity.BriarActivity;
|
import org.briarproject.briar.android.activity.BriarActivity;
|
||||||
import org.briarproject.briar.android.fragment.BaseFragment;
|
import org.briarproject.briar.android.fragment.BaseFragment;
|
||||||
@@ -37,16 +32,19 @@ import java.util.logging.Logger;
|
|||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import static android.Manifest.permission.ACCESS_COARSE_LOCATION;
|
||||||
import static android.Manifest.permission.CAMERA;
|
import static android.Manifest.permission.CAMERA;
|
||||||
import static android.bluetooth.BluetoothAdapter.ACTION_REQUEST_ENABLE;
|
import static android.bluetooth.BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE;
|
||||||
|
import static android.bluetooth.BluetoothAdapter.ACTION_SCAN_MODE_CHANGED;
|
||||||
import static android.bluetooth.BluetoothAdapter.ACTION_STATE_CHANGED;
|
import static android.bluetooth.BluetoothAdapter.ACTION_STATE_CHANGED;
|
||||||
|
import static android.bluetooth.BluetoothAdapter.EXTRA_SCAN_MODE;
|
||||||
import static android.bluetooth.BluetoothAdapter.EXTRA_STATE;
|
import static android.bluetooth.BluetoothAdapter.EXTRA_STATE;
|
||||||
|
import static android.bluetooth.BluetoothAdapter.SCAN_MODE_CONNECTABLE;
|
||||||
|
import static android.bluetooth.BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE;
|
||||||
import static android.bluetooth.BluetoothAdapter.STATE_ON;
|
import static android.bluetooth.BluetoothAdapter.STATE_ON;
|
||||||
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 org.briarproject.briar.android.activity.RequestCodes.REQUEST_BLUETOOTH_DISCOVERABLE;
|
||||||
import static android.widget.Toast.LENGTH_LONG;
|
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_PERMISSION_CAMERA_LOCATION;
|
||||||
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_ENABLE_BLUETOOTH;
|
|
||||||
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_PERMISSION_CAMERA;
|
|
||||||
|
|
||||||
@MethodsNotNullByDefault
|
@MethodsNotNullByDefault
|
||||||
@ParametersNotNullByDefault
|
@ParametersNotNullByDefault
|
||||||
@@ -55,7 +53,11 @@ public abstract class KeyAgreementActivity extends BriarActivity implements
|
|||||||
KeyAgreementEventListener {
|
KeyAgreementEventListener {
|
||||||
|
|
||||||
private enum BluetoothState {
|
private enum BluetoothState {
|
||||||
UNKNOWN, NO_ADAPTER, WAITING, REFUSED, ENABLED
|
UNKNOWN, NO_ADAPTER, WAITING, REFUSED, ENABLED, DISCOVERABLE
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum Permission {
|
||||||
|
UNKNOWN, GRANTED, SHOW_RATIONALE, PERMANENTLY_DENIED
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final Logger LOG =
|
private static final Logger LOG =
|
||||||
@@ -64,8 +66,27 @@ public abstract class KeyAgreementActivity extends BriarActivity implements
|
|||||||
@Inject
|
@Inject
|
||||||
EventBus eventBus;
|
EventBus eventBus;
|
||||||
|
|
||||||
private boolean isResumed = false, enableWasRequested = false;
|
/**
|
||||||
private boolean continueClicked, gotCameraPermission;
|
* 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.
|
||||||
|
*/
|
||||||
|
private boolean isResumed = false;
|
||||||
|
/**
|
||||||
|
* Set to true when the continue button is clicked, and false when the QR
|
||||||
|
* code fragment is shown. This prevents the QR code fragment from being
|
||||||
|
* shown automatically before the continue button has been clicked.
|
||||||
|
*/
|
||||||
|
private boolean continueClicked = false;
|
||||||
|
/**
|
||||||
|
* Records whether the Bluetooth adapter was already enabled before we
|
||||||
|
* asked for Bluetooth discoverability, so we know whether to broadcast a
|
||||||
|
* {@link BluetoothEnabledEvent}.
|
||||||
|
*/
|
||||||
|
private boolean wasAdapterEnabled = false;
|
||||||
|
private Permission cameraPermission = Permission.UNKNOWN;
|
||||||
|
private Permission locationPermission = Permission.UNKNOWN;
|
||||||
private BluetoothState bluetoothState = BluetoothState.UNKNOWN;
|
private BluetoothState bluetoothState = BluetoothState.UNKNOWN;
|
||||||
private BroadcastReceiver bluetoothReceiver = null;
|
private BroadcastReceiver bluetoothReceiver = null;
|
||||||
|
|
||||||
@@ -85,7 +106,9 @@ public abstract class KeyAgreementActivity extends BriarActivity implements
|
|||||||
if (state == null) {
|
if (state == null) {
|
||||||
showInitialFragment(IntroFragment.newInstance());
|
showInitialFragment(IntroFragment.newInstance());
|
||||||
}
|
}
|
||||||
IntentFilter filter = new IntentFilter(ACTION_STATE_CHANGED);
|
IntentFilter filter = new IntentFilter();
|
||||||
|
filter.addAction(ACTION_STATE_CHANGED);
|
||||||
|
filter.addAction(ACTION_SCAN_MODE_CHANGED);
|
||||||
bluetoothReceiver = new BluetoothStateReceiver();
|
bluetoothReceiver = new BluetoothStateReceiver();
|
||||||
registerReceiver(bluetoothReceiver, filter);
|
registerReceiver(bluetoothReceiver, filter);
|
||||||
}
|
}
|
||||||
@@ -107,20 +130,40 @@ public abstract class KeyAgreementActivity extends BriarActivity implements
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStart() {
|
||||||
|
super.onStart();
|
||||||
|
// Permissions may have been granted manually while we were stopped
|
||||||
|
cameraPermission = Permission.UNKNOWN;
|
||||||
|
locationPermission = Permission.UNKNOWN;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onPostResume() {
|
protected void onPostResume() {
|
||||||
super.onPostResume();
|
super.onPostResume();
|
||||||
isResumed = true;
|
isResumed = true;
|
||||||
// Workaround for
|
// Workaround for
|
||||||
// https://code.google.com/p/android/issues/detail?id=190966
|
// https://code.google.com/p/android/issues/detail?id=190966
|
||||||
if (canShowQrCodeFragment()) showQrCodeFragment();
|
showQrCodeFragmentIfAllowed();
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean canShowQrCodeFragment() {
|
private void showQrCodeFragmentIfAllowed() {
|
||||||
return isResumed && continueClicked
|
if (isResumed && continueClicked && areEssentialPermissionsGranted()) {
|
||||||
&& (SDK_INT < 23 || gotCameraPermission)
|
if (bluetoothState == BluetoothState.UNKNOWN ||
|
||||||
&& bluetoothState != BluetoothState.UNKNOWN
|
bluetoothState == BluetoothState.ENABLED) {
|
||||||
&& bluetoothState != BluetoothState.WAITING;
|
requestBluetoothDiscoverable();
|
||||||
|
} else if (bluetoothState != BluetoothState.WAITING) {
|
||||||
|
showQrCodeFragment();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -132,50 +175,54 @@ public abstract class KeyAgreementActivity extends BriarActivity implements
|
|||||||
@Override
|
@Override
|
||||||
public void showNextScreen() {
|
public void showNextScreen() {
|
||||||
continueClicked = true;
|
continueClicked = true;
|
||||||
if (checkPermissions()) {
|
if (checkPermissions()) showQrCodeFragmentIfAllowed();
|
||||||
if (shouldRequestEnableBluetooth()) requestEnableBluetooth();
|
|
||||||
else if (canShowQrCodeFragment()) showQrCodeFragment();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean shouldRequestEnableBluetooth() {
|
private void requestBluetoothDiscoverable() {
|
||||||
return bluetoothState == BluetoothState.UNKNOWN
|
|
||||||
|| bluetoothState == BluetoothState.REFUSED;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void requestEnableBluetooth() {
|
|
||||||
BluetoothAdapter bt = BluetoothAdapter.getDefaultAdapter();
|
BluetoothAdapter bt = BluetoothAdapter.getDefaultAdapter();
|
||||||
if (bt == null) {
|
if (bt == null) {
|
||||||
setBluetoothState(BluetoothState.NO_ADAPTER);
|
setBluetoothState(BluetoothState.NO_ADAPTER);
|
||||||
} else if (bt.isEnabled()) {
|
|
||||||
setBluetoothState(BluetoothState.ENABLED);
|
|
||||||
} else {
|
} else {
|
||||||
enableWasRequested = true;
|
|
||||||
setBluetoothState(BluetoothState.WAITING);
|
setBluetoothState(BluetoothState.WAITING);
|
||||||
Intent i = new Intent(ACTION_REQUEST_ENABLE);
|
wasAdapterEnabled = bt.isEnabled();
|
||||||
startActivityForResult(i, REQUEST_ENABLE_BLUETOOTH);
|
Intent i = new Intent(ACTION_REQUEST_DISCOVERABLE);
|
||||||
|
startActivityForResult(i, REQUEST_BLUETOOTH_DISCOVERABLE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setBluetoothState(BluetoothState bluetoothState) {
|
private void setBluetoothState(BluetoothState bluetoothState) {
|
||||||
LOG.info("Setting Bluetooth state to " + bluetoothState);
|
LOG.info("Setting Bluetooth state to " + bluetoothState);
|
||||||
this.bluetoothState = bluetoothState;
|
this.bluetoothState = bluetoothState;
|
||||||
if (enableWasRequested && bluetoothState == BluetoothState.ENABLED) {
|
if (!wasAdapterEnabled && bluetoothState == BluetoothState.ENABLED) {
|
||||||
eventBus.broadcast(new BluetoothEnabledEvent());
|
eventBus.broadcast(new BluetoothEnabledEvent());
|
||||||
enableWasRequested = false;
|
wasAdapterEnabled = true;
|
||||||
}
|
}
|
||||||
if (canShowQrCodeFragment()) showQrCodeFragment();
|
showQrCodeFragmentIfAllowed();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onActivityResult(int request, int result, Intent data) {
|
public void onActivityResult(int request, int result, Intent data) {
|
||||||
// If the request was granted we'll catch the state change event
|
if (request == REQUEST_BLUETOOTH_DISCOVERABLE) {
|
||||||
if (request == REQUEST_ENABLE_BLUETOOTH && result == RESULT_CANCELED)
|
if (result == RESULT_CANCELED) {
|
||||||
setBluetoothState(BluetoothState.REFUSED);
|
setBluetoothState(BluetoothState.REFUSED);
|
||||||
|
} else {
|
||||||
|
// If Bluetooth is already discoverable, show the QR code -
|
||||||
|
// otherwise wait for the state or scan mode to change
|
||||||
|
BluetoothAdapter bt = BluetoothAdapter.getDefaultAdapter();
|
||||||
|
if (bt == null) throw new AssertionError();
|
||||||
|
if (bt.getScanMode() == SCAN_MODE_CONNECTABLE_DISCOVERABLE)
|
||||||
|
setBluetoothState(BluetoothState.DISCOVERABLE);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showQrCodeFragment() {
|
private void showQrCodeFragment() {
|
||||||
|
// If we return to the intro fragment, the continue button needs to be
|
||||||
|
// clicked again before showing the QR code fragment
|
||||||
continueClicked = false;
|
continueClicked = false;
|
||||||
|
// If we return to the intro fragment, ask for Bluetooth
|
||||||
|
// discoverability again before showing the QR code fragment
|
||||||
|
bluetoothState = BluetoothState.UNKNOWN;
|
||||||
// FIXME #824
|
// FIXME #824
|
||||||
FragmentManager fm = getSupportFragmentManager();
|
FragmentManager fm = getSupportFragmentManager();
|
||||||
if (fm.findFragmentByTag(KeyAgreementFragment.TAG) == null) {
|
if (fm.findFragmentByTag(KeyAgreementFragment.TAG) == null) {
|
||||||
@@ -194,74 +241,113 @@ public abstract class KeyAgreementActivity extends BriarActivity implements
|
|||||||
}
|
}
|
||||||
|
|
||||||
private boolean checkPermissions() {
|
private boolean checkPermissions() {
|
||||||
if (ContextCompat.checkSelfPermission(this, CAMERA) !=
|
if (areEssentialPermissionsGranted()) return true;
|
||||||
PERMISSION_GRANTED) {
|
// If the camera permission has been permanently denied, ask the
|
||||||
// Should we show an explanation?
|
// user to change the setting
|
||||||
if (ActivityCompat.shouldShowRequestPermissionRationale(this,
|
if (cameraPermission == Permission.PERMANENTLY_DENIED) {
|
||||||
CAMERA)) {
|
Builder builder = new Builder(this, R.style.BriarDialogTheme);
|
||||||
OnClickListener continueListener =
|
builder.setTitle(R.string.permission_camera_title);
|
||||||
(dialog, which) -> requestPermission();
|
builder.setMessage(R.string.permission_camera_denied_body);
|
||||||
Builder builder = new Builder(this, style.BriarDialogTheme);
|
builder.setPositiveButton(R.string.ok,
|
||||||
builder.setTitle(string.permission_camera_title);
|
UiUtils.getGoToSettingsListener(this));
|
||||||
builder.setMessage(string.permission_camera_request_body);
|
builder.setNegativeButton(R.string.cancel,
|
||||||
builder.setNeutralButton(string.continue_button,
|
(dialog, which) -> supportFinishAfterTransition());
|
||||||
continueListener);
|
builder.show();
|
||||||
builder.show();
|
|
||||||
} else {
|
|
||||||
requestPermission();
|
|
||||||
}
|
|
||||||
gotCameraPermission = false;
|
|
||||||
return false;
|
return false;
|
||||||
} else {
|
|
||||||
gotCameraPermission = true;
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
// Should we show the rationale for one or both permissions?
|
||||||
|
if (cameraPermission == Permission.SHOW_RATIONALE &&
|
||||||
|
locationPermission == Permission.SHOW_RATIONALE) {
|
||||||
|
showRationale(R.string.permission_camera_location_title,
|
||||||
|
R.string.permission_camera_location_request_body);
|
||||||
|
} else if (cameraPermission == Permission.SHOW_RATIONALE) {
|
||||||
|
showRationale(R.string.permission_camera_title,
|
||||||
|
R.string.permission_camera_request_body);
|
||||||
|
} else if (locationPermission == Permission.SHOW_RATIONALE) {
|
||||||
|
showRationale(R.string.permission_location_title,
|
||||||
|
R.string.permission_location_request_body);
|
||||||
|
} else {
|
||||||
|
requestPermissions();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void requestPermission() {
|
private void showRationale(@StringRes int title, @StringRes int body) {
|
||||||
ActivityCompat.requestPermissions(this, new String[] {CAMERA},
|
Builder builder = new Builder(this, R.style.BriarDialogTheme);
|
||||||
REQUEST_PERMISSION_CAMERA);
|
builder.setTitle(title);
|
||||||
|
builder.setMessage(body);
|
||||||
|
builder.setNeutralButton(R.string.continue_button,
|
||||||
|
(dialog, which) -> requestPermissions());
|
||||||
|
builder.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void requestPermissions() {
|
||||||
|
ActivityCompat.requestPermissions(this,
|
||||||
|
new String[] {CAMERA, ACCESS_COARSE_LOCATION},
|
||||||
|
REQUEST_PERMISSION_CAMERA_LOCATION);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@UiThread
|
@UiThread
|
||||||
public void onRequestPermissionsResult(int requestCode,
|
public void onRequestPermissionsResult(int requestCode,
|
||||||
String permissions[], int[] grantResults) {
|
String[] permissions, int[] grantResults) {
|
||||||
if (requestCode == REQUEST_PERMISSION_CAMERA) {
|
if (requestCode != REQUEST_PERMISSION_CAMERA_LOCATION)
|
||||||
// If request is cancelled, the result arrays are empty.
|
throw new AssertionError();
|
||||||
if (grantResults.length > 0 &&
|
if (gotPermission(CAMERA, permissions, grantResults)) {
|
||||||
grantResults[0] == PERMISSION_GRANTED) {
|
cameraPermission = Permission.GRANTED;
|
||||||
gotCameraPermission = true;
|
} else if (shouldShowRationale(CAMERA)) {
|
||||||
showNextScreen();
|
cameraPermission = Permission.SHOW_RATIONALE;
|
||||||
} else {
|
} else {
|
||||||
if (!ActivityCompat.shouldShowRequestPermissionRationale(this,
|
cameraPermission = Permission.PERMANENTLY_DENIED;
|
||||||
CAMERA)) {
|
|
||||||
// The user has permanently denied the request
|
|
||||||
OnClickListener cancelListener =
|
|
||||||
(dialog, which) -> supportFinishAfterTransition();
|
|
||||||
Builder builder = new Builder(this, style.BriarDialogTheme);
|
|
||||||
builder.setTitle(string.permission_camera_title);
|
|
||||||
builder.setMessage(string.permission_camera_denied_body);
|
|
||||||
builder.setPositiveButton(string.ok,
|
|
||||||
UiUtils.getGoToSettingsListener(this));
|
|
||||||
builder.setNegativeButton(string.cancel, cancelListener);
|
|
||||||
builder.show();
|
|
||||||
} else {
|
|
||||||
Toast.makeText(this, string.permission_camera_denied_toast,
|
|
||||||
LENGTH_LONG).show();
|
|
||||||
supportFinishAfterTransition();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
if (gotPermission(ACCESS_COARSE_LOCATION, permissions, grantResults)) {
|
||||||
|
locationPermission = Permission.GRANTED;
|
||||||
|
} else if (shouldShowRationale(ACCESS_COARSE_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
|
||||||
|
// https://code.google.com/p/android/issues/detail?id=190966.
|
||||||
|
// In that case the isResumed flag prevents the fragment from being
|
||||||
|
// shown here, and showQrCodeFragmentIfAllowed() will be called again
|
||||||
|
// from onPostResume().
|
||||||
|
if (checkPermissions()) showQrCodeFragmentIfAllowed();
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean gotPermission(String permission, String[] permissions,
|
||||||
|
int[] grantResults) {
|
||||||
|
for (int i = 0; i < permissions.length; i++) {
|
||||||
|
if (permission.equals(permissions[i]))
|
||||||
|
return grantResults[i] == PERMISSION_GRANTED;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean shouldShowRationale(String permission) {
|
||||||
|
return ActivityCompat.shouldShowRequestPermissionRationale(this,
|
||||||
|
permission);
|
||||||
}
|
}
|
||||||
|
|
||||||
private class BluetoothStateReceiver extends BroadcastReceiver {
|
private class BluetoothStateReceiver extends BroadcastReceiver {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onReceive(Context context, Intent intent) {
|
public void onReceive(Context context, Intent intent) {
|
||||||
int state = intent.getIntExtra(EXTRA_STATE, 0);
|
String action = intent.getAction();
|
||||||
if (state == STATE_ON) setBluetoothState(BluetoothState.ENABLED);
|
if (ACTION_STATE_CHANGED.equals(action)) {
|
||||||
else setBluetoothState(BluetoothState.UNKNOWN);
|
int state = intent.getIntExtra(EXTRA_STATE, 0);
|
||||||
|
if (state == STATE_ON)
|
||||||
|
setBluetoothState(BluetoothState.ENABLED);
|
||||||
|
else setBluetoothState(BluetoothState.UNKNOWN);
|
||||||
|
} else if (ACTION_SCAN_MODE_CHANGED.equals(action)) {
|
||||||
|
int scanMode = intent.getIntExtra(EXTRA_SCAN_MODE, 0);
|
||||||
|
if (scanMode == SCAN_MODE_CONNECTABLE_DISCOVERABLE)
|
||||||
|
setBluetoothState(BluetoothState.DISCOVERABLE);
|
||||||
|
else if (scanMode == SCAN_MODE_CONNECTABLE)
|
||||||
|
setBluetoothState(BluetoothState.ENABLED);
|
||||||
|
else setBluetoothState(BluetoothState.UNKNOWN);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -468,8 +468,11 @@
|
|||||||
<!-- Permission Requests -->
|
<!-- Permission Requests -->
|
||||||
<string name="permission_camera_title">Camera permission</string>
|
<string name="permission_camera_title">Camera permission</string>
|
||||||
<string name="permission_camera_request_body">To scan the QR code, Briar needs access to the camera.</string>
|
<string name="permission_camera_request_body">To scan the QR code, Briar needs access to the camera.</string>
|
||||||
|
<string name="permission_location_title">Location permission</string>
|
||||||
|
<string name="permission_location_request_body">To 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_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_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_camera_denied_toast">Camera permission was not granted</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