Compare commits

...

90 Commits

Author SHA1 Message Date
akwizgran
98e5d892a4 Poll Tor plugin infrequently once our HS is reachable. 2020-06-30 14:57:03 +01:00
akwizgran
94f8f68336 Keep track of connected OR connections. 2020-06-30 14:53:55 +01:00
akwizgran
2f8dd51ef8 Remove tests for removed polling code. 2020-06-30 14:53:55 +01:00
akwizgran
5dd1c28e77 Remove backoff code from plugins. 2020-06-30 14:53:55 +01:00
akwizgran
c1d0936a1e Log HS version when descriptor is uploaded. 2020-06-30 14:18:55 +01:00
akwizgran
717be0178a Allow local transport properties to be removed by setting empty values. 2020-06-30 14:05:46 +01:00
akwizgran
34677eb3a7 Migrate to v3 hidden services. 2020-06-30 12:22:16 +01:00
akwizgran
1dd15567de Merge branch 'style-guide-colours' into 'master'
Use colours from style guide

See merge request briar/briar!1257
2020-06-29 17:09:41 +00:00
akwizgran
428f06abdd Use style guide colours in drawables. 2020-06-29 17:58:10 +01:00
Torsten Grote
e1d1c62708 Merge branch 'default-plugin-settings' into 'master'
Enable LAN plugin by default, move default settings to constants

See merge request briar/briar!1256
2020-06-29 16:52:51 +00:00
akwizgran
ae75090d23 Use colours from style guide. 2020-06-29 17:20:42 +01:00
Torsten Grote
443043ae09 Merge branch 'plugin-toggles' into 'master'
Merge plugin-toggles feature branch

See merge request briar/briar!1255
2020-06-29 13:07:52 +00:00
akwizgran
fb85730b8e Enable LAN by default, as on master.
Let's not break too many things at once.
2020-06-26 17:42:05 +01:00
akwizgran
48b1e77065 Use constants for default plugin settings. 2020-06-26 17:42:05 +01:00
akwizgran
a03953563f Show appropriate text for answered introduction requests. 2020-06-26 17:37:17 +01:00
akwizgran
033fd2d3b4 Update comments about migration periods, since this isn't merged yet. 2020-06-26 17:37:16 +01:00
akwizgran
011d8e1df7 Remove unused strings. 2020-06-26 17:37:16 +01:00
akwizgran
ef5e2dad72 Make inner class just a holder for resources. 2020-06-26 17:37:16 +01:00
akwizgran
f35e87c8ad Remove unused code, fix a null safety bug. 2020-06-26 17:37:16 +01:00
akwizgran
e4940a046a Move transport toggles back to the settings screen. 2020-06-26 17:37:16 +01:00
akwizgran
0a666df164 Correctly handle connectivity events on IPv6-only networks. 2020-06-26 17:37:16 +01:00
akwizgran
6fb4b95b18 Don't allow list elements to be null. 2020-06-26 17:37:16 +01:00
akwizgran
5567982fb4 Use IPv6 for key agreement if IPv4 isn't available. 2020-06-26 17:37:16 +01:00
akwizgran
25e50ceb10 Try to detect our address on an IPv6-only wifi network. 2020-06-26 17:37:16 +01:00
akwizgran
1495daf977 Remove redundant address checks.
Remote addresses are checked for acceptability before connecting.
2020-06-26 17:37:16 +01:00
akwizgran
badc6da649 Accept any link-local IPv6 address.
This allows IPv6 to be used when providing an access point.
2020-06-26 17:37:16 +01:00
akwizgran
e065d45d16 Recognise unusual AP addresses. 2020-06-26 17:37:16 +01:00
akwizgran
d0c53f1310 Only bind to the SLAAC address of the wifi interface.
This is just a precaution - on all devices I've tested, the wifi
interface is the only one with a SLAAC address.
2020-06-26 17:37:16 +01:00
akwizgran
e1084ffadd Support IPv6 SLAAC addresses. 2020-06-26 17:37:15 +01:00
akwizgran
2bd2f67693 Interrupt sync sessions when transport becomes inactive.
This ensures connections are closed when the user disables a transport.
2020-06-26 17:37:12 +01:00
akwizgran
c2b0a4b8d1 Remove unnecessary plugin lookup. 2020-06-26 17:34:38 +01:00
akwizgran
ee19d2f574 Use tinted icon for plugin settings dialog. 2020-06-26 17:34:38 +01:00
akwizgran
e9ec5734e2 Show dialog from controller. 2020-06-26 17:34:38 +01:00
akwizgran
7b1c6f3fdd Add icon, title to Change Settings dialog. 2020-06-26 17:34:38 +01:00
akwizgran
d689cf776c Change Tor settings after asking for confirmation. 2020-06-26 17:34:38 +01:00
akwizgran
f0fd1844dd Transition from one constraint set to another. 2020-06-26 17:34:38 +01:00
akwizgran
d16a301fc4 Make entire collapsed view clickable. 2020-06-26 17:34:38 +01:00
akwizgran
3ab88181eb Put the transport toggles in an expandable view (no animations). 2020-06-26 17:34:37 +01:00
akwizgran
802e599f09 Add STARTING_STOPPING state, use flags for reasons disabled. 2020-06-26 17:34:37 +01:00
akwizgran
a6bd59d3c9 Close small gap between setStarted() and setDisabledBySettings(). 2020-06-26 17:34:37 +01:00
akwizgran
b04b724028 Don't show Tor in the enabling state if it's disabled by settings. 2020-06-26 17:34:37 +01:00
akwizgran
71b0408fe6 Remove "don't connect" option from Tor network setting.
This has been replaced by the enable/disable setting and no longer
works.
2020-06-26 17:34:37 +01:00
Torsten Grote
2d38bd5734 [android] Scroll down when nav drawer chevron is pressed 2020-06-26 17:34:37 +01:00
Torsten Grote
ff5da8404a [android] remove unused strings 2020-06-26 17:34:37 +01:00
Torsten Grote
75615a4e7f [android] make transport plugin toggles functional 2020-06-26 17:34:37 +01:00
Torsten Grote
96e32ad64e [android] Add transport plugin toggles to NavDrawer 2020-06-26 17:34:37 +01:00
Torsten Grote
0fec5d7783 [bramble] Add method for enabling/disabling plugins to PluginManager 2020-06-26 17:34:37 +01:00
akwizgran
ee74b3774b Remove another redundant call to pluginStateChanged(). 2020-06-26 17:34:37 +01:00
akwizgran
c783a2f352 Enable LAN plugin before showing QR code. 2020-06-26 17:34:37 +01:00
akwizgran
77aa5401f3 Remove redundant call to pluginStateChanged(). 2020-06-26 17:34:37 +01:00
akwizgran
99686f5316 Use XML to specify dependencies between settings. 2020-06-26 17:34:37 +01:00
akwizgran
f5b4f6e071 Clean up logic for enabling/disabling settings. 2020-06-26 17:34:36 +01:00
akwizgran
a2de841e6a 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-06-26 17:34:36 +01:00
akwizgran
1f94c2d4e8 Enable LAN plugin in unit test. 2020-06-26 17:34:36 +01:00
akwizgran
413ce29c0c Enable BT plugin before showing QR code. 2020-06-26 17:34:36 +01:00
akwizgran
c67f758c90 Small code cleanups in key agreement UI. 2020-06-26 17:34:36 +01:00
akwizgran
339524500b Make REASON_USER into a generic reason code. 2020-06-26 17:34:36 +01:00
akwizgran
03811f78fa Add toggle setting for LAN plugin. 2020-06-26 17:34:36 +01:00
akwizgran
fc86c46456 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-06-26 17:34:36 +01:00
akwizgran
7ae86d70af Convert Bluetooth setting to a switch. 2020-06-26 17:34:36 +01:00
akwizgran
63e3c661a3 Add toggle setting for Tor plugin. 2020-06-26 17:34:36 +01:00
akwizgran
4f54bd90fb Remove redundant casts. 2020-06-26 17:34:36 +01:00
akwizgran
706c03aa8b Skip fetching RSS feeds if Tor is not active. 2020-06-26 17:34:36 +01:00
akwizgran
c42a987927 Use amber icon when enabling transports. 2020-06-26 17:34:36 +01:00
akwizgran
297dbe0b16 Only update bridge and padding settings if network is enabled. 2020-06-26 17:34:35 +01:00
akwizgran
4130662e1f Notify callback of state changes while holding lock. 2020-06-26 17:34:35 +01:00
akwizgran
c08bdf96cd Update javadocs for lock-safe methods. 2020-06-26 17:34:35 +01:00
akwizgran
8bb534564f Remove redundant logging. 2020-06-26 17:34:35 +01:00
akwizgran
5e60a717fc Remove debug logging. 2020-06-26 17:34:35 +01:00
akwizgran
dd1509350c Close server socket when BT is disabled. 2020-06-26 17:34:35 +01:00
akwizgran
465ba3d337 Remove unnecessary inner class, state checks. 2020-06-26 17:34:35 +01:00
akwizgran
7561c5039e 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-06-26 17:34:35 +01:00
akwizgran
242d6f8a0e Move to enabling state earlier in Tor startup. 2020-06-26 17:34:35 +01:00
akwizgran
c554847b54 Add TransportStateEvent, rename existing events. 2020-06-26 17:34:35 +01:00
akwizgran
d30b250389 Ensure server socket is closed. 2020-06-26 17:34:35 +01:00
akwizgran
ecea2c587d Add method for getting reason why plugin is disabled. 2020-06-26 17:34:35 +01:00
akwizgran
43a91e2e57 Fix test expectations. 2020-06-26 17:34:35 +01:00
akwizgran
ea288b998b Rename available/unavailable states. 2020-06-26 17:34:34 +01:00
akwizgran
48dc598ca3 Update tests. 2020-06-26 17:34:34 +01:00
akwizgran
e2d63ac6a4 If adapter is disabled, forget that we enabled it. 2020-06-26 17:34:34 +01:00
akwizgran
afc85cdf52 Check that server sockets are closed as expected. 2020-06-26 17:34:34 +01:00
akwizgran
b2a1ea84f8 Provide more information about plugin states. 2020-06-26 17:34:32 +01:00
akwizgran
fcc26c093b Avoid NPE if there's no TelephonyManager. 2020-06-26 17:33:54 +01:00
Torsten Grote
5a741bf13b Merge branch '1712-bluetooth-connection-wake-lock' into 'master'
Hold a wake lock while Bluetooth connections are open

See merge request briar/briar!1251
2020-06-26 16:11:36 +00:00
akwizgran
5dc460851b Remove redundant logging. 2020-06-26 15:33:37 +01:00
akwizgran
b805514f70 Use renewable wake lock, try to guess a "safe" tag. 2020-06-26 14:58:06 +01:00
akwizgran
69d94c9f29 Hold a wake lock while Bluetooth connections are open. 2020-06-26 14:58:06 +01:00
Torsten Grote
53d4b7a0df Merge branch '1712-simple-connection-limiter' into 'master'
Simple connection limiter that closes connections cleanly

Closes #1712

See merge request briar/briar!1254
2020-06-26 11:36:21 +00:00
akwizgran
dcb5f95934 Merge branch '1712-prefer-lan-to-bluetooth' into 'master'
Close redundant connections

See merge request briar/briar!1249
2020-06-26 09:46:24 +00:00
Torsten Grote
ff8a422638 Merge branch '1712-connection-manager-refactoring' into 'master'
Connection manager refactoring

See merge request briar/briar!1248
2020-06-25 14:21:49 +00:00
113 changed files with 2551 additions and 1644 deletions

View File

@@ -12,7 +12,6 @@ import android.content.IntentFilter;
import org.briarproject.bramble.api.io.TimeoutMonitor; import org.briarproject.bramble.api.io.TimeoutMonitor;
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.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;
@@ -31,6 +30,7 @@ import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ScheduledExecutorService;
import java.util.logging.Logger; import java.util.logging.Logger;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@@ -66,6 +66,7 @@ class AndroidBluetoothPlugin extends BluetoothPlugin<BluetoothServerSocket> {
private static final int MAX_DISCOVERY_MS = 10_000; private static final int MAX_DISCOVERY_MS = 10_000;
private final ScheduledExecutorService scheduler;
private final AndroidExecutor androidExecutor; private final AndroidExecutor androidExecutor;
private final Context appContext; private final Context appContext;
private final Clock clock; private final Clock clock;
@@ -78,11 +79,13 @@ class AndroidBluetoothPlugin extends BluetoothPlugin<BluetoothServerSocket> {
AndroidBluetoothPlugin(BluetoothConnectionLimiter connectionLimiter, AndroidBluetoothPlugin(BluetoothConnectionLimiter connectionLimiter,
TimeoutMonitor timeoutMonitor, Executor ioExecutor, TimeoutMonitor timeoutMonitor, Executor ioExecutor,
SecureRandom secureRandom, AndroidExecutor androidExecutor, SecureRandom secureRandom, ScheduledExecutorService scheduler,
Context appContext, Clock clock, Backoff backoff, AndroidExecutor androidExecutor, Context appContext, Clock clock,
PluginCallback callback, int maxLatency, int maxIdleTime) { PluginCallback callback, int maxLatency, int maxIdleTime,
int pollingInterval) {
super(connectionLimiter, timeoutMonitor, ioExecutor, secureRandom, super(connectionLimiter, timeoutMonitor, ioExecutor, secureRandom,
backoff, callback, maxLatency, maxIdleTime); callback, maxLatency, maxIdleTime, pollingInterval);
this.scheduler = scheduler;
this.androidExecutor = androidExecutor; this.androidExecutor = androidExecutor;
this.appContext = appContext; this.appContext = appContext;
this.clock = clock; this.clock = clock;
@@ -150,6 +153,12 @@ class AndroidBluetoothPlugin extends BluetoothPlugin<BluetoothServerSocket> {
wasEnabledByUs = true; wasEnabledByUs = true;
} }
@Override
void onAdapterDisabled() {
super.onAdapterDisabled();
wasEnabledByUs = false;
}
@Override @Override
@Nullable @Nullable
String getBluetoothAddress() { String getBluetoothAddress() {
@@ -177,7 +186,7 @@ class AndroidBluetoothPlugin extends BluetoothPlugin<BluetoothServerSocket> {
private DuplexTransportConnection wrapSocket(BluetoothSocket s) private DuplexTransportConnection wrapSocket(BluetoothSocket s)
throws IOException { throws IOException {
return new AndroidBluetoothTransportConnection(this, connectionLimiter, return new AndroidBluetoothTransportConnection(this, connectionLimiter,
timeoutMonitor, s); timeoutMonitor, appContext, scheduler, s);
} }
@Override @Override

View File

@@ -5,8 +5,6 @@ import android.content.Context;
import org.briarproject.bramble.api.event.EventBus; import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.io.TimeoutMonitor; import org.briarproject.bramble.api.io.TimeoutMonitor;
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.BackoffFactory;
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.DuplexPlugin; import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
@@ -16,42 +14,44 @@ import org.briarproject.bramble.api.system.Clock;
import java.security.SecureRandom; import java.security.SecureRandom;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.concurrent.ScheduledExecutorService;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
import static java.util.concurrent.TimeUnit.MINUTES;
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.briarproject.bramble.api.plugin.BluetoothConstants.ID; import static org.briarproject.bramble.api.plugin.BluetoothConstants.ID;
@Immutable @Immutable
@NotNullByDefault @NotNullByDefault
public class AndroidBluetoothPluginFactory implements DuplexPluginFactory { public class AndroidBluetoothPluginFactory implements DuplexPluginFactory {
private static final int MAX_LATENCY = 30 * 1000; // 30 seconds private static final int MAX_LATENCY = (int) SECONDS.toMillis(30);
private static final int MAX_IDLE_TIME = 30 * 1000; // 30 seconds private static final int MAX_IDLE_TIME = (int) SECONDS.toMillis(30);
private static final int MIN_POLLING_INTERVAL = 60 * 1000; // 1 minute private static final int POLLING_INTERVAL = (int) MINUTES.toMillis(2);
private static final int MAX_POLLING_INTERVAL = 10 * 60 * 1000; // 10 mins
private static final double BACKOFF_BASE = 1.2;
private final Executor ioExecutor; private final Executor ioExecutor;
private final ScheduledExecutorService scheduler;
private final AndroidExecutor androidExecutor; private final AndroidExecutor androidExecutor;
private final Context appContext; private final Context appContext;
private final SecureRandom secureRandom; private final SecureRandom secureRandom;
private final EventBus eventBus; private final EventBus eventBus;
private final Clock clock; private final Clock clock;
private final TimeoutMonitor timeoutMonitor; private final TimeoutMonitor timeoutMonitor;
private final BackoffFactory backoffFactory;
public AndroidBluetoothPluginFactory(Executor ioExecutor, public AndroidBluetoothPluginFactory(Executor ioExecutor,
ScheduledExecutorService scheduler,
AndroidExecutor androidExecutor, Context appContext, AndroidExecutor androidExecutor, Context appContext,
SecureRandom secureRandom, EventBus eventBus, Clock clock, SecureRandom secureRandom, EventBus eventBus, Clock clock,
TimeoutMonitor timeoutMonitor, BackoffFactory backoffFactory) { TimeoutMonitor timeoutMonitor) {
this.ioExecutor = ioExecutor; this.ioExecutor = ioExecutor;
this.scheduler = scheduler;
this.androidExecutor = androidExecutor; this.androidExecutor = androidExecutor;
this.appContext = appContext; this.appContext = appContext;
this.secureRandom = secureRandom; this.secureRandom = secureRandom;
this.eventBus = eventBus; this.eventBus = eventBus;
this.clock = clock; this.clock = clock;
this.timeoutMonitor = timeoutMonitor; this.timeoutMonitor = timeoutMonitor;
this.backoffFactory = backoffFactory;
} }
@Override @Override
@@ -68,12 +68,10 @@ public class AndroidBluetoothPluginFactory implements DuplexPluginFactory {
public DuplexPlugin createPlugin(PluginCallback callback) { public DuplexPlugin createPlugin(PluginCallback callback) {
BluetoothConnectionLimiter connectionLimiter = BluetoothConnectionLimiter connectionLimiter =
new BluetoothConnectionLimiterImpl(eventBus); new BluetoothConnectionLimiterImpl(eventBus);
Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL,
MAX_POLLING_INTERVAL, BACKOFF_BASE);
AndroidBluetoothPlugin plugin = new AndroidBluetoothPlugin( AndroidBluetoothPlugin plugin = new AndroidBluetoothPlugin(
connectionLimiter, timeoutMonitor, ioExecutor, secureRandom, connectionLimiter, timeoutMonitor, ioExecutor, secureRandom,
androidExecutor, appContext, clock, backoff, scheduler, androidExecutor, appContext, clock, callback,
callback, MAX_LATENCY, MAX_IDLE_TIME); MAX_LATENCY, MAX_IDLE_TIME, POLLING_INTERVAL);
eventBus.addListener(plugin); eventBus.addListener(plugin);
return plugin; return plugin;
} }

View File

@@ -1,17 +1,26 @@
package org.briarproject.bramble.plugin.bluetooth; package org.briarproject.bramble.plugin.bluetooth;
import android.bluetooth.BluetoothSocket; import android.bluetooth.BluetoothSocket;
import android.content.Context;
import android.os.PowerManager;
import org.briarproject.bramble.api.io.TimeoutMonitor; import org.briarproject.bramble.api.io.TimeoutMonitor;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.Plugin; import org.briarproject.bramble.api.plugin.Plugin;
import org.briarproject.bramble.api.plugin.duplex.AbstractDuplexTransportConnection; import org.briarproject.bramble.api.plugin.duplex.AbstractDuplexTransportConnection;
import org.briarproject.bramble.util.RenewableWakeLock;
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.concurrent.ScheduledExecutorService;
import static android.content.Context.POWER_SERVICE;
import static android.os.PowerManager.PARTIAL_WAKE_LOCK;
import static java.util.concurrent.TimeUnit.MINUTES;
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_ADDRESS;
import static org.briarproject.bramble.util.AndroidUtils.getWakeLockTag;
import static org.briarproject.bramble.util.AndroidUtils.isValidBluetoothAddress; import static org.briarproject.bramble.util.AndroidUtils.isValidBluetoothAddress;
@NotNullByDefault @NotNullByDefault
@@ -19,18 +28,26 @@ class AndroidBluetoothTransportConnection
extends AbstractDuplexTransportConnection { extends AbstractDuplexTransportConnection {
private final BluetoothConnectionLimiter connectionLimiter; private final BluetoothConnectionLimiter connectionLimiter;
private final RenewableWakeLock wakeLock;
private final BluetoothSocket socket; private final BluetoothSocket socket;
private final InputStream in; private final InputStream in;
AndroidBluetoothTransportConnection(Plugin plugin, AndroidBluetoothTransportConnection(Plugin plugin,
BluetoothConnectionLimiter connectionLimiter, BluetoothConnectionLimiter connectionLimiter,
TimeoutMonitor timeoutMonitor, BluetoothSocket socket) TimeoutMonitor timeoutMonitor, Context appContext,
ScheduledExecutorService scheduler, BluetoothSocket socket)
throws IOException { throws IOException {
super(plugin); super(plugin);
this.connectionLimiter = connectionLimiter; this.connectionLimiter = connectionLimiter;
this.socket = socket; this.socket = socket;
in = timeoutMonitor.createTimeoutInputStream( in = timeoutMonitor.createTimeoutInputStream(
socket.getInputStream(), plugin.getMaxIdleTime() * 2); socket.getInputStream(), plugin.getMaxIdleTime() * 2);
PowerManager powerManager = (PowerManager)
requireNonNull(appContext.getSystemService(POWER_SERVICE));
String tag = getWakeLockTag(appContext);
wakeLock = new RenewableWakeLock(powerManager, scheduler,
PARTIAL_WAKE_LOCK, tag, 1, MINUTES);
wakeLock.acquire();
String address = socket.getRemoteDevice().getAddress(); String address = socket.getRemoteDevice().getAddress();
if (isValidBluetoothAddress(address)) remote.put(PROP_ADDRESS, address); if (isValidBluetoothAddress(address)) remote.put(PROP_ADDRESS, address);
} }
@@ -50,6 +67,7 @@ class AndroidBluetoothTransportConnection
try { try {
socket.close(); socket.close();
} finally { } finally {
wakeLock.release();
connectionLimiter.connectionClosed(this); connectionLimiter.connectionClosed(this);
} }
} }

View File

@@ -1,23 +1,29 @@
package org.briarproject.bramble.plugin.tcp; package org.briarproject.bramble.plugin.tcp;
import android.annotation.TargetApi;
import android.content.Context; import android.content.Context;
import android.net.ConnectivityManager; import android.net.ConnectivityManager;
import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.Network; import android.net.Network;
import android.net.NetworkInfo; import android.net.NetworkCapabilities;
import android.net.wifi.WifiInfo; import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager; import android.net.wifi.WifiManager;
import org.briarproject.bramble.PoliteExecutor; import org.briarproject.bramble.PoliteExecutor;
import org.briarproject.bramble.api.Pair;
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.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;
import java.net.InterfaceAddress;
import java.net.NetworkInterface;
import java.net.Socket; import java.net.Socket;
import java.net.SocketException;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import java.util.List; import java.util.List;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
@@ -28,14 +34,21 @@ import javax.net.SocketFactory;
import static android.content.Context.CONNECTIVITY_SERVICE; import static android.content.Context.CONNECTIVITY_SERVICE;
import static android.content.Context.WIFI_SERVICE; import static android.content.Context.WIFI_SERVICE;
import static android.net.ConnectivityManager.TYPE_WIFI; import static android.net.NetworkCapabilities.TRANSPORT_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.list;
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.LanTcpConstants.DEFAULT_PREF_PLUGIN_ENABLE;
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;
import static org.briarproject.bramble.util.LogUtils.logException;
@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());
@@ -48,9 +61,9 @@ class AndroidLanTcpPlugin extends LanTcpPlugin implements EventListener {
private volatile SocketFactory socketFactory; private volatile SocketFactory socketFactory;
AndroidLanTcpPlugin(Executor ioExecutor, Context appContext, AndroidLanTcpPlugin(Executor ioExecutor, Context appContext,
Backoff backoff, PluginCallback callback, int maxLatency, PluginCallback callback, int maxLatency, int maxIdleTime,
int maxIdleTime, int connectionTimeout) { int pollingInterval, int connectionTimeout) {
super(ioExecutor, backoff, callback, maxLatency, maxIdleTime, super(ioExecutor, callback, maxLatency, maxIdleTime, pollingInterval,
connectionTimeout); connectionTimeout);
// 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 =
@@ -68,37 +81,137 @@ 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,
DEFAULT_PREF_PLUGIN_ENABLE));
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();
} }
@Override @Override
protected List<InetAddress> getUsableLocalInetAddresses() { protected List<InetAddress> getUsableLocalInetAddresses(boolean ipv4) {
// If the device doesn't have wifi, don't open any sockets InetAddress addr = getWifiAddress(ipv4);
if (wifiManager == null) return emptyList(); return addr == null ? emptyList() : singletonList(addr);
}
@Nullable
private InetAddress getWifiAddress(boolean ipv4) {
Pair<InetAddress, Boolean> wifi = getWifiIpv4Address();
if (ipv4) return wifi == null ? null : wifi.getFirst();
// If there's no wifi IPv4 address, we might be a client on an
// IPv6-only wifi network. We can only detect this on API 21+
if (wifi == null) {
return SDK_INT >= 21 ? getWifiClientIpv6Address() : null;
}
// Use the wifi IPv4 address to determine which interface's IPv6
// address we should return (if the interface has a suitable address)
return getIpv6AddressForInterface(wifi.getFirst());
}
/**
* Returns a {@link Pair} where the first element is the IPv4 address of
* the wifi interface and the second element is true if this device is
* providing an access point, or false if this device is a client. Returns
* null if this device isn't connected to wifi as an access point or client.
*/
@Nullable
private Pair<InetAddress, Boolean> getWifiIpv4Address() {
if (wifiManager == null) return null;
// If we're connected to a wifi network, return its address // If we're connected to a wifi network, return its address
WifiInfo info = wifiManager.getConnectionInfo(); WifiInfo info = wifiManager.getConnectionInfo();
if (info != null && info.getIpAddress() != 0) { if (info != null && info.getIpAddress() != 0) {
return singletonList(intToInetAddress(info.getIpAddress())); return new Pair<>(intToInetAddress(info.getIpAddress()), false);
} }
// If we're running an access point, return its address List<InterfaceAddress> ifAddrs = getLocalInterfaceAddresses();
for (InetAddress addr : getLocalInetAddresses()) { // If we're providing a normal access point, return its address
if (addr.equals(WIFI_AP_ADDRESS)) return singletonList(addr); for (InterfaceAddress ifAddr : ifAddrs) {
if (addr.equals(WIFI_DIRECT_AP_ADDRESS)) return singletonList(addr); if (isAndroidWifiApAddress(ifAddr)) {
return new Pair<>(ifAddr.getAddress(), true);
}
}
// If we're providing a wifi direct access point, return its address
for (InterfaceAddress ifAddr : ifAddrs) {
if (isAndroidWifiDirectApAddress(ifAddr)) {
return new Pair<>(ifAddr.getAddress(), true);
}
}
// Not connected to wifi
return null;
}
/**
* Returns true if the given address belongs to a network provided by an
* Android access point (including the access point's own address).
* <p>
* The access point's address is usually 192.168.43.1, but at least one
* device (Honor 8A) may use other addresses in the range 192.168.43.0/24.
*/
private boolean isAndroidWifiApAddress(InterfaceAddress ifAddr) {
if (ifAddr.getNetworkPrefixLength() != 24) return false;
byte[] ip = ifAddr.getAddress().getAddress();
return ip.length == 4
&& ip[0] == (byte) 192
&& ip[1] == (byte) 168
&& ip[2] == (byte) 43;
}
/**
* Returns true if the given address belongs to a network provided by an
* Android wifi direct legacy mode access point (including the access
* point's own address).
*/
private boolean isAndroidWifiDirectApAddress(InterfaceAddress ifAddr) {
if (ifAddr.getNetworkPrefixLength() != 24) return false;
byte[] ip = ifAddr.getAddress().getAddress();
return ip.length == 4
&& ip[0] == (byte) 192
&& ip[1] == (byte) 168
&& ip[2] == (byte) 49;
}
/**
* Returns a link-local IPv6 address for the wifi client interface, or null
* if there's no such interface or it doesn't have a suitable address.
*/
@TargetApi(21)
@Nullable
private InetAddress getWifiClientIpv6Address() {
for (Network net : connectivityManager.getAllNetworks()) {
NetworkCapabilities caps =
connectivityManager.getNetworkCapabilities(net);
if (caps == null || !caps.hasTransport(TRANSPORT_WIFI)) continue;
LinkProperties props = connectivityManager.getLinkProperties(net);
if (props == null) continue;
for (LinkAddress linkAddress : props.getLinkAddresses()) {
InetAddress addr = linkAddress.getAddress();
if (isIpv6LinkLocalAddress(addr)) return addr;
}
}
return null;
}
/**
* Returns a link-local IPv6 address for the interface with the given IPv4
* address, or null if the interface doesn't have a suitable address.
*/
@Nullable
private InetAddress getIpv6AddressForInterface(InetAddress ipv4) {
try {
NetworkInterface iface = NetworkInterface.getByInetAddress(ipv4);
if (iface == null) return null;
for (InetAddress addr : list(iface.getInetAddresses())) {
if (isIpv6LinkLocalAddress(addr)) return addr;
}
// No suitable address
return null;
} catch (SocketException e) {
logException(LOG, WARNING, e);
return null;
} }
// No suitable addresses
return emptyList();
} }
private InetAddress intToInetAddress(int ip) { private InetAddress intToInetAddress(int ip) {
@@ -120,9 +233,11 @@ class AndroidLanTcpPlugin extends LanTcpPlugin implements EventListener {
private SocketFactory getSocketFactory() { private SocketFactory getSocketFactory() {
if (SDK_INT < 21) return SocketFactory.getDefault(); if (SDK_INT < 21) return SocketFactory.getDefault();
for (Network net : connectivityManager.getAllNetworks()) { for (Network net : connectivityManager.getAllNetworks()) {
NetworkInfo info = connectivityManager.getNetworkInfo(net); NetworkCapabilities caps =
if (info != null && info.getType() == TYPE_WIFI) connectivityManager.getNetworkCapabilities(net);
if (caps != null && caps.hasTransport(TRANSPORT_WIFI)) {
return net.getSocketFactory(); return net.getSocketFactory();
}
} }
LOG.warning("Could not find suitable socket factory"); LOG.warning("Could not find suitable socket factory");
return SocketFactory.getDefault(); return SocketFactory.getDefault();
@@ -130,31 +245,59 @@ 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();
List<InetAddress> addrs = getUsableLocalInetAddresses(); if (s != ACTIVE && s != INACTIVE) return;
if (addrs.contains(WIFI_AP_ADDRESS) Pair<InetAddress, Boolean> wifi = getPreferredWifiAddress();
|| addrs.contains(WIFI_DIRECT_AP_ADDRESS)) { if (wifi == null) {
LOG.info("Not connected to wifi");
socketFactory = SocketFactory.getDefault();
// Server sockets may not have been closed automatically when
// interface was taken down. If any sockets are open, closing
// them here will cause the sockets to be cleared and the state
// to be updated in acceptContactConnections()
if (s == ACTIVE) {
LOG.info("Closing server sockets");
tryToClose(state.getServerSocket(true), LOG, WARNING);
tryToClose(state.getServerSocket(false), LOG, WARNING);
}
} else if (wifi.getSecond()) {
LOG.info("Providing wifi hotspot"); LOG.info("Providing wifi hotspot");
// There's no corresponding Network object and thus no way // There's no corresponding Network object and thus no way
// to get a suitable socket factory, so we won't be able to // to get a suitable socket factory, so we won't be able to
// 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()) {
LOG.info("Not connected to wifi");
socketFactory = SocketFactory.getDefault();
tryToClose(socket);
} 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();
} }
}); });
} }
/**
* Returns a {@link Pair} where the first element is an IP address (IPv4 if
* available, otherwise IPv6) of the wifi interface and the second element
* is true if this device is providing an access point, or false if this
* device is a client. Returns null if this device isn't connected to wifi
* as an access point or client.
*/
@Nullable
private Pair<InetAddress, Boolean> getPreferredWifiAddress() {
Pair<InetAddress, Boolean> wifi = getWifiIpv4Address();
// If there's no wifi IPv4 address, we might be a client on an
// IPv6-only wifi network. We can only detect this on API 21+
if (wifi == null && SDK_INT >= 21) {
InetAddress ipv6 = getWifiClientIpv6Address();
if (ipv6 != null) return new Pair<>(ipv6, false);
}
return wifi;
}
} }

View File

@@ -4,8 +4,6 @@ import android.content.Context;
import org.briarproject.bramble.api.event.EventBus; 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.BackoffFactory;
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.DuplexPlugin; import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
@@ -15,29 +13,27 @@ import java.util.concurrent.Executor;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
import static java.util.concurrent.TimeUnit.MINUTES;
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.briarproject.bramble.api.plugin.LanTcpConstants.ID; import static org.briarproject.bramble.api.plugin.LanTcpConstants.ID;
@Immutable @Immutable
@NotNullByDefault @NotNullByDefault
public class AndroidLanTcpPluginFactory implements DuplexPluginFactory { public class AndroidLanTcpPluginFactory implements DuplexPluginFactory {
private static final int MAX_LATENCY = 30_000; // 30 seconds private static final int MAX_LATENCY = (int) SECONDS.toMillis(30);
private static final int MAX_IDLE_TIME = 30_000; // 30 seconds private static final int MAX_IDLE_TIME = (int) SECONDS.toMillis(30);
private static final int CONNECTION_TIMEOUT = 3_000; // 3 seconds private static final int POLLING_INTERVAL = (int) MINUTES.toMillis(1);
private static final int MIN_POLLING_INTERVAL = 60_000; // 1 minute private static final int CONNECTION_TIMEOUT = (int) SECONDS.toMillis(3);
private static final int MAX_POLLING_INTERVAL = 600_000; // 10 mins
private static final double BACKOFF_BASE = 1.2;
private final Executor ioExecutor; private final Executor ioExecutor;
private final EventBus eventBus; private final EventBus eventBus;
private final BackoffFactory backoffFactory;
private final Context appContext; private final Context appContext;
public AndroidLanTcpPluginFactory(Executor ioExecutor, EventBus eventBus, public AndroidLanTcpPluginFactory(Executor ioExecutor, EventBus eventBus,
BackoffFactory backoffFactory, Context appContext) { Context appContext) {
this.ioExecutor = ioExecutor; this.ioExecutor = ioExecutor;
this.eventBus = eventBus; this.eventBus = eventBus;
this.backoffFactory = backoffFactory;
this.appContext = appContext; this.appContext = appContext;
} }
@@ -53,11 +49,9 @@ public class AndroidLanTcpPluginFactory implements DuplexPluginFactory {
@Override @Override
public DuplexPlugin createPlugin(PluginCallback callback) { public DuplexPlugin createPlugin(PluginCallback callback) {
Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL,
MAX_POLLING_INTERVAL, BACKOFF_BASE);
AndroidLanTcpPlugin plugin = new AndroidLanTcpPlugin(ioExecutor, AndroidLanTcpPlugin plugin = new AndroidLanTcpPlugin(ioExecutor,
appContext, backoff, callback, MAX_LATENCY, MAX_IDLE_TIME, appContext, callback, MAX_LATENCY, MAX_IDLE_TIME,
CONNECTION_TIMEOUT); POLLING_INTERVAL, CONNECTION_TIMEOUT);
eventBus.addListener(plugin); eventBus.addListener(plugin);
return plugin; return plugin;
} }

View File

@@ -10,7 +10,6 @@ import org.briarproject.bramble.api.battery.BatteryManager;
import org.briarproject.bramble.api.network.NetworkManager; import org.briarproject.bramble.api.network.NetworkManager;
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.PluginCallback; import org.briarproject.bramble.api.plugin.PluginCallback;
import org.briarproject.bramble.api.system.Clock; import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.api.system.LocationUtils; import org.briarproject.bramble.api.system.LocationUtils;
@@ -27,6 +26,7 @@ import static android.content.Context.MODE_PRIVATE;
import static android.content.Context.POWER_SERVICE; import static android.content.Context.POWER_SERVICE;
import static android.os.PowerManager.PARTIAL_WAKE_LOCK; import static android.os.PowerManager.PARTIAL_WAKE_LOCK;
import static java.util.concurrent.TimeUnit.MINUTES; import static java.util.concurrent.TimeUnit.MINUTES;
import static org.briarproject.bramble.util.AndroidUtils.getWakeLockTag;
@MethodsNotNullByDefault @MethodsNotNullByDefault
@ParametersNotNullByDefault @ParametersNotNullByDefault
@@ -40,20 +40,22 @@ class AndroidTorPlugin extends TorPlugin {
LocationUtils locationUtils, SocketFactory torSocketFactory, LocationUtils locationUtils, SocketFactory torSocketFactory,
Clock clock, ResourceProvider resourceProvider, Clock clock, ResourceProvider resourceProvider,
CircumventionProvider circumventionProvider, CircumventionProvider circumventionProvider,
BatteryManager batteryManager, Backoff backoff, BatteryManager batteryManager,
TorRendezvousCrypto torRendezvousCrypto, TorRendezvousCrypto torRendezvousCrypto,
PluginCallback callback, String architecture, int maxLatency, PluginCallback callback, String architecture, int maxLatency,
int maxIdleTime) { int maxIdleTime, int initialPollingInterval,
int stablePollingInterval) {
super(ioExecutor, networkManager, locationUtils, torSocketFactory, super(ioExecutor, networkManager, locationUtils, torSocketFactory,
clock, resourceProvider, circumventionProvider, batteryManager, clock, resourceProvider, circumventionProvider, batteryManager,
backoff, torRendezvousCrypto, callback, architecture, maxLatency, maxIdleTime, torRendezvousCrypto, callback, architecture, maxLatency,
maxIdleTime, initialPollingInterval, stablePollingInterval,
appContext.getDir("tor", MODE_PRIVATE)); appContext.getDir("tor", MODE_PRIVATE));
this.appContext = appContext; this.appContext = appContext;
PowerManager pm = (PowerManager) PowerManager pm = (PowerManager)
appContext.getSystemService(POWER_SERVICE); appContext.getSystemService(POWER_SERVICE);
if (pm == null) throw new AssertionError(); if (pm == null) throw new AssertionError();
wakeLock = new RenewableWakeLock(pm, scheduler, PARTIAL_WAKE_LOCK, wakeLock = new RenewableWakeLock(pm, scheduler, PARTIAL_WAKE_LOCK,
getWakeLockTag(), 1, MINUTES); getWakeLockTag(appContext), 1, MINUTES);
} }
@Override @Override
@@ -74,7 +76,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();
@@ -85,17 +86,4 @@ class AndroidTorPlugin extends TorPlugin {
super.stop(); super.stop();
wakeLock.release(); wakeLock.release();
} }
private String getWakeLockTag() {
PackageManager pm = appContext.getPackageManager();
for (PackageInfo info : pm.getInstalledPackages(0)) {
String name = info.packageName.toLowerCase();
if (name.startsWith("com.huawei.powergenie")) {
return "LocationManagerService";
} else if (name.startsWith("com.evenwell.powermonitor")) {
return "AudioIn";
}
}
return getClass().getSimpleName();
}
} }

