diff --git a/bramble-android/src/main/java/org/briarproject/bramble/plugin/AndroidPluginModule.java b/bramble-android/src/main/java/org/briarproject/bramble/plugin/AndroidPluginModule.java index 52b2a293d2a53759940e3902dd5b0e35e7c4809f..c62aac754f3102e7652bcd6414bf6ed7fc0d2d81 100644 --- a/bramble-android/src/main/java/org/briarproject/bramble/plugin/AndroidPluginModule.java +++ b/bramble-android/src/main/java/org/briarproject/bramble/plugin/AndroidPluginModule.java @@ -13,6 +13,7 @@ import org.briarproject.bramble.api.plugin.simplex.SimplexPluginFactory; import org.briarproject.bramble.api.reporting.DevReporter; import org.briarproject.bramble.api.system.AndroidExecutor; import org.briarproject.bramble.api.system.LocationUtils; +import org.briarproject.bramble.api.system.Scheduler; import org.briarproject.bramble.plugin.bluetooth.AndroidBluetoothPluginFactory; import org.briarproject.bramble.plugin.tcp.AndroidLanTcpPluginFactory; import org.briarproject.bramble.plugin.tor.TorPluginFactory; @@ -22,6 +23,7 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.concurrent.Executor; +import java.util.concurrent.ScheduledExecutorService; import javax.net.SocketFactory; @@ -33,6 +35,7 @@ public class AndroidPluginModule { @Provides PluginConfig providePluginConfig(@IoExecutor Executor ioExecutor, + @Scheduler ScheduledExecutorService scheduler, AndroidExecutor androidExecutor, SecureRandom random, SocketFactory torSocketFactory, BackoffFactory backoffFactory, Application app, LocationUtils locationUtils, DevReporter reporter, @@ -41,9 +44,9 @@ public class AndroidPluginModule { DuplexPluginFactory bluetooth = new AndroidBluetoothPluginFactory(ioExecutor, androidExecutor, appContext, random, eventBus, backoffFactory); - DuplexPluginFactory tor = new TorPluginFactory(ioExecutor, appContext, - locationUtils, reporter, eventBus, torSocketFactory, - backoffFactory); + DuplexPluginFactory tor = new TorPluginFactory(ioExecutor, scheduler, + appContext, locationUtils, reporter, eventBus, + torSocketFactory, backoffFactory); DuplexPluginFactory lan = new AndroidLanTcpPluginFactory(ioExecutor, backoffFactory, appContext); Collection<DuplexPluginFactory> duplex = diff --git a/bramble-android/src/main/java/org/briarproject/bramble/plugin/tor/TorPlugin.java b/bramble-android/src/main/java/org/briarproject/bramble/plugin/tor/TorPlugin.java index 1045f372ce34a604054cf8036ea81611f9288642..67e48d3da0865ad1aa7c3bbd72855092bf289b9f 100644 --- a/bramble-android/src/main/java/org/briarproject/bramble/plugin/tor/TorPlugin.java +++ b/bramble-android/src/main/java/org/briarproject/bramble/plugin/tor/TorPlugin.java @@ -59,7 +59,12 @@ import java.util.Map.Entry; import java.util.Scanner; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executor; +import java.util.concurrent.Future; +import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; import java.util.logging.Logger; import java.util.regex.Pattern; import java.util.zip.ZipInputStream; @@ -70,10 +75,15 @@ import javax.net.SocketFactory; import static android.content.Context.CONNECTIVITY_SERVICE; import static android.content.Context.MODE_PRIVATE; import static android.content.Context.POWER_SERVICE; +import static android.content.Intent.ACTION_SCREEN_OFF; +import static android.content.Intent.ACTION_SCREEN_ON; import static android.net.ConnectivityManager.CONNECTIVITY_ACTION; import static android.net.ConnectivityManager.TYPE_WIFI; +import static android.os.Build.VERSION.SDK_INT; +import static android.os.PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED; import static android.os.PowerManager.PARTIAL_WAKE_LOCK; import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static java.util.concurrent.TimeUnit.MINUTES; import static java.util.logging.Level.INFO; import static java.util.logging.Level.WARNING; import static net.freehaven.tor.control.TorControlCommands.HS_ADDRESS; @@ -102,6 +112,7 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener { Logger.getLogger(TorPlugin.class.getName()); private final Executor ioExecutor; + private final ScheduledExecutorService scheduler; private final Context appContext; private final LocationUtils locationUtils; private final DevReporter reporter; @@ -114,6 +125,9 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener { private final File torDirectory, torFile, geoIpFile, configFile; private final File doneFile, cookieFile; private final PowerManager.WakeLock wakeLock; + private final Lock connectionStatusLock; + private final AtomicReference<Future<?>> connectivityCheck = + new AtomicReference<>(); private final AtomicBoolean used = new AtomicBoolean(false); private volatile boolean running = false; @@ -122,12 +136,13 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener { private volatile TorControlConnection controlConnection = null; private volatile BroadcastReceiver networkStateReceiver = null; - TorPlugin(Executor ioExecutor, Context appContext, - LocationUtils locationUtils, DevReporter reporter, - SocketFactory torSocketFactory, Backoff backoff, - DuplexPluginCallback callback, String architecture, int maxLatency, - int maxIdleTime) { + TorPlugin(Executor ioExecutor, ScheduledExecutorService scheduler, + Context appContext, LocationUtils locationUtils, + DevReporter reporter, SocketFactory torSocketFactory, + Backoff backoff, DuplexPluginCallback callback, + String architecture, int maxLatency, int maxIdleTime) { this.ioExecutor = ioExecutor; + this.scheduler = scheduler; this.appContext = appContext; this.locationUtils = locationUtils; this.reporter = reporter; @@ -152,6 +167,7 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener { // This tag will prevent Huawei's powermanager from killing us. wakeLock = pm.newWakeLock(PARTIAL_WAKE_LOCK, "LocationManagerService"); wakeLock.setReferenceCounted(false); + connectionStatusLock = new ReentrantLock(); } @Override @@ -204,11 +220,11 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener { if (LOG.isLoggable(INFO)) { Scanner stdout = new Scanner(torProcess.getInputStream()); Scanner stderr = new Scanner(torProcess.getErrorStream()); - while (stdout.hasNextLine() || stderr.hasNextLine()){ - if(stdout.hasNextLine()) { + while (stdout.hasNextLine() || stderr.hasNextLine()) { + if (stdout.hasNextLine()) { LOG.info(stdout.nextLine()); } - if(stderr.hasNextLine()){ + if (stderr.hasNextLine()) { LOG.info(stderr.nextLine()); } } @@ -257,7 +273,11 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener { } // Register to receive network status events networkStateReceiver = new NetworkStateReceiver(); - IntentFilter filter = new IntentFilter(CONNECTIVITY_ACTION); + IntentFilter filter = new IntentFilter(); + filter.addAction(CONNECTIVITY_ACTION); + filter.addAction(ACTION_SCREEN_ON); + filter.addAction(ACTION_SCREEN_OFF); + if (SDK_INT >= 23) filter.addAction(ACTION_DEVICE_IDLE_MODE_CHANGED); appContext.registerReceiver(networkStateReceiver, filter); // Bind a server socket to receive incoming hidden service connections bind(); @@ -618,6 +638,8 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener { @Override public void orConnStatus(String status, String orName) { if (LOG.isLoggable(INFO)) LOG.info("OR connection " + status); + if (status.equals("CLOSED") || status.equals("FAILED")) + updateConnectionStatus(); // Check whether we've lost connectivity } @Override @@ -657,7 +679,7 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener { } @Override - public void onEvent(int event, String path) { + public void onEvent(int event, @Nullable String path) { stopWatching(); latch.countDown(); } @@ -677,43 +699,60 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener { private void updateConnectionStatus() { ioExecutor.execute(() -> { if (!running) return; + try { + connectionStatusLock.lock(); + updateConnectionStatusLocked(); + } finally { + connectionStatusLock.unlock(); + } + }); + } - Object o = appContext.getSystemService(CONNECTIVITY_SERVICE); - ConnectivityManager cm = (ConnectivityManager) o; - NetworkInfo net = cm.getActiveNetworkInfo(); - boolean online = net != null && net.isConnected(); - boolean wifi = online && net.getType() == TYPE_WIFI; - String country = locationUtils.getCurrentCountry(); - boolean blocked = TorNetworkMetadata.isTorProbablyBlocked( - country); - Settings s = callback.getSettings(); - int network = s.getInt(PREF_TOR_NETWORK, PREF_TOR_NETWORK_ALWAYS); + // Locking: connectionStatusLock + private void updateConnectionStatusLocked() { + Object o = appContext.getSystemService(CONNECTIVITY_SERVICE); + ConnectivityManager cm = (ConnectivityManager) o; + NetworkInfo net = cm.getActiveNetworkInfo(); + boolean online = net != null && net.isConnected(); + boolean wifi = online && net.getType() == TYPE_WIFI; + String country = locationUtils.getCurrentCountry(); + boolean blocked = TorNetworkMetadata.isTorProbablyBlocked( + country); + Settings s = callback.getSettings(); + int network = s.getInt(PREF_TOR_NETWORK, PREF_TOR_NETWORK_ALWAYS); - if (LOG.isLoggable(INFO)) { - LOG.info("Online: " + online + ", wifi: " + wifi); - if ("".equals(country)) LOG.info("Country code unknown"); - else LOG.info("Country code: " + country); - } + if (LOG.isLoggable(INFO)) { + LOG.info("Online: " + online + ", wifi: " + wifi); + if ("".equals(country)) LOG.info("Country code unknown"); + else LOG.info("Country code: " + country); + } - try { - if (!online) { - LOG.info("Disabling network, device is offline"); - enableNetwork(false); - } else if (blocked) { - LOG.info("Disabling network, country is blocked"); - enableNetwork(false); - } else if (network == PREF_TOR_NETWORK_NEVER - || (network == PREF_TOR_NETWORK_WIFI && !wifi)) { - LOG.info("Disabling network due to data setting"); - enableNetwork(false); - } else { - LOG.info("Enabling network"); - enableNetwork(true); - } - } catch (IOException e) { - if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); + try { + if (!online) { + LOG.info("Disabling network, device is offline"); + enableNetwork(false); + } else if (blocked) { + LOG.info("Disabling network, country is blocked"); + enableNetwork(false); + } else if (network == PREF_TOR_NETWORK_NEVER + || (network == PREF_TOR_NETWORK_WIFI && !wifi)) { + LOG.info("Disabling network due to data setting"); + enableNetwork(false); + } else { + LOG.info("Enabling network"); + enableNetwork(true); } - }); + } catch (IOException e) { + if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); + } + } + + private void scheduleConnectionStatusUpdate() { + Future<?> newConnectivityCheck = + scheduler.schedule(this::updateConnectionStatus, 1, MINUTES); + Future<?> oldConnectivityCheck = + connectivityCheck.getAndSet(newConnectivityCheck); + if (oldConnectivityCheck != null) oldConnectivityCheck.cancel(false); } private class NetworkStateReceiver extends BroadcastReceiver { @@ -721,9 +760,12 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener { @Override public void onReceive(Context ctx, Intent i) { if (!running) return; - if (CONNECTIVITY_ACTION.equals(i.getAction())) { - LOG.info("Detected connectivity change"); - updateConnectionStatus(); + String action = i.getAction(); + if (LOG.isLoggable(INFO)) LOG.info("Received broadcast " + action); + updateConnectionStatus(); + if (ACTION_SCREEN_ON.equals(action) + || ACTION_SCREEN_OFF.equals(action)) { + scheduleConnectionStatusUpdate(); } } } diff --git a/bramble-android/src/main/java/org/briarproject/bramble/plugin/tor/TorPluginFactory.java b/bramble-android/src/main/java/org/briarproject/bramble/plugin/tor/TorPluginFactory.java index 2f479d4ef8a147b755c9b3f0a17fe83359dfdbc9..4ad1e8f3ac6255b55ac8b7a357a93001b5b7d04d 100644 --- a/bramble-android/src/main/java/org/briarproject/bramble/plugin/tor/TorPluginFactory.java +++ b/bramble-android/src/main/java/org/briarproject/bramble/plugin/tor/TorPluginFactory.java @@ -17,6 +17,7 @@ import org.briarproject.bramble.api.system.LocationUtils; import org.briarproject.bramble.util.AndroidUtils; import java.util.concurrent.Executor; +import java.util.concurrent.ScheduledExecutorService; import java.util.logging.Logger; import javax.annotation.concurrent.Immutable; @@ -36,6 +37,7 @@ public class TorPluginFactory implements DuplexPluginFactory { private static final double BACKOFF_BASE = 1.2; private final Executor ioExecutor; + private final ScheduledExecutorService scheduler; private final Context appContext; private final LocationUtils locationUtils; private final DevReporter reporter; @@ -43,11 +45,13 @@ public class TorPluginFactory implements DuplexPluginFactory { private final SocketFactory torSocketFactory; private final BackoffFactory backoffFactory; - public TorPluginFactory(Executor ioExecutor, Context appContext, + public TorPluginFactory(Executor ioExecutor, + ScheduledExecutorService scheduler, Context appContext, LocationUtils locationUtils, DevReporter reporter, EventBus eventBus, SocketFactory torSocketFactory, BackoffFactory backoffFactory) { this.ioExecutor = ioExecutor; + this.scheduler = scheduler; this.appContext = appContext; this.locationUtils = locationUtils; this.reporter = reporter; @@ -89,9 +93,9 @@ public class TorPluginFactory implements DuplexPluginFactory { Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL, MAX_POLLING_INTERVAL, BACKOFF_BASE); - TorPlugin plugin = new TorPlugin(ioExecutor, appContext, locationUtils, - reporter, torSocketFactory, backoff, callback, architecture, - MAX_LATENCY, MAX_IDLE_TIME); + TorPlugin plugin = new TorPlugin(ioExecutor, scheduler, appContext, + locationUtils, reporter, torSocketFactory, backoff, callback, + architecture, MAX_LATENCY, MAX_IDLE_TIME); eventBus.addListener(plugin); return plugin; }