Show a status screen when opening the database or applying migrations

This commit is contained in:
Torsten Grote
2018-02-26 18:15:00 -03:00
parent 9f02bbbba1
commit 80770b0216
26 changed files with 289 additions and 36 deletions

View File

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

View File

@@ -0,0 +1,9 @@
package org.briarproject.bramble.api.db;
import org.briarproject.bramble.api.event.Event;
/**
* An event that is broadcast before database migrations are being applied.
*/
public class DatabaseMigrationEvent extends Event {
}

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,14 @@ public interface LifecycleManager {
SUCCESS
}
/**
* The state the lifecycle can be in.
* Returned by {@link #getLifecycleState()}
*/
enum LifecycleState {
STARTING, MIGRATING, RUNNING
}
/**
* Registers a {@link Service} to be started and stopped.
*/
@@ -76,4 +84,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();
}

View File

@@ -0,0 +1,10 @@
package org.briarproject.bramble.api.lifecycle.event;
import org.briarproject.bramble.api.event.Event;
/**
* An event that is broadcast when the app is starting.
* This happens after the database was opened and services were started.
*/
public class StartupEvent 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.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

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.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();

View File

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

View File

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

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

View File

@@ -5,7 +5,9 @@ import org.briarproject.bramble.api.crypto.KeyPair;
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.DatabaseMigrationEvent;
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;
@@ -15,6 +17,7 @@ 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.StartupEvent;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.Client;
@@ -31,6 +34,9 @@ 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;
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.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());
@@ -57,6 +63,7 @@ class LifecycleManagerImpl implements LifecycleManager {
private final CountDownLatch dbLatch = new CountDownLatch(1);
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,
@@ -123,7 +130,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)
@@ -162,6 +169,8 @@ class LifecycleManagerImpl implements LifecycleManager {
}
}
startupLatch.countDown();
state = RUNNING;
eventBus.broadcast(new StartupEvent());
return SUCCESS;
} catch (DataTooOldException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
@@ -180,6 +189,12 @@ class LifecycleManagerImpl implements LifecycleManager {
}
}
@Override
public void onMigrationRun() {
state = MIGRATING;
eventBus.broadcast(new DatabaseMigrationEvent());
}
@Override
public void stopServices() {
try {
@@ -235,4 +250,8 @@ class LifecycleManagerImpl implements LifecycleManager {
shutdownLatch.await();
}
@Override
public LifecycleState getLifecycleState() {
return state;
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -17,6 +17,7 @@ import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult;
import org.briarproject.bramble.api.system.AndroidExecutor;
import org.briarproject.briar.R;
import org.briarproject.briar.android.login.OpenDatabaseActivity;
import org.briarproject.briar.android.navdrawer.NavDrawerActivity;
import java.util.concurrent.CountDownLatch;
@@ -88,6 +89,12 @@ public class BriarService extends Service {
stopSelf();
return;
}
Intent intent =
new Intent(getApplicationContext(), OpenDatabaseActivity.class);
intent.setFlags(FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
// Create notification channels
if (SDK_INT >= 26) {
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.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);

View File

@@ -0,0 +1,90 @@
package org.briarproject.briar.android.login;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.widget.ImageView;
import android.widget.TextView;
import org.briarproject.bramble.api.db.DatabaseMigrationEvent;
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.event.StartupEvent;
import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.activity.BaseActivity;
import javax.annotation.ParametersAreNonnullByDefault;
import javax.inject.Inject;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.MIGRATING;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.RUNNING;
@ParametersAreNonnullByDefault
public class OpenDatabaseActivity extends BaseActivity
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 onBackPressed() {
// do not let the user bail out of here
}
@Override
protected void onStart() {
super.onStart();
if (lifecycleManager.getLifecycleState() == RUNNING) {
supportFinishAfterTransition();
} else {
if (lifecycleManager.getLifecycleState() == MIGRATING) {
showMigration();
}
eventBus.addListener(this);
}
}
@Override
protected void onStop() {
super.onStop();
eventBus.removeListener(this);
}
@Override
public void eventOccurred(Event e) {
if (e instanceof StartupEvent) {
runOnUiThreadUnlessDestroyed(this::supportFinishAfterTransition);
} else if (e instanceof DatabaseMigrationEvent) {
runOnUiThreadUnlessDestroyed(this::showMigration);
}
}
private void showMigration() {
if (showingMigration) return;
textView.setText(R.string.startup_migrate_database);
imageView.setImageResource(R.drawable.startup_migration);
showingMigration = true;
}
}

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