View File

@@ -6,8 +6,6 @@ import org.briarproject.bramble.api.battery.BatteryManager;
import org.briarproject.bramble.api.event.EventBus; import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.network.NetworkManager; import org.briarproject.bramble.api.network.NetworkManager;
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.BackoffFactory;
import org.briarproject.bramble.api.plugin.PluginCallback; import org.briarproject.bramble.api.plugin.PluginCallback;
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;
@@ -25,18 +23,33 @@ import java.util.logging.Logger;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
import javax.net.SocketFactory; import javax.net.SocketFactory;
import static java.util.concurrent.TimeUnit.MINUTES;
import static java.util.concurrent.TimeUnit.SECONDS;
import static java.util.logging.Logger.getLogger;
@Immutable @Immutable
@NotNullByDefault @NotNullByDefault
public class AndroidTorPluginFactory implements DuplexPluginFactory { public class AndroidTorPluginFactory implements DuplexPluginFactory {
private static final Logger LOG = private static final Logger LOG =
Logger.getLogger(AndroidTorPluginFactory.class.getName()); getLogger(AndroidTorPluginFactory.class.getName());
private static final int MAX_LATENCY = 30 * 1000; // 30 seconds private static final int MAX_LATENCY = (int) SECONDS.toMillis(30);
private static final int MAX_IDLE_TIME = 30 * 1000; // 30 seconds private static final int MAX_IDLE_TIME = (int) SECONDS.toMillis(30);
private static final int MIN_POLLING_INTERVAL = 60 * 1000; // 1 minute
private static final int MAX_POLLING_INTERVAL = 10 * 60 * 1000; // 10 mins /**
private static final double BACKOFF_BASE = 1.2; * How often to poll before our hidden service becomes reachable.
*/
private static final int INITIAL_POLLING_INTERVAL =
(int) MINUTES.toMillis(1);
/**
* How often to poll when our hidden service is reachable. Our contacts
* will poll when they come online, so our polling is just a fallback in
* case of repeated connection failures.
*/
private static final int STABLE_POLLING_INTERVAL =
(int) MINUTES.toMillis(15);
private final Executor ioExecutor; private final Executor ioExecutor;
private final ScheduledExecutorService scheduler; private final ScheduledExecutorService scheduler;
@@ -45,7 +58,6 @@ public class AndroidTorPluginFactory implements DuplexPluginFactory {
private final LocationUtils locationUtils; private final LocationUtils locationUtils;
private final EventBus eventBus; private final EventBus eventBus;
private final SocketFactory torSocketFactory; private final SocketFactory torSocketFactory;
private final BackoffFactory backoffFactory;
private final ResourceProvider resourceProvider; private final ResourceProvider resourceProvider;
private final CircumventionProvider circumventionProvider; private final CircumventionProvider circumventionProvider;
private final BatteryManager batteryManager; private final BatteryManager batteryManager;
@@ -55,7 +67,7 @@ public class AndroidTorPluginFactory implements DuplexPluginFactory {
ScheduledExecutorService scheduler, Context appContext, ScheduledExecutorService scheduler, Context appContext,
NetworkManager networkManager, LocationUtils locationUtils, NetworkManager networkManager, LocationUtils locationUtils,
EventBus eventBus, SocketFactory torSocketFactory, EventBus eventBus, SocketFactory torSocketFactory,
BackoffFactory backoffFactory, ResourceProvider resourceProvider, ResourceProvider resourceProvider,
CircumventionProvider circumventionProvider, CircumventionProvider circumventionProvider,
BatteryManager batteryManager, Clock clock) { BatteryManager batteryManager, Clock clock) {
this.ioExecutor = ioExecutor; this.ioExecutor = ioExecutor;
@@ -65,7 +77,6 @@ public class AndroidTorPluginFactory implements DuplexPluginFactory {
this.locationUtils = locationUtils; this.locationUtils = locationUtils;
this.eventBus = eventBus; this.eventBus = eventBus;
this.torSocketFactory = torSocketFactory; this.torSocketFactory = torSocketFactory;
this.backoffFactory = backoffFactory;
this.resourceProvider = resourceProvider; this.resourceProvider = resourceProvider;
this.circumventionProvider = circumventionProvider; this.circumventionProvider = circumventionProvider;
this.batteryManager = batteryManager; this.batteryManager = batteryManager;
@@ -109,14 +120,13 @@ public class AndroidTorPluginFactory implements DuplexPluginFactory {
// Use position-independent executable // Use position-independent executable
architecture += "_pie"; architecture += "_pie";
Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL,
MAX_POLLING_INTERVAL, BACKOFF_BASE);
TorRendezvousCrypto torRendezvousCrypto = new TorRendezvousCryptoImpl(); TorRendezvousCrypto torRendezvousCrypto = new TorRendezvousCryptoImpl();
AndroidTorPlugin plugin = new AndroidTorPlugin(ioExecutor, scheduler, AndroidTorPlugin plugin = new AndroidTorPlugin(ioExecutor, scheduler,
appContext, networkManager, locationUtils, torSocketFactory, appContext, networkManager, locationUtils, torSocketFactory,
clock, resourceProvider, circumventionProvider, batteryManager, clock, resourceProvider, circumventionProvider, batteryManager,
backoff, torRendezvousCrypto, callback, architecture, torRendezvousCrypto, callback, architecture, MAX_LATENCY,
MAX_LATENCY, MAX_IDLE_TIME); MAX_IDLE_TIME, INITIAL_POLLING_INTERVAL,
STABLE_POLLING_INTERVAL);
eventBus.addListener(plugin); eventBus.addListener(plugin);
return plugin; return plugin;
} }

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

@@ -3,6 +3,8 @@ package org.briarproject.bramble.util;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothAdapter;
import android.content.Context; import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Build; import android.os.Build;
import android.provider.Settings; import android.provider.Settings;
@@ -117,4 +119,17 @@ public class AndroidUtils {
if (SDK_INT < 24) return new String[] {"image/jpeg", "image/png"}; if (SDK_INT < 24) return new String[] {"image/jpeg", "image/png"};
else return new String[] {"image/jpeg", "image/png", "image/gif"}; else return new String[] {"image/jpeg", "image/png", "image/gif"};
} }
public static String getWakeLockTag(Context ctx) {
PackageManager pm = ctx.getPackageManager();
for (PackageInfo info : pm.getInstalledPackages(0)) {
String name = info.packageName.toLowerCase();
if (name.startsWith("com.huawei.powergenie")) {
return "LocationManagerService";
} else if (name.startsWith("com.evenwell.powermonitor")) {
return "AudioIn";
}
}
return ctx.getPackageName();
}
} }

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

@@ -1,22 +0,0 @@
package org.briarproject.bramble.api.plugin;
/**
* Calculates polling intervals for transport plugins that use backoff.
*/
public interface Backoff {
/**
* Returns the current polling interval.
*/
int getPollingInterval();
/**
* Increments the backoff counter.
*/
void increment();
/**
* Resets the backoff counter.
*/
void reset();
}

View File

@@ -1,7 +0,0 @@
package org.briarproject.bramble.api.plugin;
public interface BackoffFactory {
Backoff createBackoff(int minInterval, int maxInterval,
double base);
}

View File

@@ -6,8 +6,10 @@ public interface BluetoothConstants {
int UUID_BYTES = 16; int UUID_BYTES = 16;
// Transport properties
String PROP_ADDRESS = "address"; String PROP_ADDRESS = "address";
String PROP_UUID = "uuid"; String PROP_UUID = "uuid";
String PREF_BT_ENABLE = "enable"; // Default value for PREF_PLUGIN_ENABLE
boolean DEFAULT_PREF_PLUGIN_ENABLE = false;
} }

View File

@@ -7,7 +7,12 @@ public interface LanTcpConstants {
// Transport properties (shared with contacts) // Transport properties (shared with contacts)
String PROP_IP_PORTS = "ipPorts"; String PROP_IP_PORTS = "ipPorts";
String PROP_PORT = "port"; String PROP_PORT = "port";
String PROP_IPV6 = "ipv6";
// A local setting // Local settings (not shared with contacts)
String PREF_LAN_IP_PORTS = "ipPorts"; String PREF_LAN_IP_PORTS = "ipPorts";
String PREF_IPV6 = "ipv6";
// Default value for PREF_PLUGIN_ENABLE
boolean DEFAULT_PREF_PLUGIN_ENABLE = true;
} }

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.Collection;
@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

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

