From 245b0a7e50688d58427e5bf0af0604c42591efcb Mon Sep 17 00:00:00 2001 From: Greyson Parrelli Date: Sun, 1 Mar 2020 10:54:11 -0500 Subject: [PATCH] Add a new buildType with Flipper. --- app/build.gradle | 222 ++++++++-------- app/src/flipper/AndroidManifest.xml | 9 + .../securesms/FlipperApplicationContext.java | 27 ++ .../database/FlipperSqlCipherAdapter.java | 245 ++++++++++++++++++ app/src/flipper/res/values/strings.xml | 4 + .../securesms/database/DatabaseFactory.java | 4 + .../database/helpers/SQLCipherOpenHelper.java | 4 + app/src/staging/res/values/strings.xml | 2 +- 8 files changed, 409 insertions(+), 108 deletions(-) create mode 100644 app/src/flipper/AndroidManifest.xml create mode 100644 app/src/flipper/java/org/thoughtcrime/securesms/FlipperApplicationContext.java create mode 100644 app/src/flipper/java/org/thoughtcrime/securesms/database/FlipperSqlCipherAdapter.java create mode 100644 app/src/flipper/res/values/strings.xml diff --git a/app/build.gradle b/app/build.gradle index 2196d42819..ec3db808cc 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -80,113 +80,6 @@ protobuf { } } -dependencies { - implementation 'androidx.appcompat:appcompat:1.1.0-beta01' - implementation 'androidx.recyclerview:recyclerview:1.0.0' - implementation 'com.google.android.material:material:1.0.0' - implementation 'androidx.legacy:legacy-support-v13:1.0.0' - implementation 'androidx.cardview:cardview:1.0.0' - implementation 'androidx.preference:preference:1.0.0' - implementation 'androidx.legacy:legacy-preference-v14:1.0.0' - implementation 'androidx.gridlayout:gridlayout:1.0.0' - implementation 'androidx.exifinterface:exifinterface:1.0.0' - implementation 'androidx.constraintlayout:constraintlayout:1.1.3' - implementation 'androidx.multidex:multidex:2.0.1' - implementation 'androidx.navigation:navigation-fragment:2.1.0' - implementation 'androidx.navigation:navigation-ui:2.1.0' - implementation 'androidx.lifecycle:lifecycle-extensions:2.1.0' - implementation 'androidx.lifecycle:lifecycle-viewmodel-savedstate:1.0.0-alpha05' - implementation 'androidx.lifecycle:lifecycle-common-java8:2.1.0' - implementation "androidx.camera:camera-core:1.0.0-alpha06" - implementation "androidx.camera:camera-camera2:1.0.0-alpha06" - - implementation('com.google.firebase:firebase-messaging:17.3.4') { - exclude group: 'com.google.firebase', module: 'firebase-core' - exclude group: 'com.google.firebase', module: 'firebase-analytics' - exclude group: 'com.google.firebase', module: 'firebase-measurement-connector' - } - - implementation 'com.google.android.gms:play-services-maps:16.1.0' - implementation 'com.google.android.gms:play-services-auth:16.0.1' - - implementation 'com.google.android.exoplayer:exoplayer-core:2.9.1' - implementation 'com.google.android.exoplayer:exoplayer-ui:2.9.1' - - implementation 'org.conscrypt:conscrypt-android:2.0.0' - implementation 'org.signal:aesgcmprovider:0.0.3' - - implementation project(':libsignal-service') - - implementation 'org.signal:argon2:13.1@aar' - - implementation 'org.signal:ringrtc-android:1.0.2' - - implementation "me.leolin:ShortcutBadger:1.1.16" - implementation 'se.emilsjolander:stickylistheaders:2.7.0' - implementation 'com.jpardogo.materialtabstrip:library:1.0.9' - implementation 'org.apache.httpcomponents:httpclient-android:4.3.5' - implementation 'com.github.chrisbanes:PhotoView:2.1.3' - implementation 'com.github.bumptech.glide:glide:4.9.0' - annotationProcessor 'com.github.bumptech.glide:compiler:4.9.0' - annotationProcessor 'androidx.annotation:annotation:1.1.0' - implementation 'com.makeramen:roundedimageview:2.1.0' - implementation 'com.pnikosis:materialish-progress:1.5' - implementation 'org.greenrobot:eventbus:3.0.0' - implementation 'pl.tajchert:waitingdots:0.1.0' - implementation 'com.melnykov:floatingactionbutton:1.3.0' - implementation 'com.google.zxing:android-integration:3.1.0' - implementation 'mobi.upod:time-duration-picker:1.1.3' - implementation 'com.amulyakhare:com.amulyakhare.textdrawable:1.0.1' - implementation 'com.google.zxing:core:3.2.1' - implementation ('com.davemorrissey.labs:subsampling-scale-image-view:3.6.0') { - exclude group: 'com.android.support', module: 'support-annotations' - } - implementation ('cn.carbswang.android:NumberPickerView:1.0.9') { - exclude group: 'com.android.support', module: 'appcompat-v7' - } - implementation ('com.tomergoldst.android:tooltips:1.0.6') { - exclude group: 'com.android.support', module: 'appcompat-v7' - } - implementation ('com.klinkerapps:android-smsmms:4.0.1') { - exclude group: 'com.squareup.okhttp', module: 'okhttp' - exclude group: 'com.squareup.okhttp', module: 'okhttp-urlconnection' - } - implementation 'com.annimon:stream:1.1.8' - implementation ('com.takisoft.fix:colorpicker:0.9.1') { - exclude group: 'com.android.support', module: 'appcompat-v7' - exclude group: 'com.android.support', module: 'recyclerview-v7' - } - - implementation 'com.airbnb.android:lottie:3.0.7' - - implementation 'com.codewaves.stickyheadergrid:stickyheadergrid:0.9.4' - implementation 'com.github.dmytrodanylyk.circular-progress-button:library:1.1.3-S2' - implementation 'org.signal:android-database-sqlcipher:3.5.9-S3' - implementation ('com.googlecode.ez-vcard:ez-vcard:0.9.11') { - exclude group: 'com.fasterxml.jackson.core' - exclude group: 'org.freemarker' - } - - testImplementation 'junit:junit:4.12' - testImplementation 'org.assertj:assertj-core:3.11.1' - testImplementation 'org.mockito:mockito-core:1.9.5' - testImplementation 'org.powermock:powermock-api-mockito:1.6.1' - testImplementation 'org.powermock:powermock-module-junit4:1.6.1' - testImplementation 'org.powermock:powermock-module-junit4-rule:1.6.1' - testImplementation 'org.powermock:powermock-classloading-xstream:1.6.1' - - testImplementation 'androidx.test:core:1.2.0' - testImplementation 'org.robolectric:robolectric:4.2' - testImplementation 'org.robolectric:shadows-multidex:4.2' - - androidTestImplementation 'androidx.test.ext:junit:1.1.1' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' -} - -dependencyVerification { - configuration = '(play|website)(Debug|Release)RuntimeClasspath' -} - def canonicalVersionCode = 610 def canonicalVersionName = "4.56.4" @@ -307,6 +200,10 @@ android { buildConfigField "String", "UNIDENTIFIED_SENDER_TRUST_ROOT", "\"BbqY1DzohE4NUZoVF+L18oUPrK3kILllLEJh2UnPSsEx\"" buildConfigField "String", "ZKGROUP_SERVER_PUBLIC_PARAMS", "\"\"" } + flipper { + initWith debug + minifyEnabled false + } release { minifyEnabled true proguardFiles = buildTypes.debug.proguardFiles @@ -354,6 +251,117 @@ android { } } +dependencies { + implementation 'androidx.appcompat:appcompat:1.1.0-beta01' + implementation 'androidx.recyclerview:recyclerview:1.0.0' + implementation 'com.google.android.material:material:1.0.0' + implementation 'androidx.legacy:legacy-support-v13:1.0.0' + implementation 'androidx.cardview:cardview:1.0.0' + implementation 'androidx.preference:preference:1.0.0' + implementation 'androidx.legacy:legacy-preference-v14:1.0.0' + implementation 'androidx.gridlayout:gridlayout:1.0.0' + implementation 'androidx.exifinterface:exifinterface:1.0.0' + implementation 'androidx.constraintlayout:constraintlayout:1.1.3' + implementation 'androidx.multidex:multidex:2.0.1' + implementation 'androidx.navigation:navigation-fragment:2.1.0' + implementation 'androidx.navigation:navigation-ui:2.1.0' + implementation 'androidx.lifecycle:lifecycle-extensions:2.1.0' + implementation 'androidx.lifecycle:lifecycle-viewmodel-savedstate:1.0.0-alpha05' + implementation 'androidx.lifecycle:lifecycle-common-java8:2.1.0' + implementation "androidx.camera:camera-core:1.0.0-alpha06" + implementation "androidx.camera:camera-camera2:1.0.0-alpha06" + + implementation('com.google.firebase:firebase-messaging:17.3.4') { + exclude group: 'com.google.firebase', module: 'firebase-core' + exclude group: 'com.google.firebase', module: 'firebase-analytics' + exclude group: 'com.google.firebase', module: 'firebase-measurement-connector' + } + + implementation 'com.google.android.gms:play-services-maps:16.1.0' + implementation 'com.google.android.gms:play-services-auth:16.0.1' + + implementation 'com.google.android.exoplayer:exoplayer-core:2.9.1' + implementation 'com.google.android.exoplayer:exoplayer-ui:2.9.1' + + implementation 'org.conscrypt:conscrypt-android:2.0.0' + implementation 'org.signal:aesgcmprovider:0.0.3' + + implementation project(':libsignal-service') + + implementation 'org.signal:argon2:13.1@aar' + + implementation 'org.signal:ringrtc-android:1.0.2' + + implementation "me.leolin:ShortcutBadger:1.1.16" + implementation 'se.emilsjolander:stickylistheaders:2.7.0' + implementation 'com.jpardogo.materialtabstrip:library:1.0.9' + implementation 'org.apache.httpcomponents:httpclient-android:4.3.5' + implementation 'com.github.chrisbanes:PhotoView:2.1.3' + implementation 'com.github.bumptech.glide:glide:4.9.0' + annotationProcessor 'com.github.bumptech.glide:compiler:4.9.0' + annotationProcessor 'androidx.annotation:annotation:1.1.0' + implementation 'com.makeramen:roundedimageview:2.1.0' + implementation 'com.pnikosis:materialish-progress:1.5' + implementation 'org.greenrobot:eventbus:3.0.0' + implementation 'pl.tajchert:waitingdots:0.1.0' + implementation 'com.melnykov:floatingactionbutton:1.3.0' + implementation 'com.google.zxing:android-integration:3.1.0' + implementation 'mobi.upod:time-duration-picker:1.1.3' + implementation 'com.amulyakhare:com.amulyakhare.textdrawable:1.0.1' + implementation 'com.google.zxing:core:3.2.1' + implementation ('com.davemorrissey.labs:subsampling-scale-image-view:3.6.0') { + exclude group: 'com.android.support', module: 'support-annotations' + } + implementation ('cn.carbswang.android:NumberPickerView:1.0.9') { + exclude group: 'com.android.support', module: 'appcompat-v7' + } + implementation ('com.tomergoldst.android:tooltips:1.0.6') { + exclude group: 'com.android.support', module: 'appcompat-v7' + } + implementation ('com.klinkerapps:android-smsmms:4.0.1') { + exclude group: 'com.squareup.okhttp', module: 'okhttp' + exclude group: 'com.squareup.okhttp', module: 'okhttp-urlconnection' + } + implementation 'com.annimon:stream:1.1.8' + implementation ('com.takisoft.fix:colorpicker:0.9.1') { + exclude group: 'com.android.support', module: 'appcompat-v7' + exclude group: 'com.android.support', module: 'recyclerview-v7' + } + + implementation 'com.airbnb.android:lottie:3.0.7' + + implementation 'com.codewaves.stickyheadergrid:stickyheadergrid:0.9.4' + implementation 'com.github.dmytrodanylyk.circular-progress-button:library:1.1.3-S2' + implementation 'org.signal:android-database-sqlcipher:3.5.9-S3' + implementation ('com.googlecode.ez-vcard:ez-vcard:0.9.11') { + exclude group: 'com.fasterxml.jackson.core' + exclude group: 'org.freemarker' + } + + flipperImplementation 'com.facebook.flipper:flipper:0.32.2' + flipperImplementation 'com.facebook.soloader:soloader:0.8.2' + + testImplementation 'junit:junit:4.12' + testImplementation 'org.assertj:assertj-core:3.11.1' + testImplementation 'org.mockito:mockito-core:1.9.5' + testImplementation 'org.powermock:powermock-api-mockito:1.6.1' + testImplementation 'org.powermock:powermock-module-junit4:1.6.1' + testImplementation 'org.powermock:powermock-module-junit4-rule:1.6.1' + testImplementation 'org.powermock:powermock-classloading-xstream:1.6.1' + + testImplementation 'androidx.test:core:1.2.0' + testImplementation 'org.robolectric:robolectric:4.2' + testImplementation 'org.robolectric:shadows-multidex:4.2' + + androidTestImplementation 'androidx.test.ext:junit:1.1.1' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' +} + +dependencyVerification { + configuration = '(play|website)(Debug|Release)RuntimeClasspath' +} + + def assembleWebsiteDescriptor = { variant, file -> if (file.exists()) { MessageDigest md = MessageDigest.getInstance("SHA-256"); diff --git a/app/src/flipper/AndroidManifest.xml b/app/src/flipper/AndroidManifest.xml new file mode 100644 index 0000000000..1f15fab864 --- /dev/null +++ b/app/src/flipper/AndroidManifest.xml @@ -0,0 +1,9 @@ + + + + + diff --git a/app/src/flipper/java/org/thoughtcrime/securesms/FlipperApplicationContext.java b/app/src/flipper/java/org/thoughtcrime/securesms/FlipperApplicationContext.java new file mode 100644 index 0000000000..7c5fe2c2db --- /dev/null +++ b/app/src/flipper/java/org/thoughtcrime/securesms/FlipperApplicationContext.java @@ -0,0 +1,27 @@ +package org.thoughtcrime.securesms; + +import com.facebook.flipper.android.AndroidFlipperClient; +import com.facebook.flipper.core.FlipperClient; +import com.facebook.flipper.plugins.databases.DatabasesFlipperPlugin; +import com.facebook.flipper.plugins.inspector.DescriptorMapping; +import com.facebook.flipper.plugins.inspector.InspectorFlipperPlugin; +import com.facebook.flipper.plugins.sharedpreferences.SharedPreferencesFlipperPlugin; +import com.facebook.soloader.SoLoader; + +import org.thoughtcrime.securesms.database.FlipperSqlCipherAdapter; + +public class FlipperApplicationContext extends ApplicationContext { + + @Override + public void onCreate() { + super.onCreate(); + + SoLoader.init(this, false); + + FlipperClient client = AndroidFlipperClient.getInstance(this); + client.addPlugin(new InspectorFlipperPlugin(this, DescriptorMapping.withDefaults())); + client.addPlugin(new DatabasesFlipperPlugin(new FlipperSqlCipherAdapter(this))); + client.addPlugin(new SharedPreferencesFlipperPlugin(this)); + client.start(); + } +} diff --git a/app/src/flipper/java/org/thoughtcrime/securesms/database/FlipperSqlCipherAdapter.java b/app/src/flipper/java/org/thoughtcrime/securesms/database/FlipperSqlCipherAdapter.java new file mode 100644 index 0000000000..786c616289 --- /dev/null +++ b/app/src/flipper/java/org/thoughtcrime/securesms/database/FlipperSqlCipherAdapter.java @@ -0,0 +1,245 @@ +package org.thoughtcrime.securesms.database; + +import android.content.Context; +import android.database.Cursor; +import android.text.TextUtils; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.facebook.flipper.plugins.databases.DatabaseDescriptor; +import com.facebook.flipper.plugins.databases.DatabaseDriver; + +import net.sqlcipher.DatabaseUtils; +import net.sqlcipher.database.SQLiteDatabase; +import net.sqlcipher.database.SQLiteStatement; + +import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * A lot of this code is taken from {@link com.facebook.flipper.plugins.databases.impl.SqliteDatabaseDriver} + * and made to work with SqlCipher. Unfortunately I couldn't use it directly, nor subclass it. + */ +public class FlipperSqlCipherAdapter extends DatabaseDriver { + + public FlipperSqlCipherAdapter(Context context) { + super(context); + } + + @Override + public List getDatabases() { + return Collections.singletonList(new Descriptor(DatabaseFactory.getRawDatabase(getContext()))); + } + + @Override + public List getTableNames(Descriptor descriptor) { + SQLiteDatabase db = descriptor.getReadable(); + List tableNames = new ArrayList<>(); + + try (Cursor cursor = db.rawQuery("SELECT name FROM sqlite_master WHERE type IN (?, ?)", new String[] { "table", "view" })) { + while (cursor != null && cursor.moveToNext()) { + tableNames.add(cursor.getString(0)); + } + } + + return tableNames; + } + + @Override + public DatabaseGetTableDataResponse getTableData(Descriptor descriptor, String table, String order, boolean reverse, int start, int count) { + SQLiteDatabase db = descriptor.getReadable(); + + long total = DatabaseUtils.queryNumEntries(db, table); + String orderBy = order != null ? order + (reverse ? " DESC" : " ASC") : null; + String limitBy = start + ", " + count; + + try (Cursor cursor = db.query(table, null, null, null, null, null, orderBy, limitBy)) { + String[] columnNames = cursor.getColumnNames(); + List> rows = cursorToList(cursor); + + return new DatabaseGetTableDataResponse(Arrays.asList(columnNames), rows, start, rows.size(), total); + } + } + + @Override + public DatabaseGetTableStructureResponse getTableStructure(Descriptor descriptor, String table) { + SQLiteDatabase db = descriptor.getReadable(); + + Map foreignKeyValues = new HashMap<>(); + + try(Cursor cursor = db.rawQuery("PRAGMA foreign_key_list(" + table + ")", null)) { + while (cursor != null && cursor.moveToNext()) { + String from = cursor.getString(cursor.getColumnIndex("from")); + String to = cursor.getString(cursor.getColumnIndex("to")); + String tableName = cursor.getString(cursor.getColumnIndex("table")) + "(" + to + ")"; + + foreignKeyValues.put(from, tableName); + } + } + + + List structureColumns = Arrays.asList("column_name", "data_type", "nullable", "default", "primary_key", "foreign_key"); + List> structureValues = new ArrayList<>(); + + try (Cursor cursor = db.rawQuery("PRAGMA table_info(" + table + ")", null)) { + while (cursor != null && cursor.moveToNext()) { + String columnName = cursor.getString(cursor.getColumnIndex("name")); + String foreignKey = foreignKeyValues.containsKey(columnName) ? foreignKeyValues.get(columnName) : null; + + structureValues.add(Arrays.asList(columnName, + cursor.getString(cursor.getColumnIndex("type")), + cursor.getInt(cursor.getColumnIndex("notnull")) == 0, + getObjectFromColumnIndex(cursor, cursor.getColumnIndex("dflt_value")), + cursor.getInt(cursor.getColumnIndex("pk")) == 1, + foreignKey)); + } + } + + + List indexesColumns = Arrays.asList("index_name", "unique", "indexed_column_name"); + List> indexesValues = new ArrayList<>(); + + try (Cursor indexesCursor = db.rawQuery("PRAGMA index_list(" + table + ")", null)) { + List indexedColumnNames = new ArrayList<>(); + String indexName = indexesCursor.getString(indexesCursor.getColumnIndex("name")); + + try(Cursor indexInfoCursor = db.rawQuery("PRAGMA index_info(" + indexName + ")", null)) { + while (indexInfoCursor.moveToNext()) { + indexedColumnNames.add(indexInfoCursor.getString(indexInfoCursor.getColumnIndex("name"))); + } + } + + indexesValues.add(Arrays.asList(indexName, + indexesCursor.getInt(indexesCursor.getColumnIndex("unique")) == 1, + TextUtils.join(",", indexedColumnNames))); + + } + + return new DatabaseGetTableStructureResponse(structureColumns, structureValues, indexesColumns, indexesValues); + } + + @Override + public DatabaseGetTableInfoResponse getTableInfo(Descriptor databaseDescriptor, String table) { + SQLiteDatabase db = databaseDescriptor.getReadable(); + + try (Cursor cursor = db.rawQuery("SELECT sql FROM sqlite_master WHERE name = ?", new String[] { table })) { + cursor.moveToFirst(); + return new DatabaseGetTableInfoResponse(cursor.getString(cursor.getColumnIndex("sql"))); + } + } + + @Override + public DatabaseExecuteSqlResponse executeSQL(Descriptor descriptor, String query) { + SQLiteDatabase db = descriptor.getWritable(); + + String firstWordUpperCase = getFirstWord(query).toUpperCase(); + + switch (firstWordUpperCase) { + case "UPDATE": + case "DELETE": + return executeUpdateDelete(db, query); + case "INSERT": + return executeInsert(db, query); + case "SELECT": + case "PRAGMA": + case "EXPLAIN": + return executeSelect(db, query); + default: + return executeRawQuery(db, query); + } + } + + private static String getFirstWord(String s) { + s = s.trim(); + int firstSpace = s.indexOf(' '); + return firstSpace >= 0 ? s.substring(0, firstSpace) : s; + } + + private static DatabaseExecuteSqlResponse executeUpdateDelete(SQLiteDatabase database, String query) { + SQLiteStatement statement = database.compileStatement(query); + int count = statement.executeUpdateDelete(); + + return DatabaseExecuteSqlResponse.successfulUpdateDelete(count); + } + + private static DatabaseExecuteSqlResponse executeInsert(SQLiteDatabase database, String query) { + SQLiteStatement statement = database.compileStatement(query); + long insertedId = statement.executeInsert(); + + return DatabaseExecuteSqlResponse.successfulInsert(insertedId); + } + + private static DatabaseExecuteSqlResponse executeSelect(SQLiteDatabase database, String query) { + try (Cursor cursor = database.rawQuery(query, null)) { + String[] columnNames = cursor.getColumnNames(); + List> rows = cursorToList(cursor); + + return DatabaseExecuteSqlResponse.successfulSelect(Arrays.asList(columnNames), rows); + } + } + + private static DatabaseExecuteSqlResponse executeRawQuery(SQLiteDatabase database, String query) { + database.execSQL(query); + return DatabaseExecuteSqlResponse.successfulRawQuery(); + } + + private static @NonNull List> cursorToList(Cursor cursor) { + List> rows = new ArrayList<>(); + int numColumns = cursor.getColumnCount(); + + while (cursor.moveToNext()) { + List values = new ArrayList<>(numColumns); + + for (int column = 0; column < numColumns; column++) { + values.add(getObjectFromColumnIndex(cursor, column)); + } + + rows.add(values); + } + return rows; + } + + private static @Nullable Object getObjectFromColumnIndex(Cursor cursor, int column) { + switch (cursor.getType(column)) { + case Cursor.FIELD_TYPE_NULL: + return null; + case Cursor.FIELD_TYPE_INTEGER: + return cursor.getLong(column); + case Cursor.FIELD_TYPE_FLOAT: + return cursor.getDouble(column); + case Cursor.FIELD_TYPE_BLOB: + return cursor.getBlob(column); + case Cursor.FIELD_TYPE_STRING: + default: + return cursor.getString(column); + } + } + + static class Descriptor implements DatabaseDescriptor { + private final SQLCipherOpenHelper sqlCipherOpenHelper; + + Descriptor(@NonNull SQLCipherOpenHelper sqlCipherOpenHelper) { + this.sqlCipherOpenHelper = sqlCipherOpenHelper; + } + + @Override + public String name() { + return sqlCipherOpenHelper.getDatabaseName(); + } + + public @NonNull SQLiteDatabase getReadable() { + return sqlCipherOpenHelper.getReadableDatabase(); + } + + public @NonNull SQLiteDatabase getWritable() { + return sqlCipherOpenHelper.getWritableDatabase(); + } + } +} diff --git a/app/src/flipper/res/values/strings.xml b/app/src/flipper/res/values/strings.xml new file mode 100644 index 0000000000..ed7ba2307d --- /dev/null +++ b/app/src/flipper/res/values/strings.xml @@ -0,0 +1,4 @@ + + + Signal (Flipper) + \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/DatabaseFactory.java b/app/src/main/java/org/thoughtcrime/securesms/database/DatabaseFactory.java index 48af240fe5..95aa9632da 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/DatabaseFactory.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/DatabaseFactory.java @@ -175,6 +175,10 @@ public class DatabaseFactory { } } + static SQLCipherOpenHelper getRawDatabase(Context context) { + return getInstance(context).databaseHelper; + } + private DatabaseFactory(@NonNull Context context) { SQLiteDatabase.loadLibs(context); diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java index 5546c7390e..4e69924806 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java @@ -818,6 +818,10 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper { return context.getDatabasePath(DATABASE_NAME).exists(); } + public static File getDatabaseFile(@NonNull Context context) { + return context.getDatabasePath(DATABASE_NAME); + } + private void executeStatements(SQLiteDatabase db, String[] statements) { for (String statement : statements) db.execSQL(statement); diff --git a/app/src/staging/res/values/strings.xml b/app/src/staging/res/values/strings.xml index 2c92311374..c8a66e8613 100644 --- a/app/src/staging/res/values/strings.xml +++ b/app/src/staging/res/values/strings.xml @@ -1,4 +1,4 @@ - Signal Staging + Signal (Staging) \ No newline at end of file