diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/db/DatabaseComponent.java b/bramble-api/src/main/java/org/briarproject/bramble/api/db/DatabaseComponent.java
index a76138c1f98b83e0230ae782e2ab7f4a8012380f..123ce4396f9b0d1e08a7c888792e5bca4cd304f7 100644
--- a/bramble-api/src/main/java/org/briarproject/bramble/api/db/DatabaseComponent.java
+++ b/bramble-api/src/main/java/org/briarproject/bramble/api/db/DatabaseComponent.java
@@ -43,7 +43,7 @@ public interface DatabaseComponent {
 	 * @throws DataTooOldException if the data uses an older schema than the
 	 * current code and cannot be migrated
 	 */
-	boolean open() throws DbException;
+	boolean open(@Nullable MigrationListener listener) throws DbException;
 
 	/**
 	 * Waits for any open transactions to finish and closes the database.
diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/db/MigrationListener.java b/bramble-api/src/main/java/org/briarproject/bramble/api/db/MigrationListener.java
new file mode 100644
index 0000000000000000000000000000000000000000..79e292a75f3b9afd9cb40b1c95911afbaa4223dd
--- /dev/null
+++ b/bramble-api/src/main/java/org/briarproject/bramble/api/db/MigrationListener.java
@@ -0,0 +1,11 @@
+package org.briarproject.bramble.api.db;
+
+public interface MigrationListener {
+
+	/**
+	 * This is called when a migration is started while opening the database.
+	 * It will be called once for each migration being applied.
+	 */
+	void onMigrationRun();
+
+}
diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/lifecycle/LifecycleManager.java b/bramble-api/src/main/java/org/briarproject/bramble/api/lifecycle/LifecycleManager.java
index b70c9dec9b2a624e88d8d5dd9c7519810f9b08fb..5a036a3b6cc2005a36c28bbaacb67638256941a1 100644
--- a/bramble-api/src/main/java/org/briarproject/bramble/api/lifecycle/LifecycleManager.java
+++ b/bramble-api/src/main/java/org/briarproject/bramble/api/lifecycle/LifecycleManager.java
@@ -29,6 +29,19 @@ public interface LifecycleManager {
 		SUCCESS
 	}
 
+	/**
+	 * The state the lifecycle can be in.
+	 * Returned by {@link #getLifecycleState()}
+	 */
+	enum LifecycleState {
+
+		STARTING, MIGRATING_DATABASE, STARTING_SERVICES, RUNNING, STOPPING;
+
+		public boolean isAfter(LifecycleState state) {
+			return ordinal() > state.ordinal();
+		}
+	}
+
 	/**
 	 * Registers a {@link Service} to be started and stopped.
 	 */
@@ -76,4 +89,10 @@ public interface LifecycleManager {
 	 * the {@link DatabaseComponent} to be closed before returning.
 	 */
 	void waitForShutdown() throws InterruptedException;
+
+	/**
+	 * Returns the current state of the lifecycle.
+	 */
+	LifecycleState getLifecycleState();
+
 }
\ No newline at end of file
diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/lifecycle/event/LifecycleEvent.java b/bramble-api/src/main/java/org/briarproject/bramble/api/lifecycle/event/LifecycleEvent.java
new file mode 100644
index 0000000000000000000000000000000000000000..4e6370bcfb0064b42f6391f141b4fc8702778a0b
--- /dev/null
+++ b/bramble-api/src/main/java/org/briarproject/bramble/api/lifecycle/event/LifecycleEvent.java
@@ -0,0 +1,20 @@
+package org.briarproject.bramble.api.lifecycle.event;
+
+import org.briarproject.bramble.api.event.Event;
+import org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState;
+
+/**
+ * An event that is broadcast when the app enters a new lifecycle state.
+ */
+public class LifecycleEvent extends Event {
+
+	private final LifecycleState state;
+
+	public LifecycleEvent(LifecycleState state) {
+		this.state = state;
+	}
+
+	public LifecycleState getLifecycleState() {
+		return state;
+	}
+}
diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/lifecycle/event/ShutdownEvent.java b/bramble-api/src/main/java/org/briarproject/bramble/api/lifecycle/event/ShutdownEvent.java
deleted file mode 100644
index ba0c4d37f03e963483432eb115070379d2abbcc5..0000000000000000000000000000000000000000
--- a/bramble-api/src/main/java/org/briarproject/bramble/api/lifecycle/event/ShutdownEvent.java
+++ /dev/null
@@ -1,9 +0,0 @@
-package org.briarproject.bramble.api.lifecycle.event;
-
-import org.briarproject.bramble.api.event.Event;
-
-/**
- * An event that is broadcast when the app is shutting down.
- */
-public class ShutdownEvent extends Event {
-}
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/db/Database.java b/bramble-core/src/main/java/org/briarproject/bramble/db/Database.java
index 9d1c7636498a869c856ad57a134514725fdd2dbc..0a6e54fbd85d47b5417444ab5b0c2b0513004289 100644
--- a/bramble-core/src/main/java/org/briarproject/bramble/db/Database.java
+++ b/bramble-core/src/main/java/org/briarproject/bramble/db/Database.java
@@ -6,6 +6,7 @@ import org.briarproject.bramble.api.db.DataTooNewException;
 import org.briarproject.bramble.api.db.DataTooOldException;
 import org.briarproject.bramble.api.db.DbException;
 import org.briarproject.bramble.api.db.Metadata;
+import org.briarproject.bramble.api.db.MigrationListener;
 import org.briarproject.bramble.api.identity.Author;
 import org.briarproject.bramble.api.identity.AuthorId;
 import org.briarproject.bramble.api.identity.LocalAuthor;