@@ -1,9 +1,12 @@
package org.briarproject.bramble.api.plugin; package org.briarproject.bramble.api.plugin;
import static java.util.concurrent.TimeUnit.DAYS;
public interface TorConstants { public interface TorConstants {
TransportId ID = new TransportId("org.briarproject.bramble.tor"); TransportId ID = new TransportId("org.briarproject.bramble.tor");
// Transport properties
String PROP_ONION_V2 = "onion"; String PROP_ONION_V2 = "onion";
String PROP_ONION_V3 = "onion3"; String PROP_ONION_V3 = "onion3";
@@ -13,14 +16,45 @@ public interface TorConstants {
int CONNECT_TO_PROXY_TIMEOUT = 5000; // Milliseconds int CONNECT_TO_PROXY_TIMEOUT = 5000; // Milliseconds
int EXTRA_SOCKET_TIMEOUT = 30000; // Milliseconds int EXTRA_SOCKET_TIMEOUT = 30000; // Milliseconds
// Local settings (not shared with contacts)
String PREF_TOR_NETWORK = "network2"; String PREF_TOR_NETWORK = "network2";
String PREF_TOR_PORT = "port"; String PREF_TOR_PORT = "port";
String PREF_TOR_MOBILE = "useMobileData"; String PREF_TOR_MOBILE = "useMobileData";
String PREF_TOR_ONLY_WHEN_CHARGING = "onlyWhenCharging"; String PREF_TOR_ONLY_WHEN_CHARGING = "onlyWhenCharging";
String HS_PRIVATE_KEY_V2 = "onionPrivKey";
String HS_PRIVATE_KEY_V3 = "onionPrivKey3";
String HS_V3_CREATED = "onionPrivKey3Created";
/**
* How long to publish a v3 hidden service before retiring the v2 service.
*/
long V3_MIGRATION_PERIOD_MS = DAYS.toMillis(180);
// Values for PREF_TOR_NETWORK
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;
// Default values for local settings
boolean DEFAULT_PREF_PLUGIN_ENABLE = true;
int DEFAULT_PREF_TOR_NETWORK = PREF_TOR_NETWORK_AUTOMATIC;
boolean DEFAULT_PREF_TOR_MOBILE = true;
boolean DEFAULT_PREF_TOR_ONLY_WHEN_CHARGING = false;
/**
* 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

@@ -4,4 +4,7 @@ public interface WanTcpConstants {
TransportId ID = new TransportId("org.briarproject.bramble.wan"); TransportId ID = new TransportId("org.briarproject.bramble.wan");
// Default value for PREF_PLUGIN_ENABLE
boolean DEFAULT_PREF_PLUGIN_ENABLE = false;
} }

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

@@ -2,13 +2,17 @@ package org.briarproject.bramble.util;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.net.Inet6Address; import java.net.Inet4Address;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.SocketAddress; import java.net.SocketAddress;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
import static org.briarproject.bramble.util.StringUtils.isValidMac;
import static org.briarproject.bramble.util.StringUtils.toHexString;
@NotNullByDefault @NotNullByDefault
public class PrivacyUtils { public class PrivacyUtils {
@@ -19,7 +23,7 @@ public class PrivacyUtils {
@Nullable @Nullable
public static String scrubMacAddress(@Nullable String address) { public static String scrubMacAddress(@Nullable String address) {
if (address == null || address.length() == 0) return null; if (isNullOrEmpty(address) || !isValidMac(address)) return address;
// this is a fake address we need to know about // this is a fake address we need to know about
if (address.equals("02:00:00:00:00:00")) return address; if (address.equals("02:00:00:00:00:00")) return address;
// keep first and last octet of MAC address // keep first and last octet of MAC address
@@ -27,39 +31,37 @@ public class PrivacyUtils {
+ address.substring(14, 17); + address.substring(14, 17);
} }
@Nullable
public static String scrubInetAddress(InetAddress address) { public static String scrubInetAddress(InetAddress address) {
// don't scrub link and site local addresses if (address instanceof Inet4Address) {
if (address.isLinkLocalAddress() || address.isSiteLocalAddress()) // Don't scrub local IPv4 addresses
return address.toString(); if (address.isLoopbackAddress() || address.isLinkLocalAddress() ||
// completely scrub IPv6 addresses address.isSiteLocalAddress()) {
if (address instanceof Inet6Address) return "[scrubbed]"; return address.getHostAddress();
// keep first and last octet of IPv4 addresses }
return scrubInetAddress(address.toString()); // Keep first and last octet of non-local IPv4 addresses
return scrubIpv4Address(address.getAddress());
} else {
// Keep first and last octet of IPv6 addresses
return scrubIpv6Address(address.getAddress());
}
} }
@Nullable private static String scrubIpv4Address(byte[] ipv4) {
public static String scrubInetAddress(@Nullable String address) { return (ipv4[0] & 0xFF) + ".[scrubbed]." + (ipv4[3] & 0xFF);
if (address == null) return null; }
int firstDot = address.indexOf("."); private static String scrubIpv6Address(byte[] ipv6) {
if (firstDot == -1) return "[scrubbed]"; String hex = toHexString(ipv6).toLowerCase();
String prefix = address.substring(0, firstDot + 1); return hex.substring(0, 2) + "[scrubbed]" + hex.substring(30);
int lastDot = address.lastIndexOf(".");
String suffix = address.substring(lastDot, address.length());
return prefix + "[scrubbed]" + suffix;
} }
@Nullable
public static String scrubSocketAddress(InetSocketAddress address) { public static String scrubSocketAddress(InetSocketAddress address) {
InetAddress inetAddress = address.getAddress(); return scrubInetAddress(address.getAddress());
return scrubInetAddress(inetAddress);
} }
@Nullable
public static String scrubSocketAddress(SocketAddress address) { public static String scrubSocketAddress(SocketAddress address) {
if (address instanceof InetSocketAddress) if (address instanceof InetSocketAddress)
return scrubSocketAddress((InetSocketAddress) address); return scrubSocketAddress((InetSocketAddress) address);
return scrubInetAddress(address.toString()); return "[scrubbed]";
} }
} }

View File

@@ -63,18 +63,12 @@ class ConnectionRegistryImpl implements ConnectionRegistry {
@Override @Override
public void registerIncomingConnection(ContactId c, TransportId t, public void registerIncomingConnection(ContactId c, TransportId t,
InterruptibleConnection conn) { InterruptibleConnection conn) {
if (LOG.isLoggable(INFO)) {
LOG.info("Incoming connection registered: " + t);
}
registerConnection(c, t, conn, true); registerConnection(c, t, conn, true);
} }
@Override @Override
public void registerOutgoingConnection(ContactId c, TransportId t, public void registerOutgoingConnection(ContactId c, TransportId t,
InterruptibleConnection conn, Priority priority) { InterruptibleConnection conn, Priority priority) {
if (LOG.isLoggable(INFO)) {
LOG.info("Outgoing connection registered: " + t);
}
registerConnection(c, t, conn, false); registerConnection(c, t, conn, false);
setPriority(c, t, conn, priority); setPriority(c, t, conn, priority);
} }

View File

@@ -1,18 +0,0 @@
package org.briarproject.bramble.plugin;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.Backoff;
import org.briarproject.bramble.api.plugin.BackoffFactory;
import javax.annotation.concurrent.Immutable;
@Immutable
@NotNullByDefault
class BackoffFactoryImpl implements BackoffFactory {
@Override
public Backoff createBackoff(int minInterval, int maxInterval,
double base) {
return new BackoffImpl(minInterval, maxInterval, base);
}
}

View File

@@ -1,42 +0,0 @@
package org.briarproject.bramble.plugin;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.Backoff;
import java.util.concurrent.atomic.AtomicInteger;
import javax.annotation.concurrent.ThreadSafe;
@ThreadSafe
@NotNullByDefault
class BackoffImpl implements Backoff {
private final int minInterval, maxInterval;
private final double base;
private final AtomicInteger backoff;
BackoffImpl(int minInterval, int maxInterval, double base) {
this.minInterval = minInterval;
this.maxInterval = maxInterval;
this.base = base;
backoff = new AtomicInteger(0);
}
@Override
public int getPollingInterval() {
double multiplier = Math.pow(base, backoff.get());
// Large or infinite values will be rounded to Integer.MAX_VALUE
int interval = (int) (minInterval * multiplier);
return Math.min(interval, maxInterval);
}
@Override
public void increment() {
backoff.incrementAndGet();
}
@Override
public void reset() {
backoff.set(0);
}
}

View File

@@ -8,6 +8,7 @@ import org.briarproject.bramble.api.lifecycle.Service;
import org.briarproject.bramble.api.lifecycle.ServiceException; 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.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
@Override + " to " + newState);
public void transportDisabled() { }
if (enabled.getAndSet(false)) eventBus.broadcast(new TransportStateEvent(id, newState));
eventBus.broadcast(new TransportDisabledEvent(id)); if (newState == ACTIVE) {
eventBus.broadcast(new TransportActiveEvent(id));
} else if (oldState == ACTIVE) {
eventBus.broadcast(new TransportInactiveEvent(id));
}
}
} }
@Override @Override

View File

@@ -2,7 +2,6 @@ package org.briarproject.bramble.plugin;
import org.briarproject.bramble.api.event.EventBus; import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.lifecycle.LifecycleManager; import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.plugin.BackoffFactory;
import org.briarproject.bramble.api.plugin.PluginConfig; import org.briarproject.bramble.api.plugin.PluginConfig;
import org.briarproject.bramble.api.plugin.PluginManager; import org.briarproject.bramble.api.plugin.PluginManager;
@@ -22,11 +21,6 @@ public class PluginModule {
Poller poller; Poller poller;
} }
@Provides
BackoffFactory provideBackoffFactory() {
return new BackoffFactoryImpl();
}
@Provides @Provides
@Singleton @Singleton
PluginManager providePluginManager(LifecycleManager lifecycleManager, PluginManager providePluginManager(LifecycleManager lifecycleManager,

View File

@@ -19,9 +19,8 @@ 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.ConnectionClosedEvent; import org.briarproject.bramble.api.plugin.event.ConnectionClosedEvent;
import org.briarproject.bramble.api.plugin.event.ConnectionOpenedEvent; import org.briarproject.bramble.api.plugin.event.TransportActiveEvent;
import org.briarproject.bramble.api.plugin.event.TransportDisabledEvent; import org.briarproject.bramble.api.plugin.event.TransportInactiveEvent;
import org.briarproject.bramble.api.plugin.event.TransportEnabledEvent;
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;
@@ -96,23 +95,17 @@ class PollerImpl implements Poller, EventListener {
connectToContact(c.getContactId()); connectToContact(c.getContactId());
} else if (e instanceof ConnectionClosedEvent) { } else if (e instanceof ConnectionClosedEvent) {
ConnectionClosedEvent c = (ConnectionClosedEvent) e; ConnectionClosedEvent c = (ConnectionClosedEvent) e;
// Reschedule polling, the polling interval may have decreased
reschedule(c.getTransportId());
// If an outgoing connection failed, try to reconnect // If an outgoing connection failed, try to reconnect
if (!c.isIncoming() && c.isException()) { if (!c.isIncoming() && c.isException()) {
connectToContact(c.getContactId(), c.getTransportId()); connectToContact(c.getContactId(), c.getTransportId());
} }
} else if (e instanceof ConnectionOpenedEvent) { } else if (e instanceof TransportActiveEvent) {
ConnectionOpenedEvent c = (ConnectionOpenedEvent) e; TransportActiveEvent t = (TransportActiveEvent) e;
// Reschedule polling, the polling interval may have decreased // Poll the newly activated transport
reschedule(c.getTransportId());
} else if (e instanceof TransportEnabledEvent) {
TransportEnabledEvent t = (TransportEnabledEvent) e;
// Poll the newly enabled 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());
} }
} }
@@ -164,12 +157,6 @@ class PollerImpl implements Poller, EventListener {
}); });
} }
private void reschedule(TransportId t) {
Plugin p = pluginManager.getPlugin(t);
if (p != null && p.shouldPoll())
schedule(p, p.getPollingInterval(), false);
}
private void pollNow(TransportId t) { private void pollNow(TransportId t) {
Plugin p = pluginManager.getPlugin(t); Plugin p = pluginManager.getPlugin(t);
// Randomise next polling interval // Randomise next polling interval

View File

@@ -10,9 +10,10 @@ 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.ConnectionHandler; import org.briarproject.bramble.api.plugin.ConnectionHandler;
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;
@@ -37,16 +38,22 @@ 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.DEFAULT_PREF_PLUGIN_ENABLE;
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;
@@ -65,14 +72,13 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
private final Executor ioExecutor; private final Executor ioExecutor;
private final SecureRandom secureRandom; private final SecureRandom secureRandom;
private final Backoff backoff;
private final PluginCallback callback; private final PluginCallback callback;
private final int maxLatency, maxIdleTime; private final int maxLatency, maxIdleTime, pollingInterval;
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;
@@ -108,30 +114,34 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
BluetoothPlugin(BluetoothConnectionLimiter connectionLimiter, BluetoothPlugin(BluetoothConnectionLimiter connectionLimiter,
TimeoutMonitor timeoutMonitor, Executor ioExecutor, TimeoutMonitor timeoutMonitor, Executor ioExecutor,
SecureRandom secureRandom, Backoff backoff, SecureRandom secureRandom, PluginCallback callback, int maxLatency, int maxIdleTime,
PluginCallback callback, int maxLatency, int maxIdleTime) { int pollingInterval) {
this.connectionLimiter = connectionLimiter; this.connectionLimiter = connectionLimiter;
this.timeoutMonitor = timeoutMonitor; this.timeoutMonitor = timeoutMonitor;
this.ioExecutor = ioExecutor; this.ioExecutor = ioExecutor;
this.secureRandom = secureRandom; this.secureRandom = secureRandom;
this.backoff = backoff;
this.callback = callback; this.callback = callback;
this.maxLatency = maxLatency; this.maxLatency = maxLatency;
this.maxIdleTime = maxIdleTime; this.maxIdleTime = maxIdleTime;
this.pollingInterval = pollingInterval;
} }
void onAdapterEnabled() { void onAdapterEnabled() {
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
@@ -152,31 +162,25 @@ 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,
DEFAULT_PREF_PLUGIN_ENABLE);
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 {
@@ -185,14 +189,12 @@ 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; acceptContactConnections(ss);
backoff.reset();
callback.transportEnabled();
acceptContactConnections();
}); });
} }
@@ -221,35 +223,38 @@ 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"); LOG.info("Connection received");
connectionLimiter.connectionOpened(conn); connectionLimiter.connectionOpened(conn);
backoff.reset();
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
@@ -259,14 +264,13 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
@Override @Override
public int getPollingInterval() { public int getPollingInterval() {
return backoff.getPollingInterval(); return pollingInterval;
} }
@Override @Override
public void poll(Collection<Pair<TransportProperties, ConnectionHandler>> public void poll(Collection<Pair<TransportProperties, ConnectionHandler>>
properties) { properties) {
if (!isRunning() || !shouldAllowContactConnections()) return; if (getState() != ACTIVE) return;
backoff.increment();
for (Pair<TransportProperties, ConnectionHandler> p : properties) { for (Pair<TransportProperties, ConnectionHandler> p : properties) {
connect(p.getFirst(), p.getSecond()); connect(p.getFirst(), p.getSecond());
} }
@@ -279,10 +283,7 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
if (isNullOrEmpty(uuid)) return; if (isNullOrEmpty(uuid)) return;
ioExecutor.execute(() -> { ioExecutor.execute(() -> {
DuplexTransportConnection d = createConnection(p); DuplexTransportConnection d = createConnection(p);
if (d != null) { if (d != null) h.handleConnection(d);
backoff.reset();
h.handleConnection(d);
}
}); });
} }
@@ -319,7 +320,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;
@@ -337,7 +338,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);
@@ -349,7 +350,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;
} }
@@ -363,7 +364,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;
@@ -423,17 +424,18 @@ 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,
loadSettings(settings); DEFAULT_PREF_PLUGIN_ENABLE);
boolean isAllowed = shouldAllowContactConnections(); SS ss = state.setEnabledByUser(enabledByUser);
if (wasAllowed && !isAllowed) { State s = getState();
LOG.info("Contact connections disabled"); if (ss != null) {
tryToClose(socket); LOG.info("Disabled by user, closing server socket");
callback.transportDisabled(); tryToClose(ss);
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();
} }
@@ -461,4 +463,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

@@ -5,16 +5,15 @@ import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.keyagreement.KeyAgreementConnection; 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.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
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.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;
import java.net.Inet6Address;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.InterfaceAddress; import java.net.InterfaceAddress;
@@ -22,8 +21,9 @@ import java.net.ServerSocket;
import java.net.Socket; import java.net.Socket;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Deque;
import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Random;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.logging.Logger; import java.util.logging.Logger;
@@ -31,38 +31,49 @@ import javax.annotation.Nullable;
import static java.lang.Integer.parseInt; import static java.lang.Integer.parseInt;
import static java.util.Collections.addAll; import static java.util.Collections.addAll;
import static java.util.Collections.emptyList;
import static java.util.Collections.sort; import static java.util.Collections.sort;
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_LAN; import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.TRANSPORT_ID_LAN;
import static org.briarproject.bramble.api.plugin.LanTcpConstants.DEFAULT_PREF_PLUGIN_ENABLE;
import static org.briarproject.bramble.api.plugin.LanTcpConstants.ID; import static org.briarproject.bramble.api.plugin.LanTcpConstants.ID;
import static org.briarproject.bramble.api.plugin.LanTcpConstants.PREF_IPV6;
import static org.briarproject.bramble.api.plugin.LanTcpConstants.PREF_LAN_IP_PORTS; import static org.briarproject.bramble.api.plugin.LanTcpConstants.PREF_LAN_IP_PORTS;
import static org.briarproject.bramble.api.plugin.LanTcpConstants.PROP_IPV6;
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.api.properties.TransportPropertyConstants.MAX_PROPERTY_LENGTH;
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.fromHexString;
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;
import static org.briarproject.bramble.util.StringUtils.toHexString;
import static org.briarproject.bramble.util.StringUtils.utf8IsTooLong;
@NotNullByDefault @NotNullByDefault
class LanTcpPlugin extends TcpPlugin { class LanTcpPlugin extends TcpPlugin {
private static final Logger LOG = getLogger(LanTcpPlugin.class.getName()); private static final Logger LOG = getLogger(LanTcpPlugin.class.getName());
private static final int MAX_ADDRESSES = 4;
private static final String SEPARATOR = ","; private static final String SEPARATOR = ",";
/** /**
* The IP address of an Android device providing a wifi access point. * The IP address of an Android device providing a wifi access point.
* <p>
* Most devices use this address, but at least one device (Honor 8A) may
* use other addresses in the range 192.168.43.0/24.
*/ */
protected static final InetAddress WIFI_AP_ADDRESS; private static final InetAddress WIFI_AP_ADDRESS;
/** /**
* The IP address of an Android device providing a wifi direct * The IP address of an Android device providing a wifi direct
* legacy mode access point. * legacy mode access point.
*/ */
protected static final InetAddress WIFI_DIRECT_AP_ADDRESS; private static final InetAddress WIFI_DIRECT_AP_ADDRESS;
static { static {
try { try {
@@ -76,9 +87,9 @@ class LanTcpPlugin extends TcpPlugin {
} }
} }
LanTcpPlugin(Executor ioExecutor, Backoff backoff, PluginCallback callback, LanTcpPlugin(Executor ioExecutor, PluginCallback callback, int maxLatency,
int maxLatency, int maxIdleTime, int connectionTimeout) { int maxIdleTime, int pollingInterval, int connectionTimeout) {
super(ioExecutor, backoff, callback, maxLatency, maxIdleTime, super(ioExecutor, callback, maxLatency, maxIdleTime, pollingInterval,
connectionTimeout); connectionTimeout);
} }
@@ -91,29 +102,36 @@ 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,
DEFAULT_PREF_PLUGIN_ENABLE));
bind(); bind();
} }
protected void initialisePortProperty() { protected void initialisePortProperty() {
TransportProperties p = callback.getLocalProperties(); TransportProperties p = callback.getLocalProperties();
if (isNullOrEmpty(p.get(PROP_PORT))) { if (isNullOrEmpty(p.get(PROP_PORT))) {
int port = new Random().nextInt(32768) + 32768; int port = chooseEphemeralPort();
p.put(PROP_PORT, String.valueOf(port)); p.put(PROP_PORT, String.valueOf(port));
callback.mergeLocalProperties(p); callback.mergeLocalProperties(p);
} }
} }
@Override @Override
protected List<InetSocketAddress> getLocalSocketAddresses() { protected boolean isEnabledByDefault() {
return DEFAULT_PREF_PLUGIN_ENABLE;
}
@Override
protected List<InetSocketAddress> getLocalSocketAddresses(boolean ipv4) {
TransportProperties p = callback.getLocalProperties(); TransportProperties p = callback.getLocalProperties();
int preferredPort = parsePortProperty(p.get(PROP_PORT)); int preferredPort = parsePortProperty(p.get(PROP_PORT));
String oldIpPorts = p.get(PROP_IP_PORTS); String oldIpPorts = p.get(PROP_IP_PORTS);
List<InetSocketAddress> olds = parseSocketAddresses(oldIpPorts); List<InetSocketAddress> olds = parseIpv4SocketAddresses(oldIpPorts);
List<InetSocketAddress> locals = new ArrayList<>(); List<InetSocketAddress> locals = new ArrayList<>();
List<InetSocketAddress> fallbacks = new ArrayList<>(); List<InetSocketAddress> fallbacks = new ArrayList<>();
for (InetAddress local : getUsableLocalInetAddresses()) { for (InetAddress local : getUsableLocalInetAddresses(ipv4)) {
// If we've used this address before, try to use the same port // If we've used this address before, try to use the same port
int port = preferredPort; int port = preferredPort;
for (InetSocketAddress old : olds) { for (InetSocketAddress old : olds) {
@@ -139,17 +157,17 @@ class LanTcpPlugin extends TcpPlugin {
} }
} }
private List<InetSocketAddress> parseSocketAddresses(String ipPorts) { private List<InetSocketAddress> parseIpv4SocketAddresses(String ipPorts) {
List<InetSocketAddress> addresses = new ArrayList<>(); List<InetSocketAddress> addresses = new ArrayList<>();
if (isNullOrEmpty(ipPorts)) return addresses; if (isNullOrEmpty(ipPorts)) return addresses;
for (String ipPort : ipPorts.split(SEPARATOR)) { for (String ipPort : ipPorts.split(SEPARATOR)) {
InetSocketAddress a = parseSocketAddress(ipPort); InetSocketAddress a = parseIpv4SocketAddress(ipPort);
if (a != null) addresses.add(a); if (a != null) addresses.add(a);
} }
return addresses; return addresses;
} }
protected List<InetAddress> getUsableLocalInetAddresses() { protected List<InetAddress> getUsableLocalInetAddresses(boolean ipv4) {
List<InterfaceAddress> ifAddrs = List<InterfaceAddress> ifAddrs =
new ArrayList<>(getLocalInterfaceAddresses()); new ArrayList<>(getLocalInterfaceAddresses());
// Prefer longer network prefixes // Prefer longer network prefixes
@@ -158,50 +176,74 @@ class LanTcpPlugin extends TcpPlugin {
List<InetAddress> addrs = new ArrayList<>(); List<InetAddress> addrs = new ArrayList<>();
for (InterfaceAddress ifAddr : ifAddrs) { for (InterfaceAddress ifAddr : ifAddrs) {
InetAddress addr = ifAddr.getAddress(); InetAddress addr = ifAddr.getAddress();
if (isAcceptableAddress(addr)) addrs.add(addr); if (isAcceptableAddress(addr, ipv4)) addrs.add(addr);
} }
return addrs; return addrs;
} }
@Override @Override
protected void setLocalSocketAddress(InetSocketAddress a) { protected void setLocalSocketAddress(InetSocketAddress a, boolean ipv4) {
if (ipv4) setLocalIpv4SocketAddress(a);
else setLocalIpv6SocketAddress(a);
}
private void setLocalIpv4SocketAddress(InetSocketAddress a) {
String ipPort = getIpPortString(a); String ipPort = getIpPortString(a);
updateRecentAddresses(PREF_LAN_IP_PORTS, PROP_IP_PORTS, ipPort);
}
private void setLocalIpv6SocketAddress(InetSocketAddress a) {
String hex = toHexString(a.getAddress().getAddress());
updateRecentAddresses(PREF_IPV6, PROP_IPV6, hex);
}
private void updateRecentAddresses(String settingKey, String propertyKey,
String item) {
// Get the list of recently used addresses // Get the list of recently used addresses
String setting = callback.getSettings().get(PREF_LAN_IP_PORTS); String setting = callback.getSettings().get(settingKey);
List<String> recent = new ArrayList<>(); Deque<String> recent = new LinkedList<>();
if (!isNullOrEmpty(setting)) if (!isNullOrEmpty(setting)) {
addAll(recent, setting.split(SEPARATOR)); addAll(recent, setting.split(SEPARATOR));
// Is the address already in the list? }
if (recent.remove(ipPort)) { if (recent.remove(item)) {
// Move the address to the start of the list // Move the item to the start of the list
recent.add(0, ipPort); recent.addFirst(item);
setting = join(recent, SEPARATOR); setting = join(recent, SEPARATOR);
} else { } else {
// Add the address to the start of the list // Add the item to the start of the list
recent.add(0, ipPort); recent.addFirst(item);
// Drop the least recently used address if the list is full // Drop items from the end of the list if it's too long to encode
if (recent.size() > MAX_ADDRESSES)
recent = recent.subList(0, MAX_ADDRESSES);
setting = join(recent, SEPARATOR); setting = join(recent, SEPARATOR);
while (utf8IsTooLong(setting, MAX_PROPERTY_LENGTH)) {
recent.removeLast();
setting = join(recent, SEPARATOR);
}
// Update the list of addresses shared with contacts // Update the list of addresses shared with contacts
List<String> shared = new ArrayList<>(recent);
sort(shared);
String property = join(shared, SEPARATOR);
TransportProperties properties = new TransportProperties(); TransportProperties properties = new TransportProperties();
properties.put(PROP_IP_PORTS, property); properties.put(propertyKey, setting);
callback.mergeLocalProperties(properties); callback.mergeLocalProperties(properties);
} }
// Save the setting // Save the setting
Settings settings = new Settings(); Settings settings = new Settings();
settings.put(PREF_LAN_IP_PORTS, setting); settings.put(settingKey, setting);
callback.mergeSettings(settings); callback.mergeSettings(settings);
} }
protected boolean isIpv6LinkLocalAddress(InetAddress a) {
return a instanceof Inet6Address && a.isLinkLocalAddress();
}
@Override @Override
protected List<InetSocketAddress> getRemoteSocketAddresses( protected List<InetSocketAddress> getRemoteSocketAddresses(
TransportProperties p, boolean ipv4) {
if (ipv4) return getRemoteIpv4SocketAddresses(p);
else return getRemoteIpv6SocketAddresses(p);
}
private List<InetSocketAddress> getRemoteIpv4SocketAddresses(
TransportProperties p) { TransportProperties p) {
String ipPorts = p.get(PROP_IP_PORTS); String ipPorts = p.get(PROP_IP_PORTS);
List<InetSocketAddress> remotes = parseSocketAddresses(ipPorts); List<InetSocketAddress> remotes = parseIpv4SocketAddresses(ipPorts);
int port = parsePortProperty(p.get(PROP_PORT)); int port = parsePortProperty(p.get(PROP_PORT));
// If the contact has a preferred port, we can guess their IP:port when // If the contact has a preferred port, we can guess their IP:port when
// they're providing a wifi access point // they're providing a wifi access point
@@ -216,20 +258,52 @@ class LanTcpPlugin extends TcpPlugin {
return remotes; return remotes;
} }
private boolean isAcceptableAddress(InetAddress a) { private List<InetSocketAddress> getRemoteIpv6SocketAddresses(
// Accept link-local and site-local IPv4 addresses TransportProperties p) {
boolean ipv4 = a instanceof Inet4Address; List<InetAddress> addrs = parseIpv6Addresses(p.get(PROP_IPV6));
boolean loop = a.isLoopbackAddress(); int port = parsePortProperty(p.get(PROP_PORT));
boolean link = a.isLinkLocalAddress(); if (addrs.isEmpty() || port == 0) return emptyList();
boolean site = a.isSiteLocalAddress(); List<InetSocketAddress> remotes = new ArrayList<>();
return ipv4 && !loop && (link || site); for (InetAddress addr : addrs) {
remotes.add(new InetSocketAddress(addr, port));
}
return remotes;
}
private List<InetAddress> parseIpv6Addresses(String property) {
if (isNullOrEmpty(property)) return emptyList();
try {
List<InetAddress> addrs = new ArrayList<>();
for (String hex : property.split(SEPARATOR)) {
byte[] ip = fromHexString(hex);
if (ip.length == 16) addrs.add(InetAddress.getByAddress(ip));
}
return addrs;
} catch (IllegalArgumentException | UnknownHostException e) {
return emptyList();
}
}
private boolean isAcceptableAddress(InetAddress a, boolean ipv4) {
if (ipv4) {
// Accept link-local and site-local IPv4 addresses
boolean isIpv4 = a instanceof Inet4Address;
boolean link = a.isLinkLocalAddress();
boolean site = a.isSiteLocalAddress();
return isIpv4 && (link || site);
} else {
// Accept link-local IPv6 addresses
return isIpv6LinkLocalAddress(a);
}
} }
@Override @Override
protected boolean isConnectable(InterfaceAddress local, protected boolean isConnectable(InterfaceAddress local,
InetSocketAddress remote) { InetSocketAddress remote) {
if (remote.getPort() == 0) return false; if (remote.getPort() == 0) return false;
if (!isAcceptableAddress(remote.getAddress())) return false; InetAddress remoteAddress = remote.getAddress();
boolean ipv4 = local.getAddress() instanceof Inet4Address;
if (!isAcceptableAddress(remoteAddress, ipv4)) return false;
// Try to determine whether the address is on the same LAN as us // Try to determine whether the address is on the same LAN as us
byte[] localIp = local.getAddress().getAddress(); byte[] localIp = local.getAddress().getAddress();
byte[] remoteIp = remote.getAddress().getAddress(); byte[] remoteIp = remote.getAddress().getAddress();
@@ -271,10 +345,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;
} }
@@ -287,11 +361,18 @@ class LanTcpPlugin extends TcpPlugin {
return new LanKeyAgreementListener(descriptor, ss); return new LanKeyAgreementListener(descriptor, ss);
} }
private List<InetSocketAddress> getLocalSocketAddresses() {
List<InetSocketAddress> addrs = new ArrayList<>();
addrs.addAll(getLocalSocketAddresses(true));
addrs.addAll(getLocalSocketAddresses(false));
return addrs;
}
@Override @Override
public DuplexTransportConnection createKeyAgreementConnection( public DuplexTransportConnection createKeyAgreementConnection(
byte[] commitment, BdfList descriptor) { byte[] commitment, BdfList descriptor) {
if (!isRunning()) return null; ServerSocket ss = state.getServerSocket(true);
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 +444,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,8 +1,7 @@
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.BackoffFactory;
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.DuplexPlugin; import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
@@ -12,26 +11,25 @@ import java.util.concurrent.Executor;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
import static java.util.concurrent.TimeUnit.MINUTES;
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.briarproject.bramble.api.plugin.LanTcpConstants.ID; import static org.briarproject.bramble.api.plugin.LanTcpConstants.ID;
@Immutable @Immutable
@NotNullByDefault @NotNullByDefault
public class LanTcpPluginFactory implements DuplexPluginFactory { public class LanTcpPluginFactory implements DuplexPluginFactory {
private static final int MAX_LATENCY = 30_000; // 30 seconds private static final int MAX_LATENCY = (int) SECONDS.toMillis(30);
private static final int MAX_IDLE_TIME = 30_000; // 30 seconds private static final int MAX_IDLE_TIME = (int) SECONDS.toMillis(30);
private static final int CONNECTION_TIMEOUT = 3_000; // 3 seconds private static final int POLLING_INTERVAL = (int) MINUTES.toMillis(1);
private static final int MIN_POLLING_INTERVAL = 60_000; // 1 minute private static final int CONNECTION_TIMEOUT = (int) SECONDS.toMillis(3);
private static final int MAX_POLLING_INTERVAL = 600_000; // 10 mins
private static final double BACKOFF_BASE = 1.2;
private final Executor ioExecutor; private final Executor ioExecutor;
private final BackoffFactory backoffFactory; private final EventBus eventBus;
public LanTcpPluginFactory(Executor ioExecutor, public LanTcpPluginFactory(Executor ioExecutor, EventBus eventBus) {
BackoffFactory backoffFactory) {
this.ioExecutor = ioExecutor; this.ioExecutor = ioExecutor;
this.backoffFactory = backoffFactory; this.eventBus = eventBus;
} }
@Override @Override
@@ -46,9 +44,10 @@ public class LanTcpPluginFactory implements DuplexPluginFactory {
@Override @Override
public DuplexPlugin createPlugin(PluginCallback callback) { public DuplexPlugin createPlugin(PluginCallback callback) {
Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL, LanTcpPlugin plugin = new LanTcpPlugin(ioExecutor, callback,
MAX_POLLING_INTERVAL, BACKOFF_BASE); MAX_LATENCY, MAX_IDLE_TIME, POLLING_INTERVAL,
return new LanTcpPlugin(ioExecutor, backoff, callback, MAX_LATENCY, CONNECTION_TIMEOUT);
MAX_IDLE_TIME, CONNECTION_TIMEOUT); eventBus.addListener(plugin);
return plugin;
} }
} }

View File

@@ -54,11 +54,13 @@ class PortMapperImpl implements PortMapper {
shutdownManager.addShutdownHook(() -> deleteMapping(port)); shutdownManager.addShutdownHook(() -> deleteMapping(port));
} }
String externalString = gateway.getExternalIPAddress(); String externalString = gateway.getExternalIPAddress();
if (LOG.isLoggable(INFO)) if (externalString == null) {
LOG.info( LOG.info("External address not available");
"External address " + scrubInetAddress(externalString)); } else {
if (externalString != null)
external = InetAddress.getByName(externalString); external = InetAddress.getByName(externalString);
if (LOG.isLoggable(INFO))
LOG.info("External address " + scrubInetAddress(external));
}
} catch (IOException | SAXException e) { } catch (IOException | SAXException e) {
logException(LOG, WARNING, e); logException(LOG, WARNING, e);
} }

View File

@@ -3,10 +3,13 @@ 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.data.BdfList;
import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.event.EventListener;
import org.briarproject.bramble.api.keyagreement.KeyAgreementListener; import org.briarproject.bramble.api.keyagreement.KeyAgreementListener;
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.ConnectionHandler; import org.briarproject.bramble.api.plugin.ConnectionHandler;
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;
@@ -14,7 +17,8 @@ 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());
@@ -55,33 +66,32 @@ abstract class TcpPlugin implements DuplexPlugin {
Pattern.compile("^\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}$"); Pattern.compile("^\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}$");
protected final Executor ioExecutor, bindExecutor; protected final Executor ioExecutor, bindExecutor;
protected final Backoff backoff;
protected final PluginCallback callback; protected final PluginCallback callback;
protected final int maxLatency, maxIdleTime; protected final int maxLatency, maxIdleTime, pollingInterval;
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,
* in order of preference. At most one of the addresses will be bound. * in order of preference. At most one of the addresses will be bound.
*/ */
protected abstract List<InetSocketAddress> getLocalSocketAddresses(); protected abstract List<InetSocketAddress> getLocalSocketAddresses(
boolean ipv4);
/** /**
* Adds the address on which the plugin is listening to the transport * Adds the address on which the plugin is listening to the transport
* properties. * properties.
*/ */
protected abstract void setLocalSocketAddress(InetSocketAddress a); protected abstract void setLocalSocketAddress(InetSocketAddress a,
boolean ipv4);
/** /**
* Returns zero or more socket addresses for connecting to a contact with * Returns zero or more socket addresses for connecting to a contact with
* the given transport properties. * the given transport properties.
*/ */
protected abstract List<InetSocketAddress> getRemoteSocketAddresses( protected abstract List<InetSocketAddress> getRemoteSocketAddresses(
TransportProperties p); TransportProperties p, boolean ipv4);
/** /**
* Returns true if connections to the given address can be attempted. * Returns true if connections to the given address can be attempted.
@@ -90,13 +100,18 @@ abstract class TcpPlugin implements DuplexPlugin {
protected abstract boolean isConnectable(InterfaceAddress local, protected abstract boolean isConnectable(InterfaceAddress local,
InetSocketAddress remote); InetSocketAddress remote);
TcpPlugin(Executor ioExecutor, Backoff backoff, PluginCallback callback, /**
int maxLatency, int maxIdleTime, int connectionTimeout) { * Returns true if the plugin is enabled by default.
*/
protected abstract boolean isEnabledByDefault();
TcpPlugin(Executor ioExecutor, PluginCallback callback, int maxLatency,
int maxIdleTime, int pollingInterval, int connectionTimeout) {
this.ioExecutor = ioExecutor; this.ioExecutor = ioExecutor;
this.backoff = backoff;
this.callback = callback; this.callback = callback;
this.maxLatency = maxLatency; this.maxLatency = maxLatency;
this.maxIdleTime = maxIdleTime; this.maxIdleTime = maxIdleTime;
this.pollingInterval = pollingInterval;
this.connectionTimeout = connectionTimeout; this.connectionTimeout = connectionTimeout;
if (maxIdleTime > Integer.MAX_VALUE / 2) if (maxIdleTime > Integer.MAX_VALUE / 2)
socketTimeout = Integer.MAX_VALUE; socketTimeout = Integer.MAX_VALUE;
@@ -118,49 +133,49 @@ 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, isEnabledByDefault()));
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; bind(true);
ServerSocket ss = null; bind(false);
for (InetSocketAddress addr : getLocalSocketAddresses()) {
try {
ss = new ServerSocket();
ss.bind(addr);
break;
} catch (IOException e) {
if (LOG.isLoggable(INFO))
LOG.info("Failed to bind " + scrubSocketAddress(addr));
tryToClose(ss);
}
}
if (ss == null || !ss.isBound()) {
LOG.info("Could not bind server socket");
return;
}
if (!running) {
tryToClose(ss);
return;
}
socket = ss;
backoff.reset();
InetSocketAddress local =
(InetSocketAddress) ss.getLocalSocketAddress();
setLocalSocketAddress(local);
if (LOG.isLoggable(INFO))
LOG.info("Listening on " + scrubSocketAddress(local));
callback.transportEnabled();
acceptContactConnections();
}); });
} }
protected void tryToClose(@Nullable ServerSocket ss) { private void bind(boolean ipv4) {
IoUtils.tryToClose(ss, LOG, WARNING); ServerSocket ss = null;
callback.transportDisabled(); for (InetSocketAddress addr : getLocalSocketAddresses(ipv4)) {
try {
ss = new ServerSocket();
ss.bind(addr);
break;
} catch (IOException e) {
if (LOG.isLoggable(INFO))
LOG.info("Failed to bind " + scrubSocketAddress(addr));
tryToClose(ss, LOG, WARNING);
}
}
if (ss == null) {
LOG.info("Could not bind server socket");
return;
}
if (!state.setServerSocket(ss, ipv4)) {
LOG.info("Closing redundant server socket");
tryToClose(ss, LOG, WARNING);
return;
}
InetSocketAddress local =
(InetSocketAddress) ss.getLocalSocketAddress();
setLocalSocketAddress(local, ipv4);
if (LOG.isLoggable(INFO))
LOG.info("Listening on " + scrubSocketAddress(local));
ServerSocket finalSocket = ss;
ioExecutor.execute(() -> acceptContactConnections(finalSocket, ipv4));
} }
String getIpPortString(InetSocketAddress a) { String getIpPortString(InetSocketAddress a) {
@@ -170,34 +185,39 @@ abstract class TcpPlugin implements DuplexPlugin {
return addr + ":" + a.getPort(); return addr + ":" + a.getPort();
} }
private void acceptContactConnections() { private void acceptContactConnections(ServerSocket ss, boolean ipv4) {
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, ipv4);
return; return;
} }
if (LOG.isLoggable(INFO)) if (LOG.isLoggable(INFO)) {
LOG.info("Connection from " + LOG.info("Connection from " +
scrubSocketAddress(s.getRemoteSocketAddress())); scrubSocketAddress(s.getRemoteSocketAddress()));
backoff.reset(); }
callback.handleConnection(new TcpTransportConnection(this, s)); callback.handleConnection(new TcpTransportConnection(this, s));
} }
} }
@Override @Override
public void stop() { public void stop() {
running = false; for (ServerSocket ss : state.setStopped()) tryToClose(ss, LOG, WARNING);
tryToClose(socket);
} }
@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
@@ -207,14 +227,13 @@ abstract class TcpPlugin implements DuplexPlugin {
@Override @Override
public int getPollingInterval() { public int getPollingInterval() {
return backoff.getPollingInterval(); return pollingInterval;
} }
@Override @Override
public void poll(Collection<Pair<TransportProperties, ConnectionHandler>> public void poll(Collection<Pair<TransportProperties, ConnectionHandler>>
properties) { properties) {
if (!isRunning()) return; if (getState() != ACTIVE) return;
backoff.increment();
for (Pair<TransportProperties, ConnectionHandler> p : properties) { for (Pair<TransportProperties, ConnectionHandler> p : properties) {
connect(p.getFirst(), p.getSecond()); connect(p.getFirst(), p.getSecond());
} }
@@ -223,23 +242,28 @@ abstract class TcpPlugin implements DuplexPlugin {
private void connect(TransportProperties p, ConnectionHandler h) { private void connect(TransportProperties p, ConnectionHandler h) {
ioExecutor.execute(() -> { ioExecutor.execute(() -> {
DuplexTransportConnection d = createConnection(p); DuplexTransportConnection d = createConnection(p);
if (d != null) { if (d != null) h.handleConnection(d);
backoff.reset();
h.handleConnection(d);
}
}); });
} }
@Override @Override
public DuplexTransportConnection createConnection(TransportProperties p) { public DuplexTransportConnection createConnection(TransportProperties p) {
if (!isRunning()) return null; DuplexTransportConnection c = createConnection(p, true);
ServerSocket ss = socket; if (c != null) return c;
return createConnection(p, false);
}
@Nullable
private DuplexTransportConnection createConnection(TransportProperties p,
boolean ipv4) {
ServerSocket ss = state.getServerSocket(ipv4);
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");
return null; return null;
} }
for (InetSocketAddress remote : getRemoteSocketAddresses(p)) { for (InetSocketAddress remote : getRemoteSocketAddresses(p, ipv4)) {
// Don't try to connect to our own address // Don't try to connect to our own address
if (!canConnectToOwnAddress() && if (!canConnectToOwnAddress() &&
remote.getAddress().equals(ss.getInetAddress())) { remote.getAddress().equals(ss.getInetAddress())) {
@@ -264,9 +288,10 @@ abstract class TcpPlugin implements DuplexPlugin {
LOG.info("Connected to " + scrubSocketAddress(remote)); LOG.info("Connected to " + scrubSocketAddress(remote));
return new TcpTransportConnection(this, s); return new TcpTransportConnection(this, s);
} catch (IOException e) { } catch (IOException e) {
if (LOG.isLoggable(INFO)) if (LOG.isLoggable(INFO)) {
LOG.info("Could not connect to " + LOG.info("Could not connect to " +
scrubSocketAddress(remote)); scrubSocketAddress(remote));
}
} }
} }
return null; return null;
@@ -289,8 +314,12 @@ abstract class TcpPlugin implements DuplexPlugin {
return new Socket(); return new Socket();
} }
int chooseEphemeralPort() {
return 32768 + (int) (Math.random() * 32768);
}
@Nullable @Nullable
InetSocketAddress parseSocketAddress(String ipPort) { InetSocketAddress parseIpv4SocketAddress(String ipPort) {
if (isNullOrEmpty(ipPort)) return null; if (isNullOrEmpty(ipPort)) return null;
String[] split = ipPort.split(":"); String[] split = ipPort.split(":");
if (split.length != 2) return null; if (split.length != 2) return null;
@@ -301,14 +330,7 @@ abstract class TcpPlugin implements DuplexPlugin {
InetAddress a = InetAddress.getByName(addr); InetAddress a = InetAddress.getByName(addr);
int p = Integer.parseInt(port); int p = Integer.parseInt(port);
return new InetSocketAddress(a, p); return new InetSocketAddress(a, p);
} catch (UnknownHostException e) { } catch (UnknownHostException | NumberFormatException e) {
if (LOG.isLoggable(WARNING))
// not scrubbing to enable us to find the problem
LOG.warning("Invalid address: " + addr);
return null;
} catch (NumberFormatException e) {
if (LOG.isLoggable(WARNING))
LOG.warning("Invalid port: " + port);
return null; return null;
} }
} }
@@ -366,4 +388,114 @@ 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, isEnabledByDefault());
List<ServerSocket> toClose = state.setEnabledByUser(enabledByUser);
State s = getState();
if (!toClose.isEmpty()) {
LOG.info("Disabled by user, closing server sockets");
for (ServerSocket ss : toClose) tryToClose(ss, LOG, WARNING);
} else if (s == INACTIVE) {
LOG.info("Enabled by user, opening server sockets");
bind();
}
}
@ThreadSafe
@NotNullByDefault
protected class PluginState {
@GuardedBy("this")
private boolean started = false, stopped = false, enabledByUser = false;
@GuardedBy("this")
@Nullable
private ServerSocket serverSocketV4 = null, serverSocketV6 = null;
synchronized void setStarted(boolean enabledByUser) {
started = true;
this.enabledByUser = enabledByUser;
callback.pluginStateChanged(getState());
}
synchronized List<ServerSocket> setStopped() {
stopped = true;
List<ServerSocket> toClose = clearServerSockets();
callback.pluginStateChanged(getState());
return toClose;
}
@GuardedBy("this")
private List<ServerSocket> clearServerSockets() {
List<ServerSocket> toClose = new ArrayList<>(2);
if (serverSocketV4 != null) {
toClose.add(serverSocketV4);
serverSocketV4 = null;
}
if (serverSocketV6 != null) {
toClose.add(serverSocketV6);
serverSocketV6 = null;
}
return toClose;
}
synchronized List<ServerSocket> setEnabledByUser(
boolean enabledByUser) {
this.enabledByUser = enabledByUser;
List<ServerSocket> toClose = enabledByUser
? emptyList() : clearServerSockets();
callback.pluginStateChanged(getState());
return toClose;
}
@Nullable
synchronized ServerSocket getServerSocket(boolean ipv4) {
return ipv4 ? serverSocketV4 : serverSocketV6;
}
synchronized boolean setServerSocket(ServerSocket ss, boolean ipv4) {
if (stopped) return false;
if (ipv4) {
if (serverSocketV4 != null) return false;
serverSocketV4 = ss;
} else {
if (serverSocketV6 != null) return false;
serverSocketV6 = ss;
}
callback.pluginStateChanged(getState());
return true;
}
synchronized void clearServerSocket(ServerSocket ss, boolean ipv4) {
if (ipv4) {
if (serverSocketV4 == ss) serverSocketV4 = null;
} else {
if (serverSocketV6 == ss) serverSocketV6 = null;
}
callback.pluginStateChanged(getState());
}
synchronized State getState() {
if (!started || stopped) return STARTING_STOPPING;
if (!enabledByUser) return DISABLED;
if (serverSocketV4 != null || serverSocketV6 != null) return ACTIVE;
return INACTIVE;
}
synchronized int getReasonsDisabled() {
return getState() == DISABLED ? REASON_USER : 0;
}
}
} }

