diff --git a/briar-android/AndroidManifest.xml b/briar-android/AndroidManifest.xml index c9784c094..4ffddcb79 100644 --- a/briar-android/AndroidManifest.xml +++ b/briar-android/AndroidManifest.xml @@ -24,6 +24,7 @@ + diff --git a/briar-android/src/org/briarproject/plugins/tor/TorPlugin.java b/briar-android/src/org/briarproject/plugins/tor/TorPlugin.java index 329985202..b0c651fb6 100644 --- a/briar-android/src/org/briarproject/plugins/tor/TorPlugin.java +++ b/briar-android/src/org/briarproject/plugins/tor/TorPlugin.java @@ -7,6 +7,7 @@ import android.content.IntentFilter; import android.net.ConnectivityManager; import android.net.NetworkInfo; import android.os.FileObserver; +import android.os.PowerManager; import net.freehaven.tor.control.EventHandler; import net.freehaven.tor.control.TorControlConnection; @@ -49,23 +50,21 @@ import java.util.List; import java.util.Scanner; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executor; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicInteger; import java.util.logging.Logger; import java.util.regex.Pattern; import java.util.zip.ZipInputStream; import static android.content.Context.CONNECTIVITY_SERVICE; import static android.content.Context.MODE_PRIVATE; +import static android.content.Context.POWER_SERVICE; import static android.net.ConnectivityManager.CONNECTIVITY_ACTION; -import static android.net.ConnectivityManager.EXTRA_NO_CONNECTIVITY; import static android.net.ConnectivityManager.TYPE_WIFI; +import static android.os.PowerManager.PARTIAL_WAKE_LOCK; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.logging.Level.INFO; import static java.util.logging.Level.WARNING; -class TorPlugin implements DuplexPlugin, EventHandler, - EventListener { +class TorPlugin implements DuplexPlugin, EventHandler, EventListener { static final TransportId ID = new TransportId("tor"); @@ -90,17 +89,12 @@ class TorPlugin implements DuplexPlugin, EventHandler, private final DuplexPluginCallback callback; private final String architecture; private final int maxLatency, maxIdleTime, pollingInterval, socketTimeout; + private final ConnectionStatus connectionStatus; private final File torDirectory, torFile, geoIpFile, configFile, doneFile; private final File cookieFile, hostnameFile; - private final AtomicBoolean circuitBuilt; - private final AtomicInteger descriptorsPublished; - - private volatile boolean running = false, networkEnabled = false; - private volatile boolean bootstrapped = false; - private volatile boolean connectedToWifi = false; - private volatile boolean online = false; - private volatile long descriptorsPublishedTime = Long.MAX_VALUE; + private final PowerManager.WakeLock wakeLock; + private volatile boolean running = false; private volatile ServerSocket socket = null; private volatile Socket controlSocket = null; private volatile TorControlConnection controlConnection = null; @@ -123,6 +117,7 @@ class TorPlugin implements DuplexPlugin, EventHandler, if (maxIdleTime > Integer.MAX_VALUE / 2) socketTimeout = Integer.MAX_VALUE; else socketTimeout = maxIdleTime * 2; + connectionStatus = new ConnectionStatus(pollingInterval); torDirectory = appContext.getDir("tor", MODE_PRIVATE); torFile = new File(torDirectory, "tor"); geoIpFile = new File(torDirectory, "geoip"); @@ -130,8 +125,10 @@ class TorPlugin implements DuplexPlugin, EventHandler, doneFile = new File(torDirectory, "done"); cookieFile = new File(torDirectory, ".tor/control_auth_cookie"); hostnameFile = new File(torDirectory, "hs/hostname"); - circuitBuilt = new AtomicBoolean(false); - descriptorsPublished = new AtomicInteger(0); + Object o = appContext.getSystemService(POWER_SERVICE); + PowerManager pm = (PowerManager) o; + wakeLock = pm.newWakeLock(PARTIAL_WAKE_LOCK, "TorPlugin"); + wakeLock.setReferenceCounted(false); } public TransportId getId() { @@ -229,7 +226,7 @@ class TorPlugin implements DuplexPlugin, EventHandler, String phase = controlConnection.getInfo("status/bootstrap-phase"); if (phase != null && phase.contains("PROGRESS=100")) { LOG.info("Tor has already bootstrapped"); - bootstrapped = true; + connectionStatus.setBootstrapped(); sendCrashReports(); } } @@ -487,15 +484,13 @@ class TorPlugin implements DuplexPlugin, EventHandler, private void enableNetwork(boolean enable) throws IOException { if (!running) return; - if (LOG.isLoggable(INFO)) LOG.info("Enabling network: " + enable); - if (!enable) { - circuitBuilt.set(false); - descriptorsPublished.set(0); - descriptorsPublishedTime = Long.MAX_VALUE; - callback.transportDisabled(); - } - networkEnabled = enable; + if (enable) wakeLock.acquire(); + connectionStatus.enableNetwork(enable); controlConnection.setConf("DisableNetwork", enable ? "0" : "1"); + if (!enable) { + callback.transportDisabled(); + wakeLock.release(); + } } public void stop() throws IOException { @@ -517,10 +512,11 @@ class TorPlugin implements DuplexPlugin, EventHandler, } catch (IOException e) { if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); } + wakeLock.release(); } public boolean isRunning() { - return running && networkEnabled && bootstrapped && circuitBuilt.get(); + return running && connectionStatus.isConnected(); } public boolean shouldPoll() { @@ -533,16 +529,13 @@ class TorPlugin implements DuplexPlugin, EventHandler, public void poll(Collection connected) { if (!isRunning()) return; - if (descriptorsPublished.get() >= MIN_DESCRIPTORS_PUBLISHED) { - long now = clock.currentTimeMillis(); - if (now - descriptorsPublishedTime >= 2 * pollingInterval) { - LOG.info("Hidden service descriptor published, not polling"); - return; - } + if (connectionStatus.shouldPoll(clock.currentTimeMillis())) { + // TODO: Pass properties to connectAndCallBack() + for (ContactId c : callback.getRemoteProperties().keySet()) + if (!connected.contains(c)) connectAndCallBack(c); + } else { + LOG.info("Hidden service descriptor published, not polling"); } - // TODO: Pass properties to connectAndCallBack() - for (ContactId c : callback.getRemoteProperties().keySet()) - if (!connected.contains(c)) connectAndCallBack(c); } private void connectAndCallBack(final ContactId c) { @@ -604,7 +597,8 @@ class TorPlugin implements DuplexPlugin, EventHandler, } public void circuitStatus(String status, String id, String path) { - if (status.equals("BUILT") && !circuitBuilt.getAndSet(true)) { + if (status.equals("BUILT") && + connectionStatus.getAndSetCircuitBuilt()) { LOG.info("First circuit built"); if (isRunning()) callback.transportEnabled(); } @@ -626,20 +620,15 @@ class TorPlugin implements DuplexPlugin, EventHandler, public void message(String severity, String msg) { if (LOG.isLoggable(INFO)) LOG.info(severity + " " + msg); if (severity.equals("NOTICE") && msg.startsWith("Bootstrapped 100%")) { - bootstrapped = true; + connectionStatus.setBootstrapped(); sendCrashReports(); if (isRunning()) callback.transportEnabled(); } } public void unrecognized(String type, String msg) { - if (type.equals("HS_DESC") && msg.startsWith("UPLOADED")) { - int descriptors = descriptorsPublished.incrementAndGet(); - if (descriptors == MIN_DESCRIPTORS_PUBLISHED) { - LOG.info("Hidden service descriptor published"); - descriptorsPublishedTime = clock.currentTimeMillis(); - } - } + if (type.equals("HS_DESC") && msg.startsWith("UPLOADED")) + connectionStatus.descriptorPublished(clock.currentTimeMillis()); } private static class WriteObserver extends FileObserver { @@ -661,7 +650,7 @@ class TorPlugin implements DuplexPlugin, EventHandler, public void eventOccurred(Event e) { if (e instanceof SettingsUpdatedEvent) { if (((SettingsUpdatedEvent) e).getNamespace().equals("tor")) { - // Wifi setting may have been updated + LOG.info("Tor settings updated"); updateConnectionStatus(); } } @@ -672,17 +661,23 @@ class TorPlugin implements DuplexPlugin, EventHandler, public void run() { if (!running) return; + 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(); - if (LOG.isLoggable(INFO)) { - LOG.info("Online: " + online); - if ("".equals(country)) LOG.info("Country code unknown"); - else LOG.info("Country code: " + country); - } boolean blocked = TorNetworkMetadata.isTorProbablyBlocked( country); Settings s = callback.getSettings(); boolean useMobileData = s.getBoolean("torOverMobile", true); + 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"); @@ -690,10 +685,11 @@ class TorPlugin implements DuplexPlugin, EventHandler, } else if (blocked) { LOG.info("Disabling network, country is blocked"); enableNetwork(false); - } else if (!useMobileData & !connectedToWifi) { + } else if (!wifi && !useMobileData) { LOG.info("Disabling network due to data setting"); enableNetwork(false); } else { + LOG.info("Enabling network"); enableNetwork(true); } } catch (IOException e) { @@ -709,15 +705,57 @@ class TorPlugin implements DuplexPlugin, EventHandler, @Override public void onReceive(Context ctx, Intent i) { if (!running) return; - online = !i.getBooleanExtra(EXTRA_NO_CONNECTIVITY, false); - // Some devices fail to set EXTRA_NO_CONNECTIVITY, double check - Object o = ctx.getSystemService(CONNECTIVITY_SERVICE); - ConnectivityManager cm = (ConnectivityManager) o; - NetworkInfo net = cm.getActiveNetworkInfo(); - if (net == null || !net.isConnected()) online = false; - connectedToWifi = (net != null && net.getType() == TYPE_WIFI - && net.isConnected()); - updateConnectionStatus(); + if (CONNECTIVITY_ACTION.equals(i.getAction())) { + LOG.info("Detected connectivity change"); + updateConnectionStatus(); + } + } + } + + private static class ConnectionStatus { + + private final int pollingInterval; + + // All of the following are locking: this + private boolean networkEnabled = false; + private boolean bootstrapped = false, circuitBuilt = false; + private int descriptorsPublished = 0; + private long descriptorsPublishedTime = Long.MAX_VALUE; + + private ConnectionStatus(int pollingInterval) { + this.pollingInterval = pollingInterval; + } + + private synchronized void setBootstrapped() { + bootstrapped = true; + } + + private synchronized boolean getAndSetCircuitBuilt() { + boolean firstCircuit = !circuitBuilt; + circuitBuilt = true; + return firstCircuit; + } + + private synchronized void descriptorPublished(long now) { + descriptorsPublished++; + if (descriptorsPublished == MIN_DESCRIPTORS_PUBLISHED) + descriptorsPublishedTime = now; + } + + private synchronized void enableNetwork(boolean enable) { + networkEnabled = enable; + circuitBuilt = false; + descriptorsPublished = 0; + descriptorsPublishedTime = Long.MAX_VALUE; + } + + private synchronized boolean isConnected() { + return networkEnabled && bootstrapped && circuitBuilt; + } + + private synchronized boolean shouldPoll(long now) { + return descriptorsPublished < MIN_DESCRIPTORS_PUBLISHED + || now - descriptorsPublishedTime < 2 * pollingInterval; } } }