diff --git a/briar-android/src/org/briarproject/android/BriarActivity.java b/briar-android/src/org/briarproject/android/BriarActivity.java
index 3b4e7c4c6289f910a3b957a1fa35b41a420aecc5..8043b1a14038cb473963917a06bfe2e427b97edc 100644
--- a/briar-android/src/org/briarproject/android/BriarActivity.java
+++ b/briar-android/src/org/briarproject/android/BriarActivity.java
@@ -41,7 +41,8 @@ public abstract class BriarActivity extends BaseActivity {
 			new BriarServiceConnection();
 
 	@Inject
-	DatabaseConfig databaseConfig;
+	protected DatabaseConfig databaseConfig;
+
 	private boolean bound = false;
 
 	// Fields that are accessed from background threads must be volatile
@@ -94,6 +95,7 @@ public abstract class BriarActivity extends BaseActivity {
 	}
 
 	protected void signOut(final boolean removeFromRecentApps) {
+		// Use a new thread to avoid deadlock with executor tasks
 		new Thread() {
 			@Override
 			public void run() {
diff --git a/briar-android/src/org/briarproject/android/SettingsActivity.java b/briar-android/src/org/briarproject/android/SettingsActivity.java
index 0ec6c916ce5dd461d705c61fa36c14c5593378f8..e270578a74065285f7ee43ee4f7dabc0ca8cffa2 100644
--- a/briar-android/src/org/briarproject/android/SettingsActivity.java
+++ b/briar-android/src/org/briarproject/android/SettingsActivity.java
@@ -5,6 +5,7 @@ import android.support.v7.app.ActionBar;
 import android.view.MenuItem;
 
 import org.briarproject.R;
+import org.briarproject.android.api.AndroidExecutor;
 import org.briarproject.api.event.EventBus;
 import org.briarproject.api.settings.SettingsManager;
 
@@ -12,8 +13,12 @@ import javax.inject.Inject;
 
 public class SettingsActivity extends BriarActivity {
 
-	@Inject protected SettingsManager settingsManager;
-	@Inject protected EventBus eventBus;
+	@Inject
+	protected AndroidExecutor androidExecutor;
+	@Inject
+	protected SettingsManager settingsManager;
+	@Inject
+	protected EventBus eventBus;
 
 	@Override
 	public void onCreate(Bundle bundle) {
@@ -33,6 +38,10 @@ public class SettingsActivity extends BriarActivity {
 		component.inject(this);
 	}
 
+	public AndroidExecutor getAndroidExecutor() {
+		return androidExecutor;
+	}
+
 	public SettingsManager getSettingsManager() {
 		return settingsManager;
 	}
diff --git a/briar-android/src/org/briarproject/android/SplashScreenActivity.java b/briar-android/src/org/briarproject/android/SplashScreenActivity.java
index c4cac86f5457f8d9e01c25064b82261f13c9d638..b7d1aad25197ceb4313f5ab7bbc5d36b13ef81bc 100644
--- a/briar-android/src/org/briarproject/android/SplashScreenActivity.java
+++ b/briar-android/src/org/briarproject/android/SplashScreenActivity.java
@@ -9,6 +9,7 @@ import android.os.StrictMode.VmPolicy;
 import android.support.v7.preference.PreferenceManager;
 
 import org.briarproject.R;
+import org.briarproject.android.api.AndroidExecutor;
 import org.briarproject.android.util.AndroidUtils;
 import org.briarproject.api.db.DatabaseConfig;
 
@@ -29,6 +30,8 @@ public class SplashScreenActivity extends BaseActivity {
 
 	@Inject
 	protected DatabaseConfig dbConfig;
+	@Inject
+	protected AndroidExecutor androidExecutor;
 
 	public SplashScreenActivity() {
 		Logger.getLogger("").setLevel(DEFAULT_LOG_LEVEL);
@@ -87,12 +90,11 @@ public class SplashScreenActivity extends BaseActivity {
 	}
 
 	private void setPreferencesDefaults() {
-		new Thread() {
-			@Override
+		androidExecutor.execute(new Runnable() {
 			public void run() {
 				PreferenceManager.setDefaultValues(SplashScreenActivity.this,
 						R.xml.panic_preferences, false);
 			}
-		}.start();
+		});
 	}
 }
diff --git a/briar-android/src/org/briarproject/android/api/AndroidExecutor.java b/briar-android/src/org/briarproject/android/api/AndroidExecutor.java
index 5ed8ef0f89e9d19ce60bd932e4e91bc3cac91157..047a74c14701e85a174f0acc9e78a16038aab638 100644
--- a/briar-android/src/org/briarproject/android/api/AndroidExecutor.java
+++ b/briar-android/src/org/briarproject/android/api/AndroidExecutor.java
@@ -10,11 +10,13 @@ import java.util.concurrent.Future;
 public interface AndroidExecutor {
 
 	/**
-	 * Runs the given task on the main UI thread and returns a Future for
-	 * getting the result.
+	 * 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);
 
-	/** Runs the given task on the main UI thread. */
+	/**
+	 * Runs the given task on a background thread with a message queue.
+	 */
 	void execute(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 2e7280ae5f605b97393ab55fdca8597b32ec6c9a..b7750dc17a91f3f96f480126eb31397c602376f8 100644
--- a/briar-android/src/org/briarproject/android/fragment/SettingsFragment.java
+++ b/briar-android/src/org/briarproject/android/fragment/SettingsFragment.java
@@ -18,7 +18,7 @@ import android.view.ViewGroup;
 import org.acra.ACRA;
 import org.briarproject.R;
 import org.briarproject.android.SettingsActivity;
-import org.briarproject.android.util.AndroidUtils;
+import org.briarproject.android.api.AndroidExecutor;
 import org.briarproject.android.util.UserFeedback;
 import org.briarproject.android.widget.PreferenceDividerDecoration;
 import org.briarproject.api.db.DbException;
@@ -55,6 +55,7 @@ public class SettingsFragment extends PreferenceFragmentCompat
 			Logger.getLogger(SettingsFragment.class.getName());
 
 	private SettingsActivity listener;
+	private AndroidExecutor androidExecutor;
 	private ListPreference enableBluetooth;
 	private ListPreference torOverMobile;
 	private CheckBoxPreference notifyPrivateMessages;
@@ -74,6 +75,7 @@ public class SettingsFragment extends PreferenceFragmentCompat
 
 		try {
 			listener = (SettingsActivity) context;
+			androidExecutor = listener.getAndroidExecutor();
 			settingsManager = listener.getSettingsManager();
 			eventBus = listener.getEventBus();
 		} catch (ClassCastException e) {
@@ -218,23 +220,19 @@ public class SettingsFragment extends PreferenceFragmentCompat
 	}
 
 	private void triggerFeedback() {
-		new Thread(new Runnable() {
-			@Override
+		androidExecutor.execute(new Runnable() {
 			public void run() {
-				ACRA.getErrorReporter()
-						.handleException(new UserFeedback(), false);
+				ACRA.getErrorReporter().handleException(new UserFeedback(),
+						false);
 			}
-		}).start();
+		});
 	}
 
 	@Override
 	public boolean onPreferenceChange(Preference preference, Object o) {
 		if (preference == enableBluetooth) {
 			bluetoothSetting = Boolean.valueOf((String) o);
-			BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
-			if (adapter != null) {
-				AndroidUtils.enableBluetooth(adapter, bluetoothSetting);
-			}
+			enableOrDisableBluetooth(bluetoothSetting);
 			storeBluetoothSettings();
 		} else if (preference == torOverMobile) {
 			torSetting = Boolean.valueOf((String) o);
@@ -255,6 +253,18 @@ public class SettingsFragment extends PreferenceFragmentCompat
 		return true;
 	}
 
+	private void enableOrDisableBluetooth(final boolean enable) {
+		final BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+		if (adapter != null) {
+			androidExecutor.execute(new Runnable() {
+				public void run() {
+					if (enable) adapter.enable();
+					else adapter.disable();
+				}
+			});
+		}
+	}
+
 	private void storeTorSettings() {
 		listener.runOnDbThread(new Runnable() {
 			public void run() {
diff --git a/briar-android/src/org/briarproject/android/keyagreement/ShowQrCodeFragment.java b/briar-android/src/org/briarproject/android/keyagreement/ShowQrCodeFragment.java
index df18a12fce80d8cf024b0599269ed2e6f4470460..4f0937d39c2b78ca77068a7c7282ba9a25ebacd0 100644
--- a/briar-android/src/org/briarproject/android/keyagreement/ShowQrCodeFragment.java
+++ b/briar-android/src/org/briarproject/android/keyagreement/ShowQrCodeFragment.java
@@ -24,8 +24,8 @@ import com.google.zxing.Result;
 
 import org.briarproject.R;
 import org.briarproject.android.AndroidComponent;
+import org.briarproject.android.api.AndroidExecutor;
 import org.briarproject.android.fragment.BaseEventFragment;
-import org.briarproject.android.util.AndroidUtils;
 import org.briarproject.android.util.CameraView;
 import org.briarproject.android.util.QrCodeDecoder;
 import org.briarproject.android.util.QrCodeUtils;
@@ -70,6 +70,8 @@ public class ShowQrCodeFragment extends BaseEventFragment
 	protected PayloadEncoder payloadEncoder;
 	@Inject
 	protected PayloadParser payloadParser;
+	@Inject
+	protected AndroidExecutor androidExecutor;
 
 	private LinearLayout qrLayout;
 	private CameraView cameraView;
@@ -81,7 +83,6 @@ public class ShowQrCodeFragment extends BaseEventFragment
 	private boolean gotRemotePayload;
 
 	private volatile KeyAgreementTask task;
-	private volatile BluetoothAdapter adapter;
 	private volatile boolean waitingForBluetooth;
 
 	public static ShowQrCodeFragment newInstance() {
@@ -130,8 +131,6 @@ public class ShowQrCodeFragment extends BaseEventFragment
 		Display display = getActivity().getWindowManager().getDefaultDisplay();
 		boolean portrait = display.getWidth() < display.getHeight();
 		qrLayout.setOrientation(portrait ? VERTICAL : HORIZONTAL);
-
-		adapter = BluetoothAdapter.getDefaultAdapter();
 	}
 
 	@Override
@@ -145,11 +144,17 @@ public class ShowQrCodeFragment extends BaseEventFragment
 		getActivity().registerReceiver(receiver, filter);
 
 		// Enable BT adapter if it is not already on.
+		final BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
 		if (adapter != null && !adapter.isEnabled()) {
 			waitingForBluetooth = true;
-			AndroidUtils.enableBluetooth(adapter, true);
-		} else
+			androidExecutor.execute(new Runnable() {
+				public void run() {
+					adapter.enable();
+				}
+			});
+		} else {
 			startListening();
+		}
 	}
 
 	@Override
diff --git a/briar-android/src/org/briarproject/android/panic/PanicResponderActivity.java b/briar-android/src/org/briarproject/android/panic/PanicResponderActivity.java
index 29f271f0e4113f82d950aea02b0e63aed166b247..87ba49e7023eada4ff2051e63baed477500d75a4 100644
--- a/briar-android/src/org/briarproject/android/panic/PanicResponderActivity.java
+++ b/briar-android/src/org/briarproject/android/panic/PanicResponderActivity.java
@@ -9,9 +9,9 @@ import android.support.v7.preference.PreferenceManager;
 
 import org.briarproject.android.AndroidComponent;
 import org.briarproject.android.BriarActivity;
+import org.briarproject.android.api.AndroidExecutor;
 import org.briarproject.android.util.AndroidUtils;
 import org.briarproject.api.db.DatabaseConfig;
-import org.briarproject.util.FileUtils;
 import org.iilab.IilabEngineeringRSA2048Pin;
 
 import java.util.logging.Logger;
@@ -23,6 +23,7 @@ import info.guardianproject.panic.Panic;
 import info.guardianproject.panic.PanicResponder;
 import info.guardianproject.trustedintents.TrustedIntents;
 
+import static android.content.Intent.ACTION_DELETE;
 import static org.briarproject.android.panic.PanicPreferencesFragment.KEY_LOCK;
 import static org.briarproject.android.panic.PanicPreferencesFragment.KEY_PURGE;
 import static org.briarproject.android.panic.PanicPreferencesFragment.KEY_UNINSTALL;
@@ -31,7 +32,11 @@ public class PanicResponderActivity extends BriarActivity {
 
 	private static final Logger LOG =
 			Logger.getLogger(PanicResponderActivity.class.getName());
-	@Inject protected DatabaseConfig databaseConfig;
+
+	@Inject
+	protected DatabaseConfig databaseConfig;
+	@Inject
+	protected AndroidExecutor androidExecutor;
 
 	@Override
 	public void onCreate(Bundle savedInstanceState) {
@@ -62,16 +67,14 @@ public class PanicResponderActivity extends BriarActivity {
 						deleteAllData();
 
 						LOG.info("Uninstalling...");
-						Intent uninstall = new Intent(Intent.ACTION_DELETE);
+						Intent uninstall = new Intent(ACTION_DELETE);
 						uninstall.setData(
 								Uri.parse("package:" + getPackageName()));
 						startActivity(uninstall);
-					}
-					else if (sharedPref.getBoolean(KEY_PURGE, false)) {
+					} else if (sharedPref.getBoolean(KEY_PURGE, false)) {
 						LOG.info("Purging all data...");
 						deleteAllData();
-					}
-					else if (sharedPref.getBoolean(KEY_LOCK, true)) {
+					} else if (sharedPref.getBoolean(KEY_LOCK, true)) {
 						LOG.info("Signing out...");
 						signOut(true);
 					}
@@ -107,8 +110,7 @@ public class PanicResponderActivity extends BriarActivity {
 	}
 
 	private void deleteAllData() {
-		new Thread() {
-			@Override
+		androidExecutor.execute(new Runnable() {
 			public void run() {
 				clearSharedPrefs();
 				// TODO somehow delete/shred the database more thoroughly
@@ -120,7 +122,6 @@ public class PanicResponderActivity extends BriarActivity {
 				LOG.info("Signing out...");
 				signOut(true);
 			}
-		}.start();
+		});
 	}
-
 }
\ No newline at end of file
diff --git a/briar-android/src/org/briarproject/android/util/AndroidUtils.java b/briar-android/src/org/briarproject/android/util/AndroidUtils.java
index 841dba8f6520fc24d7bec04fe3976121ee2c9de1..f3b33196cb7a9c5bbabf456472dcc37be9c34184 100644
--- a/briar-android/src/org/briarproject/android/util/AndroidUtils.java
+++ b/briar-android/src/org/briarproject/android/util/AndroidUtils.java
@@ -48,17 +48,6 @@ public class AndroidUtils {
 			til.setError(null);
 	}
 
-	public static void enableBluetooth(final BluetoothAdapter adapter,
-			final boolean enable) {
-		new Thread() {
-			@Override
-			public void run() {
-				if (enable) adapter.enable();
-				else adapter.disable();
-			}
-		}.start();
-	}
-
 	public static String getBluetoothAddress(Context ctx,
 			BluetoothAdapter adapter) {
 		// Return the adapter's address if it's valid and not fake
diff --git a/briar-android/src/org/briarproject/system/AndroidExecutorImpl.java b/briar-android/src/org/briarproject/system/AndroidExecutorImpl.java
index 0b9674c25274f1f7341f00b8d683374522ebf949..07b6e69e5e89e656e0b61290c551dd1d3b09b3b8 100644
--- a/briar-android/src/org/briarproject/system/AndroidExecutorImpl.java
+++ b/briar-android/src/org/briarproject/system/AndroidExecutorImpl.java
@@ -1,46 +1,59 @@
 package org.briarproject.system;
 
-import android.app.Application;
-import android.content.Context;
 import android.os.Handler;
 import android.os.Looper;
-import android.os.Message;
 
 import org.briarproject.android.api.AndroidExecutor;
 
 import java.util.concurrent.Callable;
+import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.Future;
 import java.util.concurrent.FutureTask;
+import java.util.concurrent.RejectedExecutionException;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import javax.inject.Inject;
 
 class AndroidExecutorImpl implements AndroidExecutor {
 
-	private final Handler handler;
+	private final Runnable loop;
+	private final AtomicBoolean started = new AtomicBoolean(false);
+	private final CountDownLatch startLatch = new CountDownLatch(1);
+
+	private volatile Handler handler = null;
+
+	@Inject
+	AndroidExecutorImpl() {
+		loop = new Runnable() {
+			public void run() {
+				Looper.prepare();
+				handler = new Handler();
+				startLatch.countDown();
+				Looper.loop();
+			}
+		};
+	}
 
-	AndroidExecutorImpl(Application app) {
-		Context ctx = app.getApplicationContext();
-		handler = new FutureTaskHandler(ctx.getMainLooper());
+	private void startIfNecessary() {
+		if (started.getAndSet(true)) return;
+		Thread t = new Thread(loop, "AndroidExecutor");
+		t.setDaemon(true);
+		t.start();
+		try {
+			startLatch.await();
+		} catch (InterruptedException e) {
+			throw new RejectedExecutionException(e);
+		}
 	}
 
 	public <V> Future<V> submit(Callable<V> c) {
-		Future<V> f = new FutureTask<V>(c);
-		handler.sendMessage(Message.obtain(handler, 0, f));
+		FutureTask<V> f = new FutureTask<>(c);
+		execute(f);
 		return f;
 	}
 
 	public void execute(Runnable r) {
+		startIfNecessary();
 		handler.post(r);
 	}
-
-	private static class FutureTaskHandler extends Handler {
-
-		private FutureTaskHandler(Looper looper) {
-			super(looper);
-		}
-
-		@Override
-		public void handleMessage(Message m) {
-			((FutureTask<?>) m.obj).run();
-		}
-	}
 }
-
diff --git a/briar-android/src/org/briarproject/system/AndroidSystemModule.java b/briar-android/src/org/briarproject/system/AndroidSystemModule.java
index 74e39e06af0bd375d43098fbedc3c5bf295711b5..f6fb19f16d9ea7b73dec8bee6fad856c96c22100 100644
--- a/briar-android/src/org/briarproject/system/AndroidSystemModule.java
+++ b/briar-android/src/org/briarproject/system/AndroidSystemModule.java
@@ -6,6 +6,8 @@ import org.briarproject.android.api.AndroidExecutor;
 import org.briarproject.api.system.LocationUtils;
 import org.briarproject.api.system.SeedProvider;
 
+import javax.inject.Singleton;
+
 import dagger.Module;
 import dagger.Provides;
 
@@ -23,7 +25,8 @@ public class AndroidSystemModule {
 	}
 
 	@Provides
-	public AndroidExecutor providePlatformExecutor(Application app) {
-		return new AndroidExecutorImpl(app);
+	@Singleton
+	public AndroidExecutor provideAndroidExecutor() {
+		return new AndroidExecutorImpl();
 	}
 }