diff --git a/briar-android/src/org/briarproject/plugins/droidtooth/DroidtoothPlugin.java b/briar-android/src/org/briarproject/plugins/droidtooth/DroidtoothPlugin.java
index 0cb68003a1b3f3a68471459f3c92733fc8e04bdc..16d40d43a9a568d21660547334e1bb44bede7e90 100644
--- a/briar-android/src/org/briarproject/plugins/droidtooth/DroidtoothPlugin.java
+++ b/briar-android/src/org/briarproject/plugins/droidtooth/DroidtoothPlugin.java
@@ -19,11 +19,10 @@ import org.briarproject.api.plugins.duplex.DuplexPlugin;
 import org.briarproject.api.plugins.duplex.DuplexPluginCallback;
 import org.briarproject.api.plugins.duplex.DuplexTransportConnection;
 import org.briarproject.api.properties.TransportProperties;
-import org.briarproject.api.system.Clock;
-import org.briarproject.util.LatchedReference;
 import org.briarproject.util.StringUtils;
 
 import java.io.IOException;
+import java.io.InputStream;
 import java.security.SecureRandom;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -33,9 +32,12 @@ import java.util.Map;
 import java.util.Map.Entry;
 import java.util.UUID;
 import java.util.concurrent.Callable;
+import java.util.concurrent.CompletionService;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Executor;
+import java.util.concurrent.ExecutorCompletionService;
+import java.util.concurrent.Future;
 import java.util.logging.Logger;
 
 import static android.bluetooth.BluetoothAdapter.ACTION_SCAN_MODE_CHANGED;