View File

@@ -2,7 +2,6 @@ package org.briarproject.bramble.plugin.tcp;
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.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.properties.TransportProperties; import org.briarproject.bramble.api.properties.TransportProperties;
@@ -17,6 +16,7 @@ import java.util.concurrent.Executor;
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 org.briarproject.bramble.api.plugin.WanTcpConstants.DEFAULT_PREF_PLUGIN_ENABLE;
import static org.briarproject.bramble.api.plugin.WanTcpConstants.ID; import static org.briarproject.bramble.api.plugin.WanTcpConstants.ID;
@MethodsNotNullByDefault @MethodsNotNullByDefault
@@ -29,10 +29,10 @@ class WanTcpPlugin extends TcpPlugin {
private volatile MappingResult mappingResult; private volatile MappingResult mappingResult;
WanTcpPlugin(Executor ioExecutor, Backoff backoff, PortMapper portMapper, WanTcpPlugin(Executor ioExecutor, PortMapper portMapper,
PluginCallback callback, int maxLatency, int maxIdleTime, PluginCallback callback, int maxLatency, int maxIdleTime,
int connectionTimeout) { int pollingInterval, int connectionTimeout) {
super(ioExecutor, backoff, callback, maxLatency, maxIdleTime, super(ioExecutor, callback, maxLatency, maxIdleTime, pollingInterval,
connectionTimeout); connectionTimeout);
this.portMapper = portMapper; this.portMapper = portMapper;
} }
@@ -43,10 +43,16 @@ class WanTcpPlugin extends TcpPlugin {
} }
@Override @Override
protected List<InetSocketAddress> getLocalSocketAddresses() { protected boolean isEnabledByDefault() {
return DEFAULT_PREF_PLUGIN_ENABLE;
}
@Override
protected List<InetSocketAddress> getLocalSocketAddresses(boolean ipv4) {
if (!ipv4) return emptyList();
// Use the same address and port as last time if available // Use the same address and port as last time if available
TransportProperties p = callback.getLocalProperties(); TransportProperties p = callback.getLocalProperties();
InetSocketAddress old = parseSocketAddress(p.get(PROP_IP_PORT)); InetSocketAddress old = parseIpv4SocketAddress(p.get(PROP_IP_PORT));
List<InetSocketAddress> addrs = new LinkedList<>(); List<InetSocketAddress> addrs = new LinkedList<>();
for (InetAddress a : getLocalInetAddresses()) { for (InetAddress a : getLocalInetAddresses()) {
if (isAcceptableAddress(a)) { if (isAcceptableAddress(a)) {
@@ -76,14 +82,11 @@ class WanTcpPlugin extends TcpPlugin {
return ipv4 && !loop && !link && !site; return ipv4 && !loop && !link && !site;
} }
private int chooseEphemeralPort() {
return 32768 + (int) (Math.random() * 32768);
}
@Override @Override
protected List<InetSocketAddress> getRemoteSocketAddresses( protected List<InetSocketAddress> getRemoteSocketAddresses(
TransportProperties p) { TransportProperties p, boolean ipv4) {
InetSocketAddress parsed = parseSocketAddress(p.get(PROP_IP_PORT)); if (!ipv4) return emptyList();
InetSocketAddress parsed = parseIpv4SocketAddress(p.get(PROP_IP_PORT));
if (parsed == null) return emptyList(); if (parsed == null) return emptyList();
return singletonList(parsed); return singletonList(parsed);
} }
@@ -96,7 +99,8 @@ class WanTcpPlugin extends TcpPlugin {
} }
@Override @Override
protected void setLocalSocketAddress(InetSocketAddress a) { protected void setLocalSocketAddress(InetSocketAddress a, boolean ipv4) {
if (!ipv4) throw new AssertionError();
if (mappingResult != null && mappingResult.isUsable()) { if (mappingResult != null && mappingResult.isUsable()) {
// Advertise the external address to contacts // Advertise the external address to contacts
if (a.equals(mappingResult.getInternal())) { if (a.equals(mappingResult.getInternal())) {

View File

@@ -1,9 +1,8 @@
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.BackoffFactory;
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.DuplexPlugin; import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
@@ -13,27 +12,27 @@ import java.util.concurrent.Executor;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
import static java.util.concurrent.TimeUnit.MINUTES;
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.briarproject.bramble.api.plugin.WanTcpConstants.ID; import static org.briarproject.bramble.api.plugin.WanTcpConstants.ID;
@Immutable @Immutable
@NotNullByDefault @NotNullByDefault
public class WanTcpPluginFactory implements DuplexPluginFactory { public class WanTcpPluginFactory implements DuplexPluginFactory {
private static final int MAX_LATENCY = 30_000; // 30 seconds private static final int MAX_LATENCY = (int) SECONDS.toMillis(30);
private static final int MAX_IDLE_TIME = 30_000; // 30 seconds private static final int MAX_IDLE_TIME = (int) SECONDS.toMillis(30);
private static final int CONNECTION_TIMEOUT = 30_000; // 30 seconds private static final int POLLING_INTERVAL = (int) MINUTES.toMillis(1);
private static final int MIN_POLLING_INTERVAL = 60_000; // 1 minute private static final int CONNECTION_TIMEOUT = (int) SECONDS.toMillis(30);
private static final int MAX_POLLING_INTERVAL = 600_000; // 10 mins
private static final double BACKOFF_BASE = 1.2;
private final Executor ioExecutor; private final Executor ioExecutor;
private final BackoffFactory backoffFactory; private final EventBus eventBus;
private final ShutdownManager shutdownManager; private final ShutdownManager shutdownManager;
public WanTcpPluginFactory(Executor ioExecutor, public WanTcpPluginFactory(Executor ioExecutor, EventBus eventBus,
BackoffFactory backoffFactory, ShutdownManager shutdownManager) { ShutdownManager shutdownManager) {
this.ioExecutor = ioExecutor; this.ioExecutor = ioExecutor;
this.backoffFactory = backoffFactory; this.eventBus = eventBus;
this.shutdownManager = shutdownManager; this.shutdownManager = shutdownManager;
} }
@@ -49,10 +48,10 @@ public class WanTcpPluginFactory implements DuplexPluginFactory {
@Override @Override
public DuplexPlugin createPlugin(PluginCallback callback) { public DuplexPlugin createPlugin(PluginCallback callback) {
Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL, WanTcpPlugin plugin = new WanTcpPlugin(ioExecutor,
MAX_POLLING_INTERVAL, BACKOFF_BASE);
return new WanTcpPlugin(ioExecutor, backoff,
new PortMapperImpl(shutdownManager), callback, MAX_LATENCY, new PortMapperImpl(shutdownManager), callback, MAX_LATENCY,
MAX_IDLE_TIME, CONNECTION_TIMEOUT); MAX_IDLE_TIME, POLLING_INTERVAL, 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,8 +15,8 @@ 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.ConnectionHandler; import org.briarproject.bramble.api.plugin.ConnectionHandler;
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;
@@ -45,15 +45,20 @@ import java.net.ServerSocket;
import java.net.Socket; import java.net.Socket;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Scanner; import java.util.Scanner;
import java.util.Set;
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 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,7 +70,19 @@ 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.DEFAULT_PREF_PLUGIN_ENABLE;
import static org.briarproject.bramble.api.plugin.TorConstants.DEFAULT_PREF_TOR_MOBILE;
import static org.briarproject.bramble.api.plugin.TorConstants.DEFAULT_PREF_TOR_NETWORK;
import static org.briarproject.bramble.api.plugin.TorConstants.DEFAULT_PREF_TOR_ONLY_WHEN_CHARGING;
import static org.briarproject.bramble.api.plugin.TorConstants.HS_PRIVATE_KEY_V2;
import static org.briarproject.bramble.api.plugin.TorConstants.HS_PRIVATE_KEY_V3;
import static org.briarproject.bramble.api.plugin.TorConstants.HS_V3_CREATED;
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;
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_NETWORK; import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_NETWORK;
@@ -76,6 +93,10 @@ 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.api.plugin.TorConstants.V3_MIGRATION_PERIOD_MS;
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;
@@ -89,40 +110,69 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
private static final Logger LOG = getLogger(TorPlugin.class.getName()); private static final Logger LOG = getLogger(TorPlugin.class.getName());
/**
* Controller events we want to receive.
*/
private static final String[] EVENTS = { private static final String[] EVENTS = {
"CIRC", "ORCONN", "HS_DESC", "NOTICE", "WARN", "ERR" "CIRC", "ORCONN", "HS_DESC", "NOTICE", "WARN", "ERR"
}; };
/**
* Command-line argument to set our process as Tor's owning controller
* so Tor exits when our process dies.
*/
private static final String OWNER = "__OwningControllerProcess"; private static final String OWNER = "__OwningControllerProcess";
/**
* How long to wait for the authentication cookie file to be created.
*/
private static final int COOKIE_TIMEOUT_MS = 3000; private static final int COOKIE_TIMEOUT_MS = 3000;
/**
* How often to check whether the authentication cookie file has been
* created.
*/
private static final int COOKIE_POLLING_INTERVAL_MS = 200; private static final int COOKIE_POLLING_INTERVAL_MS = 200;
/**
* Regex for matching v2 hidden service names.
*/
private static final Pattern ONION_V2 = Pattern.compile("[a-z2-7]{16}"); private static final Pattern ONION_V2 = Pattern.compile("[a-z2-7]{16}");
/**
* Regex for matching v3 hidden service names.
*/
private static final Pattern ONION_V3 = Pattern.compile("[a-z2-7]{56}"); private static final Pattern ONION_V3 = Pattern.compile("[a-z2-7]{56}");
/**
* How many copies of our descriptor must be uploaded before we consider
* our hidden service to be reachable and switch to less frequent polling.
*/
private static final int MIN_DESCRIPTORS_UPLOADED = 5;
private final Executor ioExecutor, connectionStatusExecutor; private final Executor ioExecutor, connectionStatusExecutor;
private final NetworkManager networkManager; private final NetworkManager networkManager;
private final LocationUtils locationUtils; private final LocationUtils locationUtils;
private final SocketFactory torSocketFactory; private final SocketFactory torSocketFactory;
private final Clock clock; private final Clock clock;
private final BatteryManager batteryManager; private final BatteryManager batteryManager;
private final Backoff backoff;
private final TorRendezvousCrypto torRendezvousCrypto; private final TorRendezvousCrypto torRendezvousCrypto;
private final PluginCallback callback; private final PluginCallback callback;
private final String architecture; private final String architecture;
private final CircumventionProvider circumventionProvider; private final CircumventionProvider circumventionProvider;
private final ResourceProvider resourceProvider; private final ResourceProvider resourceProvider;
private final int maxLatency, maxIdleTime, socketTimeout; private final int maxLatency, maxIdleTime, socketTimeout;
private final int initialPollingInterval, stablePollingInterval;
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();
@@ -131,10 +181,11 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
LocationUtils locationUtils, SocketFactory torSocketFactory, LocationUtils locationUtils, SocketFactory torSocketFactory,
Clock clock, ResourceProvider resourceProvider, Clock clock, ResourceProvider resourceProvider,
CircumventionProvider circumventionProvider, CircumventionProvider circumventionProvider,
BatteryManager batteryManager, Backoff backoff, BatteryManager batteryManager,
TorRendezvousCrypto torRendezvousCrypto, TorRendezvousCrypto torRendezvousCrypto,
PluginCallback callback, String architecture, int maxLatency, PluginCallback callback, String architecture, int maxLatency,
int maxIdleTime, File torDirectory) { int maxIdleTime, int initialPollingInterval,
int stablePollingInterval, File torDirectory) {
this.ioExecutor = ioExecutor; this.ioExecutor = ioExecutor;
this.networkManager = networkManager; this.networkManager = networkManager;
this.locationUtils = locationUtils; this.locationUtils = locationUtils;
@@ -143,7 +194,6 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
this.resourceProvider = resourceProvider; this.resourceProvider = resourceProvider;
this.circumventionProvider = circumventionProvider; this.circumventionProvider = circumventionProvider;
this.batteryManager = batteryManager; this.batteryManager = batteryManager;
this.backoff = backoff;
this.torRendezvousCrypto = torRendezvousCrypto; this.torRendezvousCrypto = torRendezvousCrypto;
this.callback = callback; this.callback = callback;
this.architecture = architecture; this.architecture = architecture;
@@ -152,6 +202,8 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
if (maxIdleTime > Integer.MAX_VALUE / 2) if (maxIdleTime > Integer.MAX_VALUE / 2)
socketTimeout = Integer.MAX_VALUE; socketTimeout = Integer.MAX_VALUE;
else socketTimeout = maxIdleTime * 2; else socketTimeout = maxIdleTime * 2;
this.initialPollingInterval = initialPollingInterval;
this.stablePollingInterval = stablePollingInterval;
this.torDirectory = torDirectory; this.torDirectory = torDirectory;
torFile = new File(torDirectory, "tor"); torFile = new File(torDirectory, "tor");
geoIpFile = new File(torDirectory, "geoip"); geoIpFile = new File(torDirectory, "geoip");
@@ -159,7 +211,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 +241,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 +309,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 +316,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 +329,18 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
bind(); bind();
} }
// TODO: Remove after a reasonable migration period (added 2020-06-25)
private Settings migrateSettings(Settings settings) {
int network = settings.getInt(PREF_TOR_NETWORK,
DEFAULT_PREF_TOR_NETWORK);
if (network == PREF_TOR_NETWORK_NEVER) {
settings.putInt(PREF_TOR_NETWORK, DEFAULT_PREF_TOR_NETWORK);
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 +456,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();
@@ -405,23 +468,73 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
callback.mergeSettings(s); callback.mergeSettings(s);
// Create a hidden service if necessary // Create a hidden service if necessary
ioExecutor.execute(() -> publishHiddenService(localPort)); ioExecutor.execute(() -> publishHiddenService(localPort));
backoff.reset();
// Accept incoming hidden service connections from Tor // Accept incoming hidden service connections from Tor
acceptContactConnections(ss); acceptContactConnections(ss);
}); });
} }
private void publishHiddenService(String port) { private void publishHiddenService(String port) {
if (!running) return; if (!state.isTorRunning()) return;
LOG.info("Creating hidden service"); // TODO: Remove support for v2 hidden services after a reasonable
String privKey = settings.get(HS_PRIVKEY); // migration period (migration started 2020-06-30)
String privKey2 = settings.get(HS_PRIVATE_KEY_V2);
String privKey3 = settings.get(HS_PRIVATE_KEY_V3);
String v3Created = settings.get(HS_V3_CREATED);
// Publish a v2 hidden service if we've already created one, and
// either we've never published a v3 hidden service or we're still
// in the migration period since first publishing it
if (!isNullOrEmpty(privKey2)) {
long now = clock.currentTimeMillis();
long then = v3Created == null ? now : Long.valueOf(v3Created);
if (now - then >= V3_MIGRATION_PERIOD_MS) retireV2HiddenService();
else publishV2HiddenService(port, privKey2);
}
publishV3HiddenService(port, privKey3);
}
private void publishV2HiddenService(String port, String privKey) {
LOG.info("Creating v2 hidden service");
Map<Integer, String> portLines = singletonMap(80, "127.0.0.1:" + port);
Map<String, String> response;
try {
response = controlConnection.addOnion(privKey, portLines);
} catch (IOException e) {
logException(LOG, WARNING, e);
return;
}
if (!response.containsKey(HS_ADDRESS)) {
LOG.warning("Tor did not return a hidden service address");
return;
}
String onion2 = response.get(HS_ADDRESS);
if (LOG.isLoggable(INFO)) {
LOG.info("V2 hidden service " + scrubOnion(onion2));
}
// The hostname has already been published and the private key stored
}
private void retireV2HiddenService() {
LOG.info("Retiring v2 hidden service");
TransportProperties p = new TransportProperties();
p.put(PROP_ONION_V2, "");
callback.mergeLocalProperties(p);
Settings s = new Settings();
s.put(HS_PRIVATE_KEY_V2, "");
callback.mergeSettings(s);
}
private void publishV3HiddenService(String port, @Nullable String privKey) {
LOG.info("Creating v3 hidden service");
Map<Integer, String> portLines = singletonMap(80, "127.0.0.1:" + port); Map<Integer, String> portLines = singletonMap(80, "127.0.0.1:" + port);
Map<String, String> response; Map<String, String> response;
try { try {
// Use the control connection to set up the hidden service // Use the control connection to set up the hidden service
if (privKey == null) if (privKey == null) {
response = controlConnection.addOnion(portLines); response = controlConnection.addOnion("NEW:ED25519-V3",
else response = controlConnection.addOnion(privKey, portLines); portLines, null);
} else {
response = controlConnection.addOnion(privKey, portLines);
}
} catch (IOException e) { } catch (IOException e) {
logException(LOG, WARNING, e); logException(LOG, WARNING, e);
return; return;
@@ -435,42 +548,42 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
return; return;
} }
// Publish the hidden service's onion hostname in transport properties // Publish the hidden service's onion hostname in transport properties
String onion2 = response.get(HS_ADDRESS); String onion3 = response.get(HS_ADDRESS);
if (LOG.isLoggable(INFO)) if (LOG.isLoggable(INFO)) {
LOG.info("Hidden service " + scrubOnion(onion2)); LOG.info("V3 hidden service " + scrubOnion(onion3));
}
TransportProperties p = new TransportProperties(); TransportProperties p = new TransportProperties();
p.put(PROP_ONION_V2, onion2); p.put(PROP_ONION_V3, onion3);
callback.mergeLocalProperties(p); callback.mergeLocalProperties(p);
if (privKey == null) { if (privKey == null) {
// Save the hidden service's private key for next time // Save the hidden service's private key for next time
Settings s = new Settings(); Settings s = new Settings();
s.put(HS_PRIVKEY, response.get(HS_PRIVKEY)); s.put(HS_PRIVATE_KEY_V3, response.get(HS_PRIVKEY));
s.put(HS_V3_CREATED, String.valueOf(clock.currentTimeMillis()));
callback.mergeSettings(s); callback.mergeSettings(s);
} }
} }
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");
backoff.reset();
callback.handleConnection(new TorTransportConnection(this, s)); callback.handleConnection(new TorTransportConnection(this, s));
} }
} }
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 +607,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 +622,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
@@ -521,14 +638,19 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
@Override @Override
public int getPollingInterval() { public int getPollingInterval() {
return backoff.getPollingInterval(); if (state.isDescriptorPublished()) {
LOG.info("Using stable polling interval");
return stablePollingInterval;
} else {
LOG.info("Using initial polling interval");
return initialPollingInterval;
}
} }
@Override @Override
public void poll(Collection<Pair<TransportProperties, ConnectionHandler>> public void poll(Collection<Pair<TransportProperties, ConnectionHandler>>
properties) { properties) {
if (!isRunning()) return; if (getState() != ACTIVE) return;
backoff.increment();
for (Pair<TransportProperties, ConnectionHandler> p : properties) { for (Pair<TransportProperties, ConnectionHandler> p : properties) {
connect(p.getFirst(), p.getSecond()); connect(p.getFirst(), p.getSecond());
} }
@@ -537,22 +659,22 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
private void connect(TransportProperties p, ConnectionHandler h) { private void connect(TransportProperties p, ConnectionHandler h) {
ioExecutor.execute(() -> { ioExecutor.execute(() -> {
DuplexTransportConnection d = createConnection(p); DuplexTransportConnection d = createConnection(p);
if (d != null) { if (d != null) h.handleConnection(d);
backoff.reset();
h.handleConnection(d);
}
}); });
} }
@Override @Override
public DuplexTransportConnection createConnection(TransportProperties p) { public DuplexTransportConnection createConnection(TransportProperties p) {
if (!isRunning()) return null; if (getState() != ACTIVE) return null;
String bestOnion = null; // TODO: Remove support for v2 hidden services after a reasonable
// migration period (migration started 2020-06-30)
String bestOnion = null, version = 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);
if (!isNullOrEmpty(onion2)) { if (!isNullOrEmpty(onion2)) {
if (ONION_V2.matcher(onion2).matches()) { if (ONION_V2.matcher(onion2).matches()) {
bestOnion = onion2; bestOnion = onion2;
version = "v2";
} else { } else {
// Don't scrub the address so we can find the problem // Don't scrub the address so we can find the problem
if (LOG.isLoggable(INFO)) if (LOG.isLoggable(INFO))
@@ -562,6 +684,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
if (!isNullOrEmpty(onion3)) { if (!isNullOrEmpty(onion3)) {
if (ONION_V3.matcher(onion3).matches()) { if (ONION_V3.matcher(onion3).matches()) {
bestOnion = onion3; bestOnion = onion3;
version = "v3";
} else { } else {
// Don't scrub the address so we can find the problem // Don't scrub the address so we can find the problem
if (LOG.isLoggable(INFO)) if (LOG.isLoggable(INFO))
@@ -571,17 +694,21 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
if (bestOnion == null) return null; if (bestOnion == null) return null;
Socket s = null; Socket s = null;
try { try {
if (LOG.isLoggable(INFO)) if (LOG.isLoggable(INFO)) {
LOG.info("Connecting to " + scrubOnion(bestOnion)); LOG.info("Connecting to " + version + " "
+ scrubOnion(bestOnion));
}
s = torSocketFactory.createSocket(bestOnion + ".onion", 80); s = torSocketFactory.createSocket(bestOnion + ".onion", 80);
s.setSoTimeout(socketTimeout); s.setSoTimeout(socketTimeout);
if (LOG.isLoggable(INFO)) if (LOG.isLoggable(INFO)) {
LOG.info("Connected to " + scrubOnion(bestOnion)); LOG.info("Connected to " + version + " "
+ scrubOnion(bestOnion));
}
return new TorTransportConnection(this, s); return new TorTransportConnection(this, s);
} catch (IOException e) { } catch (IOException e) {
if (LOG.isLoggable(INFO)) { if (LOG.isLoggable(INFO)) {
LOG.info("Could not connect to " + scrubOnion(bestOnion) LOG.info("Could not connect to " + version + " "
+ ": " + e.toString()); + scrubOnion(bestOnion) + ": " + e.toString());
} }
tryToClose(s, LOG, WARNING); tryToClose(s, LOG, WARNING);
return null; return null;
@@ -634,8 +761,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 =
@@ -662,11 +789,8 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
@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") && state.getAndSetCircuitBuilt()) {
connectionStatus.getAndSetCircuitBuilt()) {
LOG.info("First circuit built"); LOG.info("First circuit built");
backoff.reset();
if (isRunning()) callback.transportEnabled();
} }
} }
@@ -676,8 +800,8 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
@Override @Override
public void orConnStatus(String status, String orName) { public void orConnStatus(String status, String orName) {
if (LOG.isLoggable(INFO)) if (LOG.isLoggable(INFO)) LOG.info("OR connection " + status);
LOG.info("OR connection " + status + " " + orName); state.setOrConnectionStatus(orName, status);
if (status.equals("CLOSED") || status.equals("FAILED")) { if (status.equals("CLOSED") || status.equals("FAILED")) {
// Check whether we've lost connectivity // Check whether we've lost connectivity
updateConnectionStatus(networkManager.getNetworkStatus(), updateConnectionStatus(networkManager.getNetworkStatus(),
@@ -697,16 +821,21 @@ 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();
if (isRunning()) callback.transportEnabled();
} }
} }
@Override @Override
public void unrecognized(String type, String msg) { public void unrecognized(String type, String msg) {
if (type.equals("HS_DESC") && msg.startsWith("UPLOADED")) if (type.equals("HS_DESC") && msg.startsWith("UPLOADED")) {
LOG.info("Descriptor uploaded"); String[] words = msg.split(" ");
if (words.length > 1 && ONION_V3.matcher(words[1]).matches()) {
LOG.info("V3 descriptor uploaded");
state.descriptorUploaded();
} else {
LOG.info("V2 descriptor uploaded");
}
}
} }
@Override @Override
@@ -736,7 +865,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,63 +875,90 @@ 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,
DEFAULT_PREF_PLUGIN_ENABLE);
int network = settings.getInt(PREF_TOR_NETWORK, int network = settings.getInt(PREF_TOR_NETWORK,
PREF_TOR_NETWORK_AUTOMATIC); DEFAULT_PREF_TOR_NETWORK);
boolean useMobile = settings.getBoolean(PREF_TOR_MOBILE, true); boolean useMobile = settings.getBoolean(PREF_TOR_MOBILE,
DEFAULT_PREF_TOR_MOBILE);
boolean onlyWhenCharging = boolean onlyWhenCharging =
settings.getBoolean(PREF_TOR_ONLY_WHEN_CHARGING, false); settings.getBoolean(PREF_TOR_ONLY_WHEN_CHARGING,
DEFAULT_PREF_TOR_ONLY_WHEN_CHARGING);
boolean bridgesWork = circumventionProvider.doBridgesWork(country); boolean bridgesWork = circumventionProvider.doBridgesWork(country);
boolean automatic = network == PREF_TOR_NETWORK_AUTOMATIC; boolean automatic = network == PREF_TOR_NETWORK_AUTOMATIC;
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;
if (!online) { boolean enableNetwork = false, enableBridges = false;
LOG.info("Disabling network, device is offline"); boolean useMeek = false, enableConnectionPadding = false;
enableNetwork(false);
} else if (!charging && onlyWhenCharging) { if (!online) {
LOG.info("Disabling network, device is on battery"); LOG.info("Disabling network, device is offline");
enableNetwork(false); } else {
} else if (network == PREF_TOR_NETWORK_NEVER || if (!enabledByUser) {
(!useMobile && !wifi)) { LOG.info("User has disabled Tor");
LOG.info("Disabling network, device is using mobile data"); reasonsDisabled |= REASON_USER;
enableNetwork(false); }
} else if (automatic && blocked && !bridgesWork) { if (!charging && onlyWhenCharging) {
LOG.info("Disabling network, country is blocked"); LOG.info("Configured not to use battery");
enableNetwork(false); reasonsDisabled |= REASON_BATTERY;
} else if (network == PREF_TOR_NETWORK_WITH_BRIDGES || }
(automatic && bridgesWork)) { if (!useMobile && !wifi) {
if (circumventionProvider.needsMeek(country)) { LOG.info("Configured not to use mobile data");
LOG.info("Enabling network, using meek bridges"); reasonsDisabled |= REASON_MOBILE_DATA;
enableBridges(true, true); }
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)) {
if (circumventionProvider.needsMeek(country)) {
LOG.info("Using meek bridges");
enableBridges = true;
useMeek = true;
} else {
LOG.info("Using obfs4 bridges");
enableBridges = true;
}
} else { } else {
LOG.info("Enabling network, using obfs4 bridges"); LOG.info("Not using bridges");
enableBridges(true, false); }
if (wifi && charging) {
LOG.info("Enabling connection padding");
enableConnectionPadding = true;
} else {
LOG.info("Disabling connection padding");
} }
enableNetwork(true);
} else {
LOG.info("Enabling network, not using bridges");
enableBridges(false, false);
enableNetwork(true);
} }
if (online && wifi && charging) { }
LOG.info("Enabling connection padding");
enableConnectionPadding(true); state.setReasonsDisabled(reasonsDisabled);
} else {
LOG.info("Disabling connection padding"); try {
enableConnectionPadding(false); 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 +966,134 @@ 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;
@GuardedBy("this")
private final Set<String> orConnections = new HashSet<>();
@GuardedBy("this")
private int descriptorsUploaded = 0;
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;
descriptorsUploaded = 0;
}
callback.pluginStateChanged(getState());
} }
private synchronized boolean isConnected() { synchronized void setReasonsDisabled(int reasonsDisabled) {
return networkEnabled && bootstrapped && circuitBuilt; settingsChecked = true;
this.reasonsDisabled = reasonsDisabled;
callback.pluginStateChanged(getState());
}
synchronized void setOrConnectionStatus(String orName, String status) {
if (status.equals("CONNECTED")) {
if (!orConnections.add(orName)) {
LOG.warning("Duplicate OR connection");
}
} else {
orConnections.remove(orName);
if (orConnections.isEmpty()) descriptorsUploaded = 0;
}
if (LOG.isLoggable(INFO)) {
LOG.info(orConnections.size() + " OR connections");
}
callback.pluginStateChanged(getState());
}
// Doesn't affect getState()
synchronized void descriptorUploaded() {
if (networkEnabled && !orConnections.isEmpty()) {
descriptorsUploaded++;
} else {
LOG.warning("Descriptor was uploaded with no OR connection");
}
}
synchronized boolean isDescriptorPublished() {
return descriptorsUploaded >= MIN_DESCRIPTORS_UPLOADED;
}
// 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 && !orConnections.isEmpty()
? ACTIVE : ENABLING;
}
synchronized int getReasonsDisabled() {
return getState() == DISABLED ? reasonsDisabled : 0;
} }
} }
} }

View File

@@ -30,6 +30,7 @@ import org.briarproject.bramble.api.versioning.ClientVersioningManager;
import org.briarproject.bramble.api.versioning.ClientVersioningManager.ClientVersioningHook; import org.briarproject.bramble.api.versioning.ClientVersioningManager.ClientVersioningHook;
import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
@@ -41,6 +42,7 @@ import static org.briarproject.bramble.api.properties.TransportPropertyConstants
import static org.briarproject.bramble.api.properties.TransportPropertyConstants.MSG_KEY_LOCAL; import static org.briarproject.bramble.api.properties.TransportPropertyConstants.MSG_KEY_LOCAL;
import static org.briarproject.bramble.api.properties.TransportPropertyConstants.MSG_KEY_TRANSPORT_ID; import static org.briarproject.bramble.api.properties.TransportPropertyConstants.MSG_KEY_TRANSPORT_ID;
import static org.briarproject.bramble.api.properties.TransportPropertyConstants.MSG_KEY_VERSION; import static org.briarproject.bramble.api.properties.TransportPropertyConstants.MSG_KEY_VERSION;
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
@Immutable @Immutable
@NotNullByDefault @NotNullByDefault
@@ -272,14 +274,22 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
LatestUpdate latest = findLatest(txn, localGroup.getId(), t, LatestUpdate latest = findLatest(txn, localGroup.getId(), t,
true); true);
if (latest == null) { if (latest == null) {
merged = p; merged = new TransportProperties(p);
Iterator<String> it = merged.values().iterator();
while (it.hasNext()) {
if (isNullOrEmpty(it.next())) it.remove();
}
changed = true; changed = true;
} else { } else {
BdfList message = clientHelper.getMessageAsList(txn, BdfList message = clientHelper.getMessageAsList(txn,
latest.messageId); latest.messageId);
TransportProperties old = parseProperties(message); TransportProperties old = parseProperties(message);
merged = new TransportProperties(old); merged = new TransportProperties(old);
merged.putAll(p); for (Entry<String, String> e : p.entrySet()) {
String key = e.getKey(), value = e.getValue();
if (isNullOrEmpty(value)) merged.remove(key);
else merged.put(key, value);
}
changed = !merged.equals(old); changed = !merged.equals(old);
} }
if (changed) { if (changed) {

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

@@ -12,6 +12,7 @@ import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.lifecycle.event.LifecycleEvent; import org.briarproject.bramble.api.lifecycle.event.LifecycleEvent;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportId; import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.event.TransportInactiveEvent;
import org.briarproject.bramble.api.sync.Ack; import org.briarproject.bramble.api.sync.Ack;
import org.briarproject.bramble.api.sync.Message; import org.briarproject.bramble.api.sync.Message;
import org.briarproject.bramble.api.sync.Offer; import org.briarproject.bramble.api.sync.Offer;
@@ -238,6 +239,9 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
} else if (e instanceof CloseSyncConnectionsEvent) { } else if (e instanceof CloseSyncConnectionsEvent) {
CloseSyncConnectionsEvent c = (CloseSyncConnectionsEvent) e; CloseSyncConnectionsEvent c = (CloseSyncConnectionsEvent) e;
if (c.getTransportId().equals(transportId)) interrupt(); if (c.getTransportId().equals(transportId)) interrupt();
} else if (e instanceof TransportInactiveEvent) {
TransportInactiveEvent t = (TransportInactiveEvent) e;
if (t.getTransportId().equals(transportId)) interrupt();
} }
} }

View File

@@ -12,6 +12,7 @@ import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.lifecycle.event.LifecycleEvent; import org.briarproject.bramble.api.lifecycle.event.LifecycleEvent;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportId; import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.event.TransportInactiveEvent;
import org.briarproject.bramble.api.sync.Ack; import org.briarproject.bramble.api.sync.Ack;
import org.briarproject.bramble.api.sync.Message; import org.briarproject.bramble.api.sync.Message;
import org.briarproject.bramble.api.sync.SyncRecordWriter; import org.briarproject.bramble.api.sync.SyncRecordWriter;
@@ -131,6 +132,9 @@ class SimplexOutgoingSession implements SyncSession, EventListener {
} else if (e instanceof CloseSyncConnectionsEvent) { } else if (e instanceof CloseSyncConnectionsEvent) {
CloseSyncConnectionsEvent c = (CloseSyncConnectionsEvent) e; CloseSyncConnectionsEvent c = (CloseSyncConnectionsEvent) e;
if (c.getTransportId().equals(transportId)) interrupt(); if (c.getTransportId().equals(transportId)) interrupt();
} else if (e instanceof TransportInactiveEvent) {
TransportInactiveEvent t = (TransportInactiveEvent) e;
if (t.getTransportId().equals(transportId)) interrupt();
} }
} }

View File

@@ -1,63 +0,0 @@
package org.briarproject.bramble.plugin;
import org.briarproject.bramble.test.BrambleTestCase;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
public class BackoffImplTest extends BrambleTestCase {
private static final int MIN_INTERVAL = 60 * 1000;
private static final int MAX_INTERVAL = 60 * 60 * 1000;
private static final double BASE = 1.2;
@Test
public void testPollingIntervalStartsAtMinimum() {
BackoffImpl b = new BackoffImpl(MIN_INTERVAL, MAX_INTERVAL, BASE);
assertEquals(MIN_INTERVAL, b.getPollingInterval());
}
@Test
public void testIncrementIncreasesPollingInterval() {
BackoffImpl b = new BackoffImpl(MIN_INTERVAL, MAX_INTERVAL, BASE);
b.increment();
assertTrue(b.getPollingInterval() > MIN_INTERVAL);
}
@Test
public void testResetResetsPollingInterval() {
BackoffImpl b = new BackoffImpl(MIN_INTERVAL, MAX_INTERVAL, BASE);
b.increment();
b.increment();
b.reset();
assertEquals(MIN_INTERVAL, b.getPollingInterval());
}
@Test
public void testBaseAffectsBackoffSpeed() {
BackoffImpl b = new BackoffImpl(MIN_INTERVAL, MAX_INTERVAL, BASE);
b.increment();
int interval = b.getPollingInterval();
BackoffImpl b1 = new BackoffImpl(MIN_INTERVAL, MAX_INTERVAL, BASE * 2);
b1.increment();
int interval1 = b1.getPollingInterval();
assertTrue(interval < interval1);
}
@Test
public void testIntervalDoesNotExceedMaxInterval() {
BackoffImpl b = new BackoffImpl(MIN_INTERVAL, MAX_INTERVAL, BASE);
for (int i = 0; i < 100; i++) b.increment();
assertEquals(MAX_INTERVAL, b.getPollingInterval());
}
@Test
public void testIntervalDoesNotExceedMaxIntervalWithInfiniteMultiplier() {
BackoffImpl b = new BackoffImpl(MIN_INTERVAL, MAX_INTERVAL,
Double.POSITIVE_INFINITY);
b.increment();
assertEquals(MAX_INTERVAL, b.getPollingInterval());
}
}

View File

@@ -12,9 +12,8 @@ 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.ConnectionClosedEvent; import org.briarproject.bramble.api.plugin.event.ConnectionClosedEvent;
import org.briarproject.bramble.api.plugin.event.ConnectionOpenedEvent; import org.briarproject.bramble.api.plugin.event.TransportActiveEvent;
import org.briarproject.bramble.api.plugin.event.TransportDisabledEvent; import org.briarproject.bramble.api.plugin.event.TransportInactiveEvent;
import org.briarproject.bramble.api.plugin.event.TransportEnabledEvent;
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;
@@ -157,22 +156,20 @@ public class PollerImplTest extends BrambleMockTestCase {
} }
@Test @Test
public void testRescheduleOnOutgoingConnectionClosed() { public void testDoesNotReconnectOnOutgoingConnectionClosed() {
DuplexPlugin plugin = context.mock(DuplexPlugin.class); DuplexPlugin plugin = context.mock(DuplexPlugin.class);
context.checking(new Expectations() {{ context.checking(new Expectations() {{
allowing(plugin).getId(); allowing(plugin).getId();
will(returnValue(transportId)); will(returnValue(transportId));
}}); }});
expectReschedule(plugin);
poller.eventOccurred(new ConnectionClosedEvent(contactId, transportId, poller.eventOccurred(new ConnectionClosedEvent(contactId, transportId,
false, false)); false, false));
} }
@Test @Test
public void testRescheduleAndReconnectOnOutgoingConnectionFailed() public void testReconnectsOnOutgoingConnectionFailed() throws Exception {
throws Exception {
DuplexPlugin plugin = context.mock(DuplexPlugin.class); DuplexPlugin plugin = context.mock(DuplexPlugin.class);
DuplexTransportConnection duplexConnection = DuplexTransportConnection duplexConnection =
context.mock(DuplexTransportConnection.class); context.mock(DuplexTransportConnection.class);
@@ -181,7 +178,6 @@ public class PollerImplTest extends BrambleMockTestCase {
allowing(plugin).getId(); allowing(plugin).getId();
will(returnValue(transportId)); will(returnValue(transportId));
}}); }});
expectReschedule(plugin);
expectReconnect(plugin, duplexConnection); expectReconnect(plugin, duplexConnection);
poller.eventOccurred(new ConnectionClosedEvent(contactId, transportId, poller.eventOccurred(new ConnectionClosedEvent(contactId, transportId,
@@ -189,149 +185,33 @@ public class PollerImplTest extends BrambleMockTestCase {
} }
@Test @Test
public void testRescheduleOnIncomingConnectionClosed() { public void testDoesNotReconnectOnIncomingConnectionClosed() {
DuplexPlugin plugin = context.mock(DuplexPlugin.class); DuplexPlugin plugin = context.mock(DuplexPlugin.class);
context.checking(new Expectations() {{ context.checking(new Expectations() {{
allowing(plugin).getId(); allowing(plugin).getId();
will(returnValue(transportId)); will(returnValue(transportId));
}}); }});
expectReschedule(plugin);
poller.eventOccurred(new ConnectionClosedEvent(contactId, transportId, poller.eventOccurred(new ConnectionClosedEvent(contactId, transportId,
true, false)); true, false));
} }
@Test @Test
public void testRescheduleOnIncomingConnectionFailed() { public void testDoesNotReconnectOnIncomingConnectionFailed() {
DuplexPlugin plugin = context.mock(DuplexPlugin.class); DuplexPlugin plugin = context.mock(DuplexPlugin.class);
context.checking(new Expectations() {{ context.checking(new Expectations() {{
allowing(plugin).getId(); allowing(plugin).getId();
will(returnValue(transportId)); will(returnValue(transportId));
}}); }});
expectReschedule(plugin);
poller.eventOccurred(new ConnectionClosedEvent(contactId, transportId, poller.eventOccurred(new ConnectionClosedEvent(contactId, transportId,
true, false)); true, false));
} }
@Test @Test
public void testRescheduleOnConnectionOpened() { public void testPollsOnTransportActivated() throws Exception {
Plugin plugin = context.mock(Plugin.class);
context.checking(new Expectations() {{
allowing(plugin).getId();
will(returnValue(transportId));
// Get the plugin
oneOf(pluginManager).getPlugin(transportId);
will(returnValue(plugin));
// The plugin supports polling
oneOf(plugin).shouldPoll();
will(returnValue(true));
// Schedule the next poll
oneOf(plugin).getPollingInterval();
will(returnValue(pollingInterval));
oneOf(clock).currentTimeMillis();
will(returnValue(now));
oneOf(scheduler).schedule(with(any(Runnable.class)),
with((long) pollingInterval), with(MILLISECONDS));
will(returnValue(future));
}});
poller.eventOccurred(new ConnectionOpenedEvent(contactId, transportId,
false));
}
@Test
public void testRescheduleDoesNotReplaceEarlierTask() {
Plugin plugin = context.mock(Plugin.class);
context.checking(new Expectations() {{
allowing(plugin).getId();
will(returnValue(transportId));
// First event
// Get the plugin
oneOf(pluginManager).getPlugin(transportId);
will(returnValue(plugin));
// The plugin supports polling
oneOf(plugin).shouldPoll();
will(returnValue(true));
// Schedule the next poll
oneOf(plugin).getPollingInterval();
will(returnValue(pollingInterval));
oneOf(clock).currentTimeMillis();
will(returnValue(now));
oneOf(scheduler).schedule(with(any(Runnable.class)),
with((long) pollingInterval), with(MILLISECONDS));
will(returnValue(future));
// Second event
// Get the plugin
oneOf(pluginManager).getPlugin(transportId);
will(returnValue(plugin));
// The plugin supports polling
oneOf(plugin).shouldPoll();
will(returnValue(true));
// Don't replace the previously scheduled task, due earlier
oneOf(plugin).getPollingInterval();
will(returnValue(pollingInterval));
oneOf(clock).currentTimeMillis();
will(returnValue(now + 1));
}});
poller.eventOccurred(new ConnectionOpenedEvent(contactId, transportId,
false));
poller.eventOccurred(new ConnectionOpenedEvent(contactId, transportId,
false));
}
@Test
public void testRescheduleReplacesLaterTask() {
Plugin plugin = context.mock(Plugin.class);
context.checking(new Expectations() {{
allowing(plugin).getId();
will(returnValue(transportId));
// First event
// Get the plugin
oneOf(pluginManager).getPlugin(transportId);
will(returnValue(plugin));
// The plugin supports polling
oneOf(plugin).shouldPoll();
will(returnValue(true));
// Schedule the next poll
oneOf(plugin).getPollingInterval();
will(returnValue(pollingInterval));
oneOf(clock).currentTimeMillis();
will(returnValue(now));
oneOf(scheduler).schedule(with(any(Runnable.class)),
with((long) pollingInterval), with(MILLISECONDS));
will(returnValue(future));
// Second event
// Get the plugin
oneOf(pluginManager).getPlugin(transportId);
will(returnValue(plugin));
// The plugin supports polling
oneOf(plugin).shouldPoll();
will(returnValue(true));
// Replace the previously scheduled task, due later
oneOf(plugin).getPollingInterval();
will(returnValue(pollingInterval - 2));
oneOf(clock).currentTimeMillis();
will(returnValue(now + 1));
oneOf(future).cancel(false);
oneOf(scheduler).schedule(with(any(Runnable.class)),
with((long) pollingInterval - 2), with(MILLISECONDS));
}});
poller.eventOccurred(new ConnectionOpenedEvent(contactId, transportId,
false));
poller.eventOccurred(new ConnectionOpenedEvent(contactId, transportId,
false));
}
@Test
public void testPollsOnTransportEnabled() throws Exception {
DuplexPlugin plugin = context.mock(DuplexPlugin.class); DuplexPlugin plugin = context.mock(DuplexPlugin.class);
context.checking(new Expectations() {{ context.checking(new Expectations() {{
@@ -370,7 +250,7 @@ public class PollerImplTest extends BrambleMockTestCase {
pairOf(equal(properties), any(ConnectionHandler.class))))); pairOf(equal(properties), any(ConnectionHandler.class)))));
}}); }});
poller.eventOccurred(new TransportEnabledEvent(transportId)); poller.eventOccurred(new TransportActiveEvent(transportId));
} }
@Test @Test
@@ -411,11 +291,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() {{
@@ -433,31 +313,12 @@ 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));
}
private void expectReschedule(Plugin plugin) {
context.checking(new Expectations() {{
// Get the plugin
oneOf(pluginManager).getPlugin(transportId);
will(returnValue(plugin));
// The plugin supports polling
oneOf(plugin).shouldPoll();
will(returnValue(true));
// Schedule the next poll
oneOf(plugin).getPollingInterval();
will(returnValue(pollingInterval));
oneOf(clock).currentTimeMillis();
will(returnValue(now));
oneOf(scheduler).schedule(with(any(Runnable.class)),
with((long) pollingInterval), with(MILLISECONDS));
will(returnValue(future));
}});
} }
private void expectReconnect(DuplexPlugin plugin, private void expectReconnect(DuplexPlugin plugin,

View File

@@ -3,7 +3,7 @@ package org.briarproject.bramble.plugin.tcp;
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.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 +31,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;
@@ -40,7 +41,6 @@ import static org.junit.Assume.assumeTrue;
public class LanTcpPluginTest extends BrambleTestCase { public class LanTcpPluginTest extends BrambleTestCase {
private final Backoff backoff = new TestBackoff();
private final ExecutorService ioExecutor = newCachedThreadPool(); private final ExecutorService ioExecutor = newCachedThreadPool();
private Callback callback = null; private Callback callback = null;
@@ -49,7 +49,7 @@ public class LanTcpPluginTest extends BrambleTestCase {
@Before @Before
public void setUp() { public void setUp() {
callback = new Callback(); callback = new Callback();
plugin = new LanTcpPlugin(ioExecutor, backoff, callback, 0, 0, 1000) { plugin = new LanTcpPlugin(ioExecutor, callback, 0, 0, 0, 1000) {
@Override @Override
protected boolean canConnectToOwnAddress() { protected boolean canConnectToOwnAddress() {
return true; return true;
@@ -302,10 +302,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 +329,7 @@ public class LanTcpPluginTest extends BrambleTestCase {
} }
@Override @Override
public void transportEnabled() { public void pluginStateChanged(State newState) {
}
@Override
public void transportDisabled() {
} }
@Override @Override
@@ -344,20 +345,4 @@ public class LanTcpPluginTest extends BrambleTestCase {
public void handleWriter(TransportConnectionWriter w) { public void handleWriter(TransportConnectionWriter w) {
} }
} }
private static class TestBackoff implements Backoff {
@Override
public int getPollingInterval() {
return 60 * 1000;
}
@Override
public void increment() {
}
@Override
public void reset() {
}
}
} }

View File

@@ -566,6 +566,10 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
Contact contact = getContact(); Contact contact = getContact();
Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION); Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
// Property with an empty value should be discarded
TransportProperties properties = new TransportProperties(fooProperties);
properties.put("fooKey3", "");
context.checking(new DbExpectations() {{ context.checking(new DbExpectations() {{
oneOf(db).transaction(with(false), withDbRunnable(txn)); oneOf(db).transaction(with(false), withDbRunnable(txn));
// There are no existing properties to merge with // There are no existing properties to merge with
@@ -589,7 +593,7 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
}}); }});
TransportPropertyManagerImpl t = createInstance(); TransportPropertyManagerImpl t = createInstance();
t.mergeLocalProperties(new TransportId("foo"), fooProperties); t.mergeLocalProperties(new TransportId("foo"), properties);
} }
@Test @Test
@@ -605,16 +609,24 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
MessageId localGroupUpdateId = new MessageId(getRandomId()); MessageId localGroupUpdateId = new MessageId(getRandomId());
Map<MessageId, BdfDictionary> localGroupMessageMetadata = Map<MessageId, BdfDictionary> localGroupMessageMetadata =
singletonMap(localGroupUpdateId, oldMetadata); singletonMap(localGroupUpdateId, oldMetadata);
MessageId contactGroupUpdateId = new MessageId(getRandomId()); MessageId contactGroupUpdateId = new MessageId(getRandomId());
Map<MessageId, BdfDictionary> contactGroupMessageMetadata = Map<MessageId, BdfDictionary> contactGroupMessageMetadata =
singletonMap(contactGroupUpdateId, oldMetadata); singletonMap(contactGroupUpdateId, oldMetadata);
TransportProperties oldProperties = new TransportProperties(); TransportProperties oldProperties = new TransportProperties();
oldProperties.put("fooKey1", "oldFooValue1"); oldProperties.put("fooKey1", "oldFooValue1");
oldProperties.put("fooKey3", "oldFooValue3");
BdfDictionary oldPropertiesDict = BdfDictionary.of( BdfDictionary oldPropertiesDict = BdfDictionary.of(
new BdfEntry("fooKey1", "oldFooValue1") new BdfEntry("fooKey1", "oldFooValue1"),
new BdfEntry("fooKey3", "oldFooValue3")
); );
BdfList oldUpdate = BdfList.of("foo", 1, oldPropertiesDict); BdfList oldUpdate = BdfList.of("foo", 1, oldPropertiesDict);
// Property assigned an empty value should be removed
TransportProperties properties = new TransportProperties(fooProperties);
properties.put("fooKey3", "");
context.checking(new DbExpectations() {{ context.checking(new DbExpectations() {{
oneOf(db).transaction(with(false), withDbRunnable(txn)); oneOf(db).transaction(with(false), withDbRunnable(txn));
// Merge the new properties with the existing properties // Merge the new properties with the existing properties
@@ -647,7 +659,7 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
}}); }});
TransportPropertyManagerImpl t = createInstance(); TransportPropertyManagerImpl t = createInstance();
t.mergeLocalProperties(new TransportId("foo"), fooProperties); t.mergeLocalProperties(new TransportId("foo"), properties);
} }
private void expectGetLocalProperties(Transaction txn) throws Exception { private void expectGetLocalProperties(Transaction txn) throws Exception {

View File

@@ -17,8 +17,8 @@ import org.briarproject.bramble.api.plugin.ConnectionHandler;
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;
@@ -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
@@ -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
@@ -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,7 +5,6 @@ import org.briarproject.bramble.api.io.TimeoutMonitor;
import org.briarproject.bramble.api.lifecycle.IoExecutor; import org.briarproject.bramble.api.lifecycle.IoExecutor;
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.BackoffFactory;
import org.briarproject.bramble.api.plugin.BluetoothConstants; import org.briarproject.bramble.api.plugin.BluetoothConstants;
import org.briarproject.bramble.api.plugin.LanTcpConstants; import org.briarproject.bramble.api.plugin.LanTcpConstants;
import org.briarproject.bramble.api.plugin.PluginConfig; import org.briarproject.bramble.api.plugin.PluginConfig;
@@ -37,18 +36,16 @@ public class DesktopPluginModule extends PluginModule {
@Provides @Provides
PluginConfig getPluginConfig(@IoExecutor Executor ioExecutor, PluginConfig getPluginConfig(@IoExecutor Executor ioExecutor,
SecureRandom random, BackoffFactory backoffFactory, SecureRandom random, ReliabilityLayerFactory reliabilityFactory,
ReliabilityLayerFactory reliabilityFactory,
ShutdownManager shutdownManager, EventBus eventBus, ShutdownManager shutdownManager, EventBus eventBus,
TimeoutMonitor timeoutMonitor) { TimeoutMonitor timeoutMonitor) {
DuplexPluginFactory bluetooth = new JavaBluetoothPluginFactory( DuplexPluginFactory bluetooth = new JavaBluetoothPluginFactory(
ioExecutor, random, eventBus, timeoutMonitor, backoffFactory); ioExecutor, random, eventBus, timeoutMonitor);
DuplexPluginFactory modem = new ModemPluginFactory(ioExecutor, DuplexPluginFactory modem = new ModemPluginFactory(ioExecutor,
reliabilityFactory); reliabilityFactory);
DuplexPluginFactory lan = new LanTcpPluginFactory(ioExecutor, DuplexPluginFactory lan = new LanTcpPluginFactory(ioExecutor, eventBus);
backoffFactory); DuplexPluginFactory wan = new WanTcpPluginFactory(ioExecutor, eventBus,
DuplexPluginFactory wan = new WanTcpPluginFactory(ioExecutor, shutdownManager);
backoffFactory, shutdownManager);
Collection<DuplexPluginFactory> duplex = Collection<DuplexPluginFactory> duplex =
asList(bluetooth, modem, lan, wan); asList(bluetooth, modem, lan, wan);
@NotNullByDefault @NotNullByDefault

View File

@@ -3,7 +3,6 @@ package org.briarproject.bramble.plugin.bluetooth;
import org.briarproject.bramble.api.io.TimeoutMonitor; import org.briarproject.bramble.api.io.TimeoutMonitor;
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.PluginCallback; import org.briarproject.bramble.api.plugin.PluginCallback;
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection; import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
@@ -36,10 +35,10 @@ class JavaBluetoothPlugin extends BluetoothPlugin<StreamConnectionNotifier> {
JavaBluetoothPlugin(BluetoothConnectionLimiter connectionManager, JavaBluetoothPlugin(BluetoothConnectionLimiter connectionManager,
TimeoutMonitor timeoutMonitor, Executor ioExecutor, TimeoutMonitor timeoutMonitor, Executor ioExecutor,
SecureRandom secureRandom, Backoff backoff, SecureRandom secureRandom, PluginCallback callback, int maxLatency,
PluginCallback callback, int maxLatency, int maxIdleTime) { int maxIdleTime, int pollingInterval) {
super(connectionManager, timeoutMonitor, ioExecutor, secureRandom, super(connectionManager, timeoutMonitor, ioExecutor, secureRandom,
backoff, callback, maxLatency, maxIdleTime); callback, maxLatency, maxIdleTime, pollingInterval);
} }
@Override @Override

View File

@@ -3,8 +3,6 @@ package org.briarproject.bramble.plugin.bluetooth;
import org.briarproject.bramble.api.event.EventBus; import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.io.TimeoutMonitor; import org.briarproject.bramble.api.io.TimeoutMonitor;
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.BackoffFactory;
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.DuplexPlugin; import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
@@ -15,32 +13,30 @@ import java.util.concurrent.Executor;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
import static java.util.concurrent.TimeUnit.MINUTES;
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.briarproject.bramble.api.plugin.BluetoothConstants.ID; import static org.briarproject.bramble.api.plugin.BluetoothConstants.ID;
@Immutable @Immutable
@NotNullByDefault @NotNullByDefault
public class JavaBluetoothPluginFactory implements DuplexPluginFactory { public class JavaBluetoothPluginFactory implements DuplexPluginFactory {
private static final int MAX_LATENCY = 30 * 1000; // 30 seconds private static final int MAX_LATENCY = (int) SECONDS.toMillis(30);
private static final int MAX_IDLE_TIME = 30 * 1000; // 30 seconds private static final int MAX_IDLE_TIME = (int) SECONDS.toMillis(30);
private static final int MIN_POLLING_INTERVAL = 60 * 1000; // 1 minute private static final int POLLING_INTERVAL = (int) MINUTES.toMillis(2);
private static final int MAX_POLLING_INTERVAL = 10 * 60 * 1000; // 10 mins
private static final double BACKOFF_BASE = 1.2;
private final Executor ioExecutor; private final Executor ioExecutor;
private final SecureRandom secureRandom; private final SecureRandom secureRandom;
private final EventBus eventBus; private final EventBus eventBus;
private final TimeoutMonitor timeoutMonitor; private final TimeoutMonitor timeoutMonitor;
private final BackoffFactory backoffFactory;
public JavaBluetoothPluginFactory(Executor ioExecutor, public JavaBluetoothPluginFactory(Executor ioExecutor,
SecureRandom secureRandom, EventBus eventBus, SecureRandom secureRandom, EventBus eventBus,
TimeoutMonitor timeoutMonitor, BackoffFactory backoffFactory) { TimeoutMonitor timeoutMonitor) {
this.ioExecutor = ioExecutor; this.ioExecutor = ioExecutor;
this.secureRandom = secureRandom; this.secureRandom = secureRandom;
this.eventBus = eventBus; this.eventBus = eventBus;
this.timeoutMonitor = timeoutMonitor; this.timeoutMonitor = timeoutMonitor;
this.backoffFactory = backoffFactory;
} }
@Override @Override
@@ -57,11 +53,9 @@ public class JavaBluetoothPluginFactory implements DuplexPluginFactory {
public DuplexPlugin createPlugin(PluginCallback callback) { public DuplexPlugin createPlugin(PluginCallback callback) {
BluetoothConnectionLimiter connectionLimiter = BluetoothConnectionLimiter connectionLimiter =
new BluetoothConnectionLimiterImpl(eventBus); new BluetoothConnectionLimiterImpl(eventBus);
Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL,
MAX_POLLING_INTERVAL, BACKOFF_BASE);
JavaBluetoothPlugin plugin = new JavaBluetoothPlugin(connectionLimiter, JavaBluetoothPlugin plugin = new JavaBluetoothPlugin(connectionLimiter,
timeoutMonitor, ioExecutor, secureRandom, backoff, callback, timeoutMonitor, ioExecutor, secureRandom, callback, MAX_LATENCY,
MAX_LATENCY, MAX_IDLE_TIME); MAX_IDLE_TIME, POLLING_INTERVAL);
eventBus.addListener(plugin); eventBus.addListener(plugin);
return plugin; return plugin;
} }

View File

@@ -4,6 +4,7 @@ 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.PluginCallback; import org.briarproject.bramble.api.plugin.PluginCallback;
@@ -23,9 +24,16 @@ import java.util.Collection;
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 +52,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 +83,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 +92,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 +116,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
@@ -125,8 +141,8 @@ class ModemPlugin implements DuplexPlugin, Modem.Callback {
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 +151,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;
@@ -232,4 +248,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

@@ -3,7 +3,6 @@ package org.briarproject.bramble.plugin.tor;
import org.briarproject.bramble.api.battery.BatteryManager; import org.briarproject.bramble.api.battery.BatteryManager;
import org.briarproject.bramble.api.network.NetworkManager; import org.briarproject.bramble.api.network.NetworkManager;
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.PluginCallback; import org.briarproject.bramble.api.plugin.PluginCallback;
import org.briarproject.bramble.api.system.Clock; import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.api.system.LocationUtils; import org.briarproject.bramble.api.system.LocationUtils;
@@ -24,14 +23,16 @@ abstract class JavaTorPlugin extends TorPlugin {
LocationUtils locationUtils, SocketFactory torSocketFactory, LocationUtils locationUtils, SocketFactory torSocketFactory,
Clock clock, ResourceProvider resourceProvider, Clock clock, ResourceProvider resourceProvider,
CircumventionProvider circumventionProvider, CircumventionProvider circumventionProvider,
BatteryManager batteryManager, Backoff backoff, BatteryManager batteryManager,
TorRendezvousCrypto torRendezvousCrypto, TorRendezvousCrypto torRendezvousCrypto,
PluginCallback callback, String architecture, int maxLatency, PluginCallback callback, String architecture, int maxLatency,
int maxIdleTime, File torDirectory) { int maxIdleTime, int initialPollingInterval,
int stablePollingInterval, File torDirectory) {
super(ioExecutor, networkManager, locationUtils, torSocketFactory, super(ioExecutor, networkManager, locationUtils, torSocketFactory,
clock, resourceProvider, circumventionProvider, batteryManager, clock, resourceProvider, circumventionProvider, batteryManager,
backoff, torRendezvousCrypto, callback, architecture, torRendezvousCrypto, callback, architecture, maxLatency,
maxLatency, maxIdleTime, torDirectory); maxIdleTime, initialPollingInterval, stablePollingInterval,
torDirectory);
} }
@Override @Override

View File

@@ -6,7 +6,6 @@ import com.sun.jna.Native;
import org.briarproject.bramble.api.battery.BatteryManager; import org.briarproject.bramble.api.battery.BatteryManager;
import org.briarproject.bramble.api.network.NetworkManager; import org.briarproject.bramble.api.network.NetworkManager;
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.PluginCallback; import org.briarproject.bramble.api.plugin.PluginCallback;
import org.briarproject.bramble.api.system.Clock; import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.api.system.LocationUtils; import org.briarproject.bramble.api.system.LocationUtils;
@@ -24,14 +23,16 @@ class UnixTorPlugin extends JavaTorPlugin {
LocationUtils locationUtils, SocketFactory torSocketFactory, LocationUtils locationUtils, SocketFactory torSocketFactory,
Clock clock, ResourceProvider resourceProvider, Clock clock, ResourceProvider resourceProvider,
CircumventionProvider circumventionProvider, CircumventionProvider circumventionProvider,
BatteryManager batteryManager, Backoff backoff, BatteryManager batteryManager,
TorRendezvousCrypto torRendezvousCrypto, TorRendezvousCrypto torRendezvousCrypto,
PluginCallback callback, String architecture, int maxLatency, PluginCallback callback, String architecture, int maxLatency,
int maxIdleTime, File torDirectory) { int maxIdleTime, int initialPollingInterval,
int stablePollingInterval, File torDirectory) {
super(ioExecutor, networkManager, locationUtils, torSocketFactory, super(ioExecutor, networkManager, locationUtils, torSocketFactory,
clock, resourceProvider, circumventionProvider, batteryManager, clock, resourceProvider, circumventionProvider, batteryManager,
backoff, torRendezvousCrypto, callback, architecture, torRendezvousCrypto, callback, architecture,
maxLatency, maxIdleTime, torDirectory); maxLatency, maxIdleTime, initialPollingInterval,
stablePollingInterval, torDirectory);
} }
@Override @Override

View File

@@ -4,8 +4,6 @@ import org.briarproject.bramble.api.battery.BatteryManager;
import org.briarproject.bramble.api.event.EventBus; import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.network.NetworkManager; import org.briarproject.bramble.api.network.NetworkManager;
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.BackoffFactory;
import org.briarproject.bramble.api.plugin.PluginCallback; import org.briarproject.bramble.api.plugin.PluginCallback;
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;
@@ -22,6 +20,8 @@ import java.util.logging.Logger;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
import javax.net.SocketFactory; import javax.net.SocketFactory;
import static java.util.concurrent.TimeUnit.MINUTES;
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.util.OsUtils.isLinux; import static org.briarproject.bramble.util.OsUtils.isLinux;
@@ -32,18 +32,28 @@ public class UnixTorPluginFactory implements DuplexPluginFactory {
private static final Logger LOG = private static final Logger LOG =
getLogger(UnixTorPluginFactory.class.getName()); getLogger(UnixTorPluginFactory.class.getName());
private static final int MAX_LATENCY = 30 * 1000; // 30 seconds private static final int MAX_LATENCY = (int) SECONDS.toMillis(30);
private static final int MAX_IDLE_TIME = 30 * 1000; // 30 seconds private static final int MAX_IDLE_TIME = (int) SECONDS.toMillis(30);
private static final int MIN_POLLING_INTERVAL = 60 * 1000; // 1 minute
private static final int MAX_POLLING_INTERVAL = 10 * 60 * 1000; // 10 mins /**
private static final double BACKOFF_BASE = 1.2; * How often to poll before our hidden service becomes reachable.
*/
private static final int INITIAL_POLLING_INTERVAL =
(int) MINUTES.toMillis(1);
/**
* How often to poll when our hidden service is reachable. Our contacts
* will poll when they come online, so our polling is just a fallback in
* case of repeated connection failures.
*/
private static final int STABLE_POLLING_INTERVAL =
(int) MINUTES.toMillis(15);
private final Executor ioExecutor; private final Executor ioExecutor;
private final NetworkManager networkManager; private final NetworkManager networkManager;
private final LocationUtils locationUtils; private final LocationUtils locationUtils;
private final EventBus eventBus; private final EventBus eventBus;
private final SocketFactory torSocketFactory; private final SocketFactory torSocketFactory;
private final BackoffFactory backoffFactory;
private final ResourceProvider resourceProvider; private final ResourceProvider resourceProvider;
private final CircumventionProvider circumventionProvider; private final CircumventionProvider circumventionProvider;
private final BatteryManager batteryManager; private final BatteryManager batteryManager;
@@ -53,7 +63,7 @@ public class UnixTorPluginFactory implements DuplexPluginFactory {
public UnixTorPluginFactory(Executor ioExecutor, public UnixTorPluginFactory(Executor ioExecutor,
NetworkManager networkManager, LocationUtils locationUtils, NetworkManager networkManager, LocationUtils locationUtils,
EventBus eventBus, SocketFactory torSocketFactory, EventBus eventBus, SocketFactory torSocketFactory,
BackoffFactory backoffFactory, ResourceProvider resourceProvider, ResourceProvider resourceProvider,
CircumventionProvider circumventionProvider, CircumventionProvider circumventionProvider,
BatteryManager batteryManager, Clock clock, File torDirectory) { BatteryManager batteryManager, Clock clock, File torDirectory) {
this.ioExecutor = ioExecutor; this.ioExecutor = ioExecutor;
@@ -61,7 +71,6 @@ public class UnixTorPluginFactory implements DuplexPluginFactory {
this.locationUtils = locationUtils; this.locationUtils = locationUtils;
this.eventBus = eventBus; this.eventBus = eventBus;
this.torSocketFactory = torSocketFactory; this.torSocketFactory = torSocketFactory;
this.backoffFactory = backoffFactory;
this.resourceProvider = resourceProvider; this.resourceProvider = resourceProvider;
this.circumventionProvider = circumventionProvider; this.circumventionProvider = circumventionProvider;
this.batteryManager = batteryManager; this.batteryManager = batteryManager;
@@ -94,14 +103,13 @@ public class UnixTorPluginFactory implements DuplexPluginFactory {
return null; return null;
} }
Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL,
MAX_POLLING_INTERVAL, BACKOFF_BASE);
TorRendezvousCrypto torRendezvousCrypto = new TorRendezvousCryptoImpl(); TorRendezvousCrypto torRendezvousCrypto = new TorRendezvousCryptoImpl();
UnixTorPlugin plugin = new UnixTorPlugin(ioExecutor, networkManager, UnixTorPlugin plugin = new UnixTorPlugin(ioExecutor, networkManager,
locationUtils, torSocketFactory, clock, resourceProvider, locationUtils, torSocketFactory, clock, resourceProvider,
circumventionProvider, batteryManager, backoff, circumventionProvider, batteryManager, torRendezvousCrypto,
torRendezvousCrypto, callback, architecture, MAX_LATENCY, callback, architecture, MAX_LATENCY, MAX_IDLE_TIME,
MAX_IDLE_TIME, torDirectory); INITIAL_POLLING_INTERVAL, STABLE_POLLING_INTERVAL,
torDirectory);
eventBus.addListener(plugin); eventBus.addListener(plugin);
return plugin; return plugin;
} }

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

@@ -5,7 +5,6 @@ import org.briarproject.bramble.api.battery.BatteryManager;
import org.briarproject.bramble.api.event.EventBus; import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.lifecycle.IoExecutor; import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.network.NetworkManager; import org.briarproject.bramble.api.network.NetworkManager;
import org.briarproject.bramble.api.plugin.BackoffFactory;
import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin; import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
import org.briarproject.bramble.api.system.Clock; import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.api.system.LocationUtils; import org.briarproject.bramble.api.system.LocationUtils;
@@ -32,6 +31,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;
@@ -68,8 +68,6 @@ public class BridgeTest extends BrambleTestCase {
@Inject @Inject
EventBus eventBus; EventBus eventBus;
@Inject @Inject
BackoffFactory backoffFactory;
@Inject
Clock clock; Clock clock;
private final File torDir = getTestDirectory(); private final File torDir = getTestDirectory();
@@ -119,9 +117,8 @@ public class BridgeTest extends BrambleTestCase {
} }
}; };
factory = new UnixTorPluginFactory(ioExecutor, networkManager, factory = new UnixTorPluginFactory(ioExecutor, networkManager,
locationUtils, eventBus, torSocketFactory, backoffFactory, locationUtils, eventBus, torSocketFactory, resourceProvider,
resourceProvider, bridgeProvider, batteryManager, clock, bridgeProvider, batteryManager, clock, torDir);
torDir);
} }
@After @After
@@ -141,10 +138,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

@@ -164,7 +164,7 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
nc.setLockscreenVisibility(VISIBILITY_SECRET); nc.setLockscreenVisibility(VISIBILITY_SECRET);
nc.enableVibration(true); nc.enableVibration(true);
nc.enableLights(true); nc.enableLights(true);
nc.setLightColor(getColor(appContext, R.color.briar_green_light)); nc.setLightColor(getColor(appContext, R.color.briar_lime_400));
notificationManager.createNotificationChannel(nc); notificationManager.createNotificationChannel(nc);
} }

View File

@@ -19,7 +19,6 @@ import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.lifecycle.LifecycleManager; import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.network.NetworkManager; import org.briarproject.bramble.api.network.NetworkManager;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.BackoffFactory;
import org.briarproject.bramble.api.plugin.BluetoothConstants; import org.briarproject.bramble.api.plugin.BluetoothConstants;
import org.briarproject.bramble.api.plugin.LanTcpConstants; import org.briarproject.bramble.api.plugin.LanTcpConstants;
import org.briarproject.bramble.api.plugin.PluginConfig; import org.briarproject.bramble.api.plugin.PluginConfig;
@@ -41,6 +40,7 @@ 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.login.LoginModule; import org.briarproject.briar.android.login.LoginModule;
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;
@@ -76,6 +76,7 @@ import static org.briarproject.briar.android.TestingConstants.IS_DEBUG_BUILD;
@Module(includes = { @Module(includes = {
ContactExchangeModule.class, ContactExchangeModule.class,
LoginModule.class, LoginModule.class,
NavDrawerModule.class,
ViewModelModule.class ViewModelModule.class
}) })
public class AppModule { public class AppModule {
@@ -125,23 +126,22 @@ public class AppModule {
PluginConfig providePluginConfig(@IoExecutor Executor ioExecutor, PluginConfig providePluginConfig(@IoExecutor Executor ioExecutor,
@Scheduler ScheduledExecutorService scheduler, @Scheduler ScheduledExecutorService scheduler,
AndroidExecutor androidExecutor, SecureRandom random, AndroidExecutor androidExecutor, SecureRandom random,
SocketFactory torSocketFactory, BackoffFactory backoffFactory, SocketFactory torSocketFactory, Application app,
Application app, NetworkManager networkManager, NetworkManager networkManager, LocationUtils locationUtils,
LocationUtils locationUtils, EventBus eventBus, EventBus eventBus, ResourceProvider resourceProvider,
ResourceProvider resourceProvider,
CircumventionProvider circumventionProvider, CircumventionProvider circumventionProvider,
BatteryManager batteryManager, Clock clock, BatteryManager batteryManager, Clock clock,
TimeoutMonitor timeoutMonitor) { TimeoutMonitor timeoutMonitor) {
Context appContext = app.getApplicationContext(); Context appContext = app.getApplicationContext();
DuplexPluginFactory bluetooth = new AndroidBluetoothPluginFactory( DuplexPluginFactory bluetooth = new AndroidBluetoothPluginFactory(
ioExecutor, androidExecutor, appContext, random, eventBus, ioExecutor, scheduler, androidExecutor, appContext, random,
clock, timeoutMonitor, backoffFactory); eventBus, clock, timeoutMonitor);
DuplexPluginFactory tor = new AndroidTorPluginFactory(ioExecutor, DuplexPluginFactory tor = new AndroidTorPluginFactory(ioExecutor,
scheduler, appContext, networkManager, locationUtils, eventBus, scheduler, appContext, networkManager, locationUtils, eventBus,
torSocketFactory, backoffFactory, resourceProvider, torSocketFactory, resourceProvider, circumventionProvider,
circumventionProvider, batteryManager, clock); batteryManager, clock);
DuplexPluginFactory lan = new AndroidLanTcpPluginFactory(ioExecutor, DuplexPluginFactory lan = new AndroidLanTcpPluginFactory(ioExecutor,
eventBus, backoffFactory, appContext); eventBus, appContext);
Collection<DuplexPluginFactory> duplex = asList(bluetooth, tor, lan); Collection<DuplexPluginFactory> duplex = asList(bluetooth, tor, lan);
@NotNullByDefault @NotNullByDefault
PluginConfig pluginConfig = new PluginConfig() { PluginConfig pluginConfig = new PluginConfig() {

View File

@@ -8,8 +8,6 @@ import org.briarproject.briar.android.controller.BriarController;
import org.briarproject.briar.android.controller.BriarControllerImpl; import org.briarproject.briar.android.controller.BriarControllerImpl;
import org.briarproject.briar.android.controller.DbController; 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.navdrawer.NavDrawerController;
import org.briarproject.briar.android.navdrawer.NavDrawerControllerImpl;
import dagger.Module; import dagger.Module;
import dagger.Provides; import dagger.Provides;
@@ -58,14 +56,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

@@ -76,7 +76,7 @@ public class PendingContactListActivity extends BriarActivity
list.showProgressBar(); list.showProgressBar();
offlineSnackbar = new BriarSnackbarBuilder() offlineSnackbar = new BriarSnackbarBuilder()
.setBackgroundColor(R.color.briar_red) .setBackgroundColor(R.color.briar_red_500)
.make(list, R.string.offline_state, LENGTH_INDEFINITE); .make(list, R.string.offline_state, LENGTH_INDEFINITE);
} }

View File

@@ -46,11 +46,11 @@ class PendingContactViewHolder extends ViewHolder {
}); });
int color = ContextCompat int color = ContextCompat
.getColor(status.getContext(), R.color.briar_green); .getColor(status.getContext(), R.color.briar_lime_600);
switch (item.getState()) { switch (item.getState()) {
case WAITING_FOR_CONNECTION: case WAITING_FOR_CONNECTION:
color = ContextCompat color = ContextCompat.getColor(status.getContext(),
.getColor(status.getContext(), R.color.briar_yellow); R.color.briar_orange_500);
status.setText(R.string.waiting_for_contact_to_come_online); status.setText(R.string.waiting_for_contact_to_come_online);
break; break;
case OFFLINE: case OFFLINE:
@@ -64,7 +64,7 @@ class PendingContactViewHolder extends ViewHolder {
break; break;
case FAILED: case FAILED:
color = ContextCompat color = ContextCompat
.getColor(status.getContext(), R.color.briar_red); .getColor(status.getContext(), R.color.briar_red_500);
status.setText(R.string.adding_contact_failed); status.setText(R.string.adding_contact_failed);
break; break;
default: default:

View File

@@ -41,7 +41,8 @@ class ConversationMessageViewHolder extends ConversationItemViewHolder {
// remember original status text color // remember original status text color
timeColor = time.getCurrentTextColor(); timeColor = time.getCurrentTextColor();
timeColorBubble = getColor(v.getContext(), R.color.briar_white); timeColorBubble =
getColor(v.getContext(), R.color.msg_status_bubble_foreground);
// clone constraint sets from layout files // clone constraint sets from layout files
textConstraints.clone(v.getContext(), textConstraints.clone(v.getContext(),

View File

@@ -230,7 +230,11 @@ class ConversationVisitor implements
R.layout.list_item_conversation_notice_out, text, r); R.layout.list_item_conversation_notice_out, text, r);
} else { } else {
String text; String text;
if (r.isContact()) { if (r.wasAnswered()) {
text = ctx.getString(
R.string.introduction_request_answered_received,
contactName.getValue(), name);
} else if (r.isContact()) {
text = ctx.getString( text = ctx.getString(
R.string.introduction_request_exists_received, R.string.introduction_request_exists_received,
contactName.getValue(), name); contactName.getValue(), name);

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();
@@ -304,7 +299,7 @@ public class ImageActivity extends BriarActivity
int stringRes = error ? int stringRes = error ?
R.string.save_image_error : R.string.save_image_success; R.string.save_image_error : R.string.save_image_success;
int colorRes = error ? int colorRes = error ?
R.color.briar_red : R.color.briar_primary; R.color.briar_red_500 : R.color.briar_primary;
new BriarSnackbarBuilder() new BriarSnackbarBuilder()
.setBackgroundColor(colorRes) .setBackgroundColor(colorRes)
.make(layout, stringRes, LENGTH_LONG) .make(layout, stringRes, LENGTH_LONG)

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() {
BluetoothAdapter bt = BluetoothAdapter.getDefaultAdapter();
if (bt == null) {
setBluetoothState(BluetoothState.NO_ADAPTER);
} else {
Intent i = new Intent(ACTION_REQUEST_DISCOVERABLE);
if (i.resolveActivity(getPackageManager()) != null) {
setBluetoothState(BluetoothState.WAITING);
wasAdapterEnabled = bt.isEnabled();
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());
wasAdapterEnabled = true;
}
showQrCodeFragmentIfAllowed();
}
@Override @Override
public void onActivityResult(int request, int result, Intent data) { public void onActivityResult(int request, int result,
@Nullable Intent data) {
if (request == REQUEST_BLUETOOTH_DISCOVERABLE) { if (request == REQUEST_BLUETOOTH_DISCOVERABLE) {
if (result == RESULT_CANCELED) { if (result == RESULT_CANCELED) {
setBluetoothState(BluetoothState.REFUSED); LOG.info("Bluetooth discoverability was refused");
bluetoothDecision = BluetoothDecision.REFUSED;
} else { } else {
// If Bluetooth is already discoverable, show the QR code - LOG.info("Bluetooth discoverability was accepted");
// otherwise wait for the state or scan mode to change bluetoothDecision = BluetoothDecision.ACCEPTED;
BluetoothAdapter bt = BluetoothAdapter.getDefaultAdapter(); if (!wasAdapterEnabled) {
if (bt == null) throw new AssertionError(); LOG.info("Bluetooth adapter was enabled by us");
if (bt.getScanMode() == SCAN_MODE_CONNECTABLE_DISCOVERABLE) eventBus.broadcast(new BluetoothEnabledEvent());
setBluetoothState(BluetoothState.DISCOVERABLE); wasAdapterEnabled = true;
}
} }
showQrCodeFragmentIfAllowed();
} 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,6 +1,5 @@
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;
@@ -23,6 +22,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.bramble.api.plugin.BluetoothConstants; import org.briarproject.bramble.api.plugin.BluetoothConstants;
import org.briarproject.bramble.api.plugin.LanTcpConstants; 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.TorConstants;
import org.briarproject.bramble.api.plugin.TransportId; import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.briar.R; import org.briarproject.briar.R;
@@ -30,7 +30,6 @@ 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;
@@ -44,9 +43,11 @@ import java.util.logging.Logger;
import javax.inject.Inject; import javax.inject.Inject;
import androidx.annotation.ColorRes;
import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.UiThread; import androidx.annotation.StringRes;
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;
@@ -55,6 +56,8 @@ 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;
@@ -64,6 +67,9 @@ import static androidx.fragment.app.FragmentManager.POP_BACK_STACK_INCLUSIVE;
import static java.util.Objects.requireNonNull; import static java.util.Objects.requireNonNull;
import static java.util.logging.Logger.getLogger; import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.RUNNING; import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.RUNNING;
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.STARTING_STOPPING;
import static org.briarproject.briar.android.BriarService.EXTRA_STARTUP_FAILED; import static org.briarproject.briar.android.BriarService.EXTRA_STARTUP_FAILED;
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_PASSWORD; import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_PASSWORD;
import static org.briarproject.briar.android.navdrawer.IntentRouter.handleExternalIntent; import static org.briarproject.briar.android.navdrawer.IntentRouter.handleExternalIntent;
@@ -72,8 +78,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,10 +96,13 @@ 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 navDrawerViewModel;
private PluginViewModel pluginViewModel;
private ActionBarDrawerToggle drawerToggle; private ActionBarDrawerToggle drawerToggle;
@Inject @Inject
NavDrawerController controller; ViewModelProvider.Factory viewModelFactory;
@Inject @Inject
LifecycleManager lifecycleManager; LifecycleManager lifecycleManager;
@@ -115,6 +123,17 @@ public class NavDrawerActivity extends BriarActivity implements
exitIfStartupFailed(getIntent()); exitIfStartupFailed(getIntent());
setContentView(R.layout.activity_nav_drawer); setContentView(R.layout.activity_nav_drawer);
ViewModelProvider provider =
ViewModelProviders.of(this, viewModelFactory);
navDrawerViewModel = provider.get(NavDrawerViewModel.class);
pluginViewModel = provider.get(PluginViewModel.class);
navDrawerViewModel.showExpiryWarning()
.observe(this, this::showExpiryWarning);
navDrawerViewModel.shouldAskForDozeWhitelisting().observe(this, ask -> {
if (ask) showDozeDialog(getString(R.string.setup_doze_intro));
});
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);
@@ -131,7 +150,7 @@ public class NavDrawerActivity extends BriarActivity implements
drawerLayout.addDrawerListener(drawerToggle); drawerLayout.addDrawerListener(drawerToggle);
navigation.setNavigationItemSelectedListener(this); navigation.setNavigationItemSelectedListener(this);
initializeTransports(getLayoutInflater()); initializeTransports();
transportsView.setAdapter(transportsAdapter); transportsView.setAdapter(transportsAdapter);
lockManager.isLockable().observe(this, this::setLockVisible); lockManager.isLockable().observe(this, this::setLockVisible);
@@ -149,17 +168,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) { navDrawerViewModel.checkExpiryWarning();
@Override
public void onResultUi(Boolean expiry) {
if (expiry) showExpiryWarning();
}
});
} }
@Override @Override
@@ -167,16 +179,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, navDrawerViewModel.checkDozeWhitelisting();
new UiResultHandler<Boolean>(this) {
@Override
public void onResultUi(Boolean ask) {
if (ask) {
showDozeDialog(
getString(R.string.setup_doze_intro));
}
}
});
} }
} }
@@ -346,55 +349,37 @@ 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);
TextView expiryWarningText = if (show) {
expiryWarning.findViewById(R.id.expiryWarningText); // show expiry warning text
// make close button functional TextView expiryWarningText =
ImageView expiryWarningClose = expiryWarning.findViewById(R.id.expiryWarningText);
expiryWarning.findViewById(R.id.expiryWarningClose); String text = getResources().getQuantityString(
R.plurals.expiry_warning, daysUntilExpiry, daysUntilExpiry);
expiryWarningText.setText(getResources() expiryWarningText.setText(text);
.getQuantityString(R.plurals.expiry_warning, // make close button functional
daysUntilExpiry, daysUntilExpiry)); ImageView expiryWarningClose =
expiryWarning.findViewById(R.id.expiryWarningClose);
expiryWarningClose.setOnClickListener(v -> { expiryWarningClose.setOnClickListener(v ->
controller.expiryWarningDismissed(); navDrawerViewModel.expiryWarningDismissed());
expiryWarning.setVisibility(VISIBLE);
} else {
expiryWarning.setVisibility(GONE); expiryWarning.setVisibility(GONE);
}); }
expiryWarning.setVisibility(VISIBLE);
} }
private void initializeTransports(LayoutInflater inflater) { private void initializeTransports() {
transports = new ArrayList<>(3); 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() { transportsAdapter = new BaseAdapter() {
@Override @Override
public int getCount() { public int getCount() {
return transports.size(); return transports.size();
@@ -417,63 +402,67 @@ public class NavDrawerActivity extends BriarActivity implements
if (convertView != null) { if (convertView != null) {
view = convertView; view = convertView;
} else { } else {
LayoutInflater inflater = getLayoutInflater();
view = inflater.inflate(R.layout.list_item_transport, view = inflater.inflate(R.layout.list_item_transport,
parent, false); parent, false);
} }
Transport t = getItem(position); 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); ImageView icon = view.findViewById(R.id.imageView);
icon.setImageDrawable(ContextCompat icon.setImageDrawable(ContextCompat.getDrawable(
.getDrawable(NavDrawerActivity.this, t.iconId)); NavDrawerActivity.this, t.iconDrawable));
icon.setColorFilter(c); icon.setColorFilter(ContextCompat.getColor(
NavDrawerActivity.this, t.iconColor));
TextView text = view.findViewById(R.id.textView); TextView text = view.findViewById(R.id.textView);
text.setText(getString(t.textId)); text.setText(getString(t.label));
return view; return view;
} }
}; };
transports.add(createTransport(TorConstants.ID,
R.drawable.transport_tor, R.string.transport_tor));
transports.add(createTransport(LanTcpConstants.ID,
R.drawable.transport_lan, R.string.transport_lan));
transports.add(createTransport(BluetoothConstants.ID,
R.drawable.transport_bt, R.string.transport_bt));
} }
@UiThread @ColorRes
private void setTransport(TransportId id, boolean enabled) { private int getIconColor(State state) {
if (transports == null || transportsAdapter == null) return; if (state == ACTIVE) return R.color.briar_lime_400;
for (Transport t : transports) { else if (state == ENABLING) return R.color.briar_orange_500;
if (t.id.equals(id)) { else return android.R.color.tertiary_text_light;
t.enabled = enabled;
transportsAdapter.notifyDataSetChanged();
break;
}
}
} }
private void updateTransports() { private Transport createTransport(TransportId id,
if (transports == null || transportsAdapter == null) return; @DrawableRes int iconDrawable, @StringRes int label) {
for (Transport t : transports) { int iconColor = getIconColor(STARTING_STOPPING);
t.enabled = controller.isTransportRunning(t.id); Transport transport = new Transport(iconDrawable, label, iconColor);
} pluginViewModel.getPluginState(id).observe(this, state -> {
transportsAdapter.notifyDataSetChanged(); transport.iconColor = getIconColor(state);
} transportsAdapter.notifyDataSetChanged();
});
@Override return transport;
public void stateUpdate(TransportId id, boolean enabled) {
setTransport(id, enabled);
} }
private static class Transport { private static class Transport {
private TransportId id; @DrawableRes
private boolean enabled; private final int iconDrawable;
private int iconId; @StringRes
private int textId; private final int label;
@ColorRes
private int iconColor;
private Transport(@DrawableRes int iconDrawable, @StringRes int label,
@ColorRes int iconColor) {
this.iconDrawable = iconDrawable;
this.label = label;
this.iconColor = iconColor;
}
} }
} }

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,23 @@
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);
@Binds
@IntoMap
@ViewModelKey(PluginViewModel.class)
abstract ViewModel bindPluginViewModel(PluginViewModel pluginViewModel);
}