@@ -45,7 +46,7 @@ interface Database<T> {
 	 * @throws DataTooOldException if the data uses an older schema than the
 	 * current code and cannot be migrated
 	 */
-	boolean open() throws DbException;
+	boolean open(@Nullable MigrationListener listener) throws DbException;
 
 	/**
 	 * Prevents new transactions from starting, waits for all current
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/db/DatabaseComponentImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/db/DatabaseComponentImpl.java
index af80da92b3c7f8d7499b1a0e126d0af9f75f42e0..5809c09eef279fb8b284c31bfb5b049027d28a57 100644
--- a/bramble-core/src/main/java/org/briarproject/bramble/db/DatabaseComponentImpl.java
+++ b/bramble-core/src/main/java/org/briarproject/bramble/db/DatabaseComponentImpl.java
@@ -10,6 +10,7 @@ import org.briarproject.bramble.api.db.ContactExistsException;
 import org.briarproject.bramble.api.db.DatabaseComponent;
 import org.briarproject.bramble.api.db.DbException;
 import org.briarproject.bramble.api.db.Metadata;
+import org.briarproject.bramble.api.db.MigrationListener;
 import org.briarproject.bramble.api.db.NoSuchContactException;
 import org.briarproject.bramble.api.db.NoSuchGroupException;
 import org.briarproject.bramble.api.db.NoSuchLocalAuthorException;
@@ -100,8 +101,9 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
 	}
 
 	@Override
-	public boolean open() throws DbException {
-		boolean reopened = db.open();
+	public boolean open(@Nullable MigrationListener listener)
+			throws DbException {
+		boolean reopened = db.open(listener);
 		shutdown.addShutdownHook(() -> {
 			try {
 				close();
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/db/H2Database.java b/bramble-core/src/main/java/org/briarproject/bramble/db/H2Database.java
index 6a81969260df8283a07c2ee9ab6a48efdd040778..1c1983e6771a2cc174ebcbf68ede5879aab7569f 100644
--- a/bramble-core/src/main/java/org/briarproject/bramble/db/H2Database.java
+++ b/bramble-core/src/main/java/org/briarproject/bramble/db/H2Database.java
@@ -3,6 +3,7 @@ package org.briarproject.bramble.db;
 import org.briarproject.bramble.api.crypto.SecretKey;
 import org.briarproject.bramble.api.db.DatabaseConfig;
 import org.briarproject.bramble.api.db.DbException;
+import org.briarproject.bramble.api.db.MigrationListener;
 import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
 import org.briarproject.bramble.api.system.Clock;
 import org.briarproject.bramble.util.StringUtils;
@@ -13,6 +14,7 @@ import java.sql.DriverManager;
 import java.sql.SQLException;
 import java.util.Properties;
 
+import javax.annotation.Nullable;
 import javax.inject.Inject;
 
 /**
@@ -42,10 +44,11 @@ class H2Database extends JdbcDatabase {
 	}
 
 	@Override
-	public boolean open() throws DbException {
+	public boolean open(@Nullable MigrationListener listener)
+			throws DbException {
 		boolean reopen = config.databaseExists();
 		if (!reopen) config.getDatabaseDirectory().mkdirs();
-		super.open("org.h2.Driver", reopen);
+		super.open("org.h2.Driver", reopen, listener);
 		return reopen;
 	}
 
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/db/HyperSqlDatabase.java b/bramble-core/src/main/java/org/briarproject/bramble/db/HyperSqlDatabase.java
index 1db9248cb28b6d67f7de3818b822480296dabf5e..6a87ededae7b862c992745f15ac5695f881a6cf0 100644
--- a/bramble-core/src/main/java/org/briarproject/bramble/db/HyperSqlDatabase.java
+++ b/bramble-core/src/main/java/org/briarproject/bramble/db/HyperSqlDatabase.java
@@ -3,6 +3,7 @@ package org.briarproject.bramble.db;
 import org.briarproject.bramble.api.crypto.SecretKey;
 import org.briarproject.bramble.api.db.DatabaseConfig;
 import org.briarproject.bramble.api.db.DbException;
+import org.briarproject.bramble.api.db.MigrationListener;
 import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
 import org.briarproject.bramble.api.system.Clock;
 import org.briarproject.bramble.util.StringUtils;
@@ -13,6 +14,7 @@ import java.sql.DriverManager;
 import java.sql.SQLException;
 import java.sql.Statement;
 
+import javax.annotation.Nullable;
 import javax.inject.Inject;
 
 /**
@@ -44,10 +46,10 @@ class HyperSqlDatabase extends JdbcDatabase {
 	}
 
 	@Override
-	public boolean open() throws DbException {
+	public boolean open(@Nullable MigrationListener listener) throws DbException {
 		boolean reopen = config.databaseExists();
 		if (!reopen) config.getDatabaseDirectory().mkdirs();
-		super.open("org.hsqldb.jdbc.JDBCDriver", reopen);
+		super.open("org.hsqldb.jdbc.JDBCDriver", reopen, listener);
 		return reopen;
 	}
 
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/db/JdbcDatabase.java b/bramble-core/src/main/java/org/briarproject/bramble/db/JdbcDatabase.java
index f1f795a8c1eb85cb33dd73c7894ad144a9649fde..e71f30c587215dda0cd8510981258e5fdc94c548 100644
--- a/bramble-core/src/main/java/org/briarproject/bramble/db/JdbcDatabase.java
+++ b/bramble-core/src/main/java/org/briarproject/bramble/db/JdbcDatabase.java
@@ -8,6 +8,7 @@ import org.briarproject.bramble.api.db.DataTooOldException;
 import org.briarproject.bramble.api.db.DbClosedException;
 import org.briarproject.bramble.api.db.DbException;
 import org.briarproject.bramble.api.db.Metadata;
+import org.briarproject.bramble.api.db.MigrationListener;
 import org.briarproject.bramble.api.identity.Author;
 import org.briarproject.bramble.api.identity.AuthorId;
 import org.briarproject.bramble.api.identity.LocalAuthor;
@@ -301,7 +302,8 @@ abstract class JdbcDatabase implements Database<Connection> {
 		this.clock = clock;
 	}
 
-	protected void open(String driverClass, boolean reopen) throws DbException {
+	protected void open(String driverClass, boolean reopen,
+			@Nullable MigrationListener listener) throws DbException {
 		// Load the JDBC driver
 		try {
 			Class.forName(driverClass);
@@ -312,7 +314,7 @@ abstract class JdbcDatabase implements Database<Connection> {
 		Connection txn = startTransaction();
 		try {
 			if (reopen) {
-				checkSchemaVersion(txn);
+				checkSchemaVersion(txn, listener);
 			} else {
 				createTables(txn);
 				storeSchemaVersion(txn, CODE_SCHEMA_VERSION);
@@ -335,7 +337,8 @@ abstract class JdbcDatabase implements Database<Connection> {
 	 * @throws DataTooOldException if the data uses an older schema than the
 	 * current code and cannot be migrated
 	 */
-	private void checkSchemaVersion(Connection txn) throws DbException {
+	private void checkSchemaVersion(Connection txn,
+			@Nullable MigrationListener listener) throws DbException {
 		Settings s = getSettings(txn, DB_SETTINGS_NAMESPACE);
 		int dataSchemaVersion = s.getInt(SCHEMA_VERSION_KEY, -1);
 		if (dataSchemaVersion == -1) throw new DbException();
@@ -348,6 +351,7 @@ abstract class JdbcDatabase implements Database<Connection> {
 			if (start == dataSchemaVersion) {
 				if (LOG.isLoggable(INFO))
 					LOG.info("Migrating from schema " + start + " to " + end);
+				if (listener != null) listener.onMigrationRun();
 				// Apply the migration
 				m.migrate(txn);
 				// Store the new schema version
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/lifecycle/LifecycleManagerImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/lifecycle/LifecycleManagerImpl.java
index 029170c911c4814c76b020115c6172a47ef12f79..0f41cbf211676f5427b56591a24e872c085ecef9 100644
--- a/bramble-core/src/main/java/org/briarproject/bramble/lifecycle/LifecycleManagerImpl.java
+++ b/bramble-core/src/main/java/org/briarproject/bramble/lifecycle/LifecycleManagerImpl.java
@@ -6,6 +6,7 @@ import org.briarproject.bramble.api.db.DataTooNewException;
 import org.briarproject.bramble.api.db.DataTooOldException;
 import org.briarproject.bramble.api.db.DatabaseComponent;
 import org.briarproject.bramble.api.db.DbException;
+import org.briarproject.bramble.api.db.MigrationListener;
 import org.briarproject.bramble.api.db.Transaction;
 import org.briarproject.bramble.api.event.EventBus;
 import org.briarproject.bramble.api.identity.AuthorFactory;
@@ -14,7 +15,7 @@ import org.briarproject.bramble.api.identity.LocalAuthor;
 import org.briarproject.bramble.api.lifecycle.LifecycleManager;
 import org.briarproject.bramble.api.lifecycle.Service;
 import org.briarproject.bramble.api.lifecycle.ServiceException;
-import org.briarproject.bramble.api.lifecycle.event.ShutdownEvent;
+import org.briarproject.bramble.api.lifecycle.event.LifecycleEvent;
 import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
 import org.briarproject.bramble.api.sync.Client;
 
@@ -31,6 +32,11 @@ import javax.inject.Inject;
 
 import static java.util.logging.Level.INFO;
 import static java.util.logging.Level.WARNING;
+import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.MIGRATING_DATABASE;
+import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.RUNNING;
+import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STARTING;
+import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STARTING_SERVICES;
+import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STOPPING;
 import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.ALREADY_RUNNING;
 import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.DATA_TOO_NEW_ERROR;
 import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.DATA_TOO_OLD_ERROR;
@@ -40,7 +46,7 @@ import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResul
 
 @ThreadSafe
 @NotNullByDefault
-class LifecycleManagerImpl implements LifecycleManager {
+class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
 
 	private static final Logger LOG =
 			Logger.getLogger(LifecycleManagerImpl.class.getName());
@@ -58,6 +64,8 @@ class LifecycleManagerImpl implements LifecycleManager {
 	private final CountDownLatch startupLatch = new CountDownLatch(1);
 	private final CountDownLatch shutdownLatch = new CountDownLatch(1);
 
+	private volatile LifecycleState state = STARTING;
+
 	@Inject
 	LifecycleManagerImpl(DatabaseComponent db, EventBus eventBus,
 			CryptoComponent crypto, AuthorFactory authorFactory,
@@ -123,7 +131,7 @@ class LifecycleManagerImpl implements LifecycleManager {
 			LOG.info("Starting services");
 			long start = System.currentTimeMillis();
 
-			boolean reopened = db.open();
+			boolean reopened = db.open(this);
 			long duration = System.currentTimeMillis() - start;
 			if (LOG.isLoggable(INFO)) {
 				if (reopened)
@@ -135,7 +143,10 @@ class LifecycleManagerImpl implements LifecycleManager {
 				registerLocalAuthor(createLocalAuthor(nickname));
 			}
 
+			state = STARTING_SERVICES;
 			dbLatch.countDown();
+			eventBus.broadcast(new LifecycleEvent(STARTING_SERVICES));
+
 			Transaction txn = db.startTransaction(false);
 			try {
 				for (Client c : clients) {
@@ -161,7 +172,10 @@ class LifecycleManagerImpl implements LifecycleManager {
 							+ " took " + duration + " ms");
 				}
 			}
+
+			state = RUNNING;
 			startupLatch.countDown();
+			eventBus.broadcast(new LifecycleEvent(RUNNING));
 			return SUCCESS;
 		} catch (DataTooOldException e) {
 			if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
@@ -180,6 +194,12 @@ class LifecycleManagerImpl implements LifecycleManager {
 		}
 	}
 
+	@Override
+	public void onMigrationRun() {
+		state = MIGRATING_DATABASE;
+		eventBus.broadcast(new LifecycleEvent(MIGRATING_DATABASE));
+	}
+
 	@Override
 	public void stopServices() {
 		try {
@@ -190,7 +210,8 @@ class LifecycleManagerImpl implements LifecycleManager {
 		}
 		try {
 			LOG.info("Stopping services");
-			eventBus.broadcast(new ShutdownEvent());
+			state = STOPPING;
+			eventBus.broadcast(new LifecycleEvent(STOPPING));
 			for (Service s : services) {
 				long start = System.currentTimeMillis();
 				s.stopService();
@@ -235,4 +256,8 @@ class LifecycleManagerImpl implements LifecycleManager {
 		shutdownLatch.await();
 	}
 
+	@Override
+	public LifecycleState getLifecycleState() {
+		return state;
+	}
 }
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/sync/DuplexOutgoingSession.java b/bramble-core/src/main/java/org/briarproject/bramble/sync/DuplexOutgoingSession.java
index 3fdabc01dcc239959f3c54d339c050cc7e6d4536..80f699a1f9e6ebdb134ae0835541018992c6c406 100644
--- a/bramble-core/src/main/java/org/briarproject/bramble/sync/DuplexOutgoingSession.java
+++ b/bramble-core/src/main/java/org/briarproject/bramble/sync/DuplexOutgoingSession.java
@@ -10,7 +10,7 @@ import org.briarproject.bramble.api.event.Event;
 import org.briarproject.bramble.api.event.EventBus;
 import org.briarproject.bramble.api.event.EventListener;
 import org.briarproject.bramble.api.lifecycle.IoExecutor;
-import org.briarproject.bramble.api.lifecycle.event.ShutdownEvent;
+import org.briarproject.bramble.api.lifecycle.event.LifecycleEvent;
 import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
 import org.briarproject.bramble.api.sync.Ack;
 import org.briarproject.bramble.api.sync.Offer;
@@ -38,6 +38,7 @@ import javax.annotation.concurrent.ThreadSafe;
 import static java.util.concurrent.TimeUnit.MILLISECONDS;
 import static java.util.logging.Level.INFO;
 import static java.util.logging.Level.WARNING;
+import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STOPPING;
 import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_IDS;
 import static org.briarproject.bramble.api.sync.SyncConstants.MAX_RECORD_PAYLOAD_LENGTH;
 
@@ -209,8 +210,9 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
 		} else if (e instanceof MessageToRequestEvent) {
 			if (((MessageToRequestEvent) e).getContactId().equals(contactId))
 				generateRequest();
-		} else if (e instanceof ShutdownEvent) {
-			interrupt();
+		} else if (e instanceof LifecycleEvent) {
+			LifecycleEvent l = (LifecycleEvent) e;
+			if (l.getLifecycleState() == STOPPING) interrupt();
 		}
 	}
 
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/sync/IncomingSession.java b/bramble-core/src/main/java/org/briarproject/bramble/sync/IncomingSession.java
index 249d1f2c6be2e227f557b84b36131f567f10fcdb..32eb58df4854d87c080671e913926e7dd3a2e925 100644
--- a/bramble-core/src/main/java/org/briarproject/bramble/sync/IncomingSession.java
+++ b/bramble-core/src/main/java/org/briarproject/bramble/sync/IncomingSession.java
@@ -11,7 +11,7 @@ import org.briarproject.bramble.api.event.Event;
 import org.briarproject.bramble.api.event.EventBus;
 import org.briarproject.bramble.api.event.EventListener;
 import org.briarproject.bramble.api.lifecycle.IoExecutor;
-import org.briarproject.bramble.api.lifecycle.event.ShutdownEvent;
+import org.briarproject.bramble.api.lifecycle.event.LifecycleEvent;
 import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
 import org.briarproject.bramble.api.sync.Ack;
 import org.briarproject.bramble.api.sync.Message;
@@ -27,6 +27,7 @@ import java.util.logging.Logger;
 import javax.annotation.concurrent.ThreadSafe;
 
 import static java.util.logging.Level.WARNING;
+import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STOPPING;
 
 /**
  * An incoming {@link SyncSession}.
@@ -96,8 +97,9 @@ class IncomingSession implements SyncSession, EventListener {
 		if (e instanceof ContactRemovedEvent) {
 			ContactRemovedEvent c = (ContactRemovedEvent) e;
 			if (c.getContactId().equals(contactId)) interrupt();
-		} else if (e instanceof ShutdownEvent) {
-			interrupt();
+		} else if (e instanceof LifecycleEvent) {
+			LifecycleEvent l = (LifecycleEvent) e;
+			if (l.getLifecycleState() == STOPPING) interrupt();
 		}
 	}
 
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/sync/SimplexOutgoingSession.java b/bramble-core/src/main/java/org/briarproject/bramble/sync/SimplexOutgoingSession.java
index 3d0ecae22b739912b498e380156a9970eedb2028..b1f8198f86dcb6b0d771527876de0ce9261ab057 100644
--- a/bramble-core/src/main/java/org/briarproject/bramble/sync/SimplexOutgoingSession.java
+++ b/bramble-core/src/main/java/org/briarproject/bramble/sync/SimplexOutgoingSession.java
@@ -10,7 +10,7 @@ import org.briarproject.bramble.api.event.Event;
 import org.briarproject.bramble.api.event.EventBus;
 import org.briarproject.bramble.api.event.EventListener;
 import org.briarproject.bramble.api.lifecycle.IoExecutor;
-import org.briarproject.bramble.api.lifecycle.event.ShutdownEvent;
+import org.briarproject.bramble.api.lifecycle.event.LifecycleEvent;
 import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
 import org.briarproject.bramble.api.sync.Ack;
 import org.briarproject.bramble.api.sync.RecordWriter;
@@ -28,6 +28,7 @@ import javax.annotation.concurrent.ThreadSafe;
 
 import static java.util.logging.Level.INFO;
 import static java.util.logging.Level.WARNING;
+import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STOPPING;
 import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_IDS;
 import static org.briarproject.bramble.api.sync.SyncConstants.MAX_RECORD_PAYLOAD_LENGTH;
 
@@ -109,8 +110,9 @@ class SimplexOutgoingSession implements SyncSession, EventListener {
 		if (e instanceof ContactRemovedEvent) {
 			ContactRemovedEvent c = (ContactRemovedEvent) e;
 			if (c.getContactId().equals(contactId)) interrupt();
-		} else if (e instanceof ShutdownEvent) {
-			interrupt();
+		} else if (e instanceof LifecycleEvent) {
+			LifecycleEvent l = (LifecycleEvent) e;
+			if (l.getLifecycleState() == STOPPING) interrupt();
 		}
 	}
 
diff --git a/bramble-core/src/test/java/org/briarproject/bramble/db/DatabaseComponentImplTest.java b/bramble-core/src/test/java/org/briarproject/bramble/db/DatabaseComponentImplTest.java
index b2e4842db87c3f0a6633f1196756be456b7671b9..18de0d7e847f57156dd7ebbcab6474bd63e21404 100644
--- a/bramble-core/src/test/java/org/briarproject/bramble/db/DatabaseComponentImplTest.java
+++ b/bramble-core/src/test/java/org/briarproject/bramble/db/DatabaseComponentImplTest.java
@@ -134,7 +134,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
 		int shutdownHandle = 12345;
 		context.checking(new Expectations() {{
 			// open()
-			oneOf(database).open();
+			oneOf(database).open(null);
 			will(returnValue(false));
 			oneOf(shutdown).addShutdownHook(with(any(Runnable.class)));
 			will(returnValue(shutdownHandle));
@@ -201,7 +201,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
 		DatabaseComponent db = createDatabaseComponent(database, eventBus,
 				shutdown);
 
-		assertFalse(db.open());
+		assertFalse(db.open(null));
 		Transaction transaction = db.startTransaction(false);
 		try {
 			db.addLocalAuthor(transaction, localAuthor);
@@ -1501,7 +1501,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
 		MessageId messageId2 = new MessageId(TestUtils.getRandomId());
 		context.checking(new Expectations() {{
 			// open()
-			oneOf(database).open();
+			oneOf(database).open(null);
 			will(returnValue(false));
 			oneOf(shutdown).addShutdownHook(with(any(Runnable.class)));
 			will(returnValue(shutdownHandle));
@@ -1543,7 +1543,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
 		DatabaseComponent db = createDatabaseComponent(database, eventBus,
 				shutdown);
 
-		assertFalse(db.open());
+		assertFalse(db.open(null));
 		Transaction transaction = db.startTransaction(false);
 		try {
 			db.addLocalMessage(transaction, message, metadata, true);
diff --git a/bramble-core/src/test/java/org/briarproject/bramble/db/DatabaseMigrationTest.java b/bramble-core/src/test/java/org/briarproject/bramble/db/DatabaseMigrationTest.java
index 203f4dfd21f6fd2b09ceed436f3a10dfca9696b7..89bdf8a4c1a8da94963bc41c7473be59b3f7c063 100644
--- a/bramble-core/src/test/java/org/briarproject/bramble/db/DatabaseMigrationTest.java
+++ b/bramble-core/src/test/java/org/briarproject/bramble/db/DatabaseMigrationTest.java
@@ -62,7 +62,7 @@ public abstract class DatabaseMigrationTest extends BrambleMockTestCase {
 	public void testDoesNotRunMigrationsWhenCreatingDatabase()
 			throws Exception {
 		Database<Connection> db = createDatabase(singletonList(migration));
-		assertFalse(db.open());
+		assertFalse(db.open(null));
 		assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db));
 		db.close();
 	}
@@ -72,14 +72,14 @@ public abstract class DatabaseMigrationTest extends BrambleMockTestCase {
 			throws Exception {
 		// Open the DB for the first time
 		Database<Connection> db = createDatabase(asList(migration, migration1));
-		assertFalse(db.open());
+		assertFalse(db.open(null));
 		assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db));
 		// Override the data schema version
 		setDataSchemaVersion(db, -1);
 		db.close();
 		// Reopen the DB - an exception should be thrown
 		db = createDatabase(asList(migration, migration1));
-		db.open();
+		db.open(null);
 	}
 
 	@Test
@@ -87,12 +87,12 @@ public abstract class DatabaseMigrationTest extends BrambleMockTestCase {
 			throws Exception {
 		// Open the DB for the first time
 		Database<Connection> db = createDatabase(asList(migration, migration1));
-		assertFalse(db.open());
+		assertFalse(db.open(null));
 		assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db));
 		db.close();
 		// Reopen the DB - migrations should not be run
 		db = createDatabase(asList(migration, migration1));
-		assertTrue(db.open());
+		assertTrue(db.open(null));
 		assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db));
 		db.close();
 	}
@@ -101,14 +101,14 @@ public abstract class DatabaseMigrationTest extends BrambleMockTestCase {
 	public void testThrowsExceptionIfDataIsNewerThanCode() throws Exception {
 		// Open the DB for the first time
 		Database<Connection> db = createDatabase(asList(migration, migration1));
-		assertFalse(db.open());
+		assertFalse(db.open(null));
 		assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db));
 		// Override the data schema version
 		setDataSchemaVersion(db, CODE_SCHEMA_VERSION + 1);
 		db.close();
 		// Reopen the DB - an exception should be thrown
 		db = createDatabase(asList(migration, migration1));
-		db.open();
+		db.open(null);
 	}
 
 	@Test(expected = DataTooOldException.class)
@@ -116,13 +116,13 @@ public abstract class DatabaseMigrationTest extends BrambleMockTestCase {
 			throws Exception {
 		// Open the DB for the first time
 		Database<Connection> db = createDatabase(emptyList());
-		assertFalse(db.open());
+		assertFalse(db.open(null));
 		assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db));
 		setDataSchemaVersion(db, CODE_SCHEMA_VERSION - 1);
 		db.close();
 		// Reopen the DB - an exception should be thrown
 		db = createDatabase(emptyList());
-		db.open();
+		db.open(null);
 	}
 
 	@Test(expected = DataTooOldException.class)
@@ -141,14 +141,14 @@ public abstract class DatabaseMigrationTest extends BrambleMockTestCase {
 
 		// Open the DB for the first time
 		Database<Connection> db = createDatabase(asList(migration, migration1));
-		assertFalse(db.open());
+		assertFalse(db.open(null));
 		assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db));
 		// Override the data schema version
 		setDataSchemaVersion(db, CODE_SCHEMA_VERSION - 3);
 		db.close();
 		// Reopen the DB - an exception should be thrown
 		db = createDatabase(asList(migration, migration1));
-		db.open();
+		db.open(null);
 	}
 
 	@Test
@@ -170,14 +170,14 @@ public abstract class DatabaseMigrationTest extends BrambleMockTestCase {
 
 		// Open the DB for the first time
 		Database<Connection> db = createDatabase(asList(migration, migration1));
-		assertFalse(db.open());
+		assertFalse(db.open(null));
 		assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db));
 		// Override the data schema version
 		setDataSchemaVersion(db, CODE_SCHEMA_VERSION - 2);
 		db.close();
 		// Reopen the DB - the first migration should be run
 		db = createDatabase(asList(migration, migration1));
-		assertTrue(db.open());
+		assertTrue(db.open(null));
 		assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db));
 		db.close();
 	}
@@ -202,14 +202,14 @@ public abstract class DatabaseMigrationTest extends BrambleMockTestCase {
 
 		// Open the DB for the first time
 		Database<Connection> db = createDatabase(asList(migration, migration1));
-		assertFalse(db.open());
+		assertFalse(db.open(null));
 		assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db));
 		// Override the data schema version
 		setDataSchemaVersion(db, CODE_SCHEMA_VERSION - 2);
 		db.close();
 		// Reopen the DB - both migrations should be run
 		db = createDatabase(asList(migration, migration1));
-		assertTrue(db.open());
+		assertTrue(db.open(null));
 		assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db));
 		db.close();
 	}
diff --git a/bramble-core/src/test/java/org/briarproject/bramble/db/DatabasePerformanceComparisonTest.java b/bramble-core/src/test/java/org/briarproject/bramble/db/DatabasePerformanceComparisonTest.java
index c2a124bb299c1729be0e9c83da3ad7555c65b82c..1807d30dc33374654a740b4acb58336bee1f5b52 100644
--- a/bramble-core/src/test/java/org/briarproject/bramble/db/DatabasePerformanceComparisonTest.java
+++ b/bramble-core/src/test/java/org/briarproject/bramble/db/DatabasePerformanceComparisonTest.java
@@ -71,7 +71,7 @@ public abstract class DatabasePerformanceComparisonTest
 			throws DbException {
 		Database<Connection> db = createDatabase(conditionA,
 				new TestDatabaseConfig(testDir, MAX_SIZE), new SystemClock());
-		db.open();
+		db.open(null);
 		return db;
 	}
 
diff --git a/bramble-core/src/test/java/org/briarproject/bramble/db/DatabaseTraceTest.java b/bramble-core/src/test/java/org/briarproject/bramble/db/DatabaseTraceTest.java
index a78fb5f55a4aa93fd3929bd6e583c5e6a676cf6c..f4e0daa19dedf3d6e0187a4aa1b7e8d181136906 100644
--- a/bramble-core/src/test/java/org/briarproject/bramble/db/DatabaseTraceTest.java
+++ b/bramble-core/src/test/java/org/briarproject/bramble/db/DatabaseTraceTest.java
@@ -43,7 +43,7 @@ public abstract class DatabaseTraceTest extends DatabasePerformanceTest {
 	private Database<Connection> openDatabase() throws DbException {
 		Database<Connection> db = createDatabase(
 				new TestDatabaseConfig(testDir, MAX_SIZE), new SystemClock());
-		db.open();
+		db.open(null);
 		return db;
 	}
 
diff --git a/bramble-core/src/test/java/org/briarproject/bramble/db/JdbcDatabaseTest.java b/bramble-core/src/test/java/org/briarproject/bramble/db/JdbcDatabaseTest.java
index 32d4e5faa4b36b6330f0eeb1c2ab404dde264d7d..81d1c902578e1eaa6f7b6e3ba84fda69ad9a5b0a 100644
--- a/bramble-core/src/test/java/org/briarproject/bramble/db/JdbcDatabaseTest.java
+++ b/bramble-core/src/test/java/org/briarproject/bramble/db/JdbcDatabaseTest.java
@@ -1700,7 +1700,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
 		Database<Connection> db = createDatabase(
 				new TestDatabaseConfig(testDir, MAX_SIZE), clock);
 		if (!resume) TestUtils.deleteTestDirectory(testDir);
-		db.open();
+		db.open(null);
 		return db;
 	}
 
diff --git a/bramble-core/src/test/java/org/briarproject/bramble/db/SingleDatabasePerformanceTest.java b/bramble-core/src/test/java/org/briarproject/bramble/db/SingleDatabasePerformanceTest.java
index 5a836476782da918dc9047721839c60402f5a76b..670c7cddb79746be691317387a0c779d0c6b5213 100644
--- a/bramble-core/src/test/java/org/briarproject/bramble/db/SingleDatabasePerformanceTest.java
+++ b/bramble-core/src/test/java/org/briarproject/bramble/db/SingleDatabasePerformanceTest.java
@@ -40,7 +40,7 @@ public abstract class SingleDatabasePerformanceTest
 	private Database<Connection> openDatabase() throws DbException {
 		Database<Connection> db = createDatabase(
 				new TestDatabaseConfig(testDir, MAX_SIZE), new SystemClock());
-		db.open();
+		db.open(null);
 		return db;
 	}
 
diff --git a/bramble-core/src/test/java/org/briarproject/bramble/test/TestLifecycleModule.java b/bramble-core/src/test/java/org/briarproject/bramble/test/TestLifecycleModule.java
index ee8d5115e07b3285bb0ac5be2251878102d84935..15943adc2b6dabfa86fc8ebb72d270ad396a00ed 100644
--- a/bramble-core/src/test/java/org/briarproject/bramble/test/TestLifecycleModule.java
+++ b/bramble-core/src/test/java/org/briarproject/bramble/test/TestLifecycleModule.java
@@ -17,6 +17,8 @@ import javax.inject.Singleton;
 import dagger.Module;
 import dagger.Provides;
 
+import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.RUNNING;
+
 @Module
 public class TestLifecycleModule {
 
@@ -57,6 +59,11 @@ public class TestLifecycleModule {
 			@Override
 			public void waitForShutdown() throws InterruptedException {
 			}
+
+			@Override
+			public LifecycleState getLifecycleState() {
+				return RUNNING;
+			}
 		};
 		return lifecycleManager;
 	}
diff --git a/briar-android/src/main/AndroidManifest.xml b/briar-android/src/main/AndroidManifest.xml
index a2fed381f9a296c31c69b8e918ab4d7658b49c2e..102de1290c936f258fbc59e2c1714eda23fe5320 100644
--- a/briar-android/src/main/AndroidManifest.xml
+++ b/briar-android/src/main/AndroidManifest.xml
@@ -77,6 +77,11 @@
 			</intent-filter>
 		</activity>
 
+		<activity
+			android:name=".android.login.OpenDatabaseActivity"
+			android:label="@string/app_name"
+			android:launchMode="singleTop"/>
+
 		<activity
 			android:name="org.briarproject.briar.android.navdrawer.NavDrawerActivity"
 			android:theme="@style/BriarTheme.NoActionBar"
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/AppModule.java b/briar-android/src/main/java/org/briarproject/briar/android/AppModule.java
index eba5d57eec64dfdc9645227d19e8809ecc793702..67af25ac69474c763f47a3ec09f3d483afec0feb 100644
--- a/briar-android/src/main/java/org/briarproject/briar/android/AppModule.java
+++ b/briar-android/src/main/java/org/briarproject/briar/android/AppModule.java
@@ -94,6 +94,7 @@ public class AppModule {
 
 			@Override
 			public boolean databaseExists() {
+				// FIXME should not run on UiThread #620
 				if (!dir.isDirectory()) return false;
 				File[] files = dir.listFiles();
 				return files != null && files.length > 0;
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/BriarService.java b/briar-android/src/main/java/org/briarproject/briar/android/BriarService.java
index abd9a20a7977c2ff452fbdcb4f1cb86bb5e9014b..8e1abd66a2ac03720c8190175a7c8308f71a7f3a 100644
--- a/briar-android/src/main/java/org/briarproject/briar/android/BriarService.java
+++ b/briar-android/src/main/java/org/briarproject/briar/android/BriarService.java
@@ -88,6 +88,7 @@ public class BriarService extends Service {
 			stopSelf();
 			return;
 		}
+
 		// Create notification channels
 		if (SDK_INT >= 26) {
 			NotificationManager nm = (NotificationManager)
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/activity/ActivityComponent.java b/briar-android/src/main/java/org/briarproject/briar/android/activity/ActivityComponent.java
index 7403d01e019fa91893cb2b59ea45faf243131f5c..2f8ca83faa6bd97999870c692cf6452479a23f70 100644
--- a/briar-android/src/main/java/org/briarproject/briar/android/activity/ActivityComponent.java
+++ b/briar-android/src/main/java/org/briarproject/briar/android/activity/ActivityComponent.java
@@ -32,6 +32,7 @@ import org.briarproject.briar.android.keyagreement.ShowQrCodeFragment;
 import org.briarproject.briar.android.login.AuthorNameFragment;
 import org.briarproject.briar.android.login.ChangePasswordActivity;
 import org.briarproject.briar.android.login.DozeFragment;
+import org.briarproject.briar.android.login.OpenDatabaseActivity;
 import org.briarproject.briar.android.login.PasswordActivity;
 import org.briarproject.briar.android.login.PasswordFragment;
 import org.briarproject.briar.android.login.SetupActivity;
@@ -88,6 +89,8 @@ public interface ActivityComponent {
 
 	void inject(SetupActivity activity);
 
+	void inject(OpenDatabaseActivity activity);
+
 	void inject(NavDrawerActivity activity);
 
 	void inject(PasswordActivity activity);
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/controller/ConfigController.java b/briar-android/src/main/java/org/briarproject/briar/android/controller/ConfigController.java
index d6bc554f6ee29d356e0e70d68f5a56f60b6a15d1..07b32b0e2f53bf2bd6f65b6aa165aff1c39c1411 100644
--- a/briar-android/src/main/java/org/briarproject/briar/android/controller/ConfigController.java
+++ b/briar-android/src/main/java/org/briarproject/briar/android/controller/ConfigController.java
@@ -17,4 +17,7 @@ public interface ConfigController {
 	void deleteAccount(Context ctx);
 
 	boolean accountExists();
+
+	boolean accountSignedIn();
+
 }
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/controller/ConfigControllerImpl.java b/briar-android/src/main/java/org/briarproject/briar/android/controller/ConfigControllerImpl.java
index 0af60bb981d993699630a665699b2dfbf769aef4..3c19794441e0d0b3e335b824de0530c13587e810 100644
--- a/briar-android/src/main/java/org/briarproject/briar/android/controller/ConfigControllerImpl.java
+++ b/briar-android/src/main/java/org/briarproject/briar/android/controller/ConfigControllerImpl.java
@@ -52,4 +52,10 @@ public class ConfigControllerImpl implements ConfigController {
 		String hex = getEncryptedDatabaseKey();
 		return hex != null && databaseConfig.databaseExists();
 	}
+
+	@Override
+	public boolean accountSignedIn() {
+		return databaseConfig.getEncryptionKey() != null;
+	}
+
 }
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/login/OpenDatabaseActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/login/OpenDatabaseActivity.java
new file mode 100644
index 0000000000000000000000000000000000000000..d24196d32b8a56d69dd2925fc8575a52d41b0a74
--- /dev/null
+++ b/briar-android/src/main/java/org/briarproject/briar/android/login/OpenDatabaseActivity.java
@@ -0,0 +1,93 @@
+package org.briarproject.briar.android.login;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import org.briarproject.bramble.api.event.Event;
+import org.briarproject.bramble.api.event.EventBus;
+import org.briarproject.bramble.api.event.EventListener;
+import org.briarproject.bramble.api.lifecycle.LifecycleManager;
+import org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState;
+import org.briarproject.bramble.api.lifecycle.event.LifecycleEvent;
+import org.briarproject.briar.R;
+import org.briarproject.briar.android.activity.ActivityComponent;
+import org.briarproject.briar.android.activity.BriarActivity;
+import org.briarproject.briar.android.navdrawer.NavDrawerActivity;
+
+import javax.annotation.ParametersAreNonnullByDefault;
+import javax.inject.Inject;
+
+import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.MIGRATING_DATABASE;
+import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STARTING_SERVICES;
+
+@ParametersAreNonnullByDefault
+public class OpenDatabaseActivity extends BriarActivity
+		implements EventListener {
+
+	@Inject
+	LifecycleManager lifecycleManager;
+	@Inject
+	EventBus eventBus;
+
+	private TextView textView;
+	private ImageView imageView;
+	private boolean showingMigration = false;
+
+	@Override
+	public void onCreate(@Nullable Bundle state) {
+		super.onCreate(state);
+		setContentView(R.layout.activity_open_database);
+		textView = findViewById(R.id.textView);
+		imageView = findViewById(R.id.imageView);
+	}
+
+	@Override
+	public void injectActivity(ActivityComponent component) {
+		component.inject(this);
+	}
+
+	@Override
+	public void onStart() {
+		super.onStart();
+		LifecycleState state = lifecycleManager.getLifecycleState();
+		if (state.isAfter(STARTING_SERVICES)) {
+			finishAndStartApp();
+		} else {
+			if (state == MIGRATING_DATABASE) showMigration();
+			eventBus.addListener(this);
+		}
+	}
+
+	@Override
+	protected void onStop() {
+		super.onStop();
+		eventBus.removeListener(this);
+	}
+
+	@Override
+	public void eventOccurred(Event e) {
+		if (e instanceof LifecycleEvent) {
+			LifecycleState state = ((LifecycleEvent) e).getLifecycleState();
+			if (state.isAfter(STARTING_SERVICES))
+				runOnUiThreadUnlessDestroyed(this::finishAndStartApp);
+			else if (state == MIGRATING_DATABASE)
+				runOnUiThreadUnlessDestroyed(this::showMigration);
+		}
+	}
+
+	private void showMigration() {
+		if (showingMigration) return;
+		textView.setText(R.string.startup_migrate_database);
+		imageView.setImageResource(R.drawable.startup_migration);
+		showingMigration = true;
+	}
+
+	private void finishAndStartApp() {
+		startActivity(new Intent(this, NavDrawerActivity.class));
+		supportFinishAfterTransition();
+	}
+
+}
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/login/SetupActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/login/SetupActivity.java
index 17c6f522f384d2bc68e7e5aa49905404f22d0d44..2b17df6eaf0f93d47b7234b728993e37000d0d40 100644
--- a/briar-android/src/main/java/org/briarproject/briar/android/login/SetupActivity.java
+++ b/briar-android/src/main/java/org/briarproject/briar/android/login/SetupActivity.java
@@ -8,7 +8,6 @@ import org.briarproject.briar.R;
 import org.briarproject.briar.android.activity.ActivityComponent;
 import org.briarproject.briar.android.activity.BaseActivity;
 import org.briarproject.briar.android.fragment.BaseFragment.BaseFragmentListener;
-import org.briarproject.briar.android.navdrawer.NavDrawerActivity;
 
 import javax.inject.Inject;
 
@@ -48,7 +47,7 @@ public class SetupActivity extends BaseActivity
 	}
 
 	public void showApp() {
-		Intent i = new Intent(this, NavDrawerActivity.class);
+		Intent i = new Intent(this, OpenDatabaseActivity.class);
 		i.setFlags(FLAG_ACTIVITY_NEW_TASK);
 		startActivity(i);
 		supportFinishAfterTransition();
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/navdrawer/NavDrawerActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/navdrawer/NavDrawerActivity.java
index d083343e91f329e01410c7d2e2a2d1ca7cac6ab5..dcc42f6c33cdb6ca8e795b023f5644d5594ed4e6 100644
--- a/briar-android/src/main/java/org/briarproject/briar/android/navdrawer/NavDrawerActivity.java
+++ b/briar-android/src/main/java/org/briarproject/briar/android/navdrawer/NavDrawerActivity.java
@@ -46,8 +46,6 @@ import java.util.logging.Logger;
 
 import javax.inject.Inject;
 
-import static android.os.Build.MANUFACTURER;
-import static android.os.Build.VERSION.SDK_INT;
 import static android.support.v4.app.FragmentManager.POP_BACK_STACK_INCLUSIVE;
 import static android.support.v4.view.GravityCompat.START;
 import static android.support.v4.widget.DrawerLayout.LOCK_MODE_LOCKED_CLOSED;
@@ -214,18 +212,6 @@ public class NavDrawerActivity extends BriarActivity implements
 	public void onBackPressed() {
 		if (drawerLayout.isDrawerOpen(START)) {
 			drawerLayout.closeDrawer(START);
-		} else if (getSupportFragmentManager().getBackStackEntryCount() == 0 &&
-				getSupportFragmentManager()
-						.findFragmentByTag(ContactListFragment.TAG) != null) {
-			if (SDK_INT == 19 && MANUFACTURER.equalsIgnoreCase("Samsung")) {
-				// workaround for #1116 causes splash screen to show again
-				super.onBackPressed();
-			} else {
-				Intent i = new Intent(Intent.ACTION_MAIN);
-				i.addCategory(Intent.CATEGORY_HOME);
-				i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-				startActivity(i);
-			}
 		} else if (getSupportFragmentManager().getBackStackEntryCount() == 0 &&
 				getSupportFragmentManager()
 						.findFragmentByTag(ContactListFragment.TAG) == null) {
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/splash/SplashScreenActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/splash/SplashScreenActivity.java
index 7c19f3c780231ab3d2196a7875ef30e6cda226c7..f21f460aa17bcc17d27da0a3db4f9c9b6627f6b2 100644
--- a/briar-android/src/main/java/org/briarproject/briar/android/splash/SplashScreenActivity.java
+++ b/briar-android/src/main/java/org/briarproject/briar/android/splash/SplashScreenActivity.java
@@ -12,8 +12,8 @@ import org.briarproject.briar.R;
 import org.briarproject.briar.android.activity.ActivityComponent;
 import org.briarproject.briar.android.activity.BaseActivity;
 import org.briarproject.briar.android.controller.ConfigController;
+import org.briarproject.briar.android.login.OpenDatabaseActivity;
 import org.briarproject.briar.android.login.SetupActivity;
-import org.briarproject.briar.android.navdrawer.NavDrawerActivity;
 
 import java.util.logging.Logger;
 
@@ -43,10 +43,15 @@ public class SplashScreenActivity extends BaseActivity {
 
 		setContentView(R.layout.splash);
 
-		new Handler().postDelayed(() -> {
-			startNextActivity();
-			supportFinishAfterTransition();
-		}, 500);
+		if (configController.accountSignedIn()) {
+			startActivity(new Intent(this, OpenDatabaseActivity.class));
+			finish();
+		} else {
+			new Handler().postDelayed(() -> {
+				startNextActivity();
+				supportFinishAfterTransition();
+			}, 500);
+		}
 	}
 
 	@Override
@@ -60,7 +65,7 @@ public class SplashScreenActivity extends BaseActivity {
 			startActivity(new Intent(this, ExpiredActivity.class));
 		} else {
 			if (configController.accountExists()) {
-				startActivity(new Intent(this, NavDrawerActivity.class));
+				startActivity(new Intent(this, OpenDatabaseActivity.class));
 			} else {
 				configController.deleteAccount(this);
 				startActivity(new Intent(this, SetupActivity.class));
diff --git a/briar-android/src/main/res/drawable/startup_lock.xml b/briar-android/src/main/res/drawable/startup_lock.xml
new file mode 100644
index 0000000000000000000000000000000000000000..181a64cc9162141b4bfe208b94a70bf94d4b31da
--- /dev/null
+++ b/briar-android/src/main/res/drawable/startup_lock.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="64dp"
+        android:height="64dp"
+        android:viewportHeight="24.0"
+        android:viewportWidth="24.0">
+	<path
+		android:fillColor="#FF000000"
+		android:pathData="M18,8h-1L17,6c0,-2.76 -2.24,-5 -5,-5S7,3.24 7,6v2L6,8c-1.1,0 -2,0.9 -2,2v10c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2L20,10c0,-1.1 -0.9,-2 -2,-2zM12,17c-1.1,0 -2,-0.9 -2,-2s0.9,-2 2,-2 2,0.9 2,2 -0.9,2 -2,2zM15.1,8L8.9,8L8.9,6c0,-1.71 1.39,-3.1 3.1,-3.1 1.71,0 3.1,1.39 3.1,3.1v2z"/>
+</vector>
diff --git a/briar-android/src/main/res/drawable/startup_migration.xml b/briar-android/src/main/res/drawable/startup_migration.xml
new file mode 100644
index 0000000000000000000000000000000000000000..a5021ef0dad493614ddb3e068540d5cf992d6e77
--- /dev/null
+++ b/briar-android/src/main/res/drawable/startup_migration.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="64dp"
+        android:height="64dp"
+        android:viewportHeight="24.0"
+        android:viewportWidth="24.0">
+	<path
+		android:fillColor="#FF000000"
+		android:pathData="M21,10.12h-6.78l2.74,-2.82c-2.73,-2.7 -7.15,-2.8 -9.88,-0.1 -2.73,2.71 -2.73,7.08 0,9.79 2.73,2.71 7.15,2.71 9.88,0C18.32,15.65 19,14.08 19,12.1h2c0,1.98 -0.88,4.55 -2.64,6.29 -3.51,3.48 -9.21,3.48 -12.72,0 -3.5,-3.47 -3.53,-9.11 -0.02,-12.58 3.51,-3.47 9.14,-3.47 12.65,0L21,3v7.12zM12.5,8v4.25l3.5,2.08 -0.72,1.21L11,13V8h1.5z"/>
+</vector>
diff --git a/briar-android/src/main/res/layout/activity_open_database.xml b/briar-android/src/main/res/layout/activity_open_database.xml
new file mode 100644
index 0000000000000000000000000000000000000000..ab20f17cb9dbb3a0924a9513f69f67a9f12e8b85
--- /dev/null
+++ b/briar-android/src/main/res/layout/activity_open_database.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8"?>
+<android.support.constraint.ConstraintLayout
+	xmlns:android="http://schemas.android.com/apk/res/android"
+	xmlns:app="http://schemas.android.com/apk/res-auto"
+	xmlns:tools="http://schemas.android.com/tools"
+	android:layout_width="match_parent"
+	android:layout_height="match_parent">
+
+	<ImageView
+		android:id="@+id/imageView"
+		android:layout_width="128dp"
+		android:layout_height="128dp"
+		android:scaleType="center"
+		android:src="@drawable/startup_lock"
+		android:tint="@color/briar_primary"
+		app:layout_constraintBottom_toTopOf="@+id/textView"
+		app:layout_constraintEnd_toEndOf="parent"
+		app:layout_constraintStart_toStartOf="parent"
+		app:layout_constraintTop_toTopOf="parent"
+		app:layout_constraintVertical_bias="0.5"
+		app:layout_constraintVertical_chainStyle="packed"
+		tools:ignore="ContentDescription"/>
+
+	<ProgressBar
+		android:id="@+id/progressBar"
+		style="?android:attr/progressBarStyleLarge"
+		android:layout_width="0dp"
+		android:layout_height="0dp"
+		app:layout_constraintBottom_toBottomOf="@+id/imageView"
+		app:layout_constraintEnd_toEndOf="@+id/imageView"
+		app:layout_constraintStart_toStartOf="@+id/imageView"
+		app:layout_constraintTop_toTopOf="@+id/imageView"/>
+
+	<TextView
+		android:id="@+id/textView"
+		android:layout_width="wrap_content"
+		android:layout_height="wrap_content"
+		android:layout_margin="8dp"
+		android:text="@string/startup_open_database"
+		android:textSize="@dimen/text_size_large"
+		app:layout_constraintBottom_toBottomOf="parent"
+		app:layout_constraintEnd_toEndOf="parent"
+		app:layout_constraintStart_toStartOf="parent"
+		app:layout_constraintTop_toBottomOf="@+id/imageView"/>
+
+</android.support.constraint.ConstraintLayout>
\ No newline at end of file
diff --git a/briar-android/src/main/res/values/strings.xml b/briar-android/src/main/res/values/strings.xml
index c682069f77c29abed6b07cb0a20e4b460e86ce90..14de9071b5dd7e220b2ed96e66999f29c6e197b2 100644
--- a/briar-android/src/main/res/values/strings.xml
+++ b/briar-android/src/main/res/values/strings.xml
@@ -46,6 +46,8 @@
 	</plurals>
 	<string name="expiry_update">The testing expiry date has been extended. Your account will now expire in %d days.</string>
 	<string name="expiry_date_reached">This software has expired.\nThank you for testing!</string>
+	<string name="startup_open_database">Decrypting Database…</string>
+	<string name="startup_migrate_database">Upgrading Database…</string>
 
 	<!-- Navigation Drawer -->
 	<string name="nav_drawer_open_description">Open the navigation drawer</string>