diff --git a/briar-android/src/org/briarproject/android/AndroidNotificationManagerImpl.java b/briar-android/src/org/briarproject/android/AndroidNotificationManagerImpl.java
index 9ba3f6792fd4b025b60db54a974a8a0aa8be8d55..966de01db949176bda374f03248e51174c1c0b88 100644
--- a/briar-android/src/org/briarproject/android/AndroidNotificationManagerImpl.java
+++ b/briar-android/src/org/briarproject/android/AndroidNotificationManagerImpl.java
@@ -9,6 +9,7 @@ import android.content.Intent;
 import android.content.IntentFilter;
 import android.net.Uri;
 import android.os.Build;
+import android.support.annotation.UiThread;
 import android.support.v4.app.NotificationCompat;
 import android.support.v4.app.TaskStackBuilder;
 
@@ -105,7 +106,7 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
 	private final BroadcastReceiver receiver = new DeleteIntentReceiver();
 	private final AtomicBoolean used = new AtomicBoolean(false);
 
-	// The following must only be accessed on the AndroidExecutor thread
+	// The following must only be accessed on the main UI thread
 	private final Map<GroupId, Integer> contactCounts = new HashMap<>();
 	private final Map<GroupId, Integer> forumCounts = new HashMap<>();
 	private final Map<GroupId, Integer> blogCounts = new HashMap<>();
@@ -139,7 +140,7 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
 			throw new ServiceException(e);
 		}
 		// Register a broadcast receiver for notifications being dismissed