View File

@@ -0,0 +1,136 @@
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.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.settings.Settings;
import org.briarproject.bramble.api.settings.SettingsManager;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
import javax.inject.Inject;
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.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;
@NotNullByDefault
public class NavDrawerViewModel extends AndroidViewModel {
private static final Logger LOG =
getLogger(NavDrawerViewModel.class.getName());
private static final String EXPIRY_DATE_WARNING = "expiryDateWarning";
@DatabaseExecutor
private final Executor dbExecutor;
private final SettingsManager settingsManager;
private final MutableLiveData<Boolean> showExpiryWarning =
new MutableLiveData<>();
private final MutableLiveData<Boolean> shouldAskForDozeWhitelisting =
new MutableLiveData<>();
@Inject
NavDrawerViewModel(Application app, @DatabaseExecutor Executor dbExecutor,
SettingsManager settingsManager) {
super(app);
this.dbExecutor = dbExecutor;
this.settingsManager = settingsManager;
}
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);
}
});
}
}

View File

@@ -0,0 +1,92 @@
package org.briarproject.briar.android.navdrawer;
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 java.util.logging.Logger;
import javax.inject.Inject;
import androidx.annotation.Nullable;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
import static java.util.logging.Level.INFO;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.plugin.Plugin.State.STARTING_STOPPING;
@NotNullByDefault
public class PluginViewModel extends ViewModel implements EventListener {
private static final Logger LOG =
getLogger(PluginViewModel.class.getName());
private final PluginManager pluginManager;
private final EventBus eventBus;
private final MutableLiveData<State> torPluginState =
new MutableLiveData<>();
private final MutableLiveData<State> wifiPluginState =
new MutableLiveData<>();
private final MutableLiveData<State> btPluginState =
new MutableLiveData<>();
@Inject
PluginViewModel(PluginManager pluginManager, EventBus eventBus) {
this.pluginManager = pluginManager;
this.eventBus = eventBus;
eventBus.addListener(this);
torPluginState.setValue(getTransportState(TorConstants.ID));
wifiPluginState.setValue(getTransportState(LanTcpConstants.ID));
btPluginState.setValue(getTransportState(BluetoothConstants.ID));
}
@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<State> liveData = getPluginLiveData(id);
if (liveData != null) liveData.postValue(state);
}
}
LiveData<State> getPluginState(TransportId id) {
LiveData<State> liveData = getPluginLiveData(id);
if (liveData == null) throw new IllegalArgumentException();
return liveData;
}
private State getTransportState(TransportId id) {
Plugin plugin = pluginManager.getPlugin(id);
return plugin == null ? STARTING_STOPPING : plugin.getState();
}
@Nullable
private MutableLiveData<State> getPluginLiveData(TransportId id) {
if (id.equals(TorConstants.ID)) return torPluginState;
else if (id.equals(LanTcpConstants.ID)) return wifiPluginState;
else if (id.equals(BluetoothConstants.ID)) return btPluginState;
else return null;
}
}

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

