Compare commits

...

62 Commits

Author SHA1 Message Date
akwizgran
085987febd DO NOT MERGE: Add hacky poller method for testing discovery. 2020-03-10 15:29:05 +00:00
akwizgran
65c96514b5 Add workaround for Android UUID endianness bug. 2020-03-10 15:29:05 +00:00
akwizgran
174e678304 Don't try to add extra UUIDs to emptyList(). 2020-03-10 15:29:05 +00:00
akwizgran
14d7abc823 Don't try to discover peers if discovery is already in progress. 2020-03-10 15:29:05 +00:00
akwizgran
52fca33d0b Restart discovery if it finishes too quickly.
Discovery finishes quickly on some devices if called at the wrong time.
2020-03-10 15:29:05 +00:00
akwizgran
472d560dda Wait for discovery to finish before returning discovered devices. 2020-03-10 15:29:05 +00:00
akwizgran
2522034397 Implement peer discovery for Android Bluetooth plugin. 2020-03-10 15:29:05 +00:00
akwizgran
3e6b65b1a0 Return devices from discoverDevices() method. 2020-03-10 15:29:05 +00:00
akwizgran
155746b24e Add discovery methods to plugin API. 2020-03-10 15:29:04 +00:00
akwizgran
91caff183f Remove unnecessary plugin lookup. 2020-03-05 14:45:35 +00:00
akwizgran
249dcda34e Use tinted icon for plugin settings dialog. 2020-03-05 14:45:35 +00:00
akwizgran
c0a82f96a3 Show dialog from controller. 2020-03-05 14:45:35 +00:00
akwizgran
79f5229230 Add icon, title to Change Settings dialog. 2020-03-05 14:45:35 +00:00
akwizgran
02b4925609 Change Tor settings after asking for confirmation. 2020-03-05 14:45:35 +00:00
akwizgran
0664720680 Transition from one constraint set to another. 2020-03-05 14:45:35 +00:00
akwizgran
f04d32f7f2 Make entire collapsed view clickable. 2020-03-05 14:45:35 +00:00
akwizgran
dfa05fc473 Put the transport toggles in an expandable view (no animations). 2020-03-05 14:45:35 +00:00
akwizgran
cb936d95c5 Add STARTING_STOPPING state, use flags for reasons disabled. 2020-03-05 14:45:35 +00:00
akwizgran
1b402ba0c2 Close small gap between setStarted() and setDisabledBySettings(). 2020-03-05 14:45:34 +00:00
akwizgran
2c6f81a120 Don't show Tor in the enabling state if it's disabled by settings. 2020-03-05 14:45:34 +00:00
akwizgran
b69eb8f203 Remove "don't connect" option from Tor network setting.
This has been replaced by the enable/disable setting and no longer
works.
2020-03-05 14:45:34 +00:00
Torsten Grote
e956f073ae [android] Scroll down when nav drawer chevron is pressed 2020-03-05 14:45:34 +00:00
Torsten Grote
f4b6389163 [android] remove unused strings 2020-03-05 14:45:34 +00:00
Torsten Grote
82bfb4d95e [android] make transport plugin toggles functional 2020-03-05 14:45:34 +00:00
Torsten Grote
93ec646634 [android] Add transport plugin toggles to NavDrawer 2020-03-05 14:45:34 +00:00
Torsten Grote
2420456f25 [bramble] Add method for enabling/disabling plugins to PluginManager 2020-03-05 14:45:32 +00:00
akwizgran
b32417e7d3 Remove another redundant call to pluginStateChanged(). 2020-03-05 14:45:17 +00:00
akwizgran
9efa3cc44e Enable LAN plugin before showing QR code. 2020-03-05 14:45:17 +00:00
akwizgran
90c8603d3a Remove redundant call to pluginStateChanged(). 2020-03-05 14:45:17 +00:00
akwizgran
1ae9750c13 Use XML to specify dependencies between settings. 2020-03-05 14:45:17 +00:00
akwizgran
b0b87fc0db Clean up logic for enabling/disabling settings. 2020-03-05 14:45:17 +00:00
akwizgran
62cb6095ca Don't remove old settings yet.
This avoids an unlikely race condition at startup, where the user opens
the settings screen before the Tor plugin has migrated the settings.
2020-03-05 14:45:17 +00:00
akwizgran
d4a64f4ee3 Enable LAN plugin in unit test. 2020-03-05 14:45:14 +00:00
akwizgran
6886551895 Enable BT plugin before showing QR code. 2020-03-05 14:44:58 +00:00
akwizgran
b50b9f8088 Small code cleanups in key agreement UI. 2020-03-05 14:44:58 +00:00
akwizgran
c1aade221a Make REASON_USER into a generic reason code. 2020-03-05 14:44:57 +00:00
akwizgran
40f2c1923b Add toggle setting for LAN plugin. 2020-03-05 14:44:57 +00:00
akwizgran
cfc640f4ce Update semantics of Bluetooth setting.
The setting now enables/disables the plugin, not just contact
connections. The key agreement UI will need to be updated to change the
setting if the user agrees to use Bluetooth.
2020-03-05 14:44:57 +00:00
akwizgran
c865b90c6c Convert Bluetooth setting to a switch. 2020-03-05 14:44:57 +00:00
akwizgran
4db2d0fda2 Add toggle setting for Tor plugin. 2020-03-05 14:44:57 +00:00
akwizgran
719debc36a Remove redundant casts. 2020-03-05 14:44:57 +00:00
akwizgran
ce1b5eb0d9 Skip fetching RSS feeds if Tor is not active. 2020-03-05 14:44:57 +00:00
akwizgran
5bd9a29eab Use amber icon when enabling transports. 2020-03-05 14:44:57 +00:00
akwizgran
e6d093c52f Only update bridge and padding settings if network is enabled. 2020-03-05 14:44:57 +00:00
akwizgran
fe5bbfdd17 Notify callback of state changes while holding lock. 2020-03-05 14:44:57 +00:00
akwizgran
e6ac6913a7 Update javadocs for lock-safe methods. 2020-03-05 14:44:57 +00:00
akwizgran
54068a9e24 Remove redundant logging. 2020-03-05 14:44:57 +00:00
akwizgran
4bb14f51d2 Remove debug logging. 2020-03-05 14:44:57 +00:00
akwizgran
37ea59a89e Close server socket when BT is disabled. 2020-03-05 14:44:56 +00:00
akwizgran
f19dbf144a Remove unnecessary inner class, state checks. 2020-03-05 14:44:56 +00:00
akwizgran
4b94bd0f1b Reset backoff before notifying of new state.
The new state may cause the poller to poll the
plugin. Let's avoid a race between updating and
querying the polling interval.
2020-03-05 14:44:56 +00:00
akwizgran
0b29e3ce11 Move to enabling state earlier in Tor startup. 2020-03-05 14:44:56 +00:00
akwizgran
6a9dbcf482 Add TransportStateEvent, rename existing events. 2020-03-05 14:44:56 +00:00
akwizgran
f5a21d8c07 Ensure server socket is closed. 2020-03-05 14:44:54 +00:00
akwizgran
b6a73f2c98 Add method for getting reason why plugin is disabled. 2020-03-05 14:44:28 +00:00
akwizgran
d084f6dd8d Fix test expectations. 2020-03-05 14:44:28 +00:00
akwizgran
0259c23cb4 Rename available/unavailable states. 2020-03-05 14:44:26 +00:00
akwizgran
341382cfa8 Update tests. 2020-03-05 14:44:08 +00:00
akwizgran
49baf1020b If adapter is disabled, forget that we enabled it. 2020-03-05 14:44:08 +00:00
akwizgran
6b33c5b913 Check that server sockets are closed as expected. 2020-03-05 14:44:08 +00:00
akwizgran
53889436fc Provide more information about plugin states. 2020-03-05 14:44:05 +00:00
akwizgran
e35d1763bc Avoid NPE if there's no TelephonyManager. 2020-03-05 14:42:43 +00:00
63 changed files with 2478 additions and 1031 deletions

View File

