diff --git a/api/net/sf/briar/api/transport/batch/BatchTransportCallback.java b/api/net/sf/briar/api/transport/batch/BatchTransportCallback.java
index 8ad8b4221060af085a876ec403e8ddb73ff7c90b..44387029f696fc461a8a6a9457c14460fc919fcf 100644
--- a/api/net/sf/briar/api/transport/batch/BatchTransportCallback.java
+++ b/api/net/sf/briar/api/transport/batch/BatchTransportCallback.java
@@ -1,5 +1,7 @@
 package net.sf.briar.api.transport.batch;
 
+import net.sf.briar.api.ContactId;
+import net.sf.briar.api.TransportId;
 import net.sf.briar.api.transport.TransportCallback;
 
 /**
@@ -8,7 +10,9 @@ import net.sf.briar.api.transport.TransportCallback;
  */
 public interface BatchTransportCallback extends TransportCallback {
 
-	void readerCreated(BatchTransportReader r);
+	void readerCreated(ContactId contactId, byte[] encryptedIv,
+			BatchTransportReader r);
 
-	void writerCreated(BatchTransportWriter w);
+	void writerCreated(ContactId contactId, TransportId t, long connection,
+			BatchTransportWriter w);
 }
diff --git a/api/net/sf/briar/api/transport/batch/BatchTransportPlugin.java b/api/net/sf/briar/api/transport/batch/BatchTransportPlugin.java
index f217b4691b77745880fb8bca5c99dbf8f47c732f..09482f12bf468780631c87402eeaef3501e28ca7 100644
--- a/api/net/sf/briar/api/transport/batch/BatchTransportPlugin.java
+++ b/api/net/sf/briar/api/transport/batch/BatchTransportPlugin.java
@@ -1,5 +1,6 @@
 package net.sf.briar.api.transport.batch;
 
+import java.io.IOException;
 import java.util.Map;
 
 import net.sf.briar.api.ContactId;