@@ -21,6 +21,8 @@ import org.briarproject.briar.android.logging.BriefLogFormatter;
import java.io.File; import java.io.File;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.Map; import java.util.Map;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
@@ -185,12 +187,18 @@ public class BriarReportPrimer implements ReportPrimer {
WifiInfo wifiInfo = wm.getConnectionInfo(); WifiInfo wifiInfo = wm.getConnectionInfo();
if (wifiInfo != null) { if (wifiInfo != null) {
int ip = wifiInfo.getIpAddress(); // Nice API, Google int ip = wifiInfo.getIpAddress(); // Nice API, Google
int ip1 = ip & 0xFF; byte[] ipBytes = new byte[4];
int ip2 = (ip >> 8) & 0xFF; ipBytes[0] = (byte) (ip & 0xFF);
int ip3 = (ip >> 16) & 0xFF; ipBytes[1] = (byte) ((ip >> 8) & 0xFF);
int ip4 = (ip >> 24) & 0xFF; ipBytes[2] = (byte) ((ip >> 16) & 0xFF);
String address = ip1 + "." + ip2 + "." + ip3 + "." + ip4; ipBytes[3] = (byte) ((ip >> 24) & 0xFF);
customData.put("Wi-Fi address", scrubInetAddress(address)); try {
InetAddress address = InetAddress.getByAddress(ipBytes);
customData.put("Wi-Fi address",
scrubInetAddress(address));
} catch (UnknownHostException ignored) {
// Should only be thrown if address has illegal length
}
} }
} }

