Merge branch '1168-startup-status-screen' into 'master'

Show status message while opening and migrating DB

Closes #1168

See merge request akwizgran/briar!708
This commit is contained in:
akwizgran
2018-03-07 10:30:11 +00:00
35 changed files with 337 additions and 78 deletions

View File

@@ -43,7 +43,7 @@ public interface DatabaseComponent {
* @throws DataTooOldException if the data uses an older schema than the * @throws DataTooOldException if the data uses an older schema than the
* current code and cannot be migrated * 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. * Waits for any open transactions to finish and closes the database.

View File

@@ -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();
}

View File

@@ -29,6 +29,19 @@ public interface LifecycleManager {
SUCCESS 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. * Registers a {@link Service} to be started and stopped.
*/ */
@@ -76,4 +89,10 @@ public interface LifecycleManager {
* the {@link DatabaseComponent} to be closed before returning. * the {@link DatabaseComponent} to be closed before returning.
*/ */
void waitForShutdown() throws InterruptedException; void waitForShutdown() throws InterruptedException;
/**
* Returns the current state of the lifecycle.
*/
LifecycleState getLifecycleState();
} }

View File

@@ -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;
}
}

View File

@@ -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 {
}

View File

@@ -6,6 +6,7 @@ import org.briarproject.bramble.api.db.DataTooNewException;
import org.briarproject.bramble.api.db.DataTooOldException; import org.briarproject.bramble.api.db.DataTooOldException;
import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Metadata; 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.Author;
import org.briarproject.bramble.api.identity.AuthorId; import org.briarproject.bramble.api.identity.AuthorId;
import org.briarproject.bramble.api.identity.LocalAuthor; 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 * @throws DataTooOldException if the data uses an older schema than the
* current code and cannot be migrated * 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 * Prevents new transactions from starting, waits for all current

View File

