diff --git a/bramble-android/src/main/java/org/briarproject/bramble/plugin/tcp/AndroidLanTcpPlugin.java b/bramble-android/src/main/java/org/briarproject/bramble/plugin/tcp/AndroidLanTcpPlugin.java index 577a773e7..d4a8a417b 100644 --- a/bramble-android/src/main/java/org/briarproject/bramble/plugin/tcp/AndroidLanTcpPlugin.java +++ b/bramble-android/src/main/java/org/briarproject/bramble/plugin/tcp/AndroidLanTcpPlugin.java @@ -9,11 +9,11 @@ import android.net.wifi.WifiManager; import org.briarproject.bramble.PoliteExecutor; 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.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.plugin.Backoff; import org.briarproject.bramble.api.plugin.PluginCallback; +import org.briarproject.bramble.api.settings.Settings; import java.io.IOException; import java.net.InetAddress; @@ -36,10 +36,11 @@ import static java.util.logging.Level.WARNING; import static java.util.logging.Logger.getLogger; import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE; import static org.briarproject.bramble.api.plugin.Plugin.State.INACTIVE; +import static org.briarproject.bramble.api.plugin.TcpConstants.PREF_TCP_ENABLE; import static org.briarproject.bramble.util.IoUtils.tryToClose; @NotNullByDefault -class AndroidLanTcpPlugin extends LanTcpPlugin implements EventListener { +class AndroidLanTcpPlugin extends LanTcpPlugin { private static final Logger LOG = getLogger(AndroidLanTcpPlugin.class.getName()); @@ -83,7 +84,8 @@ class AndroidLanTcpPlugin extends LanTcpPlugin implements EventListener { @Override public void start() { if (used.getAndSet(true)) throw new IllegalStateException(); - state.setStarted(); + Settings settings = callback.getSettings(); + state.setStarted(settings.getBoolean(PREF_TCP_ENABLE, false)); updateConnectionStatus(); } @@ -136,6 +138,7 @@ class AndroidLanTcpPlugin extends LanTcpPlugin implements EventListener { @Override public void eventOccurred(Event e) { + super.eventOccurred(e); if (e instanceof NetworkStatusEvent) updateConnectionStatus(); } diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/BluetoothConstants.java b/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/BluetoothConstants.java index 785aee2b3..915dfbbab 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/BluetoothConstants.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/BluetoothConstants.java @@ -10,4 +10,7 @@ public interface BluetoothConstants { String PROP_UUID = "uuid"; String PREF_BT_ENABLE = "enable"; + + // Reason code returned by Plugin#getReasonDisabled() + int REASON_NO_BT_ADAPTER = 2; } diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/Plugin.java b/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/Plugin.java index b7247967c..ee26500f1 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/Plugin.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/Plugin.java @@ -35,12 +35,18 @@ public interface Plugin { } /** - * Reason code returned by {@link #getReasonDisabled()} ()} to indicate - * that the plugin is disabled because it has not been started or has been + * Reason code returned by {@link #getReasonDisabled()} to indicate that + * the plugin is disabled because it has not been started or has been * stopped. */ int REASON_STARTING_STOPPING = 0; + /** + * Reason code returned by {@link #getReasonDisabled()} to indicate that + * the plugin has been disabled by the user. + */ + int REASON_USER = 1; + /** * Returns the plugin's transport identifier. */ @@ -75,9 +81,9 @@ public interface Plugin { * Returns an integer code indicating why the plugin is * {@link State#DISABLED disabled}, or -1 if the plugin is not disabled. *

- * The codes used are plugin-specific, except the generic code - * {@link #REASON_STARTING_STOPPING}, which may be used by - * any plugin. + * The codes used are plugin-specific, except the generic codes + * {@link #REASON_STARTING_STOPPING} and {@link #REASON_USER}, which may + * be used by any plugin. */ int getReasonDisabled(); diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/TcpConstants.java b/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/TcpConstants.java new file mode 100644 index 000000000..503f2cb88 --- /dev/null +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/TcpConstants.java @@ -0,0 +1,6 @@ +package org.briarproject.bramble.api.plugin; + +public interface TcpConstants { + + String PREF_TCP_ENABLE = "enable"; +} diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/TorConstants.java b/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/TorConstants.java index 2d808eaa9..727dab4b4 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/TorConstants.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/TorConstants.java @@ -13,6 +13,7 @@ public interface TorConstants { int CONNECT_TO_PROXY_TIMEOUT = 5000; // Milliseconds int EXTRA_SOCKET_TIMEOUT = 30000; // Milliseconds + String PREF_TOR_ENABLE = "enable"; String PREF_TOR_NETWORK = "network2"; String PREF_TOR_PORT = "port"; String PREF_TOR_MOBILE = "useMobileData"; @@ -23,7 +24,7 @@ public interface TorConstants { int PREF_TOR_NETWORK_WITH_BRIDGES = 2; int PREF_TOR_NETWORK_NEVER = 3; - int REASON_USER = 1; + // Reason codes returned by Plugin#getReasonDisabled() int REASON_BATTERY = 2; int REASON_MOBILE_DATA = 3; int REASON_COUNTRY_BLOCKED = 4; diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/bluetooth/BluetoothPlugin.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/bluetooth/BluetoothPlugin.java index e15a3e623..a8c53b417 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/plugin/bluetooth/BluetoothPlugin.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/bluetooth/BluetoothPlugin.java @@ -9,6 +9,7 @@ import org.briarproject.bramble.api.keyagreement.KeyAgreementConnection; import org.briarproject.bramble.api.keyagreement.KeyAgreementListener; import org.briarproject.bramble.api.keyagreement.event.KeyAgreementListeningEvent; 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.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; @@ -48,6 +49,7 @@ 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_UUID; +import static org.briarproject.bramble.api.plugin.BluetoothConstants.REASON_NO_BT_ADAPTER; 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; @@ -76,7 +78,6 @@ abstract class BluetoothPlugin implements DuplexPlugin, EventListener { protected final PluginState state = new PluginState(); - private volatile boolean contactConnections = false; private volatile String contactConnectionsUuid = null; abstract void initialiseAdapter() throws IOException; @@ -126,16 +127,18 @@ abstract class BluetoothPlugin implements DuplexPlugin, EventListener { LOG.info("Bluetooth enabled"); // We may not have been able to get the local address before ioExecutor.execute(this::updateProperties); - if (shouldAllowContactConnections()) bind(); - callback.pluginStateChanged(getState()); + if (getState() == INACTIVE) bind(); } void onAdapterDisabled() { LOG.info("Bluetooth disabled"); connectionLimiter.allConnectionsClosed(); // The server socket may not have been closed automatically - tryToClose(state.clearServerSocket()); - callback.pluginStateChanged(getState()); + SS ss = state.clearServerSocket(); + if (ss != null) { + LOG.info("Closing server socket"); + tryToClose(ss); + } } @Override @@ -160,29 +163,22 @@ abstract class BluetoothPlugin implements DuplexPlugin, EventListener { try { initialiseAdapter(); } catch (IOException e) { + state.setNoAdapter(); throw new PluginException(e); } updateProperties(); - state.setStarted(); - loadSettings(callback.getSettings()); - if (shouldAllowContactConnections()) { + Settings settings = callback.getSettings(); + boolean enabledByUser = settings.getBoolean(PREF_BT_ENABLE, false); + state.setStarted(enabledByUser); + if (enabledByUser) { if (isAdapterEnabled()) bind(); else enableAdapter(); } } - private void loadSettings(Settings settings) { - contactConnections = settings.getBoolean(PREF_BT_ENABLE, false); - } - - private boolean shouldAllowContactConnections() { - return contactConnections; - } - private void bind() { ioExecutor.execute(() -> { - if (!shouldAllowContactConnections() || getState() != ACTIVE) - return; + if (getState() != INACTIVE) return; // Bind a server socket to accept connections from contacts SS ss; try { @@ -191,8 +187,7 @@ abstract class BluetoothPlugin implements DuplexPlugin, EventListener { logException(LOG, WARNING, e); return; } - if (!shouldAllowContactConnections() || - !state.setServerSocket(ss)) { + if (!state.setServerSocket(ss)) { LOG.info("Closing redundant server socket"); tryToClose(ss); return; @@ -259,7 +254,7 @@ abstract class BluetoothPlugin implements DuplexPlugin, EventListener { @Override public int getReasonDisabled() { - return getState() == DISABLED ? REASON_STARTING_STOPPING : -1; + return state.getReasonDisabled(); } @Override @@ -275,7 +270,7 @@ abstract class BluetoothPlugin implements DuplexPlugin, EventListener { @Override public void poll(Collection> properties) { - if (!shouldAllowContactConnections() || getState() != ACTIVE) return; + if (getState() != ACTIVE) return; backoff.increment(); for (Pair p : properties) { connect(p.getFirst(), p.getSecond()); @@ -288,8 +283,7 @@ abstract class BluetoothPlugin implements DuplexPlugin, EventListener { String uuid = p.get(PROP_UUID); if (isNullOrEmpty(uuid)) return; ioExecutor.execute(() -> { - if (!shouldAllowContactConnections() || getState() != ACTIVE) - return; + if (getState() != ACTIVE) return; if (!connectionLimiter.canOpenContactConnection()) return; DuplexTransportConnection d = createConnection(p); if (d != null) { @@ -333,8 +327,7 @@ abstract class BluetoothPlugin implements DuplexPlugin, EventListener { @Override public DuplexTransportConnection createConnection(TransportProperties p) { - if (!shouldAllowContactConnections() || getState() != ACTIVE) - return null; + if (getState() != ACTIVE) return null; if (!connectionLimiter.canOpenContactConnection()) return null; String address = p.get(PROP_ADDRESS); if (isNullOrEmpty(address)) return null; @@ -439,16 +432,17 @@ abstract class BluetoothPlugin implements DuplexPlugin, EventListener { } } + @IoExecutor private void onSettingsUpdated(Settings settings) { - boolean wasAllowed = shouldAllowContactConnections(); - loadSettings(settings); - boolean isAllowed = shouldAllowContactConnections(); - if (wasAllowed && !isAllowed) { - LOG.info("Contact connections disabled"); - tryToClose(state.clearServerSocket()); + boolean enabledByUser = settings.getBoolean(PREF_BT_ENABLE, false); + SS ss = state.setEnabledByUser(enabledByUser); + State s = getState(); + if (ss != null) { + LOG.info("Disabled by user, closing server socket"); + tryToClose(ss); disableAdapterIfEnabledByUs(); - } else if (!wasAllowed && isAllowed) { - LOG.info("Contact connections enabled"); + } else if (s == INACTIVE) { + LOG.info("Enabled by user, opening server socket"); if (isAdapterEnabled()) bind(); else enableAdapter(); } @@ -482,13 +476,18 @@ abstract class BluetoothPlugin implements DuplexPlugin, EventListener { protected class PluginState { @GuardedBy("this") - private boolean started = false, stopped = false; + private boolean started = false, + stopped = false, + noAdapter = false, + enabledByUser = false; + @GuardedBy("this") @Nullable private SS serverSocket = null; - synchronized void setStarted() { + synchronized void setStarted(boolean enabledByUser) { started = true; + this.enabledByUser = enabledByUser; callback.pluginStateChanged(getState()); } @@ -501,6 +500,23 @@ abstract class BluetoothPlugin implements DuplexPlugin, EventListener { return ss; } + synchronized void setNoAdapter() { + noAdapter = true; + callback.pluginStateChanged(getState()); + } + + @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; @@ -512,12 +528,19 @@ abstract class BluetoothPlugin implements DuplexPlugin, EventListener { synchronized SS clearServerSocket() { SS ss = serverSocket; serverSocket = null; + callback.pluginStateChanged(getState()); return ss; } synchronized State getState() { - if (!started || stopped) return DISABLED; - return isAdapterEnabled() ? ACTIVE : INACTIVE; + if (!started || stopped || !enabledByUser) return DISABLED; + return serverSocket == null ? INACTIVE : ACTIVE; + } + + synchronized int getReasonDisabled() { + if (noAdapter && !stopped) return REASON_NO_BT_ADAPTER; + if (!started || stopped) return REASON_STARTING_STOPPING; + return enabledByUser ? -1 : REASON_USER; } } } diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/tcp/LanTcpPluginFactory.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/tcp/LanTcpPluginFactory.java index caa4c6a2e..7b0d20463 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/plugin/tcp/LanTcpPluginFactory.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/tcp/LanTcpPluginFactory.java @@ -1,5 +1,6 @@ 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.plugin.Backoff; import org.briarproject.bramble.api.plugin.BackoffFactory; @@ -25,11 +26,13 @@ public class LanTcpPluginFactory implements DuplexPluginFactory { private static final double BACKOFF_BASE = 1.2; private final Executor ioExecutor; + private final EventBus eventBus; private final BackoffFactory backoffFactory; - public LanTcpPluginFactory(Executor ioExecutor, + public LanTcpPluginFactory(Executor ioExecutor, EventBus eventBus, BackoffFactory backoffFactory) { this.ioExecutor = ioExecutor; + this.eventBus = eventBus; this.backoffFactory = backoffFactory; } @@ -47,7 +50,9 @@ public class LanTcpPluginFactory implements DuplexPluginFactory { public DuplexPlugin createPlugin(PluginCallback callback) { Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL, MAX_POLLING_INTERVAL, BACKOFF_BASE); - return new LanTcpPlugin(ioExecutor, backoff, callback, MAX_LATENCY, - MAX_IDLE_TIME); + LanTcpPlugin plugin = new LanTcpPlugin(ioExecutor, backoff, callback, + MAX_LATENCY, MAX_IDLE_TIME); + eventBus.addListener(plugin); + return plugin; } } diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/tcp/TcpPlugin.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/tcp/TcpPlugin.java index d4713fecf..215a47ea1 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/plugin/tcp/TcpPlugin.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/tcp/TcpPlugin.java @@ -3,7 +3,10 @@ package org.briarproject.bramble.plugin.tcp; import org.briarproject.bramble.PoliteExecutor; import org.briarproject.bramble.api.Pair; import org.briarproject.bramble.api.data.BdfList; +import org.briarproject.bramble.api.event.Event; +import org.briarproject.bramble.api.event.EventListener; 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.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; @@ -15,6 +18,8 @@ import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection; import org.briarproject.bramble.api.properties.TransportProperties; import org.briarproject.bramble.api.rendezvous.KeyMaterialSource; import org.briarproject.bramble.api.rendezvous.RendezvousEndpoint; +import org.briarproject.bramble.api.settings.Settings; +import org.briarproject.bramble.api.settings.event.SettingsUpdatedEvent; import java.io.IOException; import java.net.InetAddress; @@ -46,6 +51,7 @@ 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.TcpConstants.PREF_TCP_ENABLE; import static org.briarproject.bramble.util.IoUtils.tryToClose; import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.bramble.util.PrivacyUtils.scrubSocketAddress; @@ -53,7 +59,7 @@ import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty; @MethodsNotNullByDefault @ParametersNotNullByDefault -abstract class TcpPlugin implements DuplexPlugin { +abstract class TcpPlugin implements DuplexPlugin, EventListener { private static final Logger LOG = getLogger(TcpPlugin.class.getName()); @@ -119,7 +125,8 @@ abstract class TcpPlugin implements DuplexPlugin { @Override public void start() { if (used.getAndSet(true)) throw new IllegalStateException(); - state.setStarted(); + Settings settings = callback.getSettings(); + state.setStarted(settings.getBoolean(PREF_TCP_ENABLE, false)); bind(); } @@ -197,7 +204,7 @@ abstract class TcpPlugin implements DuplexPlugin { @Override public int getReasonDisabled() { - return getState() == DISABLED ? REASON_STARTING_STOPPING : -1; + return state.getReasonDisabled(); } @Override @@ -331,18 +338,43 @@ abstract class TcpPlugin implements DuplexPlugin { } } + @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_TCP_ENABLE, false); + ServerSocket ss = state.setEnabledByUser(enabledByUser); + State s = getState(); + if (ss != null) { + LOG.info("Disabled by user, closing server socket"); + tryToClose(ss, LOG, WARNING); + } else if (s == INACTIVE) { + LOG.info("Enabled by user, opening server socket"); + bind(); + } + } + @ThreadSafe @NotNullByDefault protected class PluginState { @GuardedBy("this") - private boolean started = false, stopped = false; + private boolean started = false, stopped = false, enabledByUser = false; + @GuardedBy("this") @Nullable private ServerSocket serverSocket = null; - synchronized void setStarted() { + synchronized void setStarted(boolean enabledByUser) { started = true; + this.enabledByUser = enabledByUser; callback.pluginStateChanged(getState()); } @@ -355,6 +387,18 @@ abstract class TcpPlugin implements DuplexPlugin { return ss; } + @Nullable + synchronized ServerSocket setEnabledByUser(boolean enabledByUser) { + this.enabledByUser = enabledByUser; + ServerSocket ss = null; + if (!enabledByUser) { + ss = serverSocket; + serverSocket = null; + } + callback.pluginStateChanged(getState()); + return ss; + } + @Nullable synchronized ServerSocket getServerSocket() { return serverSocket; @@ -373,8 +417,13 @@ abstract class TcpPlugin implements DuplexPlugin { } synchronized State getState() { - if (!started || stopped) return DISABLED; + if (!started || stopped || !enabledByUser) return DISABLED; return serverSocket == null ? INACTIVE : ACTIVE; } + + synchronized int getReasonDisabled() { + if (!started || stopped) return REASON_STARTING_STOPPING; + return enabledByUser ? -1 : REASON_USER; + } } } diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/tcp/WanTcpPluginFactory.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/tcp/WanTcpPluginFactory.java index 440b208f1..7237f71e1 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/plugin/tcp/WanTcpPluginFactory.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/tcp/WanTcpPluginFactory.java @@ -1,5 +1,6 @@ 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.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.plugin.Backoff; @@ -26,12 +27,14 @@ public class WanTcpPluginFactory implements DuplexPluginFactory { private static final double BACKOFF_BASE = 1.2; private final Executor ioExecutor; + private final EventBus eventBus; private final BackoffFactory backoffFactory; private final ShutdownManager shutdownManager; - public WanTcpPluginFactory(Executor ioExecutor, + public WanTcpPluginFactory(Executor ioExecutor, EventBus eventBus, BackoffFactory backoffFactory, ShutdownManager shutdownManager) { this.ioExecutor = ioExecutor; + this.eventBus = eventBus; this.backoffFactory = backoffFactory; this.shutdownManager = shutdownManager; } @@ -50,8 +53,10 @@ public class WanTcpPluginFactory implements DuplexPluginFactory { public DuplexPlugin createPlugin(PluginCallback callback) { Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL, MAX_POLLING_INTERVAL, BACKOFF_BASE); - return new WanTcpPlugin(ioExecutor, backoff, + WanTcpPlugin plugin = new WanTcpPlugin(ioExecutor, backoff, new PortMapperImpl(shutdownManager), callback, MAX_LATENCY, MAX_IDLE_TIME); + eventBus.addListener(plugin); + return plugin; } } diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/tor/TorPlugin.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/tor/TorPlugin.java index d20901002..afdeb6dd6 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/plugin/tor/TorPlugin.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/tor/TorPlugin.java @@ -75,6 +75,7 @@ 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.TorConstants.CONTROL_PORT; import static org.briarproject.bramble.api.plugin.TorConstants.ID; +import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_ENABLE; 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_AUTOMATIC; @@ -87,7 +88,6 @@ 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.REASON_USER; 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.tryToClose; @@ -200,7 +200,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener { } } // Load the settings - settings = callback.getSettings(); + settings = migrateSettings(callback.getSettings()); // Install or update the assets if necessary if (!assetsAreUpToDate()) installAssets(); if (cookieFile.exists() && !cookieFile.delete()) @@ -288,6 +288,18 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener { bind(); } + // TODO: Remove after a reasonable migration period (added 2020-01-16) + private Settings migrateSettings(Settings settings) { + int network = settings.getInt(PREF_TOR_NETWORK, + PREF_TOR_NETWORK_AUTOMATIC); + if (network == PREF_TOR_NETWORK_NEVER) { + settings.putInt(PREF_TOR_NETWORK, PREF_TOR_NETWORK_AUTOMATIC); + settings.putBoolean(PREF_TOR_ENABLE, false); + callback.mergeSettings(settings); + } + return settings; + } + private boolean assetsAreUpToDate() { return doneFile.lastModified() > getLastUpdateTime(); } @@ -763,6 +775,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener { String country = locationUtils.getCurrentCountry(); boolean blocked = circumventionProvider.isTorProbablyBlocked(country); + boolean enabledByUser = settings.getBoolean(PREF_TOR_ENABLE, true); int network = settings.getInt(PREF_TOR_NETWORK, PREF_TOR_NETWORK_AUTOMATIC); boolean useMobile = settings.getBoolean(PREF_TOR_MOBILE, true); @@ -785,7 +798,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener { if (!online) { LOG.info("Disabling network, device is offline"); - } else if (network == PREF_TOR_NETWORK_NEVER) { + } else if (!enabledByUser) { LOG.info("Disabling network, user has disabled Tor"); disabledBySettings = true; reasonDisabled = REASON_USER; @@ -870,11 +883,11 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener { callback.pluginStateChanged(getState()); } + // Doesn't affect getState() synchronized void setTorStarted() { torStarted = true; } - @SuppressWarnings("BooleanMethodIsAlwaysInverted") synchronized boolean isTorRunning() { return torStarted && !stopped; } @@ -914,12 +927,14 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener { callback.pluginStateChanged(getState()); } + // Doesn't affect getState() synchronized boolean setServerSocket(ServerSocket ss) { if (stopped || serverSocket != null) return false; serverSocket = ss; return true; } + // Doesn't affect getState() synchronized void clearServerSocket(ServerSocket ss) { if (serverSocket == ss) serverSocket = null; } diff --git a/bramble-core/src/test/java/org/briarproject/bramble/plugin/tcp/LanTcpPluginTest.java b/bramble-core/src/test/java/org/briarproject/bramble/plugin/tcp/LanTcpPluginTest.java index 7b175f805..af5f23da9 100644 --- a/bramble-core/src/test/java/org/briarproject/bramble/plugin/tcp/LanTcpPluginTest.java +++ b/bramble-core/src/test/java/org/briarproject/bramble/plugin/tcp/LanTcpPluginTest.java @@ -34,6 +34,7 @@ import static java.util.concurrent.Executors.newCachedThreadPool; 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.TRANSPORT_ID_LAN; +import static org.briarproject.bramble.api.plugin.TcpConstants.PREF_TCP_ENABLE; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; @@ -345,10 +346,15 @@ public class LanTcpPluginTest extends BrambleTestCase { private final CountDownLatch propertiesLatch = new CountDownLatch(1); private final CountDownLatch connectionsLatch = new CountDownLatch(1); private final TransportProperties local = new TransportProperties(); + private final Settings settings = new Settings(); + + private Callback() { + settings.putBoolean(PREF_TCP_ENABLE, true); + } @Override public Settings getSettings() { - return new Settings(); + return settings; } @Override diff --git a/bramble-java/src/main/java/org/briarproject/bramble/plugin/DesktopPluginModule.java b/bramble-java/src/main/java/org/briarproject/bramble/plugin/DesktopPluginModule.java index 5b4cf7014..ce7ceb48b 100644 --- a/bramble-java/src/main/java/org/briarproject/bramble/plugin/DesktopPluginModule.java +++ b/bramble-java/src/main/java/org/briarproject/bramble/plugin/DesktopPluginModule.java @@ -37,9 +37,9 @@ public class DesktopPluginModule extends PluginModule { backoffFactory); DuplexPluginFactory modem = new ModemPluginFactory(ioExecutor, reliabilityFactory); - DuplexPluginFactory lan = new LanTcpPluginFactory(ioExecutor, + DuplexPluginFactory lan = new LanTcpPluginFactory(ioExecutor, eventBus, backoffFactory); - DuplexPluginFactory wan = new WanTcpPluginFactory(ioExecutor, + DuplexPluginFactory wan = new WanTcpPluginFactory(ioExecutor, eventBus, backoffFactory, shutdownManager); Collection duplex = asList(bluetooth, modem, lan, wan); diff --git a/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/ContactExchangeActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/ContactExchangeActivity.java index 062f12059..f18c41f75 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/ContactExchangeActivity.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/ContactExchangeActivity.java @@ -9,6 +9,7 @@ import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.briar.R; import org.briarproject.briar.android.activity.ActivityComponent; +import org.briarproject.briar.android.fragment.BaseFragment; import javax.annotation.Nullable; import javax.inject.Inject; @@ -79,13 +80,13 @@ public class ContactExchangeActivity extends KeyAgreementActivity { @UiThread private void contactExchangeFailed() { - showErrorFragment(R.string.connection_error_explanation); + showErrorFragment(); } @UiThread @Override public void keyAgreementFailed() { - showErrorFragment(R.string.connection_error_explanation); + showErrorFragment(); } @UiThread @@ -103,7 +104,7 @@ public class ContactExchangeActivity extends KeyAgreementActivity { @UiThread @Override public void keyAgreementAborted(boolean remoteAborted) { - showErrorFragment(R.string.connection_error_explanation); + showErrorFragment(); } @UiThread @@ -112,4 +113,10 @@ public class ContactExchangeActivity extends KeyAgreementActivity { startContactExchange(result); 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); + } } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/KeyAgreementActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/KeyAgreementActivity.java index 16d8e2a03..771c17cdc 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/KeyAgreementActivity.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/KeyAgreementActivity.java @@ -8,10 +8,23 @@ import android.content.IntentFilter; import android.os.Bundle; import android.view.MenuItem; +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.IoExecutor; import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; 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.TransportId; import org.briarproject.bramble.api.plugin.event.BluetoothEnabledEvent; +import org.briarproject.bramble.api.plugin.event.TransportStateEvent; +import org.briarproject.bramble.api.settings.Settings; +import org.briarproject.bramble.api.settings.SettingsManager; import org.briarproject.briar.R; import org.briarproject.briar.android.activity.ActivityComponent; import org.briarproject.briar.android.activity.BriarActivity; @@ -21,6 +34,7 @@ import org.briarproject.briar.android.keyagreement.IntroFragment.IntroScreenSeen import org.briarproject.briar.android.keyagreement.KeyAgreementFragment.KeyAgreementEventListener; import org.briarproject.briar.android.util.UiUtils; +import java.util.concurrent.Executor; import java.util.logging.Logger; import javax.annotation.Nullable; @@ -37,13 +51,18 @@ import static android.Manifest.permission.ACCESS_COARSE_LOCATION; import static android.Manifest.permission.CAMERA; import static android.bluetooth.BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE; import static android.bluetooth.BluetoothAdapter.ACTION_SCAN_MODE_CHANGED; -import static android.bluetooth.BluetoothAdapter.ACTION_STATE_CHANGED; -import static android.bluetooth.BluetoothAdapter.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.STATE_ON; import static android.content.pm.PackageManager.PERMISSION_GRANTED; +import static java.util.logging.Level.INFO; +import static java.util.logging.Level.WARNING; +import static java.util.logging.Logger.getLogger; +import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull; +import static org.briarproject.bramble.api.plugin.BluetoothConstants.PREF_BT_ENABLE; +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.TcpConstants.PREF_TCP_ENABLE; +import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_BLUETOOTH_DISCOVERABLE; import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_PERMISSION_CAMERA_LOCATION; @@ -51,10 +70,33 @@ import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_PERMI @ParametersNotNullByDefault public abstract class KeyAgreementActivity extends BriarActivity implements BaseFragmentListener, IntroScreenSeenListener, - KeyAgreementEventListener { + KeyAgreementEventListener, EventListener { - private enum BluetoothState { - UNKNOWN, NO_ADAPTER, WAITING, REFUSED, ENABLED, DISCOVERABLE + private enum BluetoothDecision { + /** + * 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 { @@ -62,11 +104,21 @@ public abstract class KeyAgreementActivity extends BriarActivity implements } private static final Logger LOG = - Logger.getLogger(KeyAgreementActivity.class.getName()); + getLogger(KeyAgreementActivity.class.getName()); @Inject EventBus eventBus; + @Inject + PluginManager pluginManager; + + @Inject + @IoExecutor + Executor ioExecutor; + + @Inject + SettingsManager settingsManager; + /** * Set to true in onPostResume() and false in onPause(). This prevents the * QR code fragment from being shown if onRequestPermissionsResult() is @@ -74,21 +126,36 @@ public abstract class KeyAgreementActivity extends BriarActivity implements * https://issuetracker.google.com/issues/37067655. */ private boolean isResumed = false; + /** * Set to true when the continue button is clicked, and false when the QR * code fragment is shown. This prevents the QR code fragment from being * shown automatically before the continue button has been clicked. */ private boolean continueClicked = false; + /** * Records whether the Bluetooth adapter was already enabled before we * asked for Bluetooth discoverability, so we know whether to broadcast a * {@link BluetoothEnabledEvent}. */ private boolean wasAdapterEnabled = false; + + /** + * 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 locationPermission = Permission.UNKNOWN; - private BluetoothState bluetoothState = BluetoothState.UNKNOWN; + private BluetoothDecision bluetoothDecision = BluetoothDecision.UNKNOWN; private BroadcastReceiver bluetoothReceiver = null; @Override @@ -96,20 +163,17 @@ public abstract class KeyAgreementActivity extends BriarActivity implements component.inject(this); } - @SuppressWarnings("ConstantConditions") @Override public void onCreate(@Nullable Bundle state) { super.onCreate(state); setContentView(R.layout.activity_fragment_container_toolbar); Toolbar toolbar = findViewById(R.id.toolbar); setSupportActionBar(toolbar); - getSupportActionBar().setDisplayHomeAsUpEnabled(true); + requireNonNull(getSupportActionBar()).setDisplayHomeAsUpEnabled(true); if (state == null) { showInitialFragment(IntroFragment.newInstance()); } - IntentFilter filter = new IntentFilter(); - filter.addAction(ACTION_STATE_CHANGED); - filter.addAction(ACTION_SCAN_MODE_CHANGED); + IntentFilter filter = new IntentFilter(ACTION_SCAN_MODE_CHANGED); bluetoothReceiver = new BluetoothStateReceiver(); registerReceiver(bluetoothReceiver, filter); } @@ -122,18 +186,17 @@ public abstract class KeyAgreementActivity extends BriarActivity implements @Override public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case android.R.id.home: - onBackPressed(); - return true; - default: - return super.onOptionsItemSelected(item); + if (item.getItemId() == android.R.id.home) { + onBackPressed(); + return true; } + return super.onOptionsItemSelected(item); } @Override public void onStart() { super.onStart(); + eventBus.addListener(this); // Permissions may have been granted manually while we were stopped cameraPermission = Permission.UNKNOWN; locationPermission = Permission.UNKNOWN; @@ -150,11 +213,22 @@ public abstract class KeyAgreementActivity extends BriarActivity implements private void showQrCodeFragmentIfAllowed() { if (isResumed && continueClicked && areEssentialPermissionsGranted()) { - if (bluetoothState == BluetoothState.UNKNOWN || - bluetoothState == BluetoothState.ENABLED) { - requestBluetoothDiscoverable(); - } else if (bluetoothState != BluetoothState.WAITING) { + if (isWifiReady() && isBluetoothReady()) { + LOG.info("Wifi and Bluetooth are ready"); showQrCodeFragment(); + } else { + if (shouldEnableWifi()) { + LOG.info("Enabling wifi plugin"); + hasEnabledWifi = true; + enablePlugin(LanTcpConstants.ID, PREF_TCP_ENABLE); + } + if (bluetoothDecision == BluetoothDecision.UNKNOWN) { + requestBluetoothDiscoverable(); + } else if (shouldEnableBluetooth()) { + LOG.info("Enabling Bluetooth plugin"); + hasEnabledBluetooth = true; + enablePlugin(BluetoothConstants.ID, PREF_BT_ENABLE); + } } } } @@ -167,57 +241,116 @@ public abstract class KeyAgreementActivity extends BriarActivity implements 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); + return p != null && p.getState() == DISABLED; + } + + private void enablePlugin(TransportId t, String settingKey) { + ioExecutor.execute(() -> { + try { + Settings s = new Settings(); + s.putBoolean(settingKey, true); + settingsManager.mergeSettings(s, t.getString()); + } catch (DbException e) { + logException(LOG, WARNING, e); + } + }); + } + + 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); + return p != null && p.getState() == DISABLED; + } + @Override protected void onPause() { super.onPause(); isResumed = false; } + @Override + protected void onStop() { + super.onStop(); + eventBus.removeListener(this); + } + @Override public void showNextScreen() { continueClicked = true; 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 - 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 (result == RESULT_CANCELED) { - setBluetoothState(BluetoothState.REFUSED); + LOG.info("Bluetooth discoverability was refused"); + bluetoothDecision = BluetoothDecision.REFUSED; } else { - // If Bluetooth is already discoverable, show the QR code - - // otherwise wait for the state or scan mode to change - BluetoothAdapter bt = BluetoothAdapter.getDefaultAdapter(); - if (bt == null) throw new AssertionError(); - if (bt.getScanMode() == SCAN_MODE_CONNECTABLE_DISCOVERABLE) - setBluetoothState(BluetoothState.DISCOVERABLE); + LOG.info("Bluetooth discoverability was accepted"); + bluetoothDecision = BluetoothDecision.ACCEPTED; + if (!wasAdapterEnabled) { + LOG.info("Bluetooth adapter was enabled by us"); + eventBus.broadcast(new BluetoothEnabledEvent()); + wasAdapterEnabled = true; + } } + showQrCodeFragmentIfAllowed(); } else super.onActivityResult(request, result, data); } @@ -227,7 +360,12 @@ public abstract class KeyAgreementActivity extends BriarActivity implements continueClicked = false; // If we return to the intro fragment, ask for Bluetooth // 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 FragmentManager fm = getSupportFragmentManager(); if (fm.findFragmentByTag(KeyAgreementFragment.TAG) == null) { @@ -239,12 +377,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() { if (areEssentialPermissionsGranted()) return true; // If the camera permission has been permanently denied, ask the @@ -335,24 +467,30 @@ public abstract class KeyAgreementActivity extends BriarActivity implements 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 { @Override public void onReceive(Context context, Intent intent) { - String action = intent.getAction(); - if (ACTION_STATE_CHANGED.equals(action)) { - 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); - } + LOG.info("Bluetooth scan mode changed"); + showQrCodeFragmentIfAllowed(); } } } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/settings/SettingsFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/settings/SettingsFragment.java index dc7096db0..4e2ab8f83 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/settings/SettingsFragment.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/settings/SettingsFragment.java @@ -21,6 +21,7 @@ import org.briarproject.bramble.api.event.EventListener; import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.bramble.api.plugin.BluetoothConstants; +import org.briarproject.bramble.api.plugin.LanTcpConstants; import org.briarproject.bramble.api.plugin.TorConstants; import org.briarproject.bramble.api.settings.Settings; import org.briarproject.bramble.api.settings.SettingsManager; @@ -73,6 +74,8 @@ import static androidx.core.view.ViewCompat.LAYOUT_DIRECTION_LTR; import static java.util.logging.Level.INFO; 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.TcpConstants.PREF_TCP_ENABLE; +import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_ENABLE; 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_AUTOMATIC; @@ -105,16 +108,24 @@ public class SettingsFragment extends PreferenceFragmentCompat implements EventListener, OnPreferenceChangeListener { 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 PREF_SCREEN_LOCK = "pref_key_lock"; public static final String PREF_SCREEN_LOCK_TIMEOUT = "pref_key_lock_timeout"; 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"; - public static final String TOR_ONLY_WHEN_CHARGING = + + private static final String BT_NAMESPACE = + 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"; private static final Logger LOG = @@ -122,7 +133,9 @@ public class SettingsFragment extends PreferenceFragmentCompat private SettingsActivity listener; private ListPreference language; - private ListPreference enableBluetooth; + private SwitchPreference enableBluetooth; + private SwitchPreference enableWifi; + private SwitchPreference enableTor; private ListPreference torNetwork; private SwitchPreference torMobile; private SwitchPreference torOnlyWhenCharging; @@ -137,7 +150,7 @@ public class SettingsFragment extends PreferenceFragmentCompat private Preference notifySound; // 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; @Inject @@ -163,28 +176,23 @@ public class SettingsFragment extends PreferenceFragmentCompat public void onCreatePreferences(Bundle bundle, String s) { addPreferencesFromResource(R.xml.settings); - language = (ListPreference) findPreference(LANGUAGE); + language = findPreference(LANGUAGE); setLanguageEntries(); - ListPreference theme = - (ListPreference) findPreference("pref_key_theme"); - enableBluetooth = (ListPreference) findPreference("pref_key_bluetooth"); - torNetwork = (ListPreference) findPreference(TOR_NETWORK); - torMobile = (SwitchPreference) findPreference(TOR_MOBILE); - torOnlyWhenCharging = - (SwitchPreference) findPreference(TOR_ONLY_WHEN_CHARGING); - screenLock = (SwitchPreference) findPreference(PREF_SCREEN_LOCK); - screenLockTimeout = - (ListPreference) findPreference(PREF_SCREEN_LOCK_TIMEOUT); - notifyPrivateMessages = (SwitchPreference) findPreference( - "pref_key_notify_private_messages"); - notifyGroupMessages = (SwitchPreference) findPreference( - "pref_key_notify_group_messages"); - notifyForumPosts = (SwitchPreference) findPreference( - "pref_key_notify_forum_posts"); - notifyBlogPosts = (SwitchPreference) findPreference( - "pref_key_notify_blog_posts"); - notifyVibration = (SwitchPreference) findPreference( - "pref_key_notify_vibration"); + ListPreference theme = findPreference("pref_key_theme"); + enableBluetooth = findPreference(BT_ENABLE); + enableWifi = findPreference(WIFI_ENABLE); + enableTor = findPreference(TOR_ENABLE); + torNetwork = findPreference(TOR_NETWORK); + torMobile = findPreference(TOR_MOBILE); + torOnlyWhenCharging = findPreference(TOR_ONLY_WHEN_CHARGING); + screenLock = findPreference(PREF_SCREEN_LOCK); + screenLockTimeout = findPreference(PREF_SCREEN_LOCK_TIMEOUT); + notifyPrivateMessages = + findPreference("pref_key_notify_private_messages"); + notifyGroupMessages = findPreference("pref_key_notify_group_messages"); + notifyForumPosts = findPreference("pref_key_notify_forum_posts"); + notifyBlogPosts = findPreference("pref_key_notify_blog_posts"); + notifyVibration = findPreference("pref_key_notify_vibration"); notifySound = findPreference("pref_key_notify_sound"); language.setOnPreferenceChangeListener(this); @@ -194,8 +202,7 @@ public class SettingsFragment extends PreferenceFragmentCompat UiUtils.setTheme(getActivity(), (String) newValue); // bring up parent activity, so it can change its theme as well // upstream bug: https://issuetracker.google.com/issues/38352704 - Intent intent = - new Intent(getActivity(), ENTRY_ACTIVITY); + Intent intent = new Intent(getActivity(), ENTRY_ACTIVITY); intent.setFlags( FLAG_ACTIVITY_CLEAR_TASK | FLAG_ACTIVITY_NEW_TASK); startActivity(intent); @@ -207,6 +214,8 @@ public class SettingsFragment extends PreferenceFragmentCompat return true; }); enableBluetooth.setOnPreferenceChangeListener(this); + enableWifi.setOnPreferenceChangeListener(this); + enableTor.setOnPreferenceChangeListener(this); torNetwork.setOnPreferenceChangeListener(this); torMobile.setOnPreferenceChangeListener(this); torOnlyWhenCharging.setOnPreferenceChangeListener(this); @@ -335,7 +344,8 @@ public class SettingsFragment extends PreferenceFragmentCompat boolean blocked = circumventionProvider.isTorProbablyBlocked(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) { setting = getString(R.string.tor_network_setting_with_bridges); } else if (blocked) { @@ -352,6 +362,7 @@ public class SettingsFragment extends PreferenceFragmentCompat long start = now(); settings = settingsManager.getSettings(SETTINGS_NAMESPACE); btSettings = settingsManager.getSettings(BT_NAMESPACE); + wifiSettings = settingsManager.getSettings(WIFI_NAMESPACE); torSettings = settingsManager.getSettings(TOR_NAMESPACE); settingsLoaded = true; logDuration(LOG, "Loading settings", start); @@ -369,7 +380,15 @@ public class SettingsFragment extends PreferenceFragmentCompat boolean btEnabledSetting = btSettings.getBoolean(PREF_BT_ENABLE, false); - enableBluetooth.setValue(Boolean.toString(btEnabledSetting)); + enableBluetooth.setChecked(btEnabledSetting); + + boolean wifiEnabledSetting = + wifiSettings.getBoolean(PREF_TCP_ENABLE, false); + enableWifi.setChecked(wifiEnabledSetting); + + boolean torEnabledSetting = + torSettings.getBoolean(PREF_TOR_ENABLE, true); + enableTor.setChecked(torEnabledSetting); int torNetworkSetting = torSettings.getInt(PREF_TOR_NETWORK, PREF_TOR_NETWORK_AUTOMATIC); @@ -443,6 +462,8 @@ public class SettingsFragment extends PreferenceFragmentCompat // - pref_key_lock (screenLock -> displayScreenLockSetting()) // - pref_key_lock_timeout (screenLockTimeout) enableBluetooth.setEnabled(enabled); + enableWifi.setEnabled(enabled); + enableTor.setEnabled(enabled); torNetwork.setEnabled(enabled); torMobile.setEnabled(enabled); torOnlyWhenCharging.setEnabled(enabled); @@ -545,8 +566,14 @@ public class SettingsFragment extends PreferenceFragmentCompat languageChanged((String) newValue); return false; } else if (preference == enableBluetooth) { - boolean btSetting = Boolean.valueOf((String) newValue); - storeBluetoothSettings(btSetting); + boolean btSetting = (Boolean) newValue; + 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) { int torNetworkSetting = Integer.valueOf((String) newValue); storeTorNetworkSetting(torNetworkSetting); @@ -610,6 +637,12 @@ public class SettingsFragment extends PreferenceFragmentCompat builder.show(); } + private void storeTorEnabledSetting(boolean torEnabledSetting) { + Settings s = new Settings(); + s.putBoolean(PREF_TOR_ENABLE, torEnabledSetting); + mergeSettings(s, TOR_NAMESPACE); + } + private void storeTorNetworkSetting(int torNetworkSetting) { Settings s = new Settings(); s.putInt(PREF_TOR_NETWORK, torNetworkSetting); @@ -628,12 +661,18 @@ public class SettingsFragment extends PreferenceFragmentCompat mergeSettings(s, TOR_NAMESPACE); } - private void storeBluetoothSettings(boolean btSetting) { + private void storeBluetoothSetting(boolean btSetting) { Settings s = new Settings(); s.putBoolean(PREF_BT_ENABLE, btSetting); mergeSettings(s, BT_NAMESPACE); } + private void storeWifiSetting(boolean wifiSetting) { + Settings s = new Settings(); + s.putBoolean(PREF_TCP_ENABLE, wifiSetting); + mergeSettings(s, WIFI_NAMESPACE); + } + private void storeSettings(Settings s) { mergeSettings(s, SETTINGS_NAMESPACE); } @@ -696,6 +735,10 @@ public class SettingsFragment extends PreferenceFragmentCompat LOG.info("Bluetooth settings updated"); btSettings = s.getSettings(); displaySettings(); + } else if (namespace.equals(WIFI_NAMESPACE)) { + LOG.info("Wifi settings updated"); + wifiSettings = s.getSettings(); + displaySettings(); } else if (namespace.equals(TOR_NAMESPACE)) { LOG.info("Tor settings updated"); torSettings = s.getSettings(); diff --git a/briar-android/src/main/res/values/arrays.xml b/briar-android/src/main/res/values/arrays.xml index a7a950e78..a9dc6aed6 100644 --- a/briar-android/src/main/res/values/arrays.xml +++ b/briar-android/src/main/res/values/arrays.xml @@ -1,14 +1,5 @@ - - true - false - - - @string/bluetooth_setting_enabled - @string/bluetooth_setting_disabled - - @string/tor_network_setting_automatic @string/tor_network_setting_without_bridges @@ -66,6 +57,7 @@ zh-CN zh-TW + @string/pref_theme_light @string/pref_theme_dark diff --git a/briar-android/src/main/res/values/strings.xml b/briar-android/src/main/res/values/strings.xml index d7e5d7090..902a56c89 100644 --- a/briar-android/src/main/res/values/strings.xml +++ b/briar-android/src/main/res/values/strings.xml @@ -441,12 +441,12 @@ Automatic (Daytime) System Default - - Networks - Connect via Bluetooth - Whenever contacts are nearby - Only when adding contacts - Connect via Internet (Tor) + + Connections + Connect to contacts via Bluetooth + Connect to contacts on the same Wi-Fi network + Connect to contacts via Internet (Tor) + Connection method for Internet (Tor) Automatic based on location Use Tor without bridges Use Tor with bridges diff --git a/briar-android/src/main/res/xml/settings.xml b/briar-android/src/main/res/xml/settings.xml index d45a80f86..db50e4732 100644 --- a/briar-android/src/main/res/xml/settings.xml +++ b/briar-android/src/main/res/xml/settings.xml @@ -29,18 +29,33 @@ android:layout="@layout/preferences_category" android:title="@string/network_settings_title"> - + + + +