@@ -69,7 +71,6 @@ class DroidtoothPlugin implements DuplexPlugin {
 	private final AndroidExecutor androidExecutor;
 	private final Context appContext;
 	private final SecureRandom secureRandom;
-	private final Clock clock;
 	private final Backoff backoff;
 	private final DuplexPluginCallback callback;
 	private final int maxLatency;
@@ -83,13 +84,12 @@ class DroidtoothPlugin implements DuplexPlugin {
 	private volatile BluetoothAdapter adapter = null;
 
 	DroidtoothPlugin(Executor ioExecutor, AndroidExecutor androidExecutor,
-			Context appContext, SecureRandom secureRandom, Clock clock,
-			Backoff backoff, DuplexPluginCallback callback, int maxLatency) {
+			Context appContext, SecureRandom secureRandom, Backoff backoff,
+			DuplexPluginCallback callback, int maxLatency) {
 		this.ioExecutor = ioExecutor;
 		this.androidExecutor = androidExecutor;
 		this.appContext = appContext;
 		this.secureRandom = secureRandom;
-		this.clock = clock;
 		this.backoff = backoff;
 		this.callback = callback;
 		this.maxLatency = maxLatency;
@@ -339,7 +339,7 @@ class DroidtoothPlugin implements DuplexPlugin {
 	}
 
 	public DuplexTransportConnection createInvitationConnection(PseudoRandom r,
-			long timeout) {
+			long timeout, boolean alice) {
 		if (!isRunning()) return null;
 		// Use the invitation codes to generate the UUID
 		byte[] b = r.nextBytes(UUID_BYTES);
@@ -353,23 +353,67 @@ class DroidtoothPlugin implements DuplexPlugin {
 			if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
 			return null;
 		}
-		// Start the background threads
-		LatchedReference<BluetoothSocket> socketLatch =
-				new LatchedReference<BluetoothSocket>();
-		new DiscoveryThread(socketLatch, uuid.toString(), timeout).start();
-		new BluetoothListenerThread(socketLatch, ss).start();
-		// Wait for an incoming or outgoing connection
+		// Create the background tasks
+		CompletionService<BluetoothSocket> complete =
+				new ExecutorCompletionService<BluetoothSocket>(ioExecutor);
+		List<Future<BluetoothSocket>> futures =
+				new ArrayList<Future<BluetoothSocket>>();
+		if (alice) {
+			// Return the first connected socket
+			futures.add(complete.submit(new ListeningTask(ss)));
+			futures.add(complete.submit(new DiscoveryTask(uuid.toString())));
+		} else {
+			// Return the first socket with readable data
+			futures.add(complete.submit(new ReadableTask(
+					new ListeningTask(ss))));
+			futures.add(complete.submit(new ReadableTask(
+					new DiscoveryTask(uuid.toString()))));
+		}
+		BluetoothSocket chosen = null;
 		try {
-			BluetoothSocket s = socketLatch.waitForReference(timeout);
-			if (s != null) return new DroidtoothTransportConnection(this, s);
+			Future<BluetoothSocket> f = complete.poll(timeout, MILLISECONDS);
+			if (f == null) return null; // No task completed within the timeout
+			chosen = f.get();
+			return new DroidtoothTransportConnection(this, chosen);
 		} catch (InterruptedException e) {
-			LOG.warning("Interrupted while exchanging invitations");
-			Thread.currentThread().interrupt();
+			LOG.info("Interrupted while waiting for connection");
+			return null;
+		} catch (ExecutionException e) {
+			if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
+			return null;
 		} finally {
-			// Closing the socket will terminate the listener thread
+			// Closing the socket will terminate the listener task
 			tryToClose(ss);
+			closeSockets(futures, chosen);
 		}
-		return null;
+	}
+
+	private void closeSockets(final List<Future<BluetoothSocket>> futures,
+			final BluetoothSocket chosen) {
+		ioExecutor.execute(new Runnable() {
+			public void run() {
+				for (Future<BluetoothSocket> f : futures) {
+					try {
+						if (f.cancel(true)) {
+							LOG.info("Cancelled task");
+						} else {
+							BluetoothSocket s = f.get();
+							if (s != null && s != chosen) {
+								LOG.info("Closing unwanted socket");
+								s.close();
+							}
+						}
+					} catch (InterruptedException e) {
+						LOG.info("Interrupted while closing sockets");
+						return;
+					} catch (ExecutionException e) {
+						if (LOG.isLoggable(INFO)) LOG.info(e.toString());
+					} catch (IOException e) {
+						if (LOG.isLoggable(INFO)) LOG.info(e.toString());
+					}
+				}
+			}
+		});
 	}
 
 	private class BluetoothStateReceiver extends BroadcastReceiver {
@@ -395,61 +439,37 @@ class DroidtoothPlugin implements DuplexPlugin {
 		}
 	}
 
-	private class DiscoveryThread extends Thread {
+	private class DiscoveryTask implements Callable<BluetoothSocket> {
 
-		private final LatchedReference<BluetoothSocket> socketLatch;
 		private final String uuid;
-		private final long timeout;
 
-		private DiscoveryThread(LatchedReference<BluetoothSocket> socketLatch,
-				String uuid, long timeout) {
-			this.socketLatch = socketLatch;
+		private DiscoveryTask(String uuid) {
 			this.uuid = uuid;
-			this.timeout = timeout;
 		}
 
 		@Override
-		public void run() {
-			long end = clock.currentTimeMillis() + timeout;
-			while (!finished(end)) {
+		public BluetoothSocket call() throws Exception {
+			// Repeat discovery until we connect or get interrupted
+			while (true) {
 				// Discover nearby devices
 				LOG.info("Discovering nearby devices");
-				List<String> addresses;
-				try {
-					long now = clock.currentTimeMillis();
-					addresses = discoverDevices(end - now);
-				} catch (InterruptedException e) {
-					LOG.warning("Interrupted while discovering devices");
-					Thread.currentThread().interrupt();
-					return;
-				}
+				List<String> addresses = discoverDevices();
 				if (addresses.isEmpty()) {
 					LOG.info("No devices discovered");
 					continue;
 				}
 				// Connect to any device with the right UUID
 				for (String address : addresses) {
-					if (finished(end)) return;
 					BluetoothSocket s = connect(address, uuid);
 					if (s != null) {
 						LOG.info("Outgoing connection");
-						if (!socketLatch.set(s)) {
-							LOG.info("Closing redundant connection");
-							tryToClose(s);
-						}
-						return;
+						return s;
 					}
 				}
 			}
 		}
 
-		private boolean finished(long end) {
-			long now = clock.currentTimeMillis();
-			return now >= end || !isRunning() || socketLatch.isSet();
-		}
-
-		private List<String> discoverDevices(long timeout)
-				throws InterruptedException {
+		private List<String> discoverDevices() throws InterruptedException {
 			IntentFilter filter = new IntentFilter();
 			filter.addAction(FOUND);
 			filter.addAction(DISCOVERY_FINISHED);
@@ -457,7 +477,7 @@ class DroidtoothPlugin implements DuplexPlugin {
 			appContext.registerReceiver(disco, filter);
 			LOG.info("Starting discovery");
 			adapter.startDiscovery();
-			return disco.waitForAddresses(timeout);
+			return disco.waitForAddresses();
 		}
 	}
 
@@ -481,38 +501,47 @@ class DroidtoothPlugin implements DuplexPlugin {
 			}
 		}
 
-		private List<String> waitForAddresses(long timeout)
-				throws InterruptedException {
-			finished.await(timeout, MILLISECONDS);
+		private List<String> waitForAddresses() throws InterruptedException {
+			finished.await();
+			Collections.shuffle(addresses);
 			return Collections.unmodifiableList(addresses);
 		}
 	}
 
-	private static class BluetoothListenerThread extends Thread {
+	private static class ListeningTask implements Callable<BluetoothSocket> {
 
-		private final LatchedReference<BluetoothSocket> socketLatch;
 		private final BluetoothServerSocket serverSocket;
 
-		private BluetoothListenerThread(
-				LatchedReference<BluetoothSocket> socketLatch,
-				BluetoothServerSocket serverSocket) {
-			this.socketLatch = socketLatch;
+		private ListeningTask(BluetoothServerSocket serverSocket) {
 			this.serverSocket = serverSocket;
 		}
 
 		@Override
-		public void run() {
-			try {
-				BluetoothSocket s = serverSocket.accept();
-				LOG.info("Incoming connection");
-				if (!socketLatch.set(s)) {
-					LOG.info("Closing redundant connection");
-					s.close();
-				}
-			} catch (IOException e) {
-				// This is expected when the socket is closed
-				if (LOG.isLoggable(INFO)) LOG.info(e.toString());
+		public BluetoothSocket call() throws IOException {
+			BluetoothSocket s = serverSocket.accept();
+			LOG.info("Incoming connection");
+			return s;
+		}
+	}
+
+	private static class ReadableTask implements Callable<BluetoothSocket> {
+
+		private final Callable<BluetoothSocket> connectionTask;
+
+		private ReadableTask(Callable<BluetoothSocket> connectionTask) {
+			this.connectionTask = connectionTask;
+		}
+
+		@Override
+		public BluetoothSocket call() throws Exception {
+			BluetoothSocket s = connectionTask.call();
+			InputStream in = s.getInputStream();
+			while (in.available() == 0) {
+				LOG.info("Waiting for data");
+				Thread.sleep(1000);
 			}
+			LOG.info("Data available");
+			return s;
 		}
 	}
 }
diff --git a/briar-android/src/org/briarproject/plugins/droidtooth/DroidtoothPluginFactory.java b/briar-android/src/org/briarproject/plugins/droidtooth/DroidtoothPluginFactory.java
index 15e2fc0bacbc3cb0ab5a2e509b97efa5a83b043c..db89929a2282a62e493f0872218c8e9df4ee5143 100644
--- a/briar-android/src/org/briarproject/plugins/droidtooth/DroidtoothPluginFactory.java
+++ b/briar-android/src/org/briarproject/plugins/droidtooth/DroidtoothPluginFactory.java
@@ -9,8 +9,6 @@ import org.briarproject.api.plugins.BackoffFactory;
 import org.briarproject.api.plugins.duplex.DuplexPlugin;
 import org.briarproject.api.plugins.duplex.DuplexPluginCallback;
 import org.briarproject.api.plugins.duplex.DuplexPluginFactory;
-import org.briarproject.api.system.Clock;
-import org.briarproject.system.SystemClock;
 
 import java.security.SecureRandom;
 import java.util.concurrent.Executor;
@@ -27,7 +25,6 @@ public class DroidtoothPluginFactory implements DuplexPluginFactory {
 	private final Context appContext;
 	private final SecureRandom secureRandom;
 	private final BackoffFactory backoffFactory;
-	private final Clock clock;
 
 	public DroidtoothPluginFactory(Executor ioExecutor,
 			AndroidExecutor androidExecutor, Context appContext,
@@ -37,7 +34,6 @@ public class DroidtoothPluginFactory implements DuplexPluginFactory {
 		this.appContext = appContext;
 		this.secureRandom = secureRandom;
 		this.backoffFactory = backoffFactory;
-		clock = new SystemClock();
 	}
 
 	public TransportId getId() {
@@ -48,6 +44,6 @@ public class DroidtoothPluginFactory implements DuplexPluginFactory {
 		Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL,
 				MAX_POLLING_INTERVAL, BACKOFF_BASE);
 		return new DroidtoothPlugin(ioExecutor, androidExecutor, appContext,
-				secureRandom, clock, backoff, callback, MAX_LATENCY);
+				secureRandom, backoff, callback, MAX_LATENCY);
 	}
 }
diff --git a/briar-android/src/org/briarproject/plugins/tor/TorPlugin.java b/briar-android/src/org/briarproject/plugins/tor/TorPlugin.java
index d98c2134363e611077a4f84e34500aca9714af4e..01d10f91a8188d7e733492f70fdc6c4dafab8467 100644
--- a/briar-android/src/org/briarproject/plugins/tor/TorPlugin.java
+++ b/briar-android/src/org/briarproject/plugins/tor/TorPlugin.java
@@ -565,7 +565,7 @@ class TorPlugin implements DuplexPlugin, EventHandler,
 	}
 
 	public DuplexTransportConnection createInvitationConnection(PseudoRandom r,
-			long timeout) {
+			long timeout, boolean alice) {
 		throw new UnsupportedOperationException();
 	}
 
diff --git a/briar-api/src/org/briarproject/api/plugins/duplex/DuplexPlugin.java b/briar-api/src/org/briarproject/api/plugins/duplex/DuplexPlugin.java
index b64f4a1498e200ec61c537081cff3b2dd6289f42..519400b5639fb2cc598dad8f88b16b7433853feb 100644
--- a/briar-api/src/org/briarproject/api/plugins/duplex/DuplexPlugin.java
+++ b/briar-api/src/org/briarproject/api/plugins/duplex/DuplexPlugin.java
@@ -23,5 +23,5 @@ public interface DuplexPlugin extends Plugin {
 	 * time.
 	 */
 	DuplexTransportConnection createInvitationConnection(PseudoRandom r,
-			long timeout);
+			long timeout, boolean alice);
 }
diff --git a/briar-core/src/org/briarproject/invitation/AliceConnector.java b/briar-core/src/org/briarproject/invitation/AliceConnector.java
index 39945bf6b61a0585e003df8dc3073a238f6d10c1..210d173d9e6de69942a56c7a56b834134e1d4867 100644
--- a/briar-core/src/org/briarproject/invitation/AliceConnector.java
+++ b/briar-core/src/org/briarproject/invitation/AliceConnector.java
@@ -51,7 +51,7 @@ class AliceConnector extends Connector {
 	@Override
 	public void run() {
 		// Create an incoming or outgoing connection
-		DuplexTransportConnection conn = createInvitationConnection();
+		DuplexTransportConnection conn = createInvitationConnection(true);
 		if (conn == null) return;
 		if (LOG.isLoggable(INFO)) LOG.info(pluginName + " connected");
 		// Don't proceed with more than one connection
diff --git a/briar-core/src/org/briarproject/invitation/BobConnector.java b/briar-core/src/org/briarproject/invitation/BobConnector.java
index 53bdbe3e47b7d32afb2e36667ba65175f45fc91b..f2c6e2619673922d15a32db891563e5797f25d9d 100644
--- a/briar-core/src/org/briarproject/invitation/BobConnector.java
+++ b/briar-core/src/org/briarproject/invitation/BobConnector.java
@@ -51,7 +51,7 @@ class BobConnector extends Connector {
 	@Override
 	public void run() {
 		// Create an incoming or outgoing connection
-		DuplexTransportConnection conn = createInvitationConnection();
+		DuplexTransportConnection conn = createInvitationConnection(false);
 		if (conn == null) return;
 		if (LOG.isLoggable(INFO)) LOG.info(pluginName + " connected");
 		// Carry out the key agreement protocol
diff --git a/briar-core/src/org/briarproject/invitation/Connector.java b/briar-core/src/org/briarproject/invitation/Connector.java
index 6310c4f170ad3031faeecc00c80697f2b95932dc..2934a0716e10ae432b7f11b2c6d00a2e50720979 100644
--- a/briar-core/src/org/briarproject/invitation/Connector.java
+++ b/briar-core/src/org/briarproject/invitation/Connector.java
@@ -93,10 +93,12 @@ abstract class Connector extends Thread {
 		messageDigest = crypto.getMessageDigest();
 	}
 
-	protected DuplexTransportConnection createInvitationConnection() {
+	protected DuplexTransportConnection createInvitationConnection(
+			boolean alice) {
 		if (LOG.isLoggable(INFO))
 			LOG.info(pluginName + " creating invitation connection");
-		return plugin.createInvitationConnection(random, CONNECTION_TIMEOUT);
+		return plugin.createInvitationConnection(random, CONNECTION_TIMEOUT,
+				alice);
 	}
 
 	protected void sendPublicKeyHash(BdfWriter w) throws IOException {
diff --git a/briar-core/src/org/briarproject/plugins/tcp/TcpPlugin.java b/briar-core/src/org/briarproject/plugins/tcp/TcpPlugin.java
index 64f91c4d1aba73cc117984620541496b98190924..54376310a0c33bd0b4e4f665f503d1c1ca97b76e 100644
--- a/briar-core/src/org/briarproject/plugins/tcp/TcpPlugin.java
+++ b/briar-core/src/org/briarproject/plugins/tcp/TcpPlugin.java
@@ -246,7 +246,7 @@ abstract class TcpPlugin implements DuplexPlugin {
 	}
 
 	public DuplexTransportConnection createInvitationConnection(PseudoRandom r,
-			long timeout) {
+			long timeout, boolean alice) {
 		throw new UnsupportedOperationException();
 	}
 
diff --git a/briar-core/src/org/briarproject/util/LatchedReference.java b/briar-core/src/org/briarproject/util/LatchedReference.java
deleted file mode 100644
index 43763bfa79f596901b5194c7dce53173b6ee8eea..0000000000000000000000000000000000000000
--- a/briar-core/src/org/briarproject/util/LatchedReference.java
+++ /dev/null
@@ -1,30 +0,0 @@
-package org.briarproject.util;
-
-import static java.util.concurrent.TimeUnit.MILLISECONDS;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.atomic.AtomicReference;
-
-public class LatchedReference<T> {
-
-	private final CountDownLatch latch = new CountDownLatch(1);
-	private final AtomicReference<T> reference = new AtomicReference<T>();
-
-	public boolean isSet() {
-		return reference.get() != null;
-	}
-
-	public boolean set(T t) {
-		if (t == null) throw new IllegalArgumentException();
-		if (reference.compareAndSet(null, t)) {
-			latch.countDown();
-			return true;
-		}
-		return false;
-	}
-
-	public T waitForReference(long timeout) throws InterruptedException {
-		latch.await(timeout, MILLISECONDS);
-		return reference.get();
-	}
-}
diff --git a/briar-desktop/src/org/briarproject/plugins/bluetooth/BluetoothPlugin.java b/briar-desktop/src/org/briarproject/plugins/bluetooth/BluetoothPlugin.java
index 786dcf22280a16ad044fb8ab366f13a88bb56852..669965f0d37a1d6e189ea66f7778c978aebf44dd 100644
--- a/briar-desktop/src/org/briarproject/plugins/bluetooth/BluetoothPlugin.java
+++ b/briar-desktop/src/org/briarproject/plugins/bluetooth/BluetoothPlugin.java
@@ -8,18 +8,24 @@ import org.briarproject.api.plugins.duplex.DuplexPlugin;
 import org.briarproject.api.plugins.duplex.DuplexPluginCallback;
 import org.briarproject.api.plugins.duplex.DuplexTransportConnection;
 import org.briarproject.api.properties.TransportProperties;
-import org.briarproject.api.system.Clock;
-import org.briarproject.util.LatchedReference;
 import org.briarproject.util.OsUtils;
 import org.briarproject.util.StringUtils;
 
 import java.io.IOException;
+import java.io.InputStream;
 import java.security.SecureRandom;
+import java.util.ArrayList;
 import java.util.Collection;
+import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.UUID;
+import java.util.concurrent.Callable;
+import java.util.concurrent.CompletionService;
+import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Executor;
+import java.util.concurrent.ExecutorCompletionService;
+import java.util.concurrent.Future;
 import java.util.concurrent.Semaphore;
 import java.util.logging.Logger;
 
@@ -30,6 +36,7 @@ import javax.microedition.io.Connector;
 import javax.microedition.io.StreamConnection;
 import javax.microedition.io.StreamConnectionNotifier;
 
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
 import static java.util.logging.Level.INFO;
 import static java.util.logging.Level.WARNING;
 import static javax.bluetooth.DiscoveryAgent.GIAC;
@@ -45,7 +52,6 @@ class BluetoothPlugin implements DuplexPlugin {
 
 	private final Executor ioExecutor;
 	private final SecureRandom secureRandom;
-	private final Clock clock;
 	private final Backoff backoff;
 	private final DuplexPluginCallback callback;
 	private final int maxLatency;
@@ -55,11 +61,10 @@ class BluetoothPlugin implements DuplexPlugin {
 	private volatile StreamConnectionNotifier socket = null;
 	private volatile LocalDevice localDevice = null;
 
-	BluetoothPlugin(Executor ioExecutor, SecureRandom secureRandom, Clock clock,
+	BluetoothPlugin(Executor ioExecutor, SecureRandom secureRandom,
 			Backoff backoff, DuplexPluginCallback callback, int maxLatency) {
 		this.ioExecutor = ioExecutor;
 		this.secureRandom = secureRandom;
-		this.clock = clock;
 		this.backoff = backoff;
 		this.callback = callback;
 		this.maxLatency = maxLatency;
@@ -246,7 +251,7 @@ class BluetoothPlugin implements DuplexPlugin {
 	}
 
 	public DuplexTransportConnection createInvitationConnection(PseudoRandom r,
-			long timeout) {
+			long timeout, boolean alice) {
 		if (!running) return null;
 		// Use the invitation codes to generate the UUID
 		byte[] b = r.nextBytes(UUID_BYTES);
@@ -266,23 +271,67 @@ class BluetoothPlugin implements DuplexPlugin {
 			tryToClose(ss);
 			return null;
 		}
-		// Start the background threads
-		LatchedReference<StreamConnection> socketLatch =
-				new LatchedReference<StreamConnection>();
-		new DiscoveryThread(socketLatch, uuid, timeout).start();
-		new BluetoothListenerThread(socketLatch, ss).start();
-		// Wait for an incoming or outgoing connection
+		// Create the background tasks
+		CompletionService<StreamConnection> complete =
+				new ExecutorCompletionService<StreamConnection>(ioExecutor);
+		List<Future<StreamConnection>> futures =
+				new ArrayList<Future<StreamConnection>>();
+		if (alice) {
+			// Return the first connected socket
+			futures.add(complete.submit(new ListeningTask(ss)));
+			futures.add(complete.submit(new DiscoveryTask(uuid)));
+		} else {
+			// Return the first socket with readable data
+			futures.add(complete.submit(new ReadableTask(
+					new ListeningTask(ss))));
+			futures.add(complete.submit(new ReadableTask(
+					new DiscoveryTask(uuid))));
+		}
+		StreamConnection chosen = null;
 		try {
-			StreamConnection s = socketLatch.waitForReference(timeout);
-			if (s != null) return new BluetoothTransportConnection(this, s);
+			Future<StreamConnection> f = complete.poll(timeout, MILLISECONDS);
+			if (f == null) return null; // No task completed within the timeout
+			chosen = f.get();
+			return new BluetoothTransportConnection(this, chosen);
 		} catch (InterruptedException e) {
-			LOG.warning("Interrupted while exchanging invitations");
-			Thread.currentThread().interrupt();
+			LOG.info("Interrupted while waiting for connection");
+			return null;
+		} catch (ExecutionException e) {
+			if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
+			return null;
 		} finally {
-			// Closing the socket will terminate the listener thread
+			// Closing the socket will terminate the listener task
 			tryToClose(ss);
+			closeSockets(futures, chosen);
 		}
-		return null;
+	}
+
+	private void closeSockets(final List<Future<StreamConnection>> futures,
+			final StreamConnection chosen) {
+		ioExecutor.execute(new Runnable() {
+			public void run() {
+				for (Future<StreamConnection> f : futures) {
+					try {
+						if (f.cancel(true)) {
+							LOG.info("Cancelled task");
+						} else {
+							StreamConnection s = f.get();
+							if (s != null && s != chosen) {
+								LOG.info("Closing unwanted socket");
+								s.close();
+							}
+						}
+					} catch (InterruptedException e) {
+						LOG.info("Interrupted while closing sockets");
+						return;
+					} catch (ExecutionException e) {
+						if (LOG.isLoggable(INFO)) LOG.info(e.toString());
+					} catch (IOException e) {
+						if (LOG.isLoggable(INFO)) LOG.info(e.toString());
+					}
+				}
+			}
+		});
 	}
 
 	private void makeDeviceDiscoverable() {
@@ -294,93 +343,74 @@ class BluetoothPlugin implements DuplexPlugin {
 		}
 	}
 
-	private class DiscoveryThread extends Thread {
+	private class DiscoveryTask implements Callable<StreamConnection> {
 
-		private final LatchedReference<StreamConnection> socketLatch;
 		private final String uuid;
-		private final long timeout;
 
-		private DiscoveryThread(LatchedReference<StreamConnection> socketLatch,
-				String uuid, long timeout) {
-			this.socketLatch = socketLatch;
+		private DiscoveryTask(String uuid) {
 			this.uuid = uuid;
-			this.timeout = timeout;
 		}
 
 		@Override
-		public void run() {
+		public StreamConnection call() throws Exception {
+			// Repeat discovery until we connect or get interrupted
 			DiscoveryAgent discoveryAgent = localDevice.getDiscoveryAgent();
-			long now = clock.currentTimeMillis();
-			long end = now + timeout;
-			while (now < end && running && !socketLatch.isSet()) {
-				if (!discoverySemaphore.tryAcquire()) {
-					LOG.info("Another device discovery is in progress");
-					return;
-				}
+			while (true) {
+				if (!discoverySemaphore.tryAcquire())
+					throw new Exception("Discovery is already in progress");
 				try {
 					InvitationListener listener =
 							new InvitationListener(discoveryAgent, uuid);
 					discoveryAgent.startInquiry(GIAC, listener);
 					String url = listener.waitForUrl();
-					if (url == null) continue;
-					StreamConnection s = connect(url);
-					if (s == null) continue;
-					LOG.info("Outgoing connection");
-					if (!socketLatch.set(s)) {
-						LOG.info("Closing redundant connection");
-						tryToClose(s);
+					if (url != null) {
+						StreamConnection s = connect(url);
+						if (s != null) {
+							LOG.info("Outgoing connection");
+							return s;
+						}
 					}
-					return;
-				} catch (BluetoothStateException e) {
-					if (LOG.isLoggable(WARNING))
-						LOG.log(WARNING, e.toString(), e);
-					return;
-				} catch (InterruptedException e) {
-					LOG.warning("Interrupted while waiting for URL");
-					Thread.currentThread().interrupt();
-					return;
 				} finally {
 					discoverySemaphore.release();
 				}
 			}
 		}
-
-		private void tryToClose(StreamConnection s) {
-			try {
-				if (s != null) s.close();
-			} catch (IOException e) {
-				if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
-			}
-		}
 	}
 
-	private static class BluetoothListenerThread extends Thread {
+	private static class ListeningTask implements Callable<StreamConnection> {
 
-		private final LatchedReference<StreamConnection> socketLatch;
 		private final StreamConnectionNotifier serverSocket;
 
-		private BluetoothListenerThread(
-				LatchedReference<StreamConnection> socketLatch,
-				StreamConnectionNotifier serverSocket) {
-			this.socketLatch = socketLatch;
+		private ListeningTask(StreamConnectionNotifier serverSocket) {
 			this.serverSocket = serverSocket;
 		}
 
 		@Override
-		public void run() {
-			LOG.info("Listening for invitation connections");
-			// Listen until a connection is received or the socket is closed
-			try {
-				StreamConnection s = serverSocket.acceptAndOpen();
-				LOG.info("Incoming connection");
-				if (!socketLatch.set(s)) {
-					LOG.info("Closing redundant connection");
-					s.close();
-				}
-			} catch (IOException e) {
-				// This is expected when the socket is closed
-				if (LOG.isLoggable(INFO)) LOG.info(e.toString());
+		public StreamConnection call() throws Exception  {
+			StreamConnection s = serverSocket.acceptAndOpen();
+			LOG.info("Incoming connection");
+			return s;
+		}
+	}
+
+	private static class ReadableTask implements Callable<StreamConnection> {
+
+		private final Callable<StreamConnection> connectionTask;
+
+		private ReadableTask(Callable<StreamConnection> connectionTask) {
+			this.connectionTask = connectionTask;
+		}
+
+		@Override
+		public StreamConnection call() throws Exception {
+			StreamConnection s = connectionTask.call();
+			InputStream in = s.openInputStream();
+			while (in.available() == 0) {
+				LOG.info("Waiting for data");
+				Thread.sleep(1000);
 			}
+			LOG.info("Data available");
+			return s;
 		}
 	}
 }
diff --git a/briar-desktop/src/org/briarproject/plugins/bluetooth/BluetoothPluginFactory.java b/briar-desktop/src/org/briarproject/plugins/bluetooth/BluetoothPluginFactory.java
index 5259ece49b8d7f96143b9ed719f87adfaa8771cc..2d7f156e22d861f777808cc98ba0fbbb69224d13 100644
--- a/briar-desktop/src/org/briarproject/plugins/bluetooth/BluetoothPluginFactory.java
+++ b/briar-desktop/src/org/briarproject/plugins/bluetooth/BluetoothPluginFactory.java
@@ -6,8 +6,6 @@ import org.briarproject.api.plugins.BackoffFactory;
 import org.briarproject.api.plugins.duplex.DuplexPlugin;
 import org.briarproject.api.plugins.duplex.DuplexPluginCallback;
 import org.briarproject.api.plugins.duplex.DuplexPluginFactory;
-import org.briarproject.api.system.Clock;
-import org.briarproject.system.SystemClock;
 
 import java.security.SecureRandom;
 import java.util.concurrent.Executor;
@@ -22,14 +20,12 @@ public class BluetoothPluginFactory implements DuplexPluginFactory {
 	private final Executor ioExecutor;
 	private final SecureRandom secureRandom;
 	private final BackoffFactory backoffFactory;
-	private final Clock clock;
 
 	public BluetoothPluginFactory(Executor ioExecutor,
 			SecureRandom secureRandom, BackoffFactory backoffFactory) {
 		this.ioExecutor = ioExecutor;
 		this.secureRandom = secureRandom;
 		this.backoffFactory = backoffFactory;
-		clock = new SystemClock();
 	}
 
 	public TransportId getId() {
@@ -39,7 +35,7 @@ public class BluetoothPluginFactory implements DuplexPluginFactory {
 	public DuplexPlugin createPlugin(DuplexPluginCallback callback) {
 		Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL,
 				MAX_POLLING_INTERVAL, BACKOFF_BASE);
-		return new BluetoothPlugin(ioExecutor, secureRandom, clock, backoff,
-				callback, MAX_LATENCY);
+		return new BluetoothPlugin(ioExecutor, secureRandom, backoff, callback,
+				MAX_LATENCY);
 	}
 }
diff --git a/briar-desktop/src/org/briarproject/plugins/modem/ModemPlugin.java b/briar-desktop/src/org/briarproject/plugins/modem/ModemPlugin.java
index 0245ad17d37fb8ba7f6ca5b0a8158d18e11ad851..954529a5e8221fa1341f7b0a7b198409e33efa19 100644
--- a/briar-desktop/src/org/briarproject/plugins/modem/ModemPlugin.java
+++ b/briar-desktop/src/org/briarproject/plugins/modem/ModemPlugin.java
@@ -154,7 +154,7 @@ class ModemPlugin implements DuplexPlugin, Modem.Callback {
 	}
 
 	public DuplexTransportConnection createInvitationConnection(PseudoRandom r,
-			long timeout) {
+			long timeout, boolean alice) {
 		throw new UnsupportedOperationException();
 	}
 
diff --git a/briar-tests/src/org/briarproject/plugins/DuplexClientTest.java b/briar-tests/src/org/briarproject/plugins/DuplexClientTest.java
deleted file mode 100644
index b241ec4e8fcdb0b99aacd003e5e4263e5e8514cb..0000000000000000000000000000000000000000
--- a/briar-tests/src/org/briarproject/plugins/DuplexClientTest.java
+++ /dev/null
@@ -1,111 +0,0 @@
-package org.briarproject.plugins;
-
-import org.briarproject.api.contact.ContactId;
-import org.briarproject.api.crypto.PseudoRandom;
-import org.briarproject.api.plugins.duplex.DuplexPluginCallback;
-import org.briarproject.api.plugins.duplex.DuplexTransportConnection;
-import org.briarproject.api.properties.TransportProperties;
-import org.briarproject.api.settings.Settings;
-
-import java.io.IOException;
-import java.util.Map;
-
-import static org.briarproject.api.invitation.InvitationConstants.CONNECTION_TIMEOUT;
-
-public abstract class DuplexClientTest extends DuplexTest {
-
-	protected ClientCallback callback = null;
-
-	protected void run() throws IOException {
-		assert plugin != null;
-		// Start the plugin
-		System.out.println("Starting plugin");
-		if (!plugin.start()) {
-			System.out.println("Plugin failed to start");
-			return;
-		}
-		try {
-			// Try to connect to the server
-			System.out.println("Creating connection");
-			DuplexTransportConnection d = plugin.createConnection(contactId);
-			if (d == null) {
-				System.out.println("Connection failed");
-				return;
-			} else {
-				System.out.println("Connection created");
-				receiveChallengeSendResponse(d);
-			}
-			if (!plugin.supportsInvitations()) {
-				System.out.println("Skipping invitation test");
-				return;
-			}
-			// Try to create an invitation connection
-			System.out.println("Creating invitation connection");
-			PseudoRandom r = getPseudoRandom(123);
-			d = plugin.createInvitationConnection(r, CONNECTION_TIMEOUT);
-			if (d == null) {
-				System.out.println("Connection failed");
-			} else {
-				System.out.println("Connection created");
-				sendChallengeReceiveResponse(d);
-			}
-		} finally {
-			// Stop the plugin
-			System.out.println("Stopping plugin");
-			plugin.stop();
-		}
-	}
-
-	protected static class ClientCallback implements DuplexPluginCallback {
-
-		private Settings settings = null;
-		private TransportProperties local = null;
-		private Map<ContactId, TransportProperties> remote = null;
-
-		public ClientCallback(Settings settings, TransportProperties local,
-				Map<ContactId, TransportProperties> remote) {
-			this.settings = settings;
-			this.local = local;
-			this.remote = remote;
-		}
-
-		public Settings getSettings() {
-			return settings;
-		}
-
-		public TransportProperties getLocalProperties() {
-			return local;
-		}
-
-		public Map<ContactId, TransportProperties> getRemoteProperties() {
-			return remote;
-		}
-
-		public void mergeSettings(Settings s) {
-			settings = s;
-		}
-
-		public void mergeLocalProperties(TransportProperties p) {
-			local = p;
-		}
-
-		public int showChoice(String[] options, String... message) {
-			return -1;
-		}
-
-		public boolean showConfirmationMessage(String... message) {
-			return false;
-		}
-
-		public void showMessage(String... message) {}
-
-		public void incomingConnectionCreated(DuplexTransportConnection d) {}
-
-		public void outgoingConnectionCreated(ContactId contactId,
-				DuplexTransportConnection d) {}
-
-		public void transportEnabled() {}
-
-		public void transportDisabled() {}
-	}
-}
diff --git a/briar-tests/src/org/briarproject/plugins/DuplexServerTest.java b/briar-tests/src/org/briarproject/plugins/DuplexServerTest.java
deleted file mode 100644
index ab60c9272549849861c9ddefe754ff497d3a79fc..0000000000000000000000000000000000000000
--- a/briar-tests/src/org/briarproject/plugins/DuplexServerTest.java
+++ /dev/null
@@ -1,114 +0,0 @@
-package org.briarproject.plugins;
-
-import org.briarproject.api.contact.ContactId;
-import org.briarproject.api.plugins.duplex.DuplexPluginCallback;
-import org.briarproject.api.plugins.duplex.DuplexTransportConnection;
-import org.briarproject.api.properties.TransportProperties;
-import org.briarproject.api.settings.Settings;
-
-import java.util.Map;
-import java.util.concurrent.CountDownLatch;
-
-import static java.util.concurrent.TimeUnit.SECONDS;
-import static org.briarproject.api.invitation.InvitationConstants.CONNECTION_TIMEOUT;
-
-public abstract class DuplexServerTest extends DuplexTest {
-
-	protected ServerCallback callback = null;
-
-	protected void run() throws Exception {
-		assert callback != null;
-		assert plugin != null;
-		// Start the plugin
-		System.out.println("Starting plugin");
-		if (!plugin.start()) {
-			System.out.println("Plugin failed to start");
-			return;
-		}
-		try {
-			// Wait for a connection
-			System.out.println("Waiting for connection");
-			if (!callback.latch.await(120, SECONDS)) {
-				System.out.println("No connection received");
-				return;
-			}
-			if (!plugin.supportsInvitations()) {
-				System.out.println("Skipping invitation test");
-				return;
-			}
-			// Try to create an invitation connection 
-			System.out.println("Creating invitation connection");
-			DuplexTransportConnection d = plugin.createInvitationConnection(
-					getPseudoRandom(123), CONNECTION_TIMEOUT);
-			if (d == null) {
-				System.out.println("Connection failed");
-			} else {
-				System.out.println("Connection created");
-				receiveChallengeSendResponse(d);
-			}
-		} finally {
-			// Stop the plugin
-			System.out.println("Stopping plugin");
-			plugin.stop();
-		}
-	}
-
-	protected class ServerCallback implements DuplexPluginCallback {
-
-		private final CountDownLatch latch = new CountDownLatch(1);
-
-		private Settings settings;
-		private TransportProperties local;
-		private Map<ContactId, TransportProperties> remote;
-
-		public ServerCallback(Settings settings, TransportProperties local,
-				Map<ContactId, TransportProperties> remote) {
-			this.settings = settings;
-			this.local = local;
-			this.remote = remote;
-		}
-
-		public Settings getSettings() {
-			return settings;
-		}
-
-		public TransportProperties getLocalProperties() {
-			return local;
-		}
-
-		public Map<ContactId, TransportProperties> getRemoteProperties() {
-			return remote;
-		}
-
-		public void mergeSettings(Settings s) {
-			settings = s;
-		}
-
-		public void mergeLocalProperties(TransportProperties p) {
-			local = p;
-		}
-
-		public int showChoice(String[] options, String... message) {
-			return -1;
-		}
-
-		public boolean showConfirmationMessage(String... message) {
-			return false;
-		}
-
-		public void showMessage(String... message) {}
-
-		public void incomingConnectionCreated(DuplexTransportConnection d) {
-			System.out.println("Connection received");
-			sendChallengeReceiveResponse(d);
-			latch.countDown();
-		}
-
-		public void outgoingConnectionCreated(ContactId c,
-				DuplexTransportConnection d) {}
-
-		public void transportEnabled() {}
-
-		public void transportDisabled() {}
-	}
-}
diff --git a/briar-tests/src/org/briarproject/plugins/DuplexTest.java b/briar-tests/src/org/briarproject/plugins/DuplexTest.java
deleted file mode 100644
index d20418a3f52cc06a5176522200a69d5fc675566d..0000000000000000000000000000000000000000
--- a/briar-tests/src/org/briarproject/plugins/DuplexTest.java
+++ /dev/null
@@ -1,102 +0,0 @@
-package org.briarproject.plugins;
-
-import org.briarproject.api.contact.ContactId;
-import org.briarproject.api.crypto.PseudoRandom;
-import org.briarproject.api.plugins.TransportConnectionReader;
-import org.briarproject.api.plugins.TransportConnectionWriter;
-import org.briarproject.api.plugins.duplex.DuplexPlugin;
-import org.briarproject.api.plugins.duplex.DuplexTransportConnection;
-
-import java.io.IOException;
-import java.io.PrintStream;
-import java.util.Random;
-import java.util.Scanner;
-
-abstract class DuplexTest {
-
-	protected static final String CHALLENGE = "Carrots!";
-	protected static final String RESPONSE = "Potatoes!";
-
-	protected final ContactId contactId = new ContactId(234);
-
-	protected DuplexPlugin plugin = null;
-
-	protected void sendChallengeReceiveResponse(DuplexTransportConnection d) {
-		assert plugin != null;
-		TransportConnectionReader r = d.getReader();
-		TransportConnectionWriter w = d.getWriter();
-		try {
-			PrintStream out = new PrintStream(w.getOutputStream());
-			out.println(CHALLENGE);
-			out.flush();
-			System.out.println("Sent challenge: " + CHALLENGE);
-			Scanner in = new Scanner(r.getInputStream());
-			if (in.hasNextLine()) {
-				String response = in.nextLine();
-				System.out.println("Received response: " + response);
-				if (RESPONSE.equals(response)) {
-					System.out.println("Correct response");
-				} else {
-					System.out.println("Incorrect response");
-				}
-			} else {
-				System.out.println("No response");
-			}
-			r.dispose(false, true);
-			w.dispose(false);
-		} catch (IOException e) {
-			e.printStackTrace();
-			try {
-				r.dispose(true, true);
-				w.dispose(true);
-			} catch (IOException e1) {
-				e1.printStackTrace();
-			}
-		}
-	}
-
-	protected void receiveChallengeSendResponse(DuplexTransportConnection d) {
-		assert plugin != null;
-		TransportConnectionReader r = d.getReader();
-		TransportConnectionWriter w = d.getWriter();
-		try {
-			Scanner in = new Scanner(r.getInputStream());
-			if (in.hasNextLine()) {
-				String challenge = in.nextLine();
-				System.out.println("Received challenge: " + challenge);
-				if (CHALLENGE.equals(challenge)) {
-
-					PrintStream out = new PrintStream(w.getOutputStream());
-					out.println(RESPONSE);
-					out.flush();
-					System.out.println("Sent response: " + RESPONSE);
-				} else {
-					System.out.println("Incorrect challenge");
-				}
-			} else {
-				System.out.println("No challenge");
-			}
-			r.dispose(false, true);
-			w.dispose(false);
-		} catch (IOException e) {
-			e.printStackTrace();
-			try {
-				r.dispose(true, true);
-				w.dispose(true);
-			} catch (IOException e1) {
-				e1.printStackTrace();
-			}
-		}
-	}
-
-	protected PseudoRandom getPseudoRandom(int seed) {
-		final Random random = new Random(seed);
-		return new PseudoRandom() {
-			public byte[] nextBytes(int bytes) {
-				byte[] b = new byte[bytes];
-				random.nextBytes(b);
-				return b;
-			}
-		};
-	}
-}