diff --git a/mailbox-android/src/main/AndroidManifest.xml b/mailbox-android/src/main/AndroidManifest.xml
index 2d7e4c828214f06da4492b43d79d6f3f8a24cc3c..ee9849f4d42f338fdbba1853db241dc546aeca6b 100644
--- a/mailbox-android/src/main/AndroidManifest.xml
+++ b/mailbox-android/src/main/AndroidManifest.xml
@@ -2,11 +2,16 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="org.briarproject.mailbox">
 
+    <uses-feature
+        android:name="android.hardware.touchscreen"
+        android:required="false" />
+
     <uses-permission android:name="android.permission.INTERNET" />
     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
     <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
     <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
     <uses-permission android:name="android.permission.WAKE_LOCK" />
+    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
 
     <application
         android:name=".android.MailboxApplication"
@@ -19,7 +24,9 @@
 
         <service android:name=".android.MailboxService" />
 
-        <activity android:name=".android.MainActivity">
+        <activity
+            android:name=".android.MainActivity"
+            android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
 
@@ -29,6 +36,15 @@
 
         <receiver android:name=".core.system.AlarmReceiver" />
 
+        <receiver
+            android:name=".android.StartReceiver"
+            android:exported="false">
+            <intent-filter>
+                <action android:name="android.intent.action.BOOT_COMPLETED" />
+                <action android:name="android.intent.action.MY_PACKAGE_REPLACED" />
+            </intent-filter>
+        </receiver>
+
     </application>
 
 </manifest>
diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/android/MailboxNotificationManager.kt b/mailbox-android/src/main/java/org/briarproject/mailbox/android/MailboxNotificationManager.kt
index a259a3028f52f1c68ef15b1baca609495eb53eda..fa67a900c267f9c6c5aca638f52469ed4c759efe 100644
--- a/mailbox-android/src/main/java/org/briarproject/mailbox/android/MailboxNotificationManager.kt
+++ b/mailbox-android/src/main/java/org/briarproject/mailbox/android/MailboxNotificationManager.kt
@@ -74,7 +74,7 @@ class MailboxNotificationManager @Inject constructor(
             return NotificationCompat.Builder(ctx, CHANNEL_ID)
                 .setContentTitle(ctx.getString(R.string.notification_mailbox_title))
                 .setContentText(ctx.getString(R.string.notification_mailbox_content))
-                .setSmallIcon(R.drawable.ic_launcher_foreground)
+                .setSmallIcon(R.drawable.ic_notification_foreground)
                 .setContentIntent(pendingIntent)
                 .setPriority(PRIORITY_MIN)
                 .build()
diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/android/MailboxService.kt b/mailbox-android/src/main/java/org/briarproject/mailbox/android/MailboxService.kt
index c13b1a5a0307fc9b256d17fe74c3ed37a5d7b2ed..6f47c9e9e8f0848aa3ccc97befbd83678229e42d 100644
--- a/mailbox-android/src/main/java/org/briarproject/mailbox/android/MailboxService.kt
+++ b/mailbox-android/src/main/java/org/briarproject/mailbox/android/MailboxService.kt
@@ -29,13 +29,13 @@ import androidx.core.content.ContextCompat
 import dagger.hilt.android.AndroidEntryPoint
 import org.briarproject.mailbox.android.MailboxNotificationManager.Companion.NOTIFICATION_MAIN_ID
 import org.briarproject.mailbox.core.lifecycle.LifecycleManager
-import org.briarproject.mailbox.core.lifecycle.LifecycleManager.StartResult
-import org.briarproject.mailbox.core.lifecycle.LifecycleManager.StartResult.ALREADY_RUNNING
 import org.briarproject.mailbox.core.lifecycle.LifecycleManager.StartResult.SUCCESS
+import org.briarproject.mailbox.core.system.AndroidWakeLock
 import org.briarproject.mailbox.core.system.AndroidWakeLockManager
 import org.slf4j.LoggerFactory.getLogger
 import java.util.concurrent.atomic.AtomicBoolean
 import javax.inject.Inject
+import kotlin.concurrent.thread
 import kotlin.system.exitProcess
 
 @AndroidEntryPoint
@@ -70,52 +70,59 @@ class MailboxService : Service() {
     @Inject
     internal lateinit var notificationManager: MailboxNotificationManager
 
+    private lateinit var lifecycleWakeLock: AndroidWakeLock
+
     override fun onCreate() {
         super.onCreate()
 
         LOG.info("Created")
         if (created.getAndSet(true)) {
             LOG.warn("Already created")
-            // FIXME when can this happen? Next line will kill app
+            // This is a canary to notify us about strange behavior concerning service creation
+            // in logs and bug reports. Calling stopSelf() kills the app.
             stopSelf()
             return
         }
 
-        // Hold a wake lock during startup
-        wakeLockManager.runWakefully({
-            startForeground(NOTIFICATION_MAIN_ID, notificationManager.serviceNotification)
-            // Start the services in a background thread
-            wakeLockManager.executeWakefully({
-                val result: StartResult = lifecycleManager.startServices()
-                when {
-                    result === SUCCESS -> started = true
-                    result === ALREADY_RUNNING -> {
-                        LOG.warn("Already running")
-                        // FIXME when can this happen? Next line will kill app
-                        stopSelf()
-                    }
-                    else -> {
-                        if (LOG.isWarnEnabled) LOG.warn("Startup failed: $result")
-                        // TODO: implement this
-                        //  and start activity in new process, so we can kill this one
-                        // showStartupFailure(result)
-                        stopSelf()
-                    }
-                }
-            }, "LifecycleStartup")
-            // Register for device shutdown broadcasts
-            receiver = object : BroadcastReceiver() {
-                override fun onReceive(context: Context, intent: Intent) {
-                    LOG.info("Device is shutting down")
+        // We hold a wake lock during the whole lifecycle. We have a one-to-one relationship
+        // between MailboxService and the LifecycleManager. As we do not support lifecycle restarts
+        // only a single MailboxService is allowed to start and stop with the LifecycleManager
+        // singleton. Should the service be killed and restarted, the LifecycleManager must also
+        // have been destroyed and there is no way to recover except via a restart of the app.
+        // So should a second MailboxService be started anytime, this is a unrecoverable situation
+        // and we stop the app.
+        // Acquiring the wakelock here and releasing it as the last thing before exitProcess()
+        // during onDestroy() makes sure it is being held during the whole lifecycle.
+        lifecycleWakeLock = wakeLockManager.createWakeLock("Lifecycle")
+        lifecycleWakeLock.acquire()
+
+        startForeground(NOTIFICATION_MAIN_ID, notificationManager.serviceNotification)
+        // Start the services in a background thread
+        thread {
+            val result = lifecycleManager.startServices()
+            when {
+                result === SUCCESS -> started = true
+                else -> {
+                    if (LOG.isWarnEnabled) LOG.warn("Startup failed: $result")
+                    // TODO: implement this
+                    //  and start activity in new process, so we can kill this one
+                    // showStartupFailure(result)
                     stopSelf()
                 }
             }
-            val filter = IntentFilter()
-            filter.addAction(Intent.ACTION_SHUTDOWN)
-            filter.addAction("android.intent.action.QUICKBOOT_POWEROFF")
-            filter.addAction("com.htc.intent.action.QUICKBOOT_POWEROFF")
-            registerReceiver(receiver, filter)
-        }, "LifecycleStartup")
+        }
+        // Register for device shutdown broadcasts
+        receiver = object : BroadcastReceiver() {
+            override fun onReceive(context: Context, intent: Intent) {
+                LOG.info("Device is shutting down")
+                stopSelf()
+            }
+        }
+        val filter = IntentFilter()
+        filter.addAction(Intent.ACTION_SHUTDOWN)
+        filter.addAction("android.intent.action.QUICKBOOT_POWEROFF")
+        filter.addAction("com.htc.intent.action.QUICKBOOT_POWEROFF")
+        registerReceiver(receiver, filter)
     }
 
     override fun onBind(intent: Intent): IBinder? {
@@ -123,23 +130,23 @@ class MailboxService : Service() {
     }
 
     override fun onDestroy() {
-        wakeLockManager.runWakefully({
-            super.onDestroy()
-            LOG.info("Destroyed")
-            stopForeground(true)
-            if (receiver != null) unregisterReceiver(receiver)
-            wakeLockManager.executeWakefully({
-                try {
-                    if (started) {
-                        lifecycleManager.stopServices()
-                        lifecycleManager.waitForShutdown()
-                    }
-                } catch (e: InterruptedException) {
-                    LOG.info("Interrupted while waiting for shutdown")
-                }
-                LOG.info("Exiting")
-                exitProcess(0)
-            }, "LifecycleShutdown")
-        }, "LifecycleShutdown")
+        super.onDestroy()
+        LOG.info("Destroyed")
+        stopForeground(true)
+        if (receiver != null) unregisterReceiver(receiver)
+        if (started) {
+            try {
+                lifecycleManager.stopServices()
+                lifecycleManager.waitForShutdown()
+            } catch (e: InterruptedException) {
+                LOG.info("Interrupted while waiting for shutdown")
+            }
+        }
+        // Do not exit within wakeful execution, otherwise we will never release the wake locks.
+        // Or maybe we want to do precisely that to make sure exiting really happens and the app
+        // doesn't get suspended before it gets a chance to exit?
+        lifecycleWakeLock.release()
+        LOG.info("Exiting")
+        exitProcess(0)
     }
 }
diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/android/MailboxViewModel.kt b/mailbox-android/src/main/java/org/briarproject/mailbox/android/MailboxViewModel.kt
index 6730309c147abdb859c35a6ebd52057170b17d56..7cdf4dfa16a8b100f4af24653a055ceaae941278 100644
--- a/mailbox-android/src/main/java/org/briarproject/mailbox/android/MailboxViewModel.kt
+++ b/mailbox-android/src/main/java/org/briarproject/mailbox/android/MailboxViewModel.kt
@@ -30,9 +30,9 @@ import kotlinx.coroutines.flow.StateFlow
 import org.briarproject.android.dontkillmelib.DozeHelper
 import org.briarproject.mailbox.core.lifecycle.LifecycleManager
 import org.briarproject.mailbox.core.lifecycle.LifecycleManager.LifecycleState
-import org.briarproject.mailbox.core.system.AndroidWakeLockManager
 import org.briarproject.mailbox.core.system.DozeWatchdog
 import javax.inject.Inject
+import kotlin.concurrent.thread
 
 @HiltViewModel
 class MailboxViewModel @Inject constructor(
@@ -41,7 +41,6 @@ class MailboxViewModel @Inject constructor(
     private val dozeWatchdog: DozeWatchdog,
     handle: SavedStateHandle,
     private val lifecycleManager: LifecycleManager,
-    private val wakeLockManager: AndroidWakeLockManager,
 ) : AndroidViewModel(app) {
 
     val needToShowDoNotKillMeFragment get() = dozeHelper.needToShowDoNotKillMeFragment(app)
@@ -68,10 +67,11 @@ class MailboxViewModel @Inject constructor(
     }
 
     fun wipe() {
-        wakeLockManager.executeWakefully({
+        thread {
+            // TODO: handle return value
             lifecycleManager.wipeMailbox()
             MailboxService.stopService(getApplication())
-        }, "LifecycleWipe")
+        }
     }
 
     fun getAndResetDozeFlag() = dozeWatchdog.andResetDozeFlag
diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/android/StartReceiver.kt b/mailbox-android/src/main/java/org/briarproject/mailbox/android/StartReceiver.kt
new file mode 100644
index 0000000000000000000000000000000000000000..9865d44008e9c131cd57ec6d083f8440b87ffabb
--- /dev/null
+++ b/mailbox-android/src/main/java/org/briarproject/mailbox/android/StartReceiver.kt
@@ -0,0 +1,55 @@
+/*
+ *     Briar Mailbox
+ *     Copyright (C) 2021-2022  The Briar Project
+ *
+ *     This program is free software: you can redistribute it and/or modify
+ *     it under the terms of the GNU Affero General Public License as
+ *     published by the Free Software Foundation, either version 3 of the
+ *     License, or (at your option) any later version.
+ *
+ *     This program is distributed in the hope that it will be useful,
+ *     but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *     GNU Affero General Public License for more details.
+ *
+ *     You should have received a copy of the GNU Affero General Public License
+ *     along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+package org.briarproject.mailbox.android
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.Intent.ACTION_BOOT_COMPLETED
+import android.content.Intent.ACTION_MY_PACKAGE_REPLACED
+import dagger.hilt.android.AndroidEntryPoint
+import org.briarproject.mailbox.core.lifecycle.LifecycleManager
+import org.briarproject.mailbox.core.lifecycle.LifecycleManager.LifecycleState.NOT_STARTED
+import org.briarproject.mailbox.core.util.LogUtils.debug
+import org.slf4j.LoggerFactory.getLogger
+import javax.inject.Inject
+
+private val LOG = getLogger(StartReceiver::class.java)
+
+@AndroidEntryPoint
+class StartReceiver : BroadcastReceiver() {
+
+    @Inject
+    internal lateinit var lifecycleManager: LifecycleManager
+
+    override fun onReceive(context: Context, intent: Intent) {
+        val action = intent.action
+        if (action != ACTION_BOOT_COMPLETED && action != ACTION_MY_PACKAGE_REPLACED) return
+
+        val lifecycleState = lifecycleManager.lifecycleStateFlow.value
+        LOG.debug { "Received $action in state ${lifecycleState.name}" }
+        if (lifecycleState == NOT_STARTED) {
+            // On API 31, we can still start a foreground service from background here:
+            // https://developer.android.com/guide/components/foreground-services#background-start-restriction-exemptions
+            MailboxService.startService(context)
+        }
+    }
+
+}
diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/core/system/AndroidTaskScheduler.java b/mailbox-android/src/main/java/org/briarproject/mailbox/core/system/AndroidTaskScheduler.java
index 56df13486773ef9d0b0916a54723b972b1f51e20..c6bd6839db88a7a94c662eb97c5a7028f952459f 100644
--- a/mailbox-android/src/main/java/org/briarproject/mailbox/core/system/AndroidTaskScheduler.java
+++ b/mailbox-android/src/main/java/org/briarproject/mailbox/core/system/AndroidTaskScheduler.java
@@ -63,7 +63,6 @@ public class AndroidTaskScheduler implements TaskScheduler, Service {
 	private static final long ALARM_MS = INTERVAL_FIFTEEN_MINUTES;
 
 	private final Application app;
-	private final AndroidWakeLockManager wakeLockManager;
 	private final ScheduledExecutorService scheduledExecutorService;
 	private final AlarmManager alarmManager;
 
@@ -72,10 +71,8 @@ public class AndroidTaskScheduler implements TaskScheduler, Service {
 	private final Queue<ScheduledTask> tasks = new PriorityQueue<>();
 
 	AndroidTaskScheduler(Application app,
-			AndroidWakeLockManager wakeLockManager,
 			ScheduledExecutorService scheduledExecutorService) {
 		this.app = app;
-		this.wakeLockManager = wakeLockManager;
 		this.scheduledExecutorService = scheduledExecutorService;
 		alarmManager = (AlarmManager) requireNonNull(
 				app.getSystemService(ALARM_SERVICE));
@@ -107,29 +104,25 @@ public class AndroidTaskScheduler implements TaskScheduler, Service {
 	}
 
 	public void onAlarm(Intent intent) {
-		wakeLockManager.runWakefully(() -> {
-			int extraPid = intent.getIntExtra(EXTRA_PID, -1);
-			int currentPid = Process.myPid();
-			if (extraPid == currentPid) {
-				LOG.info("Alarm");
-				rescheduleAlarm();
-				runDueTasks();
-			} else {
-				info(LOG, () -> "Ignoring alarm with PID " + extraPid +
-						", current PID is " +
-						currentPid);
-			}
-		}, "TaskAlarm");
+		int extraPid = intent.getIntExtra(EXTRA_PID, -1);
+		int currentPid = Process.myPid();
+		if (extraPid == currentPid) {
+			LOG.info("Alarm");
+			rescheduleAlarm();
+			runDueTasks();
+		} else {
+			info(LOG, () -> "Ignoring alarm with PID " + extraPid +
+					", current PID is " + currentPid);
+		}
 	}
 
 	private Cancellable schedule(Runnable task, Executor executor, long delay,
 			TimeUnit unit, AtomicBoolean cancelled) {
 		long now = SystemClock.elapsedRealtime();
 		long dueMillis = now + MILLISECONDS.convert(delay, unit);
-		Runnable wakeful = () ->
-				wakeLockManager.executeWakefully(task, executor, "TaskHandoff");
+		Runnable wrapped = () -> executor.execute(task);
 		Future<?> check = scheduleCheckForDueTasks(delay, unit);
-		ScheduledTask s = new ScheduledTask(wakeful, dueMillis, check,
+		ScheduledTask s = new ScheduledTask(wrapped, dueMillis, check,
 				cancelled);
 		synchronized (lock) {
 			tasks.add(s);
@@ -149,12 +142,10 @@ public class AndroidTaskScheduler implements TaskScheduler, Service {
 	}
 
 	private Future<?> scheduleCheckForDueTasks(long delay, TimeUnit unit) {
-		Runnable wakeful = () -> wakeLockManager.runWakefully(
-				this::runDueTasks, "TaskScheduler");
-		return scheduledExecutorService.schedule(wakeful, delay, unit);
+		return scheduledExecutorService
+				.schedule(this::runDueTasks, delay, unit);
 	}
 
-	@Wakeful
 	private void runDueTasks() {
 		long now = SystemClock.elapsedRealtime();
 		List<ScheduledTask> due = new ArrayList<>();
diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/core/system/AndroidTaskSchedulerModule.java b/mailbox-android/src/main/java/org/briarproject/mailbox/core/system/AndroidTaskSchedulerModule.java
index 72733855e7bc5bef9cc55de6bf4d304826469179..0c5383f8fcb2c2e752207e1fe6e50d6fa38db03b 100644
--- a/mailbox-android/src/main/java/org/briarproject/mailbox/core/system/AndroidTaskSchedulerModule.java
+++ b/mailbox-android/src/main/java/org/briarproject/mailbox/core/system/AndroidTaskSchedulerModule.java
@@ -43,7 +43,7 @@ public class AndroidTaskSchedulerModule {
 			AndroidWakeLockManager wakeLockManager,
 			ScheduledExecutorService scheduledExecutorService) {
 		AndroidTaskScheduler scheduler = new AndroidTaskScheduler(app,
-				wakeLockManager, scheduledExecutorService);
+				scheduledExecutorService);
 		lifecycleManager.registerService(scheduler);
 		return scheduler;
 	}
diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/core/system/AndroidWakeLockManager.java b/mailbox-android/src/main/java/org/briarproject/mailbox/core/system/AndroidWakeLockManager.java
index 449828b396ee77a736ba5c2950d0ce5d153c22c4..8d717149a29c837393a378cf1ac39e82544c86b2 100644
--- a/mailbox-android/src/main/java/org/briarproject/mailbox/core/system/AndroidWakeLockManager.java
+++ b/mailbox-android/src/main/java/org/briarproject/mailbox/core/system/AndroidWakeLockManager.java
@@ -19,8 +19,6 @@
 
 package org.briarproject.mailbox.core.system;
 
-import java.util.concurrent.Executor;
-
 public interface AndroidWakeLockManager {
 
 	/**
@@ -28,27 +26,4 @@ public interface AndroidWakeLockManager {
 	 * logging; the underlying OS wake lock will use its own tag.
 	 */
 	AndroidWakeLock createWakeLock(String tag);
-
-	/**
-	 * Runs the given task while holding a wake lock.
-	 */
-	void runWakefully(Runnable r, String tag);
-
-	/**
-	 * Submits the given task to the given executor while holding a wake lock.
-	 * The lock is released when the task completes, or if an exception is
-	 * thrown while submitting or running the task.
-	 */
-	void executeWakefully(Runnable r, Executor executor, String tag);
-
-	/**
-	 * Starts a dedicated thread to run the given task asynchronously. A wake
-	 * lock is acquired before starting the thread and released when the task
-	 * completes, or if an exception is thrown while starting the thread or
-	 * running the task.
-	 * <p>
-	 * This method should only be used for lifecycle management tasks that
-	 * can't be run on an executor.
-	 */
-	void executeWakefully(Runnable r, String tag);
 }
diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/core/system/AndroidWakeLockManagerImpl.java b/mailbox-android/src/main/java/org/briarproject/mailbox/core/system/AndroidWakeLockManagerImpl.java
index 13bbfbe4a44c7694a78b60247bbd8d3ae1b354f4..c00275eff38f81abe57be4cbf39db37d2073a64a 100644
--- a/mailbox-android/src/main/java/org/briarproject/mailbox/core/system/AndroidWakeLockManagerImpl.java
+++ b/mailbox-android/src/main/java/org/briarproject/mailbox/core/system/AndroidWakeLockManagerImpl.java
@@ -24,7 +24,6 @@ import android.content.Context;
 import android.content.pm.PackageManager;
 import android.os.PowerManager;
 
-import java.util.concurrent.Executor;
 import java.util.concurrent.ScheduledExecutorService;
 
 import javax.inject.Inject;
@@ -66,57 +65,6 @@ class AndroidWakeLockManagerImpl implements AndroidWakeLockManager {
 		return new AndroidWakeLockImpl(sharedWakeLock, tag);
 	}
 
-	@Override
-	public void runWakefully(Runnable r, String tag) {
-		AndroidWakeLock wakeLock = createWakeLock(tag);
-		wakeLock.acquire();
-		try {
-			r.run();
-		} finally {
-			wakeLock.release();
-		}
-	}
-
-	@Override
-	public void executeWakefully(Runnable r, Executor executor, String tag) {
-		AndroidWakeLock wakeLock = createWakeLock(tag);
-		wakeLock.acquire();
-		try {
-			executor.execute(() -> {
-				try {
-					r.run();
-				} finally {
-					// Release the wake lock if the task throws an exception
-					wakeLock.release();
-				}
-			});
-		} catch (Exception e) {
-			// Release the wake lock if the executor throws an exception when
-			// we submit the task (in which case the release() call above won't
-			// happen)
-			wakeLock.release();
-			throw e;
-		}
-	}
-
-	@Override
-	public void executeWakefully(Runnable r, String tag) {
-		AndroidWakeLock wakeLock = createWakeLock(tag);
-		wakeLock.acquire();
-		try {
-			new Thread(() -> {
-				try {
-					r.run();
-				} finally {
-					wakeLock.release();
-				}
-			}).start();
-		} catch (Exception e) {
-			wakeLock.release();
-			throw e;
-		}
-	}
-
 	private String getWakeLockTag(Context ctx) {
 		PackageManager pm = ctx.getPackageManager();
 		if (isInstalled(pm, "com.huawei.powergenie")) {
diff --git a/mailbox-android/src/main/res/drawable-anydpi-v24/ic_notification_foreground.xml b/mailbox-android/src/main/res/drawable-anydpi-v24/ic_notification_foreground.xml
new file mode 100644
index 0000000000000000000000000000000000000000..e6104ddb4a95553cf9442da1e96bb54c49416511
--- /dev/null
+++ b/mailbox-android/src/main/res/drawable-anydpi-v24/ic_notification_foreground.xml
@@ -0,0 +1,15 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+	android:width="24dp"
+	android:height="24dp"
+	android:viewportWidth="24"
+	android:viewportHeight="24">
+	<group
+		android:scaleX="0.64941174"
+		android:scaleY="0.64941174"
+		android:translateX="2.2588236"
+		android:translateY="0.96">
+		<path
+			android:fillColor="#ffffff"
+			android:pathData="M3.334,0C2.4499,-0 1.6017,0.3579 0.9766,0.9941C0.3514,1.6304 0,2.4928 0,3.3926L0,25.1055C0,26.0053 0.3514,26.8696 0.9766,27.5059C1.6017,28.1421 2.4499,28.498 3.334,28.498L21.4902,28.498L27.5391,33.6426C28.5157,34.4732 30,33.766 30,32.4707L30,3.3926C30,2.4928 29.6485,1.6304 29.0234,0.9941C28.3983,0.3579 27.55,0 26.666,0L3.334,0zM5.334,5.4277L24.666,5.4277L24.666,15.0332L17.9121,15.0332L17.9121,18.3555L20.3828,18.3555C20.8283,18.3555 21.0512,18.904 20.7363,19.2246L15.4102,24.6445C15.2149,24.8432 14.8984,24.8432 14.7031,24.6445L9.377,19.2246C9.062,18.904 9.285,18.3555 9.7305,18.3555L12.2012,18.3555L12.2012,15.0332L5.334,15.0332L5.334,5.4277z" />
+	</group>
+</vector>
diff --git a/mailbox-android/src/main/res/drawable-hdpi/ic_notification_foreground.png b/mailbox-android/src/main/res/drawable-hdpi/ic_notification_foreground.png
new file mode 100644
index 0000000000000000000000000000000000000000..3a8fb15ba1a045dd1d4d6bbf356103eae1bd8d11
Binary files /dev/null and b/mailbox-android/src/main/res/drawable-hdpi/ic_notification_foreground.png differ
diff --git a/mailbox-android/src/main/res/drawable-mdpi/ic_notification_foreground.png b/mailbox-android/src/main/res/drawable-mdpi/ic_notification_foreground.png
new file mode 100644
index 0000000000000000000000000000000000000000..e2db859527dabf48159265d869503331ecf819a1
Binary files /dev/null and b/mailbox-android/src/main/res/drawable-mdpi/ic_notification_foreground.png differ
diff --git a/mailbox-android/src/main/res/drawable-xhdpi/ic_notification_foreground.png b/mailbox-android/src/main/res/drawable-xhdpi/ic_notification_foreground.png
new file mode 100644
index 0000000000000000000000000000000000000000..c85b6626b9e7726309dbbad7f83c7c388ff987c9
Binary files /dev/null and b/mailbox-android/src/main/res/drawable-xhdpi/ic_notification_foreground.png differ
diff --git a/mailbox-android/src/main/res/drawable-xxhdpi/ic_notification_foreground.png b/mailbox-android/src/main/res/drawable-xxhdpi/ic_notification_foreground.png
new file mode 100644
index 0000000000000000000000000000000000000000..170f1d5ab39b5b45aeede7f546287831ffb6d294
Binary files /dev/null and b/mailbox-android/src/main/res/drawable-xxhdpi/ic_notification_foreground.png differ
diff --git a/mailbox-core/src/main/java/org/briarproject/mailbox/core/db/JdbcDatabase.kt b/mailbox-core/src/main/java/org/briarproject/mailbox/core/db/JdbcDatabase.kt
index b9039cd2ac6f2ee492ec71c19396a2bc4d76d030..73713e8015265d892f8dc6e853cc6935db7a5add 100644
--- a/mailbox-core/src/main/java/org/briarproject/mailbox/core/db/JdbcDatabase.kt
+++ b/mailbox-core/src/main/java/org/briarproject/mailbox/core/db/JdbcDatabase.kt
@@ -134,7 +134,7 @@ abstract class JdbcDatabase(private val dbTypes: DatabaseTypes, private val cloc
             listener?.onDatabaseCompaction()
             val start: Long = now()
             compactAndClose()
-            logDuration(LOG, { "Compacting database" }, start)
+            logDuration(LOG, start) { "Compacting database" }
             // Allow the next transaction to reopen the DB
             connectionsLock.withLock {
                 closed = false
@@ -221,12 +221,12 @@ abstract class JdbcDatabase(private val dbTypes: DatabaseTypes, private val cloc
             LOG.trace("locking read lock")
             lock.readLock().lock()
             LOG.trace("locked read lock")
-            logDuration(LOG, { "Waiting for read lock" }, start)
+            logDuration(LOG, start) { "Waiting for read lock" }
         } else {
             LOG.trace { "locking write lock" }
             lock.writeLock().lock()
             LOG.trace { "locked write lock" }
-            logDuration(LOG, { "Waiting for write lock" }, start)
+            logDuration(LOG, start) { "Waiting for write lock" }
         }
         return try {
             Transaction(startTransaction(), readOnly)
diff --git a/mailbox-core/src/main/java/org/briarproject/mailbox/core/lifecycle/LifecycleManager.java b/mailbox-core/src/main/java/org/briarproject/mailbox/core/lifecycle/LifecycleManager.java
index af614491e37b9624e60829d67e776e1f50e33e93..4250ab95722cfd1c107407e25824fd2f5172e83a 100644
--- a/mailbox-core/src/main/java/org/briarproject/mailbox/core/lifecycle/LifecycleManager.java
+++ b/mailbox-core/src/main/java/org/briarproject/mailbox/core/lifecycle/LifecycleManager.java
@@ -38,7 +38,6 @@ public interface LifecycleManager {
 	 * The result of calling {@link #startServices()}.
 	 */
 	enum StartResult {
-		ALREADY_RUNNING,
 		SERVICE_ERROR,
 		SUCCESS
 	}
diff --git a/mailbox-core/src/main/java/org/briarproject/mailbox/core/lifecycle/LifecycleManagerImpl.kt b/mailbox-core/src/main/java/org/briarproject/mailbox/core/lifecycle/LifecycleManagerImpl.kt
index 2e4781ed23032f3e6a7fcc1deec6717d0f5096f7..ab7a5703f57429de8b0df195699c3cd5fb59e58c 100644
--- a/mailbox-core/src/main/java/org/briarproject/mailbox/core/lifecycle/LifecycleManagerImpl.kt
+++ b/mailbox-core/src/main/java/org/briarproject/mailbox/core/lifecycle/LifecycleManagerImpl.kt
@@ -20,10 +20,8 @@
 package org.briarproject.mailbox.core.lifecycle
 
 import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.StateFlow
 import org.briarproject.mailbox.core.db.Database
 import org.briarproject.mailbox.core.db.MigrationListener
-import org.briarproject.mailbox.core.lifecycle.LifecycleManager.LifecycleState
 import org.briarproject.mailbox.core.lifecycle.LifecycleManager.LifecycleState.COMPACTING_DATABASE
 import org.briarproject.mailbox.core.lifecycle.LifecycleManager.LifecycleState.MIGRATING_DATABASE
 import org.briarproject.mailbox.core.lifecycle.LifecycleManager.LifecycleState.NOT_STARTED
@@ -35,7 +33,6 @@ import org.briarproject.mailbox.core.lifecycle.LifecycleManager.LifecycleState.S
 import org.briarproject.mailbox.core.lifecycle.LifecycleManager.LifecycleState.WIPING
 import org.briarproject.mailbox.core.lifecycle.LifecycleManager.OpenDatabaseHook
 import org.briarproject.mailbox.core.lifecycle.LifecycleManager.StartResult
-import org.briarproject.mailbox.core.lifecycle.LifecycleManager.StartResult.ALREADY_RUNNING
 import org.briarproject.mailbox.core.lifecycle.LifecycleManager.StartResult.SERVICE_ERROR
 import org.briarproject.mailbox.core.lifecycle.LifecycleManager.StartResult.SUCCESS
 import org.briarproject.mailbox.core.setup.WipeManager
@@ -85,33 +82,37 @@ internal class LifecycleManagerImpl @Inject constructor(
     }
 
     override fun registerService(s: Service) {
-        LOG.info { "Registering service ${s.javaClass.simpleName}" }
+        LOG.info { "Registering service ${s.name()}" }
         services.add(s)
     }
 
     override fun registerOpenDatabaseHook(hook: OpenDatabaseHook) {
-        LOG.info { "Registering open database hook ${hook.javaClass.simpleName}" }
+        LOG.info { "Registering open database hook ${hook.name()}" }
         openDatabaseHooks.add(hook)
     }
 
     override fun registerForShutdown(e: ExecutorService) {
-        LOG.info { "Registering executor ${e.javaClass.simpleName}" }
+        LOG.info { "Registering executor ${e.name()}" }
         executors.add(e)
     }
 
     @GuardedBy("startStopWipeSemaphore")
     override fun startServices(): StartResult {
-        if (!startStopWipeSemaphore.tryAcquire()) {
-            LOG.info("Already starting or stopping")
-            return ALREADY_RUNNING
+        try {
+            startStopWipeSemaphore.acquire()
+        } catch (e: InterruptedException) {
+            LOG.warn("Interrupted while waiting to stop services")
+            return SERVICE_ERROR
+        }
+        if (!state.compareAndSet(NOT_STARTED, STARTING)) {
+            return SERVICE_ERROR
         }
-        state.compareAndSet(NOT_STARTED, STARTING)
         return try {
             LOG.info("Opening database")
             var start = now()
             val reopened = db.open(this)
-            if (reopened) logDuration(LOG, { "Reopening database" }, start)
-            else logDuration(LOG, { "Creating database" }, start)
+            if (reopened) logDuration(LOG, start) { "Reopening database" }
+            else logDuration(LOG, start) { "Creating database" }
             // Inform hooks that DB was opened
             db.write { txn ->
                 for (hook in openDatabaseHooks) {
@@ -124,7 +125,7 @@ internal class LifecycleManagerImpl @Inject constructor(
             for (s in services) {
                 start = now()
                 s.startService()
-                logDuration(LOG, { "Starting service  ${s.javaClass.simpleName}" }, start)
+                logDuration(LOG, start) { "Starting service  ${s.name()}" }
             }
             state.compareAndSet(STARTING_SERVICES, RUNNING)
             startupLatch.countDown()
@@ -166,15 +167,10 @@ internal class LifecycleManagerImpl @Inject constructor(
             val wiped = state.value == WIPING
             LOG.info("Stopping services")
             state.value = STOPPING
-            for (s in services) {
-                val start = now()
-                s.stopService()
-                logDuration(LOG, { "Stopping service " + s.javaClass.simpleName }, start)
-            }
-            for (e in executors) {
-                LOG.trace { "Stopping executor ${e.javaClass.simpleName}" }
-                e.shutdownNow()
-            }
+
+            stopAllServices()
+            stopAllExecutors()
+
             if (wiped) {
                 // If we just wiped, the database has already been closed, so we should not call
                 // close(). Since the services are being shut down after wiping (so that the web
@@ -182,21 +178,46 @@ internal class LifecycleManagerImpl @Inject constructor(
                 // API call created some files in the meantime. To make sure we delete those in
                 // case of a wipe, repeat deletion of files here after the services have been
                 // stopped.
-                wipeManager.wipe(wipeDatabase = false)
+                wipeFilesAgain()
             } else {
-                val start = now()
-                db.close()
-                logDuration(LOG, { "Closing database" }, start)
+                closeDatabase()
             }
+
             shutdownLatch.countDown()
-        } catch (e: ServiceException) {
-            logException(LOG, e)
         } finally {
             state.compareAndSet(STOPPING, STOPPED)
             startStopWipeSemaphore.release()
         }
     }
 
+    @GuardedBy("startStopWipeSemaphore")
+    private fun stopAllServices() {
+        for (s in services) {
+            run("stopping service ${s.name()}") {
+                s.stopService()
+            }
+        }
+    }
+
+    @GuardedBy("startStopWipeSemaphore")
+    private fun stopAllExecutors() {
+        for (e in executors) {
+            run("stopping executor ${e.name()}") {
+                e.shutdownNow()
+            }
+        }
+    }
+
+    @GuardedBy("startStopWipeSemaphore")
+    private fun wipeFilesAgain() = run("wiping files again") {
+        wipeManager.wipeFilesOnly()
+    }
+
+    @GuardedBy("startStopWipeSemaphore")
+    private fun closeDatabase() = run("closing database") {
+        db.close()
+    }
+
     @GuardedBy("startStopWipeSemaphore")
     override fun wipeMailbox(): Boolean {
         try {
@@ -209,8 +230,9 @@ internal class LifecycleManagerImpl @Inject constructor(
             return false
         }
         try {
-            wipeManager.wipe(wipeDatabase = true)
-
+            run("wiping database and files") {
+                wipeManager.wipeDatabaseAndFiles()
+            }
             // We need to move this to a thread so that the webserver call can finish when it calls
             // this. Otherwise we'll end up in a deadlock: the same thread trying to stop the
             // webserver from within a call that wants to send a response on the very same webserver.
@@ -219,7 +241,6 @@ internal class LifecycleManagerImpl @Inject constructor(
             thread {
                 stopServices()
             }
-
             return true
         } finally {
             startStopWipeSemaphore.release()
@@ -227,25 +248,28 @@ internal class LifecycleManagerImpl @Inject constructor(
     }
 
     @Throws(InterruptedException::class)
-    override fun waitForDatabase() {
-        dbLatch.await()
-    }
+    override fun waitForDatabase() = dbLatch.await()
 
     @Throws(InterruptedException::class)
-    override fun waitForStartup() {
-        startupLatch.await()
-    }
+    override fun waitForStartup() = startupLatch.await()
 
     @Throws(InterruptedException::class)
-    override fun waitForShutdown() {
-        shutdownLatch.await()
-    }
+    override fun waitForShutdown() = shutdownLatch.await()
 
-    override fun getLifecycleState(): LifecycleState {
-        return state.value
-    }
+    override fun getLifecycleState() = state.value
+
+    override fun getLifecycleStateFlow() = state
 
-    override fun getLifecycleStateFlow(): StateFlow<LifecycleState> {
-        return state
+    private fun run(name: String, task: () -> Unit) {
+        LOG.trace { name }
+        val start = now()
+        try {
+            task()
+        } catch (throwable: Throwable) {
+            logException(LOG, throwable) { "Error while $name" }
+        }
+        logDuration(LOG, start) { name }
     }
+
+    private fun Any.name() = javaClass.simpleName
 }
diff --git a/mailbox-core/src/main/java/org/briarproject/mailbox/core/setup/WipeManager.kt b/mailbox-core/src/main/java/org/briarproject/mailbox/core/setup/WipeManager.kt
index 68250a4a1fd9b947503a25c4097b42f7724edabd..59629b3cd8459a2a03b562559ce2f84f13dba434 100644
--- a/mailbox-core/src/main/java/org/briarproject/mailbox/core/setup/WipeManager.kt
+++ b/mailbox-core/src/main/java/org/briarproject/mailbox/core/setup/WipeManager.kt
@@ -42,12 +42,17 @@ class WipeManager @Inject constructor(
     /*
      * This must only be called by the LifecycleManager
      */
-    fun wipe(wipeDatabase: Boolean) {
-        if (wipeDatabase) {
-            db.dropAllTablesAndClose()
-            val dir = databaseConfig.getDatabaseDirectory()
-            IoUtils.deleteFileOrDir(dir)
-        }
+    fun wipeDatabaseAndFiles() {
+        db.dropAllTablesAndClose()
+        val dir = databaseConfig.getDatabaseDirectory()
+        IoUtils.deleteFileOrDir(dir)
+        fileManager.deleteAllFiles()
+    }
+
+    /*
+     * This must only be called by the LifecycleManager
+     */
+    fun wipeFilesOnly() {
         fileManager.deleteAllFiles()
     }
 
diff --git a/mailbox-core/src/main/java/org/briarproject/mailbox/core/tor/TorConstants.java b/mailbox-core/src/main/java/org/briarproject/mailbox/core/tor/TorConstants.java
index ef1ffd649c86e183bff5f317d989aa9288cae8d8..ca6b6633ecfe7eb554e85c5016f63bf91271f57d 100644
--- a/mailbox-core/src/main/java/org/briarproject/mailbox/core/tor/TorConstants.java
+++ b/mailbox-core/src/main/java/org/briarproject/mailbox/core/tor/TorConstants.java
@@ -26,7 +26,7 @@ public interface TorConstants {
 	String HS_PRIVATE_KEY_V3 = "onionPrivKey3";
 	String HS_ADDRESS_V3 = "onionAddress3";
 
-	int SOCKS_PORT = 59052;
-	int CONTROL_PORT = 59053;
+	int SOCKS_PORT = 59054;
+	int CONTROL_PORT = 59055;
 
 }
diff --git a/mailbox-core/src/main/java/org/briarproject/mailbox/core/util/LogUtils.kt b/mailbox-core/src/main/java/org/briarproject/mailbox/core/util/LogUtils.kt
index 7adf285c8aa598a7a6746914218c7d9b9f9c97f5..a0ae2f033abc2a1a5e54c76ad9bc72e241ffacf6 100644
--- a/mailbox-core/src/main/java/org/briarproject/mailbox/core/util/LogUtils.kt
+++ b/mailbox-core/src/main/java/org/briarproject/mailbox/core/util/LogUtils.kt
@@ -68,13 +68,18 @@ object LogUtils {
      * @param start  the start time of the task, as returned by [now]
      */
     @JvmStatic
-    fun logDuration(logger: Logger, msg: () -> String, start: Long) {
+    fun logDuration(logger: Logger, start: Long, msg: () -> String) {
         logger.trace {
             val duration = now() - start
             "${msg()} took $duration ms"
         }
     }
 
+    @JvmStatic
+    fun logException(logger: Logger, t: Throwable, message: () -> String) {
+        if (logger.isWarnEnabled) logger.warn(message(), t)
+    }
+
     @JvmStatic
     fun logException(logger: Logger, t: Throwable) {
         if (logger.isWarnEnabled) logger.warn(t.toString(), t)
diff --git a/mailbox-core/src/main/resources/torrc b/mailbox-core/src/main/resources/torrc
index 23beef726a8a6ed0efc2f36e90b51fd536eb2aa1..5a99c8b36284031e47b5a90916c82af0df924608 100644
--- a/mailbox-core/src/main/resources/torrc
+++ b/mailbox-core/src/main/resources/torrc
@@ -1,6 +1,6 @@
-ControlPort 59053
+ControlPort 59055
 CookieAuthentication 1
 DisableNetwork 1
 RunAsDaemon 1
 SafeSocks 1
-SocksPort 59052
+SocksPort 59054