@@ -8,23 +8,34 @@ import android.content.BroadcastReceiver;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.IntentFilter; import android.content.IntentFilter;
import android.os.ParcelUuid;
import android.os.Parcelable;
import org.briarproject.bramble.api.Pair;
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.Backoff; import org.briarproject.bramble.api.plugin.Backoff;
import org.briarproject.bramble.api.plugin.DiscoveryHandler;
import org.briarproject.bramble.api.plugin.PluginCallback; import org.briarproject.bramble.api.plugin.PluginCallback;
import org.briarproject.bramble.api.plugin.PluginException; import org.briarproject.bramble.api.plugin.PluginException;
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection; import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
import org.briarproject.bramble.api.properties.TransportProperties;
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.api.system.Clock;
import org.briarproject.bramble.util.AndroidUtils; import org.briarproject.bramble.util.AndroidUtils;
import org.briarproject.bramble.util.IoUtils; import org.briarproject.bramble.util.IoUtils;
import java.io.IOException; import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.ByteBuffer;
import java.security.SecureRandom; import java.security.SecureRandom;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.BlockingQueue; import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
@@ -46,15 +57,25 @@ 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.ACTION_FOUND;
import static android.bluetooth.BluetoothDevice.ACTION_UUID;
import static android.bluetooth.BluetoothDevice.DEVICE_TYPE_LE; import static android.bluetooth.BluetoothDevice.DEVICE_TYPE_LE;
import static android.bluetooth.BluetoothDevice.EXTRA_DEVICE; import static android.bluetooth.BluetoothDevice.EXTRA_DEVICE;
import static android.bluetooth.BluetoothDevice.EXTRA_UUID;
import static android.os.Build.VERSION.SDK_INT; import static android.os.Build.VERSION.SDK_INT;
import static java.nio.ByteOrder.LITTLE_ENDIAN;
import static java.util.Arrays.asList;
import static java.util.Collections.emptyList;
import static java.util.Collections.shuffle; import static java.util.Collections.shuffle;
import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.MILLISECONDS;
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 java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
import static org.briarproject.bramble.api.plugin.BluetoothConstants.PROP_ADDRESS;
import static org.briarproject.bramble.api.plugin.BluetoothConstants.PROP_UUID;
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;
@MethodsNotNullByDefault @MethodsNotNullByDefault
@ParametersNotNullByDefault @ParametersNotNullByDefault
@@ -63,7 +84,9 @@ class AndroidBluetoothPlugin extends BluetoothPlugin<BluetoothServerSocket> {
private static final Logger LOG = private static final Logger LOG =
getLogger(AndroidBluetoothPlugin.class.getName()); getLogger(AndroidBluetoothPlugin.class.getName());
private static final int MAX_DISCOVERY_MS = 10_000; private static final int MIN_DEVICE_DISCOVERY_MS = 2_000;
private static final int MAX_DEVICE_DISCOVERY_MS = 30_000;
private static final int MAX_SERVICE_DISCOVERY_MS = 15_000;
private final AndroidExecutor androidExecutor; private final AndroidExecutor androidExecutor;
private final Context appContext; private final Context appContext;
@@ -137,17 +160,42 @@ class AndroidBluetoothPlugin extends BluetoothPlugin<BluetoothServerSocket> {
@Override @Override
void disableAdapterIfEnabledByUs() { void disableAdapterIfEnabledByUs() {
if (isAdapterEnabled() && wasEnabledByUs) { if (isAdapterEnabled() && wasEnabledByUs) {
cancelDiscoverability();
if (adapter.disable()) LOG.info("Disabling Bluetooth"); if (adapter.disable()) LOG.info("Disabling Bluetooth");
else LOG.info("Could not disable Bluetooth"); else LOG.info("Could not disable Bluetooth");
wasEnabledByUs = false; wasEnabledByUs = false;
} }
} }
private void cancelDiscoverability() {
if (adapter.getScanMode() == SCAN_MODE_CONNECTABLE_DISCOVERABLE) {
try {
Method setDiscoverableTimeout = BluetoothAdapter.class
.getDeclaredMethod("setDiscoverableTimeout", int.class);
setDiscoverableTimeout.setAccessible(true);
setDiscoverableTimeout.invoke(adapter, 1);
LOG.info("Cancelled discoverability");
} catch (NoSuchMethodException e) {
logException(LOG, WARNING, e);
} catch (IllegalAccessException e) {
logException(LOG, WARNING, e);
} catch (InvocationTargetException e) {
logException(LOG, WARNING, e);
}
}
}
@Override @Override
void setEnabledByUs() { void setEnabledByUs() {
wasEnabledByUs = true; wasEnabledByUs = true;
} }
@Override
void onAdapterDisabled() {
super.onAdapterDisabled();
wasEnabledByUs = false;
}
@Override @Override
@Nullable @Nullable
String getBluetoothAddress() { String getBluetoothAddress() {
@@ -202,7 +250,8 @@ class AndroidBluetoothPlugin extends BluetoothPlugin<BluetoothServerSocket> {
@Nullable @Nullable
DuplexTransportConnection discoverAndConnect(String uuid) { DuplexTransportConnection discoverAndConnect(String uuid) {
if (adapter == null) return null; if (adapter == null) return null;
for (String address : discoverDevices()) { for (BluetoothDevice d : discoverDevices()) {
String address = d.getAddress();
try { try {
if (LOG.isLoggable(INFO)) if (LOG.isLoggable(INFO))
LOG.info("Connecting to " + scrubMacAddress(address)); LOG.info("Connecting to " + scrubMacAddress(address));
@@ -218,10 +267,184 @@ class AndroidBluetoothPlugin extends BluetoothPlugin<BluetoothServerSocket> {
return null; return null;
} }
private Collection<String> discoverDevices() { @Override
List<String> addresses = new ArrayList<>(); public boolean supportsDiscovery() {
return true;
}
@Override
public void discoverPeers(
List<Pair<TransportProperties, DiscoveryHandler>> properties) {
// Discover all nearby devices
List<BluetoothDevice> devices = discoverDevices();
if (devices.isEmpty()) {
LOG.info("No devices discovered");
return;
}
List<Pair<TransportProperties, DiscoveryHandler>> discovered =
new ArrayList<>();
Map<String, Pair<TransportProperties, DiscoveryHandler>> byUuid =
new HashMap<>();
Map<String, Pair<TransportProperties, DiscoveryHandler>> byAddress =
new HashMap<>();
for (Pair<TransportProperties, DiscoveryHandler> pair : properties) {
TransportProperties p = pair.getFirst();
String uuid = p.get(PROP_UUID);
if (!isNullOrEmpty(uuid)) {
byUuid.put(uuid, pair);
String address = p.get(PROP_ADDRESS);
if (!isNullOrEmpty(address)) byAddress.put(address, pair);
}
}
List<BluetoothDevice> unknown = new ArrayList<>(devices);
for (BluetoothDevice d : devices) {
Pair<TransportProperties, DiscoveryHandler> pair =
byAddress.remove(d.getAddress());
if (pair == null) {
// Try cached UUIDs
for (String uuid : getUuids(d)) {
pair = byUuid.remove(uuid);
if (pair != null) {
if (LOG.isLoggable(INFO)) {
LOG.info("Matched "
+ scrubMacAddress(d.getAddress())
+ " by cached UUID");
}
TransportProperties p =
new TransportProperties(pair.getFirst());
p.put(PROP_ADDRESS, d.getAddress());
discovered.add(new Pair<>(p, pair.getSecond()));
unknown.remove(d);
break;
}
}
} else {
if (LOG.isLoggable(INFO)) {
LOG.info("Matched " + scrubMacAddress(d.getAddress())
+ " by address");
}
discovered.add(pair);
unknown.remove(d);
}
}
if (unknown.isEmpty()) {
LOG.info("All discovered devices are known, not fetching UUIDs");
return;
}
// Fetch up-to-date UUIDs
if (LOG.isLoggable(INFO))
LOG.info("Fetching UUIDs for " + unknown.size() + " devices");
BlockingQueue<Intent> intents = new LinkedBlockingQueue<>(); BlockingQueue<Intent> intents = new LinkedBlockingQueue<>();
DiscoveryReceiver receiver = new DiscoveryReceiver(intents); QueueingReceiver receiver = new QueueingReceiver(intents);
IntentFilter filter = new IntentFilter();
filter.addAction(ACTION_UUID);
appContext.registerReceiver(receiver, filter);
try {
List<BluetoothDevice> pending = new ArrayList<>();
for (BluetoothDevice d : unknown) {
if (d.fetchUuidsWithSdp()) {
if (LOG.isLoggable(INFO)) {
LOG.info("Fetching UUIDs for "
+ scrubMacAddress(d.getAddress()));
}
pending.add(d);
} else {
if (LOG.isLoggable(INFO)) {
LOG.info("Failed to fetch UUIDs for "
+ scrubMacAddress(d.getAddress()));
}
}
}
long now = clock.currentTimeMillis();
long end = now + MAX_SERVICE_DISCOVERY_MS;
while (now < end && !pending.isEmpty()) {
Intent i = intents.poll(end - now, MILLISECONDS);
if (i == null) break;
BluetoothDevice d = requireNonNull(
i.getParcelableExtra(EXTRA_DEVICE));
if (LOG.isLoggable(INFO)) {
LOG.info("Fetched UUIDs for "
+ scrubMacAddress(d.getAddress()));
}
Set<String> uuids = getUuids(d);
Parcelable[] extra = i.getParcelableArrayExtra(EXTRA_UUID);
if (extra != null) {
for (Parcelable p : extra) {
uuids.addAll(getUuidStrings((ParcelUuid) p));
}
}
for (String uuid : uuids) {
Pair<TransportProperties, DiscoveryHandler> pair =
byUuid.remove(uuid);
if (pair != null) {
if (LOG.isLoggable(INFO)) {
LOG.info("Matched "
+ scrubMacAddress(d.getAddress())
+ " by fetched UUID");
}
TransportProperties p =
new TransportProperties(pair.getFirst());
p.put(PROP_ADDRESS, d.getAddress());
discovered.add(new Pair<>(p, pair.getSecond()));
break;
}
}
pending.remove(d);
now = clock.currentTimeMillis();
}
if (LOG.isLoggable(INFO)) {
if (pending.isEmpty()) {
LOG.info("Finished fetching UUIDs");
} else {
LOG.info("Failed to fetch UUIDs for " + pending.size()
+ " devices");
}
}
} catch (InterruptedException e) {
LOG.info("Interrupted while fetching UUIDs");
Thread.currentThread().interrupt();
} finally {
appContext.unregisterReceiver(receiver);
}
if (LOG.isLoggable(INFO)) {
LOG.info("Discovered " + discovered.size() + " contacts");
}
for (Pair<TransportProperties, DiscoveryHandler> pair : discovered) {
pair.getSecond().handleDevice(pair.getFirst());
}
}
private Set<String> getUuids(BluetoothDevice d) {
Set<String> strings = new TreeSet<>();
ParcelUuid[] uuids = d.getUuids();
if (uuids == null) return strings;
for (ParcelUuid u : uuids) strings.addAll(getUuidStrings(u));
return strings;
}
// Workaround for https://code.google.com/p/android/issues/detail?id=197341
private List<String> getUuidStrings(ParcelUuid u) {
UUID forwards = u.getUuid();
ByteBuffer buf = ByteBuffer.allocate(16);
buf.putLong(forwards.getLeastSignificantBits());
buf.putLong(forwards.getMostSignificantBits());
buf.rewind();
buf.order(LITTLE_ENDIAN);
UUID backwards = new UUID(buf.getLong(), buf.getLong());
return asList(forwards.toString(), backwards.toString());
}
private List<BluetoothDevice> discoverDevices() {
if (adapter.isDiscovering()) {
LOG.info("Already discovering");
return emptyList();
}
List<BluetoothDevice> devices = new ArrayList<>();
BlockingQueue<Intent> intents = new LinkedBlockingQueue<>();
QueueingReceiver receiver = new QueueingReceiver(intents);
IntentFilter filter = new IntentFilter(); IntentFilter filter = new IntentFilter();
filter.addAction(ACTION_DISCOVERY_STARTED); filter.addAction(ACTION_DISCOVERY_STARTED);
filter.addAction(ACTION_DISCOVERY_FINISHED); filter.addAction(ACTION_DISCOVERY_FINISHED);
@@ -229,8 +452,9 @@ class AndroidBluetoothPlugin extends BluetoothPlugin<BluetoothServerSocket> {
appContext.registerReceiver(receiver, filter); appContext.registerReceiver(receiver, filter);
try { try {
if (adapter.startDiscovery()) { if (adapter.startDiscovery()) {
long now = clock.currentTimeMillis(); long start = clock.currentTimeMillis();
long end = now + MAX_DISCOVERY_MS; long end = start + MAX_DEVICE_DISCOVERY_MS;
long now = start;
while (now < end) { while (now < end) {
Intent i = intents.poll(end - now, MILLISECONDS); Intent i = intents.poll(end - now, MILLISECONDS);
if (i == null) break; if (i == null) break;
@@ -239,17 +463,26 @@ class AndroidBluetoothPlugin extends BluetoothPlugin<BluetoothServerSocket> {
LOG.info("Discovery started"); LOG.info("Discovery started");
} else if (ACTION_DISCOVERY_FINISHED.equals(action)) { } else if (ACTION_DISCOVERY_FINISHED.equals(action)) {
LOG.info("Discovery finished"); LOG.info("Discovery finished");
now = clock.currentTimeMillis();
if (now - start < MIN_DEVICE_DISCOVERY_MS) {
LOG.info("Discovery finished quickly, retrying");
if (!adapter.startDiscovery()) {
LOG.info("Could not restart discovery");
break; break;
}
} else {
break;
}
} else if (ACTION_FOUND.equals(action)) { } else if (ACTION_FOUND.equals(action)) {
BluetoothDevice d = i.getParcelableExtra(EXTRA_DEVICE); BluetoothDevice d = requireNonNull(
i.getParcelableExtra(EXTRA_DEVICE));
// Ignore Bluetooth LE devices // Ignore Bluetooth LE devices
if (SDK_INT < 18 || d.getType() != DEVICE_TYPE_LE) { if (SDK_INT < 18 || d.getType() != DEVICE_TYPE_LE) {
String address = d.getAddress(); if (LOG.isLoggable(INFO)) {
if (LOG.isLoggable(INFO)) LOG.info("Discovered "
LOG.info("Discovered " + + scrubMacAddress(d.getAddress()));
scrubMacAddress(address)); }
if (!addresses.contains(address)) if (!devices.contains(d)) devices.add(d);
addresses.add(address);
} }
} }
now = clock.currentTimeMillis(); now = clock.currentTimeMillis();
@@ -265,9 +498,9 @@ class AndroidBluetoothPlugin extends BluetoothPlugin<BluetoothServerSocket> {
adapter.cancelDiscovery(); adapter.cancelDiscovery();
appContext.unregisterReceiver(receiver); appContext.unregisterReceiver(receiver);
} }
// Shuffle the addresses so we don't always try the same one first // Shuffle the devices so we don't always try the same one first
shuffle(addresses); shuffle(devices);
return addresses; return devices;
} }
private class BluetoothStateReceiver extends BroadcastReceiver { private class BluetoothStateReceiver extends BroadcastReceiver {
@@ -288,11 +521,11 @@ class AndroidBluetoothPlugin extends BluetoothPlugin<BluetoothServerSocket> {
} }
} }
private static class DiscoveryReceiver extends BroadcastReceiver { private static class QueueingReceiver extends BroadcastReceiver {
private final BlockingQueue<Intent> intents; private final BlockingQueue<Intent> intents;
private DiscoveryReceiver(BlockingQueue<Intent> intents) { private QueueingReceiver(BlockingQueue<Intent> intents) {
this.intents = intents; this.intents = intents;
} }

View File

@@ -9,11 +9,11 @@ import android.net.wifi.WifiManager;
import org.briarproject.bramble.PoliteExecutor; import org.briarproject.bramble.PoliteExecutor;
import org.briarproject.bramble.api.event.Event; import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.event.EventListener;
import org.briarproject.bramble.api.network.event.NetworkStatusEvent; import org.briarproject.bramble.api.network.event.NetworkStatusEvent;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.Backoff; import org.briarproject.bramble.api.plugin.Backoff;
import org.briarproject.bramble.api.plugin.PluginCallback; import org.briarproject.bramble.api.plugin.PluginCallback;
import org.briarproject.bramble.api.settings.Settings;
import java.io.IOException; import java.io.IOException;
import java.net.InetAddress; import java.net.InetAddress;
@@ -32,10 +32,14 @@ import static android.net.ConnectivityManager.TYPE_WIFI;
import static android.os.Build.VERSION.SDK_INT; import static android.os.Build.VERSION.SDK_INT;
import static java.util.Collections.emptyList; import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList; import static java.util.Collections.singletonList;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger; import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE;
import static org.briarproject.bramble.api.plugin.Plugin.State.INACTIVE;
import static org.briarproject.bramble.util.IoUtils.tryToClose;
@NotNullByDefault @NotNullByDefault
class AndroidLanTcpPlugin extends LanTcpPlugin implements EventListener { class AndroidLanTcpPlugin extends LanTcpPlugin {
private static final Logger LOG = private static final Logger LOG =
getLogger(AndroidLanTcpPlugin.class.getName()); getLogger(AndroidLanTcpPlugin.class.getName());
@@ -68,16 +72,11 @@ class AndroidLanTcpPlugin extends LanTcpPlugin implements EventListener {
public void start() { public void start() {
if (used.getAndSet(true)) throw new IllegalStateException(); if (used.getAndSet(true)) throw new IllegalStateException();
initialisePortProperty(); initialisePortProperty();
running = true; Settings settings = callback.getSettings();
state.setStarted(settings.getBoolean(PREF_PLUGIN_ENABLE, false));
updateConnectionStatus(); updateConnectionStatus();
} }
@Override
public void stop() {
running = false;
tryToClose(socket);
}
@Override @Override
protected Socket createSocket() throws IOException { protected Socket createSocket() throws IOException {
return socketFactory.createSocket(); return socketFactory.createSocket();
@@ -130,12 +129,14 @@ class AndroidLanTcpPlugin extends LanTcpPlugin implements EventListener {
@Override @Override
public void eventOccurred(Event e) { public void eventOccurred(Event e) {
super.eventOccurred(e);
if (e instanceof NetworkStatusEvent) updateConnectionStatus(); if (e instanceof NetworkStatusEvent) updateConnectionStatus();
} }
private void updateConnectionStatus() { private void updateConnectionStatus() {
connectionStatusExecutor.execute(() -> { connectionStatusExecutor.execute(() -> {
if (!running) return; State s = getState();
if (s != ACTIVE && s != INACTIVE) return;
List<InetAddress> addrs = getLocalInetAddresses(); List<InetAddress> addrs = getLocalInetAddresses();
if (addrs.contains(WIFI_AP_ADDRESS) if (addrs.contains(WIFI_AP_ADDRESS)
|| addrs.contains(WIFI_DIRECT_AP_ADDRESS)) { || addrs.contains(WIFI_DIRECT_AP_ADDRESS)) {
@@ -145,15 +146,21 @@ class AndroidLanTcpPlugin extends LanTcpPlugin implements EventListener {
// make outgoing connections on API 21+ if another network // make outgoing connections on API 21+ if another network
// has internet access // has internet access
socketFactory = SocketFactory.getDefault(); socketFactory = SocketFactory.getDefault();
if (socket == null || socket.isClosed()) bind(); if (s == INACTIVE) bind();
} else if (addrs.isEmpty()) { } else if (addrs.isEmpty()) {
LOG.info("Not connected to wifi"); LOG.info("Not connected to wifi");
socketFactory = SocketFactory.getDefault(); socketFactory = SocketFactory.getDefault();
tryToClose(socket); // Server socket may not have been closed automatically when
// interface was taken down. Socket will be cleared and state
// updated in acceptContactConnections()
if (s == ACTIVE) {
LOG.info("Closing server socket");
tryToClose(state.getServerSocket(), LOG, WARNING);
}
} else { } else {
LOG.info("Connected to wifi"); LOG.info("Connected to wifi");
socketFactory = getSocketFactory(); socketFactory = getSocketFactory();
if (socket == null || socket.isClosed()) bind(); if (s == INACTIVE) bind();
} }
}); });
} }

View File

@@ -74,7 +74,6 @@ class AndroidTorPlugin extends TorPlugin {
@Override @Override
protected void enableNetwork(boolean enable) throws IOException { protected void enableNetwork(boolean enable) throws IOException {
if (!running) return;
if (enable) wakeLock.acquire(); if (enable) wakeLock.acquire();
super.enableNetwork(enable); super.enableNetwork(enable);
if (!enable) wakeLock.release(); if (!enable) wakeLock.release();

View File

@@ -61,12 +61,12 @@ class AndroidLocationUtils implements LocationUtils {
private String getCountryFromPhoneNetwork() { private String getCountryFromPhoneNetwork() {
Object o = appContext.getSystemService(TELEPHONY_SERVICE); Object o = appContext.getSystemService(TELEPHONY_SERVICE);
TelephonyManager tm = (TelephonyManager) o; TelephonyManager tm = (TelephonyManager) o;
return tm.getNetworkCountryIso(); return tm == null ? "" : tm.getNetworkCountryIso();
} }
private String getCountryFromSimCard() { private String getCountryFromSimCard() {
Object o = appContext.getSystemService(TELEPHONY_SERVICE); Object o = appContext.getSystemService(TELEPHONY_SERVICE);
TelephonyManager tm = (TelephonyManager) o; TelephonyManager tm = (TelephonyManager) o;
return tm.getSimCountryIso(); return tm == null ? "" : tm.getSimCountryIso();
} }
} }

View File

@@ -18,6 +18,8 @@ public interface EventBus {
/** /**
* Asynchronously notifies all listeners of an event. Listeners are * Asynchronously notifies all listeners of an event. Listeners are
* notified on the {@link EventExecutor}. * notified on the {@link EventExecutor}.
* <p>
* This method can safely be called while holding a lock.
*/ */
void broadcast(Event e); void broadcast(Event e);
} }

View File

@@ -8,6 +8,4 @@ public interface BluetoothConstants {
String PROP_ADDRESS = "address"; String PROP_ADDRESS = "address";
String PROP_UUID = "uuid"; String PROP_UUID = "uuid";
String PREF_BT_ENABLE = "enable";
} }

View File

@@ -0,0 +1,18 @@
package org.briarproject.bramble.api.plugin;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.properties.TransportProperties;
/**
* An interface for handling peers discovered by transport plugins.
*/
@NotNullByDefault
public interface DiscoveryHandler {
/**
* Handles a peer discovered by a transport plugin.
*
* @param p A set of properties describing the discovered peer.
*/
void handleDevice(TransportProperties p);
}

View File

@@ -3,12 +3,55 @@ package org.briarproject.bramble.api.plugin;
import org.briarproject.bramble.api.Pair; import org.briarproject.bramble.api.Pair;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.properties.TransportProperties; import org.briarproject.bramble.api.properties.TransportProperties;
import org.briarproject.bramble.api.settings.SettingsManager;
import java.util.Collection; import java.util.List;
@NotNullByDefault @NotNullByDefault
public interface Plugin { public interface Plugin {
enum State {
/**
* The plugin has not finished starting or has been stopped.
*/
STARTING_STOPPING,
/**
* The plugin is disabled by settings. Use {@link #getReasonsDisabled()}
* to find out which settings are responsible.
*/
DISABLED,
/**
* The plugin is being enabled and can't yet make or receive
* connections.
*/
ENABLING,
/**
* The plugin is enabled and can make or receive connections.
*/
ACTIVE,
/**
* The plugin is enabled but can't make or receive connections
*/
INACTIVE
}
/**
* The string for the boolean preference
* to use with the {@link SettingsManager} to enable or disable the plugin.
*/
String PREF_PLUGIN_ENABLE = "enable";
/**
* Reason flag returned by {@link #getReasonsDisabled()} to indicate that
* the plugin has been disabled by the user.
*/
int REASON_USER = 1;
/** /**
* Returns the plugin's transport identifier. * Returns the plugin's transport identifier.
*/ */
@@ -35,9 +78,18 @@ public interface Plugin {
void stop() throws PluginException; void stop() throws PluginException;
/** /**
* Returns true if the plugin is running. * Returns the current state of the plugin.
*/ */
boolean isRunning(); State getState();
/**
* Returns a set of flags indicating why the plugin is
* {@link State#DISABLED disabled}, or 0 if the plugin is not disabled.
* <p>
* The flags used are plugin-specific, except the generic flag
* {@link #REASON_USER}, which may be used by any plugin.
*/
int getReasonsDisabled();
/** /**
* Returns true if the plugin should be polled periodically to attempt to * Returns true if the plugin should be polled periodically to attempt to
@@ -54,6 +106,5 @@ public interface Plugin {
* Attempts to create connections using the given transport properties, * Attempts to create connections using the given transport properties,
* passing any created connections to the corresponding handlers. * passing any created connections to the corresponding handlers.
*/ */
void poll(Collection<Pair<TransportProperties, ConnectionHandler>> void poll(List<Pair<TransportProperties, ConnectionHandler>> properties);
properties);
} }

View File

@@ -1,6 +1,10 @@
package org.briarproject.bramble.api.plugin; package org.briarproject.bramble.api.plugin;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.Plugin.State;
import org.briarproject.bramble.api.plugin.event.TransportActiveEvent;
import org.briarproject.bramble.api.plugin.event.TransportInactiveEvent;
import org.briarproject.bramble.api.plugin.event.TransportStateEvent;
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;
@@ -32,12 +36,17 @@ public interface PluginCallback extends ConnectionHandler {
void mergeLocalProperties(TransportProperties p); void mergeLocalProperties(TransportProperties p);
/** /**
* Signals that the transport is enabled. * Informs the callback of the plugin's current state.
* <p>
* If the current state is different from the previous state, the callback
* will broadcast a {@link TransportStateEvent}. If the current state is
* {@link State#ACTIVE} and the previous state was not
* {@link State#ACTIVE}, the callback will broadcast a
* {@link TransportActiveEvent}. If the current state is not
* {@link State#ACTIVE} and the previous state was {@link State#ACTIVE},
* the callback will broadcast a {@link TransportInactiveEvent}.
* <p>
* This method can safely be called while holding a lock.
*/ */
void transportEnabled(); void pluginStateChanged(State state);
/**
* Signals that the transport is disabled.
*/
void transportDisabled();
} }

View File

@@ -41,4 +41,17 @@ public interface PluginManager {
* Returns any duplex plugins that support rendezvous. * Returns any duplex plugins that support rendezvous.
*/ */
Collection<DuplexPlugin> getRendezvousPlugins(); Collection<DuplexPlugin> getRendezvousPlugins();
/**
* Enables or disables the plugin
* identified by the given {@link TransportId}.
* <p>
* Note that this applies the change asynchronously
* and there are no order guarantees.
* <p>
* If no plugin with the given {@link TransportId} is registered,
* this is a no-op.
*/
void setPluginEnabled(TransportId t, boolean enabled);
} }

View File

@@ -21,6 +21,21 @@ public interface TorConstants {
int PREF_TOR_NETWORK_AUTOMATIC = 0; int PREF_TOR_NETWORK_AUTOMATIC = 0;
int PREF_TOR_NETWORK_WITHOUT_BRIDGES = 1; int PREF_TOR_NETWORK_WITHOUT_BRIDGES = 1;
int PREF_TOR_NETWORK_WITH_BRIDGES = 2; int PREF_TOR_NETWORK_WITH_BRIDGES = 2;
// TODO: Remove when settings migration code is removed
int PREF_TOR_NETWORK_NEVER = 3; int PREF_TOR_NETWORK_NEVER = 3;
/**
* Reason flag returned by {@link Plugin#getReasonsDisabled()}.
*/
int REASON_BATTERY = 2;
/**
* Reason flag returned by {@link Plugin#getReasonsDisabled()}.
*/
int REASON_MOBILE_DATA = 4;
/**
* Reason flag returned by {@link Plugin#getReasonsDisabled()}.
*/
int REASON_COUNTRY_BLOCKED = 8;
} }

View File

@@ -1,14 +1,18 @@
package org.briarproject.bramble.api.plugin.duplex; package org.briarproject.bramble.api.plugin.duplex;
import org.briarproject.bramble.api.Pair;
import org.briarproject.bramble.api.data.BdfList; import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.keyagreement.KeyAgreementListener; import org.briarproject.bramble.api.keyagreement.KeyAgreementListener;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.ConnectionHandler; import org.briarproject.bramble.api.plugin.ConnectionHandler;
import org.briarproject.bramble.api.plugin.DiscoveryHandler;
import org.briarproject.bramble.api.plugin.Plugin; import org.briarproject.bramble.api.plugin.Plugin;
import org.briarproject.bramble.api.properties.TransportProperties; import org.briarproject.bramble.api.properties.TransportProperties;
import org.briarproject.bramble.api.rendezvous.KeyMaterialSource; import org.briarproject.bramble.api.rendezvous.KeyMaterialSource;
import org.briarproject.bramble.api.rendezvous.RendezvousEndpoint; import org.briarproject.bramble.api.rendezvous.RendezvousEndpoint;
import java.util.List;
import javax.annotation.Nullable; import javax.annotation.Nullable;
/** /**
@@ -58,4 +62,16 @@ public interface DuplexPlugin extends Plugin {
@Nullable @Nullable
RendezvousEndpoint createRendezvousEndpoint(KeyMaterialSource k, RendezvousEndpoint createRendezvousEndpoint(KeyMaterialSource k,
boolean alice, ConnectionHandler incoming); boolean alice, ConnectionHandler incoming);
/**
* Returns true if the plugin supports peer discovery.
*/
boolean supportsDiscovery();
/**
* Attempts to discover peers using the given transport properties, passing
* any discovered peers to the corresponding handlers.
*/
void discoverPeers(List<Pair<TransportProperties, DiscoveryHandler>>
properties);
} }

View File

@@ -2,20 +2,22 @@ package org.briarproject.bramble.api.plugin.event;
import org.briarproject.bramble.api.event.Event; import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.Plugin.State;
import org.briarproject.bramble.api.plugin.TransportId; import org.briarproject.bramble.api.plugin.TransportId;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
/** /**
* An event that is broadcast when a transport is disabled. * An event that is broadcast when a plugin enters the {@link State#ACTIVE}
* state.
*/ */
@Immutable @Immutable
@NotNullByDefault @NotNullByDefault
public class TransportDisabledEvent extends Event { public class TransportActiveEvent extends Event {
private final TransportId transportId; private final TransportId transportId;
public TransportDisabledEvent(TransportId transportId) { public TransportActiveEvent(TransportId transportId) {
this.transportId = transportId; this.transportId = transportId;
} }

View File

@@ -2,20 +2,22 @@ package org.briarproject.bramble.api.plugin.event;
import org.briarproject.bramble.api.event.Event; import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.Plugin.State;
import org.briarproject.bramble.api.plugin.TransportId; import org.briarproject.bramble.api.plugin.TransportId;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
/** /**
* An event that is broadcast when a transport is enabled. * An event that is broadcast when a plugin leaves the {@link State#ACTIVE}
* state.
*/ */
@Immutable @Immutable
@NotNullByDefault @NotNullByDefault
public class TransportEnabledEvent extends Event { public class TransportInactiveEvent extends Event {
private final TransportId transportId; private final TransportId transportId;
public TransportEnabledEvent(TransportId transportId) { public TransportInactiveEvent(TransportId transportId) {
this.transportId = transportId; this.transportId = transportId;
} }

View File

@@ -0,0 +1,32 @@
package org.briarproject.bramble.api.plugin.event;
import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.Plugin.State;
import org.briarproject.bramble.api.plugin.TransportId;
import javax.annotation.concurrent.Immutable;
/**
* An event that is broadcast when the {@link State state} of a plugin changes.
*/
@Immutable
@NotNullByDefault
public class TransportStateEvent extends Event {
private final TransportId transportId;
private final State state;
public TransportStateEvent(TransportId transportId, State state) {
this.transportId = transportId;
this.state = state;
}
public TransportId getTransportId() {
return transportId;
}
public State getState() {
return state;
}
}

View File

@@ -8,6 +8,7 @@ import org.briarproject.bramble.api.lifecycle.ServiceException;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.ConnectionManager; import org.briarproject.bramble.api.plugin.ConnectionManager;
import org.briarproject.bramble.api.plugin.Plugin; import org.briarproject.bramble.api.plugin.Plugin;
import org.briarproject.bramble.api.plugin.Plugin.State;
import org.briarproject.bramble.api.plugin.PluginCallback; import org.briarproject.bramble.api.plugin.PluginCallback;
import org.briarproject.bramble.api.plugin.PluginConfig; import org.briarproject.bramble.api.plugin.PluginConfig;
import org.briarproject.bramble.api.plugin.PluginException; import org.briarproject.bramble.api.plugin.PluginException;
@@ -18,8 +19,9 @@ import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin; import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginFactory; import org.briarproject.bramble.api.plugin.duplex.DuplexPluginFactory;
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection; import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
import org.briarproject.bramble.api.plugin.event.TransportDisabledEvent; import org.briarproject.bramble.api.plugin.event.TransportActiveEvent;
import org.briarproject.bramble.api.plugin.event.TransportEnabledEvent; import org.briarproject.bramble.api.plugin.event.TransportInactiveEvent;
import org.briarproject.bramble.api.plugin.event.TransportStateEvent;
import org.briarproject.bramble.api.plugin.simplex.SimplexPlugin; import org.briarproject.bramble.api.plugin.simplex.SimplexPlugin;
import org.briarproject.bramble.api.plugin.simplex.SimplexPluginFactory; import org.briarproject.bramble.api.plugin.simplex.SimplexPluginFactory;
import org.briarproject.bramble.api.properties.TransportProperties; import org.briarproject.bramble.api.properties.TransportProperties;
@@ -36,6 +38,7 @@ import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.logging.Logger; import java.util.logging.Logger;
import javax.annotation.concurrent.ThreadSafe; import javax.annotation.concurrent.ThreadSafe;
@@ -45,6 +48,9 @@ import static java.util.logging.Level.FINE;
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 java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.plugin.Plugin.PREF_PLUGIN_ENABLE;
import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE;
import static org.briarproject.bramble.api.plugin.Plugin.State.STARTING_STOPPING;
import static org.briarproject.bramble.util.LogUtils.logDuration; import static org.briarproject.bramble.util.LogUtils.logDuration;
import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.LogUtils.now; import static org.briarproject.bramble.util.LogUtils.now;
@@ -177,6 +183,26 @@ class PluginManagerImpl implements PluginManager, Service {
return supported; return supported;
} }
@Override
public void setPluginEnabled(TransportId t, boolean enabled) {
Plugin plugin = plugins.get(t);
if (plugin == null) return;
Settings s = new Settings();
s.putBoolean(PREF_PLUGIN_ENABLE, enabled);
ioExecutor.execute(() -> mergeSettings(s, t.getString()));
}
private void mergeSettings(Settings s, String namespace) {
try {
long start = now();
settingsManager.mergeSettings(s, namespace);
logDuration(LOG, "Merging settings", start);
} catch (DbException e) {
logException(LOG, WARNING, e);
}
}
private class PluginStarter implements Runnable { private class PluginStarter implements Runnable {
private final Plugin plugin; private final Plugin plugin;
@@ -250,7 +276,8 @@ class PluginManagerImpl implements PluginManager, Service {
private class Callback implements PluginCallback { private class Callback implements PluginCallback {
private final TransportId id; private final TransportId id;
private final AtomicBoolean enabled = new AtomicBoolean(false); private final AtomicReference<State> state =
new AtomicReference<>(STARTING_STOPPING);
private Callback(TransportId id) { private Callback(TransportId id) {
this.id = id; this.id = id;
@@ -278,11 +305,7 @@ class PluginManagerImpl implements PluginManager, Service {
@Override @Override
public void mergeSettings(Settings s) { public void mergeSettings(Settings s) {
try { PluginManagerImpl.this.mergeSettings(s, id.getString());
settingsManager.mergeSettings(s, id.getString());
} catch (DbException e) {
logException(LOG, WARNING, e);
}
} }
@Override @Override
@@ -295,15 +318,20 @@ class PluginManagerImpl implements PluginManager, Service {
} }
@Override @Override
public void transportEnabled() { public void pluginStateChanged(State newState) {
if (!enabled.getAndSet(true)) State oldState = state.getAndSet(newState);
eventBus.broadcast(new TransportEnabledEvent(id)); if (newState != oldState) {
if (LOG.isLoggable(INFO)) {
LOG.info(id + " changed from state " + oldState
+ " to " + newState);
}
eventBus.broadcast(new TransportStateEvent(id, newState));
if (newState == ACTIVE) {
eventBus.broadcast(new TransportActiveEvent(id));
} else if (oldState == ACTIVE) {
eventBus.broadcast(new TransportInactiveEvent(id));
}
} }
@Override
public void transportDisabled() {
if (enabled.getAndSet(false))
eventBus.broadcast(new TransportDisabledEvent(id));
} }
@Override @Override

View File

@@ -11,6 +11,7 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.ConnectionHandler; import org.briarproject.bramble.api.plugin.ConnectionHandler;
import org.briarproject.bramble.api.plugin.ConnectionManager; import org.briarproject.bramble.api.plugin.ConnectionManager;
import org.briarproject.bramble.api.plugin.ConnectionRegistry; import org.briarproject.bramble.api.plugin.ConnectionRegistry;
import org.briarproject.bramble.api.plugin.DiscoveryHandler;
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.TransportConnectionReader; import org.briarproject.bramble.api.plugin.TransportConnectionReader;
@@ -20,8 +21,8 @@ import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection; import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
import org.briarproject.bramble.api.plugin.event.ConnectionClosedEvent; import org.briarproject.bramble.api.plugin.event.ConnectionClosedEvent;
import org.briarproject.bramble.api.plugin.event.ConnectionOpenedEvent; import org.briarproject.bramble.api.plugin.event.ConnectionOpenedEvent;
import org.briarproject.bramble.api.plugin.event.TransportDisabledEvent; import org.briarproject.bramble.api.plugin.event.TransportActiveEvent;
import org.briarproject.bramble.api.plugin.event.TransportEnabledEvent; import org.briarproject.bramble.api.plugin.event.TransportInactiveEvent;
import org.briarproject.bramble.api.plugin.simplex.SimplexPlugin; import org.briarproject.bramble.api.plugin.simplex.SimplexPlugin;
import org.briarproject.bramble.api.properties.TransportProperties; import org.briarproject.bramble.api.properties.TransportProperties;
import org.briarproject.bramble.api.properties.TransportPropertyManager; import org.briarproject.bramble.api.properties.TransportPropertyManager;
@@ -32,6 +33,7 @@ import java.security.SecureRandom;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
@@ -106,13 +108,13 @@ class PollerImpl implements Poller, EventListener {
ConnectionOpenedEvent c = (ConnectionOpenedEvent) e; ConnectionOpenedEvent c = (ConnectionOpenedEvent) e;
// Reschedule polling, the polling interval may have decreased // Reschedule polling, the polling interval may have decreased
reschedule(c.getTransportId()); reschedule(c.getTransportId());
} else if (e instanceof TransportEnabledEvent) { } else if (e instanceof TransportActiveEvent) {
TransportEnabledEvent t = (TransportEnabledEvent) e; TransportActiveEvent t = (TransportActiveEvent) e;
// Poll the newly enabled transport // Poll the newly activated transport
pollNow(t.getTransportId()); pollNow(t.getTransportId());
} else if (e instanceof TransportDisabledEvent) { } else if (e instanceof TransportInactiveEvent) {
TransportDisabledEvent t = (TransportDisabledEvent) e; TransportInactiveEvent t = (TransportInactiveEvent) e;
// Cancel polling for the disabled transport // Cancel polling for the deactivated transport
cancel(t.getTransportId()); cancel(t.getTransportId());
} }
} }
@@ -210,18 +212,20 @@ class PollerImpl implements Poller, EventListener {
@IoExecutor @IoExecutor
private void poll(Plugin p) { private void poll(Plugin p) {
TransportId t = p.getId(); TransportId t = p.getId();
if (LOG.isLoggable(INFO)) LOG.info("Polling plugin " + t); if (LOG.isLoggable(INFO)) LOG.info("Polling " + t);
try { try {
Map<ContactId, TransportProperties> remote = Map<ContactId, TransportProperties> remote =
transportPropertyManager.getRemoteProperties(t); transportPropertyManager.getRemoteProperties(t);
Collection<ContactId> connected = Collection<ContactId> connected =
connectionRegistry.getConnectedContacts(t); connectionRegistry.getConnectedContacts(t);
Collection<Pair<TransportProperties, ConnectionHandler>> List<Pair<TransportProperties, ConnectionHandler>> properties =
properties = new ArrayList<>(); new ArrayList<>();
for (Entry<ContactId, TransportProperties> e : remote.entrySet()) { for (Entry<ContactId, TransportProperties> e : remote.entrySet()) {
ContactId c = e.getKey(); ContactId c = e.getKey();
if (!connected.contains(c)) if (!connected.contains(c)) {
properties.add(new Pair<>(e.getValue(), new Handler(c, t))); ConnHandler handler = new ConnHandler(c, t);
properties.add(new Pair<>(e.getValue(), handler));
}
} }
if (!properties.isEmpty()) p.poll(properties); if (!properties.isEmpty()) p.poll(properties);
} catch (DbException e) { } catch (DbException e) {
@@ -229,6 +233,30 @@ class PollerImpl implements Poller, EventListener {
} }
} }
@IoExecutor
private void discover(DuplexPlugin p) {
TransportId t = p.getId();
if (LOG.isLoggable(INFO)) LOG.info("Discovering peers for " + t);
try {
Map<ContactId, TransportProperties> remote =
transportPropertyManager.getRemoteProperties(t);
Collection<ContactId> connected =
connectionRegistry.getConnectedContacts(t);
List<Pair<TransportProperties, DiscoveryHandler>> properties =
new ArrayList<>();
for (Entry<ContactId, TransportProperties> e : remote.entrySet()) {
ContactId c = e.getKey();
if (!connected.contains(c)) {
DiscoHandler handler = new DiscoHandler(c, p);
properties.add(new Pair<>(e.getValue(), handler));
}
}
if (!properties.isEmpty()) p.discoverPeers(properties);
} catch (DbException e) {
logException(LOG, WARNING, e);
}
}
private class ScheduledPollTask { private class ScheduledPollTask {
private final PollTask task; private final PollTask task;
@@ -268,16 +296,23 @@ class PollerImpl implements Poller, EventListener {
int delay = plugin.getPollingInterval(); int delay = plugin.getPollingInterval();
if (randomiseNext) delay = (int) (delay * random.nextDouble()); if (randomiseNext) delay = (int) (delay * random.nextDouble());
schedule(plugin, delay, false); schedule(plugin, delay, false);
// FIXME: Revert
if (plugin instanceof DuplexPlugin) {
DuplexPlugin d = (DuplexPlugin) plugin;
if (d.supportsDiscovery()) discover(d);
else poll(d);
} else {
poll(plugin); poll(plugin);
} }
} }
}
private class Handler implements ConnectionHandler { private class ConnHandler implements ConnectionHandler {
private final ContactId contactId; private final ContactId contactId;
private final TransportId transportId; private final TransportId transportId;
private Handler(ContactId contactId, TransportId transportId) { private ConnHandler(ContactId contactId, TransportId transportId) {
this.contactId = contactId; this.contactId = contactId;
this.transportId = transportId; this.transportId = transportId;
} }
@@ -300,4 +335,27 @@ class PollerImpl implements Poller, EventListener {
transportId, w); transportId, w);
} }
} }
private class DiscoHandler implements DiscoveryHandler {
private final ContactId contactId;
private final DuplexPlugin plugin;
private DiscoHandler(ContactId contactId, DuplexPlugin plugin) {
this.contactId = contactId;
this.plugin = plugin;
}
@Override
public void handleDevice(TransportProperties p) {
LOG.info("Discovered contact via " + plugin.getId());
ioExecutor.execute(() -> {
DuplexTransportConnection c = plugin.createConnection(p);
if (c != null) {
connectionManager.manageOutgoingConnection(contactId,
plugin.getId(), c);
}
});
}
}
} }

View File

@@ -9,10 +9,13 @@ import org.briarproject.bramble.api.keyagreement.KeyAgreementConnection;
import org.briarproject.bramble.api.keyagreement.KeyAgreementListener; import org.briarproject.bramble.api.keyagreement.KeyAgreementListener;
import org.briarproject.bramble.api.keyagreement.event.KeyAgreementListeningEvent; import org.briarproject.bramble.api.keyagreement.event.KeyAgreementListeningEvent;
import org.briarproject.bramble.api.keyagreement.event.KeyAgreementStoppedListeningEvent; import org.briarproject.bramble.api.keyagreement.event.KeyAgreementStoppedListeningEvent;
import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.plugin.Backoff; import org.briarproject.bramble.api.plugin.Backoff;
import org.briarproject.bramble.api.plugin.ConnectionHandler; import org.briarproject.bramble.api.plugin.ConnectionHandler;
import org.briarproject.bramble.api.plugin.DiscoveryHandler;
import org.briarproject.bramble.api.plugin.PluginCallback; import org.briarproject.bramble.api.plugin.PluginCallback;
import org.briarproject.bramble.api.plugin.PluginException; import org.briarproject.bramble.api.plugin.PluginException;
import org.briarproject.bramble.api.plugin.TransportId; import org.briarproject.bramble.api.plugin.TransportId;
@@ -29,23 +32,28 @@ import org.briarproject.bramble.api.settings.event.SettingsUpdatedEvent;
import java.io.IOException; import java.io.IOException;
import java.security.SecureRandom; import java.security.SecureRandom;
import java.util.Collection; import java.util.List;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Logger; import java.util.logging.Logger;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.ThreadSafe;
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 java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.TRANSPORT_ID_BLUETOOTH; import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.TRANSPORT_ID_BLUETOOTH;
import static org.briarproject.bramble.api.plugin.BluetoothConstants.ID; import static org.briarproject.bramble.api.plugin.BluetoothConstants.ID;
import static org.briarproject.bramble.api.plugin.BluetoothConstants.PREF_BT_ENABLE;
import static org.briarproject.bramble.api.plugin.BluetoothConstants.PROP_ADDRESS; import static org.briarproject.bramble.api.plugin.BluetoothConstants.PROP_ADDRESS;
import static org.briarproject.bramble.api.plugin.BluetoothConstants.PROP_UUID; 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.api.plugin.Plugin.State.ACTIVE;
import static org.briarproject.bramble.api.plugin.Plugin.State.DISABLED;
import static org.briarproject.bramble.api.plugin.Plugin.State.INACTIVE;
import static org.briarproject.bramble.api.plugin.Plugin.State.STARTING_STOPPING;
import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.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.isNullOrEmpty;
@@ -68,9 +76,9 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
private final int maxLatency; private final int maxLatency;
private final AtomicBoolean used = new AtomicBoolean(false); private final AtomicBoolean used = new AtomicBoolean(false);
private volatile boolean running = false, contactConnections = false; protected final PluginState state = new PluginState();
private volatile String contactConnectionsUuid = null; private volatile String contactConnectionsUuid = null;
private volatile SS socket = null;
abstract void initialiseAdapter() throws IOException; abstract void initialiseAdapter() throws IOException;
@@ -119,14 +127,18 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
LOG.info("Bluetooth enabled"); LOG.info("Bluetooth enabled");
// We may not have been able to get the local address before // We may not have been able to get the local address before
ioExecutor.execute(this::updateProperties); ioExecutor.execute(this::updateProperties);
if (shouldAllowContactConnections()) bind(); if (getState() == INACTIVE) bind();
} }
void onAdapterDisabled() { void onAdapterDisabled() {
LOG.info("Bluetooth disabled"); LOG.info("Bluetooth disabled");
tryToClose(socket);
connectionLimiter.allConnectionsClosed(); connectionLimiter.allConnectionsClosed();
callback.transportDisabled(); // The server socket may not have been closed automatically
SS ss = state.clearServerSocket();
if (ss != null) {
LOG.info("Closing server socket");
tryToClose(ss);
}
} }
@Override @Override
@@ -148,31 +160,24 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
@Override @Override
public void start() throws PluginException { public void start() throws PluginException {
if (used.getAndSet(true)) throw new IllegalStateException(); if (used.getAndSet(true)) throw new IllegalStateException();
Settings settings = callback.getSettings();
boolean enabledByUser = settings.getBoolean(PREF_PLUGIN_ENABLE, false);
state.setStarted(enabledByUser);
try { try {
initialiseAdapter(); initialiseAdapter();
} catch (IOException e) { } catch (IOException e) {
throw new PluginException(e); throw new PluginException(e);
} }
updateProperties(); updateProperties();
running = true; if (enabledByUser) {
loadSettings(callback.getSettings());
if (shouldAllowContactConnections()) {
if (isAdapterEnabled()) bind(); if (isAdapterEnabled()) bind();
else enableAdapter(); else enableAdapter();
} }
} }
private void loadSettings(Settings settings) {
contactConnections = settings.getBoolean(PREF_BT_ENABLE, false);
}
private boolean shouldAllowContactConnections() {
return contactConnections;
}
private void bind() { private void bind() {
ioExecutor.execute(() -> { ioExecutor.execute(() -> {
if (!isRunning() || !shouldAllowContactConnections()) return; if (getState() != INACTIVE) return;
// Bind a server socket to accept connections from contacts // Bind a server socket to accept connections from contacts
SS ss; SS ss;
try { try {
@@ -181,14 +186,13 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
logException(LOG, WARNING, e); logException(LOG, WARNING, e);
return; return;
} }
if (!isRunning() || !shouldAllowContactConnections()) { if (!state.setServerSocket(ss)) {
LOG.info("Closing redundant server socket");
tryToClose(ss); tryToClose(ss);
return; return;
} }
socket = ss;
backoff.reset(); backoff.reset();
callback.transportEnabled(); acceptContactConnections(ss);
acceptContactConnections();
}); });
} }
@@ -217,34 +221,39 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
if (changed) callback.mergeLocalProperties(p); if (changed) callback.mergeLocalProperties(p);
} }
private void acceptContactConnections() { private void acceptContactConnections(SS ss) {
while (true) { while (true) {
DuplexTransportConnection conn; DuplexTransportConnection conn;
try { try {
conn = acceptConnection(socket); conn = acceptConnection(ss);
} catch (IOException e) { } catch (IOException e) {
// This is expected when the socket is closed // This is expected when the server socket is closed
if (LOG.isLoggable(INFO)) LOG.info(e.toString()); LOG.info("Server socket closed");
state.clearServerSocket();
return; return;
} }
LOG.info("Connection received");
backoff.reset(); backoff.reset();
if (connectionLimiter.contactConnectionOpened(conn)) if (connectionLimiter.contactConnectionOpened(conn))
callback.handleConnection(conn); callback.handleConnection(conn);
if (!running) return;
} }
} }
@Override @Override
public void stop() { public void stop() {
running = false; SS ss = state.setStopped();
tryToClose(socket); tryToClose(ss);
callback.transportDisabled();
disableAdapterIfEnabledByUs(); disableAdapterIfEnabledByUs();
} }
@Override @Override
public boolean isRunning() { public State getState() {
return running && isAdapterEnabled(); return state.getState();
}
@Override
public int getReasonsDisabled() {
return state.getReasonsDisabled();
} }
@Override @Override
@@ -258,9 +267,9 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
} }
@Override @Override
public void poll(Collection<Pair<TransportProperties, ConnectionHandler>> public void poll(
properties) { List<Pair<TransportProperties, ConnectionHandler>> properties) {
if (!isRunning() || !shouldAllowContactConnections()) return; if (getState() != ACTIVE) return;
backoff.increment(); backoff.increment();
for (Pair<TransportProperties, ConnectionHandler> p : properties) { for (Pair<TransportProperties, ConnectionHandler> p : properties) {
connect(p.getFirst(), p.getSecond()); connect(p.getFirst(), p.getSecond());
@@ -273,7 +282,7 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
String uuid = p.get(PROP_UUID); String uuid = p.get(PROP_UUID);
if (isNullOrEmpty(uuid)) return; if (isNullOrEmpty(uuid)) return;
ioExecutor.execute(() -> { ioExecutor.execute(() -> {
if (!isRunning() || !shouldAllowContactConnections()) return; if (getState() != ACTIVE) return;
if (!connectionLimiter.canOpenContactConnection()) return; if (!connectionLimiter.canOpenContactConnection()) return;
DuplexTransportConnection d = createConnection(p); DuplexTransportConnection d = createConnection(p);
if (d != null) { if (d != null) {
@@ -317,7 +326,7 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
@Override @Override
public DuplexTransportConnection createConnection(TransportProperties p) { public DuplexTransportConnection createConnection(TransportProperties p) {
if (!isRunning() || !shouldAllowContactConnections()) return null; if (getState() != ACTIVE) 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 (isNullOrEmpty(address)) return null; if (isNullOrEmpty(address)) return null;
@@ -336,7 +345,7 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
@Override @Override
public KeyAgreementListener createKeyAgreementListener(byte[] commitment) { public KeyAgreementListener createKeyAgreementListener(byte[] commitment) {
if (!isRunning()) 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();
if (LOG.isLoggable(INFO)) LOG.info("Key agreement UUID " + uuid); if (LOG.isLoggable(INFO)) LOG.info("Key agreement UUID " + uuid);
@@ -348,7 +357,7 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
logException(LOG, WARNING, e); logException(LOG, WARNING, e);
return null; return null;
} }
if (!isRunning()) { if (getState() != ACTIVE) {
tryToClose(ss); tryToClose(ss);
return null; return null;
} }
@@ -362,7 +371,7 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
@Override @Override
public DuplexTransportConnection createKeyAgreementConnection( public DuplexTransportConnection createKeyAgreementConnection(
byte[] commitment, BdfList descriptor) { byte[] commitment, BdfList descriptor) {
if (!isRunning()) 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;
@@ -403,6 +412,17 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
@Override
public boolean supportsDiscovery() {
return false;
}
@Override
public void discoverPeers(
List<Pair<TransportProperties, DiscoveryHandler>> properties) {
throw new UnsupportedOperationException();
}
@Override @Override
public void eventOccurred(Event e) { public void eventOccurred(Event e) {
if (e instanceof EnableBluetoothEvent) { if (e instanceof EnableBluetoothEvent) {
@@ -422,17 +442,17 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
} }
} }
@IoExecutor
private void onSettingsUpdated(Settings settings) { private void onSettingsUpdated(Settings settings) {
boolean wasAllowed = shouldAllowContactConnections(); boolean enabledByUser = settings.getBoolean(PREF_PLUGIN_ENABLE, false);
loadSettings(settings); SS ss = state.setEnabledByUser(enabledByUser);
boolean isAllowed = shouldAllowContactConnections(); State s = getState();
if (wasAllowed && !isAllowed) { if (ss != null) {
LOG.info("Contact connections disabled"); LOG.info("Disabled by user, closing server socket");
tryToClose(socket); tryToClose(ss);
callback.transportDisabled();
disableAdapterIfEnabledByUs(); disableAdapterIfEnabledByUs();
} else if (!wasAllowed && isAllowed) { } else if (s == INACTIVE) {
LOG.info("Contact connections enabled"); LOG.info("Enabled by user, opening server socket");
if (isAdapterEnabled()) bind(); if (isAdapterEnabled()) bind();
else enableAdapter(); else enableAdapter();
} }
@@ -460,4 +480,70 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
tryToClose(ss); tryToClose(ss);
} }
} }
@ThreadSafe
@NotNullByDefault
protected class PluginState {
@GuardedBy("this")
private boolean started = false,
stopped = false,
enabledByUser = false;
@GuardedBy("this")
@Nullable
private SS serverSocket = null;
synchronized void setStarted(boolean enabledByUser) {
started = true;
this.enabledByUser = enabledByUser;
callback.pluginStateChanged(getState());
}
@Nullable
synchronized SS setStopped() {
stopped = true;
SS ss = serverSocket;
serverSocket = null;
callback.pluginStateChanged(getState());
return ss;
}
@Nullable
synchronized SS setEnabledByUser(boolean enabledByUser) {
this.enabledByUser = enabledByUser;
SS ss = null;
if (!enabledByUser) {
ss = serverSocket;
serverSocket = null;
}
callback.pluginStateChanged(getState());
return ss;
}
synchronized boolean setServerSocket(SS ss) {
if (stopped || serverSocket != null) return false;
serverSocket = ss;
callback.pluginStateChanged(getState());
return true;
}
@Nullable
synchronized SS clearServerSocket() {
SS ss = serverSocket;
serverSocket = null;
callback.pluginStateChanged(getState());
return ss;
}
synchronized State getState() {
if (!started || stopped) return STARTING_STOPPING;
if (!enabledByUser) return DISABLED;
return serverSocket == null ? INACTIVE : ACTIVE;
}
synchronized int getReasonsDisabled() {
return getState() == DISABLED ? REASON_USER : 0;
}
}
} }

View File

@@ -16,6 +16,7 @@ import java.util.logging.Logger;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger; import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.plugin.FileConstants.PROP_PATH; import static org.briarproject.bramble.api.plugin.FileConstants.PROP_PATH;
import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE;
import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty; import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
@@ -45,7 +46,7 @@ abstract class FilePlugin implements SimplexPlugin {
@Override @Override
public TransportConnectionReader createReader(TransportProperties p) { public TransportConnectionReader createReader(TransportProperties p) {
if (!isRunning()) return null; if (getState() != ACTIVE) return null;
String path = p.get(PROP_PATH); String path = p.get(PROP_PATH);
if (isNullOrEmpty(path)) return null; if (isNullOrEmpty(path)) return null;
try { try {
@@ -60,7 +61,7 @@ abstract class FilePlugin implements SimplexPlugin {
@Override @Override
public TransportConnectionWriter createWriter(TransportProperties p) { public TransportConnectionWriter createWriter(TransportProperties p) {
if (!isRunning()) return null; if (getState() != ACTIVE) return null;
String path = p.get(PROP_PATH); String path = p.get(PROP_PATH);
if (isNullOrEmpty(path)) return null; if (isNullOrEmpty(path)) return null;
try { try {

View File

@@ -11,7 +11,6 @@ import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection; import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
import org.briarproject.bramble.api.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.util.IoUtils;
import java.io.IOException; import java.io.IOException;
import java.net.Inet4Address; import java.net.Inet4Address;
@@ -41,6 +40,7 @@ import static org.briarproject.bramble.api.plugin.LanTcpConstants.PREF_LAN_IP_PO
import static org.briarproject.bramble.api.plugin.LanTcpConstants.PROP_IP_PORTS; import static org.briarproject.bramble.api.plugin.LanTcpConstants.PROP_IP_PORTS;
import static org.briarproject.bramble.api.plugin.LanTcpConstants.PROP_PORT; import static org.briarproject.bramble.api.plugin.LanTcpConstants.PROP_PORT;
import static org.briarproject.bramble.util.ByteUtils.MAX_16_BIT_UNSIGNED; import static org.briarproject.bramble.util.ByteUtils.MAX_16_BIT_UNSIGNED;
import static org.briarproject.bramble.util.IoUtils.tryToClose;
import static org.briarproject.bramble.util.PrivacyUtils.scrubSocketAddress; import static org.briarproject.bramble.util.PrivacyUtils.scrubSocketAddress;
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty; import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
import static org.briarproject.bramble.util.StringUtils.join; import static org.briarproject.bramble.util.StringUtils.join;
@@ -91,7 +91,8 @@ class LanTcpPlugin extends TcpPlugin {
public void start() { public void start() {
if (used.getAndSet(true)) throw new IllegalStateException(); if (used.getAndSet(true)) throw new IllegalStateException();
initialisePortProperty(); initialisePortProperty();
running = true; Settings settings = callback.getSettings();
state.setStarted(settings.getBoolean(PREF_PLUGIN_ENABLE, false));
bind(); bind();
} }
@@ -271,10 +272,10 @@ class LanTcpPlugin extends TcpPlugin {
} catch (IOException e) { } catch (IOException e) {
if (LOG.isLoggable(INFO)) if (LOG.isLoggable(INFO))
LOG.info("Failed to bind " + scrubSocketAddress(addr)); LOG.info("Failed to bind " + scrubSocketAddress(addr));
tryToClose(ss); tryToClose(ss, LOG, WARNING);
} }
} }
if (ss == null || !ss.isBound()) { if (ss == null) {
LOG.info("Could not bind server socket for key agreement"); LOG.info("Could not bind server socket for key agreement");
return null; return null;
} }
@@ -290,8 +291,8 @@ class LanTcpPlugin extends TcpPlugin {
@Override @Override
public DuplexTransportConnection createKeyAgreementConnection( public DuplexTransportConnection createKeyAgreementConnection(
byte[] commitment, BdfList descriptor) { byte[] commitment, BdfList descriptor) {
if (!isRunning()) return null; ServerSocket ss = state.getServerSocket();
ServerSocket ss = socket; if (ss == null) return null;
InterfaceAddress local = getLocalInterfaceAddress(ss.getInetAddress()); InterfaceAddress local = getLocalInterfaceAddress(ss.getInetAddress());
if (local == null) { if (local == null) {
LOG.warning("No interface for key agreement server socket"); LOG.warning("No interface for key agreement server socket");
@@ -363,7 +364,7 @@ class LanTcpPlugin extends TcpPlugin {
@Override @Override
public void close() { public void close() {
IoUtils.tryToClose(ss, LOG, WARNING); tryToClose(ss, LOG, WARNING);
} }
} }
} }

View File

@@ -1,5 +1,6 @@
package org.briarproject.bramble.plugin.tcp; package org.briarproject.bramble.plugin.tcp;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.Backoff; import org.briarproject.bramble.api.plugin.Backoff;
import org.briarproject.bramble.api.plugin.BackoffFactory; import org.briarproject.bramble.api.plugin.BackoffFactory;
@@ -26,11 +27,13 @@ public class LanTcpPluginFactory implements DuplexPluginFactory {
private static final double BACKOFF_BASE = 1.2; private static final double BACKOFF_BASE = 1.2;
private final Executor ioExecutor; private final Executor ioExecutor;
private final EventBus eventBus;
private final BackoffFactory backoffFactory; private final BackoffFactory backoffFactory;
public LanTcpPluginFactory(Executor ioExecutor, public LanTcpPluginFactory(Executor ioExecutor, EventBus eventBus,
BackoffFactory backoffFactory) { BackoffFactory backoffFactory) {
this.ioExecutor = ioExecutor; this.ioExecutor = ioExecutor;
this.eventBus = eventBus;
this.backoffFactory = backoffFactory; this.backoffFactory = backoffFactory;
} }
@@ -48,7 +51,9 @@ public class LanTcpPluginFactory implements DuplexPluginFactory {
public DuplexPlugin createPlugin(PluginCallback callback) { public DuplexPlugin createPlugin(PluginCallback callback) {
Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL, Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL,
MAX_POLLING_INTERVAL, BACKOFF_BASE); MAX_POLLING_INTERVAL, BACKOFF_BASE);
return new LanTcpPlugin(ioExecutor, backoff, callback, MAX_LATENCY, LanTcpPlugin plugin = new LanTcpPlugin(ioExecutor, backoff, callback, MAX_LATENCY,
MAX_IDLE_TIME, CONNECTION_TIMEOUT); MAX_IDLE_TIME, CONNECTION_TIMEOUT);
eventBus.addListener(plugin);
return plugin;
} }
} }

View File

@@ -2,19 +2,23 @@ package org.briarproject.bramble.plugin.tcp;
import org.briarproject.bramble.PoliteExecutor; import org.briarproject.bramble.PoliteExecutor;
import org.briarproject.bramble.api.Pair; import org.briarproject.bramble.api.Pair;
import org.briarproject.bramble.api.data.BdfList; import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.keyagreement.KeyAgreementListener; import org.briarproject.bramble.api.event.EventListener;
import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.plugin.Backoff; import org.briarproject.bramble.api.plugin.Backoff;
import org.briarproject.bramble.api.plugin.ConnectionHandler; import org.briarproject.bramble.api.plugin.ConnectionHandler;
import org.briarproject.bramble.api.plugin.DiscoveryHandler;
import org.briarproject.bramble.api.plugin.PluginCallback; import org.briarproject.bramble.api.plugin.PluginCallback;
import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin; import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection; import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
import org.briarproject.bramble.api.properties.TransportProperties; import org.briarproject.bramble.api.properties.TransportProperties;
import org.briarproject.bramble.api.rendezvous.KeyMaterialSource; import org.briarproject.bramble.api.rendezvous.KeyMaterialSource;
import org.briarproject.bramble.api.rendezvous.RendezvousEndpoint; import org.briarproject.bramble.api.rendezvous.RendezvousEndpoint;
import org.briarproject.bramble.util.IoUtils; import org.briarproject.bramble.api.settings.Settings;
import org.briarproject.bramble.api.settings.event.SettingsUpdatedEvent;
import java.io.IOException; import java.io.IOException;
import java.net.InetAddress; import java.net.InetAddress;
@@ -35,19 +39,26 @@ import java.util.logging.Logger;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.ThreadSafe;
import static java.util.Collections.emptyList; import static java.util.Collections.emptyList;
import static java.util.Collections.list; import static java.util.Collections.list;
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 java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE;
import static org.briarproject.bramble.api.plugin.Plugin.State.DISABLED;
import static org.briarproject.bramble.api.plugin.Plugin.State.INACTIVE;
import static org.briarproject.bramble.api.plugin.Plugin.State.STARTING_STOPPING;
import static org.briarproject.bramble.util.IoUtils.tryToClose;
import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.PrivacyUtils.scrubSocketAddress; import static org.briarproject.bramble.util.PrivacyUtils.scrubSocketAddress;
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty; import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
@MethodsNotNullByDefault @MethodsNotNullByDefault
@ParametersNotNullByDefault @ParametersNotNullByDefault
abstract class TcpPlugin implements DuplexPlugin { abstract class TcpPlugin implements DuplexPlugin, EventListener {
private static final Logger LOG = getLogger(TcpPlugin.class.getName()); private static final Logger LOG = getLogger(TcpPlugin.class.getName());
@@ -60,9 +71,7 @@ abstract class TcpPlugin implements DuplexPlugin {
protected final int maxLatency, maxIdleTime; protected final int maxLatency, maxIdleTime;
protected final int connectionTimeout, socketTimeout; protected final int connectionTimeout, socketTimeout;
protected final AtomicBoolean used = new AtomicBoolean(false); protected final AtomicBoolean used = new AtomicBoolean(false);
protected final PluginState state = new PluginState();
protected volatile boolean running = false;
protected volatile ServerSocket socket = null;
/** /**
* Returns zero or more socket addresses on which the plugin should listen, * Returns zero or more socket addresses on which the plugin should listen,
@@ -118,14 +127,14 @@ abstract class TcpPlugin implements DuplexPlugin {
@Override @Override
public void start() { public void start() {
if (used.getAndSet(true)) throw new IllegalStateException(); if (used.getAndSet(true)) throw new IllegalStateException();
running = true; Settings settings = callback.getSettings();
state.setStarted(settings.getBoolean(PREF_PLUGIN_ENABLE, false));
bind(); bind();
} }
protected void bind() { protected void bind() {
bindExecutor.execute(() -> { bindExecutor.execute(() -> {
if (!running) return; if (getState() != INACTIVE) return;
if (socket != null && !socket.isClosed()) return;
ServerSocket ss = null; ServerSocket ss = null;
for (InetSocketAddress addr : getLocalSocketAddresses()) { for (InetSocketAddress addr : getLocalSocketAddresses()) {
try { try {
@@ -135,34 +144,28 @@ abstract class TcpPlugin implements DuplexPlugin {
} catch (IOException e) { } catch (IOException e) {
if (LOG.isLoggable(INFO)) if (LOG.isLoggable(INFO))
LOG.info("Failed to bind " + scrubSocketAddress(addr)); LOG.info("Failed to bind " + scrubSocketAddress(addr));
tryToClose(ss); tryToClose(ss, LOG, WARNING);
} }
} }
if (ss == null || !ss.isBound()) { if (ss == null) {
LOG.info("Could not bind server socket"); LOG.info("Could not bind server socket");
return; return;
} }
if (!running) { if (!state.setServerSocket(ss)) {
tryToClose(ss); LOG.info("Closing redundant server socket");
tryToClose(ss, LOG, WARNING);
return; return;
} }
socket = ss;
backoff.reset(); backoff.reset();
InetSocketAddress local = InetSocketAddress local =
(InetSocketAddress) ss.getLocalSocketAddress(); (InetSocketAddress) ss.getLocalSocketAddress();
setLocalSocketAddress(local); setLocalSocketAddress(local);
if (LOG.isLoggable(INFO)) if (LOG.isLoggable(INFO))
LOG.info("Listening on " + scrubSocketAddress(local)); LOG.info("Listening on " + scrubSocketAddress(local));
callback.transportEnabled(); acceptContactConnections(ss);
acceptContactConnections();
}); });
} }
protected void tryToClose(@Nullable ServerSocket ss) {
IoUtils.tryToClose(ss, LOG, WARNING);
callback.transportDisabled();
}
String getIpPortString(InetSocketAddress a) { String getIpPortString(InetSocketAddress a) {
String addr = a.getAddress().getHostAddress(); String addr = a.getAddress().getHostAddress();
int percent = addr.indexOf('%'); int percent = addr.indexOf('%');
@@ -170,15 +173,16 @@ abstract class TcpPlugin implements DuplexPlugin {
return addr + ":" + a.getPort(); return addr + ":" + a.getPort();
} }
private void acceptContactConnections() { private void acceptContactConnections(ServerSocket ss) {
while (isRunning()) { while (true) {
Socket s; Socket s;
try { try {
s = socket.accept(); s = ss.accept();
s.setSoTimeout(socketTimeout); s.setSoTimeout(socketTimeout);
} catch (IOException e) { } catch (IOException e) {
// This is expected when the socket is closed // This is expected when the server socket is closed
if (LOG.isLoggable(INFO)) LOG.info(e.toString()); LOG.info("Server socket closed");
state.clearServerSocket(ss);
return; return;
} }
if (LOG.isLoggable(INFO)) if (LOG.isLoggable(INFO))
@@ -191,13 +195,18 @@ abstract class TcpPlugin implements DuplexPlugin {
@Override @Override
public void stop() { public void stop() {
running = false; ServerSocket ss = state.setStopped();
tryToClose(socket); tryToClose(ss, LOG, WARNING);
} }
@Override @Override
public boolean isRunning() { public State getState() {
return running && socket != null && !socket.isClosed(); return state.getState();
}
@Override
public int getReasonsDisabled() {
return state.getReasonsDisabled();
} }
@Override @Override
@@ -211,9 +220,9 @@ abstract class TcpPlugin implements DuplexPlugin {
} }
@Override @Override
public void poll(Collection<Pair<TransportProperties, ConnectionHandler>> public void poll(
properties) { List<Pair<TransportProperties, ConnectionHandler>> properties) {
if (!isRunning()) return; if (getState() != ACTIVE) return;
backoff.increment(); backoff.increment();
for (Pair<TransportProperties, ConnectionHandler> p : properties) { for (Pair<TransportProperties, ConnectionHandler> p : properties) {
connect(p.getFirst(), p.getSecond()); connect(p.getFirst(), p.getSecond());
@@ -232,8 +241,8 @@ abstract class TcpPlugin implements DuplexPlugin {
@Override @Override
public DuplexTransportConnection createConnection(TransportProperties p) { public DuplexTransportConnection createConnection(TransportProperties p) {
if (!isRunning()) return null; ServerSocket ss = state.getServerSocket();
ServerSocket ss = socket; if (ss == null) return null;
InterfaceAddress local = getLocalInterfaceAddress(ss.getInetAddress()); InterfaceAddress local = getLocalInterfaceAddress(ss.getInetAddress());
if (local == null) { if (local == null) {
LOG.warning("No interface for server socket"); LOG.warning("No interface for server socket");
@@ -313,22 +322,6 @@ abstract class TcpPlugin implements DuplexPlugin {
} }
} }
@Override
public boolean supportsKeyAgreement() {
return false;
}
@Override
public KeyAgreementListener createKeyAgreementListener(byte[] commitment) {
throw new UnsupportedOperationException();
}
@Override
public DuplexTransportConnection createKeyAgreementConnection(
byte[] commitment, BdfList descriptor) {
throw new UnsupportedOperationException();
}
@Override @Override
public boolean supportsRendezvous() { public boolean supportsRendezvous() {
return false; return false;
@@ -340,6 +333,17 @@ abstract class TcpPlugin implements DuplexPlugin {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
@Override
public boolean supportsDiscovery() {
return false;
}
@Override
public void discoverPeers(
List<Pair<TransportProperties, DiscoveryHandler>> properties) {
throw new UnsupportedOperationException();
}
List<InterfaceAddress> getLocalInterfaceAddresses() { List<InterfaceAddress> getLocalInterfaceAddresses() {
List<InterfaceAddress> addrs = new ArrayList<>(); List<InterfaceAddress> addrs = new ArrayList<>();
for (NetworkInterface iface : getNetworkInterfaces()) { for (NetworkInterface iface : getNetworkInterfaces()) {
@@ -366,4 +370,93 @@ abstract class TcpPlugin implements DuplexPlugin {
return emptyList(); return emptyList();
} }
} }
@Override
public void eventOccurred(Event e) {
if (e instanceof SettingsUpdatedEvent) {
SettingsUpdatedEvent s = (SettingsUpdatedEvent) e;
if (s.getNamespace().equals(getId().getString()))
ioExecutor.execute(() -> onSettingsUpdated(s.getSettings()));
}
}
@IoExecutor
private void onSettingsUpdated(Settings settings) {
boolean enabledByUser = settings.getBoolean(PREF_PLUGIN_ENABLE, false);
ServerSocket ss = state.setEnabledByUser(enabledByUser);
State s = getState();
if (ss != null) {
LOG.info("Disabled by user, closing server socket");
tryToClose(ss, LOG, WARNING);
} else if (s == INACTIVE) {
LOG.info("Enabled by user, opening server socket");
bind();
}
}
@ThreadSafe
@NotNullByDefault
protected class PluginState {
@GuardedBy("this")
private boolean started = false, stopped = false, enabledByUser = false;
@GuardedBy("this")
@Nullable
private ServerSocket serverSocket = null;
synchronized void setStarted(boolean enabledByUser) {
started = true;
this.enabledByUser = enabledByUser;
callback.pluginStateChanged(getState());
}
@Nullable
synchronized ServerSocket setStopped() {
stopped = true;
ServerSocket ss = serverSocket;
serverSocket = null;
callback.pluginStateChanged(getState());
return ss;
}
@Nullable
synchronized ServerSocket setEnabledByUser(boolean enabledByUser) {
this.enabledByUser = enabledByUser;
ServerSocket ss = null;
if (!enabledByUser) {
ss = serverSocket;
serverSocket = null;
}
callback.pluginStateChanged(getState());
return ss;
}
@Nullable
synchronized ServerSocket getServerSocket() {
return serverSocket;
}
synchronized boolean setServerSocket(ServerSocket ss) {
if (stopped || serverSocket != null) return false;
serverSocket = ss;
callback.pluginStateChanged(getState());
return true;
}
synchronized void clearServerSocket(ServerSocket ss) {
if (serverSocket == ss) serverSocket = null;
callback.pluginStateChanged(getState());
}
synchronized State getState() {
if (!started || stopped) return STARTING_STOPPING;
if (!enabledByUser) return DISABLED;
return serverSocket == null ? INACTIVE : ACTIVE;
}
synchronized int getReasonsDisabled() {
return getState() == DISABLED ? REASON_USER : 0;
}
}
} }

View File

@@ -1,10 +1,13 @@
package org.briarproject.bramble.plugin.tcp; package org.briarproject.bramble.plugin.tcp;
import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.keyagreement.KeyAgreementListener;
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.Backoff; import org.briarproject.bramble.api.plugin.Backoff;
import org.briarproject.bramble.api.plugin.PluginCallback; import org.briarproject.bramble.api.plugin.PluginCallback;
import org.briarproject.bramble.api.plugin.TransportId; import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
import org.briarproject.bramble.api.properties.TransportProperties; import org.briarproject.bramble.api.properties.TransportProperties;
import java.net.Inet4Address; import java.net.Inet4Address;
@@ -42,6 +45,22 @@ class WanTcpPlugin extends TcpPlugin {
return ID; return ID;
} }
@Override
public boolean supportsKeyAgreement() {
return false;
}
@Override
public KeyAgreementListener createKeyAgreementListener(byte[] commitment) {
throw new UnsupportedOperationException();
}
@Override
public DuplexTransportConnection createKeyAgreementConnection(
byte[] commitment, BdfList descriptor) {
throw new UnsupportedOperationException();
}
@Override @Override
protected List<InetSocketAddress> getLocalSocketAddresses() { protected List<InetSocketAddress> getLocalSocketAddresses() {
// Use the same address and port as last time if available // Use the same address and port as last time if available

View File

@@ -1,5 +1,6 @@
package org.briarproject.bramble.plugin.tcp; package org.briarproject.bramble.plugin.tcp;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.lifecycle.ShutdownManager; import org.briarproject.bramble.api.lifecycle.ShutdownManager;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.Backoff; import org.briarproject.bramble.api.plugin.Backoff;
@@ -27,12 +28,14 @@ public class WanTcpPluginFactory implements DuplexPluginFactory {
private static final double BACKOFF_BASE = 1.2; private static final double BACKOFF_BASE = 1.2;
private final Executor ioExecutor; private final Executor ioExecutor;
private final EventBus eventBus;
private final BackoffFactory backoffFactory; private final BackoffFactory backoffFactory;
private final ShutdownManager shutdownManager; private final ShutdownManager shutdownManager;
public WanTcpPluginFactory(Executor ioExecutor, public WanTcpPluginFactory(Executor ioExecutor, EventBus eventBus,
BackoffFactory backoffFactory, ShutdownManager shutdownManager) { BackoffFactory backoffFactory, ShutdownManager shutdownManager) {
this.ioExecutor = ioExecutor; this.ioExecutor = ioExecutor;
this.eventBus = eventBus;
this.backoffFactory = backoffFactory; this.backoffFactory = backoffFactory;
this.shutdownManager = shutdownManager; this.shutdownManager = shutdownManager;
} }
@@ -51,8 +54,10 @@ public class WanTcpPluginFactory implements DuplexPluginFactory {
public DuplexPlugin createPlugin(PluginCallback callback) { public DuplexPlugin createPlugin(PluginCallback callback) {
Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL, Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL,
MAX_POLLING_INTERVAL, BACKOFF_BASE); MAX_POLLING_INTERVAL, BACKOFF_BASE);
return new WanTcpPlugin(ioExecutor, backoff, WanTcpPlugin plugin = new WanTcpPlugin(ioExecutor, backoff,
new PortMapperImpl(shutdownManager), callback, MAX_LATENCY, new PortMapperImpl(shutdownManager), callback, MAX_LATENCY,
MAX_IDLE_TIME, CONNECTION_TIMEOUT); MAX_IDLE_TIME, CONNECTION_TIMEOUT);
eventBus.addListener(plugin);
return plugin;
} }
} }

View File

@@ -17,7 +17,7 @@ public interface CircumventionProvider {
String[] BLOCKED = {"CN", "IR", "EG", "BY", "TR", "SY", "VE"}; String[] BLOCKED = {"CN", "IR", "EG", "BY", "TR", "SY", "VE"};
/** /**
* Countries where obfs4 bridge connection are likely to work. * Countries where obfs4 or meek bridge connections are likely to work.
* Should be a subset of {@link #BLOCKED}. * Should be a subset of {@link #BLOCKED}.
*/ */
String[] BRIDGES = { "CN", "IR", "EG", "BY", "TR", "SY", "VE" }; String[] BRIDGES = { "CN", "IR", "EG", "BY", "TR", "SY", "VE" };

View File

@@ -15,9 +15,11 @@ import org.briarproject.bramble.api.network.NetworkManager;
import org.briarproject.bramble.api.network.NetworkStatus; import org.briarproject.bramble.api.network.NetworkStatus;
import org.briarproject.bramble.api.network.event.NetworkStatusEvent; import org.briarproject.bramble.api.network.event.NetworkStatusEvent;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.plugin.Backoff; import org.briarproject.bramble.api.plugin.Backoff;
import org.briarproject.bramble.api.plugin.ConnectionHandler; import org.briarproject.bramble.api.plugin.ConnectionHandler;
import org.briarproject.bramble.api.plugin.DiscoveryHandler;
import org.briarproject.bramble.api.plugin.PluginCallback; import org.briarproject.bramble.api.plugin.PluginCallback;
import org.briarproject.bramble.api.plugin.PluginException; import org.briarproject.bramble.api.plugin.PluginException;
import org.briarproject.bramble.api.plugin.TorConstants; import org.briarproject.bramble.api.plugin.TorConstants;
@@ -54,6 +56,9 @@ import java.util.logging.Logger;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import java.util.zip.ZipInputStream; import java.util.zip.ZipInputStream;
import javax.annotation.Nullable;
import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.ThreadSafe;
import javax.net.SocketFactory; import javax.net.SocketFactory;
import static java.util.Arrays.asList; import static java.util.Arrays.asList;
@@ -65,6 +70,11 @@ import static java.util.logging.Logger.getLogger;
import static net.freehaven.tor.control.TorControlCommands.HS_ADDRESS; import static net.freehaven.tor.control.TorControlCommands.HS_ADDRESS;
import static net.freehaven.tor.control.TorControlCommands.HS_PRIVKEY; import static net.freehaven.tor.control.TorControlCommands.HS_PRIVKEY;
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull; import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE;
import static org.briarproject.bramble.api.plugin.Plugin.State.DISABLED;
import static org.briarproject.bramble.api.plugin.Plugin.State.ENABLING;
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.TorConstants.CONTROL_PORT; import static org.briarproject.bramble.api.plugin.TorConstants.CONTROL_PORT;
import static org.briarproject.bramble.api.plugin.TorConstants.ID; import static org.briarproject.bramble.api.plugin.TorConstants.ID;
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_MOBILE; import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_MOBILE;
@@ -76,6 +86,9 @@ import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_ONLY_WHE
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_PORT; import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_PORT;
import static org.briarproject.bramble.api.plugin.TorConstants.PROP_ONION_V2; import static org.briarproject.bramble.api.plugin.TorConstants.PROP_ONION_V2;
import static org.briarproject.bramble.api.plugin.TorConstants.PROP_ONION_V3; import static org.briarproject.bramble.api.plugin.TorConstants.PROP_ONION_V3;
import static org.briarproject.bramble.api.plugin.TorConstants.REASON_BATTERY;
import static org.briarproject.bramble.api.plugin.TorConstants.REASON_COUNTRY_BLOCKED;
import static org.briarproject.bramble.api.plugin.TorConstants.REASON_MOBILE_DATA;
import static org.briarproject.bramble.plugin.tor.TorRendezvousCrypto.SEED_BYTES; import static org.briarproject.bramble.plugin.tor.TorRendezvousCrypto.SEED_BYTES;
import static org.briarproject.bramble.util.IoUtils.copyAndClose; import static org.briarproject.bramble.util.IoUtils.copyAndClose;
import static org.briarproject.bramble.util.IoUtils.tryToClose; import static org.briarproject.bramble.util.IoUtils.tryToClose;
@@ -113,16 +126,14 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
private final int maxLatency, maxIdleTime, socketTimeout; private final int maxLatency, maxIdleTime, socketTimeout;
private final File torDirectory, torFile, geoIpFile, obfs4File, configFile; private final File torDirectory, torFile, geoIpFile, obfs4File, configFile;
private final File doneFile, cookieFile; private final File doneFile, cookieFile;
private final ConnectionStatus connectionStatus;
private final AtomicBoolean used = new AtomicBoolean(false); private final AtomicBoolean used = new AtomicBoolean(false);
private volatile ServerSocket socket = null; protected final PluginState state = new PluginState();
private volatile Socket controlSocket = null; private volatile Socket controlSocket = null;
private volatile TorControlConnection controlConnection = null; private volatile TorControlConnection controlConnection = null;
private volatile Settings settings = null; private volatile Settings settings = null;
protected volatile boolean running = false;
protected abstract int getProcessId(); protected abstract int getProcessId();
protected abstract long getLastUpdateTime(); protected abstract long getLastUpdateTime();
@@ -159,7 +170,6 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
configFile = new File(torDirectory, "torrc"); configFile = new File(torDirectory, "torrc");
doneFile = new File(torDirectory, "done"); doneFile = new File(torDirectory, "done");
cookieFile = new File(torDirectory, ".tor/control_auth_cookie"); cookieFile = new File(torDirectory, ".tor/control_auth_cookie");
connectionStatus = new ConnectionStatus();
// Don't execute more than one connection status check at a time // Don't execute more than one connection status check at a time
connectionStatusExecutor = connectionStatusExecutor =
new PoliteExecutor("TorPlugin", ioExecutor, 1); new PoliteExecutor("TorPlugin", ioExecutor, 1);
@@ -190,7 +200,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
} }
} }
// Load the settings // Load the settings
settings = callback.getSettings(); settings = migrateSettings(callback.getSettings());
// Install or update the assets if necessary // Install or update the assets if necessary
if (!assetsAreUpToDate()) installAssets(); if (!assetsAreUpToDate()) installAssets();
if (cookieFile.exists() && !cookieFile.delete()) if (cookieFile.exists() && !cookieFile.delete())
@@ -258,7 +268,6 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
// Tell Tor to exit when the control connection is closed // Tell Tor to exit when the control connection is closed
controlConnection.takeOwnership(); controlConnection.takeOwnership();
controlConnection.resetConf(singletonList(OWNER)); controlConnection.resetConf(singletonList(OWNER));
running = true;
// Register to receive events from the Tor process // Register to receive events from the Tor process
controlConnection.setEventHandler(this); controlConnection.setEventHandler(this);
controlConnection.setEvents(asList(EVENTS)); controlConnection.setEvents(asList(EVENTS));
@@ -266,11 +275,12 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
String phase = controlConnection.getInfo("status/bootstrap-phase"); String phase = controlConnection.getInfo("status/bootstrap-phase");
if (phase != null && phase.contains("PROGRESS=100")) { if (phase != null && phase.contains("PROGRESS=100")) {
LOG.info("Tor has already bootstrapped"); LOG.info("Tor has already bootstrapped");
connectionStatus.setBootstrapped(); state.setBootstrapped();
} }
} catch (IOException e) { } catch (IOException e) {
throw new PluginException(e); throw new PluginException(e);
} }
state.setStarted();
// Check whether we're online // Check whether we're online
updateConnectionStatus(networkManager.getNetworkStatus(), updateConnectionStatus(networkManager.getNetworkStatus(),
batteryManager.isCharging()); batteryManager.isCharging());
@@ -278,6 +288,18 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
bind(); bind();
} }
// TODO: Remove after a reasonable migration period (added 2020-01-16)
private Settings migrateSettings(Settings settings) {
int network = settings.getInt(PREF_TOR_NETWORK,
PREF_TOR_NETWORK_AUTOMATIC);
if (network == PREF_TOR_NETWORK_NEVER) {
settings.putInt(PREF_TOR_NETWORK, PREF_TOR_NETWORK_AUTOMATIC);
settings.putBoolean(PREF_PLUGIN_ENABLE, false);
callback.mergeSettings(settings);
}
return settings;
}
private boolean assetsAreUpToDate() { private boolean assetsAreUpToDate() {
return doneFile.lastModified() > getLastUpdateTime(); return doneFile.lastModified() > getLastUpdateTime();
} }
@@ -393,11 +415,11 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
tryToClose(ss, LOG, WARNING); tryToClose(ss, LOG, WARNING);
return; return;
} }
if (!running) { if (!state.setServerSocket(ss)) {
LOG.info("Closing redundant server socket");
tryToClose(ss, LOG, WARNING); tryToClose(ss, LOG, WARNING);
return; return;
} }
socket = ss;
// Store the port number // Store the port number
String localPort = String.valueOf(ss.getLocalPort()); String localPort = String.valueOf(ss.getLocalPort());
Settings s = new Settings(); Settings s = new Settings();
@@ -412,7 +434,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
} }
private void publishHiddenService(String port) { private void publishHiddenService(String port) {
if (!running) return; if (!state.isTorRunning()) return;
LOG.info("Creating hidden service"); LOG.info("Creating hidden service");
String privKey = settings.get(HS_PRIVKEY); String privKey = settings.get(HS_PRIVKEY);
Map<Integer, String> portLines = singletonMap(80, "127.0.0.1:" + port); Map<Integer, String> portLines = singletonMap(80, "127.0.0.1:" + port);
@@ -450,14 +472,15 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
} }
private void acceptContactConnections(ServerSocket ss) { private void acceptContactConnections(ServerSocket ss) {
while (running) { while (true) {
Socket s; Socket s;
try { try {
s = ss.accept(); s = ss.accept();
s.setSoTimeout(socketTimeout); s.setSoTimeout(socketTimeout);
} catch (IOException e) { } catch (IOException e) {
// This is expected when the socket is closed // This is expected when the server socket is closed
if (LOG.isLoggable(INFO)) LOG.info(e.toString()); LOG.info("Server socket closed");
state.clearServerSocket(ss);
return; return;
} }
LOG.info("Connection received"); LOG.info("Connection received");
@@ -467,10 +490,8 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
} }
protected void enableNetwork(boolean enable) throws IOException { protected void enableNetwork(boolean enable) throws IOException {
if (!running) return; state.enableNetwork(enable);
connectionStatus.enableNetwork(enable);
controlConnection.setConf("DisableNetwork", enable ? "0" : "1"); controlConnection.setConf("DisableNetwork", enable ? "0" : "1");
if (!enable) callback.transportDisabled();
} }
private void enableBridges(boolean enable, boolean needsMeek) private void enableBridges(boolean enable, boolean needsMeek)
@@ -494,9 +515,8 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
@Override @Override
public void stop() { public void stop() {
running = false; ServerSocket ss = state.setStopped();
tryToClose(socket, LOG, WARNING); tryToClose(ss, LOG, WARNING);
callback.transportDisabled();
if (controlSocket != null && controlConnection != null) { if (controlSocket != null && controlConnection != null) {
try { try {
LOG.info("Stopping Tor"); LOG.info("Stopping Tor");
@@ -510,8 +530,13 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
} }
@Override @Override
public boolean isRunning() { public State getState() {
return running && connectionStatus.isConnected(); return state.getState();
}
@Override
public int getReasonsDisabled() {
return state.getReasonsDisabled();
} }
@Override @Override
@@ -525,9 +550,9 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
} }
@Override @Override
public void poll(Collection<Pair<TransportProperties, ConnectionHandler>> public void poll(
properties) { List<Pair<TransportProperties, ConnectionHandler>> properties) {
if (!isRunning()) return; if (getState() != ACTIVE) return;
backoff.increment(); backoff.increment();
for (Pair<TransportProperties, ConnectionHandler> p : properties) { for (Pair<TransportProperties, ConnectionHandler> p : properties) {
connect(p.getFirst(), p.getSecond()); connect(p.getFirst(), p.getSecond());
@@ -546,7 +571,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
@Override @Override
public DuplexTransportConnection createConnection(TransportProperties p) { public DuplexTransportConnection createConnection(TransportProperties p) {
if (!isRunning()) return null; if (getState() != ACTIVE) return null;
String bestOnion = null; String bestOnion = null;
String onion2 = p.get(PROP_ONION_V2); String onion2 = p.get(PROP_ONION_V2);
String onion3 = p.get(PROP_ONION_V3); String onion3 = p.get(PROP_ONION_V3);
@@ -634,8 +659,8 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
new TorTransportConnection(this, s)); new TorTransportConnection(this, s));
} }
} catch (IOException e) { } catch (IOException e) {
// This is expected when the socket is closed // This is expected when the server socket is closed
if (LOG.isLoggable(INFO)) LOG.info(e.toString()); LOG.info("Rendezvous server socket closed");
} }
}); });
Map<Integer, String> portLines = Map<Integer, String> portLines =
@@ -660,13 +685,23 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
} }
} }
@Override
public boolean supportsDiscovery() {
return false;
}
@Override
public void discoverPeers(
List<Pair<TransportProperties, DiscoveryHandler>> properties) {
throw new UnsupportedOperationException();
}
@Override @Override
public void circuitStatus(String status, String id, String path) { public void circuitStatus(String status, String id, String path) {
if (status.equals("BUILT") && if (status.equals("BUILT") &&
connectionStatus.getAndSetCircuitBuilt()) { state.getAndSetCircuitBuilt()) {
LOG.info("First circuit built"); LOG.info("First circuit built");
backoff.reset(); backoff.reset();
if (isRunning()) callback.transportEnabled();
} }
} }
@@ -697,9 +732,8 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
public void message(String severity, String msg) { public void message(String severity, String msg) {
if (LOG.isLoggable(INFO)) LOG.info(severity + " " + msg); if (LOG.isLoggable(INFO)) LOG.info(severity + " " + msg);
if (severity.equals("NOTICE") && msg.startsWith("Bootstrapped 100%")) { if (severity.equals("NOTICE") && msg.startsWith("Bootstrapped 100%")) {
connectionStatus.setBootstrapped(); state.setBootstrapped();
backoff.reset(); backoff.reset();
if (isRunning()) callback.transportEnabled();
} }
} }
@@ -736,7 +770,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
private void disableNetwork() { private void disableNetwork() {
connectionStatusExecutor.execute(() -> { connectionStatusExecutor.execute(() -> {
try { try {
enableNetwork(false); if (state.isTorRunning()) enableNetwork(false);
} catch (IOException ex) { } catch (IOException ex) {
logException(LOG, WARNING, ex); logException(LOG, WARNING, ex);
} }
@@ -746,12 +780,14 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
private void updateConnectionStatus(NetworkStatus status, private void updateConnectionStatus(NetworkStatus status,
boolean charging) { boolean charging) {
connectionStatusExecutor.execute(() -> { connectionStatusExecutor.execute(() -> {
if (!running) return; if (!state.isTorRunning()) return;
boolean online = status.isConnected(); boolean online = status.isConnected();
boolean wifi = status.isWifi(); boolean wifi = status.isWifi();
String country = locationUtils.getCurrentCountry(); String country = locationUtils.getCurrentCountry();
boolean blocked = boolean blocked =
circumventionProvider.isTorProbablyBlocked(country); circumventionProvider.isTorProbablyBlocked(country);
boolean enabledByUser =
settings.getBoolean(PREF_PLUGIN_ENABLE, true);
int network = settings.getInt(PREF_TOR_NETWORK, int network = settings.getInt(PREF_TOR_NETWORK,
PREF_TOR_NETWORK_AUTOMATIC); PREF_TOR_NETWORK_AUTOMATIC);
boolean useMobile = settings.getBoolean(PREF_TOR_MOBILE, true); boolean useMobile = settings.getBoolean(PREF_TOR_MOBILE, true);
@@ -762,47 +798,70 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
if (LOG.isLoggable(INFO)) { if (LOG.isLoggable(INFO)) {
LOG.info("Online: " + online + ", wifi: " + wifi); LOG.info("Online: " + online + ", wifi: " + wifi);
if ("".equals(country)) LOG.info("Country code unknown"); if (country.isEmpty()) LOG.info("Country code unknown");
else LOG.info("Country code: " + country); else LOG.info("Country code: " + country);
LOG.info("Charging: " + charging); LOG.info("Charging: " + charging);
} }
try { int reasonsDisabled = 0;
boolean enableNetwork = false, enableBridges = false;
boolean useMeek = false, enableConnectionPadding = false;
if (!online) { if (!online) {
LOG.info("Disabling network, device is offline"); LOG.info("Disabling network, device is offline");
enableNetwork(false); } else {
} else if (!charging && onlyWhenCharging) { if (!enabledByUser) {
LOG.info("Disabling network, device is on battery"); LOG.info("User has disabled Tor");
enableNetwork(false); reasonsDisabled |= REASON_USER;
} else if (network == PREF_TOR_NETWORK_NEVER || }
(!useMobile && !wifi)) { if (!charging && onlyWhenCharging) {
LOG.info("Disabling network, device is using mobile data"); LOG.info("Configured not to use battery");
enableNetwork(false); reasonsDisabled |= REASON_BATTERY;
} else if (automatic && blocked && !bridgesWork) { }
LOG.info("Disabling network, country is blocked"); if (!useMobile && !wifi) {
enableNetwork(false); LOG.info("Configured not to use mobile data");
} else if (network == PREF_TOR_NETWORK_WITH_BRIDGES || reasonsDisabled |= REASON_MOBILE_DATA;
}
if (automatic && blocked && !bridgesWork) {
LOG.info("Country is blocked");
reasonsDisabled |= REASON_COUNTRY_BLOCKED;
}
if (reasonsDisabled != 0) {
LOG.info("Disabling network due to settings");
} else {
LOG.info("Enabling network");
enableNetwork = true;
if (network == PREF_TOR_NETWORK_WITH_BRIDGES ||
(automatic && bridgesWork)) { (automatic && bridgesWork)) {
if (circumventionProvider.needsMeek(country)) { if (circumventionProvider.needsMeek(country)) {
LOG.info("Enabling network, using meek bridges"); LOG.info("Using meek bridges");
enableBridges(true, true); enableBridges = true;
useMeek = true;
} else { } else {
LOG.info("Enabling network, using obfs4 bridges"); LOG.info("Using obfs4 bridges");
enableBridges(true, false); enableBridges = true;
} }
enableNetwork(true);
} else { } else {
LOG.info("Enabling network, not using bridges"); LOG.info("Not using bridges");
enableBridges(false, false);
enableNetwork(true);
} }
if (online && wifi && charging) { if (wifi && charging) {
LOG.info("Enabling connection padding"); LOG.info("Enabling connection padding");
enableConnectionPadding(true); enableConnectionPadding = true;
} else { } else {
LOG.info("Disabling connection padding"); LOG.info("Disabling connection padding");
enableConnectionPadding(false);
} }
}
}
state.setReasonsDisabled(reasonsDisabled);
try {
if (enableNetwork) {
enableBridges(enableBridges, useMeek);
enableConnectionPadding(enableConnectionPadding);
}
enableNetwork(enableNetwork);
} catch (IOException e) { } catch (IOException e) {
logException(LOG, WARNING, e); logException(LOG, WARNING, e);
} }
@@ -810,33 +869,96 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
} }
private void enableConnectionPadding(boolean enable) throws IOException { private void enableConnectionPadding(boolean enable) throws IOException {
if (!running) return;
controlConnection.setConf("ConnectionPadding", enable ? "1" : "0"); controlConnection.setConf("ConnectionPadding", enable ? "1" : "0");
} }
private static class ConnectionStatus { @ThreadSafe
@NotNullByDefault
protected class PluginState {
// All of the following are locking: this @GuardedBy("this")
private boolean networkEnabled = false; private boolean started = false,
private boolean bootstrapped = false, circuitBuilt = false; stopped = false,
networkInitialised = false,
networkEnabled = false,
bootstrapped = false,
circuitBuilt = false,
settingsChecked = false;
private synchronized void setBootstrapped() { @GuardedBy("this")
bootstrapped = true; private int reasonsDisabled = 0;
@GuardedBy("this")
@Nullable
private ServerSocket serverSocket = null;
synchronized void setStarted() {
started = true;
callback.pluginStateChanged(getState());
} }
private synchronized boolean getAndSetCircuitBuilt() { synchronized boolean isTorRunning() {
return started && !stopped;
}
@Nullable
synchronized ServerSocket setStopped() {
stopped = true;
ServerSocket ss = serverSocket;
serverSocket = null;
callback.pluginStateChanged(getState());
return ss;
}
synchronized void setBootstrapped() {
bootstrapped = true;
callback.pluginStateChanged(getState());
}
synchronized boolean getAndSetCircuitBuilt() {
boolean firstCircuit = !circuitBuilt; boolean firstCircuit = !circuitBuilt;
circuitBuilt = true; circuitBuilt = true;
callback.pluginStateChanged(getState());
return firstCircuit; return firstCircuit;
} }
private synchronized void enableNetwork(boolean enable) { synchronized void enableNetwork(boolean enable) {
networkInitialised = true;
networkEnabled = enable; networkEnabled = enable;
if (!enable) circuitBuilt = false; if (!enable) circuitBuilt = false;
callback.pluginStateChanged(getState());
} }
private synchronized boolean isConnected() { synchronized void setReasonsDisabled(int reasonsDisabled) {
return networkEnabled && bootstrapped && circuitBuilt; settingsChecked = true;
this.reasonsDisabled = reasonsDisabled;
callback.pluginStateChanged(getState());
}
// Doesn't affect getState()
synchronized boolean setServerSocket(ServerSocket ss) {
if (stopped || serverSocket != null) return false;
serverSocket = ss;
return true;
}
// Doesn't affect getState()
synchronized void clearServerSocket(ServerSocket ss) {
if (serverSocket == ss) serverSocket = null;
}
synchronized State getState() {
if (!started || stopped || !settingsChecked) {
return STARTING_STOPPING;
}
if (reasonsDisabled != 0) return DISABLED;
if (!networkInitialised) return ENABLING;
if (!networkEnabled) return INACTIVE;
return bootstrapped && circuitBuilt ? ACTIVE : ENABLING;
}
synchronized int getReasonsDisabled() {
return getState() == DISABLED ? reasonsDisabled : 0;
} }
} }
} }

View File

@@ -31,8 +31,8 @@ import org.briarproject.bramble.api.plugin.TransportConnectionWriter;
import org.briarproject.bramble.api.plugin.TransportId; import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin; import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection; import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
import org.briarproject.bramble.api.plugin.event.TransportDisabledEvent; import org.briarproject.bramble.api.plugin.event.TransportActiveEvent;
import org.briarproject.bramble.api.plugin.event.TransportEnabledEvent; import org.briarproject.bramble.api.plugin.event.TransportInactiveEvent;
import org.briarproject.bramble.api.properties.TransportProperties; import org.briarproject.bramble.api.properties.TransportProperties;
import org.briarproject.bramble.api.rendezvous.KeyMaterialSource; import org.briarproject.bramble.api.rendezvous.KeyMaterialSource;
import org.briarproject.bramble.api.rendezvous.RendezvousEndpoint; import org.briarproject.bramble.api.rendezvous.RendezvousEndpoint;
@@ -269,11 +269,11 @@ class RendezvousPollerImpl implements RendezvousPoller, Service, EventListener {
} else if (e instanceof PendingContactRemovedEvent) { } else if (e instanceof PendingContactRemovedEvent) {
PendingContactRemovedEvent p = (PendingContactRemovedEvent) e; PendingContactRemovedEvent p = (PendingContactRemovedEvent) e;
removePendingContactAsync(p.getId()); removePendingContactAsync(p.getId());
} else if (e instanceof TransportEnabledEvent) { } else if (e instanceof TransportActiveEvent) {
TransportEnabledEvent t = (TransportEnabledEvent) e; TransportActiveEvent t = (TransportActiveEvent) e;
addTransportAsync(t.getTransportId()); addTransportAsync(t.getTransportId());
} else if (e instanceof TransportDisabledEvent) { } else if (e instanceof TransportInactiveEvent) {
TransportDisabledEvent t = (TransportDisabledEvent) e; TransportInactiveEvent t = (TransportInactiveEvent) e;
removeTransportAsync(t.getTransportId()); removeTransportAsync(t.getTransportId());
} else if (e instanceof RendezvousConnectionOpenedEvent) { } else if (e instanceof RendezvousConnectionOpenedEvent) {
RendezvousConnectionOpenedEvent r = RendezvousConnectionOpenedEvent r =

View File

@@ -6,7 +6,7 @@ import org.briarproject.bramble.api.event.EventListener;
import org.briarproject.bramble.api.lifecycle.IoExecutor; import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TorConstants; import org.briarproject.bramble.api.plugin.TorConstants;
import org.briarproject.bramble.api.plugin.event.TransportEnabledEvent; import org.briarproject.bramble.api.plugin.event.TransportActiveEvent;
import org.briarproject.bramble.api.reporting.DevConfig; import org.briarproject.bramble.api.reporting.DevConfig;
import org.briarproject.bramble.api.reporting.DevReporter; import org.briarproject.bramble.api.reporting.DevReporter;
import org.briarproject.bramble.util.IoUtils; import org.briarproject.bramble.util.IoUtils;
@@ -92,8 +92,8 @@ class DevReporterImpl implements DevReporter, EventListener {
@Override @Override
public void eventOccurred(Event e) { public void eventOccurred(Event e) {
if (e instanceof TransportEnabledEvent) { if (e instanceof TransportActiveEvent) {
TransportEnabledEvent t = (TransportEnabledEvent) e; TransportActiveEvent t = (TransportActiveEvent) e;
if (t.getTransportId().equals(TorConstants.ID)) if (t.getTransportId().equals(TorConstants.ID))
ioExecutor.execute(this::sendReports); ioExecutor.execute(this::sendReports);
} }

View File

@@ -13,8 +13,8 @@ import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection; import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
import org.briarproject.bramble.api.plugin.event.ConnectionClosedEvent; import org.briarproject.bramble.api.plugin.event.ConnectionClosedEvent;
import org.briarproject.bramble.api.plugin.event.ConnectionOpenedEvent; import org.briarproject.bramble.api.plugin.event.ConnectionOpenedEvent;
import org.briarproject.bramble.api.plugin.event.TransportDisabledEvent; import org.briarproject.bramble.api.plugin.event.TransportActiveEvent;
import org.briarproject.bramble.api.plugin.event.TransportEnabledEvent; import org.briarproject.bramble.api.plugin.event.TransportInactiveEvent;
import org.briarproject.bramble.api.plugin.simplex.SimplexPlugin; import org.briarproject.bramble.api.plugin.simplex.SimplexPlugin;
import org.briarproject.bramble.api.properties.TransportProperties; import org.briarproject.bramble.api.properties.TransportProperties;
import org.briarproject.bramble.api.properties.TransportPropertyManager; import org.briarproject.bramble.api.properties.TransportPropertyManager;
@@ -38,7 +38,7 @@ import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList; import static java.util.Collections.singletonList;
import static java.util.Collections.singletonMap; import static java.util.Collections.singletonMap;
import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static org.briarproject.bramble.test.CollectionMatcher.collectionOf; import static org.briarproject.bramble.test.ListMatcher.listOf;
import static org.briarproject.bramble.test.PairMatcher.pairOf; import static org.briarproject.bramble.test.PairMatcher.pairOf;
import static org.briarproject.bramble.test.TestUtils.getContactId; import static org.briarproject.bramble.test.TestUtils.getContactId;
import static org.briarproject.bramble.test.TestUtils.getTransportId; import static org.briarproject.bramble.test.TestUtils.getTransportId;
@@ -322,7 +322,7 @@ public class PollerImplTest extends BrambleMockTestCase {
} }
@Test @Test
public void testPollsOnTransportEnabled() throws Exception { public void testPollsOnTransportActivated() throws Exception {
DuplexPlugin plugin = context.mock(DuplexPlugin.class); DuplexPlugin plugin = context.mock(DuplexPlugin.class);
context.checking(new Expectations() {{ context.checking(new Expectations() {{
@@ -351,17 +351,20 @@ public class PollerImplTest extends BrambleMockTestCase {
oneOf(scheduler).schedule(with(any(Runnable.class)), oneOf(scheduler).schedule(with(any(Runnable.class)),
with((long) (pollingInterval * 0.5)), with(MILLISECONDS)); with((long) (pollingInterval * 0.5)), with(MILLISECONDS));
will(returnValue(future)); will(returnValue(future));
// FIXME: Revert
oneOf(plugin).supportsDiscovery();
will(returnValue(false));
// Get the transport properties and connected contacts // Get the transport properties and connected contacts
oneOf(transportPropertyManager).getRemoteProperties(transportId); oneOf(transportPropertyManager).getRemoteProperties(transportId);
will(returnValue(singletonMap(contactId, properties))); will(returnValue(singletonMap(contactId, properties)));
oneOf(connectionRegistry).getConnectedContacts(transportId); oneOf(connectionRegistry).getConnectedContacts(transportId);
will(returnValue(emptyList())); will(returnValue(emptyList()));
// Poll the plugin // Poll the plugin
oneOf(plugin).poll(with(collectionOf( oneOf(plugin).poll(with(listOf(pairOf(
pairOf(equal(properties), any(ConnectionHandler.class))))); equal(properties), any(ConnectionHandler.class)))));
}}); }});
poller.eventOccurred(new TransportEnabledEvent(transportId)); poller.eventOccurred(new TransportActiveEvent(transportId));
} }
@Test @Test
@@ -394,6 +397,9 @@ public class PollerImplTest extends BrambleMockTestCase {
oneOf(scheduler).schedule(with(any(Runnable.class)), oneOf(scheduler).schedule(with(any(Runnable.class)),
with((long) (pollingInterval * 0.5)), with(MILLISECONDS)); with((long) (pollingInterval * 0.5)), with(MILLISECONDS));
will(returnValue(future)); will(returnValue(future));
// FIXME: Revert
oneOf(plugin).supportsDiscovery();
will(returnValue(false));
// Get the transport properties and connected contacts // Get the transport properties and connected contacts
oneOf(transportPropertyManager).getRemoteProperties(transportId); oneOf(transportPropertyManager).getRemoteProperties(transportId);
will(returnValue(singletonMap(contactId, properties))); will(returnValue(singletonMap(contactId, properties)));
@@ -402,11 +408,11 @@ public class PollerImplTest extends BrambleMockTestCase {
// All contacts are connected, so don't poll the plugin // All contacts are connected, so don't poll the plugin
}}); }});
poller.eventOccurred(new TransportEnabledEvent(transportId)); poller.eventOccurred(new TransportActiveEvent(transportId));
} }
@Test @Test
public void testCancelsPollingOnTransportDisabled() { public void testCancelsPollingOnTransportDeactivated() {
Plugin plugin = context.mock(Plugin.class); Plugin plugin = context.mock(Plugin.class);
context.checking(new Expectations() {{ context.checking(new Expectations() {{
@@ -424,11 +430,11 @@ public class PollerImplTest extends BrambleMockTestCase {
oneOf(scheduler).schedule(with(any(Runnable.class)), with(0L), oneOf(scheduler).schedule(with(any(Runnable.class)), with(0L),
with(MILLISECONDS)); with(MILLISECONDS));
will(returnValue(future)); will(returnValue(future));
// The plugin is disabled before the task runs - cancel the task // The plugin is deactivated before the task runs - cancel the task
oneOf(future).cancel(false); oneOf(future).cancel(false);
}}); }});
poller.eventOccurred(new TransportEnabledEvent(transportId)); poller.eventOccurred(new TransportActiveEvent(transportId));
poller.eventOccurred(new TransportDisabledEvent(transportId)); poller.eventOccurred(new TransportInactiveEvent(transportId));
} }
} }

View File

@@ -4,6 +4,7 @@ import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.keyagreement.KeyAgreementListener; import org.briarproject.bramble.api.keyagreement.KeyAgreementListener;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.Backoff; import org.briarproject.bramble.api.plugin.Backoff;
import org.briarproject.bramble.api.plugin.Plugin.State;
import org.briarproject.bramble.api.plugin.PluginCallback; import org.briarproject.bramble.api.plugin.PluginCallback;
import org.briarproject.bramble.api.plugin.TransportConnectionReader; import org.briarproject.bramble.api.plugin.TransportConnectionReader;
import org.briarproject.bramble.api.plugin.TransportConnectionWriter; import org.briarproject.bramble.api.plugin.TransportConnectionWriter;
@@ -31,6 +32,7 @@ import static java.util.concurrent.Executors.newCachedThreadPool;
import static java.util.concurrent.TimeUnit.SECONDS; import static java.util.concurrent.TimeUnit.SECONDS;
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.COMMIT_LENGTH; import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.COMMIT_LENGTH;
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.TRANSPORT_ID_LAN; import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.TRANSPORT_ID_LAN;
import static org.briarproject.bramble.api.plugin.Plugin.PREF_PLUGIN_ENABLE;
import static org.briarproject.bramble.plugin.tcp.LanTcpPlugin.areAddressesInSameNetwork; import static org.briarproject.bramble.plugin.tcp.LanTcpPlugin.areAddressesInSameNetwork;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
@@ -302,10 +304,15 @@ public class LanTcpPluginTest extends BrambleTestCase {
private final CountDownLatch propertiesLatch = new CountDownLatch(2); private final CountDownLatch propertiesLatch = new CountDownLatch(2);
private final CountDownLatch connectionsLatch = new CountDownLatch(1); private final CountDownLatch connectionsLatch = new CountDownLatch(1);
private final TransportProperties local = new TransportProperties(); private final TransportProperties local = new TransportProperties();
private final Settings settings = new Settings();
private Callback() {
settings.putBoolean(PREF_PLUGIN_ENABLE, true);
}
@Override @Override
public Settings getSettings() { public Settings getSettings() {
return new Settings(); return settings;
} }
@Override @Override
@@ -324,11 +331,7 @@ public class LanTcpPluginTest extends BrambleTestCase {
} }
@Override @Override
public void transportEnabled() { public void pluginStateChanged(State newState) {
}
@Override
public void transportDisabled() {
} }
@Override @Override

View File

@@ -17,8 +17,8 @@ import org.briarproject.bramble.api.plugin.ConnectionManager;
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;
import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin; import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
import org.briarproject.bramble.api.plugin.event.TransportDisabledEvent; import org.briarproject.bramble.api.plugin.event.TransportActiveEvent;
import org.briarproject.bramble.api.plugin.event.TransportEnabledEvent; import org.briarproject.bramble.api.plugin.event.TransportInactiveEvent;
import org.briarproject.bramble.api.properties.TransportProperties; import org.briarproject.bramble.api.properties.TransportProperties;
import org.briarproject.bramble.api.rendezvous.KeyMaterialSource; import org.briarproject.bramble.api.rendezvous.KeyMaterialSource;
import org.briarproject.bramble.api.rendezvous.RendezvousEndpoint; import org.briarproject.bramble.api.rendezvous.RendezvousEndpoint;
@@ -49,7 +49,7 @@ import static org.briarproject.bramble.api.contact.PendingContactState.OFFLINE;
import static org.briarproject.bramble.api.contact.PendingContactState.WAITING_FOR_CONNECTION; import static org.briarproject.bramble.api.contact.PendingContactState.WAITING_FOR_CONNECTION;
import static org.briarproject.bramble.rendezvous.RendezvousConstants.POLLING_INTERVAL_MS; import static org.briarproject.bramble.rendezvous.RendezvousConstants.POLLING_INTERVAL_MS;
import static org.briarproject.bramble.rendezvous.RendezvousConstants.RENDEZVOUS_TIMEOUT_MS; import static org.briarproject.bramble.rendezvous.RendezvousConstants.RENDEZVOUS_TIMEOUT_MS;
import static org.briarproject.bramble.test.CollectionMatcher.collectionOf; import static org.briarproject.bramble.test.ListMatcher.listOf;
import static org.briarproject.bramble.test.PairMatcher.pairOf; import static org.briarproject.bramble.test.PairMatcher.pairOf;
import static org.briarproject.bramble.test.TestUtils.getAgreementPrivateKey; import static org.briarproject.bramble.test.TestUtils.getAgreementPrivateKey;
import static org.briarproject.bramble.test.TestUtils.getAgreementPublicKey; import static org.briarproject.bramble.test.TestUtils.getAgreementPublicKey;
@@ -178,10 +178,10 @@ public class RendezvousPollerImplTest extends BrambleMockTestCase {
rendezvousPoller.startService(); rendezvousPoller.startService();
context.assertIsSatisfied(); context.assertIsSatisfied();
// Enable the transport - no endpoints should be created yet // Activate the transport - no endpoints should be created yet
expectGetPlugin(); expectGetPlugin();
rendezvousPoller.eventOccurred(new TransportEnabledEvent(transportId)); rendezvousPoller.eventOccurred(new TransportActiveEvent(transportId));
context.assertIsSatisfied(); context.assertIsSatisfied();
// Add the pending contact - endpoint should be created and polled // Add the pending contact - endpoint should be created and polled
@@ -196,7 +196,7 @@ public class RendezvousPollerImplTest extends BrambleMockTestCase {
oneOf(clock).currentTimeMillis(); oneOf(clock).currentTimeMillis();
will(returnValue(beforeExpiry)); will(returnValue(beforeExpiry));
oneOf(eventBus).broadcast(with(any(RendezvousPollEvent.class))); oneOf(eventBus).broadcast(with(any(RendezvousPollEvent.class)));
oneOf(plugin).poll(with(collectionOf(pairOf( oneOf(plugin).poll(with(listOf(pairOf(
equal(transportProperties), equal(transportProperties),
any(ConnectionHandler.class))))); any(ConnectionHandler.class)))));
}}); }});
@@ -212,8 +212,8 @@ public class RendezvousPollerImplTest extends BrambleMockTestCase {
new PendingContactRemovedEvent(pendingContact.getId())); new PendingContactRemovedEvent(pendingContact.getId()));
context.assertIsSatisfied(); context.assertIsSatisfied();
// Disable the transport - endpoint is already closed // Deactivate the transport - endpoint is already closed
rendezvousPoller.eventOccurred(new TransportDisabledEvent(transportId)); rendezvousPoller.eventOccurred(new TransportInactiveEvent(transportId));
} }
@Test @Test
@@ -230,10 +230,10 @@ public class RendezvousPollerImplTest extends BrambleMockTestCase {
rendezvousPoller.startService(); rendezvousPoller.startService();
context.assertIsSatisfied(); context.assertIsSatisfied();
// Enable the transport - no endpoints should be created yet // Activate the transport - no endpoints should be created yet
expectGetPlugin(); expectGetPlugin();
rendezvousPoller.eventOccurred(new TransportEnabledEvent(transportId)); rendezvousPoller.eventOccurred(new TransportActiveEvent(transportId));
context.assertIsSatisfied(); context.assertIsSatisfied();
// Add the pending contact - endpoint should be created and polled // Add the pending contact - endpoint should be created and polled
@@ -248,7 +248,7 @@ public class RendezvousPollerImplTest extends BrambleMockTestCase {
oneOf(clock).currentTimeMillis(); oneOf(clock).currentTimeMillis();
will(returnValue(beforeExpiry)); will(returnValue(beforeExpiry));
oneOf(eventBus).broadcast(with(any(RendezvousPollEvent.class))); oneOf(eventBus).broadcast(with(any(RendezvousPollEvent.class)));
oneOf(plugin).poll(with(collectionOf(pairOf( oneOf(plugin).poll(with(listOf(pairOf(
equal(transportProperties), equal(transportProperties),
any(ConnectionHandler.class))))); any(ConnectionHandler.class)))));
}}); }});
@@ -269,12 +269,12 @@ public class RendezvousPollerImplTest extends BrambleMockTestCase {
new PendingContactRemovedEvent(pendingContact.getId())); new PendingContactRemovedEvent(pendingContact.getId()));
context.assertIsSatisfied(); context.assertIsSatisfied();
// Disable the transport - endpoint is already closed // Deactivate the transport - endpoint is already closed
rendezvousPoller.eventOccurred(new TransportDisabledEvent(transportId)); rendezvousPoller.eventOccurred(new TransportInactiveEvent(transportId));
} }
@Test @Test
public void testCreatesAndClosesEndpointsWhenTransportIsEnabledAndDisabled() public void testCreatesAndClosesEndpointsWhenTransportIsActivatedAndDeactivated()
throws Exception { throws Exception {
long beforeExpiry = pendingContact.getTimestamp(); long beforeExpiry = pendingContact.getTimestamp();
@@ -292,19 +292,19 @@ public class RendezvousPollerImplTest extends BrambleMockTestCase {
new PendingContactAddedEvent(pendingContact)); new PendingContactAddedEvent(pendingContact));
context.assertIsSatisfied(); context.assertIsSatisfied();
// Enable the transport - endpoint should be created // Activate the transport - endpoint should be created
expectGetPlugin(); expectGetPlugin();
expectCreateEndpoint(); expectCreateEndpoint();
expectStateChangedEvent(WAITING_FOR_CONNECTION); expectStateChangedEvent(WAITING_FOR_CONNECTION);
rendezvousPoller.eventOccurred(new TransportEnabledEvent(transportId)); rendezvousPoller.eventOccurred(new TransportActiveEvent(transportId));
context.assertIsSatisfied(); context.assertIsSatisfied();
// Disable the transport - endpoint should be closed // Deactivate the transport - endpoint should be closed
expectCloseEndpoint(); expectCloseEndpoint();
expectStateChangedEvent(OFFLINE); expectStateChangedEvent(OFFLINE);
rendezvousPoller.eventOccurred(new TransportDisabledEvent(transportId)); rendezvousPoller.eventOccurred(new TransportInactiveEvent(transportId));
context.assertIsSatisfied(); context.assertIsSatisfied();
// Remove the pending contact - endpoint is already closed // Remove the pending contact - endpoint is already closed

View File

@@ -5,24 +5,24 @@ import org.hamcrest.BaseMatcher;
import org.hamcrest.Description; import org.hamcrest.Description;
import org.hamcrest.Matcher; import org.hamcrest.Matcher;
import java.util.Collection; import java.util.List;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@NotNullByDefault @NotNullByDefault
public class CollectionMatcher<T> extends BaseMatcher<Collection<T>> { public class ListMatcher<T> extends BaseMatcher<List<T>> {
private final Matcher<T> elementMatcher; private final Matcher<T> elementMatcher;
public CollectionMatcher(Matcher<T> elementMatcher) { public ListMatcher(Matcher<T> elementMatcher) {
this.elementMatcher = elementMatcher; this.elementMatcher = elementMatcher;
} }
@Override @Override
public boolean matches(@Nullable Object item) { public boolean matches(@Nullable Object item) {
if (!(item instanceof Collection)) return false; if (!(item instanceof List)) return false;
Collection collection = (Collection) item; List list = (List) item;
for (Object element : collection) { for (Object element : list) {
if (!elementMatcher.matches(element)) return false; if (!elementMatcher.matches(element)) return false;
} }
return true; return true;
@@ -33,7 +33,7 @@ public class CollectionMatcher<T> extends BaseMatcher<Collection<T>> {
description.appendText("matches a collection"); description.appendText("matches a collection");
} }
public static <T> CollectionMatcher<T> collectionOf(Matcher<T> t) { public static <T> ListMatcher<T> listOf(Matcher<T> t) {
return new CollectionMatcher<>(t); return new ListMatcher<>(t);
} }
} }

View File

@@ -37,9 +37,9 @@ public class DesktopPluginModule extends PluginModule {
backoffFactory); backoffFactory);
DuplexPluginFactory modem = new ModemPluginFactory(ioExecutor, DuplexPluginFactory modem = new ModemPluginFactory(ioExecutor,
reliabilityFactory); reliabilityFactory);
DuplexPluginFactory lan = new LanTcpPluginFactory(ioExecutor, DuplexPluginFactory lan = new LanTcpPluginFactory(ioExecutor, eventBus,
backoffFactory); backoffFactory);
DuplexPluginFactory wan = new WanTcpPluginFactory(ioExecutor, DuplexPluginFactory wan = new WanTcpPluginFactory(ioExecutor, eventBus,
backoffFactory, shutdownManager); backoffFactory, shutdownManager);
Collection<DuplexPluginFactory> duplex = Collection<DuplexPluginFactory> duplex =
asList(bluetooth, modem, lan, wan); asList(bluetooth, modem, lan, wan);

View File

@@ -4,8 +4,10 @@ import org.briarproject.bramble.api.Pair;
import org.briarproject.bramble.api.data.BdfList; import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.keyagreement.KeyAgreementListener; import org.briarproject.bramble.api.keyagreement.KeyAgreementListener;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.plugin.ConnectionHandler; import org.briarproject.bramble.api.plugin.ConnectionHandler;
import org.briarproject.bramble.api.plugin.DiscoveryHandler;
import org.briarproject.bramble.api.plugin.PluginCallback; import org.briarproject.bramble.api.plugin.PluginCallback;
import org.briarproject.bramble.api.plugin.PluginException; import org.briarproject.bramble.api.plugin.PluginException;
import org.briarproject.bramble.api.plugin.TransportId; import org.briarproject.bramble.api.plugin.TransportId;
@@ -19,13 +21,20 @@ import org.briarproject.bramble.api.rendezvous.RendezvousEndpoint;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.util.Collection; import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Logger; import java.util.logging.Logger;
import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.ThreadSafe;
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 java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE;
import static org.briarproject.bramble.api.plugin.Plugin.State.ENABLING;
import static org.briarproject.bramble.api.plugin.Plugin.State.INACTIVE;
import static org.briarproject.bramble.api.plugin.Plugin.State.STARTING_STOPPING;
import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty; import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
@@ -44,8 +53,8 @@ class ModemPlugin implements DuplexPlugin, Modem.Callback {
private final PluginCallback callback; private final PluginCallback callback;
private final int maxLatency; private final int maxLatency;
private final AtomicBoolean used = new AtomicBoolean(false); private final AtomicBoolean used = new AtomicBoolean(false);
private final PluginState state = new PluginState();
private volatile boolean running = false;
private volatile Modem modem = null; private volatile Modem modem = null;
ModemPlugin(ModemFactory modemFactory, SerialPortList serialPortList, ModemPlugin(ModemFactory modemFactory, SerialPortList serialPortList,
@@ -75,6 +84,7 @@ class ModemPlugin implements DuplexPlugin, Modem.Callback {
@Override @Override
public void start() throws PluginException { public void start() throws PluginException {
if (used.getAndSet(true)) throw new IllegalStateException(); if (used.getAndSet(true)) throw new IllegalStateException();
state.setStarted();
for (String portName : serialPortList.getPortNames()) { for (String portName : serialPortList.getPortNames()) {
if (LOG.isLoggable(INFO)) if (LOG.isLoggable(INFO))
LOG.info("Trying to initialise modem on " + portName); LOG.info("Trying to initialise modem on " + portName);
@@ -83,18 +93,20 @@ class ModemPlugin implements DuplexPlugin, Modem.Callback {
if (!modem.start()) continue; if (!modem.start()) continue;
if (LOG.isLoggable(INFO)) if (LOG.isLoggable(INFO))
LOG.info("Initialised modem on " + portName); LOG.info("Initialised modem on " + portName);
running = true; state.setInitialised();
return; return;
} catch (IOException e) { } catch (IOException e) {
logException(LOG, WARNING, e); logException(LOG, WARNING, e);
} }
} }
LOG.warning("Failed to initialised modem");
state.setFailed();
throw new PluginException(); throw new PluginException();
} }
@Override @Override
public void stop() { public void stop() {
running = false; state.setStopped();
if (modem != null) { if (modem != null) {
try { try {
modem.stop(); modem.stop();
@@ -105,8 +117,13 @@ class ModemPlugin implements DuplexPlugin, Modem.Callback {
} }
@Override @Override
public boolean isRunning() { public State getState() {
return running; return state.getState();
}
@Override
public int getReasonsDisabled() {
return 0;
} }
@Override @Override
@@ -120,13 +137,13 @@ class ModemPlugin implements DuplexPlugin, Modem.Callback {
} }
@Override @Override
public void poll(Collection<Pair<TransportProperties, ConnectionHandler>> public void poll(
properties) { List<Pair<TransportProperties, ConnectionHandler>> properties) {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
private boolean resetModem() { private void resetModem() {
if (!running) return false; if (getState() != ACTIVE) return;
for (String portName : serialPortList.getPortNames()) { for (String portName : serialPortList.getPortNames()) {
if (LOG.isLoggable(INFO)) if (LOG.isLoggable(INFO))
LOG.info("Trying to initialise modem on " + portName); LOG.info("Trying to initialise modem on " + portName);
@@ -135,18 +152,18 @@ class ModemPlugin implements DuplexPlugin, Modem.Callback {
if (!modem.start()) continue; if (!modem.start()) continue;
if (LOG.isLoggable(INFO)) if (LOG.isLoggable(INFO))
LOG.info("Initialised modem on " + portName); LOG.info("Initialised modem on " + portName);
return true; return;
} catch (IOException e) { } catch (IOException e) {
logException(LOG, WARNING, e); logException(LOG, WARNING, e);
} }
} }
running = false; LOG.warning("Failed to initialise modem");
return false; state.setFailed();
} }
@Override @Override
public DuplexTransportConnection createConnection(TransportProperties p) { public DuplexTransportConnection createConnection(TransportProperties p) {
if (!running) return null; if (getState() != ACTIVE) return null;
// Get the ISO 3166 code for the caller's country // Get the ISO 3166 code for the caller's country
String fromIso = callback.getLocalProperties().get("iso3166"); String fromIso = callback.getLocalProperties().get("iso3166");
if (isNullOrEmpty(fromIso)) return null; if (isNullOrEmpty(fromIso)) return null;
@@ -197,6 +214,17 @@ class ModemPlugin implements DuplexPlugin, Modem.Callback {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
@Override
public boolean supportsDiscovery() {
return false;
}
@Override
public void discoverPeers(
List<Pair<TransportProperties, DiscoveryHandler>> properties) {
throw new UnsupportedOperationException();
}
@Override @Override
public void incomingCallConnected() { public void incomingCallConnected() {
LOG.info("Incoming call connected"); LOG.info("Incoming call connected");
@@ -232,4 +260,41 @@ class ModemPlugin implements DuplexPlugin, Modem.Callback {
if (exception) resetModem(); if (exception) resetModem();
} }
} }
@ThreadSafe
@NotNullByDefault
private class PluginState {
@GuardedBy("this")
private boolean started = false,
stopped = false,
initialised = false,
failed = false;
private synchronized void setStarted() {
started = true;
callback.pluginStateChanged(getState());
}
private synchronized void setStopped() {
stopped = true;
callback.pluginStateChanged(getState());
}
private synchronized void setInitialised() {
initialised = true;
callback.pluginStateChanged(getState());
}
private synchronized void setFailed() {
failed = true;
callback.pluginStateChanged(getState());
}
private State getState() {
if (!started || stopped) return STARTING_STOPPING;
if (failed) return INACTIVE;
return initialised ? ACTIVE : ENABLING;
}
}
} }

View File

@@ -9,6 +9,8 @@ import org.junit.Test;
import java.io.IOException; import java.io.IOException;
import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE;
import static org.briarproject.bramble.api.plugin.Plugin.State.ENABLING;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull; import static org.junit.Assert.assertNull;
@@ -33,6 +35,7 @@ public class ModemPluginTest extends BrambleMockTestCase {
@Test @Test
public void testModemCreation() throws Exception { public void testModemCreation() throws Exception {
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(callback).pluginStateChanged(ENABLING);
oneOf(serialPortList).getPortNames(); oneOf(serialPortList).getPortNames();
will(returnValue(new String[] {"foo", "bar", "baz"})); will(returnValue(new String[] {"foo", "bar", "baz"}));
// First call to createModem() returns false // First call to createModem() returns false
@@ -50,6 +53,7 @@ public class ModemPluginTest extends BrambleMockTestCase {
will(returnValue(modem)); will(returnValue(modem));
oneOf(modem).start(); oneOf(modem).start();
will(returnValue(true)); will(returnValue(true));
oneOf(callback).pluginStateChanged(ACTIVE);
}}); }});
plugin.start(); plugin.start();
@@ -65,12 +69,14 @@ public class ModemPluginTest extends BrambleMockTestCase {
context.checking(new Expectations() {{ context.checking(new Expectations() {{
// start() // start()
oneOf(callback).pluginStateChanged(ENABLING);
oneOf(serialPortList).getPortNames(); oneOf(serialPortList).getPortNames();
will(returnValue(new String[] {"foo"})); will(returnValue(new String[] {"foo"}));
oneOf(modemFactory).createModem(plugin, "foo"); oneOf(modemFactory).createModem(plugin, "foo");
will(returnValue(modem)); will(returnValue(modem));
oneOf(modem).start(); oneOf(modem).start();
will(returnValue(true)); will(returnValue(true));
oneOf(callback).pluginStateChanged(ACTIVE);
// createConnection() // createConnection()
oneOf(callback).getLocalProperties(); oneOf(callback).getLocalProperties();
will(returnValue(local)); will(returnValue(local));
@@ -93,12 +99,14 @@ public class ModemPluginTest extends BrambleMockTestCase {
context.checking(new Expectations() {{ context.checking(new Expectations() {{
// start() // start()
oneOf(callback).pluginStateChanged(ENABLING);
oneOf(serialPortList).getPortNames(); oneOf(serialPortList).getPortNames();
will(returnValue(new String[] {"foo"})); will(returnValue(new String[] {"foo"}));
oneOf(modemFactory).createModem(plugin, "foo"); oneOf(modemFactory).createModem(plugin, "foo");
will(returnValue(modem)); will(returnValue(modem));
oneOf(modem).start(); oneOf(modem).start();
will(returnValue(true)); will(returnValue(true));
oneOf(callback).pluginStateChanged(ACTIVE);
// createConnection() // createConnection()
oneOf(callback).getLocalProperties(); oneOf(callback).getLocalProperties();
will(returnValue(local)); will(returnValue(local));
@@ -121,12 +129,14 @@ public class ModemPluginTest extends BrambleMockTestCase {
context.checking(new Expectations() {{ context.checking(new Expectations() {{
// start() // start()
oneOf(callback).pluginStateChanged(ENABLING);
oneOf(serialPortList).getPortNames(); oneOf(serialPortList).getPortNames();
will(returnValue(new String[] {"foo"})); will(returnValue(new String[] {"foo"}));
oneOf(modemFactory).createModem(plugin, "foo"); oneOf(modemFactory).createModem(plugin, "foo");
will(returnValue(modem)); will(returnValue(modem));
oneOf(modem).start(); oneOf(modem).start();
will(returnValue(true)); will(returnValue(true));
oneOf(callback).pluginStateChanged(ACTIVE);
// createConnection() // createConnection()
oneOf(callback).getLocalProperties(); oneOf(callback).getLocalProperties();
will(returnValue(local)); will(returnValue(local));

View File

@@ -32,6 +32,7 @@ import javax.net.SocketFactory;
import static java.util.Collections.singletonList; import static java.util.Collections.singletonList;
import static java.util.concurrent.TimeUnit.SECONDS; import static java.util.concurrent.TimeUnit.SECONDS;
import static java.util.logging.Logger.getLogger; import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE;
import static org.briarproject.bramble.test.TestUtils.deleteTestDirectory; import static org.briarproject.bramble.test.TestUtils.deleteTestDirectory;
import static org.briarproject.bramble.test.TestUtils.getTestDirectory; import static org.briarproject.bramble.test.TestUtils.getTestDirectory;
import static org.briarproject.bramble.test.TestUtils.isOptionalTestEnabled; import static org.briarproject.bramble.test.TestUtils.isOptionalTestEnabled;
@@ -141,10 +142,10 @@ public class BridgeTest extends BrambleTestCase {
plugin.start(); plugin.start();
long start = clock.currentTimeMillis(); long start = clock.currentTimeMillis();
while (clock.currentTimeMillis() - start < TIMEOUT) { while (clock.currentTimeMillis() - start < TIMEOUT) {
if (plugin.isRunning()) return; if (plugin.getState() == ACTIVE) return;
clock.sleep(500); clock.sleep(500);
} }
if (!plugin.isRunning()) { if (plugin.getState() != ACTIVE) {
fail("Could not connect to Tor within timeout."); fail("Could not connect to Tor within timeout.");
} }
} finally { } finally {

View File

@@ -1,6 +1,7 @@
package org.briarproject.bramble.plugin.tor; package org.briarproject.bramble.plugin.tor;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.Plugin.State;
import org.briarproject.bramble.api.plugin.PluginCallback; import org.briarproject.bramble.api.plugin.PluginCallback;
import org.briarproject.bramble.api.plugin.TransportConnectionReader; import org.briarproject.bramble.api.plugin.TransportConnectionReader;
import org.briarproject.bramble.api.plugin.TransportConnectionWriter; import org.briarproject.bramble.api.plugin.TransportConnectionWriter;
@@ -30,11 +31,7 @@ public class TestPluginCallback implements PluginCallback {
} }
@Override @Override
public void transportEnabled() { public void pluginStateChanged(State state) {
}
@Override
public void transportDisabled() {
} }
@Override @Override

View File

@@ -36,6 +36,7 @@ import org.briarproject.bramble.util.AndroidUtils;
import org.briarproject.bramble.util.StringUtils; import org.briarproject.bramble.util.StringUtils;
import org.briarproject.briar.android.account.LockManagerImpl; import org.briarproject.briar.android.account.LockManagerImpl;
import org.briarproject.briar.android.keyagreement.ContactExchangeModule; import org.briarproject.briar.android.keyagreement.ContactExchangeModule;
import org.briarproject.briar.android.navdrawer.NavDrawerModule;
import org.briarproject.briar.android.viewmodel.ViewModelModule; import org.briarproject.briar.android.viewmodel.ViewModelModule;
import org.briarproject.briar.api.android.AndroidNotificationManager; import org.briarproject.briar.api.android.AndroidNotificationManager;
import org.briarproject.briar.api.android.DozeWatchdog; import org.briarproject.briar.api.android.DozeWatchdog;
@@ -64,7 +65,11 @@ import static org.briarproject.bramble.api.reporting.ReportingConstants.DEV_ONIO
import static org.briarproject.bramble.api.reporting.ReportingConstants.DEV_PUBLIC_KEY_HEX; import static org.briarproject.bramble.api.reporting.ReportingConstants.DEV_PUBLIC_KEY_HEX;
import static org.briarproject.briar.android.TestingConstants.IS_DEBUG_BUILD; import static org.briarproject.briar.android.TestingConstants.IS_DEBUG_BUILD;
@Module(includes = {ContactExchangeModule.class, ViewModelModule.class}) @Module(includes = {
NavDrawerModule.class,
ContactExchangeModule.class,
ViewModelModule.class
})
public class AppModule { public class AppModule {
static class EagerSingletons { static class EagerSingletons {

View File

@@ -10,8 +10,6 @@ import org.briarproject.briar.android.controller.DbController;
import org.briarproject.briar.android.controller.DbControllerImpl; import org.briarproject.briar.android.controller.DbControllerImpl;
import org.briarproject.briar.android.login.ChangePasswordController; import org.briarproject.briar.android.login.ChangePasswordController;
import org.briarproject.briar.android.login.ChangePasswordControllerImpl; import org.briarproject.briar.android.login.ChangePasswordControllerImpl;
import org.briarproject.briar.android.navdrawer.NavDrawerController;
import org.briarproject.briar.android.navdrawer.NavDrawerControllerImpl;
import dagger.Module; import dagger.Module;
import dagger.Provides; import dagger.Provides;
@@ -67,14 +65,6 @@ public class ActivityModule {
return dbController; return dbController;
} }
@ActivityScope
@Provides
NavDrawerController provideNavDrawerController(
NavDrawerControllerImpl navDrawerController) {
activity.addLifecycleController(navDrawerController);
return navDrawerController;
}
@ActivityScope @ActivityScope
@Provides @Provides
BriarServiceConnection provideBriarServiceConnection() { BriarServiceConnection provideBriarServiceConnection() {

View File

@@ -3,7 +3,6 @@ package org.briarproject.briar.android.contact.add.remote;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface.OnClickListener; import android.content.DialogInterface.OnClickListener;
import android.content.Intent; import android.content.Intent;
import android.graphics.drawable.Drawable;
import android.os.Bundle; import android.os.Bundle;
import android.text.Editable; import android.text.Editable;
import android.view.LayoutInflater; import android.view.LayoutInflater;
@@ -38,12 +37,10 @@ import androidx.lifecycle.ViewModelProviders;
import static android.view.View.INVISIBLE; import static android.view.View.INVISIBLE;
import static android.view.View.VISIBLE; import static android.view.View.VISIBLE;
import static android.widget.Toast.LENGTH_LONG; import static android.widget.Toast.LENGTH_LONG;
import static androidx.core.content.ContextCompat.getColor;
import static androidx.core.content.ContextCompat.getDrawable;
import static androidx.core.graphics.drawable.DrawableCompat.setTint;
import static java.util.Objects.requireNonNull; import static java.util.Objects.requireNonNull;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH; import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
import static org.briarproject.bramble.util.StringUtils.utf8IsTooLong; import static org.briarproject.bramble.util.StringUtils.utf8IsTooLong;
import static org.briarproject.briar.android.util.UiUtils.getDialogIcon;
@MethodsNotNullByDefault @MethodsNotNullByDefault
@ParametersNotNullByDefault @ParametersNotNullByDefault
@@ -199,9 +196,7 @@ public class NicknameFragment extends BaseFragment {
private void showWarningDialog(String name1, String name2) { private void showWarningDialog(String name1, String name2) {
Context ctx = requireContext(); Context ctx = requireContext();
Builder b = new Builder(ctx, R.style.BriarDialogTheme); Builder b = new Builder(ctx, R.style.BriarDialogTheme);
Drawable icon = getDrawable(ctx, R.drawable.alerts_and_states_error); b.setIcon(getDialogIcon(ctx, R.drawable.alerts_and_states_error));
setTint(requireNonNull(icon), getColor(ctx, R.color.color_primary));
b.setIcon(icon);
b.setTitle(getString(R.string.duplicate_link_dialog_title)); b.setTitle(getString(R.string.duplicate_link_dialog_title));
b.setMessage( b.setMessage(
getString(R.string.duplicate_link_dialog_text_3, name1, name2)); getString(R.string.duplicate_link_dialog_text_3, name1, name2));

View File

@@ -2,7 +2,6 @@ package org.briarproject.briar.android.conversation;
import android.content.DialogInterface.OnClickListener; import android.content.DialogInterface.OnClickListener;
import android.content.Intent; import android.content.Intent;
import android.graphics.drawable.Drawable;
import android.os.Bundle; import android.os.Bundle;
import android.transition.Fade; import android.transition.Fade;
import android.transition.Transition; import android.transition.Transition;
@@ -35,8 +34,6 @@ import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi; import androidx.annotation.RequiresApi;
import androidx.appcompat.app.AlertDialog.Builder; import androidx.appcompat.app.AlertDialog.Builder;
import androidx.appcompat.widget.Toolbar; import androidx.appcompat.widget.Toolbar;
import androidx.core.content.ContextCompat;
import androidx.core.graphics.drawable.DrawableCompat;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentStatePagerAdapter; import androidx.fragment.app.FragmentStatePagerAdapter;
@@ -59,6 +56,7 @@ import static com.google.android.material.snackbar.Snackbar.LENGTH_LONG;
import static java.util.Objects.requireNonNull; import static java.util.Objects.requireNonNull;
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_SAVE_ATTACHMENT; import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_SAVE_ATTACHMENT;
import static org.briarproject.briar.android.util.UiUtils.formatDateAbsolute; import static org.briarproject.briar.android.util.UiUtils.formatDateAbsolute;
import static org.briarproject.briar.android.util.UiUtils.getDialogIcon;
@MethodsNotNullByDefault @MethodsNotNullByDefault
@ParametersNotNullByDefault @ParametersNotNullByDefault
@@ -278,10 +276,7 @@ public class ImageActivity extends BriarActivity
Builder builder = new Builder(this, R.style.BriarDialogTheme); Builder builder = new Builder(this, R.style.BriarDialogTheme);
builder.setTitle(getString(R.string.dialog_title_save_image)); builder.setTitle(getString(R.string.dialog_title_save_image));
builder.setMessage(getString(R.string.dialog_message_save_image)); builder.setMessage(getString(R.string.dialog_message_save_image));
Drawable icon = ContextCompat.getDrawable(this, R.drawable.ic_security); builder.setIcon(getDialogIcon(this, R.drawable.ic_security));
DrawableCompat.setTint(requireNonNull(icon),
ContextCompat.getColor(this, R.color.color_primary));
builder.setIcon(icon);
builder.setPositiveButton(R.string.save_image, okListener); builder.setPositiveButton(R.string.save_image, okListener);
builder.setNegativeButton(R.string.cancel, null); builder.setNegativeButton(R.string.cancel, null);
builder.show(); builder.show();

View File

@@ -9,6 +9,7 @@ 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;
@@ -79,13 +80,13 @@ public class ContactExchangeActivity extends KeyAgreementActivity {
@UiThread @UiThread
private void contactExchangeFailed() { private void contactExchangeFailed() {
showErrorFragment(R.string.connection_error_explanation); showErrorFragment();
} }
@UiThread @UiThread
@Override @Override
public void keyAgreementFailed() { public void keyAgreementFailed() {
showErrorFragment(R.string.connection_error_explanation); showErrorFragment();
} }
@UiThread @UiThread
@@ -103,7 +104,7 @@ public class ContactExchangeActivity extends KeyAgreementActivity {
@UiThread @UiThread
@Override @Override
public void keyAgreementAborted(boolean remoteAborted) { public void keyAgreementAborted(boolean remoteAborted) {
showErrorFragment(R.string.connection_error_explanation); showErrorFragment();
} }
@UiThread @UiThread
@@ -112,4 +113,10 @@ public class ContactExchangeActivity extends KeyAgreementActivity {
startContactExchange(result); startContactExchange(result);
return getString(R.string.exchanging_contact_details); 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);
}
} }

View File

@@ -8,10 +8,18 @@ import android.content.IntentFilter;
import android.os.Bundle; import android.os.Bundle;
import android.view.MenuItem; import android.view.MenuItem;
import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.event.EventBus; import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.event.EventListener;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.plugin.BluetoothConstants;
import org.briarproject.bramble.api.plugin.LanTcpConstants;
import org.briarproject.bramble.api.plugin.Plugin;
import org.briarproject.bramble.api.plugin.Plugin.State;
import org.briarproject.bramble.api.plugin.PluginManager;
import org.briarproject.bramble.api.plugin.event.BluetoothEnabledEvent; import org.briarproject.bramble.api.plugin.event.BluetoothEnabledEvent;
import org.briarproject.bramble.api.plugin.event.TransportStateEvent;
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.activity.BriarActivity; import org.briarproject.briar.android.activity.BriarActivity;
@@ -37,13 +45,15 @@ 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_DISCOVERABLE; 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.ACTION_STATE_CHANGED;
import static android.bluetooth.BluetoothAdapter.EXTRA_SCAN_MODE;
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.SCAN_MODE_CONNECTABLE_DISCOVERABLE;
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 java.util.logging.Level.INFO;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE;
import static org.briarproject.bramble.api.plugin.Plugin.State.DISABLED;
import static org.briarproject.bramble.api.plugin.Plugin.State.INACTIVE;
import static org.briarproject.bramble.api.plugin.Plugin.State.STARTING_STOPPING;
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_BLUETOOTH_DISCOVERABLE; import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_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;
@@ -51,10 +61,33 @@ import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_PERMI
@ParametersNotNullByDefault @ParametersNotNullByDefault
public abstract class KeyAgreementActivity extends BriarActivity implements public abstract class KeyAgreementActivity extends BriarActivity implements
BaseFragmentListener, IntroScreenSeenListener, BaseFragmentListener, IntroScreenSeenListener,
KeyAgreementEventListener { KeyAgreementEventListener, EventListener {
private enum BluetoothState { private enum BluetoothDecision {
UNKNOWN, NO_ADAPTER, WAITING, REFUSED, ENABLED, DISCOVERABLE /**
* We haven't asked the user about Bluetooth discoverability.
*/
UNKNOWN,
/**
* The device doesn't have a Bluetooth adapter.
*/
NO_ADAPTER,
/**
* We're waiting for the user to accept or refuse discoverability.
*/
WAITING,
/**
* The user has accepted discoverability.
*/
ACCEPTED,
/**
* The user has refused discoverability.
*/
REFUSED
} }
private enum Permission { private enum Permission {
@@ -62,11 +95,14 @@ public abstract class KeyAgreementActivity extends BriarActivity implements
} }
private static final Logger LOG = private static final Logger LOG =
Logger.getLogger(KeyAgreementActivity.class.getName()); getLogger(KeyAgreementActivity.class.getName());
@Inject @Inject
EventBus eventBus; EventBus eventBus;
@Inject
PluginManager pluginManager;
/** /**
* Set to true in onPostResume() and false in onPause(). This prevents the * Set to true in onPostResume() and false in onPause(). This prevents the
* QR code fragment from being shown if onRequestPermissionsResult() is * QR code fragment from being shown if onRequestPermissionsResult() is
@@ -74,21 +110,36 @@ public abstract class KeyAgreementActivity extends BriarActivity implements
* https://issuetracker.google.com/issues/37067655. * https://issuetracker.google.com/issues/37067655.
*/ */
private boolean isResumed = false; private boolean isResumed = false;
/** /**
* Set to true when the continue button is clicked, and false when the QR * 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 * code fragment is shown. This prevents the QR code fragment from being
* shown automatically before the continue button has been clicked. * shown automatically before the continue button has been clicked.
*/ */
private boolean continueClicked = false; private boolean continueClicked = false;
/** /**
* Records whether the Bluetooth adapter was already enabled before we * Records whether the Bluetooth adapter was already enabled before we
* asked for Bluetooth discoverability, so we know whether to broadcast a * asked for Bluetooth discoverability, so we know whether to broadcast a
* {@link BluetoothEnabledEvent}. * {@link BluetoothEnabledEvent}.
*/ */
private boolean wasAdapterEnabled = false; private boolean wasAdapterEnabled = false;
/**
* Records whether we've enabled the wifi plugin so we don't enable it more
* than once.
*/
private boolean hasEnabledWifi = false;
/**
* Records whether we've enabled the Bluetooth plugin so we don't enable it
* more than once.
*/
private boolean hasEnabledBluetooth = false;
private Permission cameraPermission = Permission.UNKNOWN; private Permission cameraPermission = Permission.UNKNOWN;
private Permission locationPermission = Permission.UNKNOWN; private Permission locationPermission = Permission.UNKNOWN;
private BluetoothState bluetoothState = BluetoothState.UNKNOWN; private BluetoothDecision bluetoothDecision = BluetoothDecision.UNKNOWN;
private BroadcastReceiver bluetoothReceiver = null; private BroadcastReceiver bluetoothReceiver = null;
@Override @Override
@@ -96,20 +147,17 @@ public abstract class KeyAgreementActivity extends BriarActivity implements
component.inject(this); component.inject(this);
} }
@SuppressWarnings("ConstantConditions")
@Override @Override
public void onCreate(@Nullable Bundle state) { public void onCreate(@Nullable Bundle state) {
super.onCreate(state); super.onCreate(state);
setContentView(R.layout.activity_fragment_container_toolbar); setContentView(R.layout.activity_fragment_container_toolbar);
Toolbar toolbar = findViewById(R.id.toolbar); Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar); setSupportActionBar(toolbar);
getSupportActionBar().setDisplayHomeAsUpEnabled(true); requireNonNull(getSupportActionBar()).setDisplayHomeAsUpEnabled(true);
if (state == null) { if (state == null) {
showInitialFragment(IntroFragment.newInstance()); showInitialFragment(IntroFragment.newInstance());
} }
IntentFilter filter = new IntentFilter(); IntentFilter filter = new IntentFilter(ACTION_SCAN_MODE_CHANGED);
filter.addAction(ACTION_STATE_CHANGED);
filter.addAction(ACTION_SCAN_MODE_CHANGED);
bluetoothReceiver = new BluetoothStateReceiver(); bluetoothReceiver = new BluetoothStateReceiver();
registerReceiver(bluetoothReceiver, filter); registerReceiver(bluetoothReceiver, filter);
} }
@@ -122,18 +170,17 @@ public abstract class KeyAgreementActivity extends BriarActivity implements
@Override @Override
public boolean onOptionsItemSelected(MenuItem item) { public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) { if (item.getItemId() == android.R.id.home) {
case android.R.id.home:
onBackPressed(); onBackPressed();
return true; return true;
default:
return super.onOptionsItemSelected(item);
} }
return super.onOptionsItemSelected(item);
} }
@Override @Override
public void onStart() { public void onStart() {
super.onStart(); super.onStart();
eventBus.addListener(this);
// Permissions may have been granted manually while we were stopped // Permissions may have been granted manually while we were stopped
cameraPermission = Permission.UNKNOWN; cameraPermission = Permission.UNKNOWN;
locationPermission = Permission.UNKNOWN; locationPermission = Permission.UNKNOWN;
@@ -150,11 +197,22 @@ public abstract class KeyAgreementActivity extends BriarActivity implements
private void showQrCodeFragmentIfAllowed() { private void showQrCodeFragmentIfAllowed() {
if (isResumed && continueClicked && areEssentialPermissionsGranted()) { if (isResumed && continueClicked && areEssentialPermissionsGranted()) {
if (bluetoothState == BluetoothState.UNKNOWN || if (isWifiReady() && isBluetoothReady()) {
bluetoothState == BluetoothState.ENABLED) { LOG.info("Wifi and Bluetooth are ready");
requestBluetoothDiscoverable();
} else if (bluetoothState != BluetoothState.WAITING) {
showQrCodeFragment(); showQrCodeFragment();
} else {
if (shouldEnableWifi()) {
LOG.info("Enabling wifi plugin");
hasEnabledWifi = true;
pluginManager.setPluginEnabled(LanTcpConstants.ID, true);
}
if (bluetoothDecision == BluetoothDecision.UNKNOWN) {
requestBluetoothDiscoverable();
} else if (shouldEnableBluetooth()) {
LOG.info("Enabling Bluetooth plugin");
hasEnabledBluetooth = true;
pluginManager.setPluginEnabled(BluetoothConstants.ID, true);
}
} }
} }
} }
@@ -167,57 +225,108 @@ public abstract class KeyAgreementActivity extends BriarActivity implements
locationPermission == Permission.PERMANENTLY_DENIED); locationPermission == Permission.PERMANENTLY_DENIED);
} }
private boolean isWifiReady() {
Plugin p = pluginManager.getPlugin(LanTcpConstants.ID);
if (p == null) return true; // Continue without wifi
State state = p.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) {
// Continue without Bluetooth
return true;
}
BluetoothAdapter bt = BluetoothAdapter.getDefaultAdapter();
if (bt == null) return true; // Continue without Bluetooth
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;
}
private boolean shouldEnableWifi() {
if (hasEnabledWifi) return false;
Plugin p = pluginManager.getPlugin(LanTcpConstants.ID);
if (p == null) return false;
State state = p.getState();
return state == STARTING_STOPPING || state == DISABLED;
}
private void requestBluetoothDiscoverable() {
BluetoothAdapter bt = BluetoothAdapter.getDefaultAdapter();
if (bt == null) {
bluetoothDecision = BluetoothDecision.NO_ADAPTER;
showQrCodeFragmentIfAllowed();
} else {
Intent i = new Intent(ACTION_REQUEST_DISCOVERABLE);
if (i.resolveActivity(getPackageManager()) != null) {
LOG.info("Asking for Bluetooth discoverability");
bluetoothDecision = BluetoothDecision.WAITING;
wasAdapterEnabled = bt.isEnabled();
startActivityForResult(i, REQUEST_BLUETOOTH_DISCOVERABLE);
} else {
bluetoothDecision = BluetoothDecision.NO_ADAPTER;
showQrCodeFragmentIfAllowed();
}
}
}
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();
return state == STARTING_STOPPING || state == DISABLED;
}
@Override @Override
protected void onPause() { protected void onPause() {
super.onPause(); super.onPause();
isResumed = false; isResumed = false;
} }
@Override
protected void onStop() {
super.onStop();
eventBus.removeListener(this);
}
@Override @Override
public void showNextScreen() { public void showNextScreen() {
continueClicked = true; continueClicked = true;
if (checkPermissions()) showQrCodeFragmentIfAllowed(); if (checkPermissions()) showQrCodeFragmentIfAllowed();
} }
private void requestBluetoothDiscoverable() { @Override
BluetoothAdapter bt = BluetoothAdapter.getDefaultAdapter(); public void onActivityResult(int request, int result,
if (bt == null) { @Nullable Intent data) {
setBluetoothState(BluetoothState.NO_ADAPTER); if (request == REQUEST_BLUETOOTH_DISCOVERABLE) {
if (result == RESULT_CANCELED) {
LOG.info("Bluetooth discoverability was refused");
bluetoothDecision = BluetoothDecision.REFUSED;
} else { } else {
Intent i = new Intent(ACTION_REQUEST_DISCOVERABLE); LOG.info("Bluetooth discoverability was accepted");
if (i.resolveActivity(getPackageManager()) != null) { bluetoothDecision = BluetoothDecision.ACCEPTED;
setBluetoothState(BluetoothState.WAITING); if (!wasAdapterEnabled) {
wasAdapterEnabled = bt.isEnabled(); LOG.info("Bluetooth adapter was enabled by us");
startActivityForResult(i, REQUEST_BLUETOOTH_DISCOVERABLE);
} else {
setBluetoothState(BluetoothState.NO_ADAPTER);
}
}
}
private void setBluetoothState(BluetoothState bluetoothState) {
LOG.info("Setting Bluetooth state to " + bluetoothState);
this.bluetoothState = bluetoothState;
if (!wasAdapterEnabled && bluetoothState == BluetoothState.ENABLED) {
eventBus.broadcast(new BluetoothEnabledEvent()); eventBus.broadcast(new BluetoothEnabledEvent());
wasAdapterEnabled = true; wasAdapterEnabled = true;
} }
}
showQrCodeFragmentIfAllowed(); showQrCodeFragmentIfAllowed();
}
@Override
public void onActivityResult(int request, int result, Intent data) {
if (request == REQUEST_BLUETOOTH_DISCOVERABLE) {
if (result == RESULT_CANCELED) {
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);
}
} else super.onActivityResult(request, result, data); } else super.onActivityResult(request, result, data);
} }
@@ -227,7 +336,12 @@ public abstract class KeyAgreementActivity extends BriarActivity implements
continueClicked = false; continueClicked = false;
// If we return to the intro fragment, ask for Bluetooth // If we return to the intro fragment, ask for Bluetooth
// discoverability again before showing the QR code fragment // discoverability again before showing the QR code fragment
bluetoothState = BluetoothState.UNKNOWN; bluetoothDecision = BluetoothDecision.UNKNOWN;
// If we return to the intro fragment, we may need to enable wifi and
// Bluetooth again
hasEnabledWifi = false;
hasEnabledBluetooth = false;
// FIXME #824 // FIXME #824
FragmentManager fm = getSupportFragmentManager(); FragmentManager fm = getSupportFragmentManager();
if (fm.findFragmentByTag(KeyAgreementFragment.TAG) == null) { if (fm.findFragmentByTag(KeyAgreementFragment.TAG) == null) {
@@ -239,12 +353,6 @@ public abstract class KeyAgreementActivity extends BriarActivity implements
} }
} }
protected void showErrorFragment(@StringRes int errorResId) {
String errorMsg = getString(errorResId);
BaseFragment f = ContactExchangeErrorFragment.newInstance(errorMsg);
showNextFragment(f);
}
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 the camera permission has been permanently denied, ask the
@@ -335,24 +443,30 @@ public abstract class KeyAgreementActivity extends BriarActivity implements
permission); permission);
} }
@Override
public void eventOccurred(Event e) {
if (e instanceof TransportStateEvent) {
TransportStateEvent t = (TransportStateEvent) e;
if (t.getTransportId().equals(BluetoothConstants.ID)) {
if (LOG.isLoggable(INFO)) {
LOG.info("Bluetooth state changed to " + t.getState());
}
showQrCodeFragmentIfAllowed();
} else if (t.getTransportId().equals(LanTcpConstants.ID)) {
if (LOG.isLoggable(INFO)) {
LOG.info("Wifi state changed to " + t.getState());
}
showQrCodeFragmentIfAllowed();
}
}
}
private class BluetoothStateReceiver extends BroadcastReceiver { private class BluetoothStateReceiver extends BroadcastReceiver {
@Override @Override
public void onReceive(Context context, Intent intent) { public void onReceive(Context context, Intent intent) {
String action = intent.getAction(); LOG.info("Bluetooth scan mode changed");
if (ACTION_STATE_CHANGED.equals(action)) { showQrCodeFragmentIfAllowed();
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);
}
} }
} }
} }

View File

@@ -1,16 +1,12 @@
package org.briarproject.briar.android.navdrawer; package org.briarproject.briar.android.navdrawer;
import android.annotation.SuppressLint;
import android.content.Intent; import android.content.Intent;
import android.content.res.Configuration; import android.content.res.Configuration;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.GridView;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.TextView; import android.widget.TextView;
@@ -21,16 +17,11 @@ import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.lifecycle.LifecycleManager; import org.briarproject.bramble.api.lifecycle.LifecycleManager;
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.BluetoothConstants;
import org.briarproject.bramble.api.plugin.LanTcpConstants;
import org.briarproject.bramble.api.plugin.TorConstants;
import org.briarproject.bramble.api.plugin.TransportId;
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.activity.BriarActivity; import org.briarproject.briar.android.activity.BriarActivity;
import org.briarproject.briar.android.blog.FeedFragment; import org.briarproject.briar.android.blog.FeedFragment;
import org.briarproject.briar.android.contact.ContactListFragment; import org.briarproject.briar.android.contact.ContactListFragment;
import org.briarproject.briar.android.controller.handler.UiResultHandler;
import org.briarproject.briar.android.forum.ForumListFragment; import org.briarproject.briar.android.forum.ForumListFragment;
import org.briarproject.briar.android.fragment.BaseFragment; import org.briarproject.briar.android.fragment.BaseFragment;
import org.briarproject.briar.android.fragment.BaseFragment.BaseFragmentListener; import org.briarproject.briar.android.fragment.BaseFragment.BaseFragmentListener;
@@ -38,23 +29,21 @@ import org.briarproject.briar.android.logout.SignOutFragment;
import org.briarproject.briar.android.privategroup.list.GroupListFragment; import org.briarproject.briar.android.privategroup.list.GroupListFragment;
import org.briarproject.briar.android.settings.SettingsActivity; import org.briarproject.briar.android.settings.SettingsActivity;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger; import java.util.logging.Logger;
import javax.inject.Inject; import javax.inject.Inject;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.ActionBarDrawerToggle; import androidx.appcompat.app.ActionBarDrawerToggle;
import androidx.appcompat.widget.Toolbar; import androidx.appcompat.widget.Toolbar;
import androidx.core.app.ActivityCompat; import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.drawerlayout.widget.DrawerLayout; import androidx.drawerlayout.widget.DrawerLayout;
import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction; import androidx.fragment.app.FragmentTransaction;
import androidx.lifecycle.ViewModelProvider;
import androidx.lifecycle.ViewModelProviders;
import static android.view.View.GONE; import static android.view.View.GONE;
import static android.view.View.VISIBLE; import static android.view.View.VISIBLE;
@@ -72,8 +61,7 @@ import static org.briarproject.briar.android.util.UiUtils.getDaysUntilExpiry;
@MethodsNotNullByDefault @MethodsNotNullByDefault
@ParametersNotNullByDefault @ParametersNotNullByDefault
public class NavDrawerActivity extends BriarActivity implements public class NavDrawerActivity extends BriarActivity implements
BaseFragmentListener, TransportStateListener, BaseFragmentListener, OnNavigationItemSelectedListener {
OnNavigationItemSelectedListener {
private static final Logger LOG = private static final Logger LOG =
getLogger(NavDrawerActivity.class.getName()); getLogger(NavDrawerActivity.class.getName());
@@ -91,19 +79,18 @@ public class NavDrawerActivity extends BriarActivity implements
public static Uri SIGN_OUT_URI = public static Uri SIGN_OUT_URI =
Uri.parse("briar-content://org.briarproject.briar/sign-out"); Uri.parse("briar-content://org.briarproject.briar/sign-out");
private NavDrawerViewModel viewModel;
private ActionBarDrawerToggle drawerToggle; private ActionBarDrawerToggle drawerToggle;
@Inject @Inject
NavDrawerController controller; ViewModelProvider.Factory viewModelFactory;
@Inject @Inject
LifecycleManager lifecycleManager; LifecycleManager lifecycleManager;
private DrawerLayout drawerLayout; private DrawerLayout drawerLayout;
private NavigationView navigation; private NavigationView navigation;
private List<Transport> transports;
private BaseAdapter transportsAdapter;
@Override @Override
public void injectActivity(ActivityComponent component) { public void injectActivity(ActivityComponent component) {
component.inject(this); component.inject(this);
@@ -115,10 +102,20 @@ public class NavDrawerActivity extends BriarActivity implements
exitIfStartupFailed(getIntent()); exitIfStartupFailed(getIntent());
setContentView(R.layout.activity_nav_drawer); setContentView(R.layout.activity_nav_drawer);
viewModel = ViewModelProviders.of(this, viewModelFactory)
.get(NavDrawerViewModel.class);
viewModel.showExpiryWarning().observe(this, this::showExpiryWarning);
viewModel.shouldAskForDozeWhitelisting().observe(this, ask -> {
if (ask) showDozeDialog(getString(R.string.setup_doze_intro));
});
View drawerScrollView = findViewById(R.id.drawerScrollView);
new PluginViewController(drawerScrollView, this, viewModel);
Toolbar toolbar = findViewById(R.id.toolbar); Toolbar toolbar = findViewById(R.id.toolbar);
drawerLayout = findViewById(R.id.drawer_layout); drawerLayout = findViewById(R.id.drawer_layout);
navigation = findViewById(R.id.navigation); navigation = findViewById(R.id.navigation);
GridView transportsView = findViewById(R.id.transportsView);
setSupportActionBar(toolbar); setSupportActionBar(toolbar);
ActionBar actionBar = requireNonNull(getSupportActionBar()); ActionBar actionBar = requireNonNull(getSupportActionBar());
@@ -131,9 +128,6 @@ public class NavDrawerActivity extends BriarActivity implements
drawerLayout.addDrawerListener(drawerToggle); drawerLayout.addDrawerListener(drawerToggle);
navigation.setNavigationItemSelectedListener(this); navigation.setNavigationItemSelectedListener(this);
initializeTransports(getLayoutInflater());
transportsView.setAdapter(transportsAdapter);
lockManager.isLockable().observe(this, this::setLockVisible); lockManager.isLockable().observe(this, this::setLockVisible);
if (lifecycleManager.getLifecycleState().isAfter(RUNNING)) { if (lifecycleManager.getLifecycleState().isAfter(RUNNING)) {
@@ -149,17 +143,10 @@ public class NavDrawerActivity extends BriarActivity implements
} }
@Override @Override
@SuppressLint("NewApi")
public void onStart() { public void onStart() {
super.onStart(); super.onStart();
updateTransports();
lockManager.checkIfLockable(); lockManager.checkIfLockable();
controller.showExpiryWarning(new UiResultHandler<Boolean>(this) { viewModel.checkExpiryWarning();
@Override
public void onResultUi(Boolean expiry) {
if (expiry) showExpiryWarning();
}
});
} }
@Override @Override
@@ -167,16 +154,7 @@ public class NavDrawerActivity extends BriarActivity implements
@Nullable Intent data) { @Nullable Intent data) {
super.onActivityResult(request, result, data); super.onActivityResult(request, result, data);
if (request == REQUEST_PASSWORD && result == RESULT_OK) { if (request == REQUEST_PASSWORD && result == RESULT_OK) {
controller.shouldAskForDozeWhitelisting(this, viewModel.checkDozeWhitelisting();
new UiResultHandler<Boolean>(this) {
@Override
public void onResultUi(Boolean ask) {
if (ask) {
showDozeDialog(
getString(R.string.setup_doze_intro));
}
}
});
} }
} }
@@ -346,134 +324,30 @@ public class NavDrawerActivity extends BriarActivity implements
if (item != null) item.setVisible(visible); if (item != null) item.setVisible(visible);
} }
private void showExpiryWarning() { private void showExpiryWarning(boolean show) {
int daysUntilExpiry = getDaysUntilExpiry(); int daysUntilExpiry = getDaysUntilExpiry();
if (daysUntilExpiry < 0) signOut(); if (daysUntilExpiry < 0) {
signOut();
return;
}
// show expiry warning text
ViewGroup expiryWarning = findViewById(R.id.expiryWarning); ViewGroup expiryWarning = findViewById(R.id.expiryWarning);
if (show) {
// show expiry warning text
TextView expiryWarningText = TextView expiryWarningText =
expiryWarning.findViewById(R.id.expiryWarningText); expiryWarning.findViewById(R.id.expiryWarningText);
String text = getResources().getQuantityString(
R.plurals.expiry_warning, daysUntilExpiry, daysUntilExpiry);
expiryWarningText.setText(text);
// make close button functional // make close button functional
ImageView expiryWarningClose = ImageView expiryWarningClose =
expiryWarning.findViewById(R.id.expiryWarningClose); expiryWarning.findViewById(R.id.expiryWarningClose);
expiryWarningClose.setOnClickListener(v ->
expiryWarningText.setText(getResources() viewModel.expiryWarningDismissed()
.getQuantityString(R.plurals.expiry_warning, );
daysUntilExpiry, daysUntilExpiry));
expiryWarningClose.setOnClickListener(v -> {
controller.expiryWarningDismissed();
expiryWarning.setVisibility(GONE);
});
expiryWarning.setVisibility(VISIBLE); expiryWarning.setVisibility(VISIBLE);
}
private void initializeTransports(LayoutInflater inflater) {
transports = new ArrayList<>(3);
Transport tor = new Transport();
tor.id = TorConstants.ID;
tor.enabled = controller.isTransportRunning(tor.id);
tor.iconId = R.drawable.transport_tor;
tor.textId = R.string.transport_tor;
transports.add(tor);
Transport bt = new Transport();
bt.id = BluetoothConstants.ID;
bt.enabled = controller.isTransportRunning(bt.id);
bt.iconId = R.drawable.transport_bt;
bt.textId = R.string.transport_bt;
transports.add(bt);
Transport lan = new Transport();
lan.id = LanTcpConstants.ID;
lan.enabled = controller.isTransportRunning(lan.id);
lan.iconId = R.drawable.transport_lan;
lan.textId = R.string.transport_lan;
transports.add(lan);
transportsAdapter = new BaseAdapter() {
@Override
public int getCount() {
return transports.size();
}
@Override
public Transport getItem(int position) {
return transports.get(position);
}
@Override
public long getItemId(int position) {
return 0;
}
@Override
public View getView(int position, View convertView,
ViewGroup parent) {
View view;
if (convertView != null) {
view = convertView;
} else { } else {
view = inflater.inflate(R.layout.list_item_transport, expiryWarning.setVisibility(GONE);
parent, false);
}
Transport t = getItem(position);
int c;
if (t.enabled) {
c = ContextCompat.getColor(NavDrawerActivity.this,
R.color.briar_green_light);
} else {
c = ContextCompat.getColor(NavDrawerActivity.this,
android.R.color.tertiary_text_light);
}
ImageView icon = view.findViewById(R.id.imageView);
icon.setImageDrawable(ContextCompat
.getDrawable(NavDrawerActivity.this, t.iconId));
icon.setColorFilter(c);
TextView text = view.findViewById(R.id.textView);
text.setText(getString(t.textId));
return view;
}
};
}
@UiThread
private void setTransport(TransportId id, boolean enabled) {
if (transports == null || transportsAdapter == null) return;
for (Transport t : transports) {
if (t.id.equals(id)) {
t.enabled = enabled;
transportsAdapter.notifyDataSetChanged();
break;
} }
} }
} }
private void updateTransports() {
if (transports == null || transportsAdapter == null) return;
for (Transport t : transports) {
t.enabled = controller.isTransportRunning(t.id);
}
transportsAdapter.notifyDataSetChanged();
}
@Override
public void stateUpdate(TransportId id, boolean enabled) {
setTransport(id, enabled);
}
private static class Transport {
private TransportId id;
private boolean enabled;
private int iconId;
private int textId;
}
}

View File

@@ -1,22 +0,0 @@
package org.briarproject.briar.android.navdrawer;
import android.content.Context;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.briar.android.controller.ActivityLifecycleController;
import org.briarproject.briar.android.controller.handler.ResultHandler;
@NotNullByDefault
public interface NavDrawerController extends ActivityLifecycleController {
boolean isTransportRunning(TransportId transportId);
void showExpiryWarning(ResultHandler<Boolean> handler);
void expiryWarningDismissed();
void shouldAskForDozeWhitelisting(Context ctx,
ResultHandler<Boolean> handler);
}

View File

@@ -1,182 +0,0 @@
package org.briarproject.briar.android.navdrawer;
import android.app.Activity;
import android.content.Context;
import org.briarproject.bramble.api.db.DatabaseExecutor;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.event.EventListener;
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.plugin.Plugin;
import org.briarproject.bramble.api.plugin.PluginManager;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.event.TransportDisabledEvent;
import org.briarproject.bramble.api.plugin.event.TransportEnabledEvent;
import org.briarproject.bramble.api.settings.Settings;
import org.briarproject.bramble.api.settings.SettingsManager;
import org.briarproject.briar.android.controller.DbControllerImpl;
import org.briarproject.briar.android.controller.handler.ResultHandler;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
import javax.inject.Inject;
import static java.util.concurrent.TimeUnit.DAYS;
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.util.LogUtils.logException;
import static org.briarproject.briar.android.TestingConstants.EXPIRY_DATE;
import static org.briarproject.briar.android.TestingConstants.IS_DEBUG_BUILD;
import static org.briarproject.briar.android.controller.BriarControllerImpl.DOZE_ASK_AGAIN;
import static org.briarproject.briar.android.settings.SettingsFragment.SETTINGS_NAMESPACE;
import static org.briarproject.briar.android.util.UiUtils.needsDozeWhitelisting;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public class NavDrawerControllerImpl extends DbControllerImpl
implements NavDrawerController, EventListener {
private static final Logger LOG =
getLogger(NavDrawerControllerImpl.class.getName());
private static final String EXPIRY_DATE_WARNING = "expiryDateWarning";
private final PluginManager pluginManager;
private final SettingsManager settingsManager;
private final EventBus eventBus;
// UI thread
private TransportStateListener listener;
@Inject
NavDrawerControllerImpl(@DatabaseExecutor Executor dbExecutor,
LifecycleManager lifecycleManager, PluginManager pluginManager,
SettingsManager settingsManager, EventBus eventBus) {
super(dbExecutor, lifecycleManager);
this.pluginManager = pluginManager;
this.settingsManager = settingsManager;
this.eventBus = eventBus;
}
@Override
public void onActivityCreate(Activity activity) {
listener = (TransportStateListener) activity;
}
@Override
public void onActivityStart() {
eventBus.addListener(this);
}
@Override
public void onActivityStop() {
eventBus.removeListener(this);
}
@Override
public void onActivityDestroy() {
}
@Override
public void eventOccurred(Event e) {
if (e instanceof TransportEnabledEvent) {
TransportId id = ((TransportEnabledEvent) e).getTransportId();
if (LOG.isLoggable(INFO)) {
LOG.info("TransportEnabledEvent: " + id.getString());
}
listener.stateUpdate(id, true);
} else if (e instanceof TransportDisabledEvent) {
TransportId id = ((TransportDisabledEvent) e).getTransportId();
if (LOG.isLoggable(INFO)) {
LOG.info("TransportDisabledEvent: " + id.getString());
}
listener.stateUpdate(id, false);
}
}
@Override
public void showExpiryWarning(ResultHandler<Boolean> handler) {
if (!IS_DEBUG_BUILD) {
handler.onResult(false);
return;
}
runOnDbThread(() -> {
try {
Settings settings =
settingsManager.getSettings(SETTINGS_NAMESPACE);
int warningInt = settings.getInt(EXPIRY_DATE_WARNING, 0);
if (warningInt == 0) {
// we have not warned before
handler.onResult(true);
} else {
long warningLong = warningInt * 1000L;
long now = System.currentTimeMillis();
long daysSinceLastWarning =
(now - warningLong) / DAYS.toMillis(1);
long daysBeforeExpiry =
(EXPIRY_DATE - now) / DAYS.toMillis(1);
if (daysSinceLastWarning >= 30) {
handler.onResult(true);
} else if (daysBeforeExpiry <= 3 &&
daysSinceLastWarning > 0) {
handler.onResult(true);
} else {
handler.onResult(false);
}
}
} catch (DbException e) {
logException(LOG, WARNING, e);
}
});
}
@Override
public void expiryWarningDismissed() {
runOnDbThread(() -> {
try {
Settings settings = new Settings();
int date = (int) (System.currentTimeMillis() / 1000L);
settings.putInt(EXPIRY_DATE_WARNING, date);
settingsManager.mergeSettings(settings, SETTINGS_NAMESPACE);
} catch (DbException e) {
logException(LOG, WARNING, e);
}
});
}
@Override
public void shouldAskForDozeWhitelisting(Context ctx,
ResultHandler<Boolean> handler) {
// check this first, to hit the DbThread only when really necessary
if (!needsDozeWhitelisting(ctx)) {
handler.onResult(false);
return;
}
runOnDbThread(() -> {
try {
Settings settings =
settingsManager.getSettings(SETTINGS_NAMESPACE);
boolean ask = settings.getBoolean(DOZE_ASK_AGAIN, true);
handler.onResult(ask);
} catch (DbException e) {
logException(LOG, WARNING, e);
handler.onResult(true);
}
});
}
@Override
public boolean isTransportRunning(TransportId transportId) {
Plugin plugin = pluginManager.getPlugin(transportId);
return plugin != null && plugin.isRunning();
}
}

View File

@@ -0,0 +1,19 @@
package org.briarproject.briar.android.navdrawer;
import org.briarproject.briar.android.viewmodel.ViewModelKey;
import androidx.lifecycle.ViewModel;
import dagger.Binds;
import dagger.Module;
import dagger.multibindings.IntoMap;
@Module
public abstract class NavDrawerModule {
@Binds
@IntoMap
@ViewModelKey(NavDrawerViewModel.class)
abstract ViewModel bindNavDrawerViewModel(
NavDrawerViewModel navDrawerViewModel);
}

View File

@@ -0,0 +1,255 @@
package org.briarproject.briar.android.navdrawer;
import android.app.Application;
import org.briarproject.bramble.api.db.DatabaseExecutor;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.event.EventListener;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.BluetoothConstants;
import org.briarproject.bramble.api.plugin.LanTcpConstants;
import org.briarproject.bramble.api.plugin.Plugin;
import org.briarproject.bramble.api.plugin.Plugin.State;
import org.briarproject.bramble.api.plugin.PluginManager;
import org.briarproject.bramble.api.plugin.TorConstants;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.event.TransportStateEvent;
import org.briarproject.bramble.api.settings.Settings;
import org.briarproject.bramble.api.settings.SettingsManager;
import org.briarproject.bramble.api.system.LocationUtils;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
import javax.inject.Inject;
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import static java.util.concurrent.TimeUnit.DAYS;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.plugin.Plugin.PREF_PLUGIN_ENABLE;
import static org.briarproject.bramble.api.plugin.Plugin.State.STARTING_STOPPING;
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_MOBILE;
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_NETWORK;
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_NETWORK_WITH_BRIDGES;
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_ONLY_WHEN_CHARGING;
import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.briar.android.TestingConstants.EXPIRY_DATE;
import static org.briarproject.briar.android.TestingConstants.IS_DEBUG_BUILD;
import static org.briarproject.briar.android.controller.BriarControllerImpl.DOZE_ASK_AGAIN;
import static org.briarproject.briar.android.settings.SettingsFragment.SETTINGS_NAMESPACE;
import static org.briarproject.briar.android.util.UiUtils.getCountryDisplayName;
import static org.briarproject.briar.android.util.UiUtils.needsDozeWhitelisting;
@NotNullByDefault
public class NavDrawerViewModel extends AndroidViewModel
implements EventListener {
private static final Logger LOG =
getLogger(NavDrawerViewModel.class.getName());
private static final String EXPIRY_DATE_WARNING = "expiryDateWarning";
static final TransportId[] TRANSPORT_IDS =
{TorConstants.ID, LanTcpConstants.ID, BluetoothConstants.ID};
@DatabaseExecutor
private final Executor dbExecutor;
private final SettingsManager settingsManager;
private final PluginManager pluginManager;
private final LocationUtils locationUtils;
private final EventBus eventBus;
private final MutableLiveData<Boolean> showExpiryWarning =
new MutableLiveData<>();
private final MutableLiveData<Boolean> shouldAskForDozeWhitelisting =
new MutableLiveData<>();
private final MutableLiveData<Plugin.State> torPluginState =
new MutableLiveData<>();
private final MutableLiveData<Plugin.State> wifiPluginState =
new MutableLiveData<>();
private final MutableLiveData<Plugin.State> btPluginState =
new MutableLiveData<>();
@Inject
NavDrawerViewModel(Application app, @DatabaseExecutor Executor dbExecutor,
SettingsManager settingsManager, PluginManager pluginManager,
LocationUtils locationUtils, EventBus eventBus) {
super(app);
this.dbExecutor = dbExecutor;
this.settingsManager = settingsManager;
this.pluginManager = pluginManager;
this.locationUtils = locationUtils;
this.eventBus = eventBus;
eventBus.addListener(this);
updatePluginStates();
}
@Override
protected void onCleared() {
eventBus.removeListener(this);
}
@Override
public void eventOccurred(Event e) {
if (e instanceof TransportStateEvent) {
TransportStateEvent t = (TransportStateEvent) e;
TransportId id = t.getTransportId();
State state = t.getState();
if (LOG.isLoggable(INFO)) {
LOG.info("TransportStateEvent: " + id + " is " + state);
}
MutableLiveData<Plugin.State> liveData = getPluginLiveData(id);
if (liveData != null) liveData.postValue(state);
}
}
LiveData<Boolean> showExpiryWarning() {
return showExpiryWarning;
}
@UiThread
void checkExpiryWarning() {
if (!IS_DEBUG_BUILD) {
showExpiryWarning.setValue(false);
return;
}
dbExecutor.execute(() -> {
try {
Settings settings =
settingsManager.getSettings(SETTINGS_NAMESPACE);
int warningInt = settings.getInt(EXPIRY_DATE_WARNING, 0);
if (warningInt == 0) {
// we have not warned before
showExpiryWarning.postValue(true);
} else {
long warningLong = warningInt * 1000L;
long now = System.currentTimeMillis();
long daysSinceLastWarning =
(now - warningLong) / DAYS.toMillis(1);
long daysBeforeExpiry =
(EXPIRY_DATE - now) / DAYS.toMillis(1);
if (daysSinceLastWarning >= 30) {
showExpiryWarning.postValue(true);
} else if (daysBeforeExpiry <= 3 &&
daysSinceLastWarning > 0) {
showExpiryWarning.postValue(true);
} else {
showExpiryWarning.postValue(false);
}
}
} catch (DbException e) {
logException(LOG, WARNING, e);
}
});
}
@UiThread
void expiryWarningDismissed() {
showExpiryWarning.setValue(false);
dbExecutor.execute(() -> {
try {
Settings settings = new Settings();
int date = (int) (System.currentTimeMillis() / 1000L);
settings.putInt(EXPIRY_DATE_WARNING, date);
settingsManager.mergeSettings(settings, SETTINGS_NAMESPACE);
} catch (DbException e) {
logException(LOG, WARNING, e);
}
});
}
LiveData<Boolean> shouldAskForDozeWhitelisting() {
return shouldAskForDozeWhitelisting;
}
@UiThread
void checkDozeWhitelisting() {
// check this first, to hit the DbThread only when really necessary
if (!needsDozeWhitelisting(getApplication())) {
shouldAskForDozeWhitelisting.setValue(false);
return;
}
dbExecutor.execute(() -> {
try {
Settings settings =
settingsManager.getSettings(SETTINGS_NAMESPACE);
boolean ask = settings.getBoolean(DOZE_ASK_AGAIN, true);
shouldAskForDozeWhitelisting.postValue(ask);
} catch (DbException e) {
logException(LOG, WARNING, e);
shouldAskForDozeWhitelisting.postValue(true);
}
});
}
private void updatePluginStates() {
for (TransportId t : TRANSPORT_IDS) {
MutableLiveData<Plugin.State> liveData = getPluginLiveData(t);
if (liveData == null) throw new AssertionError();
liveData.setValue(getTransportState(t));
}
}
private State getTransportState(TransportId id) {
Plugin plugin = pluginManager.getPlugin(id);
return plugin == null ? STARTING_STOPPING : plugin.getState();
}
@Nullable
private MutableLiveData<State> getPluginLiveData(TransportId t) {
if (t.equals(TorConstants.ID)) {
return torPluginState;
} else if (t.equals(LanTcpConstants.ID)) {
return wifiPluginState;
} else if (t.equals(BluetoothConstants.ID)) {
return btPluginState;
} else {
return null;
}
}
LiveData<State> getPluginState(TransportId t) {
LiveData<Plugin.State> liveData = getPluginLiveData(t);
if (liveData == null) throw new AssertionError();
return liveData;
}
int getReasonsDisabled(TransportId id) {
Plugin plugin = pluginManager.getPlugin(id);
return plugin == null ? 0 : plugin.getReasonsDisabled();
}
void setPluginEnabled(TransportId t, boolean enabled) {
pluginManager.setPluginEnabled(t, enabled);
}
void setTorEnabled(boolean battery, boolean mobileData, boolean location) {
Settings s = new Settings();
s.putBoolean(PREF_PLUGIN_ENABLE, true);
if (battery) s.putBoolean(PREF_TOR_ONLY_WHEN_CHARGING, false);
if (mobileData) s.putBoolean(PREF_TOR_MOBILE, true);
if (location) s.putInt(PREF_TOR_NETWORK, PREF_TOR_NETWORK_WITH_BRIDGES);
dbExecutor.execute(() -> {
try {
settingsManager.mergeSettings(s, TorConstants.ID.getString());
} catch (DbException e) {
logException(LOG, WARNING, e);
}
});
}
String getCurrentCountryName() {
return getCountryDisplayName(locationUtils.getCurrentCountry());
}
}

View File

@@ -0,0 +1,213 @@
package org.briarproject.briar.android.navdrawer;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.view.View;
import android.widget.ImageView;
import android.widget.ScrollView;
import org.briarproject.bramble.api.plugin.BluetoothConstants;
import org.briarproject.bramble.api.plugin.LanTcpConstants;
import org.briarproject.bramble.api.plugin.Plugin.State;
import org.briarproject.bramble.api.plugin.TorConstants;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.briar.R;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.AppCompatImageButton;
import androidx.appcompat.widget.SwitchCompat;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.constraintlayout.widget.ConstraintSet;
import androidx.core.app.ActivityCompat;
import static android.bluetooth.BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE;
import static android.bluetooth.BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION;
import static android.os.Build.VERSION.SDK_INT;
import static android.transition.TransitionManager.beginDelayedTransition;
import static android.view.View.FOCUS_DOWN;
import static androidx.core.content.ContextCompat.getColor;
import static org.briarproject.bramble.api.plugin.Plugin.REASON_USER;
import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE;
import static org.briarproject.bramble.api.plugin.Plugin.State.DISABLED;
import static org.briarproject.bramble.api.plugin.Plugin.State.ENABLING;
import static org.briarproject.bramble.api.plugin.Plugin.State.STARTING_STOPPING;
import static org.briarproject.bramble.api.plugin.TorConstants.REASON_BATTERY;
import static org.briarproject.bramble.api.plugin.TorConstants.REASON_COUNTRY_BLOCKED;
import static org.briarproject.bramble.api.plugin.TorConstants.REASON_MOBILE_DATA;
import static org.briarproject.briar.android.navdrawer.NavDrawerViewModel.TRANSPORT_IDS;
import static org.briarproject.briar.android.util.UiUtils.getDialogIcon;
class PluginViewController {
private final AppCompatActivity activity;
private final NavDrawerViewModel viewModel;
private final ConstraintLayout drawerContent;
private final ConstraintSet collapsedConstraints, expandedConstraints;
private final AppCompatImageButton chevronView;
private final ImageView torIcon, wifiIcon, btIcon;
private final SwitchCompat torSwitch, wifiSwitch, btSwitch;
private boolean expanded = false;
PluginViewController(View v, AppCompatActivity activity,
NavDrawerViewModel viewModel) {
this.activity = activity;
this.viewModel = viewModel;
drawerContent = v.findViewById(R.id.drawerContent);
collapsedConstraints = new ConstraintSet();
collapsedConstraints.clone(v.getContext(),
R.layout.navigation_menu_collapsed);
expandedConstraints = new ConstraintSet();
expandedConstraints.clone(v.getContext(),
R.layout.navigation_menu_expanded);
// Scroll the drawer to the bottom when the view is expanded/collapsed
ScrollView scrollView = v.findViewById(R.id.drawerScrollView);
drawerContent.addOnLayoutChangeListener((view, left, top, right,
bottom, oldLeft, oldTop, oldRight, oldBottom) ->
scrollView.fullScroll(FOCUS_DOWN));
// Clicking the chevron expands or collapses the view
chevronView = v.findViewById(R.id.chevronView);
chevronView.setOnClickListener(view -> expandOrCollapseView());
// The whole view is clickable when collapsed
v.findViewById(R.id.connectionsBackground).setOnClickListener(view ->
expandOrCollapseView());
torIcon = v.findViewById(R.id.torIcon);
wifiIcon = v.findViewById(R.id.wifiIcon);
btIcon = v.findViewById(R.id.btIcon);
torSwitch = v.findViewById(R.id.torSwitch);
wifiSwitch = v.findViewById(R.id.wifiSwitch);
btSwitch = v.findViewById(R.id.btSwitch);
for (TransportId t : TRANSPORT_IDS) {
// a OnCheckedChangeListener would get triggered on programmatic updates
SwitchCompat switchCompat = getSwitch(t);
switchCompat.setOnClickListener(buttonView -> {
if (switchCompat.isChecked()) tryToEnablePlugin(t);
else viewModel.setPluginEnabled(t, false);
// Revert the switch to its previous state until the plugin
// changes its state
switchCompat.toggle();
});
viewModel.getPluginState(t).observe(activity, state ->
stateUpdate(t, state));
}
}
private void expandOrCollapseView() {
if (SDK_INT >= 19) beginDelayedTransition(drawerContent);
if (expanded) {
collapsedConstraints.applyTo(drawerContent);
chevronView.setImageResource(R.drawable.chevron_up_white);
} else {
expandedConstraints.applyTo(drawerContent);
chevronView.setImageResource(R.drawable.chevron_down_white);
}
expanded = !expanded;
}
private void tryToEnablePlugin(TransportId id) {
if (id.equals(TorConstants.ID)) {
int reasons = viewModel.getReasonsDisabled(id);
if (reasons == 0 || reasons == REASON_USER) {
viewModel.setPluginEnabled(id, true);
} else {
showTorSettingsDialog(reasons);
}
} else if (id.equals(BluetoothConstants.ID)) {
Intent i = new Intent(ACTION_REQUEST_DISCOVERABLE);
i.putExtra(EXTRA_DISCOVERABLE_DURATION, 0);
PackageManager pm = activity.getPackageManager();
if (i.resolveActivity(pm) != null) {
ActivityCompat.startActivity(activity, i, null);
}
viewModel.setPluginEnabled(id, true);
} else {
viewModel.setPluginEnabled(id, true);
}
}
private void stateUpdate(TransportId id, State state) {
updateIcon(getIcon(id), state);
updateSwitch(getSwitch(id), state);
}
private SwitchCompat getSwitch(TransportId id) {
if (id == TorConstants.ID) return torSwitch;
if (id == BluetoothConstants.ID) return btSwitch;
if (id == LanTcpConstants.ID) return wifiSwitch;
throw new AssertionError();
}
private void updateSwitch(SwitchCompat switchCompat, State state) {
boolean checked = state != STARTING_STOPPING && state != DISABLED;
switchCompat.setChecked(checked);
switchCompat.setEnabled(state != STARTING_STOPPING);
}
private ImageView getIcon(TransportId id) {
if (id == TorConstants.ID) return torIcon;
if (id == BluetoothConstants.ID) return btIcon;
if (id == LanTcpConstants.ID) return wifiIcon;
throw new AssertionError();
}
private void updateIcon(ImageView icon, State state) {
int colorRes;
if (state == ACTIVE) {
colorRes = R.color.briar_green_light;
} else if (state == ENABLING) {
colorRes = R.color.briar_yellow;
} else {
colorRes = android.R.color.tertiary_text_light;
}
int color = getColor(icon.getContext(), colorRes);
icon.setColorFilter(color);
}
private void showTorSettingsDialog(int reasonsDisabled) {
boolean battery = (reasonsDisabled & REASON_BATTERY) != 0;
boolean mobileData = (reasonsDisabled & REASON_MOBILE_DATA) != 0;
boolean location = (reasonsDisabled & REASON_COUNTRY_BLOCKED) != 0;
StringBuilder s = new StringBuilder();
if (location) {
s.append("\t\u2022 ");
s.append(activity.getString(R.string.tor_override_network_setting,
viewModel.getCurrentCountryName()));
s.append('\n');
}
if (mobileData) {
s.append("\t\u2022 ");
s.append(activity.getString(
R.string.tor_override_mobile_data_setting));
s.append('\n');
}
if (battery) {
s.append("\t\u2022 ");
s.append(activity.getString(R.string.tor_only_when_charging_title));
s.append('\n');
}
String message = activity.getString(
R.string.tor_override_settings_body, s.toString());
AlertDialog.Builder b =
new AlertDialog.Builder(activity, R.style.BriarDialogTheme);
b.setTitle(R.string.tor_override_settings_title);
b.setIcon(getDialogIcon(activity, R.drawable.ic_settings_black_24dp));
b.setMessage(message);
b.setPositiveButton(R.string.tor_override_settings_confirm,
(dialog, which) ->
viewModel.setTorEnabled(battery, mobileData, location));
b.setNegativeButton(R.string.cancel, (dialog, which) ->
dialog.dismiss());
b.show();
}
}

View File

@@ -1,11 +0,0 @@
package org.briarproject.briar.android.navdrawer;
import org.briarproject.bramble.api.plugin.TransportId;
import androidx.annotation.UiThread;
interface TransportStateListener {
@UiThread
void stateUpdate(TransportId id, boolean enabled);
}

View File

@@ -20,7 +20,6 @@ import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.event.EventListener; import org.briarproject.bramble.api.event.EventListener;
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.BluetoothConstants;
import org.briarproject.bramble.api.plugin.TorConstants; import org.briarproject.bramble.api.plugin.TorConstants;
import org.briarproject.bramble.api.settings.Settings; import org.briarproject.bramble.api.settings.Settings;
import org.briarproject.bramble.api.settings.SettingsManager; import org.briarproject.bramble.api.settings.SettingsManager;
@@ -41,6 +40,7 @@ import java.util.logging.Logger;
import javax.inject.Inject; import javax.inject.Inject;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes; import androidx.annotation.StringRes;
import androidx.core.content.ContextCompat; import androidx.core.content.ContextCompat;
import androidx.core.text.TextUtilsCompat; import androidx.core.text.TextUtilsCompat;
@@ -72,10 +72,11 @@ import static android.widget.Toast.LENGTH_SHORT;
import static androidx.core.view.ViewCompat.LAYOUT_DIRECTION_LTR; import static androidx.core.view.ViewCompat.LAYOUT_DIRECTION_LTR;
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 org.briarproject.bramble.api.plugin.BluetoothConstants.PREF_BT_ENABLE; import static org.briarproject.bramble.api.plugin.Plugin.PREF_PLUGIN_ENABLE;
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_MOBILE; import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_MOBILE;
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_NETWORK; import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_NETWORK;
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_NETWORK_AUTOMATIC; import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_NETWORK_AUTOMATIC;
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_NETWORK_NEVER;
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_ONLY_WHEN_CHARGING; import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_ONLY_WHEN_CHARGING;
import static org.briarproject.bramble.util.LogUtils.logDuration; import static org.briarproject.bramble.util.LogUtils.logDuration;
import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.bramble.util.LogUtils.logException;
@@ -84,6 +85,7 @@ import static org.briarproject.briar.android.BriarApplication.ENTRY_ACTIVITY;
import static org.briarproject.briar.android.TestingConstants.IS_DEBUG_BUILD; import static org.briarproject.briar.android.TestingConstants.IS_DEBUG_BUILD;
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_RINGTONE; import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_RINGTONE;
import static org.briarproject.briar.android.navdrawer.NavDrawerActivity.SIGN_OUT_URI; import static org.briarproject.briar.android.navdrawer.NavDrawerActivity.SIGN_OUT_URI;
import static org.briarproject.briar.android.util.UiUtils.getCountryDisplayName;
import static org.briarproject.briar.android.util.UiUtils.hasScreenLock; import static org.briarproject.briar.android.util.UiUtils.hasScreenLock;
import static org.briarproject.briar.android.util.UiUtils.triggerFeedback; import static org.briarproject.briar.android.util.UiUtils.triggerFeedback;
import static org.briarproject.briar.api.android.AndroidNotificationManager.BLOG_CHANNEL_ID; import static org.briarproject.briar.api.android.AndroidNotificationManager.BLOG_CHANNEL_ID;
@@ -105,16 +107,16 @@ public class SettingsFragment extends PreferenceFragmentCompat
implements EventListener, OnPreferenceChangeListener { implements EventListener, OnPreferenceChangeListener {
public static final String SETTINGS_NAMESPACE = "android-ui"; public static final String SETTINGS_NAMESPACE = "android-ui";
public static final String BT_NAMESPACE = BluetoothConstants.ID.getString();
public static final String TOR_NAMESPACE = TorConstants.ID.getString();
public static final String LANGUAGE = "pref_key_language"; public static final String LANGUAGE = "pref_key_language";
public static final String PREF_SCREEN_LOCK = "pref_key_lock"; public static final String PREF_SCREEN_LOCK = "pref_key_lock";
public static final String PREF_SCREEN_LOCK_TIMEOUT = public static final String PREF_SCREEN_LOCK_TIMEOUT =
"pref_key_lock_timeout"; "pref_key_lock_timeout";
public static final String NOTIFY_SIGN_IN = "pref_key_notify_sign_in"; public static final String NOTIFY_SIGN_IN = "pref_key_notify_sign_in";
public static final String TOR_NETWORK = "pref_key_tor_network";
public static final String TOR_MOBILE = "pref_key_tor_mobile_data"; private static final String TOR_NAMESPACE = TorConstants.ID.getString();
public static final String TOR_ONLY_WHEN_CHARGING = private static final String TOR_NETWORK = "pref_key_tor_network";
private static final String TOR_MOBILE = "pref_key_tor_mobile_data";
private static final String TOR_ONLY_WHEN_CHARGING =
"pref_key_tor_only_when_charging"; "pref_key_tor_only_when_charging";
private static final Logger LOG = private static final Logger LOG =
@@ -122,7 +124,6 @@ public class SettingsFragment extends PreferenceFragmentCompat
private SettingsActivity listener; private SettingsActivity listener;
private ListPreference language; private ListPreference language;
private ListPreference enableBluetooth;
private ListPreference torNetwork; private ListPreference torNetwork;
private SwitchPreference torMobile; private SwitchPreference torMobile;
private SwitchPreference torOnlyWhenCharging; private SwitchPreference torOnlyWhenCharging;
@@ -137,7 +138,7 @@ public class SettingsFragment extends PreferenceFragmentCompat
private Preference notifySound; private Preference notifySound;
// Fields that are accessed from background threads must be volatile // Fields that are accessed from background threads must be volatile
private volatile Settings settings, btSettings, torSettings; private volatile Settings settings, torSettings;
private volatile boolean settingsLoaded = false; private volatile boolean settingsLoaded = false;
@Inject @Inject
@@ -163,28 +164,20 @@ public class SettingsFragment extends PreferenceFragmentCompat
public void onCreatePreferences(Bundle bundle, String s) { public void onCreatePreferences(Bundle bundle, String s) {
addPreferencesFromResource(R.xml.settings); addPreferencesFromResource(R.xml.settings);
language = (ListPreference) findPreference(LANGUAGE); language = findPreference(LANGUAGE);
setLanguageEntries(); setLanguageEntries();
ListPreference theme = ListPreference theme = findPreference("pref_key_theme");
(ListPreference) findPreference("pref_key_theme"); torNetwork = findPreference(TOR_NETWORK);
enableBluetooth = (ListPreference) findPreference("pref_key_bluetooth"); torMobile = findPreference(TOR_MOBILE);
torNetwork = (ListPreference) findPreference(TOR_NETWORK); torOnlyWhenCharging = findPreference(TOR_ONLY_WHEN_CHARGING);
torMobile = (SwitchPreference) findPreference(TOR_MOBILE); screenLock = findPreference(PREF_SCREEN_LOCK);
torOnlyWhenCharging = screenLockTimeout = findPreference(PREF_SCREEN_LOCK_TIMEOUT);
(SwitchPreference) findPreference(TOR_ONLY_WHEN_CHARGING); notifyPrivateMessages =
screenLock = (SwitchPreference) findPreference(PREF_SCREEN_LOCK); findPreference("pref_key_notify_private_messages");
screenLockTimeout = notifyGroupMessages = findPreference("pref_key_notify_group_messages");
(ListPreference) findPreference(PREF_SCREEN_LOCK_TIMEOUT); notifyForumPosts = findPreference("pref_key_notify_forum_posts");
notifyPrivateMessages = (SwitchPreference) findPreference( notifyBlogPosts = findPreference("pref_key_notify_blog_posts");
"pref_key_notify_private_messages"); notifyVibration = findPreference("pref_key_notify_vibration");
notifyGroupMessages = (SwitchPreference) findPreference(
"pref_key_notify_group_messages");
notifyForumPosts = (SwitchPreference) findPreference(
"pref_key_notify_forum_posts");
notifyBlogPosts = (SwitchPreference) findPreference(
"pref_key_notify_blog_posts");
notifyVibration = (SwitchPreference) findPreference(
"pref_key_notify_vibration");
notifySound = findPreference("pref_key_notify_sound"); notifySound = findPreference("pref_key_notify_sound");
language.setOnPreferenceChangeListener(this); language.setOnPreferenceChangeListener(this);
@@ -194,8 +187,7 @@ public class SettingsFragment extends PreferenceFragmentCompat
UiUtils.setTheme(getActivity(), (String) newValue); UiUtils.setTheme(getActivity(), (String) newValue);
// bring up parent activity, so it can change its theme as well // bring up parent activity, so it can change its theme as well
// upstream bug: https://issuetracker.google.com/issues/38352704 // upstream bug: https://issuetracker.google.com/issues/38352704
Intent intent = Intent intent = new Intent(getActivity(), ENTRY_ACTIVITY);
new Intent(getActivity(), ENTRY_ACTIVITY);
intent.setFlags( intent.setFlags(
FLAG_ACTIVITY_CLEAR_TASK | FLAG_ACTIVITY_NEW_TASK); FLAG_ACTIVITY_CLEAR_TASK | FLAG_ACTIVITY_NEW_TASK);
startActivity(intent); startActivity(intent);
@@ -206,7 +198,6 @@ public class SettingsFragment extends PreferenceFragmentCompat
} }
return true; return true;
}); });
enableBluetooth.setOnPreferenceChangeListener(this);
torNetwork.setOnPreferenceChangeListener(this); torNetwork.setOnPreferenceChangeListener(this);
torMobile.setOnPreferenceChangeListener(this); torMobile.setOnPreferenceChangeListener(this);
torOnlyWhenCharging.setOnPreferenceChangeListener(this); torOnlyWhenCharging.setOnPreferenceChangeListener(this);
@@ -249,8 +240,9 @@ public class SettingsFragment extends PreferenceFragmentCompat
} }
@Override @Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, public View onCreateView(LayoutInflater inflater,
Bundle savedInstanceState) { @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
View view = super.onCreateView(inflater, container, savedInstanceState); View view = super.onCreateView(inflater, container, savedInstanceState);
ColorDrawable divider = new ColorDrawable( ColorDrawable divider = new ColorDrawable(
ContextCompat.getColor(requireContext(), R.color.divider)); ContextCompat.getColor(requireContext(), R.color.divider));
@@ -325,17 +317,13 @@ public class SettingsFragment extends PreferenceFragmentCompat
// Look up country name in the user's chosen language if available // Look up country name in the user's chosen language if available
String country = locationUtils.getCurrentCountry(); String country = locationUtils.getCurrentCountry();
String countryName = country; String countryName = getCountryDisplayName(country);
for (Locale locale : Locale.getAvailableLocales()) {
if (locale.getCountry().equalsIgnoreCase(country)) {
countryName = locale.getDisplayCountry();
break;
}
}
boolean blocked = boolean blocked =
circumventionProvider.isTorProbablyBlocked(country); circumventionProvider.isTorProbablyBlocked(country);
boolean useBridges = circumventionProvider.doBridgesWork(country); boolean useBridges = circumventionProvider.doBridgesWork(country);
String setting = getString(R.string.tor_network_setting_without_bridges); String setting =
getString(R.string.tor_network_setting_without_bridges);
if (blocked && useBridges) { if (blocked && useBridges) {
setting = getString(R.string.tor_network_setting_with_bridges); setting = getString(R.string.tor_network_setting_with_bridges);
} else if (blocked) { } else if (blocked) {
@@ -351,8 +339,8 @@ public class SettingsFragment extends PreferenceFragmentCompat
try { try {
long start = now(); long start = now();
settings = settingsManager.getSettings(SETTINGS_NAMESPACE); settings = settingsManager.getSettings(SETTINGS_NAMESPACE);
btSettings = settingsManager.getSettings(BT_NAMESPACE); torSettings = migrateTorSettings(
torSettings = settingsManager.getSettings(TOR_NAMESPACE); settingsManager.getSettings(TOR_NAMESPACE));
settingsLoaded = true; settingsLoaded = true;
logDuration(LOG, "Loading settings", start); logDuration(LOG, "Loading settings", start);
displaySettings(); displaySettings();
@@ -362,15 +350,24 @@ public class SettingsFragment extends PreferenceFragmentCompat
}); });
} }
// TODO: Remove after a reasonable migration period (added 2020-01-29)
private Settings migrateTorSettings(Settings s) {
int network = s.getInt(PREF_TOR_NETWORK, PREF_TOR_NETWORK_AUTOMATIC);
if (network == PREF_TOR_NETWORK_NEVER) {
s.putInt(PREF_TOR_NETWORK, PREF_TOR_NETWORK_AUTOMATIC);
s.putBoolean(PREF_PLUGIN_ENABLE, false);
// We don't need to save the migrated settings - the Tor plugin is
// responsible for that. This code just handles the case where the
// settings are loaded before the plugin migrates them.
}
return s;
}
private void displaySettings() { private void displaySettings() {
listener.runOnUiThreadUnlessDestroyed(() -> { listener.runOnUiThreadUnlessDestroyed(() -> {
// due to events, we might try to display before a load completed // due to events, we might try to display before a load completed
if (!settingsLoaded) return; if (!settingsLoaded) return;
boolean btEnabledSetting =
btSettings.getBoolean(PREF_BT_ENABLE, false);
enableBluetooth.setValue(Boolean.toString(btEnabledSetting));
int torNetworkSetting = torSettings.getInt(PREF_TOR_NETWORK, int torNetworkSetting = torSettings.getInt(PREF_TOR_NETWORK,
PREF_TOR_NETWORK_AUTOMATIC); PREF_TOR_NETWORK_AUTOMATIC);
torNetwork.setValue(Integer.toString(torNetworkSetting)); torNetwork.setValue(Integer.toString(torNetworkSetting));
@@ -442,7 +439,6 @@ public class SettingsFragment extends PreferenceFragmentCompat
// preferences partly needed here, because they have their own logic // preferences partly needed here, because they have their own logic
// - pref_key_lock (screenLock -> displayScreenLockSetting()) // - pref_key_lock (screenLock -> displayScreenLockSetting())
// - pref_key_lock_timeout (screenLockTimeout) // - pref_key_lock_timeout (screenLockTimeout)
enableBluetooth.setEnabled(enabled);
torNetwork.setEnabled(enabled); torNetwork.setEnabled(enabled);
torMobile.setEnabled(enabled); torMobile.setEnabled(enabled);
torOnlyWhenCharging.setEnabled(enabled); torOnlyWhenCharging.setEnabled(enabled);
@@ -544,9 +540,6 @@ public class SettingsFragment extends PreferenceFragmentCompat
if (!language.getValue().equals(newValue)) if (!language.getValue().equals(newValue))
languageChanged((String) newValue); languageChanged((String) newValue);
return false; return false;
} else if (preference == enableBluetooth) {
boolean btSetting = Boolean.valueOf((String) newValue);
storeBluetoothSettings(btSetting);
} else if (preference == torNetwork) { } else if (preference == torNetwork) {
int torNetworkSetting = Integer.valueOf((String) newValue); int torNetworkSetting = Integer.valueOf((String) newValue);
storeTorNetworkSetting(torNetworkSetting); storeTorNetworkSetting(torNetworkSetting);
@@ -628,12 +621,6 @@ public class SettingsFragment extends PreferenceFragmentCompat
mergeSettings(s, TOR_NAMESPACE); mergeSettings(s, TOR_NAMESPACE);
} }
private void storeBluetoothSettings(boolean btSetting) {
Settings s = new Settings();
s.putBoolean(PREF_BT_ENABLE, btSetting);
mergeSettings(s, BT_NAMESPACE);
}
private void storeSettings(Settings s) { private void storeSettings(Settings s) {
mergeSettings(s, SETTINGS_NAMESPACE); mergeSettings(s, SETTINGS_NAMESPACE);
} }
@@ -692,13 +679,9 @@ public class SettingsFragment extends PreferenceFragmentCompat
LOG.info("Settings updated"); LOG.info("Settings updated");
settings = s.getSettings(); settings = s.getSettings();
displaySettings(); displaySettings();
} else if (namespace.equals(BT_NAMESPACE)) {
LOG.info("Bluetooth settings updated");
btSettings = s.getSettings();
displaySettings();
} else if (namespace.equals(TOR_NAMESPACE)) { } else if (namespace.equals(TOR_NAMESPACE)) {
LOG.info("Tor settings updated"); LOG.info("Tor settings updated");
torSettings = s.getSettings(); torSettings = migrateTorSettings(s.getSettings());
displaySettings(); displaySettings();
} }
} }

View File

@@ -6,6 +6,7 @@ import android.app.KeyguardManager;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface.OnClickListener; import android.content.DialogInterface.OnClickListener;
import android.content.Intent; import android.content.Intent;
import android.graphics.drawable.Drawable;
import android.net.Uri; import android.net.Uri;
import android.os.PowerManager; import android.os.PowerManager;
import android.text.Html; import android.text.Html;
@@ -38,9 +39,12 @@ import org.briarproject.briar.R;
import org.briarproject.briar.android.view.ArticleMovementMethod; import org.briarproject.briar.android.view.ArticleMovementMethod;
import org.briarproject.briar.android.widget.LinkDialogFragment; import org.briarproject.briar.android.widget.LinkDialogFragment;
import java.util.Locale;
import androidx.annotation.AttrRes; import androidx.annotation.AttrRes;
import androidx.annotation.ColorInt; import androidx.annotation.ColorInt;
import androidx.annotation.ColorRes; import androidx.annotation.ColorRes;
import androidx.annotation.DrawableRes;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi; import androidx.annotation.RequiresApi;
import androidx.annotation.UiThread; import androidx.annotation.UiThread;
@@ -79,7 +83,10 @@ import static androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM;
import static androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_NO; import static androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_NO;
import static androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_YES; import static androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_YES;
import static androidx.appcompat.app.AppCompatDelegate.setDefaultNightMode; import static androidx.appcompat.app.AppCompatDelegate.setDefaultNightMode;
import static androidx.core.content.ContextCompat.getColor;
import static androidx.core.content.ContextCompat.getDrawable;
import static androidx.core.content.ContextCompat.getSystemService; import static androidx.core.content.ContextCompat.getSystemService;
import static androidx.core.graphics.drawable.DrawableCompat.setTint;
import static androidx.core.view.ViewCompat.LAYOUT_DIRECTION_RTL; import static androidx.core.view.ViewCompat.LAYOUT_DIRECTION_RTL;
import static java.util.Objects.requireNonNull; import static java.util.Objects.requireNonNull;
import static java.util.concurrent.TimeUnit.DAYS; import static java.util.concurrent.TimeUnit.DAYS;
@@ -381,7 +388,7 @@ public class UiUtils {
/** /**
* Same as {@link #observeOnce(LiveData, LifecycleOwner, Observer)}, * Same as {@link #observeOnce(LiveData, LifecycleOwner, Observer)},
* but without a {@link LifecycleOwner}. * but without a {@link LifecycleOwner}.
* * <p>
* Warning: Do NOT call from objects that have a lifecycle. * Warning: Do NOT call from objects that have a lifecycle.
*/ */
@UiThread @UiThread
@@ -402,4 +409,19 @@ public class UiUtils {
LAYOUT_DIRECTION_RTL; LAYOUT_DIRECTION_RTL;
} }
public static String getCountryDisplayName(String isoCode) {
for (Locale locale : Locale.getAvailableLocales()) {
if (locale.getCountry().equalsIgnoreCase(isoCode)) {
return locale.getDisplayCountry();
}
}
// Name is unknown
return isoCode;
}
public static Drawable getDialogIcon(Context ctx, @DrawableRes int resId) {
Drawable icon = getDrawable(ctx, resId);
setTint(requireNonNull(icon), getColor(ctx, R.color.color_primary));
return icon;
}
} }

View File

@@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<group>
<path
android:fillColor="#000000"
android:pathData="M20,9H4v2h16V9zM4,15h16v-2H4v2z" />
</group>
</vector>

View File

@@ -1,59 +1,12 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" <ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/drawerScrollView"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="@color/window_background" android:background="@color/window_background"
android:fillViewport="true" android:fillViewport="true"
android:orientation="vertical"> android:orientation="vertical">
<androidx.constraintlayout.widget.ConstraintLayout <include layout="@layout/navigation_menu_collapsed" />
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<com.google.android.material.navigation.NavigationView
android:id="@+id/navigation"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@color/window_background"
app:elevation="0dp"
app:headerLayout="@layout/navigation_header"
app:itemBackground="@drawable/navigation_item_background"
app:itemIconTint="?attr/colorControlNormal"
app:itemTextColor="?android:textColorPrimary"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:menu="@menu/navigation_drawer" />
<View
android:id="@+id/divider1"
style="@style/Divider.Horizontal"
android:layout_width="0dp"
app:layout_constraintEnd_toEndOf="@+id/navigation"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/navigation" />
<View
android:id="@+id/spacer"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="@+id/transports"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/divider1"
app:layout_constraintVertical_weight="1" />
<include
android:id="@+id/transports"
layout="@layout/transports_list"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="@+id/navigation"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/spacer"
tools:layout_height="75dp" />
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView> </ScrollView>

View File

@@ -0,0 +1,165 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/drawerContent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@color/window_background">
<com.google.android.material.navigation.NavigationView
android:id="@+id/navigation"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@color/window_background"
app:elevation="0dp"
app:headerLayout="@layout/navigation_header"
app:itemBackground="@drawable/navigation_item_background"
app:itemIconTint="?attr/colorControlNormal"
app:itemTextColor="?android:textColorPrimary"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.0"
app:menu="@menu/navigation_drawer" />
<androidx.appcompat.widget.AppCompatImageButton
android:id="@+id/chevronView"
android:layout_width="0dp"
android:layout_height="24dp"
android:layout_marginBottom="8dp"
android:background="@color/divider"
android:foreground="?attr/selectableItemBackground"
android:src="@drawable/chevron_up_white"
app:layout_constraintBottom_toTopOf="@+id/connectionsLabel"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/navigation"
app:layout_constraintVertical_bias="1.0"
app:layout_constraintVertical_chainStyle="packed"
app:tint="?attr/colorControlNormal"
tools:ignore="ContentDescription,UnusedAttribute" />
<View
android:id="@+id/connectionsBackground"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/chevronView" />
<TextView
android:id="@+id/connectionsLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginLeft="16dp"
android:layout_marginTop="8dp"
android:layout_marginBottom="16dp"
android:text="@string/transport_connection"
android:textSize="12sp"
android:visibility="visible"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/torIcon"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/chevronView" />
<!-- Hidden -->
<View
android:id="@+id/longRangeBackground"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="@color/item_background_highlight"
android:visibility="gone"
tools:ignore="MissingConstraints" />
<!-- Hidden -->
<TextView
android:id="@+id/longRangeLabel"
android:layout_width="0dp"
android:layout_height="0dp"
android:text="@string/transport_internet"
android:textSize="12sp"
android:visibility="gone"
tools:ignore="MissingConstraints" />
<ImageView
android:id="@+id/torIcon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:layout_marginRight="16dp"
android:src="@drawable/transport_tor"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/wifiIcon"
app:layout_constraintTop_toBottomOf="@+id/chevronView"
tools:ignore="ContentDescription"
tools:tint="@color/briar_green" />
<!-- Hidden -->
<androidx.appcompat.widget.SwitchCompat
android:id="@+id/torSwitch"
android:layout_width="0dp"
android:layout_height="0dp"
android:text="@string/transport_tor"
android:visibility="gone"
tools:ignore="MissingConstraints" />
<!-- Hidden -->
<TextView
android:id="@+id/nearbyLabel"
android:layout_width="0dp"
android:layout_height="0dp"
android:text="@string/transport_nearby"
android:textSize="12sp"
android:visibility="gone"
tools:ignore="MissingConstraints" />
<ImageView
android:id="@+id/wifiIcon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:layout_marginRight="16dp"
android:src="@drawable/transport_lan"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/btIcon"
app:layout_constraintTop_toBottomOf="@+id/chevronView"
tools:checked="true"
tools:ignore="ContentDescription"
tools:tint="@color/briar_green" />
<!-- Hidden -->
<androidx.appcompat.widget.SwitchCompat
android:id="@+id/wifiSwitch"
android:layout_width="0dp"
android:layout_height="0dp"
android:text="@string/transport_lan"
android:visibility="gone"
tools:ignore="MissingConstraints" />
<ImageView
android:id="@+id/btIcon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:layout_marginRight="16dp"
android:src="@drawable/transport_bt"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/chevronView"
tools:checked="true"
tools:ignore="ContentDescription"
tools:tint="@color/briar_green" />
<!-- Hidden -->
<androidx.appcompat.widget.SwitchCompat
android:id="@+id/btSwitch"
android:layout_width="0dp"
android:layout_height="0dp"
android:text="@string/transport_bt"
android:visibility="gone"
tools:ignore="MissingConstraints" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -0,0 +1,183 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/drawerContent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@color/window_background">
<com.google.android.material.navigation.NavigationView
android:id="@+id/navigation"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@color/window_background"
app:elevation="0dp"
app:headerLayout="@layout/navigation_header"
app:itemBackground="@drawable/navigation_item_background"
app:itemIconTint="?attr/colorControlNormal"
app:itemTextColor="?android:textColorPrimary"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.0"
app:menu="@menu/navigation_drawer" />
<androidx.appcompat.widget.AppCompatImageButton
android:id="@+id/chevronView"
android:layout_width="0dp"
android:layout_height="24dp"
android:layout_marginBottom="8dp"
android:background="@color/divider"
android:foreground="?attr/selectableItemBackground"
android:src="@drawable/chevron_down_white"
app:layout_constraintBottom_toTopOf="@+id/longRangeLabel"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/navigation"
app:layout_constraintVertical_bias="1.0"
app:layout_constraintVertical_chainStyle="packed"
app:tint="?attr/colorControlNormal"
tools:ignore="ContentDescription,UnusedAttribute" />
<!-- Hidden -->
<View
android:id="@+id/connectionsBackground"
android:layout_width="0dp"
android:layout_height="0dp"
android:visibility="gone"
tools:ignore="MissingConstraints" />
<!-- Hidden -->
<TextView
android:id="@+id/connectionsLabel"
android:layout_width="0dp"
android:layout_height="0dp"
android:text="@string/transport_connection"
android:textSize="12sp"
android:visibility="gone"
tools:ignore="MissingConstraints" />
<View
android:id="@+id/longRangeBackground"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginBottom="8dp"
android:background="@color/item_background_highlight"
app:layout_constraintBottom_toTopOf="@+id/nearbyLabel"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/chevronView" />
<TextView
android:id="@+id/longRangeLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:text="@string/transport_internet"
android:textSize="12sp"
app:layout_constraintBottom_toTopOf="@+id/torSwitch"
app:layout_constraintStart_toStartOf="@+id/torIcon"
app:layout_constraintTop_toBottomOf="@+id/chevronView" />
<ImageView
android:id="@+id/torIcon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginLeft="16dp"
android:src="@drawable/transport_tor"
app:layout_constraintBottom_toBottomOf="@+id/torSwitch"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/torSwitch"
tools:ignore="ContentDescription"
tools:tint="@color/briar_green" />
<androidx.appcompat.widget.SwitchCompat
android:id="@+id/torSwitch"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginLeft="16dp"
android:layout_marginEnd="16dp"
android:layout_marginRight="16dp"
android:layout_marginBottom="16dp"
android:text="@string/transport_tor"
app:layout_constraintBottom_toTopOf="@+id/nearbyLabel"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/torIcon"
app:layout_constraintTop_toBottomOf="@+id/longRangeLabel"
tools:checked="true" />
<TextView
android:id="@+id/nearbyLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:gravity="center_vertical"
android:text="@string/transport_nearby"
android:textSize="12sp"
app:layout_constraintBottom_toTopOf="@+id/wifiSwitch"
app:layout_constraintStart_toStartOf="@+id/torIcon"
app:layout_constraintTop_toBottomOf="@+id/torSwitch" />
<ImageView
android:id="@+id/wifiIcon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginLeft="16dp"
android:src="@drawable/transport_lan"
app:layout_constraintBottom_toBottomOf="@+id/wifiSwitch"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/wifiSwitch"
tools:checked="true"
tools:ignore="ContentDescription"
tools:tint="@color/briar_green" />
<androidx.appcompat.widget.SwitchCompat
android:id="@+id/wifiSwitch"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginLeft="16dp"
android:layout_marginEnd="16dp"
android:layout_marginRight="16dp"
android:layout_marginBottom="8dp"
android:text="@string/transport_lan"
app:layout_constraintBottom_toTopOf="@+id/btSwitch"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/wifiIcon"
app:layout_constraintTop_toBottomOf="@+id/nearbyLabel"
tools:checked="true" />
<ImageView
android:id="@+id/btIcon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginLeft="16dp"
android:src="@drawable/transport_bt"
app:layout_constraintBottom_toBottomOf="@+id/btSwitch"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/btSwitch"
tools:checked="true"
tools:ignore="ContentDescription"
tools:tint="@color/briar_green" />
<androidx.appcompat.widget.SwitchCompat
android:id="@+id/btSwitch"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginLeft="16dp"
android:layout_marginEnd="16dp"
android:layout_marginRight="16dp"
android:layout_marginBottom="8dp"
android:text="@string/transport_bt"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/btIcon"
app:layout_constraintTop_toBottomOf="@+id/wifiSwitch"
tools:checked="true" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -1,19 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
tools:showIn="@layout/navigation_menu">
<View style="@style/Divider.Horizontal" />
<GridView
android:id="@+id/transportsView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:listSelector="@android:color/transparent"
android:numColumns="3"
tools:listitem="@layout/list_item_transport" />
</LinearLayout>

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<menu <menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:android="http://schemas.android.com/apk/res/android"> xmlns:tools="http://schemas.android.com/tools">
<group android:checkableBehavior="single"> <group android:checkableBehavior="single">
<item <item
@@ -30,7 +30,8 @@
android:id="@+id/nav_btn_lock" android:id="@+id/nav_btn_lock"
android:icon="@drawable/startup_lock" android:icon="@drawable/startup_lock"
android:title="@string/lock_button" android:title="@string/lock_button"
android:visible="false"/> android:visible="false"
tools:visible="false" />
<item <item
android:id="@+id/nav_btn_signout" android:id="@+id/nav_btn_signout"
android:icon="@drawable/ic_signout" android:icon="@drawable/ic_signout"

View File

@@ -1,25 +1,14 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<string-array name="boolean_array">
<item>true</item>
<item>false</item>
</string-array>
<string-array name="bt_setting_names">
<item>@string/bluetooth_setting_enabled</item>
<item>@string/bluetooth_setting_disabled</item>
</string-array>
<string-array name="tor_network_setting_names"> <string-array name="tor_network_setting_names">
<item>@string/tor_network_setting_automatic</item> <item>@string/tor_network_setting_automatic</item>
<item>@string/tor_network_setting_without_bridges</item> <item>@string/tor_network_setting_without_bridges</item>
<item>@string/tor_network_setting_with_bridges</item> <item>@string/tor_network_setting_with_bridges</item>
<item>@string/tor_network_setting_never</item>
</string-array> </string-array>
<string-array name="tor_network_setting_values"> <string-array name="tor_network_setting_values">
<item>0</item> <item>0</item>
<item>1</item> <item>1</item>
<item>2</item> <item>2</item>
<item>3</item>
</string-array> </string-array>
<string-array name="pref_language_values"> <string-array name="pref_language_values">
@@ -66,6 +55,7 @@
<item>zh-CN</item> <item>zh-CN</item>
<item>zh-TW</item> <item>zh-TW</item>
</string-array> </string-array>
<string-array name="pref_theme_entries"> <string-array name="pref_theme_entries">
<item>@string/pref_theme_light</item> <item>@string/pref_theme_light</item>
<item>@string/pref_theme_dark</item> <item>@string/pref_theme_dark</item>

View File

@@ -68,7 +68,10 @@
<string name="sign_out_button">Sign Out</string> <string name="sign_out_button">Sign Out</string>
<!-- Transports --> <!-- Transports -->
<string name="transport_tor">Internet</string> <string name="transport_connection">Connections</string>
<string name="transport_internet">Internet</string>
<string name="transport_tor">Tor</string>
<string name="transport_nearby">Nearby</string>
<string name="transport_bt">Bluetooth</string> <string name="transport_bt">Bluetooth</string>
<string name="transport_lan">Wi-Fi</string> <string name="transport_lan">Wi-Fi</string>
@@ -106,7 +109,6 @@
<string name="delete">Delete</string> <string name="delete">Delete</string>
<string name="accept">Accept</string> <string name="accept">Accept</string>
<string name="decline">Decline</string> <string name="decline">Decline</string>
<string name="options">Options</string>
<string name="online">Online</string> <string name="online">Online</string>
<string name="offline">Offline</string> <string name="offline">Offline</string>
<string name="send">Send</string> <string name="send">Send</string>
@@ -188,7 +190,6 @@
<string name="add_contact_remotely_title_case">Add Contact at a Distance</string> <string name="add_contact_remotely_title_case">Add Contact at a Distance</string>
<string name="add_contact_nearby_title">Add contact nearby</string> <string name="add_contact_nearby_title">Add contact nearby</string>
<string name="add_contact_remotely_title">Add contact at a distance</string> <string name="add_contact_remotely_title">Add contact at a distance</string>
<string name="contact_name_hint">Give contact a nickname</string>
<string name="contact_link_intro">Enter the link from your contact here</string> <string name="contact_link_intro">Enter the link from your contact here</string>
<string name="contact_link_hint">Contact\'s link</string> <string name="contact_link_hint">Contact\'s link</string>
<string name="paste_button">Paste</string> <string name="paste_button">Paste</string>
@@ -206,7 +207,6 @@
<string name="pending_contact_requests_snackbar">There are pending contact requests</string> <string name="pending_contact_requests_snackbar">There are pending contact requests</string>
<string name="pending_contact_requests">Pending Contact Requests</string> <string name="pending_contact_requests">Pending Contact Requests</string>
<string name="no_pending_contacts">No pending contacts</string> <string name="no_pending_contacts">No pending contacts</string>
<string name="add_contact_remote_connecting">Connecting…</string>
<string name="waiting_for_contact_to_come_online">Waiting for contact to come online…</string> <string name="waiting_for_contact_to_come_online">Waiting for contact to come online…</string>
<string name="connecting">Connecting…</string> <string name="connecting">Connecting…</string>
<string name="adding_contact">Adding contact…</string> <string name="adding_contact">Adding contact…</string>
@@ -227,9 +227,6 @@
<item quantity="one">New contact added.</item> <item quantity="one">New contact added.</item>
<item quantity="other">%d new contacts added.</item> <item quantity="other">%d new contacts added.</item>
</plurals> </plurals>
<string name="adding_contact_slow_warning">Adding this contact is taking longer than usual</string>
<string name="adding_contact_slow_title">Cannot Connect to Contact</string>
<string name="adding_contact_slow_text">Adding this contact is taking longer than usual.\n\nPlease check that your contact has received your link and added you:</string>
<string name="offline_state">No Internet connection</string> <string name="offline_state">No Internet connection</string>
<string name="duplicate_link_dialog_title">Duplicate Link</string> <string name="duplicate_link_dialog_title">Duplicate Link</string>
<string name="duplicate_link_dialog_text_1">You already have a pending contact with this link: %s</string> <string name="duplicate_link_dialog_text_1">You already have a pending contact with this link: %s</string>
@@ -259,11 +256,9 @@
<string name="introduction_button">Make Introduction</string> <string name="introduction_button">Make Introduction</string>
<string name="introduction_sent">Your introduction has been sent.</string> <string name="introduction_sent">Your introduction has been sent.</string>
<string name="introduction_error">There was an error making the introduction.</string> <string name="introduction_error">There was an error making the introduction.</string>
<string name="introduction_response_error">Error when responding to introduction</string>
<string name="introduction_request_sent">You have asked to introduce %1$s to %2$s.</string> <string name="introduction_request_sent">You have asked to introduce %1$s to %2$s.</string>
<string name="introduction_request_received">%1$s has asked to introduce you to %2$s. Do you want to add %2$s to your contact list?</string> <string name="introduction_request_received">%1$s has asked to introduce you to %2$s. Do you want to add %2$s to your contact list?</string>
<string name="introduction_request_exists_received">%1$s has asked to introduce you to %2$s, but %2$s is already in your contact list. Since %1$s might not know that, you can still respond:</string> <string name="introduction_request_exists_received">%1$s has asked to introduce you to %2$s, but %2$s is already in your contact list. Since %1$s might not know that, you can still respond:</string>
<string name="introduction_request_answered_received">%1$s has asked to introduce you to %2$s.</string>
<string name="introduction_response_accepted_sent">You accepted the introduction to %1$s.</string> <string name="introduction_response_accepted_sent">You accepted the introduction to %1$s.</string>
<string name="introduction_response_accepted_sent_info">Before %1$s gets added to your contacts, they need to accept the introduction as well. This might take some time.</string> <string name="introduction_response_accepted_sent_info">Before %1$s gets added to your contacts, they need to accept the introduction as well. This might take some time.</string>
<string name="introduction_response_declined_sent">You declined the introduction to %1$s.</string> <string name="introduction_response_declined_sent">You declined the introduction to %1$s.</string>
@@ -441,12 +436,9 @@
<string name="pref_theme_auto">Automatic (Daytime)</string> <string name="pref_theme_auto">Automatic (Daytime)</string>
<string name="pref_theme_system">System Default</string> <string name="pref_theme_system">System Default</string>
<!-- Settings Network --> <!-- Settings Connections -->
<string name="network_settings_title">Networks</string> <string name="network_settings_title">Connections</string>
<string name="bluetooth_setting">Connect via Bluetooth</string> <string name="tor_network_setting">Connection method for Internet (Tor)</string>
<string name="bluetooth_setting_enabled">Whenever contacts are nearby</string>
<string name="bluetooth_setting_disabled">Only when adding contacts</string>
<string name="tor_network_setting">Connect via Internet (Tor)</string>
<string name="tor_network_setting_automatic">Automatic based on location</string> <string name="tor_network_setting_automatic">Automatic based on location</string>
<string name="tor_network_setting_without_bridges">Use Tor without bridges</string> <string name="tor_network_setting_without_bridges">Use Tor without bridges</string>
<string name="tor_network_setting_with_bridges">Use Tor with bridges</string> <string name="tor_network_setting_with_bridges">Use Tor with bridges</string>
@@ -497,8 +489,6 @@
<string name="panic_setting_signout_summary">Sign out of Briar if a panic button is pressed</string> <string name="panic_setting_signout_summary">Sign out of Briar if a panic button is pressed</string>
<string name="purge_setting_title">Delete Account</string> <string name="purge_setting_title">Delete Account</string>
<string name="purge_setting_summary">Delete your Briar account if a panic button is pressed. Caution: This will permanently delete your identities, contacts and messages</string> <string name="purge_setting_summary">Delete your Briar account if a panic button is pressed. Caution: This will permanently delete your identities, contacts and messages</string>
<string name="uninstall_setting_title">Uninstall Briar</string>
<string name="uninstall_setting_summary">This requires manual confirmation in a panic event</string>
<!-- Settings Notifications --> <!-- Settings Notifications -->
<string name="notification_settings_title">Notifications</string> <string name="notification_settings_title">Notifications</string>
@@ -577,6 +567,13 @@
<string name="lock_is_locked">Briar is locked</string> <string name="lock_is_locked">Briar is locked</string>
<string name="lock_tap_to_unlock">Tap to unlock</string> <string name="lock_tap_to_unlock">Tap to unlock</string>
<!-- Overriding Tor settings -->
<string name="tor_override_settings_title">Change Settings</string>
<string name="tor_override_settings_body">Turning on Tor will change the following settings:\n\n%1$s</string>
<string name="tor_override_mobile_data_setting">Don\'t use mobile data</string>
<string name="tor_override_network_setting">Don\'t connect to Internet (Tor) in %1$s</string>
<string name="tor_override_settings_confirm">Change</string>
<!-- Screenshots --> <!-- Screenshots -->
<!-- This is a name to be used in screenshots. Feel free to change it to a local name. --> <!-- This is a name to be used in screenshots. Feel free to change it to a local name. -->

View File

@@ -29,16 +29,6 @@
android:layout="@layout/preferences_category" android:layout="@layout/preferences_category"
android:title="@string/network_settings_title"> android:title="@string/network_settings_title">
<ListPreference
android:defaultValue="false"
android:entries="@array/bt_setting_names"
android:entryValues="@array/boolean_array"
android:key="pref_key_bluetooth"
android:persistent="false"
android:summary="%s"
android:title="@string/bluetooth_setting"
app:iconSpaceReserved="false"/>
<ListPreference <ListPreference
android:defaultValue="0" android:defaultValue="0"
android:entries="@array/tor_network_setting_names" android:entries="@array/tor_network_setting_names"

View File

@@ -24,7 +24,8 @@ import org.briarproject.bramble.api.lifecycle.LifecycleManager.OpenDatabaseHook;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TorConstants; import org.briarproject.bramble.api.plugin.TorConstants;
import org.briarproject.bramble.api.plugin.TransportId; import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.event.TransportEnabledEvent; import org.briarproject.bramble.api.plugin.event.TransportActiveEvent;
import org.briarproject.bramble.api.plugin.event.TransportInactiveEvent;
import org.briarproject.bramble.api.sync.Group; import org.briarproject.bramble.api.sync.Group;
import org.briarproject.bramble.api.sync.GroupId; import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.system.Clock; import org.briarproject.bramble.api.system.Clock;
@@ -97,6 +98,8 @@ class FeedManagerImpl implements FeedManager, EventListener, OpenDatabaseHook,
private final Dns noDnsLookups; private final Dns noDnsLookups;
private final AtomicBoolean fetcherStarted = new AtomicBoolean(false); private final AtomicBoolean fetcherStarted = new AtomicBoolean(false);
private volatile boolean torActive = false;
@Inject @Inject
FeedManagerImpl(@Scheduler ScheduledExecutorService scheduler, FeedManagerImpl(@Scheduler ScheduledExecutorService scheduler,
@IoExecutor Executor ioExecutor, DatabaseComponent db, @IoExecutor Executor ioExecutor, DatabaseComponent db,
@@ -120,14 +123,23 @@ class FeedManagerImpl implements FeedManager, EventListener, OpenDatabaseHook,
@Override @Override
public void eventOccurred(Event e) { public void eventOccurred(Event e) {
if (e instanceof TransportEnabledEvent) { if (e instanceof TransportActiveEvent) {
TransportId t = ((TransportEnabledEvent) e).getTransportId(); TransportId t = ((TransportActiveEvent) e).getTransportId();
if (t.equals(TorConstants.ID)) { if (t.equals(TorConstants.ID)) {
setTorActive(true);
startFeedExecutor(); startFeedExecutor();
} }
} else if (e instanceof TransportInactiveEvent) {
TransportId t = ((TransportInactiveEvent) e).getTransportId();
if (t.equals(TorConstants.ID)) setTorActive(false);
} }
} }
// Package access for testing
void setTorActive(boolean active) {
torActive = active;
}
private void startFeedExecutor() { private void startFeedExecutor() {
if (fetcherStarted.getAndSet(true)) return; if (fetcherStarted.getAndSet(true)) return;
LOG.info("Tor started, scheduling RSS feed fetcher"); LOG.info("Tor started, scheduling RSS feed fetcher");
@@ -279,6 +291,7 @@ class FeedManagerImpl implements FeedManager, EventListener, OpenDatabaseHook,
* and we can not block the database that long. * and we can not block the database that long.
*/ */
void fetchFeeds() { void fetchFeeds() {
if (!torActive) return;
LOG.info("Updating RSS feeds..."); LOG.info("Updating RSS feeds...");
// Get current feeds // Get current feeds

View File

@@ -76,15 +76,22 @@ public class FeedManagerImplTest extends BrambleMockTestCase {
SocketFactory.getDefault(), clock, noDnsLookups); SocketFactory.getDefault(), clock, noDnsLookups);
@Test @Test
public void testEmptyFetchFeed() throws Exception { public void testFetchFeedsReturnsEarlyIfTorIsNotActive() {
BdfList feedList = new BdfList(); feedManager.setTorActive(false);
expectGetFeeds(feedList);
expectStoreFeed(feedList);
feedManager.fetchFeeds(); feedManager.fetchFeeds();
} }
@Test @Test
public void testFetchFeedIoException() throws Exception { public void testEmptyFetchFeeds() throws Exception {
BdfList feedList = new BdfList();
expectGetFeeds(feedList);
expectStoreFeed(feedList);
feedManager.setTorActive(true);
feedManager.fetchFeeds();
}
@Test
public void testFetchFeedsIoException() throws Exception {
BdfDictionary feedDict= new BdfDictionary(); BdfDictionary feedDict= new BdfDictionary();
BdfList feedList = BdfList.of(feedDict); BdfList feedList = BdfList.of(feedDict);
@@ -95,6 +102,7 @@ public class FeedManagerImplTest extends BrambleMockTestCase {
}}); }});
expectStoreFeed(feedList); expectStoreFeed(feedList);
feedManager.setTorActive(true);
feedManager.fetchFeeds(); feedManager.fetchFeeds();
} }