diff --git a/briar-api/src/org/briarproject/api/plugins/PluginManager.java b/briar-api/src/org/briarproject/api/plugins/PluginManager.java
index 838c66ff86e1ac74a41c507f9d48b6774038c247..18e71c2ec2e75ae4790c7e1580b7b95e75b1700c 100644
--- a/briar-api/src/org/briarproject/api/plugins/PluginManager.java
+++ b/briar-api/src/org/briarproject/api/plugins/PluginManager.java
@@ -2,24 +2,39 @@ package org.briarproject.api.plugins;
 
 import org.briarproject.api.TransportId;
 import org.briarproject.api.plugins.duplex.DuplexPlugin;
+import org.briarproject.api.plugins.simplex.SimplexPlugin;
 
 import java.util.Collection;
 
 /**
- * Responsible for starting transport plugins at startup, stopping them at
- * shutdown, and providing access to plugins for exchanging invitations.
+ * Responsible for starting transport plugins at startup and stopping them at
+ * shutdown.
  */
 public interface PluginManager {
 
 	/**
 	 * Returns the plugin for the given transport, or null if no such plugin
-	 * is running.
+	 * has been created.
 	 */
 	Plugin getPlugin(TransportId t);
 
-	/** Returns any running duplex plugins that support invitations. */
+	/**
+	 * Returns any simplex plugins that have been created.
+	 */
+	Collection<SimplexPlugin> getSimplexPlugins();
+
+	/**
+	 * Returns any duplex plugins that have been created.
+	 */
+	Collection<DuplexPlugin> getDuplexPlugins();
+
+	/**
+	 * Returns any duplex plugins that support invitations.
+	 */
 	Collection<DuplexPlugin> getInvitationPlugins();
 
-	/** Returns any running duplex plugins that support key agreement. */
+	/**
+	 * Returns any duplex plugins that support key agreement.
+	 */
 	Collection<DuplexPlugin> getKeyAgreementPlugins();
 }
diff --git a/briar-core/src/org/briarproject/plugins/PluginManagerImpl.java b/briar-core/src/org/briarproject/plugins/PluginManagerImpl.java
index 00f037a8c3376955f6a2d0eea1ff777255d33c3e..ebd6f6c017678c9843d60cc4b23fe1afbb77fb48 100644
--- a/briar-core/src/org/briarproject/plugins/PluginManagerImpl.java
+++ b/briar-core/src/org/briarproject/plugins/PluginManagerImpl.java
@@ -3,18 +3,13 @@ package org.briarproject.plugins;
 import org.briarproject.api.TransportId;
 import org.briarproject.api.contact.ContactId;
 import org.briarproject.api.db.DbException;
-import org.briarproject.api.event.ConnectionClosedEvent;
-import org.briarproject.api.event.ContactStatusChangedEvent;
-import org.briarproject.api.event.Event;
 import org.briarproject.api.event.EventBus;
-import org.briarproject.api.event.EventListener;
 import org.briarproject.api.event.TransportDisabledEvent;
 import org.briarproject.api.event.TransportEnabledEvent;
 import org.briarproject.api.lifecycle.IoExecutor;
 import org.briarproject.api.lifecycle.Service;
 import org.briarproject.api.lifecycle.ServiceException;
 import org.briarproject.api.plugins.ConnectionManager;
-import org.briarproject.api.plugins.ConnectionRegistry;
 import org.briarproject.api.plugins.Plugin;
 import org.briarproject.api.plugins.PluginCallback;
 import org.briarproject.api.plugins.PluginConfig;
@@ -51,7 +46,7 @@ import javax.inject.Inject;
 import static java.util.logging.Level.INFO;
 import static java.util.logging.Level.WARNING;
 
