diff --git a/briar-android/src/net/sf/briar/android/AndroidModule.java b/briar-android/src/net/sf/briar/android/AndroidModule.java
index ceac1c7a437415d1e53f25b2c77c00a43eab04b3..ee700f82086b2afeecedff37c239e7e5023bfe9a 100644
--- a/briar-android/src/net/sf/briar/android/AndroidModule.java
+++ b/briar-android/src/net/sf/briar/android/AndroidModule.java
@@ -1,11 +1,16 @@
 package net.sf.briar.android;
 
+import static java.util.concurrent.TimeUnit.SECONDS;
+
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.concurrent.BlockingQueue;
 import java.util.concurrent.Executor;
 import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.RejectedExecutionHandler;
+import java.util.concurrent.ThreadPoolExecutor;
 
 import net.sf.briar.api.android.AndroidExecutor;
 import net.sf.briar.api.android.DatabaseUiExecutor;
@@ -34,15 +39,22 @@ public class AndroidModule extends AbstractModule {
 		bind(AndroidExecutor.class).to(AndroidExecutorImpl.class);
 		bind(ReferenceManager.class).to(ReferenceManagerImpl.class).in(
 				Singleton.class);
-		// Use a single thread so DB accesses from the UI don't overlap, with
-		// an unbounded queue so submissions don't block
-		bind(Executor.class).annotatedWith(DatabaseUiExecutor.class).toInstance(
-				Executors.newSingleThreadExecutor());
+		// The queue is unbounded, so tasks can be dependent
+		BlockingQueue<Runnable> queue = new LinkedBlockingQueue<Runnable>();
+		// Discard tasks that are submitted during shutdown
+		RejectedExecutionHandler policy =
+				new ThreadPoolExecutor.DiscardPolicy();
+		// Use a single thread so DB accesses from the UI don't overlap
+		ExecutorService e = new ThreadPoolExecutor(1, 1, 60, SECONDS, queue,
+				policy);
+		bind(Executor.class).annotatedWith(
+				DatabaseUiExecutor.class).toInstance(e);
+		bind(ExecutorService.class).annotatedWith(
+				DatabaseUiExecutor.class).toInstance(e);
 	}
 
 	@Provides