-		Future<Void> f = androidExecutor.submit(new Callable<Void>() {
+		Future<Void> f = androidExecutor.runOnUiThread(new Callable<Void>() {
 			@Override
 			public Void call() {
 				IntentFilter filter = new IntentFilter();
@@ -161,7 +162,7 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
 	@Override
 	public void stopService() throws ServiceException {
 		// Clear all notifications and unregister the broadcast receiver
-		Future<Void> f = androidExecutor.submit(new Callable<Void>() {
+		Future<Void> f = androidExecutor.runOnUiThread(new Callable<Void>() {
 			@Override
 			public Void call() {
 				clearPrivateMessageNotification();
@@ -179,6 +180,7 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
 		}
 	}
 
+	@UiThread
 	private void clearPrivateMessageNotification() {
 		contactCounts.clear();
 		contactTotal = 0;
@@ -187,6 +189,7 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
 		nm.cancel(PRIVATE_MESSAGE_NOTIFICATION_ID);
 	}
 
+	@UiThread
 	private void clearForumPostNotification() {
 		forumCounts.clear();
 		forumTotal = 0;
@@ -195,6 +198,7 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
 		nm.cancel(FORUM_POST_NOTIFICATION_ID);
 	}
 
+	@UiThread
 	private void clearBlogPostNotification() {
 		blogCounts.clear();
 		blogTotal = 0;
@@ -203,6 +207,7 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
 		nm.cancel(BLOG_POST_NOTIFICATION_ID);
 	}
 
+	@UiThread
 	private void clearIntroductionSuccessNotification() {
 		introductionTotal = 0;
 		Object o = appContext.getSystemService(NOTIFICATION_SERVICE);
@@ -256,7 +261,7 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
 	}
 
 	private void showPrivateMessageNotification(final GroupId g) {
-		androidExecutor.execute(new Runnable() {
+		androidExecutor.runOnUiThread(new Runnable() {
 			@Override
 			public void run() {
 				if (blockContacts) return;
@@ -272,7 +277,7 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
 
 	@Override
 	public void clearPrivateMessageNotification(final GroupId g) {
-		androidExecutor.execute(new Runnable() {
+		androidExecutor.runOnUiThread(new Runnable() {
 			@Override
 			public void run() {
 				Integer count = contactCounts.remove(g);
@@ -283,6 +288,7 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
 		});
 	}
 
+	@UiThread
 	private void updatePrivateMessageNotification() {
 		if (contactTotal == 0) {
 			clearPrivateMessageNotification();
@@ -339,6 +345,7 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
 		}
 	}
 
+	@UiThread
 	private int getDefaults() {
 		int defaults = DEFAULT_LIGHTS;
 		boolean sound = settings.getBoolean("notifySound", true);
@@ -352,7 +359,7 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
 
 	@Override
 	public void clearAllContactNotifications() {
-		androidExecutor.execute(new Runnable() {
+		androidExecutor.runOnUiThread(new Runnable() {
 			@Override
 			public void run() {
 				clearPrivateMessageNotification();
@@ -361,8 +368,9 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
 		});
 	}
 
+	@UiThread
 	private void showForumPostNotification(final GroupId g) {
-		androidExecutor.execute(new Runnable() {
+		androidExecutor.runOnUiThread(new Runnable() {
 			@Override
 			public void run() {
 				if (blockForums) return;
@@ -378,7 +386,7 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
 
 	@Override
 	public void clearForumPostNotification(final GroupId g) {
-		androidExecutor.execute(new Runnable() {
+		androidExecutor.runOnUiThread(new Runnable() {
 			@Override
 			public void run() {
 				Integer count = forumCounts.remove(g);
@@ -389,6 +397,7 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
 		});
 	}
 
+	@UiThread
 	private void updateForumPostNotification() {
 		if (forumTotal == 0) {
 			clearForumPostNotification();
@@ -446,7 +455,7 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
 
 	@Override
 	public void clearAllForumPostNotifications() {
-		androidExecutor.execute(new Runnable() {
+		androidExecutor.runOnUiThread(new Runnable() {
 			@Override
 			public void run() {
 				clearForumPostNotification();
@@ -454,8 +463,9 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
 		});
 	}
 
+	@UiThread
 	private void showBlogPostNotification(final GroupId g) {
-		androidExecutor.execute(new Runnable() {
+		androidExecutor.runOnUiThread(new Runnable() {
 			@Override
 			public void run() {
 				if (blockBlogs) return;
@@ -471,7 +481,7 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
 
 	@Override
 	public void clearBlogPostNotification(final GroupId g) {
-		androidExecutor.execute(new Runnable() {
+		androidExecutor.runOnUiThread(new Runnable() {
 			@Override
 			public void run() {
 				Integer count = blogCounts.remove(g);
@@ -482,6 +492,7 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
 		});
 	}
 
+	@UiThread
 	private void updateBlogPostNotification() {
 		if (blogTotal == 0) {
 			clearBlogPostNotification();
@@ -525,7 +536,7 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
 
 	@Override
 	public void clearAllBlogPostNotifications() {
-		androidExecutor.execute(new Runnable() {
+		androidExecutor.runOnUiThread(new Runnable() {
 			@Override
 			public void run() {
 				clearBlogPostNotification();
@@ -534,7 +545,7 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
 	}
 
 	private void showIntroductionNotification() {
-		androidExecutor.execute(new Runnable() {
+		androidExecutor.runOnUiThread(new Runnable() {
 			@Override
 			public void run() {
 				if (blockIntroductions) return;
@@ -544,6 +555,7 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
 		});
 	}
 
+	@UiThread
 	private void updateIntroductionNotification() {
 		NotificationCompat.Builder b =
 				new NotificationCompat.Builder(appContext);
@@ -583,7 +595,7 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
 
 	@Override
 	public void blockNotification(final GroupId g) {
-		androidExecutor.execute(new Runnable() {
+		androidExecutor.runOnUiThread(new Runnable() {
 			@Override
 			public void run() {
 				blockedGroup = g;
@@ -593,7 +605,7 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
 
 	@Override
 	public void unblockNotification(final GroupId g) {
-		androidExecutor.execute(new Runnable() {
+		androidExecutor.runOnUiThread(new Runnable() {
 			@Override
 			public void run() {
 				if (g.equals(blockedGroup)) blockedGroup = null;
@@ -603,7 +615,7 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
 
 	@Override
 	public void blockAllContactNotifications() {
-		androidExecutor.execute(new Runnable() {
+		androidExecutor.runOnUiThread(new Runnable() {
 			@Override
 			public void run() {
 				blockContacts = true;
@@ -614,7 +626,7 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
 
 	@Override
 	public void unblockAllContactNotifications() {
-		androidExecutor.execute(new Runnable() {
+		androidExecutor.runOnUiThread(new Runnable() {
 			@Override
 			public void run() {
 				blockContacts = false;
@@ -625,7 +637,7 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
 
 	@Override
 	public void blockAllForumPostNotifications() {
-		androidExecutor.execute(new Runnable() {
+		androidExecutor.runOnUiThread(new Runnable() {
 			@Override
 			public void run() {
 				blockForums = true;
@@ -635,7 +647,7 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
 
 	@Override
 	public void unblockAllForumPostNotifications() {
-		androidExecutor.execute(new Runnable() {
+		androidExecutor.runOnUiThread(new Runnable() {
 			@Override
 			public void run() {
 				blockForums = false;
@@ -645,7 +657,7 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
 
 	@Override
 	public void blockAllBlogPostNotifications() {
-		androidExecutor.execute(new Runnable() {
+		androidExecutor.runOnUiThread(new Runnable() {
 			@Override
 			public void run() {
 				blockBlogs = true;
@@ -655,7 +667,7 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
 
 	@Override
 	public void unblockAllBlogPostNotifications() {
-		androidExecutor.execute(new Runnable() {
+		androidExecutor.runOnUiThread(new Runnable() {
 			@Override
 			public void run() {
 				blockBlogs = false;
@@ -683,7 +695,7 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
 		@Override
 		public void onReceive(Context context, Intent intent) {
 			final String action = intent.getAction();
-			androidExecutor.execute(new Runnable() {
+			androidExecutor.runOnUiThread(new Runnable() {
 				@Override
 				public void run() {
 					if (CLEAR_PRIVATE_MESSAGE_ACTION.equals(action)) {
diff --git a/briar-android/src/org/briarproject/android/BriarService.java b/briar-android/src/org/briarproject/android/BriarService.java
index 33d69c8f68e2a6ed7144d4e69014b51b7f783531..f03751821ddcc5886cb31715e32ffc9f1896140e 100644
--- a/briar-android/src/org/briarproject/android/BriarService.java
+++ b/briar-android/src/org/briarproject/android/BriarService.java
@@ -108,7 +108,8 @@ public class BriarService extends Service {
 	}
 
 	private void showStartupFailureNotification(final StartResult result) {
-		androidExecutor.execute(new Runnable() {
+		androidExecutor.runOnUiThread(new Runnable() {
+			@Override
 			public void run() {
 				NotificationCompat.Builder b =
 						new NotificationCompat.Builder(BriarService.this);
@@ -197,11 +198,13 @@ public class BriarService extends Service {
 
 		private volatile IBinder binder = null;
 
+		@Override
 		public void onServiceConnected(ComponentName name, IBinder binder) {
 			this.binder = binder;
 			binderLatch.countDown();
 		}
 
+		@Override
 		public void onServiceDisconnected(ComponentName name) {}
 
 		/** Waits for the service to connect and returns its binder. */
diff --git a/briar-android/src/org/briarproject/android/SplashScreenActivity.java b/briar-android/src/org/briarproject/android/SplashScreenActivity.java
index c933f637e7d33f96039493fa64e3047d4fb1cee3..12c0533c7647053de0405c84f9f4987b06c549ab 100644
--- a/briar-android/src/org/briarproject/android/SplashScreenActivity.java
+++ b/briar-android/src/org/briarproject/android/SplashScreenActivity.java
@@ -87,7 +87,7 @@ public class SplashScreenActivity extends BaseActivity {
 	}
 
 	private void setPreferencesDefaults() {
-		androidExecutor.execute(new Runnable() {
+		androidExecutor.runOnBackgroundThread(new Runnable() {
 			@Override
 			public void run() {
 				PreferenceManager.setDefaultValues(SplashScreenActivity.this,
diff --git a/briar-android/src/org/briarproject/android/api/AndroidExecutor.java b/briar-android/src/org/briarproject/android/api/AndroidExecutor.java
index 047a74c14701e85a174f0acc9e78a16038aab638..7ea06bc2bd020841367da67fc2cb27b73367a911 100644
--- a/briar-android/src/org/briarproject/android/api/AndroidExecutor.java
+++ b/briar-android/src/org/briarproject/android/api/AndroidExecutor.java
@@ -13,10 +13,21 @@ public interface AndroidExecutor {
 	 * Runs the given task on a background thread with a message queue and
 	 * returns a Future for getting the result.
 	 */
-	<V> Future<V> submit(Callable<V> c);
+	<V> Future<V> runOnBackgroundThread(Callable<V> c);
 
 	/**
 	 * Runs the given task on a background thread with a message queue.
 	 */
-	void execute(Runnable r);
+	void runOnBackgroundThread(Runnable r);
+
+	/**
+	 * Runs the given task on the main UI thread and returns a Future for
+	 * getting the result.
+	 */
+	<V> Future<V> runOnUiThread(Callable<V> c);
+
+	/**
+	 * Runs the given task on the main UI thread.
+	 */
+	void runOnUiThread(Runnable r);
 }
diff --git a/briar-android/src/org/briarproject/android/fragment/SettingsFragment.java b/briar-android/src/org/briarproject/android/fragment/SettingsFragment.java
index 7bde67e6e224a2051ffd83421c5ee96ea99a150b..5b768aaa1a1370b7b83c7bc5f9ce67a2af229ec7 100644
--- a/briar-android/src/org/briarproject/android/fragment/SettingsFragment.java
+++ b/briar-android/src/org/briarproject/android/fragment/SettingsFragment.java
@@ -173,6 +173,7 @@ public class SettingsFragment extends PreferenceFragmentCompat
 
 	private void loadSettings() {
 		listener.runOnDbThread(new Runnable() {
+			@Override
 			public void run() {
 				try {
 					long now = System.currentTimeMillis();
@@ -195,6 +196,7 @@ public class SettingsFragment extends PreferenceFragmentCompat
 
 	private void displaySettings() {
 		listener.runOnUiThread(new Runnable() {
+			@Override
 			public void run() {
 				enableBluetooth.setValue(Boolean.toString(bluetoothSetting));
 				torOverMobile.setValue(Boolean.toString(torSetting));
@@ -228,7 +230,8 @@ public class SettingsFragment extends PreferenceFragmentCompat
 	}
 
 	private void triggerFeedback() {
-		androidExecutor.execute(new Runnable() {
+		androidExecutor.runOnBackgroundThread(new Runnable() {
+			@Override
 			public void run() {
 				ACRA.getErrorReporter().handleException(new UserFeedback(),
 						false);
@@ -268,7 +271,8 @@ public class SettingsFragment extends PreferenceFragmentCompat
 	private void enableOrDisableBluetooth(final boolean enable) {
 		final BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
 		if (adapter != null) {
-			androidExecutor.execute(new Runnable() {
+			androidExecutor.runOnBackgroundThread(new Runnable() {
+				@Override
 				public void run() {
 					if (enable) adapter.enable();
 					else adapter.disable();
@@ -279,6 +283,7 @@ public class SettingsFragment extends PreferenceFragmentCompat
 
 	private void storeTorSettings() {
 		listener.runOnDbThread(new Runnable() {
+			@Override
 			public void run() {
 				try {
 					Settings s = new Settings();
@@ -298,6 +303,7 @@ public class SettingsFragment extends PreferenceFragmentCompat
 
 	private void storeBluetoothSettings() {
 		listener.runOnDbThread(new Runnable() {
+			@Override
 			public void run() {
 				try {
 					Settings s = new Settings();
@@ -317,6 +323,7 @@ public class SettingsFragment extends PreferenceFragmentCompat
 
 	private void storeSettings(final Settings settings) {
 		listener.runOnDbThread(new Runnable() {
+			@Override
 			public void run() {
 				try {
 					long now = System.currentTimeMillis();
diff --git a/briar-android/src/org/briarproject/android/keyagreement/ShowQrCodeFragment.java b/briar-android/src/org/briarproject/android/keyagreement/ShowQrCodeFragment.java
index 2ea18075dc29ee444884047e1914b2f16c4181b3..82fca3e36d63c587eac49ee6e7edabe278b00437 100644
--- a/briar-android/src/org/briarproject/android/keyagreement/ShowQrCodeFragment.java
+++ b/briar-android/src/org/briarproject/android/keyagreement/ShowQrCodeFragment.java
@@ -144,7 +144,7 @@ public class ShowQrCodeFragment extends BaseEventFragment
 		final BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
 		if (adapter != null && !adapter.isEnabled()) {
 			waitingForBluetooth = true;
-			androidExecutor.execute(new Runnable() {
+			androidExecutor.runOnBackgroundThread(new Runnable() {
 				@Override
 				public void run() {
 					adapter.enable();
diff --git a/briar-android/src/org/briarproject/android/panic/PanicResponderActivity.java b/briar-android/src/org/briarproject/android/panic/PanicResponderActivity.java
index ff3ba954a7f0580b71cb196a020ce368f383287d..ba8583fcac04e9cf99c11a27569251e42ad1a5a4 100644
--- a/briar-android/src/org/briarproject/android/panic/PanicResponderActivity.java
+++ b/briar-android/src/org/briarproject/android/panic/PanicResponderActivity.java
@@ -107,7 +107,7 @@ public class PanicResponderActivity extends BriarActivity {
 	}
 
 	private void deleteAllData() {
-		androidExecutor.execute(new Runnable() {
+		androidExecutor.runOnBackgroundThread(new Runnable() {
 			@Override
 			public void run() {
 				configController.deleteAccount(PanicResponderActivity.this);
diff --git a/briar-android/src/org/briarproject/plugins/droidtooth/DroidtoothPlugin.java b/briar-android/src/org/briarproject/plugins/droidtooth/DroidtoothPlugin.java
index 8c26d4518e330982194ad796cd6eef4d89240801..d5c0ca5bc1609eb0f95356571020a461849e5080 100644
--- a/briar-android/src/org/briarproject/plugins/droidtooth/DroidtoothPlugin.java
+++ b/briar-android/src/org/briarproject/plugins/droidtooth/DroidtoothPlugin.java
@@ -125,12 +125,13 @@ class DroidtoothPlugin implements DuplexPlugin {
 		// BluetoothAdapter.getDefaultAdapter() must be called on a thread
 		// with a message queue, so submit it to the AndroidExecutor
 		try {
-			adapter = androidExecutor.submit(new Callable<BluetoothAdapter>() {
-				@Override
-				public BluetoothAdapter call() throws Exception {
-					return BluetoothAdapter.getDefaultAdapter();
-				}
-			}).get();
+			adapter = androidExecutor.runOnBackgroundThread(
+					new Callable<BluetoothAdapter>() {
+						@Override
+						public BluetoothAdapter call() throws Exception {
+							return BluetoothAdapter.getDefaultAdapter();
+						}
+					}).get();
 		} catch (InterruptedException e) {
 			Thread.currentThread().interrupt();
 			throw new IOException("Interrupted while getting BluetoothAdapter");
diff --git a/briar-android/src/org/briarproject/system/AndroidExecutorImpl.java b/briar-android/src/org/briarproject/system/AndroidExecutorImpl.java
index 07b6e69e5e89e656e0b61290c551dd1d3b09b3b8..180881febac82ce3d888998f3ac31cbd65bb9893 100644
--- a/briar-android/src/org/briarproject/system/AndroidExecutorImpl.java
+++ b/briar-android/src/org/briarproject/system/AndroidExecutorImpl.java
@@ -1,5 +1,6 @@
 package org.briarproject.system;
 
+import android.app.Application;
 import android.os.Handler;
 import android.os.Looper;
 
@@ -16,18 +17,21 @@ import javax.inject.Inject;
 
 class AndroidExecutorImpl implements AndroidExecutor {
 
+	private final Handler uiHandler;
 	private final Runnable loop;
 	private final AtomicBoolean started = new AtomicBoolean(false);
 	private final CountDownLatch startLatch = new CountDownLatch(1);
 
-	private volatile Handler handler = null;
+	private volatile Handler backgroundHandler = null;
 
 	@Inject
-	AndroidExecutorImpl() {
+	AndroidExecutorImpl(Application app) {
+		uiHandler = new Handler(app.getApplicationContext().getMainLooper());
 		loop = new Runnable() {
+			@Override
 			public void run() {
 				Looper.prepare();
-				handler = new Handler();
+				backgroundHandler = new Handler();
 				startLatch.countDown();
 				Looper.loop();
 			}
@@ -46,14 +50,28 @@ class AndroidExecutorImpl implements AndroidExecutor {
 		}
 	}
 
-	public <V> Future<V> submit(Callable<V> c) {
+	@Override
+	public <V> Future<V> runOnBackgroundThread(Callable<V> c) {
 		FutureTask<V> f = new FutureTask<>(c);
-		execute(f);
+		runOnBackgroundThread(f);
 		return f;
 	}
 
-	public void execute(Runnable r) {
+	@Override
+	public void runOnBackgroundThread(Runnable r) {
 		startIfNecessary();
-		handler.post(r);
+		backgroundHandler.post(r);
+	}
+
+	@Override
+	public <V> Future<V> runOnUiThread(Callable<V> c) {
+		FutureTask<V> f = new FutureTask<>(c);
+		runOnUiThread(f);
+		return f;
+	}
+
+	@Override
+	public void runOnUiThread(Runnable r) {
+		uiHandler.post(r);
 	}
 }
diff --git a/briar-android/src/org/briarproject/system/AndroidSystemModule.java b/briar-android/src/org/briarproject/system/AndroidSystemModule.java
index a577ac7c3e1c4d787f80b22d44a406bcb4b170a1..99ca8fb7abf5a18feb793e9dbf1dd3ddeda1929f 100644
--- a/briar-android/src/org/briarproject/system/AndroidSystemModule.java
+++ b/briar-android/src/org/briarproject/system/AndroidSystemModule.java
@@ -27,7 +27,7 @@ public class AndroidSystemModule {
 
 	@Provides
 	@Singleton
-	public AndroidExecutor provideAndroidExecutor() {
-		return new AndroidExecutorImpl();
+	public AndroidExecutor provideAndroidExecutor(Application app) {
+		return new AndroidExecutorImpl(app);
 	}
 }