View File

@@ -21,6 +21,7 @@ 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.BluetoothConstants;
import org.briarproject.bramble.api.plugin.LanTcpConstants;
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 +42,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 +74,14 @@ 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.DEFAULT_PREF_TOR_MOBILE;
import static org.briarproject.bramble.api.plugin.TorConstants.DEFAULT_PREF_TOR_NETWORK;
import static org.briarproject.bramble.api.plugin.TorConstants.DEFAULT_PREF_TOR_ONLY_WHEN_CHARGING;
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 +90,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 +112,24 @@ 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 BT_NAMESPACE =
public static final String TOR_ONLY_WHEN_CHARGING = BluetoothConstants.ID.getString();
private static final String BT_ENABLE = "pref_key_bluetooth";
private static final String WIFI_NAMESPACE = LanTcpConstants.ID.getString();
private static final String WIFI_ENABLE = "pref_key_wifi";
private static final String TOR_NAMESPACE = TorConstants.ID.getString();
private static final String TOR_ENABLE = "pref_key_tor_enable";
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 +137,9 @@ public class SettingsFragment extends PreferenceFragmentCompat
private SettingsActivity listener; private SettingsActivity listener;
private ListPreference language; private ListPreference language;
private ListPreference enableBluetooth; private SwitchPreference enableBluetooth;
private SwitchPreference enableWifi;
private SwitchPreference enableTor;
private ListPreference torNetwork; private ListPreference torNetwork;
private SwitchPreference torMobile; private SwitchPreference torMobile;
private SwitchPreference torOnlyWhenCharging; private SwitchPreference torOnlyWhenCharging;
@@ -137,7 +154,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, btSettings, wifiSettings, torSettings;
private volatile boolean settingsLoaded = false; private volatile boolean settingsLoaded = false;
@Inject @Inject
@@ -163,28 +180,23 @@ 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"); enableBluetooth = findPreference(BT_ENABLE);
enableBluetooth = (ListPreference) findPreference("pref_key_bluetooth"); enableWifi = findPreference(WIFI_ENABLE);
torNetwork = (ListPreference) findPreference(TOR_NETWORK); enableTor = findPreference(TOR_ENABLE);
torMobile = (SwitchPreference) findPreference(TOR_MOBILE); torNetwork = findPreference(TOR_NETWORK);
torOnlyWhenCharging = torMobile = findPreference(TOR_MOBILE);
(SwitchPreference) findPreference(TOR_ONLY_WHEN_CHARGING); torOnlyWhenCharging = findPreference(TOR_ONLY_WHEN_CHARGING);
screenLock = (SwitchPreference) findPreference(PREF_SCREEN_LOCK); screenLock = findPreference(PREF_SCREEN_LOCK);
screenLockTimeout = screenLockTimeout = findPreference(PREF_SCREEN_LOCK_TIMEOUT);
(ListPreference) findPreference(PREF_SCREEN_LOCK_TIMEOUT); notifyPrivateMessages =
notifyPrivateMessages = (SwitchPreference) findPreference( findPreference("pref_key_notify_private_messages");
"pref_key_notify_private_messages"); notifyGroupMessages = findPreference("pref_key_notify_group_messages");
notifyGroupMessages = (SwitchPreference) findPreference( notifyForumPosts = findPreference("pref_key_notify_forum_posts");
"pref_key_notify_group_messages"); notifyBlogPosts = findPreference("pref_key_notify_blog_posts");
notifyForumPosts = (SwitchPreference) findPreference( notifyVibration = findPreference("pref_key_notify_vibration");
"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 +206,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);
@@ -207,6 +218,8 @@ public class SettingsFragment extends PreferenceFragmentCompat
return true; return true;
}); });
enableBluetooth.setOnPreferenceChangeListener(this); enableBluetooth.setOnPreferenceChangeListener(this);
enableWifi.setOnPreferenceChangeListener(this);
enableTor.setOnPreferenceChangeListener(this);
torNetwork.setOnPreferenceChangeListener(this); torNetwork.setOnPreferenceChangeListener(this);
torMobile.setOnPreferenceChangeListener(this); torMobile.setOnPreferenceChangeListener(this);
torOnlyWhenCharging.setOnPreferenceChangeListener(this); torOnlyWhenCharging.setOnPreferenceChangeListener(this);
@@ -249,8 +262,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 +339,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) {
@@ -352,6 +362,7 @@ public class SettingsFragment extends PreferenceFragmentCompat
long start = now(); long start = now();
settings = settingsManager.getSettings(SETTINGS_NAMESPACE); settings = settingsManager.getSettings(SETTINGS_NAMESPACE);
btSettings = settingsManager.getSettings(BT_NAMESPACE); btSettings = settingsManager.getSettings(BT_NAMESPACE);
wifiSettings = settingsManager.getSettings(WIFI_NAMESPACE);
torSettings = settingsManager.getSettings(TOR_NAMESPACE); torSettings = settingsManager.getSettings(TOR_NAMESPACE);
settingsLoaded = true; settingsLoaded = true;
logDuration(LOG, "Loading settings", start); logDuration(LOG, "Loading settings", start);
@@ -362,26 +373,50 @@ public class SettingsFragment extends PreferenceFragmentCompat
}); });
} }
// TODO: Remove after a reasonable migration period (added 2020-06-25)
private Settings migrateTorSettings(Settings s) {
int network = s.getInt(PREF_TOR_NETWORK, DEFAULT_PREF_TOR_NETWORK);
if (network == PREF_TOR_NETWORK_NEVER) {
s.putInt(PREF_TOR_NETWORK, DEFAULT_PREF_TOR_NETWORK);
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 = boolean btEnabledSetting = btSettings.getBoolean(PREF_PLUGIN_ENABLE,
btSettings.getBoolean(PREF_BT_ENABLE, false); BluetoothConstants.DEFAULT_PREF_PLUGIN_ENABLE);
enableBluetooth.setValue(Boolean.toString(btEnabledSetting)); enableBluetooth.setChecked(btEnabledSetting);
boolean wifiEnabledSetting =
wifiSettings.getBoolean(PREF_PLUGIN_ENABLE,
LanTcpConstants.DEFAULT_PREF_PLUGIN_ENABLE);
enableWifi.setChecked(wifiEnabledSetting);
boolean torEnabledSetting =
torSettings.getBoolean(PREF_PLUGIN_ENABLE,
TorConstants.DEFAULT_PREF_PLUGIN_ENABLE);
enableTor.setChecked(torEnabledSetting);
int torNetworkSetting = torSettings.getInt(PREF_TOR_NETWORK, int torNetworkSetting = torSettings.getInt(PREF_TOR_NETWORK,
PREF_TOR_NETWORK_AUTOMATIC); DEFAULT_PREF_TOR_NETWORK);
torNetwork.setValue(Integer.toString(torNetworkSetting)); torNetwork.setValue(Integer.toString(torNetworkSetting));
setTorNetworkSummary(torNetworkSetting); setTorNetworkSummary(torNetworkSetting);
boolean torMobileSetting = boolean torMobileSetting = torSettings.getBoolean(PREF_TOR_MOBILE,
torSettings.getBoolean(PREF_TOR_MOBILE, true); DEFAULT_PREF_TOR_MOBILE);
torMobile.setChecked(torMobileSetting); torMobile.setChecked(torMobileSetting);
boolean torChargingSetting = boolean torChargingSetting =
torSettings.getBoolean(PREF_TOR_ONLY_WHEN_CHARGING, false); torSettings.getBoolean(PREF_TOR_ONLY_WHEN_CHARGING,
DEFAULT_PREF_TOR_ONLY_WHEN_CHARGING);
torOnlyWhenCharging.setChecked(torChargingSetting); torOnlyWhenCharging.setChecked(torChargingSetting);
displayScreenLockSetting(); displayScreenLockSetting();
@@ -443,6 +478,8 @@ public class SettingsFragment extends PreferenceFragmentCompat
// - pref_key_lock (screenLock -> displayScreenLockSetting()) // - pref_key_lock (screenLock -> displayScreenLockSetting())
// - pref_key_lock_timeout (screenLockTimeout) // - pref_key_lock_timeout (screenLockTimeout)
enableBluetooth.setEnabled(enabled); enableBluetooth.setEnabled(enabled);
enableWifi.setEnabled(enabled);
enableTor.setEnabled(enabled);
torNetwork.setEnabled(enabled); torNetwork.setEnabled(enabled);
torMobile.setEnabled(enabled); torMobile.setEnabled(enabled);
torOnlyWhenCharging.setEnabled(enabled); torOnlyWhenCharging.setEnabled(enabled);
@@ -545,8 +582,14 @@ public class SettingsFragment extends PreferenceFragmentCompat
languageChanged((String) newValue); languageChanged((String) newValue);
return false; return false;
} else if (preference == enableBluetooth) { } else if (preference == enableBluetooth) {
boolean btSetting = Boolean.valueOf((String) newValue); boolean btSetting = (Boolean) newValue;
storeBluetoothSettings(btSetting); storeBluetoothSetting(btSetting);
} else if (preference == enableWifi) {
boolean wifiSetting = (Boolean) newValue;
storeWifiSetting(wifiSetting);
} else if (preference == enableTor) {
boolean torEnabledSetting = (Boolean) newValue;
storeTorEnabledSetting(torEnabledSetting);
} else if (preference == torNetwork) { } else if (preference == torNetwork) {
int torNetworkSetting = Integer.valueOf((String) newValue); int torNetworkSetting = Integer.valueOf((String) newValue);
storeTorNetworkSetting(torNetworkSetting); storeTorNetworkSetting(torNetworkSetting);
@@ -610,6 +653,12 @@ public class SettingsFragment extends PreferenceFragmentCompat
builder.show(); builder.show();
} }
private void storeTorEnabledSetting(boolean torEnabledSetting) {
Settings s = new Settings();
s.putBoolean(PREF_PLUGIN_ENABLE, torEnabledSetting);
mergeSettings(s, TOR_NAMESPACE);
}
private void storeTorNetworkSetting(int torNetworkSetting) { private void storeTorNetworkSetting(int torNetworkSetting) {
Settings s = new Settings(); Settings s = new Settings();
s.putInt(PREF_TOR_NETWORK, torNetworkSetting); s.putInt(PREF_TOR_NETWORK, torNetworkSetting);
@@ -628,12 +677,18 @@ public class SettingsFragment extends PreferenceFragmentCompat
mergeSettings(s, TOR_NAMESPACE); mergeSettings(s, TOR_NAMESPACE);
} }
private void storeBluetoothSettings(boolean btSetting) { private void storeBluetoothSetting(boolean btSetting) {
Settings s = new Settings(); Settings s = new Settings();
s.putBoolean(PREF_BT_ENABLE, btSetting); s.putBoolean(PREF_PLUGIN_ENABLE, btSetting);
mergeSettings(s, BT_NAMESPACE); mergeSettings(s, BT_NAMESPACE);
} }
private void storeWifiSetting(boolean wifiSetting) {
Settings s = new Settings();
s.putBoolean(PREF_PLUGIN_ENABLE, wifiSetting);
mergeSettings(s, WIFI_NAMESPACE);
}
private void storeSettings(Settings s) { private void storeSettings(Settings s) {
mergeSettings(s, SETTINGS_NAMESPACE); mergeSettings(s, SETTINGS_NAMESPACE);
} }
@@ -696,9 +751,13 @@ public class SettingsFragment extends PreferenceFragmentCompat
LOG.info("Bluetooth settings updated"); LOG.info("Bluetooth settings updated");
btSettings = s.getSettings(); btSettings = s.getSettings();
displaySettings(); displaySettings();
} else if (namespace.equals(WIFI_NAMESPACE)) {
LOG.info("Wifi settings updated");
wifiSettings = 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

@@ -20,7 +20,7 @@ public class BriarNotificationBuilder extends NotificationCompat.Builder {
// https://issuetracker.google.com/issues/36961721 // https://issuetracker.google.com/issues/36961721
setAutoCancel(true); setAutoCancel(true);
setLights(ContextCompat.getColor(context, R.color.briar_green_light), setLights(ContextCompat.getColor(context, R.color.briar_lime_400),
750, 500); 750, 500);
if (SDK_INT >= 21) setVisibility(VISIBILITY_PRIVATE); if (SDK_INT >= 21) setVisibility(VISIBILITY_PRIVATE);
} }

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;
@@ -401,4 +408,20 @@ public class UiUtils {
return ctx.getResources().getConfiguration().getLayoutDirection() == return ctx.getResources().getConfiguration().getLayoutDirection() ==
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

@@ -11,7 +11,7 @@
C7.58,20,4,16.42,4,12 S7.58,4,12,4 S20,7.58,20,12 S16.42,20,12,20 Z"/> C7.58,20,4,16.42,4,12 S7.58,4,12,4 S20,7.58,20,12 S16.42,20,12,20 Z"/>
<path <path
android:fillColor="#95d220" android:fillColor="#82c91e"
android:pathData="M10.8972,19.9503 C6.5514,19.3493,3.43091,15.2154,4.0625,10.896 android:pathData="M10.8972,19.9503 C6.5514,19.3493,3.43091,15.2154,4.0625,10.896
C4.55452,7.53099,7.09451,4.8236,10.394,4.14714 C4.55452,7.53099,7.09451,4.8236,10.394,4.14714
C14.2569,3.35517,18.1698,5.54347,19.5236,9.25295 C14.2569,3.35517,18.1698,5.54347,19.5236,9.25295

View File

@@ -17,12 +17,12 @@
android:fillColor="#ffffff" android:fillColor="#ffffff"
android:pathData="M175.3 16.5l-9.8 0 0 -5.7c0 -1.4 -1.2 -2.6 -2.6 -2.6l-19.3 0c-1.4 0 -2.6 1.2 -2.6 2.6l0 14.4c0 1.4 1.2 2.6 2.6 2.6l15.1 0 0 17.3c0 2.4 2 4.4 4.4 4.4l12.2 0c2.4 0 4.4 -2 4.4 -4.4l0 -24.2c0.1 -2.4 -1.9 -4.4 -4.4 -4.4zm-12.4 -5.9l-9.6 6 -9.6 -6 19.2 0zm-19.4 14.8l0 -12.3 9.8 6.1 9.8 -6.1 0 12.3 -19.6 0zm28.6 21.2l-5.8 0 0 -1.5 5.8 0 0 1.5zm5 -4.6l-15.8 0 0 -14.2 1.6 0c1.4 0 2.6 -1.2 2.6 -2.6l0 -4.1 11.6 0 0 20.9z"/> android:pathData="M175.3 16.5l-9.8 0 0 -5.7c0 -1.4 -1.2 -2.6 -2.6 -2.6l-19.3 0c-1.4 0 -2.6 1.2 -2.6 2.6l0 14.4c0 1.4 1.2 2.6 2.6 2.6l15.1 0 0 17.3c0 2.4 2 4.4 4.4 4.4l12.2 0c2.4 0 4.4 -2 4.4 -4.4l0 -24.2c0.1 -2.4 -1.9 -4.4 -4.4 -4.4zm-12.4 -5.9l-9.6 6 -9.6 -6 19.2 0zm-19.4 14.8l0 -12.3 9.8 6.1 9.8 -6.1 0 12.3 -19.6 0zm28.6 21.2l-5.8 0 0 -1.5 5.8 0 0 1.5zm5 -4.6l-15.8 0 0 -14.2 1.6 0c1.4 0 2.6 -1.2 2.6 -2.6l0 -4.1 11.6 0 0 20.9z"/>
<path <path
android:fillColor="#ff0000" android:fillColor="#db3b21"
android:pathData="M101.4 17.8l2 2 7.4 -7.3 7.3 7.3 2.1 -2 -7.4 -7.4 7.4 -7.3 -2.1 -2.1 -7.3 7.4 -7.4 -7.4 -2 2.1 7.3 7.3z"/> android:pathData="M101.4 17.8l2 2 7.4 -7.3 7.3 7.3 2.1 -2 -7.4 -7.4 7.4 -7.3 -2.1 -2.1 -7.3 7.4 -7.4 -7.4 -2 2.1 7.3 7.3z"/>
<path <path
android:fillColor="#ff0000" android:fillColor="#db3b21"
android:pathData="M176 17.8l2.1 2 7.3 -7.3 7.4 7.3 2 -2 -7.3 -7.4 7.3 -7.3 -2 -2.1 -7.4 7.4 -7.3 -7.4 -2.1 2.1 7.3 7.3z"/> android:pathData="M176 17.8l2.1 2 7.3 -7.3 7.4 7.3 2 -2 -7.3 -7.4 7.3 -7.3 -2 -2.1 -7.4 7.4 -7.3 -7.4 -2.1 2.1 7.3 7.3z"/>
<path <path
android:fillColor="#08b124" android:fillColor="#67a60f"
android:pathData="M35.8 18.8l0 0L52.5 2.1 50.5 0 35.6 14.8 28.5 7.7l-2.1 2.1 9.2 9.1z"/> android:pathData="M35.8 18.8l0 0L52.5 2.1 50.5 0 35.6 14.8 28.5 7.7l-2.1 2.1 9.2 9.1z"/>
</vector> </vector>

View File

@@ -7,7 +7,7 @@
android:radius="32dp"/> android:radius="32dp"/>
<solid <solid
android:color="@color/briar_green"/> android:color="@color/briar_lime_600"/>
</shape> </shape>

View File

@@ -13,7 +13,7 @@ C7.58,20,4,16.42,4,12 S7.58,4,12,4 S20,7.58,20,12 S16.42,20,12,20 Z"/>
<path <path
android:pathData="M0,0 L24,0 L24,24 L0,24 Z"/> android:pathData="M0,0 L24,0 L24,24 L0,24 Z"/>
<path <path
android:fillColor="#95d220" android:fillColor="#82c91e"
android:pathData="M10.8972,19.9503 C6.5514,19.3493,3.43091,15.2154,4.0625,10.896 android:pathData="M10.8972,19.9503 C6.5514,19.3493,3.43091,15.2154,4.0625,10.896
C4.55452,7.53099,7.09451,4.8236,10.394,4.14714 C4.55452,7.53099,7.09451,4.8236,10.394,4.14714
C14.2569,3.35517,18.1698,5.54347,19.5236,9.25295 C14.2569,3.35517,18.1698,5.54347,19.5236,9.25295

View File

@@ -6,7 +6,7 @@
android:viewportWidth="24"> android:viewportWidth="24">
<path <path
android:fillColor="#2D3E50" android:fillColor="#2e3d4f"
android:pathData="M10.8972,19.9503 C6.5514,19.3493,3.43091,15.2154,4.0625,10.896 android:pathData="M10.8972,19.9503 C6.5514,19.3493,3.43091,15.2154,4.0625,10.896
C4.55452,7.53099,7.09451,4.8236,10.394,4.14714 C4.55452,7.53099,7.09451,4.8236,10.394,4.14714
C14.2569,3.35517,18.1698,5.54347,19.5236,9.25295 C14.2569,3.35517,18.1698,5.54347,19.5236,9.25295

View File

@@ -6,7 +6,7 @@
android:viewportWidth="24"> android:viewportWidth="24">
<path <path
android:fillColor="#95D220" android:fillColor="#82c91e"
android:pathData="M10.8972,19.9503 C6.5514,19.3493,3.43091,15.2154,4.0625,10.896 android:pathData="M10.8972,19.9503 C6.5514,19.3493,3.43091,15.2154,4.0625,10.896
C4.55452,7.53099,7.09451,4.8236,10.394,4.14714 C4.55452,7.53099,7.09451,4.8236,10.394,4.14714
C14.2569,3.35517,18.1698,5.54347,19.5236,9.25295 C14.2569,3.35517,18.1698,5.54347,19.5236,9.25295

View File

@@ -1,7 +1,7 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" <vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp" android:width="24dp"
android:height="24dp" android:height="24dp"
android:tint="#2A93C6" android:tint="#418cd8"
android:viewportHeight="24.0" android:viewportHeight="24.0"
android:viewportWidth="24.0"> android:viewportWidth="24.0">
<path <path

View File

@@ -1,7 +1,7 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" <vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp" android:width="24dp"
android:height="24dp" android:height="24dp"
android:tint="#2A93C6" android:tint="#418cd8"
android:viewportHeight="24.0" android:viewportHeight="24.0"
android:viewportWidth="24.0"> android:viewportWidth="24.0">
<path <path

View File

@@ -4,6 +4,6 @@
android:viewportHeight="24.0" android:viewportHeight="24.0"
android:viewportWidth="24.0"> android:viewportWidth="24.0">
<path <path
android:fillColor="#808080" android:fillColor="#707070"
android:pathData="M21,5v6.59l-3,-3.01 -4,4.01 -4,-4 -4,4 -3,-3.01L3,5c0,-1.1 0.9,-2 2,-2h14c1.1,0 2,0.9 2,2zM18,11.42l3,3.01L21,19c0,1.1 -0.9,2 -2,2L5,21c-1.1,0 -2,-0.9 -2,-2v-6.58l3,2.99 4,-4 4,4 4,-3.99z"/> android:pathData="M21,5v6.59l-3,-3.01 -4,4.01 -4,-4 -4,4 -3,-3.01L3,5c0,-1.1 0.9,-2 2,-2h14c1.1,0 2,0.9 2,2zM18,11.42l3,3.01L21,19c0,1.1 -0.9,2 -2,2L5,21c-1.1,0 -2,-0.9 -2,-2v-6.58l3,2.99 4,-4 4,4 4,-3.99z"/>
</vector> </vector>

View File

@@ -4,6 +4,6 @@
android:viewportHeight="24.0" android:viewportHeight="24.0"
android:viewportWidth="24.0"> android:viewportWidth="24.0">
<path <path
android:fillColor="#FF2D3E50" android:fillColor="#2e3d4f"
android:pathData="M12,8c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2zM12,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM12,16c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2z"/> android:pathData="M12,8c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2zM12,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM12,16c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2z"/>
</vector> </vector>

View File

@@ -6,7 +6,7 @@
android:viewportWidth="30"> android:viewportWidth="30">
<path <path
android:fillColor="#ffa500" android:fillColor="#fc9403"
android:pathData="M0,0 L30,0 L30,30 L0,30 L0,0 Z"/> android:pathData="M0,0 L30,0 L30,30 L0,30 L0,0 Z"/>
<path <path
android:fillColor="#ffffff" android:fillColor="#ffffff"

View File

@@ -18,12 +18,12 @@
android:fillColor="#000000" android:fillColor="#000000"
android:pathData="M175.3 16.5l-9.8 0 0 -5.7c0 -1.4 -1.2 -2.6 -2.6 -2.6l-19.3 0c-1.4 0 -2.6 1.2 -2.6 2.6l0 14.4c0 1.4 1.2 2.6 2.6 2.6l15.1 0 0 17.3c0 2.4 2 4.4 4.4 4.4l12.2 0c2.4 0 4.4 -2 4.4 -4.4l0 -24.2c0.1 -2.4 -1.9 -4.4 -4.4 -4.4zm-12.4 -5.9l-9.6 6 -9.6 -6 19.2 0zm-19.4 14.8l0 -12.3 9.8 6.1 9.8 -6.1 0 12.3 -19.6 0zm28.6 21.2l-5.8 0 0 -1.5 5.8 0 0 1.5zm5 -4.6l-15.8 0 0 -14.2 1.6 0c1.4 0 2.6 -1.2 2.6 -2.6l0 -4.1 11.6 0 0 20.9z"/> android:pathData="M175.3 16.5l-9.8 0 0 -5.7c0 -1.4 -1.2 -2.6 -2.6 -2.6l-19.3 0c-1.4 0 -2.6 1.2 -2.6 2.6l0 14.4c0 1.4 1.2 2.6 2.6 2.6l15.1 0 0 17.3c0 2.4 2 4.4 4.4 4.4l12.2 0c2.4 0 4.4 -2 4.4 -4.4l0 -24.2c0.1 -2.4 -1.9 -4.4 -4.4 -4.4zm-12.4 -5.9l-9.6 6 -9.6 -6 19.2 0zm-19.4 14.8l0 -12.3 9.8 6.1 9.8 -6.1 0 12.3 -19.6 0zm28.6 21.2l-5.8 0 0 -1.5 5.8 0 0 1.5zm5 -4.6l-15.8 0 0 -14.2 1.6 0c1.4 0 2.6 -1.2 2.6 -2.6l0 -4.1 11.6 0 0 20.9z"/>
<path <path
android:fillColor="#ff0000" android:fillColor="#db3b21"
android:pathData="M101.4 17.8l2 2 7.4 -7.3 7.3 7.3 2.1 -2 -7.4 -7.4 7.4 -7.3 -2.1 -2.1 -7.3 7.4 -7.4 -7.4 -2 2.1 7.3 7.3z"/> android:pathData="M101.4 17.8l2 2 7.4 -7.3 7.3 7.3 2.1 -2 -7.4 -7.4 7.4 -7.3 -2.1 -2.1 -7.3 7.4 -7.4 -7.4 -2 2.1 7.3 7.3z"/>
<path <path
android:fillColor="#ff0000" android:fillColor="#db3b21"
android:pathData="M176 17.8l2.1 2 7.3 -7.3 7.4 7.3 2 -2 -7.3 -7.4 7.3 -7.3 -2 -2.1 -7.4 7.4 -7.3 -7.4 -2.1 2.1 7.3 7.3z"/> android:pathData="M176 17.8l2.1 2 7.3 -7.3 7.4 7.3 2 -2 -7.3 -7.4 7.3 -7.3 -2 -2.1 -7.4 7.4 -7.3 -7.4 -2.1 2.1 7.3 7.3z"/>
<path <path
android:fillColor="#08b124" android:fillColor="#67a60f"
android:pathData="M35.8 18.8l0 0L52.5 2.1 50.5 0 35.6 14.8 28.5 7.7l-2.1 2.1 9.2 9.1z"/> android:pathData="M35.8 18.8l0 0L52.5 2.1 50.5 0 35.6 14.8 28.5 7.7l-2.1 2.1 9.2 9.1z"/>
</vector> </vector>

View File

@@ -1,7 +1,7 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" <vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp" android:width="24dp"
android:height="24dp" android:height="24dp"
android:tint="#2A93C6" android:tint="#418cd8"
android:viewportHeight="24.0" android:viewportHeight="24.0"
android:viewportWidth="24.0"> android:viewportWidth="24.0">
<path <path

View File

@@ -5,7 +5,7 @@
android:viewportHeight="20" android:viewportHeight="20"
android:viewportWidth="49"> android:viewportWidth="49">
<path <path
android:fillColor="#b7b7b7" android:fillColor="#a7a7a7"
android:pathData="M15.5002,13.8797 L15.5002,11.8208 L12.9502,11.8208 L12.9502,8.38194 android:pathData="M15.5002,13.8797 L15.5002,11.8208 L12.9502,11.8208 L12.9502,8.38194
L15.5002,8.38194 L15.5002,6.32312 L12.9502,6.32312 L12.9502,2.49959 L15.5002,8.38194 L15.5002,6.32312 L12.9502,6.32312 L12.9502,2.49959
L10.5752,2.49959 L10.5752,6.32312 L7.42514,6.32312 L7.42514,2.49959 L10.5752,2.49959 L10.5752,6.32312 L7.42514,6.32312 L7.42514,2.49959
@@ -16,7 +16,7 @@ L10.5752,13.8797 L10.5752,17.4996 L12.9502,17.4996 L12.9502,13.8797
L15.5002,13.8797 Z M10.5752,11.8208 L7.42514,11.8208 L7.42514,8.38194 L15.5002,13.8797 Z M10.5752,11.8208 L7.42514,11.8208 L7.42514,8.38194
L10.5752,8.38194 L10.5752,11.8208 Z"/> L10.5752,8.38194 L10.5752,11.8208 Z"/>
<path <path
android:fillColor="#b7b7b7" android:fillColor="#a7a7a7"
android:pathData="M31.0002,13.8797 L31.0002,11.8208 L28.4502,11.8208 L28.4502,8.38194 android:pathData="M31.0002,13.8797 L31.0002,11.8208 L28.4502,11.8208 L28.4502,8.38194
L31.0002,8.38194 L31.0002,6.32312 L28.4502,6.32312 L28.4502,2.49959 L31.0002,8.38194 L31.0002,6.32312 L28.4502,6.32312 L28.4502,2.49959
L26.0752,2.49959 L26.0752,6.32312 L22.9251,6.32312 L22.9251,2.49959 L26.0752,2.49959 L26.0752,6.32312 L22.9251,6.32312 L22.9251,2.49959
@@ -27,7 +27,7 @@ L26.0752,13.8797 L26.0752,17.4996 L28.4502,17.4996 L28.4502,13.8797
L31.0002,13.8797 Z M26.0752,11.8208 L22.9251,11.8208 L22.9251,8.38194 L31.0002,13.8797 Z M26.0752,11.8208 L22.9251,11.8208 L22.9251,8.38194
L26.0752,8.38194 L26.0752,11.8208 Z"/> L26.0752,8.38194 L26.0752,11.8208 Z"/>
<path <path
android:fillColor="#b7b7b7" android:fillColor="#a7a7a7"
android:pathData="M46.5002,13.8797 L46.5002,11.8208 L43.9502,11.8208 L43.9502,8.38194 android:pathData="M46.5002,13.8797 L46.5002,11.8208 L43.9502,11.8208 L43.9502,8.38194
L46.5002,8.38194 L46.5002,6.32312 L43.9502,6.32312 L43.9502,2.49959 L46.5002,8.38194 L46.5002,6.32312 L43.9502,6.32312 L43.9502,2.49959
L41.5752,2.49959 L41.5752,6.32312 L38.4251,6.32312 L38.4251,2.49959 L41.5752,2.49959 L41.5752,6.32312 L38.4251,6.32312 L38.4251,2.49959

View File

@@ -5,7 +5,7 @@
android:viewportHeight="20" android:viewportHeight="20"
android:viewportWidth="49"> android:viewportWidth="49">
<path <path
android:fillColor="#c34032" android:fillColor="#db3b21"
android:pathData="M15.5002,13.8797 L15.5002,11.8208 L12.9502,11.8208 L12.9502,8.38194 android:pathData="M15.5002,13.8797 L15.5002,11.8208 L12.9502,11.8208 L12.9502,8.38194
L15.5002,8.38194 L15.5002,6.32312 L12.9502,6.32312 L12.9502,2.49959 L15.5002,8.38194 L15.5002,6.32312 L12.9502,6.32312 L12.9502,2.49959
L10.5752,2.49959 L10.5752,6.32312 L7.42514,6.32312 L7.42514,2.49959 L10.5752,2.49959 L10.5752,6.32312 L7.42514,6.32312 L7.42514,2.49959
@@ -16,7 +16,7 @@ L10.5752,13.8797 L10.5752,17.4996 L12.9502,17.4996 L12.9502,13.8797
L15.5002,13.8797 Z M10.5752,11.8208 L7.42514,11.8208 L7.42514,8.38194 L15.5002,13.8797 Z M10.5752,11.8208 L7.42514,11.8208 L7.42514,8.38194
L10.5752,8.38194 L10.5752,11.8208 Z"/> L10.5752,8.38194 L10.5752,11.8208 Z"/>
<path <path
android:fillColor="#b7b7b7" android:fillColor="#a7a7a7"
android:pathData="M31.0002,13.8797 L31.0002,11.8208 L28.4502,11.8208 L28.4502,8.38194 android:pathData="M31.0002,13.8797 L31.0002,11.8208 L28.4502,11.8208 L28.4502,8.38194
L31.0002,8.38194 L31.0002,6.32312 L28.4502,6.32312 L28.4502,2.49959 L31.0002,8.38194 L31.0002,6.32312 L28.4502,6.32312 L28.4502,2.49959
L26.0752,2.49959 L26.0752,6.32312 L22.9251,6.32312 L22.9251,2.49959 L26.0752,2.49959 L26.0752,6.32312 L22.9251,6.32312 L22.9251,2.49959
@@ -27,7 +27,7 @@ L26.0752,13.8797 L26.0752,17.4996 L28.4502,17.4996 L28.4502,13.8797
L31.0002,13.8797 Z M26.0752,11.8208 L22.9251,11.8208 L22.9251,8.38194 L31.0002,13.8797 Z M26.0752,11.8208 L22.9251,11.8208 L22.9251,8.38194
L26.0752,8.38194 L26.0752,11.8208 Z"/> L26.0752,8.38194 L26.0752,11.8208 Z"/>
<path <path
android:fillColor="#b7b7b7" android:fillColor="#a7a7a7"
android:pathData="M46.5002,13.8797 L46.5002,11.8208 L43.9502,11.8208 L43.9502,8.38194 android:pathData="M46.5002,13.8797 L46.5002,11.8208 L43.9502,11.8208 L43.9502,8.38194
L46.5002,8.38194 L46.5002,6.32312 L43.9502,6.32312 L43.9502,2.49959 L46.5002,8.38194 L46.5002,6.32312 L43.9502,6.32312 L43.9502,2.49959
L41.5752,2.49959 L41.5752,6.32312 L38.4251,6.32312 L38.4251,2.49959 L41.5752,2.49959 L41.5752,6.32312 L38.4251,6.32312 L38.4251,2.49959

View File

@@ -5,7 +5,7 @@
android:viewportHeight="20" android:viewportHeight="20"
android:viewportWidth="49"> android:viewportWidth="49">
<path <path
android:fillColor="#fcd53a" android:fillColor="#fc9403"
android:pathData="M15.5002,13.8797 L15.5002,11.8208 L12.9502,11.8208 L12.9502,8.38194 android:pathData="M15.5002,13.8797 L15.5002,11.8208 L12.9502,11.8208 L12.9502,8.38194
L15.5002,8.38194 L15.5002,6.32312 L12.9502,6.32312 L12.9502,2.49959 L15.5002,8.38194 L15.5002,6.32312 L12.9502,6.32312 L12.9502,2.49959
L10.5752,2.49959 L10.5752,6.32312 L7.42514,6.32312 L7.42514,2.49959 L10.5752,2.49959 L10.5752,6.32312 L7.42514,6.32312 L7.42514,2.49959
@@ -16,7 +16,7 @@ L10.5752,13.8797 L10.5752,17.4996 L12.9502,17.4996 L12.9502,13.8797
L15.5002,13.8797 Z M10.5752,11.8208 L7.42514,11.8208 L7.42514,8.38194 L15.5002,13.8797 Z M10.5752,11.8208 L7.42514,11.8208 L7.42514,8.38194
L10.5752,8.38194 L10.5752,11.8208 Z"/> L10.5752,8.38194 L10.5752,11.8208 Z"/>
<path <path
android:fillColor="#fcd53a" android:fillColor="#fc9403"
android:pathData="M31.0002,13.8797 L31.0002,11.8208 L28.4502,11.8208 L28.4502,8.38194 android:pathData="M31.0002,13.8797 L31.0002,11.8208 L28.4502,11.8208 L28.4502,8.38194
L31.0002,8.38194 L31.0002,6.32312 L28.4502,6.32312 L28.4502,2.49959 L31.0002,8.38194 L31.0002,6.32312 L28.4502,6.32312 L28.4502,2.49959
L26.0752,2.49959 L26.0752,6.32312 L22.9251,6.32312 L22.9251,2.49959 L26.0752,2.49959 L26.0752,6.32312 L22.9251,6.32312 L22.9251,2.49959
@@ -27,7 +27,7 @@ L26.0752,13.8797 L26.0752,17.4996 L28.4502,17.4996 L28.4502,13.8797
L31.0002,13.8797 Z M26.0752,11.8208 L22.9251,11.8208 L22.9251,8.38194 L31.0002,13.8797 Z M26.0752,11.8208 L22.9251,11.8208 L22.9251,8.38194
L26.0752,8.38194 L26.0752,11.8208 Z"/> L26.0752,8.38194 L26.0752,11.8208 Z"/>
<path <path
android:fillColor="#b7b7b7" android:fillColor="#a7a7a7"
android:pathData="M46.5002,13.8797 L46.5002,11.8208 L43.9502,11.8208 L43.9502,8.38194 android:pathData="M46.5002,13.8797 L46.5002,11.8208 L43.9502,11.8208 L43.9502,8.38194
L46.5002,8.38194 L46.5002,6.32312 L43.9502,6.32312 L43.9502,2.49959 L46.5002,8.38194 L46.5002,6.32312 L43.9502,6.32312 L43.9502,2.49959
L41.5752,2.49959 L41.5752,6.32312 L38.4251,6.32312 L38.4251,2.49959 L41.5752,2.49959 L41.5752,6.32312 L38.4251,6.32312 L38.4251,2.49959

View File

@@ -5,7 +5,7 @@
android:viewportHeight="20" android:viewportHeight="20"
android:viewportWidth="49"> android:viewportWidth="49">
<path <path
android:fillColor="#7fac49" android:fillColor="#67a60f"
android:pathData="M15.5002,13.8797 L15.5002,11.8208 L12.9502,11.8208 L12.9502,8.38194 android:pathData="M15.5002,13.8797 L15.5002,11.8208 L12.9502,11.8208 L12.9502,8.38194
L15.5002,8.38194 L15.5002,6.32312 L12.9502,6.32312 L12.9502,2.49959 L15.5002,8.38194 L15.5002,6.32312 L12.9502,6.32312 L12.9502,2.49959
L10.5752,2.49959 L10.5752,6.32312 L7.42514,6.32312 L7.42514,2.49959 L10.5752,2.49959 L10.5752,6.32312 L7.42514,6.32312 L7.42514,2.49959
@@ -16,7 +16,7 @@ L10.5752,13.8797 L10.5752,17.4996 L12.9502,17.4996 L12.9502,13.8797
L15.5002,13.8797 Z M10.5752,11.8208 L7.42514,11.8208 L7.42514,8.38194 L15.5002,13.8797 Z M10.5752,11.8208 L7.42514,11.8208 L7.42514,8.38194
L10.5752,8.38194 L10.5752,11.8208 Z"/> L10.5752,8.38194 L10.5752,11.8208 Z"/>
<path <path
android:fillColor="#7fac49" android:fillColor="#67a60f"
android:pathData="M31.0002,13.8797 L31.0002,11.8208 L28.4502,11.8208 L28.4502,8.38194 android:pathData="M31.0002,13.8797 L31.0002,11.8208 L28.4502,11.8208 L28.4502,8.38194
L31.0002,8.38194 L31.0002,6.32312 L28.4502,6.32312 L28.4502,2.49959 L31.0002,8.38194 L31.0002,6.32312 L28.4502,6.32312 L28.4502,2.49959
L26.0752,2.49959 L26.0752,6.32312 L22.9251,6.32312 L22.9251,2.49959 L26.0752,2.49959 L26.0752,6.32312 L22.9251,6.32312 L22.9251,2.49959
@@ -27,7 +27,7 @@ L26.0752,13.8797 L26.0752,17.4996 L28.4502,17.4996 L28.4502,13.8797
L31.0002,13.8797 Z M26.0752,11.8208 L22.9251,11.8208 L22.9251,8.38194 L31.0002,13.8797 Z M26.0752,11.8208 L22.9251,11.8208 L22.9251,8.38194
L26.0752,8.38194 L26.0752,11.8208 Z"/> L26.0752,8.38194 L26.0752,11.8208 Z"/>
<path <path
android:fillColor="#7fac49" android:fillColor="#67a60f"
android:pathData="M46.5002,13.8797 L46.5002,11.8208 L43.9502,11.8208 L43.9502,8.38194 android:pathData="M46.5002,13.8797 L46.5002,11.8208 L43.9502,11.8208 L43.9502,8.38194
L46.5002,8.38194 L46.5002,6.32312 L43.9502,6.32312 L43.9502,2.49959 L46.5002,8.38194 L46.5002,6.32312 L43.9502,6.32312 L43.9502,2.49959
L41.5752,2.49959 L41.5752,6.32312 L38.4251,6.32312 L38.4251,2.49959 L41.5752,2.49959 L41.5752,6.32312 L38.4251,6.32312 L38.4251,2.49959

View File

@@ -4,14 +4,13 @@
android:id="@+id/layout" android:id="@+id/layout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="@color/briar_black" android:background="@android:color/black"
tools:context=".android.conversation.ImageActivity"> tools:context=".android.conversation.ImageActivity">
<androidx.viewpager.widget.ViewPager <androidx.viewpager.widget.ViewPager
android:id="@+id/viewPager" android:id="@+id/viewPager"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent" />
tools:background="@color/briar_green_light" />
<com.google.android.material.appbar.AppBarLayout <com.google.android.material.appbar.AppBarLayout
android:id="@+id/appBarLayout" android:id="@+id/appBarLayout"

View File

@@ -60,7 +60,7 @@
android:layout_gravity="center_vertical" android:layout_gravity="center_vertical"
android:layout_marginTop="16dp" android:layout_marginTop="16dp"
android:layout_marginBottom="16dp" android:layout_marginBottom="16dp"
android:background="@color/briar_white" android:background="@android:color/white"
android:padding="8dp" android:padding="8dp"
android:textColor="@color/briar_text_primary" android:textColor="@color/briar_text_primary"
android:textIsSelectable="true" android:textIsSelectable="true"

Some files were not shown because too many files have changed in this diff Show More