-	SimplexPluginConfig getSimplexPluginConfig(
-			@PluginExecutor ExecutorService pluginExecutor) {
+	SimplexPluginConfig getSimplexPluginConfig() {
 		return new SimplexPluginConfig() {
 			public Collection<SimplexPluginFactory> getFactories() {
 				return Collections.emptyList();
@@ -52,7 +64,7 @@ public class AndroidModule extends AbstractModule {
 
 	@Provides
 	DuplexPluginConfig getDuplexPluginConfig(
-			@PluginExecutor ExecutorService pluginExecutor,
+			@PluginExecutor Executor pluginExecutor,
 			AndroidExecutor androidExecutor, Context appContext,
 			CryptoComponent crypto, ShutdownManager shutdownManager) {
 		DuplexPluginFactory droidtooth = new DroidtoothPluginFactory(
diff --git a/briar-android/src/net/sf/briar/android/BriarService.java b/briar-android/src/net/sf/briar/android/BriarService.java
index 3582efc54f3962f556827a1bec284669176af2a3..5cbc516a2bb7e511081688670a026384f146cea2 100644
--- a/briar-android/src/net/sf/briar/android/BriarService.java
+++ b/briar-android/src/net/sf/briar/android/BriarService.java
@@ -3,18 +3,15 @@ package net.sf.briar.android;
 import static android.app.PendingIntent.FLAG_ONE_SHOT;
 import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
 import static java.util.logging.Level.INFO;
-import static java.util.logging.Level.WARNING;
 
-import java.io.IOException;
 import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
 import java.util.logging.Logger;
 
 import net.sf.briar.R;
-import net.sf.briar.api.crypto.KeyManager;
-import net.sf.briar.api.db.DatabaseComponent;
-import net.sf.briar.api.db.DatabaseConfig;
-import net.sf.briar.api.db.DbException;
-import net.sf.briar.api.plugins.PluginManager;
+import net.sf.briar.api.android.AndroidExecutor;
+import net.sf.briar.api.android.DatabaseUiExecutor;
+import net.sf.briar.api.lifecycle.LifecycleManager;
 import roboguice.service.RoboService;
 import android.app.PendingIntent;
 import android.content.ComponentName;
@@ -31,15 +28,12 @@ public class BriarService extends RoboService {
 	private static final Logger LOG =
 			Logger.getLogger(BriarService.class.getName());
 
-	private final CountDownLatch dbLatch = new CountDownLatch(1);
-	private final CountDownLatch startupLatch = new CountDownLatch(1);
-	private final CountDownLatch shutdownLatch = new CountDownLatch(1);
 	private final Binder binder = new BriarBinder();
 
-	@Inject private DatabaseConfig databaseConfig = null;
-	@Inject private DatabaseComponent db = null;
-	@Inject private KeyManager keyManager = null;
-	@Inject private PluginManager pluginManager = null;
+	// Fields that are accessed from background threads must be volatile
+	@Inject private volatile LifecycleManager lifecycleManager;
+	@Inject private volatile AndroidExecutor androidExecutor;
+	@Inject @DatabaseUiExecutor private volatile ExecutorService dbUiExecutor;
 
 	@Override
 	public void onCreate() {
@@ -63,7 +57,7 @@ public class BriarService extends RoboService {
 		new Thread() {
 			@Override
 			public void run() {
-				startServices();
+				lifecycleManager.startServices();
 			}
 		}.start();
 	}
@@ -71,11 +65,11 @@ public class BriarService extends RoboService {
 	@Override
 	public int onStartCommand(Intent intent, int flags, int startId) {
 		if(LOG.isLoggable(INFO)) LOG.info("Started");
-		return START_STICKY;
+		return START_NOT_STICKY; // Don't restart automatically if killed
 	}
 
-	@Override
 	public IBinder onBind(Intent intent) {
+		if(LOG.isLoggable(INFO)) LOG.info("Bound");
 		return binder;
 	}
 
@@ -87,71 +81,38 @@ public class BriarService extends RoboService {
 		new Thread() {
 			@Override
 			public void run() {
-				stopServices();
+				// FIXME: This is ugly - executors should register themselves
+				// with the lifecycle manager
+				androidExecutor.shutdown();
+				dbUiExecutor.shutdown();
+				lifecycleManager.stopServices();
 			}
 		}.start();
 	}
 
-	private void startServices() {
-		if(databaseConfig.getEncryptionKey() == null)
-			throw new IllegalStateException();
-		try {
-			if(LOG.isLoggable(INFO)) LOG.info("Starting");
-			boolean reopened = db.open();
-			if(LOG.isLoggable(INFO)) {
-				if(reopened) LOG.info("Database reopened");
-				else LOG.info("Database created");
-			}
-			dbLatch.countDown();
-			keyManager.start();
-			if(LOG.isLoggable(INFO)) LOG.info("Key manager started");
-			int pluginsStarted = pluginManager.start();
-			if(LOG.isLoggable(INFO))
-				LOG.info(pluginsStarted + " plugins started");
-			startupLatch.countDown();
-		} catch(DbException e) {
-			if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
-		} catch(IOException e) {
-			if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
-		}
-	}
-
-	private void stopServices() {
-		try {
-			if(LOG.isLoggable(INFO)) LOG.info("Shutting down");
-			int pluginsStopped = pluginManager.stop();
-			if(LOG.isLoggable(INFO))
-				LOG.info(pluginsStopped + " plugins stopped");
-			keyManager.stop();
-			if(LOG.isLoggable(INFO)) LOG.info("Key manager stopped");
-			db.close();
-			if(LOG.isLoggable(INFO)) LOG.info("Database closed");
-			shutdownLatch.countDown();
-		} catch(DbException e) {
-			if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
-		} catch(IOException e) {
-			if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
-		}
-	}
-
+	/** Waits for the database to be opened before returning. */
 	public void waitForDatabase() throws InterruptedException {
-		dbLatch.await();
+		lifecycleManager.waitForDatabase();
 	}
 
+	/** Waits for all services to start before returning. */
 	public void waitForStartup() throws InterruptedException {
-		startupLatch.await();
+		lifecycleManager.waitForStartup();
 	}
 
+	/** Waits for all services to stop before returning. */
 	public void waitForShutdown() throws InterruptedException {
-		shutdownLatch.await();
+		lifecycleManager.waitForShutdown();
 	}
 
+	/** Starts the shutdown process. */
 	public void shutdown() {
-		stopSelf();
+		stopSelf(); // This will call onDestroy()
 	}
 
 	public class BriarBinder extends Binder {
 
+		/** Returns the bound service. */
 		public BriarService getService() {
 			return BriarService.this;
 		}
@@ -170,19 +131,10 @@ public class BriarService extends RoboService {
 
 		public void onServiceDisconnected(ComponentName name) {}
 
+		/** Waits for the service to connect and returns its binder. */
 		public IBinder waitForBinder() throws InterruptedException {
 			binderLatch.await();
 			return binder;
 		}
-
-		public void waitForDatabase() throws InterruptedException {
-			waitForBinder();
-			((BriarBinder) binder).getService().waitForDatabase();
-		}
-
-		public void waitForStartup() throws InterruptedException {
-			waitForBinder();
-			((BriarBinder) binder).getService().waitForStartup();
-		}
 	}
 }
diff --git a/briar-android/src/net/sf/briar/android/HomeScreenActivity.java b/briar-android/src/net/sf/briar/android/HomeScreenActivity.java
index ef7ecd0a5b972ad3fd1b87f5629e549c86a1eb31..354d2d1b2371624b8cc662cd12b72e14302526fe 100644
--- a/briar-android/src/net/sf/briar/android/HomeScreenActivity.java
+++ b/briar-android/src/net/sf/briar/android/HomeScreenActivity.java
@@ -35,6 +35,7 @@ import net.sf.briar.api.crypto.CryptoExecutor;
 import net.sf.briar.api.db.DatabaseComponent;
 import net.sf.briar.api.db.DatabaseConfig;
 import net.sf.briar.api.db.DbException;
+import net.sf.briar.api.lifecycle.LifecycleManager;
 import net.sf.briar.util.StringUtils;
 import roboguice.activity.RoboActivity;
 import android.content.Intent;
@@ -70,18 +71,19 @@ public class HomeScreenActivity extends RoboActivity {
 	private final BriarServiceConnection serviceConnection =
 			new BriarServiceConnection();
 
-	@Inject private ReferenceManager referenceManager = null;
-	@Inject private DatabaseConfig databaseConfig = null;
-	@Inject @DatabaseUiExecutor private Executor dbUiExecutor = null;
-	@Inject @CryptoExecutor private Executor cryptoExecutor = null;
+	@Inject private ReferenceManager referenceManager;
+	@Inject private DatabaseConfig databaseConfig;
+	@Inject @DatabaseUiExecutor private Executor dbUiExecutor;
+	@Inject @CryptoExecutor private Executor cryptoExecutor;
 	private boolean bound = false;
 	private TextView enterPassword = null;
 	private Button continueButton = null;
 	private ProgressBar progress = null;
 
 	// Fields that are accessed from background threads must be volatile
-	@Inject private volatile DatabaseComponent db = null;
-	@Inject private volatile CryptoComponent crypto = null;
+	@Inject private volatile CryptoComponent crypto;
+	@Inject private volatile DatabaseComponent db;
+	@Inject private volatile LifecycleManager lifecycleManager;
 
 	@Override
 	public void onCreate(Bundle state) {
@@ -136,7 +138,7 @@ public class HomeScreenActivity extends RoboActivity {
 			@Override
 			public void run() {
 				try {
-					// Wait for the service to be bound and started
+					// Wait for the service to finish starting up
 					IBinder binder = serviceConnection.waitForBinder();
 					BriarService service = ((BriarBinder) binder).getService();
 					service.waitForStartup();
@@ -146,7 +148,7 @@ public class HomeScreenActivity extends RoboActivity {
 					service.waitForShutdown();
 				} catch(InterruptedException e) {
 					if(LOG.isLoggable(INFO))
-						LOG.info("Interrupted while waiting for database");
+						LOG.info("Interrupted while waiting for service");
 				}
 				// Finish the activity and kill the JVM
 				runOnUiThread(new Runnable() {
@@ -164,7 +166,7 @@ public class HomeScreenActivity extends RoboActivity {
 		dbUiExecutor.execute(new Runnable() {
 			public void run() {
 				try {
-					serviceConnection.waitForDatabase();
+					lifecycleManager.waitForDatabase();
 					long now = System.currentTimeMillis();
 					db.addLocalAuthor(a);
 					db.setRating(a.getId(), GOOD);
diff --git a/briar-android/src/net/sf/briar/android/blogs/BlogActivity.java b/briar-android/src/net/sf/briar/android/blogs/BlogActivity.java
index aa421875ea340b595a8b8afa073c4b40a34ce797..f603373968b315cfe3bf1821f50ceababfcfa9b2 100644
--- a/briar-android/src/net/sf/briar/android/blogs/BlogActivity.java
+++ b/briar-android/src/net/sf/briar/android/blogs/BlogActivity.java
@@ -17,8 +17,6 @@ import java.util.logging.Logger;
 
 import net.sf.briar.R;
 import net.sf.briar.android.AscendingHeaderComparator;
-import net.sf.briar.android.BriarService;
-import net.sf.briar.android.BriarService.BriarServiceConnection;
 import net.sf.briar.android.widgets.HorizontalBorder;
 import net.sf.briar.android.widgets.ListLoadingProgressBar;
 import net.sf.briar.api.Author;
@@ -33,6 +31,7 @@ import net.sf.briar.api.db.event.GroupMessageAddedEvent;
 import net.sf.briar.api.db.event.MessageExpiredEvent;
 import net.sf.briar.api.db.event.RatingChangedEvent;
 import net.sf.briar.api.db.event.SubscriptionRemovedEvent;
+import net.sf.briar.api.lifecycle.LifecycleManager;
 import net.sf.briar.api.messaging.GroupId;
 import roboguice.activity.RoboFragmentActivity;
 import android.content.Intent;
@@ -53,9 +52,6 @@ implements DatabaseListener, OnClickListener, OnItemClickListener {
 	private static final Logger LOG =
 			Logger.getLogger(BlogActivity.class.getName());
 
-	private final BriarServiceConnection serviceConnection =
-			new BriarServiceConnection();
-
 	private String groupName = null;
 	private boolean postable = false;
 	private BlogAdapter adapter = null;
@@ -65,6 +61,7 @@ implements DatabaseListener, OnClickListener, OnItemClickListener {
 	// Fields that are accessed from background threads must be volatile
 	@Inject private volatile DatabaseComponent db;
 	@Inject @DatabaseUiExecutor private volatile Executor dbUiExecutor;
+	@Inject private volatile LifecycleManager lifecycleManager;
 	private volatile GroupId groupId = null;
 
 	@Override
@@ -107,10 +104,6 @@ implements DatabaseListener, OnClickListener, OnItemClickListener {
 		layout.addView(composeButton);
 
 		setContentView(layout);
-
-		// Bind to the service so we can wait for it to start
-		bindService(new Intent(BriarService.class.getName()),
-				serviceConnection, 0);
 	}
 
 	@Override
@@ -124,7 +117,7 @@ implements DatabaseListener, OnClickListener, OnItemClickListener {
 		dbUiExecutor.execute(new Runnable() {
 			public void run() {
 				try {
-					serviceConnection.waitForDatabase();
+					lifecycleManager.waitForDatabase();
 					long now = System.currentTimeMillis();
 					Collection<GroupMessageHeader> headers =
 							db.getGroupMessageHeaders(groupId);
@@ -196,12 +189,6 @@ implements DatabaseListener, OnClickListener, OnItemClickListener {
 		db.removeListener(this);
 	}
 
-	@Override
-	public void onDestroy() {
-		super.onDestroy();
-		unbindService(serviceConnection);
-	}
-
 	public void eventOccurred(DatabaseEvent e) {
 		if(e instanceof GroupMessageAddedEvent) {
 			GroupMessageAddedEvent g = (GroupMessageAddedEvent) e;
diff --git a/briar-android/src/net/sf/briar/android/blogs/BlogListActivity.java b/briar-android/src/net/sf/briar/android/blogs/BlogListActivity.java
index 6bc9d9d58067a31ee03331058c35d3b6a716c012..5680ad9303e9f0bc944649ca4c6b7134314b3278 100644
--- a/briar-android/src/net/sf/briar/android/blogs/BlogListActivity.java
+++ b/briar-android/src/net/sf/briar/android/blogs/BlogListActivity.java
@@ -21,8 +21,6 @@ import java.util.concurrent.Executor;
 import java.util.logging.Logger;
 
 import net.sf.briar.R;
-import net.sf.briar.android.BriarService;
-import net.sf.briar.android.BriarService.BriarServiceConnection;
 import net.sf.briar.android.widgets.HorizontalBorder;
 import net.sf.briar.android.widgets.HorizontalSpace;
 import net.sf.briar.android.widgets.ListLoadingProgressBar;
@@ -38,6 +36,7 @@ import net.sf.briar.api.db.event.MessageExpiredEvent;
 import net.sf.briar.api.db.event.RemoteSubscriptionsUpdatedEvent;
 import net.sf.briar.api.db.event.SubscriptionAddedEvent;
 import net.sf.briar.api.db.event.SubscriptionRemovedEvent;
+import net.sf.briar.api.lifecycle.LifecycleManager;
 import net.sf.briar.api.messaging.Group;
 import net.sf.briar.api.messaging.GroupId;
 import net.sf.briar.api.messaging.GroupStatus;
@@ -61,9 +60,6 @@ OnItemClickListener {
 	private static final Logger LOG =
 			Logger.getLogger(BlogListActivity.class.getName());
 
-	private final BriarServiceConnection serviceConnection =
-			new BriarServiceConnection();
-
 	private BlogListAdapter adapter = null;
 	private ListView list = null;
 	private ListLoadingProgressBar loading = null;
@@ -73,6 +69,7 @@ OnItemClickListener {
 	// Fields that are accessed from background threads must be volatile
 	@Inject private volatile DatabaseComponent db;
 	@Inject @DatabaseUiExecutor private volatile Executor dbUiExecutor;
+	@Inject private volatile LifecycleManager lifecycleManager;
 
 	@Override
 	public void onCreate(Bundle state) {
@@ -126,10 +123,6 @@ OnItemClickListener {
 		layout.addView(footer);
 
 		setContentView(layout);
-
-		// Bind to the service so we can wait for it to start
-		bindService(new Intent(BriarService.class.getName()),
-				serviceConnection, 0);
 	}
 
 	@Override
@@ -144,7 +137,7 @@ OnItemClickListener {
 		dbUiExecutor.execute(new Runnable() {
 			public void run() {
 				try {
-					serviceConnection.waitForDatabase();
+					lifecycleManager.waitForDatabase();
 					long now = System.currentTimeMillis();
 					Set<GroupId> local = new HashSet<GroupId>();
 					for(Group g : db.getLocalGroups()) local.add(g.getId());
@@ -252,12 +245,6 @@ OnItemClickListener {
 		db.removeListener(this);
 	}
 
-	@Override
-	public void onDestroy() {
-		super.onDestroy();
-		unbindService(serviceConnection);
-	}
-
 	public void eventOccurred(DatabaseEvent e) {
 		if(e instanceof GroupMessageAddedEvent) {
 			Group g = ((GroupMessageAddedEvent) e).getGroup();
@@ -292,7 +279,7 @@ OnItemClickListener {
 		dbUiExecutor.execute(new Runnable() {
 			public void run() {
 				try {
-					serviceConnection.waitForDatabase();
+					lifecycleManager.waitForDatabase();
 					long now = System.currentTimeMillis();
 					Collection<GroupMessageHeader> headers =
 							db.getGroupMessageHeaders(g.getId());
@@ -333,7 +320,7 @@ OnItemClickListener {
 		dbUiExecutor.execute(new Runnable() {
 			public void run() {
 				try {
-					serviceConnection.waitForDatabase();
+					lifecycleManager.waitForDatabase();
 					int available = 0;
 					long now = System.currentTimeMillis();
 					for(GroupStatus s : db.getAvailableGroups()) {
diff --git a/briar-android/src/net/sf/briar/android/blogs/ConfigureBlogActivity.java b/briar-android/src/net/sf/briar/android/blogs/ConfigureBlogActivity.java
index 2ebd2726090792d02c7134a3b944934fcb126840..0d7ca47c2c0e6ec299f26e68a328b0c9ca19fe14 100644
--- a/briar-android/src/net/sf/briar/android/blogs/ConfigureBlogActivity.java
+++ b/briar-android/src/net/sf/briar/android/blogs/ConfigureBlogActivity.java
@@ -15,8 +15,6 @@ import java.util.concurrent.Executor;
 import java.util.logging.Logger;
 
 import net.sf.briar.R;
-import net.sf.briar.android.BriarService;
-import net.sf.briar.android.BriarService.BriarServiceConnection;
 import net.sf.briar.android.contact.SelectContactsDialog;
 import net.sf.briar.android.invitation.AddContactActivity;
 import net.sf.briar.android.messages.NoContactsDialog;
@@ -25,6 +23,7 @@ import net.sf.briar.api.ContactId;
 import net.sf.briar.api.android.DatabaseUiExecutor;
 import net.sf.briar.api.db.DatabaseComponent;
 import net.sf.briar.api.db.DbException;
+import net.sf.briar.api.lifecycle.LifecycleManager;
 import net.sf.briar.api.messaging.Group;
 import net.sf.briar.api.messaging.GroupId;
 import roboguice.activity.RoboFragmentActivity;
@@ -48,9 +47,6 @@ SelectContactsDialog.Listener {
 	private static final Logger LOG =
 			Logger.getLogger(ConfigureBlogActivity.class.getName());
 
-	private final BriarServiceConnection serviceConnection =
-			new BriarServiceConnection();
-
 	private boolean subscribed = false;
 	private CheckBox subscribeCheckBox = null;
 	private RadioGroup radioGroup = null;
@@ -61,6 +57,7 @@ SelectContactsDialog.Listener {
 	// Fields that are accessed from background threads must be volatile
 	@Inject private volatile DatabaseComponent db;
 	@Inject @DatabaseUiExecutor private volatile Executor dbUiExecutor;
+	@Inject private volatile LifecycleManager lifecycleManager;
 	private volatile Group group = null;
 	private volatile Collection<ContactId> selected = Collections.emptyList();
 
@@ -127,16 +124,6 @@ SelectContactsDialog.Listener {
 		layout.addView(progress);
 
 		setContentView(layout);
-
-		// Bind to the service so we can wait for it to start
-		bindService(new Intent(BriarService.class.getName()),
-				serviceConnection, 0);
-	}
-
-	@Override
-	public void onDestroy() {
-		super.onDestroy();
-		unbindService(serviceConnection);
 	}
 
 	public void onClick(View view) {
@@ -164,7 +151,7 @@ SelectContactsDialog.Listener {
 		dbUiExecutor.execute(new Runnable() {
 			public void run() {
 				try {
-					serviceConnection.waitForDatabase();
+					lifecycleManager.waitForDatabase();
 					long now = System.currentTimeMillis();
 					Collection<Contact> contacts = db.getContacts();
 					long duration = System.currentTimeMillis() - now;
@@ -208,7 +195,7 @@ SelectContactsDialog.Listener {
 		dbUiExecutor.execute(new Runnable() {
 			public void run() {
 				try {
-					serviceConnection.waitForDatabase();
+					lifecycleManager.waitForDatabase();
 					long now = System.currentTimeMillis();
 					if(subscribe) {
 						if(!wasSubscribed) db.subscribe(group);
diff --git a/briar-android/src/net/sf/briar/android/blogs/CreateBlogActivity.java b/briar-android/src/net/sf/briar/android/blogs/CreateBlogActivity.java
index 7232e9d773c6cb9fa1eaa8e2fe8036817a47c2a7..a92b19ea54b02b2eee151d7e28fb88710af9615d 100644
--- a/briar-android/src/net/sf/briar/android/blogs/CreateBlogActivity.java
+++ b/briar-android/src/net/sf/briar/android/blogs/CreateBlogActivity.java
@@ -21,8 +21,6 @@ import java.util.concurrent.Executor;
 import java.util.logging.Logger;
 
 import net.sf.briar.R;
-import net.sf.briar.android.BriarService;
-import net.sf.briar.android.BriarService.BriarServiceConnection;
 import net.sf.briar.android.contact.SelectContactsDialog;
 import net.sf.briar.android.invitation.AddContactActivity;
 import net.sf.briar.android.messages.NoContactsDialog;
@@ -33,6 +31,7 @@ import net.sf.briar.api.crypto.CryptoComponent;
 import net.sf.briar.api.crypto.CryptoExecutor;
 import net.sf.briar.api.db.DatabaseComponent;
 import net.sf.briar.api.db.DbException;
+import net.sf.briar.api.lifecycle.LifecycleManager;
 import net.sf.briar.api.messaging.GroupFactory;
 import net.sf.briar.api.messaging.LocalGroup;
 import roboguice.activity.RoboFragmentActivity;
@@ -60,9 +59,6 @@ SelectContactsDialog.Listener {
 	private static final Logger LOG =
 			Logger.getLogger(CreateBlogActivity.class.getName());
 
-	private final BriarServiceConnection serviceConnection =
-			new BriarServiceConnection();
-
 	@Inject @CryptoExecutor private Executor cryptoExecutor;
 	private EditText nameEntry = null;
 	private RadioGroup radioGroup = null;
@@ -75,6 +71,7 @@ SelectContactsDialog.Listener {
 	@Inject private volatile GroupFactory groupFactory;
 	@Inject private volatile DatabaseComponent db;
 	@Inject @DatabaseUiExecutor private volatile Executor dbUiExecutor;
+	@Inject private volatile LifecycleManager lifecycleManager;
 	private volatile Collection<ContactId> selected = Collections.emptyList();
 
 	@Override
@@ -135,10 +132,6 @@ SelectContactsDialog.Listener {
 		layout.addView(progress);
 
 		setContentView(layout);
-
-		// Bind to the service so we can wait for it to start
-		bindService(new Intent(BriarService.class.getName()),
-				serviceConnection, 0);
 	}
 
 	private void enableOrDisableCreateButton() {
@@ -149,12 +142,6 @@ SelectContactsDialog.Listener {
 		createButton.setEnabled(nameNotEmpty && visibilitySelected);
 	}
 
-	@Override
-	public void onDestroy() {
-		super.onDestroy();
-		unbindService(serviceConnection);
-	}
-
 	public boolean onEditorAction(TextView textView, int actionId, KeyEvent e) {
 		validateName();
 		return true;
@@ -197,7 +184,7 @@ SelectContactsDialog.Listener {
 		dbUiExecutor.execute(new Runnable() {
 			public void run() {
 				try {
-					serviceConnection.waitForDatabase();
+					lifecycleManager.waitForDatabase();
 					long now = System.currentTimeMillis();
 					Collection<Contact> contacts = db.getContacts();
 					long duration = System.currentTimeMillis() - now;
@@ -240,7 +227,7 @@ SelectContactsDialog.Listener {
 		dbUiExecutor.execute(new Runnable() {
 			public void run() {
 				try {
-					serviceConnection.waitForDatabase();
+					lifecycleManager.waitForDatabase();
 					long now = System.currentTimeMillis();
 					db.addLocalGroup(g);
 					db.subscribe(g);
diff --git a/briar-android/src/net/sf/briar/android/blogs/ManageBlogsActivity.java b/briar-android/src/net/sf/briar/android/blogs/ManageBlogsActivity.java
index 91385ccd9357887bed85ccfd9ccf98b448cf1151..1055024983845b7585776a30e988a00f174ee992 100644
--- a/briar-android/src/net/sf/briar/android/blogs/ManageBlogsActivity.java
+++ b/briar-android/src/net/sf/briar/android/blogs/ManageBlogsActivity.java
@@ -13,8 +13,6 @@ import java.util.List;
 import java.util.concurrent.Executor;
 import java.util.logging.Logger;
 
-import net.sf.briar.android.BriarService;
-import net.sf.briar.android.BriarService.BriarServiceConnection;
 import net.sf.briar.android.widgets.ListLoadingProgressBar;
 import net.sf.briar.api.android.DatabaseUiExecutor;
 import net.sf.briar.api.db.DatabaseComponent;
@@ -24,6 +22,7 @@ import net.sf.briar.api.db.event.DatabaseListener;
 import net.sf.briar.api.db.event.RemoteSubscriptionsUpdatedEvent;
 import net.sf.briar.api.db.event.SubscriptionAddedEvent;
 import net.sf.briar.api.db.event.SubscriptionRemovedEvent;
+import net.sf.briar.api.lifecycle.LifecycleManager;
 import net.sf.briar.api.messaging.Group;
 import net.sf.briar.api.messaging.GroupStatus;
 import roboguice.activity.RoboFragmentActivity;
@@ -42,9 +41,6 @@ implements DatabaseListener, OnItemClickListener {
 	private static final Logger LOG =
 			Logger.getLogger(ManageBlogsActivity.class.getName());
 
-	private final BriarServiceConnection serviceConnection =
-			new BriarServiceConnection();
-
 	private ManageBlogsAdapter adapter = null;
 	private ListView list = null;
 	private ListLoadingProgressBar loading = null;
@@ -52,6 +48,7 @@ implements DatabaseListener, OnItemClickListener {
 	// Fields that are accessed from background threads must be volatile
 	@Inject private volatile DatabaseComponent db;
 	@Inject @DatabaseUiExecutor private volatile Executor dbUiExecutor;
+	@Inject private volatile LifecycleManager lifecycleManager;
 
 	@Override
 	public void onCreate(Bundle state) {
@@ -66,10 +63,6 @@ implements DatabaseListener, OnItemClickListener {
 		// Show a progress bar while the list is loading
 		loading = new ListLoadingProgressBar(this);
 		setContentView(loading);
-
-		// Bind to the service so we can wait for it to start
-		bindService(new Intent(BriarService.class.getName()),
-				serviceConnection, 0);
 	}
 
 	@Override
@@ -83,7 +76,7 @@ implements DatabaseListener, OnItemClickListener {
 		dbUiExecutor.execute(new Runnable() {
 			public void run() {
 				try {
-					serviceConnection.waitForDatabase();
+					lifecycleManager.waitForDatabase();
 					long now = System.currentTimeMillis();
 					List<GroupStatus> available = new ArrayList<GroupStatus>();
 					for(GroupStatus s : db.getAvailableGroups())
@@ -125,12 +118,6 @@ implements DatabaseListener, OnItemClickListener {
 		db.removeListener(this);
 	}
 
-	@Override
-	public void onDestroy() {
-		super.onDestroy();
-		unbindService(serviceConnection);
-	}
-
 	public void eventOccurred(DatabaseEvent e) {
 		if(e instanceof RemoteSubscriptionsUpdatedEvent) {
 			if(LOG.isLoggable(INFO))
diff --git a/briar-android/src/net/sf/briar/android/blogs/ReadBlogPostActivity.java b/briar-android/src/net/sf/briar/android/blogs/ReadBlogPostActivity.java
index 4ad6cc09dfc5fc6cb9b32334d155d86cfdeb312f..7188674f240189b5fe8944bbfb7d758532abb2f5 100644
--- a/briar-android/src/net/sf/briar/android/blogs/ReadBlogPostActivity.java
+++ b/briar-android/src/net/sf/briar/android/blogs/ReadBlogPostActivity.java
@@ -19,14 +19,13 @@ import java.util.concurrent.Executor;
 import java.util.logging.Logger;
 
 import net.sf.briar.R;
-import net.sf.briar.android.BriarService;
-import net.sf.briar.android.BriarService.BriarServiceConnection;
 import net.sf.briar.android.widgets.HorizontalBorder;
 import net.sf.briar.android.widgets.HorizontalSpace;
 import net.sf.briar.api.android.DatabaseUiExecutor;
 import net.sf.briar.api.db.DatabaseComponent;
 import net.sf.briar.api.db.DbException;
 import net.sf.briar.api.db.NoSuchMessageException;
+import net.sf.briar.api.lifecycle.LifecycleManager;
 import net.sf.briar.api.messaging.GroupId;
 import net.sf.briar.api.messaging.MessageId;
 import net.sf.briar.api.messaging.Rating;
@@ -55,9 +54,6 @@ implements OnClickListener {
 	private static final Logger LOG =
 			Logger.getLogger(ReadBlogPostActivity.class.getName());
 
-	private final BriarServiceConnection serviceConnection =
-			new BriarServiceConnection();
-
 	private GroupId groupId = null;
 	private boolean postable = false;
 	private Rating rating = UNRATED;
@@ -70,6 +66,7 @@ implements OnClickListener {
 	// Fields that are accessed from background threads must be volatile
 	@Inject private volatile DatabaseComponent db;
 	@Inject @DatabaseUiExecutor private volatile Executor dbUiExecutor;
+	@Inject private volatile LifecycleManager lifecycleManager;
 	private volatile MessageId messageId = null;
 
 	@Override
@@ -196,17 +193,13 @@ implements OnClickListener {
 		layout.addView(footer);
 
 		setContentView(layout);
-
-		// Bind to the service so we can wait for it to start
-		bindService(new Intent(BriarService.class.getName()),
-				serviceConnection, 0);
 	}
 
 	private void setReadInDatabase(final boolean read) {
 		dbUiExecutor.execute(new Runnable() {
 			public void run() {
 				try {
-					serviceConnection.waitForDatabase();
+					lifecycleManager.waitForDatabase();
 					long now = System.currentTimeMillis();
 					db.setReadFlag(messageId, read);
 					long duration = System.currentTimeMillis() - now;
@@ -239,7 +232,7 @@ implements OnClickListener {
 		dbUiExecutor.execute(new Runnable() {
 			public void run() {
 				try {
-					serviceConnection.waitForDatabase();
+					lifecycleManager.waitForDatabase();
 					long now = System.currentTimeMillis();
 					byte[] body = db.getMessageBody(messageId);
 					long duration = System.currentTimeMillis() - now;
@@ -278,12 +271,6 @@ implements OnClickListener {
 		state.putBoolean("net.sf.briar.READ", read);
 	}
 
-	@Override
-	public void onDestroy() {
-		super.onDestroy();
-		unbindService(serviceConnection);
-	}
-
 	public void onClick(View view) {
 		if(view == readButton) {
 			setReadInDatabase(!read);
diff --git a/briar-android/src/net/sf/briar/android/blogs/WriteBlogPostActivity.java b/briar-android/src/net/sf/briar/android/blogs/WriteBlogPostActivity.java
index 2a134bbaa4a4e0a4e1bf43c5563ca68d3f7a73c6..e3d60e2a9675b0b2bbdf6d0a1058a99670901067 100644
--- a/briar-android/src/net/sf/briar/android/blogs/WriteBlogPostActivity.java
+++ b/briar-android/src/net/sf/briar/android/blogs/WriteBlogPostActivity.java
@@ -17,8 +17,6 @@ import java.util.concurrent.Executor;
 import java.util.logging.Logger;
 
 import net.sf.briar.R;
-import net.sf.briar.android.BriarService;
-import net.sf.briar.android.BriarService.BriarServiceConnection;
 import net.sf.briar.android.identity.CreateIdentityActivity;
 import net.sf.briar.android.identity.LocalAuthorItem;
 import net.sf.briar.android.identity.LocalAuthorItemComparator;
@@ -31,6 +29,7 @@ import net.sf.briar.api.crypto.CryptoComponent;
 import net.sf.briar.api.crypto.KeyParser;
 import net.sf.briar.api.db.DatabaseComponent;
 import net.sf.briar.api.db.DbException;
+import net.sf.briar.api.lifecycle.LifecycleManager;
 import net.sf.briar.api.messaging.GroupId;
 import net.sf.briar.api.messaging.LocalGroup;
 import net.sf.briar.api.messaging.Message;
@@ -58,9 +57,6 @@ implements OnItemSelectedListener, OnClickListener {
 	private static final Logger LOG =
 			Logger.getLogger(WriteBlogPostActivity.class.getName());
 
-	private final BriarServiceConnection serviceConnection =
-			new BriarServiceConnection();
-
 	@Inject private CryptoComponent crypto;
 	@Inject private MessageFactory messageFactory;
 	private LocalAuthorSpinnerAdapter fromAdapter = null;
@@ -74,6 +70,7 @@ implements OnItemSelectedListener, OnClickListener {
 	// Fields that are accessed from background threads must be volatile
 	@Inject private volatile DatabaseComponent db;
 	@Inject @DatabaseUiExecutor private volatile Executor dbUiExecutor;
+	@Inject private volatile LifecycleManager lifecycleManager;
 	private volatile LocalAuthor localAuthor = null;
 	private volatile LocalGroup localGroup = null;
 	private volatile MessageId parentId = null;
@@ -152,10 +149,6 @@ implements OnItemSelectedListener, OnClickListener {
 		layout.addView(content);
 
 		setContentView(layout);
-
-		// Bind to the service so we can wait for it to start
-		bindService(new Intent(BriarService.class.getName()),
-				serviceConnection, 0);
 	}
 
 	@Override
@@ -169,7 +162,7 @@ implements OnItemSelectedListener, OnClickListener {
 		dbUiExecutor.execute(new Runnable() {
 			public void run() {
 				try {
-					serviceConnection.waitForDatabase();
+					lifecycleManager.waitForDatabase();
 					long now = System.currentTimeMillis();
 					Collection<LocalAuthor> localAuthors = db.getLocalAuthors();
 					long duration = System.currentTimeMillis() - now;
@@ -216,7 +209,7 @@ implements OnItemSelectedListener, OnClickListener {
 		dbUiExecutor.execute(new Runnable() {
 			public void run() {
 				try {
-					serviceConnection.waitForDatabase();
+					lifecycleManager.waitForDatabase();
 					long now = System.currentTimeMillis();
 					Collection<LocalGroup> groups = db.getLocalGroups();
 					long duration = System.currentTimeMillis() - now;
@@ -269,12 +262,6 @@ implements OnItemSelectedListener, OnClickListener {
 		}
 	}
 
-	@Override
-	public void onDestroy() {
-		super.onDestroy();
-		unbindService(serviceConnection);
-	}
-
 	public void onItemSelected(AdapterView<?> parent, View view, int position,
 			long id) {
 		if(parent == fromSpinner) {
@@ -350,7 +337,7 @@ implements OnItemSelectedListener, OnClickListener {
 		dbUiExecutor.execute(new Runnable() {
 			public void run() {
 				try {
-					serviceConnection.waitForDatabase();
+					lifecycleManager.waitForDatabase();
 					long now = System.currentTimeMillis();
 					db.addLocalGroupMessage(m);
 					long duration = System.currentTimeMillis() - now;
diff --git a/briar-android/src/net/sf/briar/android/contact/ContactListActivity.java b/briar-android/src/net/sf/briar/android/contact/ContactListActivity.java
index 32f92293e95e15815704b91ef4bc82e71e353a29..061d52a10b6f3fc795e03fc20b61fabeaf9a2c3c 100644
--- a/briar-android/src/net/sf/briar/android/contact/ContactListActivity.java
+++ b/briar-android/src/net/sf/briar/android/contact/ContactListActivity.java
@@ -22,8 +22,6 @@ import java.util.concurrent.Executor;
 import java.util.logging.Logger;
 
 import net.sf.briar.R;
-import net.sf.briar.android.BriarService;
-import net.sf.briar.android.BriarService.BriarServiceConnection;
 import net.sf.briar.android.invitation.AddContactActivity;
 import net.sf.briar.android.widgets.HorizontalBorder;
 import net.sf.briar.android.widgets.HorizontalSpace;
@@ -37,6 +35,7 @@ import net.sf.briar.api.db.event.ContactAddedEvent;
 import net.sf.briar.api.db.event.ContactRemovedEvent;
 import net.sf.briar.api.db.event.DatabaseEvent;
 import net.sf.briar.api.db.event.DatabaseListener;
+import net.sf.briar.api.lifecycle.LifecycleManager;
 import net.sf.briar.api.transport.ConnectionListener;
 import net.sf.briar.api.transport.ConnectionRegistry;
 import roboguice.activity.RoboActivity;
@@ -60,9 +59,6 @@ ConnectionListener {
 	private static final Logger LOG =
 			Logger.getLogger(ContactListActivity.class.getName());
 
-	private final BriarServiceConnection serviceConnection =
-			new BriarServiceConnection();
-
 	@Inject private ConnectionRegistry connectionRegistry;
 	private ContactListAdapter adapter = null;
 	private ListView list = null;
@@ -72,6 +68,7 @@ ConnectionListener {
 	// Fields that are accessed from background threads must be volatile
 	@Inject private volatile DatabaseComponent db;
 	@Inject @DatabaseUiExecutor private volatile Executor dbUiExecutor;
+	@Inject private volatile LifecycleManager lifecycleManager;
 
 	@Override
 	public void onCreate(Bundle state) {
@@ -118,10 +115,6 @@ ConnectionListener {
 		layout.addView(footer);
 
 		setContentView(layout);
-
-		// Bind to the service so we can wait for it to start
-		bindService(new Intent(BriarService.class.getName()),
-				serviceConnection, 0);
 	}
 
 	@Override
@@ -136,7 +129,7 @@ ConnectionListener {
 		dbUiExecutor.execute(new Runnable() {
 			public void run() {
 				try {
-					serviceConnection.waitForDatabase();
+					lifecycleManager.waitForDatabase();
 					long now = System.currentTimeMillis();
 					Collection<Contact> contacts = db.getContacts();
 					Map<ContactId, Long> times = db.getLastConnected();
@@ -182,12 +175,6 @@ ConnectionListener {
 		connectionRegistry.removeListener(this);
 	}
 
-	@Override
-	public void onDestroy() {
-		super.onDestroy();
-		unbindService(serviceConnection);
-	}
-
 	public void onClick(View view) {
 		if(view == addContactButton) {
 			startActivity(new Intent(this, AddContactActivity.class));
diff --git a/briar-android/src/net/sf/briar/android/groups/ConfigureGroupActivity.java b/briar-android/src/net/sf/briar/android/groups/ConfigureGroupActivity.java
index df203717cd0de226f7fa73d9acaf507f9f23d4a3..cedcdc3ffdeab6c4631ec626f5e1847404fabd13 100644
--- a/briar-android/src/net/sf/briar/android/groups/ConfigureGroupActivity.java
+++ b/briar-android/src/net/sf/briar/android/groups/ConfigureGroupActivity.java
@@ -15,8 +15,6 @@ import java.util.concurrent.Executor;
 import java.util.logging.Logger;
 
 import net.sf.briar.R;
-import net.sf.briar.android.BriarService;
-import net.sf.briar.android.BriarService.BriarServiceConnection;
 import net.sf.briar.android.contact.SelectContactsDialog;
 import net.sf.briar.android.invitation.AddContactActivity;
 import net.sf.briar.android.messages.NoContactsDialog;
@@ -25,6 +23,7 @@ import net.sf.briar.api.ContactId;
 import net.sf.briar.api.android.DatabaseUiExecutor;
 import net.sf.briar.api.db.DatabaseComponent;
 import net.sf.briar.api.db.DbException;
+import net.sf.briar.api.lifecycle.LifecycleManager;
 import net.sf.briar.api.messaging.Group;
 import net.sf.briar.api.messaging.GroupId;
 import roboguice.activity.RoboFragmentActivity;
@@ -48,9 +47,6 @@ SelectContactsDialog.Listener {
 	private static final Logger LOG =
 			Logger.getLogger(ConfigureGroupActivity.class.getName());
 
-	private final BriarServiceConnection serviceConnection =
-			new BriarServiceConnection();
-
 	private boolean subscribed = false;
 	private CheckBox subscribeCheckBox = null;
 	private RadioGroup radioGroup = null;
@@ -61,6 +57,7 @@ SelectContactsDialog.Listener {
 	// Fields that are accessed from background threads must be volatile
 	@Inject private volatile DatabaseComponent db;
 	@Inject @DatabaseUiExecutor private volatile Executor dbUiExecutor;
+	@Inject private volatile LifecycleManager lifecycleManager;
 	private volatile Group group = null;
 	private volatile Collection<ContactId> selected = Collections.emptyList();
 
@@ -125,16 +122,6 @@ SelectContactsDialog.Listener {
 		layout.addView(progress);
 
 		setContentView(layout);
-
-		// Bind to the service so we can wait for it to start
-		bindService(new Intent(BriarService.class.getName()),
-				serviceConnection, 0);
-	}
-
-	@Override
-	public void onDestroy() {
-		super.onDestroy();
-		unbindService(serviceConnection);
 	}
 
 	public void onClick(View view) {
@@ -162,7 +149,7 @@ SelectContactsDialog.Listener {
 		dbUiExecutor.execute(new Runnable() {
 			public void run() {
 				try {
-					serviceConnection.waitForDatabase();
+					lifecycleManager.waitForDatabase();
 					long now = System.currentTimeMillis();
 					Collection<Contact> contacts = db.getContacts();
 					long duration = System.currentTimeMillis() - now;
@@ -206,7 +193,7 @@ SelectContactsDialog.Listener {
 		dbUiExecutor.execute(new Runnable() {
 			public void run() {
 				try {
-					serviceConnection.waitForDatabase();
+					lifecycleManager.waitForDatabase();
 					long now = System.currentTimeMillis();
 					if(subscribe) {
 						if(!wasSubscribed) db.subscribe(group);
diff --git a/briar-android/src/net/sf/briar/android/groups/CreateGroupActivity.java b/briar-android/src/net/sf/briar/android/groups/CreateGroupActivity.java
index e75f6fbf70566e90eba25607e03349e7f05c0110..56b5187e02d237f5496368f6af951aa9d8cc9520 100644
--- a/briar-android/src/net/sf/briar/android/groups/CreateGroupActivity.java
+++ b/briar-android/src/net/sf/briar/android/groups/CreateGroupActivity.java
@@ -20,8 +20,6 @@ import java.util.concurrent.Executor;
 import java.util.logging.Logger;
 
 import net.sf.briar.R;
-import net.sf.briar.android.BriarService;
-import net.sf.briar.android.BriarService.BriarServiceConnection;
 import net.sf.briar.android.contact.SelectContactsDialog;
 import net.sf.briar.android.invitation.AddContactActivity;
 import net.sf.briar.android.messages.NoContactsDialog;
@@ -30,6 +28,7 @@ import net.sf.briar.api.ContactId;
 import net.sf.briar.api.android.DatabaseUiExecutor;
 import net.sf.briar.api.db.DatabaseComponent;
 import net.sf.briar.api.db.DbException;
+import net.sf.briar.api.lifecycle.LifecycleManager;
 import net.sf.briar.api.messaging.Group;
 import net.sf.briar.api.messaging.GroupFactory;
 import roboguice.activity.RoboFragmentActivity;
@@ -57,9 +56,6 @@ SelectContactsDialog.Listener {
 	private static final Logger LOG =
 			Logger.getLogger(CreateGroupActivity.class.getName());
 
-	private final BriarServiceConnection serviceConnection =
-			new BriarServiceConnection();
-
 	private EditText nameEntry = null;
 	private RadioGroup radioGroup = null;
 	private RadioButton visibleToAll = null, visibleToSome = null;
@@ -70,6 +66,7 @@ SelectContactsDialog.Listener {
 	@Inject private volatile GroupFactory groupFactory;
 	@Inject private volatile DatabaseComponent db;
 	@Inject @DatabaseUiExecutor private volatile Executor dbUiExecutor;
+	@Inject private volatile LifecycleManager lifecycleManager;
 	private volatile Collection<ContactId> selected = Collections.emptyList();
 
 	@Override
@@ -129,10 +126,6 @@ SelectContactsDialog.Listener {
 		layout.addView(progress);
 
 		setContentView(layout);
-
-		// Bind to the service so we can wait for it to start
-		bindService(new Intent(BriarService.class.getName()),
-				serviceConnection, 0);
 	}
 
 	private void enableOrDisableCreateButton() {
@@ -143,12 +136,6 @@ SelectContactsDialog.Listener {
 		createButton.setEnabled(nameNotEmpty && visibilitySelected);
 	}
 
-	@Override
-	public void onDestroy() {
-		super.onDestroy();
-		unbindService(serviceConnection);
-	}
-
 	public boolean onEditorAction(TextView textView, int actionId, KeyEvent e) {
 		validateName();
 		return true;
@@ -172,7 +159,7 @@ SelectContactsDialog.Listener {
 			dbUiExecutor.execute(new Runnable() {
 				public void run() {
 					try {
-						serviceConnection.waitForDatabase();
+						lifecycleManager.waitForDatabase();
 						Group g = groupFactory.createGroup(name);
 						long now = System.currentTimeMillis();
 						db.subscribe(g);
@@ -206,7 +193,7 @@ SelectContactsDialog.Listener {
 		dbUiExecutor.execute(new Runnable() {
 			public void run() {
 				try {
-					serviceConnection.waitForDatabase();
+					lifecycleManager.waitForDatabase();
 					long now = System.currentTimeMillis();
 					Collection<Contact> contacts = db.getContacts();
 					long duration = System.currentTimeMillis() - now;
diff --git a/briar-android/src/net/sf/briar/android/groups/GroupActivity.java b/briar-android/src/net/sf/briar/android/groups/GroupActivity.java
index 0ea0adb590c2f3974cc1f32c80cbc991ed26c83e..c7de96f65c64a13bcf497ef7a0d1175f14a91c9a 100644
--- a/briar-android/src/net/sf/briar/android/groups/GroupActivity.java
+++ b/briar-android/src/net/sf/briar/android/groups/GroupActivity.java
@@ -17,8 +17,6 @@ import java.util.logging.Logger;
 
 import net.sf.briar.R;
 import net.sf.briar.android.AscendingHeaderComparator;
-import net.sf.briar.android.BriarService;
-import net.sf.briar.android.BriarService.BriarServiceConnection;
 import net.sf.briar.android.widgets.HorizontalBorder;
 import net.sf.briar.android.widgets.ListLoadingProgressBar;
 import net.sf.briar.api.Author;
@@ -33,6 +31,7 @@ import net.sf.briar.api.db.event.GroupMessageAddedEvent;
 import net.sf.briar.api.db.event.MessageExpiredEvent;
 import net.sf.briar.api.db.event.RatingChangedEvent;
 import net.sf.briar.api.db.event.SubscriptionRemovedEvent;
+import net.sf.briar.api.lifecycle.LifecycleManager;
 import net.sf.briar.api.messaging.GroupId;
 import roboguice.activity.RoboActivity;
 import android.content.Intent;
@@ -53,9 +52,6 @@ OnClickListener, OnItemClickListener {
 	private static final Logger LOG =
 			Logger.getLogger(GroupActivity.class.getName());
 
-	private final BriarServiceConnection serviceConnection =
-			new BriarServiceConnection();
-
 	private String groupName = null;
 	private GroupAdapter adapter = null;
 	private ListView list = null;
@@ -64,6 +60,7 @@ OnClickListener, OnItemClickListener {
 	// Fields that are accessed from background threads must be volatile
 	@Inject private volatile DatabaseComponent db;
 	@Inject @DatabaseUiExecutor private volatile Executor dbUiExecutor;
+	@Inject private volatile LifecycleManager lifecycleManager;
 	private volatile GroupId groupId = null;
 
 	@Override
@@ -105,10 +102,6 @@ OnClickListener, OnItemClickListener {
 		layout.addView(composeButton);
 
 		setContentView(layout);
-
-		// Bind to the service so we can wait for it to start
-		bindService(new Intent(BriarService.class.getName()),
-				serviceConnection, 0);
 	}
 
 	@Override
@@ -122,7 +115,7 @@ OnClickListener, OnItemClickListener {
 		dbUiExecutor.execute(new Runnable() {
 			public void run() {
 				try {
-					serviceConnection.waitForDatabase();
+					lifecycleManager.waitForDatabase();
 					long now = System.currentTimeMillis();
 					Collection<GroupMessageHeader> headers =
 							db.getGroupMessageHeaders(groupId);
@@ -194,12 +187,6 @@ OnClickListener, OnItemClickListener {
 		db.removeListener(this);
 	}
 
-	@Override
-	public void onDestroy() {
-		super.onDestroy();
-		unbindService(serviceConnection);
-	}
-
 	public void eventOccurred(DatabaseEvent e) {
 		if(e instanceof GroupMessageAddedEvent) {
 			GroupMessageAddedEvent g = (GroupMessageAddedEvent) e;
diff --git a/briar-android/src/net/sf/briar/android/groups/GroupListActivity.java b/briar-android/src/net/sf/briar/android/groups/GroupListActivity.java
index 725ea96f669173205793ae554054bde663d8eb70..bd4d274656e010d3a6fb84d718fbd09b928feed7 100644
--- a/briar-android/src/net/sf/briar/android/groups/GroupListActivity.java
+++ b/briar-android/src/net/sf/briar/android/groups/GroupListActivity.java
@@ -19,8 +19,6 @@ import java.util.concurrent.Executor;
 import java.util.logging.Logger;
 
 import net.sf.briar.R;
-import net.sf.briar.android.BriarService;
-import net.sf.briar.android.BriarService.BriarServiceConnection;
 import net.sf.briar.android.widgets.HorizontalBorder;
 import net.sf.briar.android.widgets.HorizontalSpace;
 import net.sf.briar.android.widgets.ListLoadingProgressBar;
@@ -36,6 +34,7 @@ import net.sf.briar.api.db.event.MessageExpiredEvent;
 import net.sf.briar.api.db.event.RemoteSubscriptionsUpdatedEvent;
 import net.sf.briar.api.db.event.SubscriptionAddedEvent;
 import net.sf.briar.api.db.event.SubscriptionRemovedEvent;
+import net.sf.briar.api.lifecycle.LifecycleManager;
 import net.sf.briar.api.messaging.Group;
 import net.sf.briar.api.messaging.GroupId;
 import net.sf.briar.api.messaging.GroupStatus;
@@ -59,9 +58,6 @@ OnItemClickListener {
 	private static final Logger LOG =
 			Logger.getLogger(GroupListActivity.class.getName());
 
-	private final BriarServiceConnection serviceConnection =
-			new BriarServiceConnection();
-
 	private GroupListAdapter adapter = null;
 	private ListView list = null;
 	private ListLoadingProgressBar loading = null;
@@ -71,6 +67,7 @@ OnItemClickListener {
 	// Fields that are accessed from background threads must be volatile
 	@Inject private volatile DatabaseComponent db;
 	@Inject @DatabaseUiExecutor private volatile Executor dbUiExecutor;
+	@Inject private volatile LifecycleManager lifecycleManager;
 
 	@Override
 	public void onCreate(Bundle state) {
@@ -124,10 +121,6 @@ OnItemClickListener {
 		layout.addView(footer);
 
 		setContentView(layout);
-
-		// Bind to the service so we can wait for it to start
-		bindService(new Intent(BriarService.class.getName()),
-				serviceConnection, 0);
 	}
 
 	@Override
@@ -142,7 +135,7 @@ OnItemClickListener {
 		dbUiExecutor.execute(new Runnable() {
 			public void run() {
 				try {
-					serviceConnection.waitForDatabase();
+					lifecycleManager.waitForDatabase();
 					int available = 0;
 					long now = System.currentTimeMillis();
 					for(GroupStatus s : db.getAvailableGroups()) {
@@ -247,12 +240,6 @@ OnItemClickListener {
 		db.removeListener(this);
 	}
 
-	@Override
-	public void onDestroy() {
-		super.onDestroy();
-		unbindService(serviceConnection);
-	}
-
 	public void eventOccurred(DatabaseEvent e) {
 		if(e instanceof GroupMessageAddedEvent) {
 			Group g = ((GroupMessageAddedEvent) e).getGroup();
@@ -287,7 +274,7 @@ OnItemClickListener {
 		dbUiExecutor.execute(new Runnable() {
 			public void run() {
 				try {
-					serviceConnection.waitForDatabase();
+					lifecycleManager.waitForDatabase();
 					long now = System.currentTimeMillis();
 					Collection<GroupMessageHeader> headers =
 							db.getGroupMessageHeaders(g.getId());
@@ -327,7 +314,7 @@ OnItemClickListener {
 		dbUiExecutor.execute(new Runnable() {
 			public void run() {
 				try {
-					serviceConnection.waitForDatabase();
+					lifecycleManager.waitForDatabase();
 					int available = 0;
 					long now = System.currentTimeMillis();
 					for(GroupStatus s : db.getAvailableGroups()) {
diff --git a/briar-android/src/net/sf/briar/android/groups/ManageGroupsActivity.java b/briar-android/src/net/sf/briar/android/groups/ManageGroupsActivity.java
index aae133ce177b5ca91069ead8879a209d242c6a9b..390de0e280b0e34d80b922b1c9154e9923a4448c 100644
--- a/briar-android/src/net/sf/briar/android/groups/ManageGroupsActivity.java
+++ b/briar-android/src/net/sf/briar/android/groups/ManageGroupsActivity.java
@@ -13,8 +13,6 @@ import java.util.List;
 import java.util.concurrent.Executor;
 import java.util.logging.Logger;
 
-import net.sf.briar.android.BriarService;
-import net.sf.briar.android.BriarService.BriarServiceConnection;
 import net.sf.briar.android.widgets.ListLoadingProgressBar;
 import net.sf.briar.api.android.DatabaseUiExecutor;
 import net.sf.briar.api.db.DatabaseComponent;
@@ -24,6 +22,7 @@ import net.sf.briar.api.db.event.DatabaseListener;
 import net.sf.briar.api.db.event.RemoteSubscriptionsUpdatedEvent;
 import net.sf.briar.api.db.event.SubscriptionAddedEvent;
 import net.sf.briar.api.db.event.SubscriptionRemovedEvent;
+import net.sf.briar.api.lifecycle.LifecycleManager;
 import net.sf.briar.api.messaging.Group;
 import net.sf.briar.api.messaging.GroupStatus;
 import roboguice.activity.RoboFragmentActivity;
@@ -42,9 +41,6 @@ implements DatabaseListener, OnItemClickListener {
 	private static final Logger LOG =
 			Logger.getLogger(ManageGroupsActivity.class.getName());
 
-	private final BriarServiceConnection serviceConnection =
-			new BriarServiceConnection();
-
 	private ManageGroupsAdapter adapter = null;
 	private ListView list = null;
 	private ListLoadingProgressBar loading = null;
@@ -52,6 +48,7 @@ implements DatabaseListener, OnItemClickListener {
 	// Fields that are accessed from background threads must be volatile
 	@Inject private volatile DatabaseComponent db;
 	@Inject @DatabaseUiExecutor private volatile Executor dbUiExecutor;
+	@Inject private volatile LifecycleManager lifecycleManager;
 
 	@Override
 	public void onCreate(Bundle state) {
@@ -66,10 +63,6 @@ implements DatabaseListener, OnItemClickListener {
 		// Show a progress bar while the list is loading
 		loading = new ListLoadingProgressBar(this);
 		setContentView(loading);
-
-		// Bind to the service so we can wait for it to start
-		bindService(new Intent(BriarService.class.getName()),
-				serviceConnection, 0);
 	}
 
 	@Override
@@ -83,7 +76,7 @@ implements DatabaseListener, OnItemClickListener {
 		dbUiExecutor.execute(new Runnable() {
 			public void run() {
 				try {
-					serviceConnection.waitForDatabase();
+					lifecycleManager.waitForDatabase();
 					long now = System.currentTimeMillis();
 					List<GroupStatus> available = new ArrayList<GroupStatus>();
 					for(GroupStatus s : db.getAvailableGroups())
@@ -125,12 +118,6 @@ implements DatabaseListener, OnItemClickListener {
 		db.removeListener(this);
 	}
 
-	@Override
-	public void onDestroy() {
-		super.onDestroy();
-		unbindService(serviceConnection);
-	}
-
 	public void eventOccurred(DatabaseEvent e) {
 		if(e instanceof RemoteSubscriptionsUpdatedEvent) {
 			if(LOG.isLoggable(INFO))
diff --git a/briar-android/src/net/sf/briar/android/groups/ReadGroupPostActivity.java b/briar-android/src/net/sf/briar/android/groups/ReadGroupPostActivity.java
index df401605653415ec07be2974428d3e94bc81a3e1..097ce6024af32311721b58ea336bb13525d45b70 100644
--- a/briar-android/src/net/sf/briar/android/groups/ReadGroupPostActivity.java
+++ b/briar-android/src/net/sf/briar/android/groups/ReadGroupPostActivity.java
@@ -19,8 +19,6 @@ import java.util.concurrent.Executor;
 import java.util.logging.Logger;
 
 import net.sf.briar.R;
-import net.sf.briar.android.BriarService;
-import net.sf.briar.android.BriarService.BriarServiceConnection;
 import net.sf.briar.android.widgets.HorizontalBorder;
 import net.sf.briar.android.widgets.HorizontalSpace;
 import net.sf.briar.api.AuthorId;
@@ -28,6 +26,7 @@ import net.sf.briar.api.android.DatabaseUiExecutor;
 import net.sf.briar.api.db.DatabaseComponent;
 import net.sf.briar.api.db.DbException;
 import net.sf.briar.api.db.NoSuchMessageException;
+import net.sf.briar.api.lifecycle.LifecycleManager;
 import net.sf.briar.api.messaging.GroupId;
 import net.sf.briar.api.messaging.MessageId;
 import net.sf.briar.api.messaging.Rating;
@@ -56,9 +55,6 @@ implements OnClickListener {
 	private static final Logger LOG =
 			Logger.getLogger(ReadGroupPostActivity.class.getName());
 
-	private final BriarServiceConnection serviceConnection =
-			new BriarServiceConnection();
-
 	private GroupId groupId = null;
 	private Rating rating = UNRATED;
 	private boolean read;
@@ -71,6 +67,7 @@ implements OnClickListener {
 	// Fields that are accessed from background threads must be volatile
 	@Inject private volatile DatabaseComponent db;
 	@Inject @DatabaseUiExecutor private volatile Executor dbUiExecutor;
+	@Inject private volatile LifecycleManager lifecycleManager;
 	private volatile MessageId messageId = null;
 	private volatile AuthorId authorId = null;
 
@@ -220,17 +217,13 @@ implements OnClickListener {
 		layout.addView(footer);
 
 		setContentView(layout);
-
-		// Bind to the service so we can wait for it to start
-		bindService(new Intent(BriarService.class.getName()),
-				serviceConnection, 0);
 	}
 
 	private void setReadInDatabase(final boolean read) {
 		dbUiExecutor.execute(new Runnable() {
 			public void run() {
 				try {
-					serviceConnection.waitForDatabase();
+					lifecycleManager.waitForDatabase();
 					long now = System.currentTimeMillis();
 					db.setReadFlag(messageId, read);
 					long duration = System.currentTimeMillis() - now;
@@ -263,7 +256,7 @@ implements OnClickListener {
 		dbUiExecutor.execute(new Runnable() {
 			public void run() {
 				try {
-					serviceConnection.waitForDatabase();
+					lifecycleManager.waitForDatabase();
 					long now = System.currentTimeMillis();
 					byte[] body = db.getMessageBody(messageId);
 					long duration = System.currentTimeMillis() - now;
@@ -302,12 +295,6 @@ implements OnClickListener {
 		state.putBoolean("net.sf.briar.READ", read);
 	}
 
-	@Override
-	public void onDestroy() {
-		super.onDestroy();
-		unbindService(serviceConnection);
-	}
-
 	public void onClick(View view) {
 		if(view == goodButton) {
 			if(rating == BAD) setRatingInDatabase(UNRATED);
@@ -337,7 +324,7 @@ implements OnClickListener {
 		dbUiExecutor.execute(new Runnable() {
 			public void run() {
 				try {
-					serviceConnection.waitForDatabase();
+					lifecycleManager.waitForDatabase();
 					long now = System.currentTimeMillis();
 					db.setRating(authorId, r);
 					long duration = System.currentTimeMillis() - now;
diff --git a/briar-android/src/net/sf/briar/android/groups/WriteGroupPostActivity.java b/briar-android/src/net/sf/briar/android/groups/WriteGroupPostActivity.java
index 70d72e288aab16c2ebe9c5f6ea36550980abf993..f3168ad89c7eca0b6200561a8dad6a953c6a9509 100644
--- a/briar-android/src/net/sf/briar/android/groups/WriteGroupPostActivity.java
+++ b/briar-android/src/net/sf/briar/android/groups/WriteGroupPostActivity.java
@@ -20,8 +20,6 @@ import java.util.concurrent.Executor;
 import java.util.logging.Logger;
 
 import net.sf.briar.R;
-import net.sf.briar.android.BriarService;
-import net.sf.briar.android.BriarService.BriarServiceConnection;
 import net.sf.briar.android.identity.CreateIdentityActivity;
 import net.sf.briar.android.identity.LocalAuthorItem;
 import net.sf.briar.android.identity.LocalAuthorItemComparator;
@@ -34,6 +32,7 @@ import net.sf.briar.api.crypto.CryptoComponent;
 import net.sf.briar.api.crypto.KeyParser;
 import net.sf.briar.api.db.DatabaseComponent;
 import net.sf.briar.api.db.DbException;
+import net.sf.briar.api.lifecycle.LifecycleManager;
 import net.sf.briar.api.messaging.Group;
 import net.sf.briar.api.messaging.GroupId;
 import net.sf.briar.api.messaging.Message;
@@ -61,9 +60,6 @@ implements OnItemSelectedListener, OnClickListener {
 	private static final Logger LOG =
 			Logger.getLogger(WriteGroupPostActivity.class.getName());
 
-	private final BriarServiceConnection serviceConnection =
-			new BriarServiceConnection();
-
 	@Inject private CryptoComponent crypto;
 	@Inject private MessageFactory messageFactory;
 	private LocalAuthorSpinnerAdapter fromAdapter = null;
@@ -77,6 +73,7 @@ implements OnItemSelectedListener, OnClickListener {
 	// Fields that are accessed from background threads must be volatile
 	@Inject private volatile DatabaseComponent db;
 	@Inject @DatabaseUiExecutor private volatile Executor dbUiExecutor;
+	@Inject private volatile LifecycleManager lifecycleManager;
 	private volatile LocalAuthor localAuthor = null;
 	private volatile Group group = null;
 	private volatile MessageId parentId = null;
@@ -155,10 +152,6 @@ implements OnItemSelectedListener, OnClickListener {
 		layout.addView(content);
 
 		setContentView(layout);
-
-		// Bind to the service so we can wait for it to start
-		bindService(new Intent(BriarService.class.getName()),
-				serviceConnection, 0);
 	}
 
 	@Override
@@ -172,7 +165,7 @@ implements OnItemSelectedListener, OnClickListener {
 		dbUiExecutor.execute(new Runnable() {
 			public void run() {
 				try {
-					serviceConnection.waitForDatabase();
+					lifecycleManager.waitForDatabase();
 					long now = System.currentTimeMillis();
 					Collection<LocalAuthor> localAuthors = db.getLocalAuthors();
 					long duration = System.currentTimeMillis() - now;
@@ -219,7 +212,7 @@ implements OnItemSelectedListener, OnClickListener {
 		dbUiExecutor.execute(new Runnable() {
 			public void run() {
 				try {
-					serviceConnection.waitForDatabase();
+					lifecycleManager.waitForDatabase();
 					List<Group> groups = new ArrayList<Group>();
 					long now = System.currentTimeMillis();
 					for(Group g : db.getSubscriptions())
@@ -274,12 +267,6 @@ implements OnItemSelectedListener, OnClickListener {
 		}
 	}
 
-	@Override
-	public void onDestroy() {
-		super.onDestroy();
-		unbindService(serviceConnection);
-	}
-
 	public void onItemSelected(AdapterView<?> parent, View view, int position,
 			long id) {
 		if(parent == fromSpinner) {
@@ -352,7 +339,7 @@ implements OnItemSelectedListener, OnClickListener {
 		dbUiExecutor.execute(new Runnable() {
 			public void run() {
 				try {
-					serviceConnection.waitForDatabase();
+					lifecycleManager.waitForDatabase();
 					long now = System.currentTimeMillis();
 					db.addLocalGroupMessage(m);
 					long duration = System.currentTimeMillis() - now;
diff --git a/briar-android/src/net/sf/briar/android/identity/CreateIdentityActivity.java b/briar-android/src/net/sf/briar/android/identity/CreateIdentityActivity.java
index 48f1c6bfb960a0555a1c36ef34b4eb6e20d0a4f1..71e82cfbdde91193c24342c261ffa982b96a7221 100644
--- a/briar-android/src/net/sf/briar/android/identity/CreateIdentityActivity.java
+++ b/briar-android/src/net/sf/briar/android/identity/CreateIdentityActivity.java
@@ -20,8 +20,6 @@ import java.util.concurrent.Executor;
 import java.util.logging.Logger;
 
 import net.sf.briar.R;
-import net.sf.briar.android.BriarService;
-import net.sf.briar.android.BriarService.BriarServiceConnection;
 import net.sf.briar.api.AuthorFactory;
 import net.sf.briar.api.LocalAuthor;
 import net.sf.briar.api.android.DatabaseUiExecutor;
@@ -29,8 +27,8 @@ import net.sf.briar.api.crypto.CryptoComponent;
 import net.sf.briar.api.crypto.CryptoExecutor;
 import net.sf.briar.api.db.DatabaseComponent;
 import net.sf.briar.api.db.DbException;
+import net.sf.briar.api.lifecycle.LifecycleManager;
 import roboguice.activity.RoboActivity;
-import android.content.Intent;
 import android.os.Bundle;
 import android.view.KeyEvent;
 import android.view.View;
@@ -51,9 +49,6 @@ implements OnEditorActionListener, OnClickListener {
 	private static final Logger LOG =
 			Logger.getLogger(CreateIdentityActivity.class.getName());
 
-	private final BriarServiceConnection serviceConnection =
-			new BriarServiceConnection();
-
 	@Inject @CryptoExecutor private Executor cryptoExecutor;
 	private EditText nicknameEntry = null;
 	private Button createButton = null;
@@ -64,6 +59,7 @@ implements OnEditorActionListener, OnClickListener {
 	@Inject private volatile AuthorFactory authorFactory;
 	@Inject private volatile DatabaseComponent db;
 	@Inject @DatabaseUiExecutor private volatile Executor dbUiExecutor;
+	@Inject private volatile LifecycleManager lifecycleManager;
 
 	@Override
 	public void onCreate(Bundle state) {
@@ -109,16 +105,6 @@ implements OnEditorActionListener, OnClickListener {
 		layout.addView(progress);
 
 		setContentView(layout);
-
-		// Bind to the service so we can wait for it to start
-		bindService(new Intent(BriarService.class.getName()),
-				serviceConnection, 0);
-	}
-
-	@Override
-	public void onDestroy() {
-		super.onDestroy();
-		unbindService(serviceConnection);
 	}
 
 	public boolean onEditorAction(TextView textView, int actionId, KeyEvent e) {
@@ -154,7 +140,7 @@ implements OnEditorActionListener, OnClickListener {
 		dbUiExecutor.execute(new Runnable() {
 			public void run() {
 				try {
-					serviceConnection.waitForDatabase();
+					lifecycleManager.waitForDatabase();
 					long now = System.currentTimeMillis();
 					db.addLocalAuthor(a);
 					db.setRating(a.getId(), GOOD);
diff --git a/briar-android/src/net/sf/briar/android/invitation/AddContactActivity.java b/briar-android/src/net/sf/briar/android/invitation/AddContactActivity.java
index 926ce1df0e891921122d7844705cef21f35801b4..204df03fe08ab8609d8c279fcaeeeed0f3d7596f 100644
--- a/briar-android/src/net/sf/briar/android/invitation/AddContactActivity.java
+++ b/briar-android/src/net/sf/briar/android/invitation/AddContactActivity.java
@@ -12,8 +12,6 @@ import java.util.Collection;
 import java.util.concurrent.Executor;
 import java.util.logging.Logger;
 
-import net.sf.briar.android.BriarService;
-import net.sf.briar.android.BriarService.BriarServiceConnection;
 import net.sf.briar.android.identity.LocalAuthorItem;
 import net.sf.briar.android.identity.LocalAuthorItemComparator;
 import net.sf.briar.android.identity.LocalAuthorSpinnerAdapter;
@@ -28,6 +26,7 @@ import net.sf.briar.api.invitation.InvitationListener;
 import net.sf.briar.api.invitation.InvitationState;
 import net.sf.briar.api.invitation.InvitationTask;
 import net.sf.briar.api.invitation.InvitationTaskFactory;
+import net.sf.briar.api.lifecycle.LifecycleManager;
 import roboguice.activity.RoboActivity;
 import android.bluetooth.BluetoothAdapter;
 import android.content.BroadcastReceiver;
@@ -46,9 +45,6 @@ implements InvitationListener {
 	private static final Logger LOG =
 			Logger.getLogger(AddContactActivity.class.getName());
 
-	private final BriarServiceConnection serviceConnection =
-			new BriarServiceConnection();
-
 	@Inject private CryptoComponent crypto;
 	@Inject private InvitationTaskFactory invitationTaskFactory;
 	@Inject private ReferenceManager referenceManager;
@@ -69,6 +65,7 @@ implements InvitationListener {
 	// Fields that are accessed from background threads must be volatile
 	@Inject private volatile DatabaseComponent db;
 	@Inject @DatabaseUiExecutor private volatile Executor dbUiExecutor;
+	@Inject private volatile LifecycleManager lifecycleManager;
 
 	@Override
 	public void onCreate(Bundle state) {
@@ -159,10 +156,6 @@ implements InvitationListener {
 			if(info.getNetworkId() != -1) networkName = info.getSSID();
 		}
 		view.wifiStateChanged();
-
-		// Bind to the service so we can wait for it to start
-		bindService(new Intent(BriarService.class.getName()),
-				serviceConnection, 0);
 	}
 
 	@Override
@@ -190,7 +183,6 @@ implements InvitationListener {
 		super.onDestroy();
 		if(task != null) task.removeListener(this);
 		unregisterReceiver(receiver);
-		unbindService(serviceConnection);
 	}
 
 	void setView(AddContactView view) {
@@ -216,7 +208,7 @@ implements InvitationListener {
 		dbUiExecutor.execute(new Runnable() {
 			public void run() {
 				try {
-					serviceConnection.waitForDatabase();
+					lifecycleManager.waitForDatabase();
 					long now = System.currentTimeMillis();
 					Collection<LocalAuthor> localAuthors = db.getLocalAuthors();
 					long duration = System.currentTimeMillis() - now;
diff --git a/briar-android/src/net/sf/briar/android/messages/ConversationActivity.java b/briar-android/src/net/sf/briar/android/messages/ConversationActivity.java
index 31b28f953ad6111f694ab9c7b999d1cd65c0e5a9..3c6fc5cce9e05369b8a700e6282588b46df65c81 100644
--- a/briar-android/src/net/sf/briar/android/messages/ConversationActivity.java
+++ b/briar-android/src/net/sf/briar/android/messages/ConversationActivity.java
@@ -15,8 +15,6 @@ import java.util.logging.Logger;
 
 import net.sf.briar.R;
 import net.sf.briar.android.AscendingHeaderComparator;
-import net.sf.briar.android.BriarService;
-import net.sf.briar.android.BriarService.BriarServiceConnection;
 import net.sf.briar.android.widgets.HorizontalBorder;
 import net.sf.briar.android.widgets.ListLoadingProgressBar;
 import net.sf.briar.api.AuthorId;
@@ -31,6 +29,7 @@ import net.sf.briar.api.db.event.DatabaseEvent;
 import net.sf.briar.api.db.event.DatabaseListener;
 import net.sf.briar.api.db.event.MessageExpiredEvent;
 import net.sf.briar.api.db.event.PrivateMessageAddedEvent;
+import net.sf.briar.api.lifecycle.LifecycleManager;
 import roboguice.activity.RoboActivity;
 import android.content.Intent;
 import android.os.Bundle;
@@ -50,9 +49,6 @@ implements DatabaseListener, OnClickListener, OnItemClickListener {
 	private static final Logger LOG =
 			Logger.getLogger(ConversationActivity.class.getName());
 
-	private final BriarServiceConnection serviceConnection =
-			new BriarServiceConnection();
-
 	private String contactName = null;
 	private ConversationAdapter adapter = null;
 	private ListView list = null;
@@ -61,6 +57,7 @@ implements DatabaseListener, OnClickListener, OnItemClickListener {
 	// Fields that are accessed from background threads must be volatile
 	@Inject private volatile DatabaseComponent db;
 	@Inject @DatabaseUiExecutor private volatile Executor dbUiExecutor;
+	@Inject private volatile LifecycleManager lifecycleManager;
 	private volatile ContactId contactId = null;
 	private volatile AuthorId localAuthorId = null;
 
@@ -106,10 +103,6 @@ implements DatabaseListener, OnClickListener, OnItemClickListener {
 		layout.addView(composeButton);
 
 		setContentView(layout);
-
-		// Bind to the service so we can wait for it to start
-		bindService(new Intent(BriarService.class.getName()),
-				serviceConnection, 0);
 	}
 
 	@Override
@@ -123,7 +116,7 @@ implements DatabaseListener, OnClickListener, OnItemClickListener {
 		dbUiExecutor.execute(new Runnable() {
 			public void run() {
 				try {
-					serviceConnection.waitForDatabase();
+					lifecycleManager.waitForDatabase();
 					long now = System.currentTimeMillis();
 					Collection<PrivateMessageHeader> headers =
 							db.getPrivateMessageHeaders(contactId);
@@ -196,12 +189,6 @@ implements DatabaseListener, OnClickListener, OnItemClickListener {
 		db.removeListener(this);
 	}
 
-	@Override
-	public void onDestroy() {
-		super.onDestroy();
-		unbindService(serviceConnection);
-	}
-
 	public void eventOccurred(DatabaseEvent e) {
 		if(e instanceof ContactRemovedEvent) {
 			ContactRemovedEvent c = (ContactRemovedEvent) e;
diff --git a/briar-android/src/net/sf/briar/android/messages/ConversationListActivity.java b/briar-android/src/net/sf/briar/android/messages/ConversationListActivity.java
index 9486a615068b7d320175b920c198f968e83826ad..2ffb72ba6d7a23e630627b161b5ad0652a0076d3 100644
--- a/briar-android/src/net/sf/briar/android/messages/ConversationListActivity.java
+++ b/briar-android/src/net/sf/briar/android/messages/ConversationListActivity.java
@@ -15,8 +15,6 @@ import java.util.concurrent.Executor;
 import java.util.logging.Logger;
 
 import net.sf.briar.R;
-import net.sf.briar.android.BriarService;
-import net.sf.briar.android.BriarService.BriarServiceConnection;
 import net.sf.briar.android.invitation.AddContactActivity;
 import net.sf.briar.android.widgets.HorizontalBorder;
 import net.sf.briar.android.widgets.ListLoadingProgressBar;
@@ -32,6 +30,7 @@ import net.sf.briar.api.db.event.DatabaseEvent;
 import net.sf.briar.api.db.event.DatabaseListener;
 import net.sf.briar.api.db.event.MessageExpiredEvent;
 import net.sf.briar.api.db.event.PrivateMessageAddedEvent;
+import net.sf.briar.api.lifecycle.LifecycleManager;
 import roboguice.activity.RoboFragmentActivity;
 import android.content.Intent;
 import android.os.Bundle;
@@ -49,9 +48,6 @@ implements OnClickListener, DatabaseListener, NoContactsDialog.Listener {
 	private static final Logger LOG =
 			Logger.getLogger(ConversationListActivity.class.getName());
 
-	private final BriarServiceConnection serviceConnection =
-			new BriarServiceConnection();
-
 	private ConversationListAdapter adapter = null;
 	private ListView list = null;
 	private ListLoadingProgressBar loading = null;
@@ -59,6 +55,7 @@ implements OnClickListener, DatabaseListener, NoContactsDialog.Listener {
 	// Fields that are accessed from background threads must be volatile
 	@Inject private volatile DatabaseComponent db;
 	@Inject @DatabaseUiExecutor private volatile Executor dbUiExecutor;
+	@Inject private volatile LifecycleManager lifecycleManager;
 
 	@Override
 	public void onCreate(Bundle state) {
@@ -90,10 +87,6 @@ implements OnClickListener, DatabaseListener, NoContactsDialog.Listener {
 		layout.addView(composeButton);
 
 		setContentView(layout);
-
-		// Bind to the service so we can wait for it to start
-		bindService(new Intent(BriarService.class.getName()),
-				serviceConnection, 0);
 	}
 
 	@Override
@@ -108,7 +101,7 @@ implements OnClickListener, DatabaseListener, NoContactsDialog.Listener {
 		dbUiExecutor.execute(new Runnable() {
 			public void run() {
 				try {
-					serviceConnection.waitForDatabase();
+					lifecycleManager.waitForDatabase();
 					long now = System.currentTimeMillis();
 					for(Contact c : db.getContacts()) {
 						try {
@@ -191,12 +184,6 @@ implements OnClickListener, DatabaseListener, NoContactsDialog.Listener {
 		db.removeListener(this);
 	}
 
-	@Override
-	public void onDestroy() {
-		super.onDestroy();
-		unbindService(serviceConnection);
-	}
-
 	public void onClick(View view) {
 		if(adapter.isEmpty()) {
 			NoContactsDialog dialog = new NoContactsDialog();
@@ -225,7 +212,7 @@ implements OnClickListener, DatabaseListener, NoContactsDialog.Listener {
 		dbUiExecutor.execute(new Runnable() {
 			public void run() {
 				try {
-					serviceConnection.waitForDatabase();
+					lifecycleManager.waitForDatabase();
 					long now = System.currentTimeMillis();
 					Contact contact = db.getContact(c);
 					Collection<PrivateMessageHeader> headers =
diff --git a/briar-android/src/net/sf/briar/android/messages/ReadPrivateMessageActivity.java b/briar-android/src/net/sf/briar/android/messages/ReadPrivateMessageActivity.java
index 5d6245229a2ad648380cd11e01053584c4e91ba2..5a66b32039897d452719ebc70de3c3ece2dc8120 100644
--- a/briar-android/src/net/sf/briar/android/messages/ReadPrivateMessageActivity.java
+++ b/briar-android/src/net/sf/briar/android/messages/ReadPrivateMessageActivity.java
@@ -19,8 +19,6 @@ import java.util.concurrent.Executor;
 import java.util.logging.Logger;
 
 import net.sf.briar.R;
-import net.sf.briar.android.BriarService;
-import net.sf.briar.android.BriarService.BriarServiceConnection;
 import net.sf.briar.android.widgets.HorizontalBorder;
 import net.sf.briar.android.widgets.HorizontalSpace;
 import net.sf.briar.api.ContactId;
@@ -28,6 +26,7 @@ import net.sf.briar.api.android.DatabaseUiExecutor;
 import net.sf.briar.api.db.DatabaseComponent;
 import net.sf.briar.api.db.DbException;
 import net.sf.briar.api.db.NoSuchMessageException;
+import net.sf.briar.api.lifecycle.LifecycleManager;
 import net.sf.briar.api.messaging.MessageId;
 import net.sf.briar.api.messaging.Rating;
 import roboguice.activity.RoboActivity;
@@ -55,9 +54,6 @@ implements OnClickListener {
 	private static final Logger LOG =
 			Logger.getLogger(ReadPrivateMessageActivity.class.getName());
 
-	private final BriarServiceConnection serviceConnection =
-			new BriarServiceConnection();
-
 	private ContactId contactId = null;
 	private Rating rating = UNRATED;
 	private boolean read;
@@ -68,6 +64,7 @@ implements OnClickListener {
 	// Fields that are accessed from background threads must be volatile
 	@Inject private volatile DatabaseComponent db;
 	@Inject @DatabaseUiExecutor private volatile Executor dbUiExecutor;
+	@Inject private volatile LifecycleManager lifecycleManager;
 	private volatile MessageId messageId = null;
 
 	@Override
@@ -190,17 +187,13 @@ implements OnClickListener {
 		layout.addView(footer);
 
 		setContentView(layout);
-
-		// Bind to the service so we can wait for it to start
-		bindService(new Intent(BriarService.class.getName()),
-				serviceConnection, 0);
 	}
 
 	private void setReadInDatabase(final boolean read) {
 		dbUiExecutor.execute(new Runnable() {
 			public void run() {
 				try {
-					serviceConnection.waitForDatabase();
+					lifecycleManager.waitForDatabase();
 					long now = System.currentTimeMillis();
 					db.setReadFlag(messageId, read);
 					long duration = System.currentTimeMillis() - now;
@@ -233,7 +226,7 @@ implements OnClickListener {
 		dbUiExecutor.execute(new Runnable() {
 			public void run() {
 				try {
-					serviceConnection.waitForDatabase();
+					lifecycleManager.waitForDatabase();
 					long now = System.currentTimeMillis();
 					byte[] body = db.getMessageBody(messageId);
 					long duration = System.currentTimeMillis() - now;
@@ -272,12 +265,6 @@ implements OnClickListener {
 		state.putBoolean("net.sf.briar.READ", read);
 	}
 
-	@Override
-	public void onDestroy() {
-		super.onDestroy();
-		unbindService(serviceConnection);
-	}
-
 	public void onClick(View view) {
 		if(view == readButton) {
 			setReadInDatabase(!read);
diff --git a/briar-android/src/net/sf/briar/android/messages/WritePrivateMessageActivity.java b/briar-android/src/net/sf/briar/android/messages/WritePrivateMessageActivity.java
index f0b4b6bac2f5322b0de038a851f61d9ebb12b523..78c46c01718c74d6712ae0ce6e4b75cd5a85da77 100644
--- a/briar-android/src/net/sf/briar/android/messages/WritePrivateMessageActivity.java
+++ b/briar-android/src/net/sf/briar/android/messages/WritePrivateMessageActivity.java
@@ -17,8 +17,6 @@ import java.util.concurrent.Executor;
 import java.util.logging.Logger;
 
 import net.sf.briar.R;
-import net.sf.briar.android.BriarService;
-import net.sf.briar.android.BriarService.BriarServiceConnection;
 import net.sf.briar.android.contact.ContactItem;
 import net.sf.briar.android.contact.ContactItemComparator;
 import net.sf.briar.android.contact.ContactSpinnerAdapter;
@@ -31,6 +29,7 @@ import net.sf.briar.api.LocalAuthor;
 import net.sf.briar.api.android.DatabaseUiExecutor;
 import net.sf.briar.api.db.DatabaseComponent;
 import net.sf.briar.api.db.DbException;
+import net.sf.briar.api.lifecycle.LifecycleManager;
 import net.sf.briar.api.messaging.Message;
 import net.sf.briar.api.messaging.MessageFactory;
 import net.sf.briar.api.messaging.MessageId;
@@ -56,9 +55,6 @@ implements OnItemSelectedListener, OnClickListener {
 	private static final Logger LOG =
 			Logger.getLogger(WritePrivateMessageActivity.class.getName());
 
-	private final BriarServiceConnection serviceConnection =
-			new BriarServiceConnection();
-
 	private TextView from = null;
 	private ContactSpinnerAdapter adapter = null;
 	private Spinner spinner = null;
@@ -68,6 +64,7 @@ implements OnItemSelectedListener, OnClickListener {
 	// Fields that are accessed from background threads must be volatile
 	@Inject private volatile DatabaseComponent db;
 	@Inject @DatabaseUiExecutor private volatile Executor dbUiExecutor;
+	@Inject private volatile LifecycleManager lifecycleManager;
 	@Inject private volatile MessageFactory messageFactory;
 	private volatile LocalAuthor localAuthor = null;
 	private volatile ContactId contactId = null;
@@ -139,10 +136,6 @@ implements OnItemSelectedListener, OnClickListener {
 		layout.addView(content);
 
 		setContentView(layout);
-
-		// Bind to the service so we can wait for it to start
-		bindService(new Intent(BriarService.class.getName()),
-				serviceConnection, 0);
 	}
 
 	@Override
@@ -155,7 +148,7 @@ implements OnItemSelectedListener, OnClickListener {
 		dbUiExecutor.execute(new Runnable() {
 			public void run() {
 				try {
-					serviceConnection.waitForDatabase();
+					lifecycleManager.waitForDatabase();
 					long now = System.currentTimeMillis();
 					Collection<Contact> contacts = db.getContacts();
 					long duration = System.currentTimeMillis() - now;
@@ -201,12 +194,6 @@ implements OnItemSelectedListener, OnClickListener {
 			state.putInt("net.sf.briar.CONTACT_ID", contactId.getInt());
 	}
 
-	@Override
-	public void onDestroy() {
-		super.onDestroy();
-		unbindService(serviceConnection);
-	}
-
 	public void onItemSelected(AdapterView<?> parent, View view, int position,
 			long id) {
 		ContactItem item = adapter.getItem(position);
@@ -227,7 +214,7 @@ implements OnItemSelectedListener, OnClickListener {
 		dbUiExecutor.execute(new Runnable() {
 			public void run() {
 				try {
-					serviceConnection.waitForDatabase();
+					lifecycleManager.waitForDatabase();
 					long now = System.currentTimeMillis();
 					localAuthor = db.getLocalAuthor(a);
 					long duration = System.currentTimeMillis() - now;
@@ -277,7 +264,7 @@ implements OnItemSelectedListener, OnClickListener {
 		dbUiExecutor.execute(new Runnable() {
 			public void run() {
 				try {
-					serviceConnection.waitForDatabase();
+					lifecycleManager.waitForDatabase();
 					Message m = messageFactory.createPrivateMessage(parentId,
 							"text/plain", body);
 					long now = System.currentTimeMillis();
diff --git a/briar-api/src/net/sf/briar/api/lifecycle/LifecycleManager.java b/briar-api/src/net/sf/briar/api/lifecycle/LifecycleManager.java
new file mode 100644
index 0000000000000000000000000000000000000000..acbe586ae9d91348faeaea62ea002248670ca927
--- /dev/null
+++ b/briar-api/src/net/sf/briar/api/lifecycle/LifecycleManager.java
@@ -0,0 +1,19 @@
+package net.sf.briar.api.lifecycle;
+
+public interface LifecycleManager {
+
+	/** Starts any services that need to be started at startup. */
+	public void startServices();
+
+	/** Stops any services that need to be stopped at shutdown. */
+	public void stopServices();
+
+	/** Waits for the database to be opened before returning. */
+	public void waitForDatabase() throws InterruptedException;
+
+	/** Waits for all services to start before returning. */
+	public void waitForStartup() throws InterruptedException;
+
+	/** Waits for all services to stop before returning. */
+	public void waitForShutdown() throws InterruptedException;
+}
diff --git a/briar-core/src/net/sf/briar/reliability/ReliabilityExecutor.java b/briar-api/src/net/sf/briar/api/reliability/ReliabilityExecutor.java
similarity index 82%
rename from briar-core/src/net/sf/briar/reliability/ReliabilityExecutor.java
rename to briar-api/src/net/sf/briar/api/reliability/ReliabilityExecutor.java
index d5ce36047531c95876261ea6102444581875b019..a7ca755f3da933c883e3657d9c053376a22445fd 100644
--- a/briar-core/src/net/sf/briar/reliability/ReliabilityExecutor.java
+++ b/briar-api/src/net/sf/briar/api/reliability/ReliabilityExecutor.java
@@ -1,4 +1,4 @@
-package net.sf.briar.reliability;
+package net.sf.briar.api.reliability;
 
 import static java.lang.annotation.ElementType.PARAMETER;
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
@@ -12,4 +12,4 @@ import com.google.inject.BindingAnnotation;
 @BindingAnnotation
 @Target({ PARAMETER })
 @Retention(RUNTIME)
-@interface ReliabilityExecutor {}
\ No newline at end of file
+public @interface ReliabilityExecutor {}
\ No newline at end of file
diff --git a/briar-core/src/net/sf/briar/transport/IncomingConnectionExecutor.java b/briar-api/src/net/sf/briar/api/transport/IncomingConnectionExecutor.java
similarity index 82%
rename from briar-core/src/net/sf/briar/transport/IncomingConnectionExecutor.java
rename to briar-api/src/net/sf/briar/api/transport/IncomingConnectionExecutor.java
index 6bad0c126c04b805fc7820b30ffef90aeec10d92..e3048a1eb5730c2d09fd11b3f872cd539b47d879 100644
--- a/briar-core/src/net/sf/briar/transport/IncomingConnectionExecutor.java
+++ b/briar-api/src/net/sf/briar/api/transport/IncomingConnectionExecutor.java
@@ -1,4 +1,4 @@
-package net.sf.briar.transport;
+package net.sf.briar.api.transport;
 
 import static java.lang.annotation.ElementType.PARAMETER;
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
@@ -14,4 +14,4 @@ import com.google.inject.BindingAnnotation;
 @BindingAnnotation
 @Target({ PARAMETER })
 @Retention(RUNTIME)
-@interface IncomingConnectionExecutor {}
\ No newline at end of file
+public @interface IncomingConnectionExecutor {}
\ No newline at end of file
diff --git a/briar-core/src/net/sf/briar/crypto/CryptoModule.java b/briar-core/src/net/sf/briar/crypto/CryptoModule.java
index d006d07c8ca4627acd035eba6fa119c1b3f67500..2f23bfc6306408c862433de96d5be7e026aa8a56 100644
--- a/briar-core/src/net/sf/briar/crypto/CryptoModule.java
+++ b/briar-core/src/net/sf/briar/crypto/CryptoModule.java
@@ -1,27 +1,22 @@
 package net.sf.briar.crypto;
 
+import static java.util.concurrent.TimeUnit.SECONDS;
+
+import java.util.concurrent.BlockingQueue;
 import java.util.concurrent.Executor;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.RejectedExecutionHandler;
+import java.util.concurrent.ThreadPoolExecutor;
 
 import net.sf.briar.api.crypto.CryptoComponent;
 import net.sf.briar.api.crypto.CryptoExecutor;
-import net.sf.briar.util.BoundedExecutor;
 
 import com.google.inject.AbstractModule;
 import com.google.inject.Singleton;
 
 public class CryptoModule extends AbstractModule {
 
-	// FIXME: Determine suitable values for these constants empirically
-
-	/**
-	 * The maximum number of tasks that can be queued for execution before
-	 * submitting another task will block.
-	 */
-	private static final int MAX_QUEUED_EXECUTOR_TASKS = 10;
-
-	/** The minimum number of executor threads to keep in the pool. */
-	private static final int MIN_EXECUTOR_THREADS = 1;
-
 	/** The maximum number of executor threads. */
 	private static final int MAX_EXECUTOR_THREADS =
 			Runtime.getRuntime().availableProcessors();
@@ -30,9 +25,16 @@ public class CryptoModule extends AbstractModule {
 	protected void configure() {
 		bind(CryptoComponent.class).to(
 				CryptoComponentImpl.class).in(Singleton.class);
-		// The executor is bounded, so tasks must be independent and short-lived
-		bind(Executor.class).annotatedWith(CryptoExecutor.class).toInstance(
-				new BoundedExecutor(MAX_QUEUED_EXECUTOR_TASKS,
-						MIN_EXECUTOR_THREADS, MAX_EXECUTOR_THREADS));
+		// The queue is unbounded, so tasks can be dependent
+		BlockingQueue<Runnable> queue = new LinkedBlockingQueue<Runnable>();
+		// Discard tasks that are submitted during shutdown
+		RejectedExecutionHandler policy =
+				new ThreadPoolExecutor.DiscardPolicy();
+		// Create a limited # of threads and keep them in the pool for 60 secs
+		ExecutorService e = new ThreadPoolExecutor(0, MAX_EXECUTOR_THREADS,
+				60, SECONDS, queue, policy);
+		bind(Executor.class).annotatedWith(CryptoExecutor.class).toInstance(e);
+		bind(ExecutorService.class).annotatedWith(
+				CryptoExecutor.class).toInstance(e);
 	}
 }
diff --git a/briar-core/src/net/sf/briar/db/DatabaseModule.java b/briar-core/src/net/sf/briar/db/DatabaseModule.java
index a500f9cf93a5272decfcd0794f4ace09a4085917..e038be9c9ae854f5914c3d9d924221a4526fadb9 100644
--- a/briar-core/src/net/sf/briar/db/DatabaseModule.java
+++ b/briar-core/src/net/sf/briar/db/DatabaseModule.java
@@ -1,11 +1,13 @@
 package net.sf.briar.db;
 
-import static java.util.concurrent.TimeUnit.MILLISECONDS;
+import static java.util.concurrent.TimeUnit.SECONDS;
 
 import java.sql.Connection;
 import java.util.concurrent.BlockingQueue;
 import java.util.concurrent.Executor;
+import java.util.concurrent.ExecutorService;
 import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.RejectedExecutionHandler;
 import java.util.concurrent.ThreadPoolExecutor;
 
 import net.sf.briar.api.clock.Clock;
@@ -21,24 +23,24 @@ import com.google.inject.Singleton;
 
 public class DatabaseModule extends AbstractModule {
 
-	/**
-	 * The maximum number of database threads. When a task is submitted to the
-	 * database executor and no thread is available to run it, the task will be
-	 * queued.
-	 */
-	private static final int MAX_DB_THREADS = 10;
-
-	/** How many milliseconds to keep idle threads alive. */
-	private static final int DB_KEEPALIVE = 60 * 1000;
+	/** The maximum number of executor threads. */
+	private static final int MAX_EXECUTOR_THREADS = 10;
 
 	@Override
 	protected void configure() {
 		bind(DatabaseCleaner.class).to(DatabaseCleanerImpl.class);
-		// Use an unbounded queue to prevent deadlock between submitted tasks
+		// The queue is unbounded, so tasks can be dependent
 		BlockingQueue<Runnable> queue = new LinkedBlockingQueue<Runnable>();
-		bind(Executor.class).annotatedWith(DatabaseExecutor.class).toInstance(
-				new ThreadPoolExecutor(MAX_DB_THREADS, MAX_DB_THREADS,
-						DB_KEEPALIVE, MILLISECONDS, queue));
+		// Discard tasks that are submitted during shutdown
+		RejectedExecutionHandler policy =
+				new ThreadPoolExecutor.DiscardPolicy();
+		// Create a limited # of threads and keep them in the pool for 60 secs
+		ExecutorService e = new ThreadPoolExecutor(0, MAX_EXECUTOR_THREADS,
+				60, SECONDS, queue, policy);
+		bind(Executor.class).annotatedWith(
+				DatabaseExecutor.class).toInstance(e);
+		bind(ExecutorService.class).annotatedWith(
+				DatabaseExecutor.class).toInstance(e);
 	}
 
 	@Provides
diff --git a/briar-core/src/net/sf/briar/lifecycle/LifecycleManagerImpl.java b/briar-core/src/net/sf/briar/lifecycle/LifecycleManagerImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..d0c414cf31ff302a2a12b3b983028a559badc030
--- /dev/null
+++ b/briar-core/src/net/sf/briar/lifecycle/LifecycleManagerImpl.java
@@ -0,0 +1,116 @@
+package net.sf.briar.lifecycle;
+
+import static java.util.logging.Level.INFO;
+import static java.util.logging.Level.WARNING;
+
+import java.io.IOException;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.logging.Logger;
+
+import net.sf.briar.api.crypto.CryptoExecutor;
+import net.sf.briar.api.crypto.KeyManager;
+import net.sf.briar.api.db.DatabaseComponent;
+import net.sf.briar.api.db.DatabaseExecutor;
+import net.sf.briar.api.db.DbException;
+import net.sf.briar.api.lifecycle.LifecycleManager;
+import net.sf.briar.api.plugins.PluginExecutor;
+import net.sf.briar.api.plugins.PluginManager;
+import net.sf.briar.api.reliability.ReliabilityExecutor;
+import net.sf.briar.api.transport.IncomingConnectionExecutor;
+
+import com.google.inject.Inject;
+
+class LifecycleManagerImpl implements LifecycleManager {
+
+	private static final Logger LOG =
+			Logger.getLogger(LifecycleManagerImpl.class.getName());
+
+	private final DatabaseComponent db;
+	private final KeyManager keyManager;
+	private final PluginManager pluginManager;
+	private final ExecutorService cryptoExecutor;
+	private final ExecutorService dbExecutor;
+	private final ExecutorService connExecutor;
+	private final ExecutorService pluginExecutor;
+	private final ExecutorService reliabilityExecutor;
+	private final CountDownLatch dbLatch = new CountDownLatch(1);
+	private final CountDownLatch startupLatch = new CountDownLatch(1);
+	private final CountDownLatch shutdownLatch = new CountDownLatch(1);
+
+	@Inject
+	LifecycleManagerImpl(DatabaseComponent db, KeyManager keyManager,
+			PluginManager pluginManager,
+			@CryptoExecutor ExecutorService cryptoExecutor,
+			@DatabaseExecutor ExecutorService dbExecutor,
+			@IncomingConnectionExecutor ExecutorService connExecutor,
+			@PluginExecutor ExecutorService pluginExecutor,
+			@ReliabilityExecutor ExecutorService reliabilityExecutor) {
+		this.db = db;
+		this.keyManager = keyManager;
+		this.pluginManager = pluginManager;
+		this.cryptoExecutor = cryptoExecutor;
+		this.dbExecutor = dbExecutor;
+		this.connExecutor = connExecutor;
+		this.pluginExecutor = pluginExecutor;
+		this.reliabilityExecutor = reliabilityExecutor;
+	}
+
+	public void startServices() {
+		try {
+			if(LOG.isLoggable(INFO)) LOG.info("Starting");
+			boolean reopened = db.open();
+			if(LOG.isLoggable(INFO)) {
+				if(reopened) LOG.info("Database reopened");
+				else LOG.info("Database created");
+			}
+			dbLatch.countDown();
+			keyManager.start();
+			if(LOG.isLoggable(INFO)) LOG.info("Key manager started");
+			int pluginsStarted = pluginManager.start();
+			if(LOG.isLoggable(INFO))
+				LOG.info(pluginsStarted + " plugins started");
+			startupLatch.countDown();
+		} catch(DbException e) {
+			if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
+		} catch(IOException e) {
+			if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
+		}
+	}
+
+	public void stopServices() {
+		try {
+			if(LOG.isLoggable(INFO)) LOG.info("Shutting down");
+			int pluginsStopped = pluginManager.stop();
+			if(LOG.isLoggable(INFO))
+				LOG.info(pluginsStopped + " plugins stopped");
+			keyManager.stop();
+			if(LOG.isLoggable(INFO)) LOG.info("Key manager stopped");
+			db.close();
+			if(LOG.isLoggable(INFO)) LOG.info("Database closed");
+			cryptoExecutor.shutdownNow();
+			dbExecutor.shutdownNow();
+			connExecutor.shutdownNow();
+			pluginExecutor.shutdownNow();
+			reliabilityExecutor.shutdownNow();
+			if(LOG.isLoggable(INFO)) LOG.info("Executors shut down");
+			shutdownLatch.countDown();
+		} catch(DbException e) {
+			if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
+		} catch(IOException e) {
+			if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
+		}
+	}
+
+	public void waitForDatabase() throws InterruptedException {
+		dbLatch.await();
+	}
+
+	public void waitForStartup() throws InterruptedException {
+		startupLatch.await();
+	}
+
+	public void waitForShutdown() throws InterruptedException {
+		shutdownLatch.await();
+	}
+}
diff --git a/briar-core/src/net/sf/briar/lifecycle/LifecycleModule.java b/briar-core/src/net/sf/briar/lifecycle/LifecycleModule.java
index 241e41a09ae3963d87ceb47586e8d975744b3031..f4b214dea4ecd3acb50a195af420191e8bf3535b 100644
--- a/briar-core/src/net/sf/briar/lifecycle/LifecycleModule.java
+++ b/briar-core/src/net/sf/briar/lifecycle/LifecycleModule.java
@@ -1,14 +1,18 @@
 package net.sf.briar.lifecycle;
 
+import net.sf.briar.api.lifecycle.LifecycleManager;
 import net.sf.briar.api.lifecycle.ShutdownManager;
 import net.sf.briar.util.OsUtils;
 
 import com.google.inject.AbstractModule;
+import com.google.inject.Singleton;
 
 public class LifecycleModule extends AbstractModule {
 
 	@Override
 	protected void configure() {
+		bind(LifecycleManager.class).to(LifecycleManagerImpl.class).in(
+				Singleton.class);
 		if(OsUtils.isWindows())
 			bind(ShutdownManager.class).to(WindowsShutdownManagerImpl.class);
 		else bind(ShutdownManager.class).to(ShutdownManagerImpl.class);
diff --git a/briar-core/src/net/sf/briar/plugins/JavaSePluginsModule.java b/briar-core/src/net/sf/briar/plugins/JavaSePluginsModule.java
index 894f99df467425d44e835b5ff9876deaff7540a1..762a32e0afd650b61f98b5e7b9ab978eb10dab94 100644
--- a/briar-core/src/net/sf/briar/plugins/JavaSePluginsModule.java
+++ b/briar-core/src/net/sf/briar/plugins/JavaSePluginsModule.java
@@ -2,7 +2,7 @@ package net.sf.briar.plugins;
 
 import java.util.Arrays;
 import java.util.Collection;
-import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executor;
 
 import net.sf.briar.api.crypto.CryptoComponent;
 import net.sf.briar.api.lifecycle.ShutdownManager;
@@ -28,7 +28,7 @@ public class JavaSePluginsModule extends AbstractModule {
 
 	@Provides
 	SimplexPluginConfig getSimplexPluginConfig(
-			@PluginExecutor ExecutorService pluginExecutor) {
+			@PluginExecutor Executor pluginExecutor) {
 		SimplexPluginFactory removable =
 				new RemovableDrivePluginFactory(pluginExecutor);
 		final Collection<SimplexPluginFactory> factories =
@@ -42,7 +42,7 @@ public class JavaSePluginsModule extends AbstractModule {
 
 	@Provides
 	DuplexPluginConfig getDuplexPluginConfig(
-			@PluginExecutor ExecutorService pluginExecutor,
+			@PluginExecutor Executor pluginExecutor,
 			CryptoComponent crypto, ReliabilityLayerFactory reliabilityFactory,
 			ShutdownManager shutdownManager) {
 		DuplexPluginFactory bluetooth = new BluetoothPluginFactory(
diff --git a/briar-core/src/net/sf/briar/plugins/PluginManagerImpl.java b/briar-core/src/net/sf/briar/plugins/PluginManagerImpl.java
index 90b69bda0bb794fdd3f79eae614f68389a8ade09..bd259b8e595107d5bbce62055ae21d5256ba1443 100644
--- a/briar-core/src/net/sf/briar/plugins/PluginManagerImpl.java
+++ b/briar-core/src/net/sf/briar/plugins/PluginManagerImpl.java
@@ -11,7 +11,7 @@ import java.util.List;
 import java.util.Map;
 import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executor;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.logging.Logger;
 
@@ -19,7 +19,6 @@ import net.sf.briar.api.ContactId;
 import net.sf.briar.api.TransportConfig;
 import net.sf.briar.api.TransportId;
 import net.sf.briar.api.TransportProperties;
-import net.sf.briar.api.android.AndroidExecutor;
 import net.sf.briar.api.db.DatabaseComponent;
 import net.sf.briar.api.db.DbException;
 import net.sf.briar.api.plugins.Plugin;
@@ -49,8 +48,7 @@ class PluginManagerImpl implements PluginManager {
 	private static final Logger LOG =
 			Logger.getLogger(PluginManagerImpl.class.getName());
 
-	private final ExecutorService pluginExecutor;
-	private final AndroidExecutor androidExecutor;
+	private final Executor pluginExecutor;
 	private final SimplexPluginConfig simplexPluginConfig;
 	private final DuplexPluginConfig duplexPluginConfig;
 	private final DatabaseComponent db;
@@ -61,14 +59,12 @@ class PluginManagerImpl implements PluginManager {
 	private final List<DuplexPlugin> duplexPlugins;
 
 	@Inject
-	PluginManagerImpl(@PluginExecutor ExecutorService pluginExecutor,
-			AndroidExecutor androidExecutor,
+	PluginManagerImpl(@PluginExecutor Executor pluginExecutor,
 			SimplexPluginConfig simplexPluginConfig, 
 			DuplexPluginConfig duplexPluginConfig, DatabaseComponent db,
 			Poller poller, ConnectionDispatcher dispatcher,
 			UiCallback uiCallback) {
 		this.pluginExecutor = pluginExecutor;
-		this.androidExecutor = androidExecutor;
 		this.simplexPluginConfig = simplexPluginConfig;
 		this.duplexPluginConfig = duplexPluginConfig;
 		this.db = db;
@@ -144,10 +140,6 @@ class PluginManagerImpl implements PluginManager {
 			Thread.currentThread().interrupt();
 			return 0;
 		}
-		// Shut down the executors
-		if(LOG.isLoggable(INFO)) LOG.info("Stopping executors");
-		pluginExecutor.shutdown();
-		androidExecutor.shutdown();
 		// Return the number of plugins successfully stopped
 		return stopped.get();
 	}
diff --git a/briar-core/src/net/sf/briar/plugins/PluginsModule.java b/briar-core/src/net/sf/briar/plugins/PluginsModule.java
index 3653e6bb2b064942ea0786ab90ddea2d84c34fcd..6b584b38ac2acc57047cbeeba0bd7149f44b543b 100644
--- a/briar-core/src/net/sf/briar/plugins/PluginsModule.java
+++ b/briar-core/src/net/sf/briar/plugins/PluginsModule.java
@@ -1,7 +1,13 @@
 package net.sf.briar.plugins;
 
+import static java.util.concurrent.TimeUnit.SECONDS;
+
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.Executor;
 import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
+import java.util.concurrent.RejectedExecutionHandler;
+import java.util.concurrent.SynchronousQueue;
+import java.util.concurrent.ThreadPoolExecutor;
 
 import net.sf.briar.api.plugins.PluginExecutor;
 import net.sf.briar.api.plugins.PluginManager;
@@ -13,12 +19,19 @@ public class PluginsModule extends AbstractModule {
 
 	@Override
 	protected void configure() {
-		// The executor is unbounded, so tasks can be dependent or long-lived
-		ExecutorService e = Executors.newCachedThreadPool();
-		bind(ExecutorService.class).annotatedWith(
-				PluginExecutor.class).toInstance(e);
 		bind(PluginManager.class).to(
 				PluginManagerImpl.class).in(Singleton.class);
 		bind(Poller.class).to(PollerImpl.class);
+		// The thread pool is unbounded, so use direct handoff
+		BlockingQueue<Runnable> queue = new SynchronousQueue<Runnable>();
+		// Discard tasks that are submitted during shutdown
+		RejectedExecutionHandler policy =
+				new ThreadPoolExecutor.DiscardPolicy();
+		// Create threads as required and keep them in the pool for 60 seconds
+		ExecutorService e = new ThreadPoolExecutor(0, Integer.MAX_VALUE,
+				60, SECONDS, queue, policy);
+		bind(Executor.class).annotatedWith(PluginExecutor.class).toInstance(e);
+		bind(ExecutorService.class).annotatedWith(
+				PluginExecutor.class).toInstance(e);
 	}
 }
diff --git a/briar-core/src/net/sf/briar/plugins/PollerImpl.java b/briar-core/src/net/sf/briar/plugins/PollerImpl.java
index c5ea77feaba4897a7a6a28fe3289b1c52eb3736b..30a356feef1aacd5eaf62f7c42236dd5c26ba855 100644
--- a/briar-core/src/net/sf/briar/plugins/PollerImpl.java
+++ b/briar-core/src/net/sf/briar/plugins/PollerImpl.java
@@ -5,7 +5,7 @@ import static java.util.logging.Level.INFO;
 import java.util.Collection;
 import java.util.SortedSet;
 import java.util.TreeSet;
-import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executor;
 import java.util.logging.Logger;
 
 import net.sf.briar.api.ContactId;
@@ -21,13 +21,13 @@ class PollerImpl implements Poller, Runnable {
 	private static final Logger LOG =
 			Logger.getLogger(PollerImpl.class.getName());
 
-	private final ExecutorService pluginExecutor;
+	private final Executor pluginExecutor;
 	private final ConnectionRegistry connRegistry;
 	private final Clock clock;
 	private final SortedSet<PollTime> pollTimes;
 
 	@Inject
-	PollerImpl(@PluginExecutor ExecutorService pluginExecutor,
+	PollerImpl(@PluginExecutor Executor pluginExecutor,
 			ConnectionRegistry connRegistry, Clock clock) {
 		this.pluginExecutor = pluginExecutor;
 		this.connRegistry = connRegistry;
@@ -71,7 +71,7 @@ class PollerImpl implements Poller, Runnable {
 							connRegistry.getConnectedContacts(p.plugin.getId());
 					if(LOG.isLoggable(INFO))
 						LOG.info("Polling " + p.plugin.getClass().getName());
-					pluginExecutor.submit(new Runnable() {
+					pluginExecutor.execute(new Runnable() {
 						public void run() {
 							p.plugin.poll(connected);
 						}
diff --git a/briar-core/src/net/sf/briar/reliability/ReliabilityLayerFactoryImpl.java b/briar-core/src/net/sf/briar/reliability/ReliabilityLayerFactoryImpl.java
index 4b207b1cda0de7fdca28561f9e7c479cf161bba0..411f817b0737682cb00b447573d266f16f713681 100644
--- a/briar-core/src/net/sf/briar/reliability/ReliabilityLayerFactoryImpl.java
+++ b/briar-core/src/net/sf/briar/reliability/ReliabilityLayerFactoryImpl.java
@@ -4,6 +4,7 @@ import java.util.concurrent.Executor;
 
 import net.sf.briar.api.clock.Clock;
 import net.sf.briar.api.clock.SystemClock;
+import net.sf.briar.api.reliability.ReliabilityExecutor;
 import net.sf.briar.api.reliability.ReliabilityLayer;
 import net.sf.briar.api.reliability.ReliabilityLayerFactory;
 import net.sf.briar.api.reliability.WriteHandler;
diff --git a/briar-core/src/net/sf/briar/reliability/ReliabilityModule.java b/briar-core/src/net/sf/briar/reliability/ReliabilityModule.java
index a30ae78d7980b2fcb6efab4cacbf198476906ae2..37d4a610efa64c15a2b5568f93741a0c9f765f5b 100644
--- a/briar-core/src/net/sf/briar/reliability/ReliabilityModule.java
+++ b/briar-core/src/net/sf/briar/reliability/ReliabilityModule.java
@@ -1,8 +1,15 @@
 package net.sf.briar.reliability;
 
+import static java.util.concurrent.TimeUnit.SECONDS;
+
+import java.util.concurrent.BlockingQueue;
 import java.util.concurrent.Executor;
-import java.util.concurrent.Executors;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.RejectedExecutionHandler;
+import java.util.concurrent.SynchronousQueue;
+import java.util.concurrent.ThreadPoolExecutor;
 
+import net.sf.briar.api.reliability.ReliabilityExecutor;
 import net.sf.briar.api.reliability.ReliabilityLayerFactory;
 
 import com.google.inject.AbstractModule;
@@ -11,11 +18,19 @@ public class ReliabilityModule extends AbstractModule {
 
 	@Override
 	protected void configure() {
-		// The executor is unbounded - tasks are expected to be long-lived
-		Executor e = Executors.newCachedThreadPool();
-		bind(Executor.class).annotatedWith(
-				ReliabilityExecutor.class).toInstance(e);
 		bind(ReliabilityLayerFactory.class).to(
 				ReliabilityLayerFactoryImpl.class);
+		// The thread pool is unbounded, so use direct handoff
+		BlockingQueue<Runnable> queue = new SynchronousQueue<Runnable>();
+		// Discard tasks that are submitted during shutdown
+		RejectedExecutionHandler policy =
+				new ThreadPoolExecutor.DiscardPolicy();
+		// Create threads as required and keep them in the pool for 60 seconds
+		ExecutorService e = new ThreadPoolExecutor(0, Integer.MAX_VALUE,
+				60, SECONDS, queue, policy);
+		bind(Executor.class).annotatedWith(
+				ReliabilityExecutor.class).toInstance(e);
+		bind(ExecutorService.class).annotatedWith(
+				ReliabilityExecutor.class).toInstance(e);
 	}
 }
diff --git a/briar-core/src/net/sf/briar/transport/ConnectionDispatcherImpl.java b/briar-core/src/net/sf/briar/transport/ConnectionDispatcherImpl.java
index ddb612ba03f005ff7bad1e31ea1a8680372dc0a7..ecd57aa2b73f5846223b77ac95d4137b006cc293 100644
--- a/briar-core/src/net/sf/briar/transport/ConnectionDispatcherImpl.java
+++ b/briar-core/src/net/sf/briar/transport/ConnectionDispatcherImpl.java
@@ -20,6 +20,7 @@ import net.sf.briar.api.plugins.simplex.SimplexTransportWriter;
 import net.sf.briar.api.transport.ConnectionContext;
 import net.sf.briar.api.transport.ConnectionDispatcher;
 import net.sf.briar.api.transport.ConnectionRecogniser;
+import net.sf.briar.api.transport.IncomingConnectionExecutor;
 
 import com.google.inject.Inject;
 
diff --git a/briar-core/src/net/sf/briar/transport/TransportModule.java b/briar-core/src/net/sf/briar/transport/TransportModule.java
index 0cd57082f90724d6a5b80d2bd5c597f6597bf2c1..4985bc0654ecf61b69546f6b0c9b2c70a5fa6061 100644
--- a/briar-core/src/net/sf/briar/transport/TransportModule.java
+++ b/briar-core/src/net/sf/briar/transport/TransportModule.java
@@ -1,7 +1,13 @@
 package net.sf.briar.transport;
 
+import static java.util.concurrent.TimeUnit.SECONDS;
+
+import java.util.concurrent.BlockingQueue;
 import java.util.concurrent.Executor;
-import java.util.concurrent.Executors;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.RejectedExecutionHandler;
+import java.util.concurrent.SynchronousQueue;
+import java.util.concurrent.ThreadPoolExecutor;
 
 import net.sf.briar.api.crypto.KeyManager;
 import net.sf.briar.api.transport.ConnectionDispatcher;
@@ -9,6 +15,7 @@ import net.sf.briar.api.transport.ConnectionReaderFactory;
 import net.sf.briar.api.transport.ConnectionRecogniser;
 import net.sf.briar.api.transport.ConnectionRegistry;
 import net.sf.briar.api.transport.ConnectionWriterFactory;
+import net.sf.briar.api.transport.IncomingConnectionExecutor;
 
 import com.google.inject.AbstractModule;
 import com.google.inject.Singleton;
@@ -25,10 +32,18 @@ public class TransportModule extends AbstractModule {
 		bind(ConnectionRegistry.class).toInstance(new ConnectionRegistryImpl());
 		bind(ConnectionWriterFactory.class).to(
 				ConnectionWriterFactoryImpl.class);
-		// The executor is unbounded, so tasks can be dependent or long-lived
-		Executor e = Executors.newCachedThreadPool();
+		bind(KeyManager.class).to(KeyManagerImpl.class).in(Singleton.class);
+		// The thread pool is unbounded, so use direct handoff
+		BlockingQueue<Runnable> queue = new SynchronousQueue<Runnable>();
+		// Discard tasks that are submitted during shutdown
+		RejectedExecutionHandler policy =
+				new ThreadPoolExecutor.DiscardPolicy();
+		// Create threads as required and keep them in the pool for 60 seconds
+		ExecutorService e = new ThreadPoolExecutor(0, Integer.MAX_VALUE,
+				60, SECONDS, queue, policy);
 		bind(Executor.class).annotatedWith(
 				IncomingConnectionExecutor.class).toInstance(e);
-		bind(KeyManager.class).to(KeyManagerImpl.class).in(Singleton.class);
+		bind(ExecutorService.class).annotatedWith(
+				IncomingConnectionExecutor.class).toInstance(e);
 	}
 }
diff --git a/briar-core/src/net/sf/briar/util/BoundedExecutor.java b/briar-core/src/net/sf/briar/util/BoundedExecutor.java
deleted file mode 100644
index c7d77a8af253aec96a4c56cac6442785834b1b5d..0000000000000000000000000000000000000000
--- a/briar-core/src/net/sf/briar/util/BoundedExecutor.java
+++ /dev/null
@@ -1,58 +0,0 @@
-package net.sf.briar.util;
-
-import static java.util.concurrent.TimeUnit.SECONDS;
-import static java.util.logging.Level.INFO;
-import static java.util.logging.Level.WARNING;
-
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.Executor;
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.RejectedExecutionException;
-import java.util.concurrent.Semaphore;
-import java.util.concurrent.ThreadPoolExecutor;
-import java.util.logging.Logger;
-
-/**
- * An executor that limits the number of concurrently executing tasks and the
- * number of tasks queued for execution.
- */
-public class BoundedExecutor implements Executor {
-
-	private static final Logger LOG =
-		Logger.getLogger(BoundedExecutor.class.getName());
-
-	private final Semaphore semaphore;
-	private final BlockingQueue<Runnable> queue;
-	private final Executor executor;
-
-	public BoundedExecutor(int maxQueued, int minThreads, int maxThreads) {
-		semaphore = new Semaphore(maxQueued + maxThreads);
-		queue = new LinkedBlockingQueue<Runnable>();
-		executor = new ThreadPoolExecutor(minThreads, maxThreads, 60, SECONDS,
-				queue);
-	}
-
-	public void execute(final Runnable r) {
-		try {
-			semaphore.acquire();
-			executor.execute(new Runnable() {
-				public void run() {
-					try {
-						r.run();
-					} finally {
-						semaphore.release();
-					}
-				}
-			});
-		} catch(InterruptedException e) {
-			if(LOG.isLoggable(INFO))
-				LOG.info("Interrupted while queueing task");
-			Thread.currentThread().interrupt();
-			throw new RejectedExecutionException();
-		} catch(RejectedExecutionException e) {
-			if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
-			semaphore.release();
-			throw e;
-		}
-	}
-}
diff --git a/briar-tests/src/net/sf/briar/ProtocolIntegrationTest.java b/briar-tests/src/net/sf/briar/ProtocolIntegrationTest.java
index 76e5fecb386037b6e2d3b1cdfc2b93ec4f19ab99..ee0be61085707cf7bc221b0e0a0efd8098a7691f 100644
--- a/briar-tests/src/net/sf/briar/ProtocolIntegrationTest.java
+++ b/briar-tests/src/net/sf/briar/ProtocolIntegrationTest.java
@@ -48,6 +48,9 @@ import net.sf.briar.lifecycle.LifecycleModule;
 import net.sf.briar.messaging.MessagingModule;
 import net.sf.briar.messaging.duplex.DuplexMessagingModule;
 import net.sf.briar.messaging.simplex.SimplexMessagingModule;
+import net.sf.briar.plugins.JavaSePluginsModule;
+import net.sf.briar.plugins.PluginsModule;
+import net.sf.briar.reliability.ReliabilityModule;
 import net.sf.briar.serial.SerialModule;
 import net.sf.briar.transport.TransportModule;
 
@@ -79,9 +82,11 @@ public class ProtocolIntegrationTest extends BriarTestCase {
 	public ProtocolIntegrationTest() throws Exception {
 		super();
 		Injector i = Guice.createInjector(new TestDatabaseModule(),
-				new ClockModule(), new CryptoModule(), new DatabaseModule(),
-				new LifecycleModule(), new MessagingModule(),
-				new DuplexMessagingModule(), new SimplexMessagingModule(),
+				new TestUiModule(), new ClockModule(), new CryptoModule(),
+				new DatabaseModule(), new LifecycleModule(),
+				new MessagingModule(), new DuplexMessagingModule(),
+				new SimplexMessagingModule(), new PluginsModule(),
+				new JavaSePluginsModule(), new ReliabilityModule(),
 				new SerialModule(), new TransportModule());
 		connectionReaderFactory = i.getInstance(ConnectionReaderFactory.class);
 		connectionWriterFactory = i.getInstance(ConnectionWriterFactory.class);
diff --git a/briar-tests/src/net/sf/briar/TestUiModule.java b/briar-tests/src/net/sf/briar/TestUiModule.java
new file mode 100644
index 0000000000000000000000000000000000000000..c8cd610b6caae2728a0647e37691a6a7f157b3e0
--- /dev/null
+++ b/briar-tests/src/net/sf/briar/TestUiModule.java
@@ -0,0 +1,24 @@
+package net.sf.briar;
+
+import net.sf.briar.api.ui.UiCallback;
+
+import com.google.inject.AbstractModule;
+
+public class TestUiModule extends AbstractModule {
+
+	@Override
+	protected void configure() {
+		bind(UiCallback.class).toInstance(new UiCallback() {
+
+			public int showChoice(String[] options, String... message) {
+				return -1;
+			}
+
+			public boolean showConfirmationMessage(String... message) {
+				return false;
+			}
+
+			public void showMessage(String... message) {}			
+		});
+	}
+}
diff --git a/briar-tests/src/net/sf/briar/messaging/simplex/SimplexMessagingIntegrationTest.java b/briar-tests/src/net/sf/briar/messaging/simplex/SimplexMessagingIntegrationTest.java
index 615c2e80699736fc20323e0b68e632c002e0b820..3d4c0bc6ad45c4fd749a54e5f4af15e79f370db2 100644
--- a/briar-tests/src/net/sf/briar/messaging/simplex/SimplexMessagingIntegrationTest.java
+++ b/briar-tests/src/net/sf/briar/messaging/simplex/SimplexMessagingIntegrationTest.java
@@ -9,6 +9,7 @@ import java.util.Random;
 
 import net.sf.briar.BriarTestCase;
 import net.sf.briar.TestDatabaseModule;
+import net.sf.briar.TestUiModule;
 import net.sf.briar.TestUtils;
 import net.sf.briar.api.Author;
 import net.sf.briar.api.AuthorId;
@@ -38,6 +39,9 @@ import net.sf.briar.lifecycle.LifecycleModule;
 import net.sf.briar.messaging.MessagingModule;
 import net.sf.briar.messaging.duplex.DuplexMessagingModule;
 import net.sf.briar.plugins.ImmediateExecutor;
+import net.sf.briar.plugins.JavaSePluginsModule;
+import net.sf.briar.plugins.PluginsModule;
+import net.sf.briar.reliability.ReliabilityModule;
 import net.sf.briar.serial.SerialModule;
 import net.sf.briar.transport.TransportModule;
 
@@ -80,11 +84,13 @@ public class SimplexMessagingIntegrationTest extends BriarTestCase {
 	}
 
 	private Injector createInjector(File dir) {
-		return Guice.createInjector(new ClockModule(), new CryptoModule(),
+		return Guice.createInjector(new TestDatabaseModule(dir),
+				new TestUiModule(), new ClockModule(), new CryptoModule(),
 				new DatabaseModule(), new LifecycleModule(),
-				new MessagingModule(), new SerialModule(),
-				new TestDatabaseModule(dir), new SimplexMessagingModule(),
-				new TransportModule(), new DuplexMessagingModule());
+				new MessagingModule(), new DuplexMessagingModule(),
+				new SimplexMessagingModule(), new PluginsModule(),
+				new JavaSePluginsModule(), new ReliabilityModule(),
+				new SerialModule(), new TransportModule());
 	}
 
 	@Test
diff --git a/briar-tests/src/net/sf/briar/plugins/PluginManagerImplTest.java b/briar-tests/src/net/sf/briar/plugins/PluginManagerImplTest.java
index ed25414216b2ea502d63956f3724a05a13f8475d..d224b2f142bffc1fb55b33761cb4ad7b1b009df4 100644
--- a/briar-tests/src/net/sf/briar/plugins/PluginManagerImplTest.java
+++ b/briar-tests/src/net/sf/briar/plugins/PluginManagerImplTest.java
@@ -1,13 +1,12 @@
 package net.sf.briar.plugins;
 
 import java.util.Arrays;
-import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executor;
 import java.util.concurrent.Executors;
 
 import net.sf.briar.BriarTestCase;
 import net.sf.briar.TestUtils;
 import net.sf.briar.api.TransportId;
-import net.sf.briar.api.android.AndroidExecutor;
 import net.sf.briar.api.db.DatabaseComponent;
 import net.sf.briar.api.plugins.duplex.DuplexPlugin;
 import net.sf.briar.api.plugins.duplex.DuplexPluginCallback;
@@ -29,9 +28,7 @@ public class PluginManagerImplTest extends BriarTestCase {
 	@Test
 	public void testStartAndStop() throws Exception {
 		Mockery context = new Mockery();
-		final ExecutorService pluginExecutor = Executors.newCachedThreadPool();
-		final AndroidExecutor androidExecutor =
-				context.mock(AndroidExecutor.class);
+		final Executor pluginExecutor = Executors.newCachedThreadPool();
 		final SimplexPluginConfig simplexPluginConfig =
 				context.mock(SimplexPluginConfig.class);
 		final DuplexPluginConfig duplexPluginConfig =
@@ -119,12 +116,10 @@ public class PluginManagerImplTest extends BriarTestCase {
 			// Stop the plugins
 			oneOf(simplexPlugin).stop();
 			oneOf(duplexPlugin).stop();
-			// Shut down the executor
-			oneOf(androidExecutor).shutdown();
 		}});
 		PluginManagerImpl p = new PluginManagerImpl(pluginExecutor,
-				androidExecutor, simplexPluginConfig, duplexPluginConfig, db,
-				poller, dispatcher, uiCallback);
+				simplexPluginConfig, duplexPluginConfig, db, poller,
+				dispatcher, uiCallback);
 		// Two plugins should be started and stopped
 		assertEquals(2, p.start());
 		assertEquals(2, p.stop());