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 b0ccdab86f64b112fd95441562117dfdbcecef48..5ce2d8255830cccc798f8ea07482bf62e62816bd 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
@@ -50,7 +50,6 @@ import java.net.InetSocketAddress;
 import java.net.ServerSocket;
 import java.net.Socket;
 import java.util.Arrays;
-import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
@@ -334,7 +333,7 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
 		return zin;
 	}
 
-	private InputStream getConfigInputStream() throws IOException {
+	private InputStream getConfigInputStream() {
 		int resId = getResourceId("torrc");
 		return appContext.getResources().openRawResource(resId);
 	}
@@ -499,7 +498,7 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
 	}
 
 	@Override
-	public void stop() throws PluginException {
+	public void stop() {
 		running = false;
 		tryToClose(socket);
 		if (networkStateReceiver != null)
@@ -533,20 +532,16 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
 	}
 
 	@Override
-	public void poll(Collection<ContactId> connected) {
+	public void poll(Map<ContactId, TransportProperties> contacts) {
 		if (!isRunning()) return;
 		backoff.increment();
-		Map<ContactId, TransportProperties> remote =
-				callback.getRemoteProperties();
-		for (Entry<ContactId, TransportProperties> e : remote.entrySet()) {
-			ContactId c = e.getKey();
-			if (!connected.contains(c)) connectAndCallBack(c, e.getValue());
+		for (Entry<ContactId, TransportProperties> e : contacts.entrySet()) {
+			connectAndCallBack(e.getKey(), e.getValue());
 		}
 	}
 
 	private void connectAndCallBack(ContactId c, TransportProperties p) {
 		ioExecutor.execute(() -> {
-			if (!isRunning()) return;
 			DuplexTransportConnection d = createConnection(p);
 			if (d != null) {
 				backoff.reset();
@@ -556,13 +551,8 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
 	}
 
 	@Override
-	public DuplexTransportConnection createConnection(ContactId c) {
+	public DuplexTransportConnection createConnection(TransportProperties p) {
 		if (!isRunning()) return null;
-		return createConnection(callback.getRemoteProperties(c));
-	}
-
-	@Nullable
-	private DuplexTransportConnection createConnection(TransportProperties p) {
 		String onion = p.get(PROP_ONION);
 		if (StringUtils.isNullOrEmpty(onion)) return null;
 		if (!ONION.matcher(onion).matches()) {
diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/FileConstants.java b/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/FileConstants.java
new file mode 100644
index 0000000000000000000000000000000000000000..bed296874522402a6fa6e34e7645193ad3e1a765
--- /dev/null
+++ b/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/FileConstants.java
@@ -0,0 +1,6 @@
+package org.briarproject.bramble.api.plugin;
+
+public interface FileConstants {
+
+	String PROP_PATH = "path";
+}
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 f37ef3f07ed82aeb23b49eaa78b75ea4db19b227..9e3fdd466998c6a0f3d9333c34e04e0ec0829e12 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
@@ -2,8 +2,9 @@ package org.briarproject.bramble.api.plugin;
 
 import org.briarproject.bramble.api.contact.ContactId;
 import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
+import org.briarproject.bramble.api.properties.TransportProperties;
 
-import java.util.Collection;
+import java.util.Map;
 
 @NotNullByDefault
 public interface Plugin {
@@ -39,21 +40,19 @@ public interface Plugin {
 	boolean isRunning();
 
 	/**
-	 * Returns true if the plugin's {@link #poll(Collection)} method should be
-	 * called periodically to attempt to establish connections.
+	 * Returns true if the plugin should be polled periodically to attempt to
+	 * establish connections.
 	 */
 	boolean shouldPoll();
 
 	/**
-	 * Returns the desired interval in milliseconds between calls to the
-	 * plugin's {@link #poll(Collection)} method.
+	 * Returns the desired interval in milliseconds between polling attempts.
 	 */
 	int getPollingInterval();
 
 	/**
-	 * Attempts to establish connections to contacts, passing any created
-	 * connections to the callback. To avoid creating redundant connections,
-	 * the plugin may exclude the given contacts from polling.
+	 * Attempts to establish connections to the given contacts, passing any
+	 * created connections to the callback.
 	 */
-	void poll(Collection<ContactId> connected);
+	void poll(Map<ContactId, TransportProperties> contacts);
 }
diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/PluginCallback.java b/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/PluginCallback.java
index 3b7364c4fee854140918536197a02deece764744..f2d5e8f79364606a3c697490a9a74ac324c1111f 100644
--- a/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/PluginCallback.java
+++ b/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/PluginCallback.java
@@ -1,12 +1,9 @@
 package org.briarproject.bramble.api.plugin;
 
-import org.briarproject.bramble.api.contact.ContactId;
 import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
 import org.briarproject.bramble.api.properties.TransportProperties;
 import org.briarproject.bramble.api.settings.Settings;
 
-import java.util.Map;
-
 /**
  * An interface through which a transport plugin interacts with the rest of
  * the application.
@@ -25,17 +22,7 @@ public interface PluginCallback {
 	TransportProperties getLocalProperties();
 
 	/**
-	 * Returns the plugin's remote transport properties.
-	 */
-	Map<ContactId, TransportProperties> getRemoteProperties();
-
-	/**
-	 * Returns the plugin's remote transport properties for the given contact.
-	 */
-	TransportProperties getRemoteProperties(ContactId c);
-
-	/**
-	 * Merges the given settings with the namespaced settings
+	 * Merges the given settings with the plugin's settings
 	 */
 	void mergeSettings(Settings s);
 
@@ -45,34 +32,12 @@ public interface PluginCallback {
 	void mergeLocalProperties(TransportProperties p);
 
 	/**
-	 * Presents the user with a choice among two or more named options and
-	 * returns the user's response. The message may consist of a translatable
-	 * format string and arguments.
-	 *
-	 * @return an index into the array of options indicating the user's choice,
-	 * or -1 if the user cancelled the choice.
-	 */
-	int showChoice(String[] options, String... message);
-
-	/**
-	 * Asks the user to confirm an action and returns the user's response. The
-	 * message may consist of a translatable format string and arguments.
-	 */
-	boolean showConfirmationMessage(String... message);
-
-	/**
-	 * Shows a message to the user. The message may consist of a translatable
-	 * format string and arguments.
-	 */
-	void showMessage(String... message);
-
-	/**
-	 * Signal that the transport got enabled.
+	 * Signals that the transport is enabled.
 	 */
 	void transportEnabled();
 
 	/**
-	 * Signal that the transport got disabled.
+	 * Signals that the transport is disabled.
 	 */
 	void transportDisabled();
 }
diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/TransportConnectionWriter.java b/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/TransportConnectionWriter.java
index a066f6422e3985dbc65ca1d80bec61fd3ecdb8f3..219f33efea449fd8e419bed7393c3a84b8f7e3b1 100644
--- a/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/TransportConnectionWriter.java
+++ b/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/TransportConnectionWriter.java
@@ -22,11 +22,6 @@ public interface TransportConnectionWriter {
 	 */
 	int getMaxIdleTime();
 
-	/**
-	 * Returns the capacity of the transport connection in bytes.
-	 */
-	long getCapacity();
-
 	/**
 	 * Returns an output stream for writing to the transport connection.
 	 */
diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/duplex/AbstractDuplexTransportConnection.java b/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/duplex/AbstractDuplexTransportConnection.java
index cdad52612966d9cbf8b68ec353470cf2626ce893..ba84134e9212af26614ec48b271da4f139de2a82 100644
--- a/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/duplex/AbstractDuplexTransportConnection.java
+++ b/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/duplex/AbstractDuplexTransportConnection.java
@@ -71,11 +71,6 @@ public abstract class AbstractDuplexTransportConnection
 			return plugin.getMaxIdleTime();
 		}
 
-		@Override
-		public long getCapacity() {
-			return Long.MAX_VALUE;
-		}
-
 		@Override
 		public OutputStream getOutputStream() throws IOException {
 			return AbstractDuplexTransportConnection.this.getOutputStream();
diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/duplex/DuplexPlugin.java b/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/duplex/DuplexPlugin.java
index 8ab6c4fe445f09b0d15494b21c6637bc22c27f81..5633d77eec15f5686c3369122622986caecf89b3 100644
--- a/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/duplex/DuplexPlugin.java
+++ b/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/duplex/DuplexPlugin.java
@@ -1,10 +1,10 @@
 package org.briarproject.bramble.api.plugin.duplex;
 
-import org.briarproject.bramble.api.contact.ContactId;
 import org.briarproject.bramble.api.data.BdfList;
 import org.briarproject.bramble.api.keyagreement.KeyAgreementListener;
 import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
 import org.briarproject.bramble.api.plugin.Plugin;
+import org.briarproject.bramble.api.properties.TransportProperties;
 
 import javax.annotation.Nullable;
 
@@ -15,12 +15,11 @@ import javax.annotation.Nullable;
 public interface DuplexPlugin extends Plugin {
 
 	/**
-	 * Attempts to create and return a connection to the given contact using
-	 * the current transport and configuration properties. Returns null if a
-	 * connection cannot be created.
+	 * Attempts to create and return a connection using the given transport
+	 * properties. Returns null if a connection cannot be created.
 	 */
 	@Nullable
-	DuplexTransportConnection createConnection(ContactId c);
+	DuplexTransportConnection createConnection(TransportProperties p);
 
 	/**
 	 * Returns true if the plugin supports short-range key agreement.
diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/duplex/DuplexPluginCallback.java b/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/duplex/DuplexPluginCallback.java
index 00f1acdc65718ca974c5bfa78149a78900538e63..8a97cf7fb192e2d5bb01cbfb6797232b359c3f9c 100644
--- a/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/duplex/DuplexPluginCallback.java
+++ b/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/duplex/DuplexPluginCallback.java
@@ -5,7 +5,8 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
 import org.briarproject.bramble.api.plugin.PluginCallback;
 
 /**
- * An interface for handling connections created by a duplex transport plugin.
+ * An interface through which a duplex plugin interacts with the rest of the
+ * application.
  */
 @NotNullByDefault
 public interface DuplexPluginCallback extends PluginCallback {
diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/simplex/SimplexPlugin.java b/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/simplex/SimplexPlugin.java
index 3778918c2505cf5f5edb22747d14950f336e46f3..7f0ab0141b2a30ecc80c1e202c136a680276933a 100644
--- a/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/simplex/SimplexPlugin.java
+++ b/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/simplex/SimplexPlugin.java
@@ -1,10 +1,10 @@
 package org.briarproject.bramble.api.plugin.simplex;
 
-import org.briarproject.bramble.api.contact.ContactId;
 import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
 import org.briarproject.bramble.api.plugin.Plugin;
 import org.briarproject.bramble.api.plugin.TransportConnectionReader;
 import org.briarproject.bramble.api.plugin.TransportConnectionWriter;
+import org.briarproject.bramble.api.properties.TransportProperties;
 
 import javax.annotation.Nullable;
 
@@ -15,18 +15,16 @@ import javax.annotation.Nullable;
 public interface SimplexPlugin extends Plugin {
 
 	/**
-	 * Attempts to create and return a reader for the given contact using the
-	 * current transport and configuration properties. Returns null if a reader
-	 * cannot be created.
+	 * Attempts to create and return a reader for the given transport
+	 * properties. Returns null if a reader cannot be created.
 	 */
 	@Nullable
-	TransportConnectionReader createReader(ContactId c);
+	TransportConnectionReader createReader(TransportProperties p);
 
 	/**
-	 * Attempts to create and return a writer for the given contact using the
-	 * current transport and configuration properties. Returns null if a writer
-	 * cannot be created.
+	 * Attempts to create and return a writer for the given transport
+	 * properties. Returns null if a writer cannot be created.
 	 */
 	@Nullable
-	TransportConnectionWriter createWriter(ContactId c);
+	TransportConnectionWriter createWriter(TransportProperties p);
 }
diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/simplex/SimplexPluginCallback.java b/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/simplex/SimplexPluginCallback.java
index d36a5d5e88801be258f126761ddb15098b0c5efd..1f07ec25ae63cbfd339a3ef3767cee9ae1de80e6 100644
--- a/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/simplex/SimplexPluginCallback.java
+++ b/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/simplex/SimplexPluginCallback.java
@@ -7,8 +7,8 @@ import org.briarproject.bramble.api.plugin.TransportConnectionReader;
 import org.briarproject.bramble.api.plugin.TransportConnectionWriter;
 
 /**
- * An interface for handling readers and writers created by a simplex transport
- * plugin.
+ * An interface through which a simplex plugin interacts with the rest of the
+ * application.
  */
 @NotNullByDefault
 public interface SimplexPluginCallback extends PluginCallback {
diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/ui/UiCallback.java b/bramble-api/src/main/java/org/briarproject/bramble/api/ui/UiCallback.java
deleted file mode 100644
index c5ad6d1fc21f6f492ce3aaad41b1c17e5b43279b..0000000000000000000000000000000000000000
--- a/bramble-api/src/main/java/org/briarproject/bramble/api/ui/UiCallback.java
+++ /dev/null
@@ -1,26 +0,0 @@
-package org.briarproject.bramble.api.ui;
-
-public interface UiCallback {
-
-	/**
-	 * Presents the user with a choice among two or more named options and
-	 * returns the user's response. The message may consist of a translatable
-	 * format string and arguments.
-	 *
-	 * @return an index into the array of options indicating the user's choice,
-	 * or -1 if the user cancelled the choice.
-	 */
-	int showChoice(String[] options, String... message);
-
-	/**
-	 * Asks the user to confirm an action and returns the user's response. The
-	 * message may consist of a translatable format string and arguments.
-	 */
-	boolean showConfirmationMessage(String... message);
-
-	/**
-	 * Shows a message to the user. The message may consist of a translatable
-	 * format string and arguments.
-	 */
-	void showMessage(String... message);
-}
diff --git a/bramble-api/src/main/java/org/briarproject/bramble/util/OsUtils.java b/bramble-api/src/main/java/org/briarproject/bramble/util/OsUtils.java
index 66ee8f256cdd45d410546a17794219b1efe3414e..bcde55bf0d9171575b07422685133f174eebc51e 100644
--- a/bramble-api/src/main/java/org/briarproject/bramble/util/OsUtils.java
+++ b/bramble-api/src/main/java/org/briarproject/bramble/util/OsUtils.java
@@ -22,19 +22,6 @@ public class OsUtils {
 		return os != null && os.contains("Mac OS");
 	}
 
-	public static boolean isMacLeopardOrNewer() {
-		if (!isMac() || version == null) return false;
-		try {
-			String[] v = version.split("\\.");
-			if (v.length != 3) return false;
-			int major = Integer.parseInt(v[0]);
-			int minor = Integer.parseInt(v[1]);
-			return major >= 10 && minor >= 5;
-		} catch (NumberFormatException e) {
-			return false;
-		}
-	}
-
 	public static boolean isLinux() {
 		return os != null && os.contains("Linux") && !isAndroid();
 	}
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/PluginManagerImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/PluginManagerImpl.java
index 5b74eb4befc81ec75452e7e842a111d27b61cd47..65f66345cee6dc05e3a369048977f976cfc85c6b 100644
--- a/bramble-core/src/main/java/org/briarproject/bramble/plugin/PluginManagerImpl.java
+++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/PluginManagerImpl.java
@@ -32,12 +32,10 @@ import org.briarproject.bramble.api.settings.Settings;
 import org.briarproject.bramble.api.settings.SettingsManager;
 import org.briarproject.bramble.api.system.Clock;
 import org.briarproject.bramble.api.system.Scheduler;
-import org.briarproject.bramble.api.ui.UiCallback;
 
 import java.security.SecureRandom;
 import java.util.ArrayList;
 import java.util.Collection;
-import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
@@ -71,7 +69,6 @@ class PluginManagerImpl implements PluginManager, Service {
 	private final TransportPropertyManager transportPropertyManager;
 	private final SecureRandom random;
 	private final Clock clock;
-	private final UiCallback uiCallback;
 	private final Map<TransportId, Plugin> plugins;
 	private final List<SimplexPlugin> simplexPlugins;
 	private final List<DuplexPlugin> duplexPlugins;
@@ -85,8 +82,7 @@ class PluginManagerImpl implements PluginManager, Service {
 			ConnectionRegistry connectionRegistry,
 			SettingsManager settingsManager,
 			TransportPropertyManager transportPropertyManager,
-			SecureRandom random, Clock clock,
-			UiCallback uiCallback) {
+			SecureRandom random, Clock clock) {
 		this.ioExecutor = ioExecutor;
 		this.scheduler = scheduler;
 		this.eventBus = eventBus;
@@ -97,7 +93,6 @@ class PluginManagerImpl implements PluginManager, Service {
 		this.transportPropertyManager = transportPropertyManager;
 		this.random = random;
 		this.clock = clock;
-		this.uiCallback = uiCallback;
 		plugins = new ConcurrentHashMap<>();
 		simplexPlugins = new CopyOnWriteArrayList<>();
 		duplexPlugins = new CopyOnWriteArrayList<>();
@@ -106,13 +101,14 @@ class PluginManagerImpl implements PluginManager, Service {
 	}
 
 	@Override
-	public void startService() throws ServiceException {
+	public void startService() {
 		if (used.getAndSet(true)) throw new IllegalStateException();
 		// Instantiate the poller
 		if (pluginConfig.shouldPoll()) {
 			LOG.info("Starting poller");
 			Poller poller = new Poller(ioExecutor, scheduler, connectionManager,
-					connectionRegistry, this, random, clock);
+					connectionRegistry, this, transportPropertyManager, random,
+					clock);
 			eventBus.addListener(poller);
 		}
 		// Instantiate the simplex plugins and start them asynchronously
@@ -297,26 +293,6 @@ class PluginManagerImpl implements PluginManager, Service {
 			}
 		}
 
-		@Override
-		public Map<ContactId, TransportProperties> getRemoteProperties() {
-			try {
-				return transportPropertyManager.getRemoteProperties(id);
-			} catch (DbException e) {
-				if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
-				return Collections.emptyMap();
-			}
-		}
-
-		@Override
-		public TransportProperties getRemoteProperties(ContactId c) {
-			try {
-				return transportPropertyManager.getRemoteProperties(c, id);
-			} catch (DbException e) {
-				if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
-				return new TransportProperties();
-			}
-		}
-
 		@Override
 		public void mergeSettings(Settings s) {
 			try {
@@ -335,21 +311,6 @@ class PluginManagerImpl implements PluginManager, Service {
 			}
 		}
 
-		@Override
-		public int showChoice(String[] options, String... message) {
-			return uiCallback.showChoice(options, message);
-		}
-
-		@Override
-		public boolean showConfirmationMessage(String... message) {
-			return uiCallback.showConfirmationMessage(message);
-		}
-
-		@Override
-		public void showMessage(String... message) {
-			uiCallback.showMessage(message);
-		}
-
 		@Override
 		public void transportEnabled() {
 			eventBus.broadcast(new TransportEnabledEvent(id));
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/PluginModule.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/PluginModule.java
index 773910481b366c8b5166b1a074e4a15f26d991fc..499da88b9dea10601fc093ea3159df38a3f345dd 100644
--- a/bramble-core/src/main/java/org/briarproject/bramble/plugin/PluginModule.java
+++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/PluginModule.java
@@ -1,18 +1,10 @@
 package org.briarproject.bramble.plugin;
 
-import org.briarproject.bramble.api.event.EventBus;
-import org.briarproject.bramble.api.lifecycle.IoExecutor;
 import org.briarproject.bramble.api.lifecycle.LifecycleManager;
 import org.briarproject.bramble.api.plugin.BackoffFactory;
 import org.briarproject.bramble.api.plugin.ConnectionManager;
 import org.briarproject.bramble.api.plugin.ConnectionRegistry;
 import org.briarproject.bramble.api.plugin.PluginManager;
-import org.briarproject.bramble.api.system.Clock;
-import org.briarproject.bramble.api.system.Scheduler;
-
-import java.security.SecureRandom;
-import java.util.concurrent.Executor;
-import java.util.concurrent.ScheduledExecutorService;
 
 import javax.inject.Inject;
 import javax.inject.Singleton;
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/Poller.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/Poller.java
index c68126282a6e9f16f9eb9468407cc1227562f1c6..a089fe20328c1cbfd12d8ecde610b4a38be3f958 100644
--- a/bramble-core/src/main/java/org/briarproject/bramble/plugin/Poller.java
+++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/Poller.java
@@ -2,6 +2,7 @@ package org.briarproject.bramble.plugin;
 
 import org.briarproject.bramble.api.contact.ContactId;
 import org.briarproject.bramble.api.contact.event.ContactStatusChangedEvent;
+import org.briarproject.bramble.api.db.DbException;
 import org.briarproject.bramble.api.event.Event;
 import org.briarproject.bramble.api.event.EventListener;
 import org.briarproject.bramble.api.lifecycle.IoExecutor;
@@ -19,10 +20,13 @@ import org.briarproject.bramble.api.plugin.event.ConnectionOpenedEvent;
 import org.briarproject.bramble.api.plugin.event.TransportDisabledEvent;
 import org.briarproject.bramble.api.plugin.event.TransportEnabledEvent;
 import org.briarproject.bramble.api.plugin.simplex.SimplexPlugin;
+import org.briarproject.bramble.api.properties.TransportProperties;
+import org.briarproject.bramble.api.properties.TransportPropertyManager;
 import org.briarproject.bramble.api.system.Clock;
 import org.briarproject.bramble.api.system.Scheduler;
 
 import java.security.SecureRandom;
+import java.util.Collection;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.concurrent.Executor;
@@ -33,10 +37,10 @@ import java.util.concurrent.locks.ReentrantLock;
 import java.util.logging.Logger;
 
 import javax.annotation.concurrent.ThreadSafe;
-import javax.inject.Inject;
 
 import static java.util.concurrent.TimeUnit.MILLISECONDS;
 import static java.util.logging.Level.INFO;
+import static java.util.logging.Level.WARNING;
 
 @ThreadSafe
 @NotNullByDefault
@@ -49,6 +53,7 @@ class Poller implements EventListener {
 	private final ConnectionManager connectionManager;
 	private final ConnectionRegistry connectionRegistry;
 	private final PluginManager pluginManager;
+	private final TransportPropertyManager transportPropertyManager;
 	private final SecureRandom random;
 	private final Clock clock;
 	private final Lock lock;
@@ -58,12 +63,14 @@ class Poller implements EventListener {
 			@Scheduler ScheduledExecutorService scheduler,
 			ConnectionManager connectionManager,
 			ConnectionRegistry connectionRegistry, PluginManager pluginManager,
+			TransportPropertyManager transportPropertyManager,
 			SecureRandom random, Clock clock) {
 		this.ioExecutor = ioExecutor;
 		this.scheduler = scheduler;
 		this.connectionManager = connectionManager;
 		this.connectionRegistry = connectionRegistry;
 		this.pluginManager = pluginManager;
+		this.transportPropertyManager = transportPropertyManager;
 		this.random = random;
 		this.clock = clock;
 		lock = new ReentrantLock();
@@ -119,10 +126,15 @@ class Poller implements EventListener {
 	private void connectToContact(ContactId c, SimplexPlugin p) {
 		ioExecutor.execute(() -> {
 			TransportId t = p.getId();
-			if (!connectionRegistry.isConnected(c, t)) {
-				TransportConnectionWriter w = p.createWriter(c);
+			if (connectionRegistry.isConnected(c, t)) return;
+			try {
+				TransportProperties props =
+						transportPropertyManager.getRemoteProperties(c, t);
+				TransportConnectionWriter w = p.createWriter(props);
 				if (w != null)
 					connectionManager.manageOutgoingConnection(c, t, w);
+			} catch (DbException e) {
+				if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
 			}
 		});
 	}
@@ -130,10 +142,15 @@ class Poller implements EventListener {
 	private void connectToContact(ContactId c, DuplexPlugin p) {
 		ioExecutor.execute(() -> {
 			TransportId t = p.getId();
-			if (!connectionRegistry.isConnected(c, t)) {
-				DuplexTransportConnection d = p.createConnection(c);
+			if (connectionRegistry.isConnected(c, t)) return;
+			try {
+				TransportProperties props =
+						transportPropertyManager.getRemoteProperties(c, t);
+				DuplexTransportConnection d = p.createConnection(props);
 				if (d != null)
 					connectionManager.manageOutgoingConnection(c, t, d);
+			} catch (DbException e) {
+				if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
 			}
 		});
 	}
@@ -185,7 +202,17 @@ class Poller implements EventListener {
 	private void poll(Plugin p) {
 		TransportId t = p.getId();
 		if (LOG.isLoggable(INFO)) LOG.info("Polling plugin " + t);
-		p.poll(connectionRegistry.getConnectedContacts(t));
+		try {
+			Map<ContactId, TransportProperties> remote =
+					transportPropertyManager.getRemoteProperties(t);
+			Collection<ContactId> connected =
+					connectionRegistry.getConnectedContacts(t);
+			remote = new HashMap<>(remote);
+			remote.keySet().removeAll(connected);
+			if (!remote.isEmpty()) p.poll(remote);
+		} catch (DbException e) {
+			if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
+		}
 	}
 
 	private class ScheduledPollTask {
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 9b4ba5bb9379505fdf63b285a921de4e9f5878af..14ee2f2bf95b1e54105e0ff9c5099de412e696c3 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
@@ -26,7 +26,6 @@ import org.briarproject.bramble.util.StringUtils;
 
 import java.io.IOException;
 import java.security.SecureRandom;
-import java.util.Collection;
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.UUID;
@@ -250,19 +249,16 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
 	}
 
 	@Override
-	public void poll(Collection<ContactId> connected) {
+	public void poll(Map<ContactId, TransportProperties> contacts) {
 		if (!isRunning() || !shouldAllowContactConnections()) return;
 		backoff.increment();
 		// Try to connect to known devices in parallel
-		Map<ContactId, TransportProperties> remote =
-				callback.getRemoteProperties();
-		for (Entry<ContactId, TransportProperties> e : remote.entrySet()) {
-			ContactId c = e.getKey();
-			if (connected.contains(c)) continue;
+		for (Entry<ContactId, TransportProperties> e : contacts.entrySet()) {
 			String address = e.getValue().get(PROP_ADDRESS);
 			if (StringUtils.isNullOrEmpty(address)) continue;
 			String uuid = e.getValue().get(PROP_UUID);
 			if (StringUtils.isNullOrEmpty(uuid)) continue;
+			ContactId c = e.getKey();
 			ioExecutor.execute(() -> {
 				if (!isRunning() || !shouldAllowContactConnections()) return;
 				if (!connectionLimiter.canOpenContactConnection()) return;
@@ -308,10 +304,9 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
 	}
 
 	@Override
-	public DuplexTransportConnection createConnection(ContactId c) {
+	public DuplexTransportConnection createConnection(TransportProperties p) {
 		if (!isRunning() || !shouldAllowContactConnections()) return null;
 		if (!connectionLimiter.canOpenContactConnection()) return null;
-		TransportProperties p = callback.getRemoteProperties(c);
 		String address = p.get(PROP_ADDRESS);
 		if (StringUtils.isNullOrEmpty(address)) return null;
 		String uuid = p.get(PROP_UUID);
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/FilePlugin.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/FilePlugin.java
index 6355342e6545d5ca43a1b5e4625b53151fe79d17..5aaa9f012214af648b942a71181c410630f631b8 100644
--- a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/FilePlugin.java
+++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/FilePlugin.java
@@ -1,27 +1,21 @@
 package org.briarproject.bramble.plugin.file;
 
-import org.briarproject.bramble.api.contact.ContactId;
 import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
 import org.briarproject.bramble.api.plugin.TransportConnectionReader;
 import org.briarproject.bramble.api.plugin.TransportConnectionWriter;
 import org.briarproject.bramble.api.plugin.simplex.SimplexPlugin;
 import org.briarproject.bramble.api.plugin.simplex.SimplexPluginCallback;
+import org.briarproject.bramble.api.properties.TransportProperties;
 
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
 import java.io.IOException;
-import java.io.OutputStream;
-import java.util.Collection;
-import java.util.Locale;
-import java.util.concurrent.Executor;
-import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.logging.Logger;
 
-import javax.annotation.Nullable;
-
 import static java.util.logging.Level.WARNING;
-import static org.briarproject.bramble.api.transport.TransportConstants.MIN_STREAM_LENGTH;
+import static org.briarproject.bramble.api.plugin.FileConstants.PROP_PATH;
+import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
 
 @NotNullByDefault
 abstract class FilePlugin implements SimplexPlugin {
@@ -29,25 +23,15 @@ abstract class FilePlugin implements SimplexPlugin {
 	private static final Logger LOG =
 			Logger.getLogger(FilePlugin.class.getName());
 
-	protected final Executor ioExecutor;
 	protected final SimplexPluginCallback callback;
 	protected final int maxLatency;
-	protected final AtomicBoolean used = new AtomicBoolean(false);
-
-	protected volatile boolean running = false;
-
-	@Nullable
-	protected abstract File chooseOutputDirectory();
-
-	protected abstract Collection<File> findFilesByName(String filename);
 
-	protected abstract void writerFinished(File f);
+	protected abstract void writerFinished(File f, boolean exception);
 
-	protected abstract void readerFinished(File f);
+	protected abstract void readerFinished(File f, boolean exception,
+			boolean recognised);
 
-	protected FilePlugin(Executor ioExecutor, SimplexPluginCallback callback,
-			int maxLatency) {
-		this.ioExecutor = ioExecutor;
+	FilePlugin(SimplexPluginCallback callback, int maxLatency) {
 		this.callback = callback;
 		this.maxLatency = maxLatency;
 	}
@@ -58,81 +42,36 @@ abstract class FilePlugin implements SimplexPlugin {
 	}
 
 	@Override
-	public int getMaxIdleTime() {
-		return Integer.MAX_VALUE; // We don't need keepalives
-	}
-
-	@Override
-	public boolean isRunning() {
-		return running;
-	}
-
-	@Override
-	public TransportConnectionReader createReader(ContactId c) {
-		return null;
-	}
-
-	@Override
-	public TransportConnectionWriter createWriter(ContactId c) {
-		if (!running) return null;
-		return createWriter(createConnectionFilename());
-	}
-
-	private String createConnectionFilename() {
-		StringBuilder s = new StringBuilder(12);
-		for (int i = 0; i < 8; i++) s.append((char) ('a' + Math.random() * 26));
-		s.append(".dat");
-		return s.toString();
-	}
-
-	// Package access for testing
-	boolean isPossibleConnectionFilename(String filename) {
-		return filename.toLowerCase(Locale.US).matches("[a-z]{8}\\.dat");
-	}
-
-	@Nullable
-	private TransportConnectionWriter createWriter(String filename) {
-		if (!running) return null;
-		File dir = chooseOutputDirectory();
-		if (dir == null || !dir.exists() || !dir.isDirectory()) return null;
-		File f = new File(dir, filename);
+	public TransportConnectionReader createReader(TransportProperties p) {
+		if (!isRunning()) return null;
+		String path = p.get(PROP_PATH);
+		if (isNullOrEmpty(path)) return null;
 		try {
-			long capacity = dir.getFreeSpace();
-			if (capacity < MIN_STREAM_LENGTH) return null;
-			OutputStream out = new FileOutputStream(f);
-			return new FileTransportWriter(f, out, capacity, this);
+			File file = new File(path);
+			FileInputStream in = new FileInputStream(file);
+			return new FileTransportReader(file, in, this);
 		} catch (IOException e) {
 			if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
-			f.delete();
 			return null;
 		}
 	}
 
-	protected void createReaderFromFile(File f) {
-		if (!running) return;
-		ioExecutor.execute(new ReaderCreator(f));
-	}
-
-	private class ReaderCreator implements Runnable {
-
-		private final File file;
-
-		private ReaderCreator(File file) {
-			this.file = file;
-		}
-
-		@Override
-		public void run() {
-			if (isPossibleConnectionFilename(file.getName())) {
-				try {
-					FileInputStream in = new FileInputStream(file);
-					callback.readerCreated(new FileTransportReader(file, in,
-							FilePlugin.this));
-				} catch (IOException e) {
-					if (LOG.isLoggable(WARNING))
-						LOG.log(WARNING, e.toString(), e);
-				}
+	@Override
+	public TransportConnectionWriter createWriter(TransportProperties p) {
+		if (!isRunning()) return null;
+		String path = p.get(PROP_PATH);
+		if (isNullOrEmpty(path)) return null;
+		try {
+			File file = new File(path);
+			if (!file.exists() && !file.createNewFile()) {
+				LOG.info("Failed to create file");
+				return null;
 			}
+			FileOutputStream out = new FileOutputStream(file);
+			return new FileTransportWriter(file, out, this);
+		} catch (IOException e) {
+			if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
+			return null;
 		}
 	}
 }
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/FileTransportReader.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/FileTransportReader.java
index df5d5b9086d4c61ad5bdb26823fc9974d4e52867..9b88d841ecfdf3c7a55d3fe118e7b3107b5dd23b 100644
--- a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/FileTransportReader.java
+++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/FileTransportReader.java
@@ -38,9 +38,6 @@ class FileTransportReader implements TransportConnectionReader {
 		} catch (IOException e) {
 			if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
 		}
-		if (recognised) {
-			file.delete();
-			plugin.readerFinished(file);
-		}
+		plugin.readerFinished(file, exception, recognised);
 	}
 }
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/FileTransportWriter.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/FileTransportWriter.java
index 2752f7c8be82ad0ebc9f1323c8884fc6aa067e72..92eb26541ded5beda0cbfeb7e46fce4eab3b0978 100644
--- a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/FileTransportWriter.java
+++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/FileTransportWriter.java
@@ -18,14 +18,11 @@ class FileTransportWriter implements TransportConnectionWriter {
 
 	private final File file;
 	private final OutputStream out;
-	private final long capacity;
 	private final FilePlugin plugin;
 
-	FileTransportWriter(File file, OutputStream out, long capacity,
-			FilePlugin plugin) {
+	FileTransportWriter(File file, OutputStream out, FilePlugin plugin) {
 		this.file = file;
 		this.out = out;
-		this.capacity = capacity;
 		this.plugin = plugin;
 	}
 
@@ -39,11 +36,6 @@ class FileTransportWriter implements TransportConnectionWriter {
 		return plugin.getMaxIdleTime();
 	}
 
-	@Override
-	public long getCapacity() {
-		return capacity;
-	}
-
 	@Override
 	public OutputStream getOutputStream() {
 		return out;
@@ -56,7 +48,6 @@ class FileTransportWriter implements TransportConnectionWriter {
 		} catch (IOException e) {
 			if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
 		}
-		if (exception) file.delete();
-		else plugin.writerFinished(file);
+		plugin.writerFinished(file, exception);
 	}
 }
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 78b3cab7c52886d5fef16274b46c3a3d1c1047fc..326a8377484f06f708978b46fef7fc27abf78fe6 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
@@ -207,20 +207,16 @@ abstract class TcpPlugin implements DuplexPlugin {
 	}
 
 	@Override
-	public void poll(Collection<ContactId> connected) {
+	public void poll(Map<ContactId, TransportProperties> contacts) {
 		if (!isRunning()) return;
 		backoff.increment();
-		Map<ContactId, TransportProperties> remote =
-				callback.getRemoteProperties();
-		for (Entry<ContactId, TransportProperties> e : remote.entrySet()) {
-			ContactId c = e.getKey();
-			if (!connected.contains(c)) connectAndCallBack(c, e.getValue());
+		for (Entry<ContactId, TransportProperties> e : contacts.entrySet()) {
+			connectAndCallBack(e.getKey(), e.getValue());
 		}
 	}
 
 	private void connectAndCallBack(ContactId c, TransportProperties p) {
 		ioExecutor.execute(() -> {
-			if (!isRunning()) return;
 			DuplexTransportConnection d = createConnection(p);
 			if (d != null) {
 				backoff.reset();
@@ -230,13 +226,8 @@ abstract class TcpPlugin implements DuplexPlugin {
 	}
 
 	@Override
-	public DuplexTransportConnection createConnection(ContactId c) {
+	public DuplexTransportConnection createConnection(TransportProperties p) {
 		if (!isRunning()) return null;
-		return createConnection(callback.getRemoteProperties(c));
-	}
-
-	@Nullable
-	private DuplexTransportConnection createConnection(TransportProperties p) {
 		for (InetSocketAddress remote : getRemoteSocketAddresses(p)) {
 			if (!isConnectable(remote)) {
 				if (LOG.isLoggable(INFO)) {
diff --git a/bramble-core/src/test/java/org/briarproject/bramble/plugin/PluginManagerImplTest.java b/bramble-core/src/test/java/org/briarproject/bramble/plugin/PluginManagerImplTest.java
index 2af1852a0dd9ccb68164a71f3cc69bb096ece63e..0cd09917dddd3f6859eab50630d0f29efd0731c2 100644
--- a/bramble-core/src/test/java/org/briarproject/bramble/plugin/PluginManagerImplTest.java
+++ b/bramble-core/src/test/java/org/briarproject/bramble/plugin/PluginManagerImplTest.java
@@ -15,7 +15,6 @@ import org.briarproject.bramble.api.plugin.simplex.SimplexPluginFactory;
 import org.briarproject.bramble.api.properties.TransportPropertyManager;
 import org.briarproject.bramble.api.settings.SettingsManager;
 import org.briarproject.bramble.api.system.Clock;
-import org.briarproject.bramble.api.ui.UiCallback;
 import org.briarproject.bramble.test.BrambleTestCase;
 import org.jmock.Expectations;
 import org.jmock.Mockery;
@@ -26,9 +25,7 @@ import java.security.SecureRandom;
 import java.util.Arrays;
 import java.util.concurrent.Executor;
 import java.util.concurrent.Executors;
-import java.util.concurrent.RejectedExecutionHandler;
 import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.ScheduledThreadPoolExecutor;
 
 import static org.briarproject.bramble.test.TestUtils.getTransportId;
 
@@ -40,19 +37,20 @@ public class PluginManagerImplTest extends BrambleTestCase {
 			setThreadingPolicy(new Synchroniser());
 		}};
 		Executor ioExecutor = Executors.newSingleThreadExecutor();
-		ScheduledExecutorService scheduler = context.mock(ScheduledExecutorService.class);
+		ScheduledExecutorService scheduler =
+				context.mock(ScheduledExecutorService.class);
 		SecureRandom random = new SecureRandom();
 		Clock clock = context.mock(Clock.class);
 		EventBus eventBus = context.mock(EventBus.class);
 		PluginConfig pluginConfig = context.mock(PluginConfig.class);
 		ConnectionManager connectionManager =
 				context.mock(ConnectionManager.class);
-		ConnectionRegistry connectionRegistry = context.mock(ConnectionRegistry.class);
+		ConnectionRegistry connectionRegistry =
+				context.mock(ConnectionRegistry.class);
 		SettingsManager settingsManager =
 				context.mock(SettingsManager.class);
 		TransportPropertyManager transportPropertyManager =
 				context.mock(TransportPropertyManager.class);
-		UiCallback uiCallback = context.mock(UiCallback.class);
 
 		// Two simplex plugin factories: both create plugins, one fails to start
 		SimplexPluginFactory simplexFactory =
@@ -124,9 +122,9 @@ public class PluginManagerImplTest extends BrambleTestCase {
 			oneOf(duplexPlugin).stop();
 		}});
 
-		PluginManagerImpl p = new PluginManagerImpl(ioExecutor, scheduler, eventBus,
-				pluginConfig, connectionManager, connectionRegistry, settingsManager,
-				transportPropertyManager, random, clock, uiCallback);
+		PluginManagerImpl p = new PluginManagerImpl(ioExecutor, scheduler,
+				eventBus, pluginConfig, connectionManager, connectionRegistry,
+				settingsManager, transportPropertyManager, random, clock);
 
 		// Two plugins should be started and stopped
 		p.startService();
diff --git a/bramble-core/src/test/java/org/briarproject/bramble/plugin/PollerTest.java b/bramble-core/src/test/java/org/briarproject/bramble/plugin/PollerTest.java
index 5b0297595ce6e6b344774b3bb61ce2f4b0dcd0ef..3a60eb39dacdedbac0973652d2367d9ed7391ef4 100644
--- a/bramble-core/src/test/java/org/briarproject/bramble/plugin/PollerTest.java
+++ b/bramble-core/src/test/java/org/briarproject/bramble/plugin/PollerTest.java
@@ -15,6 +15,8 @@ import org.briarproject.bramble.api.plugin.event.ConnectionOpenedEvent;
 import org.briarproject.bramble.api.plugin.event.TransportDisabledEvent;
 import org.briarproject.bramble.api.plugin.event.TransportEnabledEvent;
 import org.briarproject.bramble.api.plugin.simplex.SimplexPlugin;
+import org.briarproject.bramble.api.properties.TransportProperties;
+import org.briarproject.bramble.api.properties.TransportPropertyManager;
 import org.briarproject.bramble.api.system.Clock;
 import org.briarproject.bramble.test.BrambleMockTestCase;
 import org.briarproject.bramble.test.ImmediateExecutor;
@@ -24,13 +26,15 @@ import org.jmock.lib.legacy.ClassImposteriser;
 import org.junit.Test;
 
 import java.security.SecureRandom;
-import java.util.Arrays;
-import java.util.Collections;
 import java.util.List;
 import java.util.concurrent.Executor;
 import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.ScheduledFuture;
 
+import static java.util.Arrays.asList;
+import static java.util.Collections.emptyList;
+import static java.util.Collections.singletonList;
+import static java.util.Collections.singletonMap;
 import static java.util.concurrent.TimeUnit.MILLISECONDS;
 import static org.briarproject.bramble.test.TestUtils.getTransportId;
 
@@ -44,6 +48,8 @@ public class PollerTest extends BrambleMockTestCase {
 			context.mock(ConnectionRegistry.class);
 	private final PluginManager pluginManager =
 			context.mock(PluginManager.class);
+	private final TransportPropertyManager transportPropertyManager =
+			context.mock(TransportPropertyManager.class);
 	private final Clock clock = context.mock(Clock.class);
 	private final ScheduledFuture future = context.mock(ScheduledFuture.class);
 	private final SecureRandom random;
@@ -51,6 +57,7 @@ public class PollerTest extends BrambleMockTestCase {
 	private final Executor ioExecutor = new ImmediateExecutor();
 	private final TransportId transportId = getTransportId();
 	private final ContactId contactId = new ContactId(234);
+	private final TransportProperties properties = new TransportProperties();
 	private final int pollingInterval = 60 * 1000;
 	private final long now = System.currentTimeMillis();
 
@@ -66,8 +73,8 @@ public class PollerTest extends BrambleMockTestCase {
 		SimplexPlugin simplexPlugin1 =
 				context.mock(SimplexPlugin.class, "simplexPlugin1");
 		TransportId simplexId1 = getTransportId();
-		List<SimplexPlugin> simplexPlugins = Arrays.asList(simplexPlugin,
-				simplexPlugin1);
+		List<SimplexPlugin> simplexPlugins =
+				asList(simplexPlugin, simplexPlugin1);
 		TransportConnectionWriter simplexWriter =
 				context.mock(TransportConnectionWriter.class);
 
@@ -76,8 +83,8 @@ public class PollerTest extends BrambleMockTestCase {
 		TransportId duplexId = getTransportId();
 		DuplexPlugin duplexPlugin1 =
 				context.mock(DuplexPlugin.class, "duplexPlugin1");
-		List<DuplexPlugin> duplexPlugins = Arrays.asList(duplexPlugin,
-				duplexPlugin1);
+		List<DuplexPlugin> duplexPlugins =
+				asList(duplexPlugin, duplexPlugin1);
 		DuplexTransportConnection duplexConnection =
 				context.mock(DuplexTransportConnection.class);
 
@@ -96,8 +103,12 @@ public class PollerTest extends BrambleMockTestCase {
 			will(returnValue(simplexId1));
 			oneOf(connectionRegistry).isConnected(contactId, simplexId1);
 			will(returnValue(false));
+			// Get the transport properties
+			oneOf(transportPropertyManager).getRemoteProperties(contactId,
+					simplexId1);
+			will(returnValue(properties));
 			// Connect to the contact
-			oneOf(simplexPlugin1).createWriter(contactId);
+			oneOf(simplexPlugin1).createWriter(properties);
 			will(returnValue(simplexWriter));
 			// Pass the connection to the connection manager
 			oneOf(connectionManager).manageOutgoingConnection(contactId,
@@ -105,7 +116,7 @@ public class PollerTest extends BrambleMockTestCase {
 			// Get the duplex plugins
 			oneOf(pluginManager).getDuplexPlugins();
 			will(returnValue(duplexPlugins));
-			// The first plugin supports polling
+			// The duplex plugin supports polling
 			oneOf(duplexPlugin).shouldPoll();
 			will(returnValue(true));
 			// Check whether the contact is already connected
@@ -113,8 +124,12 @@ public class PollerTest extends BrambleMockTestCase {
 			will(returnValue(duplexId));
 			oneOf(connectionRegistry).isConnected(contactId, duplexId);
 			will(returnValue(false));
+			// Get the transport properties
+			oneOf(transportPropertyManager).getRemoteProperties(contactId,
+					duplexId);
+			will(returnValue(properties));
 			// Connect to the contact
-			oneOf(duplexPlugin).createConnection(contactId);
+			oneOf(duplexPlugin).createConnection(properties);
 			will(returnValue(duplexConnection));
 			// Pass the connection to the connection manager
 			oneOf(connectionManager).manageOutgoingConnection(contactId,
@@ -125,7 +140,8 @@ public class PollerTest extends BrambleMockTestCase {
 		}});
 
 		Poller p = new Poller(ioExecutor, scheduler, connectionManager,
-				connectionRegistry, pluginManager, random, clock);
+				connectionRegistry, pluginManager, transportPropertyManager,
+				random, clock);
 
 		p.eventOccurred(new ContactStatusChangedEvent(contactId, true));
 	}
@@ -165,8 +181,12 @@ public class PollerTest extends BrambleMockTestCase {
 			// Check whether the contact is already connected
 			oneOf(connectionRegistry).isConnected(contactId, transportId);
 			will(returnValue(false));
+			// Get the transport properties
+			oneOf(transportPropertyManager).getRemoteProperties(contactId,
+					transportId);
+			will(returnValue(properties));
 			// Connect to the contact
-			oneOf(plugin).createConnection(contactId);
+			oneOf(plugin).createConnection(properties);
 			will(returnValue(duplexConnection));
 			// Pass the connection to the connection manager
 			oneOf(connectionManager).manageOutgoingConnection(contactId,
@@ -174,15 +194,15 @@ public class PollerTest extends BrambleMockTestCase {
 		}});
 
 		Poller p = new Poller(ioExecutor, scheduler, connectionManager,
-				connectionRegistry, pluginManager, random, clock);
+				connectionRegistry, pluginManager, transportPropertyManager,
+				random, clock);
 
 		p.eventOccurred(new ConnectionClosedEvent(contactId, transportId,
 				false));
 	}
 
-
 	@Test
-	public void testRescheduleOnConnectionOpened() throws Exception {
+	public void testRescheduleOnConnectionOpened() {
 		Plugin plugin = context.mock(Plugin.class);
 
 		context.checking(new Expectations() {{
@@ -205,14 +225,15 @@ public class PollerTest extends BrambleMockTestCase {
 		}});
 
 		Poller p = new Poller(ioExecutor, scheduler, connectionManager,
-				connectionRegistry, pluginManager, random, clock);
+				connectionRegistry, pluginManager, transportPropertyManager,
+				random, clock);
 
 		p.eventOccurred(new ConnectionOpenedEvent(contactId, transportId,
 				false));
 	}
 
 	@Test
-	public void testRescheduleDoesNotReplaceEarlierTask() throws Exception {
+	public void testRescheduleDoesNotReplaceEarlierTask() {
 		Plugin plugin = context.mock(Plugin.class);
 
 		context.checking(new Expectations() {{
@@ -248,7 +269,8 @@ public class PollerTest extends BrambleMockTestCase {
 		}});
 
 		Poller p = new Poller(ioExecutor, scheduler, connectionManager,
-				connectionRegistry, pluginManager, random, clock);
+				connectionRegistry, pluginManager, transportPropertyManager,
+				random, clock);
 
 		p.eventOccurred(new ConnectionOpenedEvent(contactId, transportId,
 				false));
@@ -257,7 +279,7 @@ public class PollerTest extends BrambleMockTestCase {
 	}
 
 	@Test
-	public void testRescheduleReplacesLaterTask() throws Exception {
+	public void testRescheduleReplacesLaterTask() {
 		Plugin plugin = context.mock(Plugin.class);
 
 		context.checking(new Expectations() {{
@@ -296,7 +318,8 @@ public class PollerTest extends BrambleMockTestCase {
 		}});
 
 		Poller p = new Poller(ioExecutor, scheduler, connectionManager,
-				connectionRegistry, pluginManager, random, clock);
+				connectionRegistry, pluginManager, transportPropertyManager,
+				random, clock);
 
 		p.eventOccurred(new ConnectionOpenedEvent(contactId, transportId,
 				false));
@@ -306,8 +329,7 @@ public class PollerTest extends BrambleMockTestCase {
 
 	@Test
 	public void testPollsOnTransportEnabled() throws Exception {
-		Plugin plugin = context.mock(Plugin.class);
-		List<ContactId> connected = Collections.singletonList(contactId);
+		DuplexPlugin plugin = context.mock(DuplexPlugin.class);
 
 		context.checking(new Expectations() {{
 			allowing(plugin).getId();
@@ -335,20 +357,69 @@ public class PollerTest extends BrambleMockTestCase {
 			oneOf(scheduler).schedule(with(any(Runnable.class)),
 					with((long) (pollingInterval * 0.5)), with(MILLISECONDS));
 			will(returnValue(future));
+			// Get the transport properties and connected contacts
+			oneOf(transportPropertyManager).getRemoteProperties(transportId);
+			will(returnValue(singletonMap(contactId, properties)));
+			oneOf(connectionRegistry).getConnectedContacts(transportId);
+			will(returnValue(emptyList()));
 			// Poll the plugin
+			oneOf(plugin).poll(singletonMap(contactId, properties));
+		}});
+
+		Poller p = new Poller(ioExecutor, scheduler, connectionManager,
+				connectionRegistry, pluginManager, transportPropertyManager,
+				random, clock);
+
+		p.eventOccurred(new TransportEnabledEvent(transportId));
+	}
+
+	@Test
+	public void testDoesNotPollIfAllContactsAreConnected() throws Exception {
+		DuplexPlugin plugin = context.mock(DuplexPlugin.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 a polling task immediately
+			oneOf(clock).currentTimeMillis();
+			will(returnValue(now));
+			oneOf(scheduler).schedule(with(any(Runnable.class)), with(0L),
+					with(MILLISECONDS));
+			will(returnValue(future));
+			will(new RunAction());
+			// Running the polling task schedules the next polling task
+			oneOf(plugin).getPollingInterval();
+			will(returnValue(pollingInterval));
+			oneOf(random).nextDouble();
+			will(returnValue(0.5));
+			oneOf(clock).currentTimeMillis();
+			will(returnValue(now));
+			oneOf(scheduler).schedule(with(any(Runnable.class)),
+					with((long) (pollingInterval * 0.5)), with(MILLISECONDS));
+			will(returnValue(future));
+			// Get the transport properties and connected contacts
+			oneOf(transportPropertyManager).getRemoteProperties(transportId);
+			will(returnValue(singletonMap(contactId, properties)));
 			oneOf(connectionRegistry).getConnectedContacts(transportId);
-			will(returnValue(connected));
-			oneOf(plugin).poll(connected);
+			will(returnValue(singletonList(contactId)));
+			// All contacts are connected, so don't poll the plugin
 		}});
 
 		Poller p = new Poller(ioExecutor, scheduler, connectionManager,
-				connectionRegistry, pluginManager, random, clock);
+				connectionRegistry, pluginManager, transportPropertyManager,
+				random, clock);
 
 		p.eventOccurred(new TransportEnabledEvent(transportId));
 	}
 
 	@Test
-	public void testCancelsPollingOnTransportDisabled() throws Exception {
+	public void testCancelsPollingOnTransportDisabled() {
 		Plugin plugin = context.mock(Plugin.class);
 
 		context.checking(new Expectations() {{
@@ -371,7 +442,8 @@ public class PollerTest extends BrambleMockTestCase {
 		}});
 
 		Poller p = new Poller(ioExecutor, scheduler, connectionManager,
-				connectionRegistry, pluginManager, random, clock);
+				connectionRegistry, pluginManager, transportPropertyManager,
+				random, clock);
 
 		p.eventOccurred(new TransportEnabledEvent(transportId));
 		p.eventOccurred(new TransportDisabledEvent(transportId));
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 b5449ed3461739bf159f16ded0e5ce3afdd3055d..8cc15b7f2b19dcb79a7070e346371a414e9cc2fa 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
@@ -23,8 +23,6 @@ import java.net.ServerSocket;
 import java.net.Socket;
 import java.util.Collections;
 import java.util.Comparator;
-import java.util.Hashtable;
-import java.util.Map;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.Executor;
 import java.util.concurrent.Executors;
@@ -40,7 +38,6 @@ import static org.junit.Assert.assertTrue;
 
 public class LanTcpPluginTest extends BrambleTestCase {
 
-	private final ContactId contactId = new ContactId(234);
 	private final Backoff backoff = new TestBackoff();
 
 	@Test
@@ -160,12 +157,10 @@ public class LanTcpPluginTest extends BrambleTestCase {
 				error.set(true);
 			}
 		}).start();
-		// Tell the plugin about the port
+		// Connect to the port
 		TransportProperties p = new TransportProperties();
 		p.put("ipPorts", addrString + ":" + port);
-		callback.remote.put(contactId, p);
-		// Connect to the port
-		DuplexTransportConnection d = plugin.createConnection(contactId);
+		DuplexTransportConnection d = plugin.createConnection(p);
 		assertNotNull(d);
 		// Check that the connection was accepted
 		assertTrue(latch.await(5, SECONDS));
@@ -281,7 +276,7 @@ public class LanTcpPluginTest extends BrambleTestCase {
 	}
 
 	@Test
-	public void testComparatorPrefersNonZeroPorts() throws Exception {
+	public void testComparatorPrefersNonZeroPorts() {
 		Comparator<InetSocketAddress> comparator = new LanAddressComparator();
 		InetSocketAddress nonZero = new InetSocketAddress("1.2.3.4", 1234);
 		InetSocketAddress zero = new InetSocketAddress("1.2.3.4", 0);
@@ -294,7 +289,7 @@ public class LanTcpPluginTest extends BrambleTestCase {
 	}
 
 	@Test
-	public void testComparatorPrefersLongerPrefixes() throws Exception {
+	public void testComparatorPrefersLongerPrefixes() {
 		Comparator<InetSocketAddress> comparator = new LanAddressComparator();
 		InetSocketAddress prefix192 = new InetSocketAddress("192.168.0.1", 0);
 		InetSocketAddress prefix172 = new InetSocketAddress("172.16.0.1", 0);
@@ -314,7 +309,7 @@ public class LanTcpPluginTest extends BrambleTestCase {
 	}
 
 	@Test
-	public void testComparatorPrefersSiteLocalToLinkLocal() throws Exception {
+	public void testComparatorPrefersSiteLocalToLinkLocal() {
 		Comparator<InetSocketAddress> comparator = new LanAddressComparator();
 		InetSocketAddress prefix192 = new InetSocketAddress("192.168.0.1", 0);
 		InetSocketAddress prefix172 = new InetSocketAddress("172.16.0.1", 0);
@@ -345,8 +340,6 @@ public class LanTcpPluginTest extends BrambleTestCase {
 	@NotNullByDefault
 	private static class Callback implements DuplexPluginCallback {
 
-		private final Map<ContactId, TransportProperties> remote =
-				new Hashtable<>();
 		private final CountDownLatch propertiesLatch = new CountDownLatch(1);
 		private final CountDownLatch connectionsLatch = new CountDownLatch(1);
 		private final TransportProperties local = new TransportProperties();
@@ -361,16 +354,6 @@ public class LanTcpPluginTest extends BrambleTestCase {
 			return local;
 		}
 
-		@Override
-		public Map<ContactId, TransportProperties> getRemoteProperties() {
-			return remote;
-		}
-
-		@Override
-		public TransportProperties getRemoteProperties(ContactId c) {
-			return remote.get(c);
-		}
-
 		@Override
 		public void mergeSettings(Settings s) {
 		}
@@ -381,20 +364,6 @@ public class LanTcpPluginTest extends BrambleTestCase {
 			propertiesLatch.countDown();
 		}
 
-		@Override
-		public int showChoice(String[] options, String... message) {
-			return -1;
-		}
-
-		@Override
-		public boolean showConfirmationMessage(String... message) {
-			return false;
-		}
-
-		@Override
-		public void showMessage(String... message) {
-		}
-
 		@Override
 		public void incomingConnectionCreated(DuplexTransportConnection d) {
 			connectionsLatch.countDown();
diff --git a/bramble-core/src/test/java/org/briarproject/bramble/test/CaptureArgumentAction.java b/bramble-core/src/test/java/org/briarproject/bramble/test/CaptureArgumentAction.java
index b0463eab3a449a9a52aed58951044bdf124455f4..00372d4ee575c8d2c5e51c7c6dab27934f412727 100644
--- a/bramble-core/src/test/java/org/briarproject/bramble/test/CaptureArgumentAction.java
+++ b/bramble-core/src/test/java/org/briarproject/bramble/test/CaptureArgumentAction.java
@@ -20,7 +20,7 @@ public class CaptureArgumentAction<T> implements Action {
 	}
 
 	@Override
-	public Object invoke(Invocation invocation) throws Throwable {
+	public Object invoke(Invocation invocation) {
 		captured.set(capturedClass.cast(invocation.getParameter(index)));
 		return null;
 	}
diff --git a/bramble-core/src/test/java/org/briarproject/bramble/test/TestPluginConfigModule.java b/bramble-core/src/test/java/org/briarproject/bramble/test/TestPluginConfigModule.java
index 9617834595389d3923df4d67462fe9ec29cb1a47..be9cd23d523a021de536bdfb3b35a8b25e49079c 100644
--- a/bramble-core/src/test/java/org/briarproject/bramble/test/TestPluginConfigModule.java
+++ b/bramble-core/src/test/java/org/briarproject/bramble/test/TestPluginConfigModule.java
@@ -9,13 +9,14 @@ import org.briarproject.bramble.api.plugin.simplex.SimplexPluginCallback;
 import org.briarproject.bramble.api.plugin.simplex.SimplexPluginFactory;
 
 import java.util.Collection;
-import java.util.Collections;
 
 import javax.annotation.Nullable;
 
 import dagger.Module;
 import dagger.Provides;
 
+import static java.util.Collections.emptyList;
+import static java.util.Collections.singletonList;
 import static org.briarproject.bramble.test.TestUtils.getTransportId;
 
 @Module
@@ -51,12 +52,12 @@ public class TestPluginConfigModule {
 
 			@Override
 			public Collection<DuplexPluginFactory> getDuplexFactories() {
-				return Collections.emptyList();
+				return emptyList();
 			}
 
 			@Override
 			public Collection<SimplexPluginFactory> getSimplexFactories() {
-				return Collections.singletonList(simplex);
+				return singletonList(simplex);
 			}
 
 			@Override
diff --git a/bramble-j2se/libs/jnotify-0.94.jar b/bramble-j2se/libs/jnotify-0.94.jar
deleted file mode 100644
index c4904349d7447018b3f06a9d1f632d90ac191773..0000000000000000000000000000000000000000
Binary files a/bramble-j2se/libs/jnotify-0.94.jar and /dev/null differ
diff --git a/bramble-j2se/libs/jnotify-x86.dll b/bramble-j2se/libs/jnotify-x86.dll
deleted file mode 100644
index 19ede477f63780d223790b82fbca89256f5dd7a9..0000000000000000000000000000000000000000
Binary files a/bramble-j2se/libs/jnotify-x86.dll and /dev/null differ
diff --git a/bramble-j2se/libs/jnotify-x86_64.dll b/bramble-j2se/libs/jnotify-x86_64.dll
deleted file mode 100644
index d7b55b69442578e75e55652eefa68229678960d7..0000000000000000000000000000000000000000
Binary files a/bramble-j2se/libs/jnotify-x86_64.dll and /dev/null differ
diff --git a/bramble-j2se/libs/libjnotify-amd64.so b/bramble-j2se/libs/libjnotify-amd64.so
deleted file mode 100644
index 3c6ba88f327ee39ffa78b3fc47eef65d71c168b4..0000000000000000000000000000000000000000
Binary files a/bramble-j2se/libs/libjnotify-amd64.so and /dev/null differ
diff --git a/bramble-j2se/libs/libjnotify-i386.so b/bramble-j2se/libs/libjnotify-i386.so
deleted file mode 100644
index 79379d693b42c3c9efb4dde2d922c69cf597856f..0000000000000000000000000000000000000000
Binary files a/bramble-j2se/libs/libjnotify-i386.so and /dev/null differ
diff --git a/bramble-j2se/libs/libjnotify.dylib b/bramble-j2se/libs/libjnotify.dylib
deleted file mode 100644
index 537e97ead05b750e3536dc0d734e734bbb4ff50e..0000000000000000000000000000000000000000
Binary files a/bramble-j2se/libs/libjnotify.dylib and /dev/null differ
diff --git a/bramble-j2se/src/main/java/org/briarproject/bramble/plugin/DesktopPluginModule.java b/bramble-j2se/src/main/java/org/briarproject/bramble/plugin/DesktopPluginModule.java
index 2a44d0e44f6bbf2667734c3635f640c42158342c..5b4cf70144b0c8c44e2fd5564ac261f7a34153b4 100644
--- a/bramble-j2se/src/main/java/org/briarproject/bramble/plugin/DesktopPluginModule.java
+++ b/bramble-j2se/src/main/java/org/briarproject/bramble/plugin/DesktopPluginModule.java
@@ -10,20 +10,20 @@ import org.briarproject.bramble.api.plugin.duplex.DuplexPluginFactory;
 import org.briarproject.bramble.api.plugin.simplex.SimplexPluginFactory;
 import org.briarproject.bramble.api.reliability.ReliabilityLayerFactory;
 import org.briarproject.bramble.plugin.bluetooth.JavaBluetoothPluginFactory;
-import org.briarproject.bramble.plugin.file.RemovableDrivePluginFactory;
 import org.briarproject.bramble.plugin.modem.ModemPluginFactory;
 import org.briarproject.bramble.plugin.tcp.LanTcpPluginFactory;
 import org.briarproject.bramble.plugin.tcp.WanTcpPluginFactory;
 
 import java.security.SecureRandom;
-import java.util.Arrays;
 import java.util.Collection;
-import java.util.Collections;
 import java.util.concurrent.Executor;
 
 import dagger.Module;
 import dagger.Provides;
 
+import static java.util.Arrays.asList;
+import static java.util.Collections.emptyList;
+
 @Module
 public class DesktopPluginModule extends PluginModule {
 
@@ -41,12 +41,8 @@ public class DesktopPluginModule extends PluginModule {
 				backoffFactory);
 		DuplexPluginFactory wan = new WanTcpPluginFactory(ioExecutor,
 				backoffFactory, shutdownManager);
-		SimplexPluginFactory removable =
-				new RemovableDrivePluginFactory(ioExecutor);
-		Collection<SimplexPluginFactory> simplex =
-				Collections.singletonList(removable);
 		Collection<DuplexPluginFactory> duplex =
-				Arrays.asList(bluetooth, modem, lan, wan);
+				asList(bluetooth, modem, lan, wan);
 		@NotNullByDefault
 		PluginConfig pluginConfig = new PluginConfig() {
 
@@ -57,7 +53,7 @@ public class DesktopPluginModule extends PluginModule {
 
 			@Override
 			public Collection<SimplexPluginFactory> getSimplexFactories() {
-				return simplex;
+				return emptyList();
 			}
 
 			@Override
diff --git a/bramble-j2se/src/main/java/org/briarproject/bramble/plugin/file/LinuxRemovableDriveFinder.java b/bramble-j2se/src/main/java/org/briarproject/bramble/plugin/file/LinuxRemovableDriveFinder.java
deleted file mode 100644
index df922489ffa3f5053a13c751a22b0e3297ed9e3f..0000000000000000000000000000000000000000
--- a/bramble-j2se/src/main/java/org/briarproject/bramble/plugin/file/LinuxRemovableDriveFinder.java
+++ /dev/null
@@ -1,28 +0,0 @@
-package org.briarproject.bramble.plugin.file;
-
-import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
-
-import javax.annotation.Nullable;
-
-@NotNullByDefault
-class LinuxRemovableDriveFinder extends UnixRemovableDriveFinder {
-
-	@Override
-	protected String getMountCommand() {
-		return "/bin/mount";
-	}
-
-	@Override
-	@Nullable
-	protected String parseMountPoint(String line) {
-		// The format is "/dev/foo on /bar/baz type bam (opt1,opt2)"
-		String pattern = "^/dev/[^ ]+ on (.*) type [^ ]+ \\([^)]+\\)$";
-		String path = line.replaceFirst(pattern, "$1");
-		return path.equals(line) ? null : path;
-	}
-
-	@Override
-	protected boolean isRemovableDriveMountPoint(String path) {
-		return path.startsWith("/mnt/") || path.startsWith("/media/");
-	}
-}
diff --git a/bramble-j2se/src/main/java/org/briarproject/bramble/plugin/file/LinuxRemovableDriveMonitor.java b/bramble-j2se/src/main/java/org/briarproject/bramble/plugin/file/LinuxRemovableDriveMonitor.java
deleted file mode 100644
index 6807f606f6c5e05902c8c6805038142abbe7b007..0000000000000000000000000000000000000000
--- a/bramble-j2se/src/main/java/org/briarproject/bramble/plugin/file/LinuxRemovableDriveMonitor.java
+++ /dev/null
@@ -1,12 +0,0 @@
-package org.briarproject.bramble.plugin.file;
-
-import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
-
-@NotNullByDefault
-class LinuxRemovableDriveMonitor extends UnixRemovableDriveMonitor {
-
-	@Override
-	protected String[] getPathsToWatch() {
-		return new String[] {"/mnt", "/media"};
-	}
-}
diff --git a/bramble-j2se/src/main/java/org/briarproject/bramble/plugin/file/MacRemovableDriveFinder.java b/bramble-j2se/src/main/java/org/briarproject/bramble/plugin/file/MacRemovableDriveFinder.java
deleted file mode 100644
index 8bd9b92d16b87172d1fb5c80de7bf81336d3858a..0000000000000000000000000000000000000000
--- a/bramble-j2se/src/main/java/org/briarproject/bramble/plugin/file/MacRemovableDriveFinder.java
+++ /dev/null
@@ -1,28 +0,0 @@
-package org.briarproject.bramble.plugin.file;
-
-import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
-
-import javax.annotation.Nullable;
-
-@NotNullByDefault
-class MacRemovableDriveFinder extends UnixRemovableDriveFinder {
-
-	@Override
-	protected String getMountCommand() {
-		return "/sbin/mount";
-	}
-
-	@Override
-	@Nullable
-	protected String parseMountPoint(String line) {
-		// The format is "/dev/foo on /bar/baz (opt1, opt2)"
-		String pattern = "^/dev/[^ ]+ on (.*) \\([^)]+\\)$";
-		String path = line.replaceFirst(pattern, "$1");
-		return path.equals(line) ? null : path;
-	}
-
-	@Override
-	protected boolean isRemovableDriveMountPoint(String path) {
-		return path.startsWith("/Volumes/");
-	}
-}
diff --git a/bramble-j2se/src/main/java/org/briarproject/bramble/plugin/file/MacRemovableDriveMonitor.java b/bramble-j2se/src/main/java/org/briarproject/bramble/plugin/file/MacRemovableDriveMonitor.java
deleted file mode 100644
index fb3b1acd492c3b125d551f1e60f44238ec9c9cf2..0000000000000000000000000000000000000000
--- a/bramble-j2se/src/main/java/org/briarproject/bramble/plugin/file/MacRemovableDriveMonitor.java
+++ /dev/null
@@ -1,12 +0,0 @@
-package org.briarproject.bramble.plugin.file;
-
-import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
-
-@NotNullByDefault
-class MacRemovableDriveMonitor extends UnixRemovableDriveMonitor {
-
-	@Override
-	protected String[] getPathsToWatch() {
-		return new String[] {"/Volumes"};
-	}
-}
diff --git a/bramble-j2se/src/main/java/org/briarproject/bramble/plugin/file/PollingRemovableDriveMonitor.java b/bramble-j2se/src/main/java/org/briarproject/bramble/plugin/file/PollingRemovableDriveMonitor.java
deleted file mode 100644
index 335051a8216e3d8d08a011404bf5be90be6cb032..0000000000000000000000000000000000000000
--- a/bramble-j2se/src/main/java/org/briarproject/bramble/plugin/file/PollingRemovableDriveMonitor.java
+++ /dev/null
@@ -1,84 +0,0 @@
-package org.briarproject.bramble.plugin.file;
-
-import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
-import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.Collection;
-import java.util.concurrent.Executor;
-import java.util.concurrent.locks.Condition;
-import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReentrantLock;
-import java.util.logging.Logger;
-
-import static java.util.concurrent.TimeUnit.MILLISECONDS;
-
-@MethodsNotNullByDefault
-@ParametersNotNullByDefault
-class PollingRemovableDriveMonitor implements RemovableDriveMonitor, Runnable {
-
-	private static final Logger LOG =
-			Logger.getLogger(PollingRemovableDriveMonitor.class.getName());
-
-	private final Executor ioExecutor;
-	private final RemovableDriveFinder finder;
-	private final int pollingInterval;
-
-	private final Lock pollingLock = new ReentrantLock();
-	private final Condition stopPolling = pollingLock.newCondition();
-
-	private volatile boolean running = false;
-	private volatile Callback callback = null;
-
-	PollingRemovableDriveMonitor(Executor ioExecutor,
-			RemovableDriveFinder finder, int pollingInterval) {
-		this.ioExecutor = ioExecutor;
-		this.finder = finder;
-		this.pollingInterval = pollingInterval;
-	}
-
-	@Override
-	public void start(Callback callback) throws IOException {
-		this.callback = callback;
-		running = true;
-		ioExecutor.execute(this);
-	}
-
-	@Override
-	public void stop() throws IOException {
-		running = false;
-		pollingLock.lock();
-		try {
-			stopPolling.signalAll();
-		} finally {
-			pollingLock.unlock();
-		}
-	}
-
-	@Override
-	public void run() {
-		try {
-			Collection<File> drives = finder.findRemovableDrives();
-			while (running) {
-				pollingLock.lock();
-				try {
-					stopPolling.await(pollingInterval, MILLISECONDS);
-				} finally {
-					pollingLock.unlock();
-				}
-				if (!running) return;
-				Collection<File> newDrives = finder.findRemovableDrives();
-				for (File f : newDrives) {
-					if (!drives.contains(f)) callback.driveInserted(f);
-				}
-				drives = newDrives;
-			}
-		} catch (InterruptedException e) {
-			LOG.warning("Interrupted while waiting to poll");
-			Thread.currentThread().interrupt();
-		} catch (IOException e) {
-			callback.exceptionThrown(e);
-		}
-	}
-}
diff --git a/bramble-j2se/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveFinder.java b/bramble-j2se/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveFinder.java
deleted file mode 100644
index bae6193569c497710e5847efb0d5106dd94202ec..0000000000000000000000000000000000000000
--- a/bramble-j2se/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveFinder.java
+++ /dev/null
@@ -1,13 +0,0 @@
-package org.briarproject.bramble.plugin.file;
-
-import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.Collection;
-
-@NotNullByDefault
-interface RemovableDriveFinder {
-
-	Collection<File> findRemovableDrives() throws IOException;
-}
diff --git a/bramble-j2se/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveMonitor.java b/bramble-j2se/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveMonitor.java
deleted file mode 100644
index 1145c5687f0a0b4a22e3a8dde482914c38ffac53..0000000000000000000000000000000000000000
--- a/bramble-j2se/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveMonitor.java
+++ /dev/null
@@ -1,21 +0,0 @@
-package org.briarproject.bramble.plugin.file;
-
-import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
-
-import java.io.File;
-import java.io.IOException;
-
-@NotNullByDefault
-interface RemovableDriveMonitor {
-
-	void start(Callback c) throws IOException;
-
-	void stop() throws IOException;
-
-	interface Callback {
-
-		void driveInserted(File root);
-
-		void exceptionThrown(IOException e);
-	}
-}
diff --git a/bramble-j2se/src/main/java/org/briarproject/bramble/plugin/file/RemovableDrivePlugin.java b/bramble-j2se/src/main/java/org/briarproject/bramble/plugin/file/RemovableDrivePlugin.java
deleted file mode 100644
index f2b687b53d0e43a07c429b7c932e30c47811f8a1..0000000000000000000000000000000000000000
--- a/bramble-j2se/src/main/java/org/briarproject/bramble/plugin/file/RemovableDrivePlugin.java
+++ /dev/null
@@ -1,140 +0,0 @@
-package org.briarproject.bramble.plugin.file;
-
-import org.briarproject.bramble.api.contact.ContactId;
-import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
-import org.briarproject.bramble.api.plugin.PluginException;
-import org.briarproject.bramble.api.plugin.TransportId;
-import org.briarproject.bramble.api.plugin.simplex.SimplexPluginCallback;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-import java.util.concurrent.Executor;
-import java.util.logging.Logger;
-
-import static java.util.logging.Level.WARNING;
-
-@NotNullByDefault
-class RemovableDrivePlugin extends FilePlugin
-		implements RemovableDriveMonitor.Callback {
-
-	static final TransportId ID =
-			new TransportId("org.briarproject.bramble.file");
-
-	private static final Logger LOG =
-			Logger.getLogger(RemovableDrivePlugin.class.getName());
-
-	private final RemovableDriveFinder finder;
-	private final RemovableDriveMonitor monitor;
-
-	RemovableDrivePlugin(Executor ioExecutor, SimplexPluginCallback callback,
-			RemovableDriveFinder finder, RemovableDriveMonitor monitor,
-			int maxLatency) {
-		super(ioExecutor, callback, maxLatency);
-		this.finder = finder;
-		this.monitor = monitor;
-	}
-
-	@Override
-	public TransportId getId() {
-		return ID;
-	}
-
-	@Override
-	public void start() throws PluginException {
-		if (used.getAndSet(true)) throw new IllegalStateException();
-		running = true;
-		try {
-			monitor.start(this);
-		} catch (IOException e) {
-			throw new PluginException(e);
-		}
-	}
-
-	@Override
-	public void stop() throws PluginException {
-		running = false;
-		try {
-			monitor.stop();
-		} catch (IOException e) {
-			throw new PluginException(e);
-		}
-	}
-
-	@Override
-	public boolean shouldPoll() {
-		return false;
-	}
-
-	@Override
-	public int getPollingInterval() {
-		throw new UnsupportedOperationException();
-	}
-
-	@Override
-	public void poll(Collection<ContactId> connected) {
-		throw new UnsupportedOperationException();
-	}
-
-	@Override
-	protected File chooseOutputDirectory() {
-		try {
-			List<File> drives = new ArrayList<>(finder.findRemovableDrives());
-			if (drives.isEmpty()) return null;
-			String[] paths = new String[drives.size()];
-			for (int i = 0; i < paths.length; i++) {
-				paths[i] = drives.get(i).getPath();
-			}
-			int i = callback.showChoice(paths, "REMOVABLE_DRIVE_CHOOSE_DRIVE");
-			if (i == -1) return null;
-			return drives.get(i);
-		} catch (IOException e) {
-			if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
-			return null;
-		}
-	}
-
-	@Override
-	protected void readerFinished(File f) {
-		callback.showMessage("REMOVABLE_DRIVE_READ_FINISHED");
-	}
-
-	@Override
-	protected void writerFinished(File f) {
-		callback.showMessage("REMOVABLE_DRIVE_WRITE_FINISHED");
-	}
-
-	@Override
-	protected Collection<File> findFilesByName(String filename) {
-		List<File> matches = new ArrayList<>();
-		try {
-			for (File drive : finder.findRemovableDrives()) {
-				File[] files = drive.listFiles();
-				if (files != null) {
-					for (File f : files) {
-						if (f.isFile() && filename.equals(f.getName()))
-							matches.add(f);
-					}
-				}
-			}
-		} catch (IOException e) {
-			if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
-		}
-		return matches;
-	}
-
-	@Override
-	public void driveInserted(File root) {
-		File[] files = root.listFiles();
-		if (files != null) {
-			for (File f : files) if (f.isFile()) createReaderFromFile(f);
-		}
-	}
-
-	@Override
-	public void exceptionThrown(IOException e) {
-		if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
-	}
-}
diff --git a/bramble-j2se/src/main/java/org/briarproject/bramble/plugin/file/RemovableDrivePluginFactory.java b/bramble-j2se/src/main/java/org/briarproject/bramble/plugin/file/RemovableDrivePluginFactory.java
deleted file mode 100644
index 1b53e250e5f0e6384b4fdf94e8a48aef76e6f4c6..0000000000000000000000000000000000000000
--- a/bramble-j2se/src/main/java/org/briarproject/bramble/plugin/file/RemovableDrivePluginFactory.java
+++ /dev/null
@@ -1,63 +0,0 @@
-package org.briarproject.bramble.plugin.file;
-
-import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
-import org.briarproject.bramble.api.plugin.TransportId;
-import org.briarproject.bramble.api.plugin.simplex.SimplexPlugin;
-import org.briarproject.bramble.api.plugin.simplex.SimplexPluginCallback;
-import org.briarproject.bramble.api.plugin.simplex.SimplexPluginFactory;
-import org.briarproject.bramble.util.OsUtils;
-
-import java.util.concurrent.Executor;
-
-import javax.annotation.concurrent.Immutable;
-
-@Immutable
-@NotNullByDefault
-public class RemovableDrivePluginFactory implements SimplexPluginFactory {
-
-	// Maximum latency 14 days (Royal Mail or lackadaisical carrier pigeon)
-	private static final int MAX_LATENCY = 14 * 24 * 60 * 60 * 1000;
-	private static final int POLLING_INTERVAL = 10 * 1000; // 10 seconds
-
-	private final Executor ioExecutor;
-
-	public RemovableDrivePluginFactory(Executor ioExecutor) {
-		this.ioExecutor = ioExecutor;
-	}
-
-	@Override
-	public TransportId getId() {
-		return RemovableDrivePlugin.ID;
-	}
-
-	@Override
-	public int getMaxLatency() {
-		return MAX_LATENCY;
-	}
-
-	@Override
-	public SimplexPlugin createPlugin(SimplexPluginCallback callback) {
-		RemovableDriveFinder finder;
-		RemovableDriveMonitor monitor;
-		if (OsUtils.isLinux()) {
-			finder = new LinuxRemovableDriveFinder();
-			monitor = new LinuxRemovableDriveMonitor();
-		} else if (OsUtils.isMacLeopardOrNewer()) {
-			finder = new MacRemovableDriveFinder();
-			monitor = new MacRemovableDriveMonitor();
-		} else if (OsUtils.isMac()) {
-			// JNotify requires OS X 10.5 or newer, so we have to poll
-			finder = new MacRemovableDriveFinder();
-			monitor = new PollingRemovableDriveMonitor(ioExecutor, finder,
-					POLLING_INTERVAL);
-		} else if (OsUtils.isWindows()) {
-			finder = new WindowsRemovableDriveFinder();
-			monitor = new PollingRemovableDriveMonitor(ioExecutor, finder,
-					POLLING_INTERVAL);
-		} else {
-			return null;
-		}
-		return new RemovableDrivePlugin(ioExecutor, callback, finder, monitor,
-				MAX_LATENCY);
-	}
-}
diff --git a/bramble-j2se/src/main/java/org/briarproject/bramble/plugin/file/UnixRemovableDriveFinder.java b/bramble-j2se/src/main/java/org/briarproject/bramble/plugin/file/UnixRemovableDriveFinder.java
deleted file mode 100644
index c5626fdb94bc7ae14ea4c8707c8c4ef45abc12f0..0000000000000000000000000000000000000000
--- a/bramble-j2se/src/main/java/org/briarproject/bramble/plugin/file/UnixRemovableDriveFinder.java
+++ /dev/null
@@ -1,48 +0,0 @@
-package org.briarproject.bramble.plugin.file;
-
-import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Scanner;
-
-import javax.annotation.Nullable;
-
-@NotNullByDefault
-abstract class UnixRemovableDriveFinder implements RemovableDriveFinder {
-
-	protected abstract String getMountCommand();
-
-	@Nullable
-	protected abstract String parseMountPoint(String line);
-
-	protected abstract boolean isRemovableDriveMountPoint(String path);
-
-	@Override
-	public List<File> findRemovableDrives() throws IOException {
-		List<File> drives = new ArrayList<>();
-		Process p = new ProcessBuilder(getMountCommand()).start();
-		Scanner s = new Scanner(p.getInputStream(), "UTF-8");
-		try {
-			while (s.hasNextLine()) {
-				String line = s.nextLine();
-				String[] tokens = line.split(" ");
-				if (tokens.length < 3) continue;
-				// The general format is "/dev/foo on /bar/baz ..."
-				if (tokens[0].startsWith("/dev/") && tokens[1].equals("on")) {
-					// The path may contain spaces so we can't use tokens[2]
-					String path = parseMountPoint(line);
-					if (path != null && isRemovableDriveMountPoint(path)) {
-						File f = new File(path);
-						if (f.exists() && f.isDirectory()) drives.add(f);
-					}
-				}
-			}
-		} finally {
-			s.close();
-		}
-		return drives;
-	}
-}
diff --git a/bramble-j2se/src/main/java/org/briarproject/bramble/plugin/file/UnixRemovableDriveMonitor.java b/bramble-j2se/src/main/java/org/briarproject/bramble/plugin/file/UnixRemovableDriveMonitor.java
deleted file mode 100644
index 4ca1a30843271160b9e039990036870f6aad3196..0000000000000000000000000000000000000000
--- a/bramble-j2se/src/main/java/org/briarproject/bramble/plugin/file/UnixRemovableDriveMonitor.java
+++ /dev/null
@@ -1,129 +0,0 @@
-package org.briarproject.bramble.plugin.file;
-
-import net.contentobjects.jnotify.JNotify;
-import net.contentobjects.jnotify.JNotifyListener;
-
-import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
-import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReentrantLock;
-
-import javax.annotation.Nullable;
-
-@MethodsNotNullByDefault
-@ParametersNotNullByDefault
-abstract class UnixRemovableDriveMonitor implements RemovableDriveMonitor,
-JNotifyListener {
-
-	//TODO: rationalise this in a further refactor
-	private static final Lock staticLock = new ReentrantLock();
-
-	// The following are locking: staticLock
-	private static boolean triedLoad = false;
-	private static Throwable loadError = null;
-
-	private final Lock lock = new ReentrantLock();
-
-	// The following are locking: lock
-	private final List<Integer> watches = new ArrayList<>();
-	private boolean started = false;
-	private Callback callback = null;
-
-	protected abstract String[] getPathsToWatch();
-
-	@Nullable
-	private static Throwable tryLoad() {
-		try {
-			Class.forName("net.contentobjects.jnotify.JNotify");
-			return null;
-		} catch (UnsatisfiedLinkError | ClassNotFoundException e) {
-			return e;
-		}
-	}
-
-	private static void checkEnabled() throws IOException {
-		staticLock.lock();
-		try {
-			if (!triedLoad) {
-				loadError = tryLoad();
-				triedLoad = true;
-			}
-			if (loadError != null) throw new IOException(loadError.toString());
-		} finally {
-			staticLock.unlock();
-		}
-	}
-
-	@Override
-	public void start(Callback callback) throws IOException {
-		checkEnabled();
-		List<Integer> watches = new ArrayList<>();
-		int mask = JNotify.FILE_CREATED;
-		for (String path : getPathsToWatch()) {
-			if (new File(path).exists())
-				watches.add(JNotify.addWatch(path, mask, false, this));
-		}
-		lock.lock();
-		try {
-			if (started) throw new AssertionError();
-			if (this.callback != null) throw new AssertionError();
-			started = true;
-			this.callback = callback;
-			this.watches.addAll(watches);
-		} finally {
-			lock.unlock();
-		}
-	}
-
-	@Override
-	public void stop() throws IOException {
-		checkEnabled();
-		List<Integer> watches;
-		lock.lock();
-		try {
-			if (!started) throw new AssertionError();
-			if (callback == null) throw new AssertionError();
-			started = false;
-			callback = null;
-			watches = new ArrayList<>(this.watches);
-			this.watches.clear();
-		} finally {
-			lock.unlock();
-		}
-		for (Integer w : watches) JNotify.removeWatch(w);
-	}
-
-	@Override
-	public void fileCreated(int wd, String rootPath, String name) {
-		Callback callback;
-		lock.lock();
-		try {
-			callback = this.callback;
-		} finally {
-			lock.unlock();
-		}
-		if (callback != null)
-			callback.driveInserted(new File(rootPath + "/" + name));
-	}
-
-	@Override
-	public void fileDeleted(int wd, String rootPath, String name) {
-		throw new UnsupportedOperationException();
-	}
-
-	@Override
-	public void fileModified(int wd, String rootPath, String name) {
-		throw new UnsupportedOperationException();
-	}
-
-	@Override
-	public void fileRenamed(int wd, String rootPath, String oldName,
-			String newName) {
-		throw new UnsupportedOperationException();
-	}
-}
diff --git a/bramble-j2se/src/main/java/org/briarproject/bramble/plugin/file/WindowsRemovableDriveFinder.java b/bramble-j2se/src/main/java/org/briarproject/bramble/plugin/file/WindowsRemovableDriveFinder.java
deleted file mode 100644
index abdbbf0bf95641c56b2d74d128a2453f26424802..0000000000000000000000000000000000000000
--- a/bramble-j2se/src/main/java/org/briarproject/bramble/plugin/file/WindowsRemovableDriveFinder.java
+++ /dev/null
@@ -1,34 +0,0 @@
-package org.briarproject.bramble.plugin.file;
-
-import com.sun.jna.platform.win32.Kernel32;
-
-import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-
-@NotNullByDefault
-class WindowsRemovableDriveFinder implements RemovableDriveFinder {
-
-	// http://msdn.microsoft.com/en-us/library/windows/desktop/aa364939.aspx
-	private static final int DRIVE_REMOVABLE = 2;
-
-	@Override
-	public Collection<File> findRemovableDrives() throws IOException {
-		File[] roots = File.listRoots();
-		if (roots == null) throw new IOException();
-		List<File> drives = new ArrayList<>();
-		for (File root : roots) {
-			try {
-				int type = Kernel32.INSTANCE.GetDriveType(root.getPath());
-				if (type == DRIVE_REMOVABLE) drives.add(root);
-			} catch (RuntimeException e) {
-				throw new IOException(e);
-			}
-		}
-		return drives;
-	}
-}
diff --git a/bramble-j2se/src/main/java/org/briarproject/bramble/plugin/modem/ModemPlugin.java b/bramble-j2se/src/main/java/org/briarproject/bramble/plugin/modem/ModemPlugin.java
index 340b27c8ee66e02c7946ea889f062e5af6cc57df..8b7806359ba7cf9f950582a39a7c176f64d2daf1 100644
--- a/bramble-j2se/src/main/java/org/briarproject/bramble/plugin/modem/ModemPlugin.java
+++ b/bramble-j2se/src/main/java/org/briarproject/bramble/plugin/modem/ModemPlugin.java
@@ -17,7 +17,7 @@ import org.briarproject.bramble.util.StringUtils;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
-import java.util.Collection;
+import java.util.Map;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.logging.Logger;
 
@@ -115,7 +115,7 @@ class ModemPlugin implements DuplexPlugin, Modem.Callback {
 	}
 
 	@Override
-	public void poll(Collection<ContactId> connected) {
+	public void poll(Map<ContactId, TransportProperties> contacts) {
 		throw new UnsupportedOperationException();
 	}
 
@@ -139,17 +139,16 @@ class ModemPlugin implements DuplexPlugin, Modem.Callback {
 	}
 
 	@Override
-	public DuplexTransportConnection createConnection(ContactId c) {
+	public DuplexTransportConnection createConnection(TransportProperties p) {
 		if (!running) return null;
 		// Get the ISO 3166 code for the caller's country
 		String fromIso = callback.getLocalProperties().get("iso3166");
 		if (StringUtils.isNullOrEmpty(fromIso)) return null;
 		// Get the ISO 3166 code for the callee's country
-		TransportProperties properties = callback.getRemoteProperties(c);
-		String toIso = properties.get("iso3166");
+		String toIso = p.get("iso3166");
 		if (StringUtils.isNullOrEmpty(toIso)) return null;
 		// Get the callee's phone number
-		String number = properties.get("number");
+		String number = p.get("number");
 		if (StringUtils.isNullOrEmpty(number)) return null;
 		// Convert the number into direct dialling form
 		number = CountryCodes.translate(number, fromIso, toIso);
diff --git a/bramble-j2se/src/test/java/org/briarproject/bramble/plugin/file/LinuxRemovableDriveFinderTest.java b/bramble-j2se/src/test/java/org/briarproject/bramble/plugin/file/LinuxRemovableDriveFinderTest.java
deleted file mode 100644
index b26bc77a889723b2a1808ff0d4c7f3276987b45f..0000000000000000000000000000000000000000
--- a/bramble-j2se/src/test/java/org/briarproject/bramble/plugin/file/LinuxRemovableDriveFinderTest.java
+++ /dev/null
@@ -1,26 +0,0 @@
-package org.briarproject.bramble.plugin.file;
-
-import org.briarproject.bramble.test.BrambleTestCase;
-import org.junit.Test;
-
-import static org.junit.Assert.assertEquals;
-
-public class LinuxRemovableDriveFinderTest extends BrambleTestCase {
-
-	@Test
-	public void testParseMountPoint() {
-		LinuxRemovableDriveFinder f = new LinuxRemovableDriveFinder();
-		String line = "/dev/sda3 on / type ext3"
-			+ " (rw,errors=remount-ro,commit=0)";
-		assertEquals("/", f.parseMountPoint(line));
-		line = "gvfs-fuse-daemon on /home/alice/.gvfs"
-			+ " type fuse.gvfs-fuse-daemon (rw,nosuid,nodev,user=alice)";
-		assertEquals(null, f.parseMountPoint(line)); // Can't be parsed
-		line = "fusectl on /sys/fs/fuse/connections type fusectl (rw)";
-		assertEquals(null, f.parseMountPoint(line)); // Can't be parsed
-		line = "/dev/sdd1 on /media/HAZ SPACE(!) type vfat"
-			+ " (rw,nosuid,nodev,uhelper=udisks,uid=1000,gid=1000,"
-			+ "shortname=mixed,dmask=0077,utf8=1,showexec,flush)";
-		assertEquals("/media/HAZ SPACE(!)", f.parseMountPoint(line));
-	}
-}
diff --git a/bramble-j2se/src/test/java/org/briarproject/bramble/plugin/file/MacRemovableDriveFinderTest.java b/bramble-j2se/src/test/java/org/briarproject/bramble/plugin/file/MacRemovableDriveFinderTest.java
deleted file mode 100644
index db2cf210074017dec145affcbbb5a0b051a3d926..0000000000000000000000000000000000000000
--- a/bramble-j2se/src/test/java/org/briarproject/bramble/plugin/file/MacRemovableDriveFinderTest.java
+++ /dev/null
@@ -1,24 +0,0 @@
-package org.briarproject.bramble.plugin.file;
-
-import org.briarproject.bramble.test.BrambleTestCase;
-import org.junit.Test;
-
-import static org.junit.Assert.assertEquals;
-
-public class MacRemovableDriveFinderTest extends BrambleTestCase {
-
-	@Test
-	public void testParseMountPoint() {
-		MacRemovableDriveFinder f = new MacRemovableDriveFinder();
-		String line = "/dev/disk0s3 on / (local, journaled)";
-		assertEquals("/", f.parseMountPoint(line));
-		line = "devfs on /dev (local)";
-		assertEquals(null, f.parseMountPoint(line)); // Can't be parsed
-		line = "<volfs> on /.vol";
-		assertEquals(null, f.parseMountPoint(line)); // Can't be parsed
-		line = "automount -nsl [117] on /Network (automounted)";
-		assertEquals(null, f.parseMountPoint(line)); // Can't be parsed
-		line = "/dev/disk1s1 on /Volumes/HAZ SPACE(!) (local, nodev, nosuid)";
-		assertEquals("/Volumes/HAZ SPACE(!)", f.parseMountPoint(line));
-	}
-}
diff --git a/bramble-j2se/src/test/java/org/briarproject/bramble/plugin/file/PollingRemovableDriveMonitorTest.java b/bramble-j2se/src/test/java/org/briarproject/bramble/plugin/file/PollingRemovableDriveMonitorTest.java
deleted file mode 100644
index 2c694b53bffefd0599e01e98eadf894712f22f20..0000000000000000000000000000000000000000
--- a/bramble-j2se/src/test/java/org/briarproject/bramble/plugin/file/PollingRemovableDriveMonitorTest.java
+++ /dev/null
@@ -1,107 +0,0 @@
-package org.briarproject.bramble.plugin.file;
-
-import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
-import org.briarproject.bramble.plugin.file.RemovableDriveMonitor.Callback;
-import org.briarproject.bramble.test.BrambleTestCase;
-import org.junit.Test;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.Executors;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-import static java.util.concurrent.TimeUnit.SECONDS;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-public class PollingRemovableDriveMonitorTest extends BrambleTestCase {
-
-	@Test
-	public void testOneCallbackPerFile() throws Exception {
-		// Create a finder that returns no files the first time, then two files
-		File file1 = new File("foo");
-		File file2 = new File("bar");
-		@NotNullByDefault
-		RemovableDriveFinder finder = new RemovableDriveFinder() {
-
-			private AtomicBoolean firstCall = new AtomicBoolean(true);
-
-			@Override
-			public Collection<File> findRemovableDrives() throws IOException {
-				if (firstCall.getAndSet(false)) return Collections.emptyList();
-				else return Arrays.asList(file1, file2);
-			}
-		};
-		// Create a callback that waits for two files
-		CountDownLatch latch = new CountDownLatch(2);
-		List<File> detected = new ArrayList<>();
-		@NotNullByDefault
-		Callback callback = new Callback() {
-
-			@Override
-			public void driveInserted(File f) {
-				detected.add(f);
-				latch.countDown();
-			}
-
-			@Override
-			public void exceptionThrown(IOException e) {
-				fail();
-			}
-		};
-		// Create the monitor and start it
-		RemovableDriveMonitor monitor = new PollingRemovableDriveMonitor(
-				Executors.newCachedThreadPool(), finder, 1);
-		monitor.start(callback);
-		// Wait for the monitor to detect the files
-		assertTrue(latch.await(10, SECONDS));
-		monitor.stop();
-		// Check that both files were detected
-		assertEquals(2, detected.size());
-		assertTrue(detected.contains(file1));
-		assertTrue(detected.contains(file2));
-	}
-
-	@Test
-	public void testExceptionCallback() throws Exception {
-		// Create a finder that throws an exception the second time it's polled
-		RemovableDriveFinder finder = new RemovableDriveFinder() {
-
-			private AtomicBoolean firstCall = new AtomicBoolean(true);
-
-			@Override
-			public Collection<File> findRemovableDrives() throws IOException {
-				if (firstCall.getAndSet(false)) return Collections.emptyList();
-				else throw new IOException();
-			}
-		};
-		// Create a callback that waits for an exception
-		CountDownLatch latch = new CountDownLatch(1);
-		@NotNullByDefault
-		Callback callback = new Callback() {
-
-			@Override
-			public void driveInserted(File root) {
-				fail();
-			}
-
-			@Override
-			public void exceptionThrown(IOException e) {
-				latch.countDown();
-			}
-		};
-		// Create the monitor and start it
-		RemovableDriveMonitor monitor = new PollingRemovableDriveMonitor(
-				Executors.newCachedThreadPool(), finder, 1);
-		monitor.start(callback);
-		assertTrue(latch.await(10, SECONDS));
-		monitor.stop();
-	}
-}
diff --git a/bramble-j2se/src/test/java/org/briarproject/bramble/plugin/file/RemovableDrivePluginTest.java b/bramble-j2se/src/test/java/org/briarproject/bramble/plugin/file/RemovableDrivePluginTest.java
deleted file mode 100644
index 7ab6001b16890fa86958840da42c5bdf7c116099..0000000000000000000000000000000000000000
--- a/bramble-j2se/src/test/java/org/briarproject/bramble/plugin/file/RemovableDrivePluginTest.java
+++ /dev/null
@@ -1,378 +0,0 @@
-package org.briarproject.bramble.plugin.file;
-
-import org.briarproject.bramble.api.contact.ContactId;
-import org.briarproject.bramble.api.plugin.TransportConnectionWriter;
-import org.briarproject.bramble.api.plugin.simplex.SimplexPluginCallback;
-import org.briarproject.bramble.plugin.file.RemovableDriveMonitor.Callback;
-import org.briarproject.bramble.test.BrambleTestCase;
-import org.briarproject.bramble.test.ImmediateExecutor;
-import org.briarproject.bramble.test.TestUtils;
-import org.jmock.Expectations;
-import org.jmock.Mockery;
-import org.jmock.lib.concurrent.Synchroniser;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.OutputStream;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.concurrent.Executor;
-
-import static org.briarproject.bramble.api.transport.TransportConstants.MIN_STREAM_LENGTH;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-
-public class RemovableDrivePluginTest extends BrambleTestCase {
-
-	private final File testDir = TestUtils.getTestDirectory();
-	private final ContactId contactId = new ContactId(234);
-
-	@Before
-	public void setUp() {
-		testDir.mkdirs();
-	}
-
-	@Test
-	public void testWriterIsNullIfNoDrivesAreFound() throws Exception {
-		List<File> drives = Collections.emptyList();
-
-		Mockery context = new Mockery() {{
-			setThreadingPolicy(new Synchroniser());
-		}};
-		Executor executor = context.mock(Executor.class);
-		SimplexPluginCallback callback =
-				context.mock(SimplexPluginCallback.class);
-		RemovableDriveFinder finder =
-				context.mock(RemovableDriveFinder.class);
-		RemovableDriveMonitor monitor =
-				context.mock(RemovableDriveMonitor.class);
-
-		context.checking(new Expectations() {{
-			oneOf(monitor).start(with(any(Callback.class)));
-			oneOf(finder).findRemovableDrives();
-			will(returnValue(drives));
-		}});
-
-		RemovableDrivePlugin plugin = new RemovableDrivePlugin(executor,
-				callback, finder, monitor, 0);
-		plugin.start();
-
-		assertNull(plugin.createWriter(contactId));
-
-		context.assertIsSatisfied();
-	}
-
-	@Test
-	public void testWriterIsNullIfNoDriveIsChosen() throws Exception {
-		File drive1 = new File(testDir, "1");
-		File drive2 = new File(testDir, "2");
-		List<File> drives = new ArrayList<>();
-		drives.add(drive1);
-		drives.add(drive2);
-
-		Mockery context = new Mockery() {{
-			setThreadingPolicy(new Synchroniser());
-		}};
-		Executor executor = context.mock(Executor.class);
-		SimplexPluginCallback callback =
-				context.mock(SimplexPluginCallback.class);
-		RemovableDriveFinder finder =
-				context.mock(RemovableDriveFinder.class);
-		RemovableDriveMonitor monitor =
-				context.mock(RemovableDriveMonitor.class);
-
-		context.checking(new Expectations() {{
-			oneOf(monitor).start(with(any(Callback.class)));
-			oneOf(finder).findRemovableDrives();
-			will(returnValue(drives));
-			oneOf(callback).showChoice(with(any(String[].class)),
-					with(any(String[].class)));
-			will(returnValue(-1)); // The user cancelled the choice
-		}});
-
-		RemovableDrivePlugin plugin = new RemovableDrivePlugin(executor,
-				callback, finder, monitor, 0);
-		plugin.start();
-
-		assertNull(plugin.createWriter(contactId));
-		File[] files = drive1.listFiles();
-		assertTrue(files == null || files.length == 0);
-
-		context.assertIsSatisfied();
-	}
-
-	@Test
-	public void testWriterIsNullIfOutputDirDoesNotExist() throws Exception {
-		File drive1 = new File(testDir, "1");
-		File drive2 = new File(testDir, "2");
-		List<File> drives = new ArrayList<>();
-		drives.add(drive1);
-		drives.add(drive2);
-
-		Mockery context = new Mockery() {{
-			setThreadingPolicy(new Synchroniser());
-		}};
-		Executor executor = context.mock(Executor.class);
-		SimplexPluginCallback callback =
-				context.mock(SimplexPluginCallback.class);
-		RemovableDriveFinder finder =
-				context.mock(RemovableDriveFinder.class);
-		RemovableDriveMonitor monitor =
-				context.mock(RemovableDriveMonitor.class);
-
-		context.checking(new Expectations() {{
-			oneOf(monitor).start(with(any(Callback.class)));
-			oneOf(finder).findRemovableDrives();
-			will(returnValue(drives));
-			oneOf(callback).showChoice(with(any(String[].class)),
-					with(any(String[].class)));
-			will(returnValue(0)); // The user chose drive1 but it doesn't exist
-		}});
-
-		RemovableDrivePlugin plugin = new RemovableDrivePlugin(executor,
-				callback, finder, monitor, 0);
-		plugin.start();
-
-		assertNull(plugin.createWriter(contactId));
-		File[] files = drive1.listFiles();
-		assertTrue(files == null || files.length == 0);
-
-		context.assertIsSatisfied();
-	}
-
-	@Test
-	public void testWriterIsNullIfOutputDirIsAFile() throws Exception {
-		File drive1 = new File(testDir, "1");
-		File drive2 = new File(testDir, "2");
-		List<File> drives = new ArrayList<>();
-		drives.add(drive1);
-		drives.add(drive2);
-		// Create drive1 as a file rather than a directory
-		assertTrue(drive1.createNewFile());
-
-		Mockery context = new Mockery() {{
-			setThreadingPolicy(new Synchroniser());
-		}};
-		Executor executor = context.mock(Executor.class);
-		SimplexPluginCallback callback =
-				context.mock(SimplexPluginCallback.class);
-		RemovableDriveFinder finder =
-				context.mock(RemovableDriveFinder.class);
-		RemovableDriveMonitor monitor =
-				context.mock(RemovableDriveMonitor.class);
-
-		context.checking(new Expectations() {{
-			oneOf(monitor).start(with(any(Callback.class)));
-			oneOf(finder).findRemovableDrives();
-			will(returnValue(drives));
-			oneOf(callback).showChoice(with(any(String[].class)),
-					with(any(String[].class)));
-			will(returnValue(0)); // The user chose drive1 but it's not a dir
-		}});
-
-		RemovableDrivePlugin plugin = new RemovableDrivePlugin(executor,
-				callback, finder, monitor, 0);
-		plugin.start();
-
-		assertNull(plugin.createWriter(contactId));
-		File[] files = drive1.listFiles();
-		assertTrue(files == null || files.length == 0);
-
-		context.assertIsSatisfied();
-	}
-
-	@Test
-	public void testWriterIsNotNullIfOutputDirIsADir() throws Exception {
-		File drive1 = new File(testDir, "1");
-		File drive2 = new File(testDir, "2");
-		List<File> drives = new ArrayList<>();
-		drives.add(drive1);
-		drives.add(drive2);
-		// Create drive1 as a directory
-		assertTrue(drive1.mkdir());
-
-		Mockery context = new Mockery() {{
-			setThreadingPolicy(new Synchroniser());
-		}};
-		Executor executor = context.mock(Executor.class);
-		SimplexPluginCallback callback =
-				context.mock(SimplexPluginCallback.class);
-		RemovableDriveFinder finder =
-				context.mock(RemovableDriveFinder.class);
-		RemovableDriveMonitor monitor =
-				context.mock(RemovableDriveMonitor.class);
-
-		context.checking(new Expectations() {{
-			oneOf(monitor).start(with(any(Callback.class)));
-			oneOf(finder).findRemovableDrives();
-			will(returnValue(drives));
-			oneOf(callback).showChoice(with(any(String[].class)),
-					with(any(String[].class)));
-			will(returnValue(0)); // The user chose drive1
-		}});
-
-		RemovableDrivePlugin plugin = new RemovableDrivePlugin(executor,
-				callback, finder, monitor, 0);
-		plugin.start();
-
-		assertNotNull(plugin.createWriter(contactId));
-		// The output file should exist and should be empty
-		File[] files = drive1.listFiles();
-		assertNotNull(files);
-		assertEquals(1, files.length);
-		assertEquals(0, files[0].length());
-
-		context.assertIsSatisfied();
-	}
-
-	@Test
-	public void testWritingToWriter() throws Exception {
-		File drive1 = new File(testDir, "1");
-		File drive2 = new File(testDir, "2");
-		List<File> drives = new ArrayList<>();
-		drives.add(drive1);
-		drives.add(drive2);
-		// Create drive1 as a directory
-		assertTrue(drive1.mkdir());
-
-		Mockery context = new Mockery() {{
-			setThreadingPolicy(new Synchroniser());
-		}};
-		Executor executor = context.mock(Executor.class);
-		SimplexPluginCallback callback =
-				context.mock(SimplexPluginCallback.class);
-		RemovableDriveFinder finder =
-				context.mock(RemovableDriveFinder.class);
-		RemovableDriveMonitor monitor =
-				context.mock(RemovableDriveMonitor.class);
-
-		context.checking(new Expectations() {{
-			oneOf(monitor).start(with(any(Callback.class)));
-			oneOf(finder).findRemovableDrives();
-			will(returnValue(drives));
-			oneOf(callback).showChoice(with(any(String[].class)),
-					with(any(String[].class)));
-			will(returnValue(0)); // The user chose drive1
-			oneOf(callback).showMessage(with(any(String[].class)));
-		}});
-
-		RemovableDrivePlugin plugin = new RemovableDrivePlugin(executor,
-				callback, finder, monitor, 0);
-		plugin.start();
-
-		TransportConnectionWriter writer = plugin.createWriter(contactId);
-		assertNotNull(writer);
-		// The output file should exist and should be empty
-		File[] files = drive1.listFiles();
-		assertNotNull(files);
-		assertEquals(1, files.length);
-		assertEquals(0, files[0].length());
-		// Writing to the output stream should increase the size of the file
-		OutputStream out = writer.getOutputStream();
-		out.write(new byte[1234]);
-		out.flush();
-		out.close();
-		// Disposing of the writer should not delete the file
-		writer.dispose(false);
-		assertTrue(files[0].exists());
-		assertEquals(1234, files[0].length());
-
-		context.assertIsSatisfied();
-	}
-
-	@Test
-	public void testEmptyDriveIsIgnored() throws Exception {
-		Mockery context = new Mockery() {{
-			setThreadingPolicy(new Synchroniser());
-		}};
-		Executor executor = context.mock(Executor.class);
-		SimplexPluginCallback callback =
-				context.mock(SimplexPluginCallback.class);
-		RemovableDriveFinder finder =
-				context.mock(RemovableDriveFinder.class);
-		RemovableDriveMonitor monitor =
-				context.mock(RemovableDriveMonitor.class);
-
-		context.checking(new Expectations() {{
-			oneOf(monitor).start(with(any(Callback.class)));
-		}});
-
-		RemovableDrivePlugin plugin = new RemovableDrivePlugin(executor,
-				callback, finder, monitor, 0);
-		plugin.start();
-
-		plugin.driveInserted(testDir);
-
-		context.assertIsSatisfied();
-	}
-
-	@Test
-	public void testFilenames() {
-		Mockery context = new Mockery() {{
-			setThreadingPolicy(new Synchroniser());
-		}};
-		Executor executor = context.mock(Executor.class);
-		SimplexPluginCallback callback =
-				context.mock(SimplexPluginCallback.class);
-		RemovableDriveFinder finder =
-				context.mock(RemovableDriveFinder.class);
-		RemovableDriveMonitor monitor =
-				context.mock(RemovableDriveMonitor.class);
-
-		RemovableDrivePlugin plugin = new RemovableDrivePlugin(executor,
-				callback, finder, monitor, 0);
-
-		assertFalse(plugin.isPossibleConnectionFilename("abcdefg.dat"));
-		assertFalse(plugin.isPossibleConnectionFilename("abcdefghi.dat"));
-		assertFalse(plugin.isPossibleConnectionFilename("abcdefgh_dat"));
-		assertFalse(plugin.isPossibleConnectionFilename("abcdefgh.rat"));
-		assertTrue(plugin.isPossibleConnectionFilename("abcdefgh.dat"));
-		assertTrue(plugin.isPossibleConnectionFilename("ABCDEFGH.DAT"));
-
-		context.assertIsSatisfied();
-	}
-
-	@Test
-	public void testReaderIsCreated() throws Exception {
-		Mockery context = new Mockery() {{
-			setThreadingPolicy(new Synchroniser());
-		}};
-		SimplexPluginCallback callback =
-				context.mock(SimplexPluginCallback.class);
-		RemovableDriveFinder finder =
-				context.mock(RemovableDriveFinder.class);
-		RemovableDriveMonitor monitor =
-				context.mock(RemovableDriveMonitor.class);
-
-		context.checking(new Expectations() {{
-			oneOf(monitor).start(with(any(Callback.class)));
-			oneOf(callback).readerCreated(with(any(FileTransportReader.class)));
-		}});
-
-		RemovableDrivePlugin plugin = new RemovableDrivePlugin(
-				new ImmediateExecutor(), callback, finder, monitor, 0);
-		plugin.start();
-
-		File f = new File(testDir, "abcdefgh.dat");
-		OutputStream out = new FileOutputStream(f);
-		out.write(new byte[MIN_STREAM_LENGTH]);
-		out.flush();
-		out.close();
-		assertEquals(MIN_STREAM_LENGTH, f.length());
-		plugin.driveInserted(testDir);
-
-		context.assertIsSatisfied();
-	}
-
-	@After
-	public void tearDown() {
-		TestUtils.deleteTestDirectory(testDir);
-	}
-}
diff --git a/bramble-j2se/src/test/java/org/briarproject/bramble/plugin/file/UnixRemovableDriveMonitorTest.java b/bramble-j2se/src/test/java/org/briarproject/bramble/plugin/file/UnixRemovableDriveMonitorTest.java
deleted file mode 100644
index f2d5204ed04242c51b5f55179bf6454960cef23a..0000000000000000000000000000000000000000
--- a/bramble-j2se/src/test/java/org/briarproject/bramble/plugin/file/UnixRemovableDriveMonitorTest.java
+++ /dev/null
@@ -1,112 +0,0 @@
-package org.briarproject.bramble.plugin.file;
-
-import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
-import org.briarproject.bramble.plugin.file.RemovableDriveMonitor.Callback;
-import org.briarproject.bramble.test.BrambleTestCase;
-import org.briarproject.bramble.test.TestUtils;
-import org.briarproject.bramble.util.OsUtils;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.CountDownLatch;
-
-import static java.util.concurrent.TimeUnit.SECONDS;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-public class UnixRemovableDriveMonitorTest extends BrambleTestCase {
-
-	private final File testDir = TestUtils.getTestDirectory();
-
-	@Before
-	public void setUp() {
-		testDir.mkdirs();
-	}
-
-	@Test
-	public void testNonexistentDir() throws Exception {
-		if (!(OsUtils.isLinux() || OsUtils.isMacLeopardOrNewer())) {
-			System.err.println("WARNING: Skipping test, can't run on this OS");
-			return;
-		}
-		File doesNotExist = new File(testDir, "doesNotExist");
-		RemovableDriveMonitor monitor = createMonitor(doesNotExist);
-		@NotNullByDefault
-		Callback callback = new Callback() {
-
-			@Override
-			public void driveInserted(File root) {
-				fail();
-			}
-
-			@Override
-			public void exceptionThrown(IOException e) {
-				fail();
-			}
-		};
-		monitor.start(callback);
-		monitor.stop();
-	}
-
-	@Test
-	public void testOneCallbackPerFile() throws Exception {
-		if (!(OsUtils.isLinux() || OsUtils.isMacLeopardOrNewer())) {
-			System.err.println("WARNING: Skipping test, can't run on this OS");
-			return;
-		}
-		// Create a callback that will wait for two files before stopping
-		List<File> detected = new ArrayList<>();
-		CountDownLatch latch = new CountDownLatch(2);
-		@NotNullByDefault
-		Callback callback = new Callback() {
-
-			@Override
-			public void driveInserted(File f) {
-				detected.add(f);
-				latch.countDown();
-			}
-
-			@Override
-			public void exceptionThrown(IOException e) {
-				fail();
-			}
-		};
-		// Create the monitor and start it
-		RemovableDriveMonitor monitor = createMonitor(testDir);
-		monitor.start(callback);
-		// Create two files in the test directory
-		File file1 = new File(testDir, "1");
-		File file2 = new File(testDir, "2");
-		assertTrue(file1.createNewFile());
-		assertTrue(file2.createNewFile());
-		// Wait for the monitor to detect the files
-		assertTrue(latch.await(5, SECONDS));
-		monitor.stop();
-		// Check that both files were detected
-		assertEquals(2, detected.size());
-		assertTrue(detected.contains(file1));
-		assertTrue(detected.contains(file2));
-	}
-
-	@After
-	public void tearDown() {
-		TestUtils.deleteTestDirectory(testDir);
-	}
-
-	private RemovableDriveMonitor createMonitor(File dir) {
-		@NotNullByDefault
-		RemovableDriveMonitor monitor = new UnixRemovableDriveMonitor() {
-			@Override
-			protected String[] getPathsToWatch() {
-				return new String[] {dir.getPath()};
-			}
-		};
-		return monitor;
-	}
-}
diff --git a/bramble-j2se/src/test/java/org/briarproject/bramble/plugin/modem/ModemPluginTest.java b/bramble-j2se/src/test/java/org/briarproject/bramble/plugin/modem/ModemPluginTest.java
index 6d12af19a6c95bfcfcb40703769d433639cc85fe..ba8fb519e50dfba5683b6138a46fc7fc1bd5af7c 100644
--- a/bramble-j2se/src/test/java/org/briarproject/bramble/plugin/modem/ModemPluginTest.java
+++ b/bramble-j2se/src/test/java/org/briarproject/bramble/plugin/modem/ModemPluginTest.java
@@ -1,6 +1,5 @@
 package org.briarproject.bramble.plugin.modem;
 
-import org.briarproject.bramble.api.contact.ContactId;
 import org.briarproject.bramble.api.plugin.duplex.DuplexPluginCallback;
 import org.briarproject.bramble.api.properties.TransportProperties;
 import org.briarproject.bramble.test.BrambleTestCase;
@@ -66,7 +65,6 @@ public class ModemPluginTest extends BrambleTestCase {
 		TransportProperties remote = new TransportProperties();
 		remote.put("iso3166", ISO_1336);
 		remote.put("number", NUMBER);
-		ContactId contactId = new ContactId(234);
 		context.checking(new Expectations() {{
 			// start()
 			oneOf(serialPortList).getPortNames();
@@ -78,14 +76,12 @@ public class ModemPluginTest extends BrambleTestCase {
 			// createConnection()
 			oneOf(callback).getLocalProperties();
 			will(returnValue(local));
-			oneOf(callback).getRemoteProperties(contactId);
-			will(returnValue(remote));
 			oneOf(modem).dial(NUMBER);
 			will(returnValue(true));
 		}});
 		plugin.start();
 		// A connection should be returned
-		assertNotNull(plugin.createConnection(contactId));
+		assertNotNull(plugin.createConnection(remote));
 		context.assertIsSatisfied();
 	}
 
@@ -105,7 +101,6 @@ public class ModemPluginTest extends BrambleTestCase {
 		TransportProperties remote = new TransportProperties();
 		remote.put("iso3166", ISO_1336);
 		remote.put("number", NUMBER);
-		ContactId contactId = new ContactId(234);
 		context.checking(new Expectations() {{
 			// start()
 			oneOf(serialPortList).getPortNames();
@@ -117,14 +112,12 @@ public class ModemPluginTest extends BrambleTestCase {
 			// createConnection()
 			oneOf(callback).getLocalProperties();
 			will(returnValue(local));
-			oneOf(callback).getRemoteProperties(contactId);
-			will(returnValue(remote));
 			oneOf(modem).dial(NUMBER);
 			will(returnValue(false));
 		}});
 		plugin.start();
 		// No connection should be returned
-		assertNull(plugin.createConnection(contactId));
+		assertNull(plugin.createConnection(remote));
 		context.assertIsSatisfied();
 	}
 
@@ -144,7 +137,6 @@ public class ModemPluginTest extends BrambleTestCase {
 		TransportProperties remote = new TransportProperties();
 		remote.put("iso3166", ISO_1336);
 		remote.put("number", NUMBER);
-		ContactId contactId = new ContactId(234);
 		context.checking(new Expectations() {{
 			// start()
 			oneOf(serialPortList).getPortNames();
@@ -156,8 +148,6 @@ public class ModemPluginTest extends BrambleTestCase {
 			// createConnection()
 			oneOf(callback).getLocalProperties();
 			will(returnValue(local));
-			oneOf(callback).getRemoteProperties(contactId);
-			will(returnValue(remote));
 			oneOf(modem).dial(NUMBER);
 			will(throwException(new IOException()));
 			// resetModem()
@@ -170,7 +160,7 @@ public class ModemPluginTest extends BrambleTestCase {
 		}});
 		plugin.start();
 		// No connection should be returned
-		assertNull(plugin.createConnection(contactId));
+		assertNull(plugin.createConnection(remote));
 		context.assertIsSatisfied();
 	}
 }
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/AppModule.java b/briar-android/src/main/java/org/briarproject/briar/android/AppModule.java
index a8c958ea8a0de7bb7717e36ca6184de42aba624b..9ce18391ab789b24e0489b2b3899473bffe40258 100644
--- a/briar-android/src/main/java/org/briarproject/briar/android/AppModule.java
+++ b/briar-android/src/main/java/org/briarproject/briar/android/AppModule.java
@@ -19,15 +19,13 @@ import org.briarproject.bramble.api.plugin.PluginConfig;
 import org.briarproject.bramble.api.plugin.duplex.DuplexPluginFactory;
 import org.briarproject.bramble.api.plugin.simplex.SimplexPluginFactory;
 import org.briarproject.bramble.api.reporting.DevConfig;
-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.api.ui.UiCallback;
-import org.briarproject.bramble.util.AndroidUtils;
 import org.briarproject.bramble.plugin.bluetooth.AndroidBluetoothPluginFactory;
 import org.briarproject.bramble.plugin.tcp.AndroidLanTcpPluginFactory;
 import org.briarproject.bramble.plugin.tor.TorPluginFactory;
+import org.briarproject.bramble.util.AndroidUtils;
 import org.briarproject.bramble.util.StringUtils;
 import org.briarproject.briar.api.android.AndroidNotificationManager;
 import org.briarproject.briar.api.android.DozeWatchdog;
@@ -37,9 +35,7 @@ import org.briarproject.briar.api.android.ScreenFilterMonitor;
 import java.io.File;
 import java.security.GeneralSecurityException;
 import java.security.SecureRandom;
-import java.util.Arrays;
 import java.util.Collection;
-import java.util.Collections;
 import java.util.concurrent.Executor;
 import java.util.concurrent.ScheduledExecutorService;
 
@@ -51,6 +47,8 @@ import dagger.Module;
 import dagger.Provides;
 
 import static android.content.Context.MODE_PRIVATE;
+import static java.util.Arrays.asList;
+import static java.util.Collections.emptyList;
 import static org.briarproject.bramble.api.reporting.ReportingConstants.DEV_ONION_ADDRESS;
 import static org.briarproject.bramble.api.reporting.ReportingConstants.DEV_PUBLIC_KEY_HEX;
 
@@ -67,27 +65,9 @@ public class AppModule {
 	}
 
 	private final Application application;
-	private final UiCallback uiCallback;
 
 	public AppModule(Application application) {
 		this.application = application;
-		uiCallback = new UiCallback() {
-
-			@Override
-			public int showChoice(String[] options, String... message) {
-				throw new UnsupportedOperationException();
-			}
-
-			@Override
-			public boolean showConfirmationMessage(String... message) {
-				throw new UnsupportedOperationException();
-			}
-
-			@Override
-			public void showMessage(String... message) {
-				throw new UnsupportedOperationException();
-			}
-		};
 	}
 
 	@Provides
@@ -96,11 +76,6 @@ public class AppModule {
 		return application;
 	}
 
-	@Provides
-	UiCallback provideUICallback() {
-		return uiCallback;
-	}
-
 	@Provides
 	@Singleton
 	DatabaseConfig provideDatabaseConfig(Application app) {
@@ -122,8 +97,7 @@ public class AppModule {
 			@Scheduler ScheduledExecutorService scheduler,
 			AndroidExecutor androidExecutor, SecureRandom random,
 			SocketFactory torSocketFactory, BackoffFactory backoffFactory,
-			Application app, LocationUtils locationUtils, DevReporter reporter,
-			EventBus eventBus) {
+			Application app, LocationUtils locationUtils, EventBus eventBus) {
 		Context appContext = app.getApplicationContext();
 		DuplexPluginFactory bluetooth =
 				new AndroidBluetoothPluginFactory(ioExecutor, androidExecutor,
@@ -133,8 +107,7 @@ public class AppModule {
 				torSocketFactory, backoffFactory);
 		DuplexPluginFactory lan = new AndroidLanTcpPluginFactory(ioExecutor,
 				scheduler, backoffFactory, appContext);
-		Collection<DuplexPluginFactory> duplex =
-				Arrays.asList(bluetooth, tor, lan);
+		Collection<DuplexPluginFactory> duplex = asList(bluetooth, tor, lan);
 		@NotNullByDefault
 		PluginConfig pluginConfig = new PluginConfig() {
 
@@ -145,14 +118,13 @@ public class AppModule {
 
 			@Override
 			public Collection<SimplexPluginFactory> getSimplexFactories() {
-				return Collections.emptyList();
+				return emptyList();
 			}
 
 			@Override
 			public boolean shouldPoll() {
 				return true;
 			}
-
 		};
 		return pluginConfig;
 	}