@@ -10,6 +10,7 @@ import org.briarproject.bramble.api.db.ContactExistsException;
import org.briarproject.bramble.api.db.DatabaseComponent; import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Metadata; 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.NoSuchContactException;
import org.briarproject.bramble.api.db.NoSuchGroupException; import org.briarproject.bramble.api.db.NoSuchGroupException;
import org.briarproject.bramble.api.db.NoSuchLocalAuthorException; import org.briarproject.bramble.api.db.NoSuchLocalAuthorException;
@@ -100,8 +101,9 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
} }
@Override @Override
public boolean open() throws DbException { public boolean open(@Nullable MigrationListener listener)
boolean reopened = db.open(); throws DbException {
boolean reopened = db.open(listener);
shutdown.addShutdownHook(() -> { shutdown.addShutdownHook(() -> {
try { try {
close(); close();

View File

@@ -3,6 +3,7 @@ package org.briarproject.bramble.db;
import org.briarproject.bramble.api.crypto.SecretKey; import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.db.DatabaseConfig; import org.briarproject.bramble.api.db.DatabaseConfig;
import org.briarproject.bramble.api.db.DbException; 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.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.system.Clock; import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.util.StringUtils; import org.briarproject.bramble.util.StringUtils;
@@ -13,6 +14,7 @@ import java.sql.DriverManager;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.Properties; import java.util.Properties;
import javax.annotation.Nullable;
import javax.inject.Inject; import javax.inject.Inject;
/** /**
@@ -42,10 +44,11 @@ class H2Database extends JdbcDatabase {
} }
@Override @Override
public boolean open() throws DbException { public boolean open(@Nullable MigrationListener listener)
throws DbException {
boolean reopen = config.databaseExists(); boolean reopen = config.databaseExists();
if (!reopen) config.getDatabaseDirectory().mkdirs(); if (!reopen) config.getDatabaseDirectory().mkdirs();
super.open("org.h2.Driver", reopen); super.open("org.h2.Driver", reopen, listener);
return reopen; return reopen;
} }

View File

@@ -3,6 +3,7 @@ package org.briarproject.bramble.db;
import org.briarproject.bramble.api.crypto.SecretKey; import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.db.DatabaseConfig; import org.briarproject.bramble.api.db.DatabaseConfig;
import org.briarproject.bramble.api.db.DbException; 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.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.system.Clock; import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.util.StringUtils; import org.briarproject.bramble.util.StringUtils;
@@ -13,6 +14,7 @@ import java.sql.DriverManager;
import java.sql.SQLException; import java.sql.SQLException;
import java.sql.Statement; import java.sql.Statement;
import javax.annotation.Nullable;
import javax.inject.Inject; import javax.inject.Inject;
/** /**
@@ -44,10 +46,10 @@ class HyperSqlDatabase extends JdbcDatabase {
} }
@Override @Override
public boolean open() throws DbException { public boolean open(@Nullable MigrationListener listener) throws DbException {
boolean reopen = config.databaseExists(); boolean reopen = config.databaseExists();
if (!reopen) config.getDatabaseDirectory().mkdirs(); if (!reopen) config.getDatabaseDirectory().mkdirs();
super.open("org.hsqldb.jdbc.JDBCDriver", reopen); super.open("org.hsqldb.jdbc.JDBCDriver", reopen, listener);
return reopen; return reopen;
} }

View File

@@ -8,6 +8,7 @@ import org.briarproject.bramble.api.db.DataTooOldException;
import org.briarproject.bramble.api.db.DbClosedException; import org.briarproject.bramble.api.db.DbClosedException;
import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Metadata; 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.Author;
import org.briarproject.bramble.api.identity.AuthorId; import org.briarproject.bramble.api.identity.AuthorId;
import org.briarproject.bramble.api.identity.LocalAuthor; import org.briarproject.bramble.api.identity.LocalAuthor;
@@ -301,7 +302,8 @@ abstract class JdbcDatabase implements Database<Connection> {
this.clock = clock; 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 // Load the JDBC driver
try { try {
Class.forName(driverClass); Class.forName(driverClass);
@@ -312,7 +314,7 @@ abstract class JdbcDatabase implements Database<Connection> {
Connection txn = startTransaction(); Connection txn = startTransaction();
try { try {
if (reopen) { if (reopen) {
checkSchemaVersion(txn); checkSchemaVersion(txn, listener);
} else { } else {
createTables(txn); createTables(txn);
storeSchemaVersion(txn, CODE_SCHEMA_VERSION); 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 * @throws DataTooOldException if the data uses an older schema than the
* current code and cannot be migrated * 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); Settings s = getSettings(txn, DB_SETTINGS_NAMESPACE);
int dataSchemaVersion = s.getInt(SCHEMA_VERSION_KEY, -1); int dataSchemaVersion = s.getInt(SCHEMA_VERSION_KEY, -1);
if (dataSchemaVersion == -1) throw new DbException(); if (dataSchemaVersion == -1) throw new DbException();
@@ -348,6 +351,7 @@ abstract class JdbcDatabase implements Database<Connection> {
if (start == dataSchemaVersion) { if (start == dataSchemaVersion) {
if (LOG.isLoggable(INFO)) if (LOG.isLoggable(INFO))
LOG.info("Migrating from schema " + start + " to " + end); LOG.info("Migrating from schema " + start + " to " + end);
if (listener != null) listener.onMigrationRun();
// Apply the migration // Apply the migration
m.migrate(txn); m.migrate(txn);
// Store the new schema version // Store the new schema version

View File

@@ -6,6 +6,7 @@ import org.briarproject.bramble.api.db.DataTooNewException;
import org.briarproject.bramble.api.db.DataTooOldException; import org.briarproject.bramble.api.db.DataTooOldException;
import org.briarproject.bramble.api.db.DatabaseComponent; import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.DbException; 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.db.Transaction;
import org.briarproject.bramble.api.event.EventBus; import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.identity.AuthorFactory; 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.LifecycleManager;
import org.briarproject.bramble.api.lifecycle.Service; import org.briarproject.bramble.api.lifecycle.Service;
import org.briarproject.bramble.api.lifecycle.ServiceException; 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.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.Client; 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.INFO;
import static java.util.logging.Level.WARNING; 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.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_NEW_ERROR;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.DATA_TOO_OLD_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 @ThreadSafe
@NotNullByDefault @NotNullByDefault
class LifecycleManagerImpl implements LifecycleManager { class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
private static final Logger LOG = private static final Logger LOG =
Logger.getLogger(LifecycleManagerImpl.class.getName()); Logger.getLogger(LifecycleManagerImpl.class.getName());
@@ -58,6 +64,8 @@ class LifecycleManagerImpl implements LifecycleManager {
private final CountDownLatch startupLatch = new CountDownLatch(1); private final CountDownLatch startupLatch = new CountDownLatch(1);
private final CountDownLatch shutdownLatch = new CountDownLatch(1); private final CountDownLatch shutdownLatch = new CountDownLatch(1);
private volatile LifecycleState state = STARTING;
@Inject @Inject
LifecycleManagerImpl(DatabaseComponent db, EventBus eventBus, LifecycleManagerImpl(DatabaseComponent db, EventBus eventBus,
CryptoComponent crypto, AuthorFactory authorFactory, CryptoComponent crypto, AuthorFactory authorFactory,
@@ -123,7 +131,7 @@ class LifecycleManagerImpl implements LifecycleManager {
LOG.info("Starting services"); LOG.info("Starting services");
long start = System.currentTimeMillis(); long start = System.currentTimeMillis();
boolean reopened = db.open(); boolean reopened = db.open(this);
long duration = System.currentTimeMillis() - start; long duration = System.currentTimeMillis() - start;
if (LOG.isLoggable(INFO)) { if (LOG.isLoggable(INFO)) {
if (reopened) if (reopened)
@@ -135,7 +143,10 @@ class LifecycleManagerImpl implements LifecycleManager {
registerLocalAuthor(createLocalAuthor(nickname)); registerLocalAuthor(createLocalAuthor(nickname));
} }
state = STARTING_SERVICES;
dbLatch.countDown(); dbLatch.countDown();
eventBus.broadcast(new LifecycleEvent(STARTING_SERVICES));
Transaction txn = db.startTransaction(false); Transaction txn = db.startTransaction(false);
try { try {
for (Client c : clients) { for (Client c : clients) {
@@ -161,7 +172,10 @@ class LifecycleManagerImpl implements LifecycleManager {
+ " took " + duration + " ms"); + " took " + duration + " ms");
} }
} }
state = RUNNING;
startupLatch.countDown(); startupLatch.countDown();
eventBus.broadcast(new LifecycleEvent(RUNNING));
return SUCCESS; return SUCCESS;
} catch (DataTooOldException e) { } catch (DataTooOldException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), 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 @Override
public void stopServices() { public void stopServices() {
try { try {
@@ -190,7 +210,8 @@ class LifecycleManagerImpl implements LifecycleManager {
} }
try { try {
LOG.info("Stopping services"); LOG.info("Stopping services");
eventBus.broadcast(new ShutdownEvent()); state = STOPPING;
eventBus.broadcast(new LifecycleEvent(STOPPING));
for (Service s : services) { for (Service s : services) {
long start = System.currentTimeMillis(); long start = System.currentTimeMillis();
s.stopService(); s.stopService();
@@ -235,4 +256,8 @@ class LifecycleManagerImpl implements LifecycleManager {
shutdownLatch.await(); shutdownLatch.await();
} }
@Override
public LifecycleState getLifecycleState() {
return state;
}
} }

View File

@@ -10,7 +10,7 @@ import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.event.EventBus; import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.event.EventListener; import org.briarproject.bramble.api.event.EventListener;
import org.briarproject.bramble.api.lifecycle.IoExecutor; 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.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.Ack; import org.briarproject.bramble.api.sync.Ack;
import org.briarproject.bramble.api.sync.Offer; 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.concurrent.TimeUnit.MILLISECONDS;
import static java.util.logging.Level.INFO; import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING; 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_MESSAGE_IDS;
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_RECORD_PAYLOAD_LENGTH; 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) { } else if (e instanceof MessageToRequestEvent) {
if (((MessageToRequestEvent) e).getContactId().equals(contactId)) if (((MessageToRequestEvent) e).getContactId().equals(contactId))
generateRequest(); generateRequest();
} else if (e instanceof ShutdownEvent) { } else if (e instanceof LifecycleEvent) {
interrupt(); LifecycleEvent l = (LifecycleEvent) e;
if (l.getLifecycleState() == STOPPING) interrupt();
} }
} }

View File

@@ -11,7 +11,7 @@ import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.event.EventBus; import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.event.EventListener; import org.briarproject.bramble.api.event.EventListener;
import org.briarproject.bramble.api.lifecycle.IoExecutor; 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.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.Ack; import org.briarproject.bramble.api.sync.Ack;
import org.briarproject.bramble.api.sync.Message; import org.briarproject.bramble.api.sync.Message;
@@ -27,6 +27,7 @@ import java.util.logging.Logger;
import javax.annotation.concurrent.ThreadSafe; import javax.annotation.concurrent.ThreadSafe;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STOPPING;
/** /**
* An incoming {@link SyncSession}. * An incoming {@link SyncSession}.
@@ -96,8 +97,9 @@ class IncomingSession implements SyncSession, EventListener {
if (e instanceof ContactRemovedEvent) { if (e instanceof ContactRemovedEvent) {
ContactRemovedEvent c = (ContactRemovedEvent) e; ContactRemovedEvent c = (ContactRemovedEvent) e;
if (c.getContactId().equals(contactId)) interrupt(); if (c.getContactId().equals(contactId)) interrupt();
} else if (e instanceof ShutdownEvent) { } else if (e instanceof LifecycleEvent) {
interrupt(); LifecycleEvent l = (LifecycleEvent) e;
if (l.getLifecycleState() == STOPPING) interrupt();
} }
} }

View File

@@ -10,7 +10,7 @@ import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.event.EventBus; import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.event.EventListener; import org.briarproject.bramble.api.event.EventListener;
import org.briarproject.bramble.api.lifecycle.IoExecutor; 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.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.Ack; import org.briarproject.bramble.api.sync.Ack;
import org.briarproject.bramble.api.sync.RecordWriter; 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.INFO;
import static java.util.logging.Level.WARNING; 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_MESSAGE_IDS;
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_RECORD_PAYLOAD_LENGTH; 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) { if (e instanceof ContactRemovedEvent) {
ContactRemovedEvent c = (ContactRemovedEvent) e; ContactRemovedEvent c = (ContactRemovedEvent) e;
if (c.getContactId().equals(contactId)) interrupt(); if (c.getContactId().equals(contactId)) interrupt();
} else if (e instanceof ShutdownEvent) { } else if (e instanceof LifecycleEvent) {
interrupt(); LifecycleEvent l = (LifecycleEvent) e;
if (l.getLifecycleState() == STOPPING) interrupt();
} }
} }

View File

@@ -134,7 +134,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
int shutdownHandle = 12345; int shutdownHandle = 12345;
context.checking(new Expectations() {{ context.checking(new Expectations() {{
// open() // open()
oneOf(database).open(); oneOf(database).open(null);
will(returnValue(false)); will(returnValue(false));
oneOf(shutdown).addShutdownHook(with(any(Runnable.class))); oneOf(shutdown).addShutdownHook(with(any(Runnable.class)));
will(returnValue(shutdownHandle)); will(returnValue(shutdownHandle));
@@ -201,7 +201,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
DatabaseComponent db = createDatabaseComponent(database, eventBus, DatabaseComponent db = createDatabaseComponent(database, eventBus,
shutdown); shutdown);
assertFalse(db.open()); assertFalse(db.open(null));
Transaction transaction = db.startTransaction(false); Transaction transaction = db.startTransaction(false);
try { try {
db.addLocalAuthor(transaction, localAuthor); db.addLocalAuthor(transaction, localAuthor);
@@ -1501,7 +1501,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
MessageId messageId2 = new MessageId(TestUtils.getRandomId()); MessageId messageId2 = new MessageId(TestUtils.getRandomId());
context.checking(new Expectations() {{ context.checking(new Expectations() {{
// open() // open()
oneOf(database).open(); oneOf(database).open(null);
will(returnValue(false)); will(returnValue(false));
oneOf(shutdown).addShutdownHook(with(any(Runnable.class))); oneOf(shutdown).addShutdownHook(with(any(Runnable.class)));
will(returnValue(shutdownHandle)); will(returnValue(shutdownHandle));
@@ -1543,7 +1543,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
DatabaseComponent db = createDatabaseComponent(database, eventBus, DatabaseComponent db = createDatabaseComponent(database, eventBus,
shutdown); shutdown);
assertFalse(db.open()); assertFalse(db.open(null));
Transaction transaction = db.startTransaction(false); Transaction transaction = db.startTransaction(false);
try { try {
db.addLocalMessage(transaction, message, metadata, true); db.addLocalMessage(transaction, message, metadata, true);

View File

@@ -62,7 +62,7 @@ public abstract class DatabaseMigrationTest extends BrambleMockTestCase {
public void testDoesNotRunMigrationsWhenCreatingDatabase() public void testDoesNotRunMigrationsWhenCreatingDatabase()
throws Exception { throws Exception {
Database<Connection> db = createDatabase(singletonList(migration)); Database<Connection> db = createDatabase(singletonList(migration));
assertFalse(db.open()); assertFalse(db.open(null));
assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db)); assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db));
db.close(); db.close();
} }
@@ -72,14 +72,14 @@ public abstract class DatabaseMigrationTest extends BrambleMockTestCase {
throws Exception { throws Exception {
// Open the DB for the first time // Open the DB for the first time
Database<Connection> db = createDatabase(asList(migration, migration1)); Database<Connection> db = createDatabase(asList(migration, migration1));
assertFalse(db.open()); assertFalse(db.open(null));
assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db)); assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db));
// Override the data schema version // Override the data schema version
setDataSchemaVersion(db, -1); setDataSchemaVersion(db, -1);
db.close(); db.close();
// Reopen the DB - an exception should be thrown // Reopen the DB - an exception should be thrown
db = createDatabase(asList(migration, migration1)); db = createDatabase(asList(migration, migration1));
db.open(); db.open(null);
} }
@Test @Test
@@ -87,12 +87,12 @@ public abstract class DatabaseMigrationTest extends BrambleMockTestCase {
throws Exception { throws Exception {
// Open the DB for the first time // Open the DB for the first time
Database<Connection> db = createDatabase(asList(migration, migration1)); Database<Connection> db = createDatabase(asList(migration, migration1));
assertFalse(db.open()); assertFalse(db.open(null));
assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db)); assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db));
db.close(); db.close();
// Reopen the DB - migrations should not be run // Reopen the DB - migrations should not be run
db = createDatabase(asList(migration, migration1)); db = createDatabase(asList(migration, migration1));
assertTrue(db.open()); assertTrue(db.open(null));
assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db)); assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db));
db.close(); db.close();
} }
@@ -101,14 +101,14 @@ public abstract class DatabaseMigrationTest extends BrambleMockTestCase {
public void testThrowsExceptionIfDataIsNewerThanCode() throws Exception { public void testThrowsExceptionIfDataIsNewerThanCode() throws Exception {
// Open the DB for the first time // Open the DB for the first time
Database<Connection> db = createDatabase(asList(migration, migration1)); Database<Connection> db = createDatabase(asList(migration, migration1));
assertFalse(db.open()); assertFalse(db.open(null));
assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db)); assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db));
// Override the data schema version // Override the data schema version
setDataSchemaVersion(db, CODE_SCHEMA_VERSION + 1); setDataSchemaVersion(db, CODE_SCHEMA_VERSION + 1);
db.close(); db.close();
// Reopen the DB - an exception should be thrown // Reopen the DB - an exception should be thrown
db = createDatabase(asList(migration, migration1)); db = createDatabase(asList(migration, migration1));
db.open(); db.open(null);
} }
@Test(expected = DataTooOldException.class) @Test(expected = DataTooOldException.class)
@@ -116,13 +116,13 @@ public abstract class DatabaseMigrationTest extends BrambleMockTestCase {
throws Exception { throws Exception {
// Open the DB for the first time // Open the DB for the first time
Database<Connection> db = createDatabase(emptyList()); Database<Connection> db = createDatabase(emptyList());
assertFalse(db.open()); assertFalse(db.open(null));
assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db)); assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db));
setDataSchemaVersion(db, CODE_SCHEMA_VERSION - 1); setDataSchemaVersion(db, CODE_SCHEMA_VERSION - 1);
db.close(); db.close();
// Reopen the DB - an exception should be thrown // Reopen the DB - an exception should be thrown
db = createDatabase(emptyList()); db = createDatabase(emptyList());
db.open(); db.open(null);
} }
@Test(expected = DataTooOldException.class) @Test(expected = DataTooOldException.class)
@@ -141,14 +141,14 @@ public abstract class DatabaseMigrationTest extends BrambleMockTestCase {
// Open the DB for the first time // Open the DB for the first time
Database<Connection> db = createDatabase(asList(migration, migration1)); Database<Connection> db = createDatabase(asList(migration, migration1));
assertFalse(db.open()); assertFalse(db.open(null));
assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db)); assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db));
// Override the data schema version // Override the data schema version
setDataSchemaVersion(db, CODE_SCHEMA_VERSION - 3); setDataSchemaVersion(db, CODE_SCHEMA_VERSION - 3);
db.close(); db.close();
// Reopen the DB - an exception should be thrown // Reopen the DB - an exception should be thrown
db = createDatabase(asList(migration, migration1)); db = createDatabase(asList(migration, migration1));
db.open(); db.open(null);
} }
@Test @Test
@@ -170,14 +170,14 @@ public abstract class DatabaseMigrationTest extends BrambleMockTestCase {
// Open the DB for the first time // Open the DB for the first time
Database<Connection> db = createDatabase(asList(migration, migration1)); Database<Connection> db = createDatabase(asList(migration, migration1));
assertFalse(db.open()); assertFalse(db.open(null));
assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db)); assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db));
// Override the data schema version // Override the data schema version
setDataSchemaVersion(db, CODE_SCHEMA_VERSION - 2); setDataSchemaVersion(db, CODE_SCHEMA_VERSION - 2);
db.close(); db.close();
// Reopen the DB - the first migration should be run // Reopen the DB - the first migration should be run
db = createDatabase(asList(migration, migration1)); db = createDatabase(asList(migration, migration1));
assertTrue(db.open()); assertTrue(db.open(null));
assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db)); assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db));
db.close(); db.close();
} }
@@ -202,14 +202,14 @@ public abstract class DatabaseMigrationTest extends BrambleMockTestCase {
// Open the DB for the first time // Open the DB for the first time
Database<Connection> db = createDatabase(asList(migration, migration1)); Database<Connection> db = createDatabase(asList(migration, migration1));
assertFalse(db.open()); assertFalse(db.open(null));
assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db)); assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db));
// Override the data schema version // Override the data schema version
setDataSchemaVersion(db, CODE_SCHEMA_VERSION - 2); setDataSchemaVersion(db, CODE_SCHEMA_VERSION - 2);
db.close(); db.close();
// Reopen the DB - both migrations should be run // Reopen the DB - both migrations should be run
db = createDatabase(asList(migration, migration1)); db = createDatabase(asList(migration, migration1));
assertTrue(db.open()); assertTrue(db.open(null));
assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db)); assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db));
db.close(); db.close();
} }

View File

@@ -71,7 +71,7 @@ public abstract class DatabasePerformanceComparisonTest
throws DbException { throws DbException {
Database<Connection> db = createDatabase(conditionA, Database<Connection> db = createDatabase(conditionA,
new TestDatabaseConfig(testDir, MAX_SIZE), new SystemClock()); new TestDatabaseConfig(testDir, MAX_SIZE), new SystemClock());
db.open(); db.open(null);
return db; return db;
} }

View File

@@ -43,7 +43,7 @@ public abstract class DatabaseTraceTest extends DatabasePerformanceTest {
private Database<Connection> openDatabase() throws DbException { private Database<Connection> openDatabase() throws DbException {
Database<Connection> db = createDatabase( Database<Connection> db = createDatabase(
new TestDatabaseConfig(testDir, MAX_SIZE), new SystemClock()); new TestDatabaseConfig(testDir, MAX_SIZE), new SystemClock());
db.open(); db.open(null);
return db; return db;
} }

View File

@@ -1700,7 +1700,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
Database<Connection> db = createDatabase( Database<Connection> db = createDatabase(
new TestDatabaseConfig(testDir, MAX_SIZE), clock); new TestDatabaseConfig(testDir, MAX_SIZE), clock);
if (!resume) TestUtils.deleteTestDirectory(testDir); if (!resume) TestUtils.deleteTestDirectory(testDir);
db.open(); db.open(null);
return db; return db;
} }

View File

@@ -40,7 +40,7 @@ public abstract class SingleDatabasePerformanceTest
private Database<Connection> openDatabase() throws DbException { private Database<Connection> openDatabase() throws DbException {
Database<Connection> db = createDatabase( Database<Connection> db = createDatabase(
new TestDatabaseConfig(testDir, MAX_SIZE), new SystemClock()); new TestDatabaseConfig(testDir, MAX_SIZE), new SystemClock());
db.open(); db.open(null);
return db; return db;
} }

View File

@@ -17,6 +17,8 @@ import javax.inject.Singleton;
import dagger.Module; import dagger.Module;
import dagger.Provides; import dagger.Provides;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.RUNNING;
@Module @Module
public class TestLifecycleModule { public class TestLifecycleModule {
@@ -57,6 +59,11 @@ public class TestLifecycleModule {
@Override @Override
public void waitForShutdown() throws InterruptedException { public void waitForShutdown() throws InterruptedException {
} }
@Override
public LifecycleState getLifecycleState() {
return RUNNING;
}
}; };
return lifecycleManager; return lifecycleManager;
} }

View File

@@ -77,6 +77,11 @@
</intent-filter> </intent-filter>
</activity> </activity>
<activity
android:name=".android.login.OpenDatabaseActivity"
android:label="@string/app_name"
android:launchMode="singleTop"/>
<activity <activity
android:name="org.briarproject.briar.android.navdrawer.NavDrawerActivity" android:name="org.briarproject.briar.android.navdrawer.NavDrawerActivity"
android:theme="@style/BriarTheme.NoActionBar" android:theme="@style/BriarTheme.NoActionBar"

View File

@@ -94,6 +94,7 @@ public class AppModule {
@Override @Override
public boolean databaseExists() { public boolean databaseExists() {
// FIXME should not run on UiThread #620
if (!dir.isDirectory()) return false; if (!dir.isDirectory()) return false;
File[] files = dir.listFiles(); File[] files = dir.listFiles();
return files != null && files.length > 0; return files != null && files.length > 0;

View File

@@ -88,6 +88,7 @@ public class BriarService extends Service {
stopSelf(); stopSelf();
return; return;
} }
// Create notification channels // Create notification channels
if (SDK_INT >= 26) { if (SDK_INT >= 26) {
NotificationManager nm = (NotificationManager) NotificationManager nm = (NotificationManager)

View File

@@ -32,6 +32,7 @@ import org.briarproject.briar.android.keyagreement.ShowQrCodeFragment;
import org.briarproject.briar.android.login.AuthorNameFragment; import org.briarproject.briar.android.login.AuthorNameFragment;
import org.briarproject.briar.android.login.ChangePasswordActivity; import org.briarproject.briar.android.login.ChangePasswordActivity;
import org.briarproject.briar.android.login.DozeFragment; 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.PasswordActivity;
import org.briarproject.briar.android.login.PasswordFragment; import org.briarproject.briar.android.login.PasswordFragment;
import org.briarproject.briar.android.login.SetupActivity; import org.briarproject.briar.android.login.SetupActivity;
@@ -88,6 +89,8 @@ public interface ActivityComponent {
void inject(SetupActivity activity); void inject(SetupActivity activity);
void inject(OpenDatabaseActivity activity);
void inject(NavDrawerActivity activity); void inject(NavDrawerActivity activity);
void inject(PasswordActivity activity); void inject(PasswordActivity activity);

View File

@@ -17,4 +17,7 @@ public interface ConfigController {
void deleteAccount(Context ctx); void deleteAccount(Context ctx);
boolean accountExists(); boolean accountExists();
boolean accountSignedIn();
} }

View File

@@ -52,4 +52,10 @@ public class ConfigControllerImpl implements ConfigController {
String hex = getEncryptedDatabaseKey(); String hex = getEncryptedDatabaseKey();
return hex != null && databaseConfig.databaseExists(); return hex != null && databaseConfig.databaseExists();
} }
@Override
public boolean accountSignedIn() {
return databaseConfig.getEncryptionKey() != null;
}
} }

View File

@@ -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();
}
}

View File

@@ -8,7 +8,6 @@ import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.ActivityComponent; import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.activity.BaseActivity; import org.briarproject.briar.android.activity.BaseActivity;
import org.briarproject.briar.android.fragment.BaseFragment.BaseFragmentListener; import org.briarproject.briar.android.fragment.BaseFragment.BaseFragmentListener;
import org.briarproject.briar.android.navdrawer.NavDrawerActivity;
import javax.inject.Inject; import javax.inject.Inject;
@@ -48,7 +47,7 @@ public class SetupActivity extends BaseActivity
} }
public void showApp() { public void showApp() {
Intent i = new Intent(this, NavDrawerActivity.class); Intent i = new Intent(this, OpenDatabaseActivity.class);
i.setFlags(FLAG_ACTIVITY_NEW_TASK); i.setFlags(FLAG_ACTIVITY_NEW_TASK);
startActivity(i); startActivity(i);
supportFinishAfterTransition(); supportFinishAfterTransition();

View File

@@ -46,8 +46,6 @@ import java.util.logging.Logger;
import javax.inject.Inject; 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.app.FragmentManager.POP_BACK_STACK_INCLUSIVE;
import static android.support.v4.view.GravityCompat.START; import static android.support.v4.view.GravityCompat.START;
import static android.support.v4.widget.DrawerLayout.LOCK_MODE_LOCKED_CLOSED; import static android.support.v4.widget.DrawerLayout.LOCK_MODE_LOCKED_CLOSED;
@@ -214,18 +212,6 @@ public class NavDrawerActivity extends BriarActivity implements
public void onBackPressed() { public void onBackPressed() {
if (drawerLayout.isDrawerOpen(START)) { if (drawerLayout.isDrawerOpen(START)) {
drawerLayout.closeDrawer(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 && } else if (getSupportFragmentManager().getBackStackEntryCount() == 0 &&
getSupportFragmentManager() getSupportFragmentManager()
.findFragmentByTag(ContactListFragment.TAG) == null) { .findFragmentByTag(ContactListFragment.TAG) == null) {

View File

@@ -12,8 +12,8 @@ import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.ActivityComponent; import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.activity.BaseActivity; import org.briarproject.briar.android.activity.BaseActivity;
import org.briarproject.briar.android.controller.ConfigController; 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.login.SetupActivity;
import org.briarproject.briar.android.navdrawer.NavDrawerActivity;
import java.util.logging.Logger; import java.util.logging.Logger;
@@ -43,10 +43,15 @@ public class SplashScreenActivity extends BaseActivity {
setContentView(R.layout.splash); setContentView(R.layout.splash);
new Handler().postDelayed(() -> { if (configController.accountSignedIn()) {
startNextActivity(); startActivity(new Intent(this, OpenDatabaseActivity.class));
supportFinishAfterTransition(); finish();
}, 500); } else {
new Handler().postDelayed(() -> {
startNextActivity();
supportFinishAfterTransition();
}, 500);
}
} }
@Override @Override
@@ -60,7 +65,7 @@ public class SplashScreenActivity extends BaseActivity {
startActivity(new Intent(this, ExpiredActivity.class)); startActivity(new Intent(this, ExpiredActivity.class));
} else { } else {
if (configController.accountExists()) { if (configController.accountExists()) {
startActivity(new Intent(this, NavDrawerActivity.class)); startActivity(new Intent(this, OpenDatabaseActivity.class));
} else { } else {
configController.deleteAccount(this); configController.deleteAccount(this);
startActivity(new Intent(this, SetupActivity.class)); startActivity(new Intent(this, SetupActivity.class));

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -46,6 +46,8 @@
</plurals> </plurals>
<string name="expiry_update">The testing expiry date has been extended. Your account will now expire in %d days.</string> <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="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 --> <!-- Navigation Drawer -->
<string name="nav_drawer_open_description">Open the navigation drawer</string> <string name="nav_drawer_open_description">Open the navigation drawer</string>