@@ -23,13 +24,13 @@ public interface BatchTransportPlugin {
 	void start(Map<String, String> localProperties,
 			Map<ContactId, Map<String, String>> remoteProperties,
 			Map<String, String> config, BatchTransportCallback c)
-	throws InvalidTransportException, InvalidConfigException;
+	throws InvalidTransportException, InvalidConfigException, IOException;
 
 	/**
 	 * Stops the plugin. No further connections will be passed to the callback
 	 * after this method has returned.
 	 */
-	void stop();
+	void stop() throws IOException;
 
 	/** Updates the plugin's local transport properties. */
 	void setLocalProperties(Map<String, String> properties)
diff --git a/api/net/sf/briar/api/transport/stream/StreamTransportCallback.java b/api/net/sf/briar/api/transport/stream/StreamTransportCallback.java
index d58d0ecbc5cb9b577ae4197b4ec77fa4b4b6284f..fe741b8c2af8c4a5a0cb4db24070c6cb4da66b1a 100644
--- a/api/net/sf/briar/api/transport/stream/StreamTransportCallback.java
+++ b/api/net/sf/briar/api/transport/stream/StreamTransportCallback.java
@@ -1,5 +1,7 @@
 package net.sf.briar.api.transport.stream;
 
+import net.sf.briar.api.ContactId;
+import net.sf.briar.api.TransportId;
 import net.sf.briar.api.transport.TransportCallback;
 
 /**
@@ -8,5 +10,9 @@ import net.sf.briar.api.transport.TransportCallback;
  */
 public interface StreamTransportCallback extends TransportCallback {
 
-	void connectionCreated(StreamTransportConnection c);
+	void incomingConnectionCreated(ContactId contactId, byte[] encryptedIv,
+			StreamTransportConnection c);
+
+	void outgoingConnectionCreated(ContactId contactId, TransportId t,
+			long connection, StreamTransportConnection c);
 }
diff --git a/components/net/sf/briar/plugins/file/FilePlugin.java b/components/net/sf/briar/plugins/file/FilePlugin.java
index 93c745fb008cc5f2ca302bdb25781b65a29d8cdf..81c1a6a1284041efb5419b14fd156cb4fc5a4f73 100644
--- a/components/net/sf/briar/plugins/file/FilePlugin.java
+++ b/components/net/sf/briar/plugins/file/FilePlugin.java
@@ -1,12 +1,15 @@
 package net.sf.briar.plugins.file;
 
 import java.io.File;
+import java.io.FileInputStream;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.OutputStream;
 import java.util.Map;
 
 import net.sf.briar.api.ContactId;
+import net.sf.briar.api.db.DbException;
+import net.sf.briar.api.transport.ConnectionRecogniser;
 import net.sf.briar.api.transport.InvalidConfigException;
 import net.sf.briar.api.transport.InvalidTransportException;
 import net.sf.briar.api.transport.TransportConstants;
@@ -19,6 +22,8 @@ import org.apache.commons.io.FileSystemUtils;
 
 abstract class FilePlugin implements BatchTransportPlugin {
 
+	private final ConnectionRecogniser recogniser;
+
 	protected Map<String, String> localProperties = null;
 	protected Map<ContactId, Map<String, String>> remoteProperties = null;
 	protected Map<String, String> config = null;
@@ -28,10 +33,14 @@ abstract class FilePlugin implements BatchTransportPlugin {
 	protected abstract File chooseOutputDirectory();
 	protected abstract void writerFinished(File f);
 
+	FilePlugin(ConnectionRecogniser recogniser) {
+		this.recogniser = recogniser;
+	}
+
 	public synchronized void start(Map<String, String> localProperties,
 			Map<ContactId, Map<String, String>> remoteProperties,
 			Map<String, String> config, BatchTransportCallback callback)
-	throws InvalidTransportException, InvalidConfigException {
+	throws InvalidTransportException, InvalidConfigException, IOException {
 		if(started) throw new IllegalStateException();
 		started = true;
 		this.localProperties = localProperties;
@@ -40,7 +49,7 @@ abstract class FilePlugin implements BatchTransportPlugin {
 		this.callback = callback;
 	}
 
-	public synchronized void stop() {
+	public synchronized void stop() throws IOException {
 		if(!started) throw new IllegalStateException();
 		started = false;
 	}
@@ -106,4 +115,37 @@ abstract class FilePlugin implements BatchTransportPlugin {
 	protected long getCapacity(String path) throws IOException {
 		return FileSystemUtils.freeSpaceKb(path) * 1024L;
 	}
+
+	protected void createReaderFromFile(File f) {
+		if(!isPossibleConnectionFilename(f.getName())) return;
+		if(f.length() < TransportConstants.MIN_CONNECTION_LENGTH) return;
+		try {
+			FileInputStream in = new FileInputStream(f);
+			byte[] iv = new byte[TransportConstants.IV_LENGTH];
+			int offset = 0;
+			while(offset < iv.length) {
+				int read = in.read(iv, offset, iv.length - offset);
+				if(read == -1) break;
+				offset += read;
+			}
+			ContactId c = recogniser.acceptConnection(iv);
+			if(c == null) {
+				// Nobody there
+				in.close();
+				return;
+			}
+			FileTransportReader reader = new FileTransportReader(f, in);
+			callback.readerCreated(c, iv, reader);
+		} catch(DbException e) {
+			// FIXME: At least log it
+			return;
+		} catch(IOException e) {
+			// FIXME: At least log it
+			return;
+		}
+	}
+
+	protected boolean isPossibleConnectionFilename(String filename) {
+		return filename.toLowerCase().matches("[a-z]{8}\\.dat");
+	}
 }
diff --git a/components/net/sf/briar/plugins/file/FileTransportReader.java b/components/net/sf/briar/plugins/file/FileTransportReader.java
new file mode 100644
index 0000000000000000000000000000000000000000..50f6b55574aacbeb5c370d629cf6187ad1326611
--- /dev/null
+++ b/components/net/sf/briar/plugins/file/FileTransportReader.java
@@ -0,0 +1,30 @@
+package net.sf.briar.plugins.file;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+
+import net.sf.briar.api.transport.batch.BatchTransportReader;
+
+class FileTransportReader implements BatchTransportReader {
+
+	private final File file;
+	private final InputStream in;
+
+	private boolean streamInUse = false;
+
+	FileTransportReader(File file, InputStream in) {
+		this.file = file;
+		this.in = in;
+	}
+
+	public InputStream getInputStream() {
+		streamInUse = true;
+		return in;
+	}
+
+	public void dispose() throws IOException {
+		if(streamInUse) in.close();
+		file.delete();
+	}
+}
diff --git a/components/net/sf/briar/plugins/file/PollingRemovableDriveMonitor.java b/components/net/sf/briar/plugins/file/PollingRemovableDriveMonitor.java
index f693aa6a9985dfb1c1c5d220406d3bd4877709a7..853899befc1d5855ff29146878f21192840c5567 100644
--- a/components/net/sf/briar/plugins/file/PollingRemovableDriveMonitor.java
+++ b/components/net/sf/briar/plugins/file/PollingRemovableDriveMonitor.java
@@ -2,52 +2,39 @@ package net.sf.briar.plugins.file;
 
 import java.io.File;
 import java.io.IOException;
-import java.util.LinkedList;
 import java.util.List;
 
 class PollingRemovableDriveMonitor implements RemovableDriveMonitor, Runnable {
 
 	private final RemovableDriveFinder finder;
 	private final long pollingInterval;
-	private final LinkedList<File> inserted;
-	private final LinkedList<IOException> exceptions;
-	private final Object pollingLock;
+	private final Object pollingLock = new Object();
 
-	private boolean started = false, stopped = false;
-	private Thread pollingThread = null;
+	private volatile boolean running = false;
+	private volatile Callback callback = null;
+	private volatile IOException exception = null;
 
 	public PollingRemovableDriveMonitor(RemovableDriveFinder finder,
 			long pollingInterval) {
 		this.finder = finder;
 		this.pollingInterval = pollingInterval;
-		inserted = new LinkedList<File>();
-		exceptions = new LinkedList<IOException>();
-		pollingLock = new Object();
 	}
 
-	public synchronized void start() throws IOException {
-		if(started || stopped) throw new IllegalStateException();
-		started = true;
-		pollingThread = new Thread(this);
-		pollingThread.start();
-	}
-
-	public synchronized File waitForInsertion() throws IOException {
-		if(!started || stopped) throw new IllegalStateException();
-		if(!exceptions.isEmpty()) throw exceptions.poll();
-		while(inserted.isEmpty()) {
-			try {
-				wait();
-			} catch(InterruptedException ignored) {}
-			if(!exceptions.isEmpty()) throw exceptions.poll();
-		}
-		return inserted.poll();
+	public synchronized void start(Callback callback) throws IOException {
+		if(running) throw new IllegalStateException();
+		running = true;
+		this.callback = callback;
+		new Thread(this).start();
 	}
 
 	public synchronized void stop() throws IOException {
-		if(!started || stopped) throw new IllegalStateException();
-		if(!exceptions.isEmpty()) throw exceptions.poll();
-		stopped = true;
+		if(!running) throw new IllegalStateException();
+		running = false;
+		if(exception != null) {
+			IOException e = exception;
+			exception = null;
+			throw e;
+		}
 		synchronized(pollingLock) {
 			pollingLock.notifyAll();
 		}
@@ -56,34 +43,21 @@ class PollingRemovableDriveMonitor implements RemovableDriveMonitor, Runnable {
 	public void run() {
 		try {
 			List<File> drives = finder.findRemovableDrives();
-			while(true) {
-				synchronized(this) {
-					if(stopped) return;
-				}
+			while(running) {
 				synchronized(pollingLock) {
 					try {
 						pollingLock.wait(pollingInterval);
 					} catch(InterruptedException ignored) {}
 				}
-				synchronized(this) {
-					if(stopped) return;
-				}
+				if(!running) return;
 				List<File> newDrives = finder.findRemovableDrives();
 				for(File f : newDrives) {
-					if(!drives.contains(f)) {
-						synchronized(this) {
-							inserted.add(f);
-							notifyAll();
-						}
-					}
+					if(!drives.contains(f)) callback.driveInserted(f);
 				}
 				drives = newDrives;
 			}
 		} catch(IOException e) {
-			synchronized(this) {
-				exceptions.add(e);
-				notifyAll();
-			}
+			exception = e;
 		}
 	}
 }
diff --git a/components/net/sf/briar/plugins/file/RemovableDriveMonitor.java b/components/net/sf/briar/plugins/file/RemovableDriveMonitor.java
index 3ce0cc5f27b716edfb44e00ca206b28f9994fcd9..ce9aeeca1633018dd2922295e297712c6529a941 100644
--- a/components/net/sf/briar/plugins/file/RemovableDriveMonitor.java
+++ b/components/net/sf/briar/plugins/file/RemovableDriveMonitor.java
@@ -5,9 +5,12 @@ import java.io.IOException;
 
 interface RemovableDriveMonitor {
 
-	void start() throws IOException;
-
-	File waitForInsertion() throws IOException;
+	void start(Callback c) throws IOException;
 
 	void stop() throws IOException;
+
+	interface Callback {
+
+		void driveInserted(File f);
+	}
 }
diff --git a/components/net/sf/briar/plugins/file/RemovableDrivePlugin.java b/components/net/sf/briar/plugins/file/RemovableDrivePlugin.java
index 758c3e9e8e79561b092fae857a8819dd386b0cf5..bd959c219c598befbd268e110e3152c0b1440141 100644
--- a/components/net/sf/briar/plugins/file/RemovableDrivePlugin.java
+++ b/components/net/sf/briar/plugins/file/RemovableDrivePlugin.java
@@ -3,25 +3,51 @@ package net.sf.briar.plugins.file;
 import java.io.File;
 import java.io.IOException;
 import java.util.List;
+import java.util.Map;
 
+import net.sf.briar.api.ContactId;
 import net.sf.briar.api.TransportId;
+import net.sf.briar.api.transport.ConnectionRecogniser;
+import net.sf.briar.api.transport.InvalidConfigException;
+import net.sf.briar.api.transport.InvalidTransportException;
+import net.sf.briar.api.transport.batch.BatchTransportCallback;
 
-class RemovableDrivePlugin extends FilePlugin {
+class RemovableDrivePlugin extends FilePlugin
+implements RemovableDriveMonitor.Callback {
 
 	public static final int TRANSPORT_ID = 0;
 
 	private static final TransportId id = new TransportId(TRANSPORT_ID);
 
 	private final RemovableDriveFinder finder;
+	private final RemovableDriveMonitor monitor;
 
-	RemovableDrivePlugin(RemovableDriveFinder finder) {
+	RemovableDrivePlugin(ConnectionRecogniser recogniser,
+			RemovableDriveFinder finder, RemovableDriveMonitor monitor) {
+		super(recogniser);
 		this.finder = finder;
+		this.monitor = monitor;
 	}
 
 	public TransportId getId() {
 		return id;
 	}
 
+	@Override
+	public void start(Map<String, String> localProperties,
+			Map<ContactId, Map<String, String>> remoteProperties,
+			Map<String, String> config, BatchTransportCallback callback)
+	throws InvalidTransportException, InvalidConfigException, IOException {
+		super.start(localProperties, remoteProperties, config, callback);
+		monitor.start(this);
+	}
+
+	@Override
+	public void stop() throws IOException {
+		super.stop();
+		monitor.stop();
+	}
+
 	@Override
 	protected File chooseOutputDirectory() {
 		try {
@@ -43,4 +69,9 @@ class RemovableDrivePlugin extends FilePlugin {
 	protected void writerFinished(File f) {
 		callback.showMessage("REMOVABLE_DRIVE_WRITE_FINISHED");
 	}
+
+	public void driveInserted(File root) {
+		File[] files = root.listFiles();
+		if(files != null) for(File f : files) createReaderFromFile(f);
+	}
 }
diff --git a/components/net/sf/briar/plugins/file/UnixRemovableDriveMonitor.java b/components/net/sf/briar/plugins/file/UnixRemovableDriveMonitor.java
index 3a211cc509063e41839fc1d5c73195a2eac4ed1b..c4daf247094c9c0d2fd3fe9fcdcdef41e537e080 100644
--- a/components/net/sf/briar/plugins/file/UnixRemovableDriveMonitor.java
+++ b/components/net/sf/briar/plugins/file/UnixRemovableDriveMonitor.java
@@ -3,7 +3,6 @@ package net.sf.briar.plugins.file;
 import java.io.File;
 import java.io.IOException;
 import java.util.ArrayList;
-import java.util.LinkedList;
 import java.util.List;
 
 import net.contentobjects.jnotify.JNotify;
@@ -13,15 +12,16 @@ abstract class UnixRemovableDriveMonitor implements RemovableDriveMonitor,
 JNotifyListener {
 
 	private final List<Integer> watches = new ArrayList<Integer>();
-	private final LinkedList<File> inserted = new LinkedList<File>();
 
-	private boolean started = false, stopped = false;
+	private boolean started = false;
+	private Callback callback = null;
 
 	protected abstract String[] getPathsToWatch();
 
-	public synchronized void start() throws IOException {
-		if(started || stopped) throw new IllegalStateException();
+	public synchronized void start(Callback callback) throws IOException {
+		if(started) throw new IllegalStateException();
 		started = true;
+		this.callback = callback;
 		int mask = JNotify.FILE_CREATED;
 		for(String path : getPathsToWatch()) {
 			if(new File(path).exists())
@@ -29,26 +29,18 @@ JNotifyListener {
 		}
 	}
 
-	public synchronized File waitForInsertion() throws IOException {
-		if(!started || stopped) throw new IllegalStateException();
-		while(inserted.isEmpty()) {
-			try {
-				wait();
-			} catch(InterruptedException ignored) {}
-		}
-		return inserted.poll();
-	}
-
 	public synchronized void stop() throws IOException {
-		if(!started || stopped) throw new IllegalStateException();
-		stopped = true;
+		if(!started) throw new IllegalStateException();
+		started = false;
+		callback = null;
 		for(Integer w : watches) JNotify.removeWatch(w);
+		watches.clear();
 	}
 
 	public void fileCreated(int wd, String rootPath, String name) {
 		synchronized(this) {
-			inserted.add(new File(rootPath + "/" + name));
-			notifyAll();
+			if(!started) throw new IllegalStateException();
+			callback.driveInserted(new File(rootPath + "/" + name));
 		}
 	}
 
diff --git a/test/net/sf/briar/plugins/file/PollingRemovableDriveMonitorTest.java b/test/net/sf/briar/plugins/file/PollingRemovableDriveMonitorTest.java
index b559886e39b07fd6d32770dcb6b6a3a335a4602a..1d64cbc5c9eb2c0a1bf348122ccc8f6522c91ec0 100644
--- a/test/net/sf/briar/plugins/file/PollingRemovableDriveMonitorTest.java
+++ b/test/net/sf/briar/plugins/file/PollingRemovableDriveMonitorTest.java
@@ -10,6 +10,8 @@ import java.util.concurrent.TimeUnit;
 
 import junit.framework.TestCase;
 
+import net.sf.briar.plugins.file.RemovableDriveMonitor.Callback;
+
 import org.jmock.Expectations;
 import org.jmock.Mockery;
 import org.junit.Test;
@@ -18,15 +20,13 @@ public class PollingRemovableDriveMonitorTest extends TestCase {
 
 	@Test
 	public void testOneCallbackPerFile() throws Exception {
-		final CountDownLatch latch = new CountDownLatch(1);
-		final List<File> detected = new ArrayList<File>();
 		final File file1 = new File("foo");
 		final File file2 = new File("bar");
+		// Create a finder that returns no files the first time, then two files
 		final List<File> noDrives = Collections.emptyList();
 		final List<File> twoDrives = new ArrayList<File>();
 		twoDrives.add(file1);
 		twoDrives.add(file2);
-		// Create a finder that returns no files the first time, then two files
 		Mockery context = new Mockery();
 		final RemovableDriveFinder finder =
 			context.mock(RemovableDriveFinder.class);
@@ -36,24 +36,21 @@ public class PollingRemovableDriveMonitorTest extends TestCase {
 			oneOf(finder).findRemovableDrives();
 			will(returnValue(twoDrives));
 		}});
-		// Create a monitor that will wait for two files before stopping
+		// Create a callback that will wait for two files before stopping
+		final List<File> detected = new ArrayList<File>();
+		final CountDownLatch latch = new CountDownLatch(2);
+		final Callback callback = new Callback() {
+			public void driveInserted(File f) {
+				detected.add(f);
+				latch.countDown();
+			}
+		};
+		// Create the monitor and start it
 		final RemovableDriveMonitor monitor =
 			new PollingRemovableDriveMonitor(finder, 10);
-		monitor.start();
-		new Thread() {
-			@Override
-			public void run() {
-				try {
-					detected.add(monitor.waitForInsertion());
-					detected.add(monitor.waitForInsertion());
-					latch.countDown();
-				} catch(IOException e) {
-					fail();
-				}
-			}
-		}.start();
+		monitor.start(callback);
 		// Wait for the monitor to detect the files
-		assertTrue(latch.await(2, TimeUnit.SECONDS));
+		assertTrue(latch.await(1, TimeUnit.SECONDS));
 		monitor.stop();
 		// Check that both files were detected
 		assertEquals(2, detected.size());
@@ -63,32 +60,6 @@ public class PollingRemovableDriveMonitorTest extends TestCase {
 		context.assertIsSatisfied();
 	}
 
-	@Test
-	public void testExceptionRethrownWhenWaiting() throws Exception {
-		final List<File> noDrives = Collections.emptyList();
-		// Create a finder that throws an exception the second time it's polled
-		Mockery context = new Mockery();
-		final RemovableDriveFinder finder =
-			context.mock(RemovableDriveFinder.class);
-		context.checking(new Expectations() {{
-			oneOf(finder).findRemovableDrives();
-			will(returnValue(noDrives));
-			oneOf(finder).findRemovableDrives();
-			will(throwException(new IOException()));
-		}});
-		// The monitor should rethrow the exception when it waits
-		final RemovableDriveMonitor monitor =
-			new PollingRemovableDriveMonitor(finder, 10);
-		monitor.start();
-		try {
-			monitor.waitForInsertion();
-			fail();
-		} catch(IOException expected) {}
-		// The exception shouldn't be thrown again
-		monitor.stop();
-		context.assertIsSatisfied();
-	}
-
 	@Test
 	public void testExceptionRethrownWhenStopping() throws Exception {
 		final List<File> noDrives = Collections.emptyList();
@@ -102,11 +73,12 @@ public class PollingRemovableDriveMonitorTest extends TestCase {
 			oneOf(finder).findRemovableDrives();
 			will(throwException(new IOException()));
 		}});
-		// The monitor should rethrow the exception when it stops
+		// Create the monitor, start it, and give it some time to run
 		final RemovableDriveMonitor monitor =
 			new PollingRemovableDriveMonitor(finder, 10);
-		monitor.start();
+		monitor.start(null);
 		Thread.sleep(50);
+		// The monitor should rethrow the exception when it stops
 		try {
 			monitor.stop();
 			fail();
diff --git a/test/net/sf/briar/plugins/file/RemovableDrivePluginTest.java b/test/net/sf/briar/plugins/file/RemovableDrivePluginTest.java
index bf1a00077e283583c1f7fc4bd54f68336bdef9af..54a49c30d69094a1f9148ce28d515becb20d8973 100644
--- a/test/net/sf/briar/plugins/file/RemovableDrivePluginTest.java
+++ b/test/net/sf/briar/plugins/file/RemovableDrivePluginTest.java
@@ -9,8 +9,10 @@ import java.util.List;
 import junit.framework.TestCase;
 import net.sf.briar.TestUtils;
 import net.sf.briar.api.ContactId;
+import net.sf.briar.api.transport.ConnectionRecogniser;
 import net.sf.briar.api.transport.batch.BatchTransportCallback;
 import net.sf.briar.api.transport.batch.BatchTransportWriter;
+import net.sf.briar.plugins.file.RemovableDriveMonitor.Callback;
 
 import org.jmock.Expectations;
 import org.jmock.Mockery;
@@ -31,9 +33,14 @@ public class RemovableDrivePluginTest extends TestCase {
 	@Test
 	public void testGetId() {
 		Mockery context = new Mockery();
+		final ConnectionRecogniser recogniser =
+			context.mock(ConnectionRecogniser.class);
 		final RemovableDriveFinder finder =
 			context.mock(RemovableDriveFinder.class);
-		RemovableDrivePlugin plugin = new RemovableDrivePlugin(finder);
+		final RemovableDriveMonitor monitor =
+			context.mock(RemovableDriveMonitor.class);
+		RemovableDrivePlugin plugin = new RemovableDrivePlugin(recogniser,
+				finder, monitor);
 
 		assertEquals(RemovableDrivePlugin.TRANSPORT_ID,
 				plugin.getId().getInt());
@@ -46,15 +53,23 @@ public class RemovableDrivePluginTest extends TestCase {
 		final List<File> drives = Collections.emptyList();
 
 		Mockery context = new Mockery();
+		final ConnectionRecogniser recogniser =
+			context.mock(ConnectionRecogniser.class);
 		final RemovableDriveFinder finder =
 			context.mock(RemovableDriveFinder.class);
+		final RemovableDriveMonitor monitor =
+			context.mock(RemovableDriveMonitor.class);
 		final BatchTransportCallback callback =
 			context.mock(BatchTransportCallback.class);
+
 		context.checking(new Expectations() {{
+			oneOf(monitor).start(with(any(Callback.class)));
 			oneOf(finder).findRemovableDrives();
 			will(returnValue(drives));
 		}});
-		RemovableDrivePlugin plugin = new RemovableDrivePlugin(finder);
+
+		RemovableDrivePlugin plugin = new RemovableDrivePlugin(recogniser,
+				finder, monitor);
 		plugin.start(null, null, null, callback);
 
 		assertNull(plugin.createWriter(contactId));
@@ -71,18 +86,26 @@ public class RemovableDrivePluginTest extends TestCase {
 		drives.add(drive2);
 
 		Mockery context = new Mockery();
+		final ConnectionRecogniser recogniser =
+			context.mock(ConnectionRecogniser.class);
 		final RemovableDriveFinder finder =
 			context.mock(RemovableDriveFinder.class);
+		final RemovableDriveMonitor monitor =
+			context.mock(RemovableDriveMonitor.class);
 		final BatchTransportCallback callback =
 			context.mock(BatchTransportCallback.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(finder);
+
+		RemovableDrivePlugin plugin = new RemovableDrivePlugin(recogniser,
+				finder, monitor);
 		plugin.start(null, null, null, callback);
 
 		assertNull(plugin.createWriter(contactId));
@@ -101,18 +124,26 @@ public class RemovableDrivePluginTest extends TestCase {
 		drives.add(drive2);
 
 		Mockery context = new Mockery();
+		final ConnectionRecogniser recogniser =
+			context.mock(ConnectionRecogniser.class);
 		final RemovableDriveFinder finder =
 			context.mock(RemovableDriveFinder.class);
+		final RemovableDriveMonitor monitor =
+			context.mock(RemovableDriveMonitor.class);
 		final BatchTransportCallback callback =
 			context.mock(BatchTransportCallback.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(finder);
+
+		RemovableDrivePlugin plugin = new RemovableDrivePlugin(recogniser,
+				finder, monitor);
 		plugin.start(null, null, null, callback);
 
 		assertNull(plugin.createWriter(contactId));
@@ -133,18 +164,26 @@ public class RemovableDrivePluginTest extends TestCase {
 		assertTrue(drive1.createNewFile());
 
 		Mockery context = new Mockery();
+		final ConnectionRecogniser recogniser =
+			context.mock(ConnectionRecogniser.class);
 		final RemovableDriveFinder finder =
 			context.mock(RemovableDriveFinder.class);
+		final RemovableDriveMonitor monitor =
+			context.mock(RemovableDriveMonitor.class);
 		final BatchTransportCallback callback =
 			context.mock(BatchTransportCallback.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(finder);
+
+		RemovableDrivePlugin plugin = new RemovableDrivePlugin(recogniser,
+				finder, monitor);
 		plugin.start(null, null, null, callback);
 
 		assertNull(plugin.createWriter(contactId));
@@ -165,18 +204,26 @@ public class RemovableDrivePluginTest extends TestCase {
 		assertTrue(drive1.mkdir());
 
 		Mockery context = new Mockery();
+		final ConnectionRecogniser recogniser =
+			context.mock(ConnectionRecogniser.class);
 		final RemovableDriveFinder finder =
 			context.mock(RemovableDriveFinder.class);
+		final RemovableDriveMonitor monitor =
+			context.mock(RemovableDriveMonitor.class);
 		final BatchTransportCallback callback =
 			context.mock(BatchTransportCallback.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(finder);
+
+		RemovableDrivePlugin plugin = new RemovableDrivePlugin(recogniser,
+				finder, monitor);
 		plugin.start(null, null, null, callback);
 
 		assertNotNull(plugin.createWriter(contactId));
@@ -200,11 +247,17 @@ public class RemovableDrivePluginTest extends TestCase {
 		assertTrue(drive1.mkdir());
 
 		Mockery context = new Mockery();
+		final ConnectionRecogniser recogniser =
+			context.mock(ConnectionRecogniser.class);
 		final RemovableDriveFinder finder =
 			context.mock(RemovableDriveFinder.class);
+		final RemovableDriveMonitor monitor =
+			context.mock(RemovableDriveMonitor.class);
 		final BatchTransportCallback callback =
 			context.mock(BatchTransportCallback.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)),
@@ -212,7 +265,9 @@ public class RemovableDrivePluginTest extends TestCase {
 			will(returnValue(0)); // The user chose drive1
 			oneOf(callback).showMessage(with(any(String.class)));
 		}});
-		RemovableDrivePlugin plugin = new RemovableDrivePlugin(finder);
+
+		RemovableDrivePlugin plugin = new RemovableDrivePlugin(recogniser,
+				finder, monitor);
 		plugin.start(null, null, null, callback);
 
 		BatchTransportWriter writer = plugin.createWriter(contactId);
diff --git a/test/net/sf/briar/plugins/file/UnixRemovableDriveMonitorTest.java b/test/net/sf/briar/plugins/file/UnixRemovableDriveMonitorTest.java
index 2c2dd6047a17d30096dfab431831daa56507e7d2..1ed814d038dfb03eccebcb07ddac128616d96b44 100644
--- a/test/net/sf/briar/plugins/file/UnixRemovableDriveMonitorTest.java
+++ b/test/net/sf/briar/plugins/file/UnixRemovableDriveMonitorTest.java
@@ -1,7 +1,6 @@
 package net.sf.briar.plugins.file;
 
 import java.io.File;
-import java.io.IOException;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.CountDownLatch;
@@ -9,6 +8,7 @@ import java.util.concurrent.TimeUnit;
 
 import junit.framework.TestCase;
 import net.sf.briar.TestUtils;
+import net.sf.briar.plugins.file.RemovableDriveMonitor.Callback;
 
 import org.junit.After;
 import org.junit.Before;
@@ -27,36 +27,31 @@ public class UnixRemovableDriveMonitorTest extends TestCase {
 	public void testNonexistentDir() throws Exception {
 		File doesNotExist = new File(testDir, "doesNotExist");
 		RemovableDriveMonitor monitor = createMonitor(doesNotExist);
-		monitor.start();
+		monitor.start(null);
 		monitor.stop();
 	}
 
 	@Test
 	public void testOneCallbackPerFile() throws Exception {
-		final CountDownLatch latch = new CountDownLatch(1);
+		// Create a callback that will wait for two files before stopping
 		final List<File> detected = new ArrayList<File>();
-		// Create a monitor that will wait for two files before stopping
-		final RemovableDriveMonitor monitor = createMonitor(testDir);
-		monitor.start();
-		new Thread() {
-			@Override
-			public void run() {
-				try {
-					detected.add(monitor.waitForInsertion());
-					detected.add(monitor.waitForInsertion());
-					latch.countDown();
-				} catch(IOException e) {
-					fail();
-				}
+		final CountDownLatch latch = new CountDownLatch(2);
+		final Callback callback = new Callback() {
+			public void driveInserted(File f) {
+				detected.add(f);
+				latch.countDown();
 			}
-		}.start();
+		};
+		// 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(2, TimeUnit.SECONDS));
+		assertTrue(latch.await(1, TimeUnit.SECONDS));
 		monitor.stop();
 		// Check that both files were detected
 		assertEquals(2, detected.size());