-class PluginManagerImpl implements PluginManager, Service, EventListener {
+class PluginManagerImpl implements PluginManager, Service {
 
 	private static final Logger LOG =
 			Logger.getLogger(PluginManagerImpl.class.getName());
@@ -59,9 +54,7 @@ class PluginManagerImpl implements PluginManager, Service, EventListener {
 	private final Executor ioExecutor;
 	private final EventBus eventBus;
 	private final PluginConfig pluginConfig;
-	private final Poller poller;
 	private final ConnectionManager connectionManager;
-	private final ConnectionRegistry connectionRegistry;
 	private final SettingsManager settingsManager;
 	private final TransportPropertyManager transportPropertyManager;
 	private final UiCallback uiCallback;
@@ -71,18 +64,14 @@ class PluginManagerImpl implements PluginManager, Service, EventListener {
 
 	@Inject
 	PluginManagerImpl(@IoExecutor Executor ioExecutor, EventBus eventBus,
-			PluginConfig pluginConfig, Poller poller,
-			ConnectionManager connectionManager,
-			ConnectionRegistry connectionRegistry,
+			PluginConfig pluginConfig, ConnectionManager connectionManager,
 			SettingsManager settingsManager,
 			TransportPropertyManager transportPropertyManager,
 			UiCallback uiCallback) {
 		this.ioExecutor = ioExecutor;
 		this.eventBus = eventBus;
 		this.pluginConfig = pluginConfig;
-		this.poller = poller;
 		this.connectionManager = connectionManager;
-		this.connectionRegistry = connectionRegistry;
 		this.settingsManager = settingsManager;
 		this.transportPropertyManager = transportPropertyManager;
 		this.uiCallback = uiCallback;
@@ -93,36 +82,55 @@ class PluginManagerImpl implements PluginManager, Service, EventListener {
 
 	@Override
 	public void startService() throws ServiceException {
+		Collection<SimplexPluginFactory> simplexFactories =
+				pluginConfig.getSimplexFactories();
+		Collection<DuplexPluginFactory> duplexFactories =
+				pluginConfig.getDuplexFactories();
+		int numPlugins = simplexFactories.size() + duplexFactories.size();
+		CountDownLatch latch = new CountDownLatch(numPlugins);
 		// Instantiate and start the simplex plugins
 		LOG.info("Starting simplex plugins");
-		Collection<SimplexPluginFactory> sFactories =
-				pluginConfig.getSimplexFactories();
-		final CountDownLatch sLatch = new CountDownLatch(sFactories.size());
-		for (SimplexPluginFactory factory : sFactories)
-			ioExecutor.execute(new SimplexPluginStarter(factory, sLatch));
+		for (SimplexPluginFactory f : simplexFactories) {
+			TransportId t = f.getId();
+			SimplexPluginCallback c = new SimplexCallback(t);
+			SimplexPlugin s = f.createPlugin(c);
+			if (s == null) {
+				if (LOG.isLoggable(WARNING))
+					LOG.warning("Could not create plugin for " + t);
+				latch.countDown();
+			} else {
+				plugins.put(t, s);
+				simplexPlugins.add(s);
+				ioExecutor.execute(new PluginStarter(s, latch));
+			}
+		}
 		// Instantiate and start the duplex plugins
 		LOG.info("Starting duplex plugins");
-		Collection<DuplexPluginFactory> dFactories =
-				pluginConfig.getDuplexFactories();
-		final CountDownLatch dLatch = new CountDownLatch(dFactories.size());
-		for (DuplexPluginFactory factory : dFactories)
-			ioExecutor.execute(new DuplexPluginStarter(factory, dLatch));
-		// Wait for the plugins to start
+		for (DuplexPluginFactory f : duplexFactories) {
+			TransportId t = f.getId();
+			DuplexPluginCallback c = new DuplexCallback(t);
+			DuplexPlugin d = f.createPlugin(c);
+			if (d == null) {
+				if (LOG.isLoggable(WARNING))
+					LOG.warning("Could not create plugin for " + t);
+				latch.countDown();
+			} else {
+				plugins.put(t, d);
+				duplexPlugins.add(d);
+				ioExecutor.execute(new PluginStarter(d, latch));
+			}
+		}
+		// Wait for all the plugins to start
 		try {
-			sLatch.await();
-			dLatch.await();
+			latch.await();
 		} catch (InterruptedException e) {
 			throw new ServiceException(e);
 		}
-		// Listen for events
-		eventBus.addListener(this);
 	}
 
 	@Override
 	public void stopService() throws ServiceException {
-		// Stop listening for events
-		eventBus.removeListener(this);
-		final CountDownLatch latch = new CountDownLatch(plugins.size());
+		CountDownLatch latch = new CountDownLatch(plugins.size());
 		// Stop the simplex plugins
 		LOG.info("Stopping simplex plugins");
 		for (SimplexPlugin plugin : simplexPlugins)
@@ -144,6 +152,18 @@ class PluginManagerImpl implements PluginManager, Service, EventListener {
 		return plugins.get(t);
 	}
 
+	@Override
+	public Collection<SimplexPlugin> getSimplexPlugins() {
+		List<SimplexPlugin> copy = new ArrayList<SimplexPlugin>(simplexPlugins);
+		return Collections.unmodifiableList(copy);
+	}
+
+	@Override
+	public Collection<DuplexPlugin> getDuplexPlugins() {
+		List<DuplexPlugin> copy = new ArrayList<DuplexPlugin>(duplexPlugins);
+		return Collections.unmodifiableList(copy);
+	}
+
 	@Override
 	public Collection<DuplexPlugin> getInvitationPlugins() {
 		List<DuplexPlugin> supported = new ArrayList<DuplexPlugin>();
@@ -160,149 +180,24 @@ class PluginManagerImpl implements PluginManager, Service, EventListener {
 		return Collections.unmodifiableList(supported);
 	}
 
-	@Override
-	public void eventOccurred(Event e) {
-		if (e instanceof ContactStatusChangedEvent) {
-			ContactStatusChangedEvent c = (ContactStatusChangedEvent) e;
-			if (c.isActive()) {
-				// Connect to the newly activated contact
-				connectToContact(c.getContactId());
-			}
-		} else if (e instanceof ConnectionClosedEvent) {
-			ConnectionClosedEvent c = (ConnectionClosedEvent) e;
-			if (!c.isIncoming()) {
-				// Connect to the disconnected contact
-				connectToContact(c.getContactId(), c.getTransportId());
-			}
-		}
-	}
-
-	private void connectToContact(ContactId c) {
-		for (SimplexPlugin s : simplexPlugins)
-			if (s.shouldPoll()) connectToContact(c, s);
-		for (DuplexPlugin d : duplexPlugins)
-			if (d.shouldPoll()) connectToContact(c, d);
-	}
+	private class PluginStarter implements Runnable {
 
-	private void connectToContact(ContactId c, TransportId t) {
-		Plugin p = plugins.get(t);
-		if (p instanceof SimplexPlugin && p.shouldPoll())
-			connectToContact(c, (SimplexPlugin) p);
-		else if (p instanceof DuplexPlugin && p.shouldPoll())
-			connectToContact(c, (DuplexPlugin) p);
-	}
-
-	private void connectToContact(final ContactId c, final SimplexPlugin p) {
-		ioExecutor.execute(new Runnable() {
-			@Override
-			public void run() {
-				TransportId t = p.getId();
-				if (!connectionRegistry.isConnected(c, t)) {
-					TransportConnectionWriter w = p.createWriter(c);
-					if (w != null)
-						connectionManager.manageOutgoingConnection(c, t, w);
-				}
-			}
-		});
-	}
-
-	private void connectToContact(final ContactId c, final DuplexPlugin p) {
-		ioExecutor.execute(new Runnable() {
-			@Override
-			public void run() {
-				TransportId t = p.getId();
-				if (!connectionRegistry.isConnected(c, t)) {
-					DuplexTransportConnection d = p.createConnection(c);
-					if (d != null)
-						connectionManager.manageOutgoingConnection(c, t, d);
-				}
-			}
-		});
-	}
-
-	private class SimplexPluginStarter implements Runnable {
-
-		private final SimplexPluginFactory factory;
-		private final CountDownLatch latch;
-
-		private SimplexPluginStarter(SimplexPluginFactory factory,
-				CountDownLatch latch) {
-			this.factory = factory;
-			this.latch = latch;
-		}
-
-		@Override
-		public void run() {
-			try {
-				TransportId id = factory.getId();
-				SimplexCallback callback = new SimplexCallback(id);
-				SimplexPlugin plugin = factory.createPlugin(callback);
-				if (plugin == null) {
-					if (LOG.isLoggable(INFO)) {
-						String name = factory.getClass().getSimpleName();
-						LOG.info(name + " did not create a plugin");
-					}
-					return;
-				}
-				try {
-					long start = System.currentTimeMillis();
-					boolean started = plugin.start();
-					long duration = System.currentTimeMillis() - start;
-					if (started) {
-						plugins.put(id, plugin);
-						simplexPlugins.add(plugin);
-						if (LOG.isLoggable(INFO)) {
-							String name = plugin.getClass().getSimpleName();
-							LOG.info("Starting " + name + " took " +
-									duration + " ms");
-						}
-					} else {
-						if (LOG.isLoggable(WARNING)) {
-							String name = plugin.getClass().getSimpleName();
-							LOG.warning(name + " did not start");
-						}
-					}
-				} catch (IOException e) {
-					if (LOG.isLoggable(WARNING))
-						LOG.log(WARNING, e.toString(), e);
-				}
-			} finally {
-				latch.countDown();
-			}
-		}
-	}
-
-	private class DuplexPluginStarter implements Runnable {
-
-		private final DuplexPluginFactory factory;
+		private final Plugin plugin;
 		private final CountDownLatch latch;
 
-		private DuplexPluginStarter(DuplexPluginFactory factory,
-				CountDownLatch latch) {
-			this.factory = factory;
+		private PluginStarter(Plugin plugin, CountDownLatch latch) {
+			this.plugin = plugin;
 			this.latch = latch;
 		}
 
 		@Override
 		public void run() {
 			try {
-				TransportId id = factory.getId();
-				DuplexCallback callback = new DuplexCallback(id);
-				DuplexPlugin plugin = factory.createPlugin(callback);
-				if (plugin == null) {
-					if (LOG.isLoggable(INFO)) {
-						String name = factory.getClass().getSimpleName();
-						LOG.info(name + " did not create a plugin");
-					}
-					return;
-				}
 				try {
 					long start = System.currentTimeMillis();
 					boolean started = plugin.start();
 					long duration = System.currentTimeMillis() - start;
 					if (started) {
-						plugins.put(id, plugin);
-						duplexPlugins.add(plugin);
 						if (LOG.isLoggable(INFO)) {
 							String name = plugin.getClass().getSimpleName();
 							LOG.info("Starting " + name + " took " +
@@ -428,8 +323,6 @@ class PluginManagerImpl implements PluginManager, Service, EventListener {
 		@Override
 		public void transportEnabled() {
 			eventBus.broadcast(new TransportEnabledEvent(id));
-			Plugin p = plugins.get(id);
-			if (p != null && p.shouldPoll()) poller.pollNow(p);
 		}
 
 		@Override
diff --git a/briar-core/src/org/briarproject/plugins/PluginsModule.java b/briar-core/src/org/briarproject/plugins/PluginsModule.java
index 97799fa24aa68ef4b487b60e1d7f6ba5cb441d2e..764708512cee7fffef5325222a19450590f617e4 100644
--- a/briar-core/src/org/briarproject/plugins/PluginsModule.java
+++ b/briar-core/src/org/briarproject/plugins/PluginsModule.java
@@ -26,6 +26,8 @@ public class PluginsModule {
 	public static class EagerSingletons {
 		@Inject
 		PluginManager pluginManager;
+		@Inject
+		Poller poller;
 	}
 
 	@Provides
@@ -35,7 +37,8 @@ public class PluginsModule {
 
 	@Provides
 	@Singleton
-	Poller providePoller(PollerImpl poller) {
+	Poller providePoller(EventBus eventBus, PollerImpl poller) {
+		eventBus.addListener(poller);
 		return poller;
 	}
 
diff --git a/briar-core/src/org/briarproject/plugins/Poller.java b/briar-core/src/org/briarproject/plugins/Poller.java
index cb98150723f89527f8ce59c6c70d0270fc894d7d..c344935ffc16181a5c456a40fcb91e458078ecb1 100644
--- a/briar-core/src/org/briarproject/plugins/Poller.java
+++ b/briar-core/src/org/briarproject/plugins/Poller.java
@@ -1,9 +1,6 @@
 package org.briarproject.plugins;
 
-import org.briarproject.api.plugins.Plugin;
-
 interface Poller {
 
-	/** Tells the poller to poll the given plugin immediately. */
-	void pollNow(Plugin p);
+	// TODO: Remove this interface
 }
diff --git a/briar-core/src/org/briarproject/plugins/PollerImpl.java b/briar-core/src/org/briarproject/plugins/PollerImpl.java
index 843e455f637b3782d98d5d678a3c7e24c205c948..32f54129d2fc4fe5c241889dc83fcf85a2bbda07 100644
--- a/briar-core/src/org/briarproject/plugins/PollerImpl.java
+++ b/briar-core/src/org/briarproject/plugins/PollerImpl.java
@@ -1,9 +1,21 @@
 package org.briarproject.plugins;
 
 import org.briarproject.api.TransportId;
+import org.briarproject.api.contact.ContactId;
+import org.briarproject.api.event.ConnectionClosedEvent;
+import org.briarproject.api.event.ContactStatusChangedEvent;
+import org.briarproject.api.event.Event;
+import org.briarproject.api.event.EventListener;
+import org.briarproject.api.event.TransportEnabledEvent;
 import org.briarproject.api.lifecycle.IoExecutor;
+import org.briarproject.api.plugins.ConnectionManager;
 import org.briarproject.api.plugins.ConnectionRegistry;
 import org.briarproject.api.plugins.Plugin;
+import org.briarproject.api.plugins.PluginManager;
+import org.briarproject.api.plugins.TransportConnectionWriter;
+import org.briarproject.api.plugins.duplex.DuplexPlugin;
+import org.briarproject.api.plugins.duplex.DuplexTransportConnection;
+import org.briarproject.api.plugins.simplex.SimplexPlugin;
 
 import java.security.SecureRandom;
 import java.util.Map;
@@ -17,30 +29,100 @@ import javax.inject.Inject;
 import static java.util.concurrent.TimeUnit.MILLISECONDS;
 import static java.util.logging.Level.INFO;
 
-class PollerImpl implements Poller {
+class PollerImpl implements Poller, EventListener {
 
 	private static final Logger LOG =
 			Logger.getLogger(PollerImpl.class.getName());
 
 	private final Executor ioExecutor;
 	private final ScheduledExecutorService scheduler;
+	private final ConnectionManager connectionManager;
 	private final ConnectionRegistry connectionRegistry;
+	private final PluginManager pluginManager;
 	private final SecureRandom random;
 	private final Map<TransportId, PollTask> tasks;
 
 	@Inject
 	PollerImpl(@IoExecutor Executor ioExecutor,
 			ScheduledExecutorService scheduler,
-			ConnectionRegistry connectionRegistry, SecureRandom random) {
+			ConnectionManager connectionManager,
+			ConnectionRegistry connectionRegistry, PluginManager pluginManager,
+			SecureRandom random) {
 		this.ioExecutor = ioExecutor;
+		this.connectionManager = connectionManager;
 		this.connectionRegistry = connectionRegistry;
+		this.pluginManager = pluginManager;
 		this.random = random;
 		this.scheduler = scheduler;
 		tasks = new ConcurrentHashMap<TransportId, PollTask>();
 	}
 
+
 	@Override
-	public void pollNow(Plugin p) {
+	public void eventOccurred(Event e) {
+		if (e instanceof ContactStatusChangedEvent) {
+			ContactStatusChangedEvent c = (ContactStatusChangedEvent) e;
+			if (c.isActive()) {
+				// Connect to the newly activated contact
+				connectToContact(c.getContactId());
+			}
+		} else if (e instanceof ConnectionClosedEvent) {
+			ConnectionClosedEvent c = (ConnectionClosedEvent) e;
+			if (!c.isIncoming()) {
+				// Connect to the disconnected contact
+				connectToContact(c.getContactId(), c.getTransportId());
+			}
+		} else if (e instanceof TransportEnabledEvent) {
+			TransportEnabledEvent t = (TransportEnabledEvent) e;
+			Plugin p = pluginManager.getPlugin(t.getTransportId());
+			if (p.shouldPoll()) pollNow(p);
+		}
+	}
+
+	private void connectToContact(ContactId c) {
+		for (SimplexPlugin s : pluginManager.getSimplexPlugins())
+			if (s.shouldPoll()) connectToContact(c, s);
+		for (DuplexPlugin d : pluginManager.getDuplexPlugins())
+			if (d.shouldPoll()) connectToContact(c, d);
+	}
+
+	private void connectToContact(ContactId c, TransportId t) {
+		Plugin p = pluginManager.getPlugin(t);
+		if (p instanceof SimplexPlugin && p.shouldPoll())
+			connectToContact(c, (SimplexPlugin) p);
+		else if (p instanceof DuplexPlugin && p.shouldPoll())
+			connectToContact(c, (DuplexPlugin) p);
+	}
+
+	private void connectToContact(final ContactId c, final SimplexPlugin p) {
+		ioExecutor.execute(new Runnable() {
+			@Override
+			public void run() {
+				TransportId t = p.getId();
+				if (!connectionRegistry.isConnected(c, t)) {
+					TransportConnectionWriter w = p.createWriter(c);
+					if (w != null)
+						connectionManager.manageOutgoingConnection(c, t, w);
+				}
+			}
+		});
+	}
+
+	private void connectToContact(final ContactId c, final DuplexPlugin p) {
+		ioExecutor.execute(new Runnable() {
+			@Override
+			public void run() {
+				TransportId t = p.getId();
+				if (!connectionRegistry.isConnected(c, t)) {
+					DuplexTransportConnection d = p.createConnection(c);
+					if (d != null)
+						connectionManager.manageOutgoingConnection(c, t, d);
+				}
+			}
+		});
+	}
+
+	private void pollNow(Plugin p) {
 		// Randomise next polling interval
 		schedule(p, 0, true);
 	}
diff --git a/briar-tests/src/org/briarproject/RunAction.java b/briar-tests/src/org/briarproject/RunAction.java
new file mode 100644
index 0000000000000000000000000000000000000000..ef547ce189deda0120d635bab425dcd693d8334f
--- /dev/null
+++ b/briar-tests/src/org/briarproject/RunAction.java
@@ -0,0 +1,20 @@
+package org.briarproject;
+
+import org.hamcrest.Description;
+import org.jmock.api.Action;
+import org.jmock.api.Invocation;
+
+public class RunAction implements Action {
+
+	@Override
+	public Object invoke(Invocation invocation) throws Throwable {
+		Runnable task = (Runnable) invocation.getParameter(0);
+		task.run();
+		return null;
+	}
+
+	@Override
+	public void describeTo(Description description) {
+		description.appendText("runs a runnable");
+	}
+}
diff --git a/briar-tests/src/org/briarproject/plugins/PluginManagerImplTest.java b/briar-tests/src/org/briarproject/plugins/PluginManagerImplTest.java
index 9f094dadbc9858db52b6a4e226e7ca792dcfb5a8..c186a170013628b51c3378d8c30a02a0ed140ac1 100644
--- a/briar-tests/src/org/briarproject/plugins/PluginManagerImplTest.java
+++ b/briar-tests/src/org/briarproject/plugins/PluginManagerImplTest.java
@@ -1,20 +1,13 @@
 package org.briarproject.plugins;
 
 import org.briarproject.BriarTestCase;
-import org.briarproject.ImmediateExecutor;
 import org.briarproject.api.TransportId;
-import org.briarproject.api.contact.ContactId;
-import org.briarproject.api.event.ContactStatusChangedEvent;
 import org.briarproject.api.event.EventBus;
-import org.briarproject.api.event.EventListener;
 import org.briarproject.api.plugins.ConnectionManager;
-import org.briarproject.api.plugins.ConnectionRegistry;
 import org.briarproject.api.plugins.PluginConfig;
-import org.briarproject.api.plugins.TransportConnectionWriter;
 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.plugins.duplex.DuplexTransportConnection;
 import org.briarproject.api.plugins.simplex.SimplexPlugin;
 import org.briarproject.api.plugins.simplex.SimplexPluginCallback;
 import org.briarproject.api.plugins.simplex.SimplexPluginFactory;
@@ -40,11 +33,8 @@ public class PluginManagerImplTest extends BriarTestCase {
 		final Executor ioExecutor = Executors.newSingleThreadExecutor();
 		final EventBus eventBus = context.mock(EventBus.class);
 		final PluginConfig pluginConfig = context.mock(PluginConfig.class);
-		final Poller poller = context.mock(Poller.class);
 		final ConnectionManager connectionManager =
 				context.mock(ConnectionManager.class);
-		final ConnectionRegistry connectionRegistry =
-				context.mock(ConnectionRegistry.class);
 		final SettingsManager settingsManager =
 				context.mock(SettingsManager.class);
 		final TransportPropertyManager transportPropertyManager =
@@ -108,19 +98,16 @@ public class PluginManagerImplTest extends BriarTestCase {
 			oneOf(duplexFailFactory).createPlugin(with(any(
 					DuplexPluginCallback.class)));
 			will(returnValue(null)); // Failed to create a plugin
-			// Start listening for events
-			oneOf(eventBus).addListener(with(any(EventListener.class)));
 			// stop()
-			// Stop listening for events
-			oneOf(eventBus).removeListener(with(any(EventListener.class)));
 			// Stop the plugins
 			oneOf(simplexPlugin).stop();
+			oneOf(simplexFailPlugin).stop();
 			oneOf(duplexPlugin).stop();
 		}});
 
 		PluginManagerImpl p = new PluginManagerImpl(ioExecutor, eventBus,
-				pluginConfig, poller, connectionManager, connectionRegistry,
-				settingsManager, transportPropertyManager, uiCallback);
+				pluginConfig, connectionManager, settingsManager,
+				transportPropertyManager, uiCallback);
 
 		// Two plugins should be started and stopped
 		p.startService();
@@ -128,139 +115,4 @@ public class PluginManagerImplTest extends BriarTestCase {
 
 		context.assertIsSatisfied();
 	}
-
-	@Test
-	public void testConnectToNewContact() throws Exception {
-		Mockery context = new Mockery();
-		final Executor ioExecutor = new ImmediateExecutor();
-		final EventBus eventBus = context.mock(EventBus.class);
-		final PluginConfig pluginConfig = context.mock(PluginConfig.class);
-		final Poller poller = context.mock(Poller.class);
-		final ConnectionManager connectionManager =
-				context.mock(ConnectionManager.class);
-		final ConnectionRegistry connectionRegistry =
-				context.mock(ConnectionRegistry.class);
-		final SettingsManager settingsManager =
-				context.mock(SettingsManager.class);
-		final TransportPropertyManager transportPropertyManager =
-				context.mock(TransportPropertyManager.class);
-		final UiCallback uiCallback = context.mock(UiCallback.class);
-		final TransportConnectionWriter transportConnectionWriter =
-				context.mock(TransportConnectionWriter.class);
-		final DuplexTransportConnection duplexTransportConnection =
-				context.mock(DuplexTransportConnection.class);
-
-		final ContactId contactId = new ContactId(234);
-
-		// Two simplex plugins: one supports polling, the other doesn't
-		final SimplexPluginFactory simplexFactory =
-				context.mock(SimplexPluginFactory.class);
-		final SimplexPlugin simplexPlugin = context.mock(SimplexPlugin.class);
-		final TransportId simplexId = new TransportId("simplex");
-		final SimplexPluginFactory simplexFactory1 =
-				context.mock(SimplexPluginFactory.class, "simplexFactory1");
-		final SimplexPlugin simplexPlugin1 =
-				context.mock(SimplexPlugin.class, "simplexPlugin1");
-		final TransportId simplexId1 = new TransportId("simplex1");
-
-		// Two duplex plugins: one supports polling, the other doesn't
-		final DuplexPluginFactory duplexFactory =
-				context.mock(DuplexPluginFactory.class);
-		final DuplexPlugin duplexPlugin = context.mock(DuplexPlugin.class);
-		final TransportId duplexId = new TransportId("duplex");
-		final DuplexPluginFactory duplexFactory1 =
-				context.mock(DuplexPluginFactory.class, "duplexFactory1");
-		final DuplexPlugin duplexPlugin1 =
-				context.mock(DuplexPlugin.class, "duplexPlugin1");
-		final TransportId duplexId1 = new TransportId("duplex1");
-
-		context.checking(new Expectations() {{
-			// start()
-			// First simplex plugin
-			oneOf(pluginConfig).getSimplexFactories();
-			will(returnValue(Arrays.asList(simplexFactory, simplexFactory1)));
-			oneOf(simplexFactory).getId();
-			will(returnValue(simplexId));
-			oneOf(simplexFactory).createPlugin(with(any(
-					SimplexPluginCallback.class)));
-			will(returnValue(simplexPlugin)); // Created
-			oneOf(simplexPlugin).start();
-			will(returnValue(true)); // Started
-			// Second simplex plugin
-			oneOf(simplexFactory1).getId();
-			will(returnValue(simplexId1));
-			oneOf(simplexFactory1).createPlugin(with(any(
-					SimplexPluginCallback.class)));
-			will(returnValue(simplexPlugin1)); // Created
-			oneOf(simplexPlugin1).start();
-			will(returnValue(true)); // Started
-			// First duplex plugin
-			oneOf(pluginConfig).getDuplexFactories();
-			will(returnValue(Arrays.asList(duplexFactory, duplexFactory1)));
-			oneOf(duplexFactory).getId();
-			will(returnValue(duplexId));
-			oneOf(duplexFactory).createPlugin(with(any(
-					DuplexPluginCallback.class)));
-			will(returnValue(duplexPlugin)); // Created
-			oneOf(duplexPlugin).start();
-			will(returnValue(true)); // Started
-			// Second duplex plugin
-			oneOf(duplexFactory1).getId();
-			will(returnValue(duplexId1));
-			oneOf(duplexFactory1).createPlugin(with(any(
-					DuplexPluginCallback.class)));
-			will(returnValue(duplexPlugin1)); // Created
-			oneOf(duplexPlugin1).start();
-			will(returnValue(true)); // Started
-			// Start listening for events
-			oneOf(eventBus).addListener(with(any(EventListener.class)));
-			// eventOccurred()
-			// First simplex plugin
-			oneOf(simplexPlugin).shouldPoll();
-			will(returnValue(true));
-			oneOf(simplexPlugin).getId();
-			will(returnValue(simplexId));
-			oneOf(connectionRegistry).isConnected(contactId, simplexId);
-			will(returnValue(false));
-			oneOf(simplexPlugin).createWriter(contactId);
-			will(returnValue(transportConnectionWriter));
-			oneOf(connectionManager).manageOutgoingConnection(contactId,
-					simplexId, transportConnectionWriter);
-			// Second simplex plugin
-			oneOf(simplexPlugin1).shouldPoll();
-			will(returnValue(false));
-			// First duplex plugin
-			oneOf(duplexPlugin).shouldPoll();
-			will(returnValue(true));
-			oneOf(duplexPlugin).getId();
-			will(returnValue(duplexId));
-			oneOf(connectionRegistry).isConnected(contactId, duplexId);
-			will(returnValue(false));
-			oneOf(duplexPlugin).createConnection(contactId);
-			will(returnValue(duplexTransportConnection));
-			oneOf(connectionManager).manageOutgoingConnection(contactId,
-					duplexId, duplexTransportConnection);
-			// Second duplex plugin
-			oneOf(duplexPlugin1).shouldPoll();
-			will(returnValue(false));
-			// stop()
-			// Stop listening for events
-			oneOf(eventBus).removeListener(with(any(EventListener.class)));
-			// Stop the plugins
-			oneOf(simplexPlugin).stop();
-			oneOf(simplexPlugin1).stop();
-			oneOf(duplexPlugin).stop();
-			oneOf(duplexPlugin1).stop();
-		}});
-
-		PluginManagerImpl p = new PluginManagerImpl(ioExecutor, eventBus,
-				pluginConfig, poller, connectionManager, connectionRegistry,
-				settingsManager, transportPropertyManager, uiCallback);
-
-		p.startService();
-		p.eventOccurred(new ContactStatusChangedEvent(contactId, true));
-		p.stopService();
-
-		context.assertIsSatisfied();
-	}
 }
diff --git a/briar-tests/src/org/briarproject/plugins/PollerImplTest.java b/briar-tests/src/org/briarproject/plugins/PollerImplTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..0093d85de5ad4cdaadb5ec80731255608fea9382
--- /dev/null
+++ b/briar-tests/src/org/briarproject/plugins/PollerImplTest.java
@@ -0,0 +1,222 @@
+package org.briarproject.plugins;
+
+import org.briarproject.BriarTestCase;
+import org.briarproject.ImmediateExecutor;
+import org.briarproject.RunAction;
+import org.briarproject.api.TransportId;
+import org.briarproject.api.contact.ContactId;
+import org.briarproject.api.event.ConnectionClosedEvent;
+import org.briarproject.api.event.ContactStatusChangedEvent;
+import org.briarproject.api.event.TransportEnabledEvent;
+import org.briarproject.api.plugins.ConnectionManager;
+import org.briarproject.api.plugins.ConnectionRegistry;
+import org.briarproject.api.plugins.Plugin;
+import org.briarproject.api.plugins.PluginManager;
+import org.briarproject.api.plugins.TransportConnectionWriter;
+import org.briarproject.api.plugins.duplex.DuplexPlugin;
+import org.briarproject.api.plugins.duplex.DuplexTransportConnection;
+import org.briarproject.api.plugins.simplex.SimplexPlugin;
+import org.jmock.Expectations;
+import org.jmock.Mockery;
+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 static java.util.concurrent.TimeUnit.MILLISECONDS;
+
+public class PollerImplTest extends BriarTestCase {
+
+	private final ContactId contactId = new ContactId(234);
+
+	@Test
+	public void testConnectToNewContact() throws Exception {
+		Mockery context = new Mockery();
+		context.setImposteriser(ClassImposteriser.INSTANCE);
+		final Executor ioExecutor = new ImmediateExecutor();
+		final ScheduledExecutorService scheduler =
+				context.mock(ScheduledExecutorService.class);
+		final ConnectionManager connectionManager =
+				context.mock(ConnectionManager.class);
+		final ConnectionRegistry connectionRegistry =
+				context.mock(ConnectionRegistry.class);
+		final PluginManager pluginManager = context.mock(PluginManager.class);
+		final SecureRandom random = context.mock(SecureRandom.class);
+
+		// Two simplex plugins: one supports polling, the other doesn't
+		final SimplexPlugin simplexPlugin = context.mock(SimplexPlugin.class);
+		final SimplexPlugin simplexPlugin1 =
+				context.mock(SimplexPlugin.class, "simplexPlugin1");
+		final TransportId simplexId1 = new TransportId("simplex1");
+		final List<SimplexPlugin> simplexPlugins = Arrays.asList(simplexPlugin,
+				simplexPlugin1);
+		final TransportConnectionWriter simplexWriter =
+				context.mock(TransportConnectionWriter.class);
+
+		// Two duplex plugins: one supports polling, the other doesn't
+		final DuplexPlugin duplexPlugin = context.mock(DuplexPlugin.class);
+		final TransportId duplexId = new TransportId("duplex");
+		final DuplexPlugin duplexPlugin1 =
+				context.mock(DuplexPlugin.class, "duplexPlugin1");
+		final List<DuplexPlugin> duplexPlugins = Arrays.asList(duplexPlugin,
+				duplexPlugin1);
+		final DuplexTransportConnection duplexConnection =
+				context.mock(DuplexTransportConnection.class);
+
+		context.checking(new Expectations() {{
+			// Get the simplex plugins
+			oneOf(pluginManager).getSimplexPlugins();
+			will(returnValue(simplexPlugins));
+			// The first plugin doesn't support polling
+			oneOf(simplexPlugin).shouldPoll();
+			will(returnValue(false));
+			// The second plugin supports polling
+			oneOf(simplexPlugin1).shouldPoll();
+			will(returnValue(true));
+			// Check whether the contact is already connected
+			oneOf(simplexPlugin1).getId();
+			will(returnValue(simplexId1));
+			oneOf(connectionRegistry).isConnected(contactId, simplexId1);
+			will(returnValue(false));
+			// Connect to the contact
+			oneOf(simplexPlugin1).createWriter(contactId);
+			will(returnValue(simplexWriter));
+			// Pass the connection to the connection manager
+			oneOf(connectionManager).manageOutgoingConnection(contactId,
+					simplexId1, simplexWriter);
+			// Get the duplex plugins
+			oneOf(pluginManager).getDuplexPlugins();
+			will(returnValue(duplexPlugins));
+			// The first plugin supports polling
+			oneOf(duplexPlugin).shouldPoll();
+			will(returnValue(true));
+			// Check whether the contact is already connected
+			oneOf(duplexPlugin).getId();
+			will(returnValue(duplexId));
+			oneOf(connectionRegistry).isConnected(contactId, duplexId);
+			will(returnValue(false));
+			// Connect to the contact
+			oneOf(duplexPlugin).createConnection(contactId);
+			will(returnValue(duplexConnection));
+			// Pass the connection to the connection manager
+			oneOf(connectionManager).manageOutgoingConnection(contactId,
+					duplexId, duplexConnection);
+			// The second plugin doesn't support polling
+			oneOf(duplexPlugin1).shouldPoll();
+			will(returnValue(false));
+		}});
+
+		PollerImpl p = new PollerImpl(ioExecutor, scheduler, connectionManager,
+				connectionRegistry, pluginManager, random);
+
+		p.eventOccurred(new ContactStatusChangedEvent(contactId, true));
+
+		context.assertIsSatisfied();
+	}
+
+	@Test
+	public void testReconnectToDisconnectedContact() throws Exception {
+		Mockery context = new Mockery();
+		context.setImposteriser(ClassImposteriser.INSTANCE);
+		final Executor ioExecutor = new ImmediateExecutor();
+		final ScheduledExecutorService scheduler =
+				context.mock(ScheduledExecutorService.class);
+		final ConnectionManager connectionManager =
+				context.mock(ConnectionManager.class);
+		final ConnectionRegistry connectionRegistry =
+				context.mock(ConnectionRegistry.class);
+		final PluginManager pluginManager = context.mock(PluginManager.class);
+		final SecureRandom random = context.mock(SecureRandom.class);
+
+		final DuplexPlugin plugin = context.mock(DuplexPlugin.class);
+		final TransportId transportId = new TransportId("id");
+		final DuplexTransportConnection duplexConnection =
+				context.mock(DuplexTransportConnection.class);
+
+		context.checking(new Expectations() {{
+			// Get the plugin
+			oneOf(pluginManager).getPlugin(transportId);
+			will(returnValue(plugin));
+			// The plugin supports polling
+			oneOf(plugin).shouldPoll();
+			will(returnValue(true));
+			// Check whether the contact is already connected
+			oneOf(plugin).getId();
+			will(returnValue(transportId));
+			oneOf(connectionRegistry).isConnected(contactId, transportId);
+			will(returnValue(false));
+			// Connect to the contact
+			oneOf(plugin).createConnection(contactId);
+			will(returnValue(duplexConnection));
+			// Pass the connection to the connection manager
+			oneOf(connectionManager).manageOutgoingConnection(contactId,
+					transportId, duplexConnection);
+		}});
+
+		PollerImpl p = new PollerImpl(ioExecutor, scheduler, connectionManager,
+				connectionRegistry, pluginManager, random);
+
+		p.eventOccurred(new ConnectionClosedEvent(contactId, transportId,
+				false));
+
+		context.assertIsSatisfied();
+	}
+
+	@Test
+	public void testPollWhenTransportIsEnabled() throws Exception {
+		Mockery context = new Mockery();
+		context.setImposteriser(ClassImposteriser.INSTANCE);
+		final Executor ioExecutor = new ImmediateExecutor();
+		final ScheduledExecutorService scheduler =
+				context.mock(ScheduledExecutorService.class);
+		final ConnectionManager connectionManager =
+				context.mock(ConnectionManager.class);
+		final ConnectionRegistry connectionRegistry =
+				context.mock(ConnectionRegistry.class);
+		final PluginManager pluginManager = context.mock(PluginManager.class);
+		final SecureRandom random = context.mock(SecureRandom.class);
+
+		final Plugin plugin = context.mock(Plugin.class);
+		final TransportId transportId = new TransportId("id");
+		final int pollingInterval = 60 * 1000;
+		final List<ContactId> connected = Collections.singletonList(contactId);
+
+		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(scheduler).schedule(with(any(Runnable.class)), with(0L),
+					with(MILLISECONDS));
+			will(new RunAction());
+			// Run the polling task
+			oneOf(plugin).getPollingInterval();
+			will(returnValue(pollingInterval));
+			oneOf(random).nextDouble();
+			will(returnValue(0.5));
+			oneOf(scheduler).schedule(with(any(Runnable.class)),
+					with((long) (pollingInterval * 0.5)), with(MILLISECONDS));
+			// Poll the plugin
+			oneOf(connectionRegistry).getConnectedContacts(transportId);
+			will(returnValue(connected));
+			oneOf(plugin).poll(connected);
+		}});
+
+		PollerImpl p = new PollerImpl(ioExecutor, scheduler, connectionManager,
+				connectionRegistry, pluginManager, random);
+
+		p.eventOccurred(new TransportEnabledEvent(transportId));
+
+		context.assertIsSatisfied();
+	}
+}
diff --git a/briar-tests/src/org/briarproject/transport/TransportKeyManagerTest.java b/briar-tests/src/org/briarproject/transport/TransportKeyManagerTest.java
index bf035fd3c72b8bb221ebb57eda13b97f597fdf03..4a32490c953999232fb0190c05affd658bb85b2e 100644
--- a/briar-tests/src/org/briarproject/transport/TransportKeyManagerTest.java
+++ b/briar-tests/src/org/briarproject/transport/TransportKeyManagerTest.java
@@ -1,6 +1,7 @@
 package org.briarproject.transport;
 
 import org.briarproject.BriarTestCase;
+import org.briarproject.RunAction;
 import org.briarproject.TestUtils;
 import org.briarproject.api.TransportId;
 import org.briarproject.api.contact.ContactId;
@@ -501,19 +502,4 @@ public class TransportKeyManagerTest extends BriarTestCase {
 			description.appendText("encodes a tag");
 		}
 	}
-
-	private static class RunAction implements Action {
-
-		@Override
-		public Object invoke(Invocation invocation) throws Throwable {
-			Runnable task = (Runnable) invocation.getParameter(0);
-			task.run();
-			return null;
-		}
-
-		@Override
-		public void describeTo(Description description) {
-			description.appendText("runs a runnable");
-		}
-	}
 }