mirror of
https://github.com/oxen-io/session-android.git
synced 2025-02-17 15:38:27 +00:00
Add determinte progress and foreground service for sqlcipher migration
This commit is contained in:
parent
bdd4b456c4
commit
23aee53c7d
@ -443,6 +443,8 @@
|
|||||||
</intent-filter>
|
</intent-filter>
|
||||||
</service>
|
</service>
|
||||||
|
|
||||||
|
<service android:name=".service.GenericForegroundService"/>
|
||||||
|
|
||||||
<receiver android:name=".gcm.GcmBroadcastReceiver" android:permission="com.google.android.c2dm.permission.SEND" >
|
<receiver android:name=".gcm.GcmBroadcastReceiver" android:permission="com.google.android.c2dm.permission.SEND" >
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="com.google.android.c2dm.intent.RECEIVE" />
|
<action android:name="com.google.android.c2dm.intent.RECEIVE" />
|
||||||
|
@ -34,7 +34,7 @@
|
|||||||
android:layout_gravity="center"/>
|
android:layout_gravity="center"/>
|
||||||
|
|
||||||
<ProgressBar android:id="@+id/determinate_progress"
|
<ProgressBar android:id="@+id/determinate_progress"
|
||||||
style="@android:style/Widget.ProgressBar.Horizontal"
|
style="@style/Widget.AppCompat.ProgressBar.Horizontal"
|
||||||
android:layout_width="fill_parent"
|
android:layout_width="fill_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:indeterminate="false"
|
android:indeterminate="false"
|
||||||
|
@ -1317,6 +1317,7 @@
|
|||||||
<string name="Permissions_not_now">Not now</string>
|
<string name="Permissions_not_now">Not now</string>
|
||||||
<string name="ConversationListActivity_signal_needs_contacts_permission_in_order_to_search_your_contacts_but_it_has_been_permanently_denied">Signal needs Contacts permission in order to search your contacts, but it has been permanently denied. Please continue to app settings, select \"Permissions\", and enable \"Contacts\".</string>
|
<string name="ConversationListActivity_signal_needs_contacts_permission_in_order_to_search_your_contacts_but_it_has_been_permanently_denied">Signal needs Contacts permission in order to search your contacts, but it has been permanently denied. Please continue to app settings, select \"Permissions\", and enable \"Contacts\".</string>
|
||||||
<string name="conversation_activity__enable_signal_messages">ENABLE SIGNAL MESSAGES</string>
|
<string name="conversation_activity__enable_signal_messages">ENABLE SIGNAL MESSAGES</string>
|
||||||
|
<string name="SQLCipherMigrationHelper_migrating_signal_database">Migrating Signal database</string>
|
||||||
|
|
||||||
|
|
||||||
<!-- EOF -->
|
<!-- EOF -->
|
||||||
|
@ -156,7 +156,8 @@ public class DatabaseFactory {
|
|||||||
|
|
||||||
SQLCipherMigrationHelper.migrateCiphertext(context, masterSecret,
|
SQLCipherMigrationHelper.migrateCiphertext(context, masterSecret,
|
||||||
legacyOpenHelper.getWritableDatabase(),
|
legacyOpenHelper.getWritableDatabase(),
|
||||||
databaseHelper.getWritableDatabase());
|
databaseHelper.getWritableDatabase(),
|
||||||
|
listener);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,13 +10,16 @@ import android.text.TextUtils;
|
|||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.util.Pair;
|
import android.util.Pair;
|
||||||
|
|
||||||
import com.annimon.stream.function.Function;
|
import com.annimon.stream.function.BiFunction;
|
||||||
|
|
||||||
|
import org.thoughtcrime.securesms.DatabaseUpgradeActivity;
|
||||||
|
import org.thoughtcrime.securesms.R;
|
||||||
import org.thoughtcrime.securesms.crypto.AsymmetricMasterCipher;
|
import org.thoughtcrime.securesms.crypto.AsymmetricMasterCipher;
|
||||||
import org.thoughtcrime.securesms.crypto.AttachmentSecretProvider;
|
import org.thoughtcrime.securesms.crypto.AttachmentSecretProvider;
|
||||||
import org.thoughtcrime.securesms.crypto.MasterCipher;
|
import org.thoughtcrime.securesms.crypto.MasterCipher;
|
||||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||||
import org.thoughtcrime.securesms.crypto.MasterSecretUtil;
|
import org.thoughtcrime.securesms.crypto.MasterSecretUtil;
|
||||||
|
import org.thoughtcrime.securesms.service.GenericForegroundService;
|
||||||
import org.thoughtcrime.securesms.util.Base64;
|
import org.thoughtcrime.securesms.util.Base64;
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||||
import org.whispersystems.libsignal.InvalidMessageException;
|
import org.whispersystems.libsignal.InvalidMessageException;
|
||||||
@ -30,11 +33,13 @@ public class SQLCipherMigrationHelper {
|
|||||||
private static final long ENCRYPTION_SYMMETRIC_BIT = 0x80000000;
|
private static final long ENCRYPTION_SYMMETRIC_BIT = 0x80000000;
|
||||||
private static final long ENCRYPTION_ASYMMETRIC_BIT = 0x40000000;
|
private static final long ENCRYPTION_ASYMMETRIC_BIT = 0x40000000;
|
||||||
|
|
||||||
static void migratePlaintext(@NonNull android.database.sqlite.SQLiteDatabase legacyDb,
|
static void migratePlaintext(@NonNull Context context,
|
||||||
|
@NonNull android.database.sqlite.SQLiteDatabase legacyDb,
|
||||||
@NonNull net.sqlcipher.database.SQLiteDatabase modernDb)
|
@NonNull net.sqlcipher.database.SQLiteDatabase modernDb)
|
||||||
{
|
{
|
||||||
modernDb.beginTransaction();
|
modernDb.beginTransaction();
|
||||||
try {
|
try {
|
||||||
|
GenericForegroundService.startForegroundTask(context, context.getString(R.string.SQLCipherMigrationHelper_migrating_signal_database));
|
||||||
copyTable("identities", legacyDb, modernDb, null);
|
copyTable("identities", legacyDb, modernDb, null);
|
||||||
copyTable("push", legacyDb, modernDb, null);
|
copyTable("push", legacyDb, modernDb, null);
|
||||||
copyTable("groups", legacyDb, modernDb, null);
|
copyTable("groups", legacyDb, modernDb, null);
|
||||||
@ -43,13 +48,15 @@ public class SQLCipherMigrationHelper {
|
|||||||
modernDb.setTransactionSuccessful();
|
modernDb.setTransactionSuccessful();
|
||||||
} finally {
|
} finally {
|
||||||
modernDb.endTransaction();
|
modernDb.endTransaction();
|
||||||
|
GenericForegroundService.stopForegroundTask(context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void migrateCiphertext(@NonNull Context context,
|
public static void migrateCiphertext(@NonNull Context context,
|
||||||
@NonNull MasterSecret masterSecret,
|
@NonNull MasterSecret masterSecret,
|
||||||
@NonNull android.database.sqlite.SQLiteDatabase legacyDb,
|
@NonNull android.database.sqlite.SQLiteDatabase legacyDb,
|
||||||
@NonNull net.sqlcipher.database.SQLiteDatabase modernDb)
|
@NonNull net.sqlcipher.database.SQLiteDatabase modernDb,
|
||||||
|
@Nullable DatabaseUpgradeActivity.DatabaseUpgradeListener listener)
|
||||||
{
|
{
|
||||||
MasterCipher legacyCipher = new MasterCipher(masterSecret);
|
MasterCipher legacyCipher = new MasterCipher(masterSecret);
|
||||||
AsymmetricMasterCipher legacyAsymmetricCipher = new AsymmetricMasterCipher(MasterSecretUtil.getAsymmetricMasterSecret(context, masterSecret));
|
AsymmetricMasterCipher legacyAsymmetricCipher = new AsymmetricMasterCipher(MasterSecretUtil.getAsymmetricMasterSecret(context, masterSecret));
|
||||||
@ -57,7 +64,10 @@ public class SQLCipherMigrationHelper {
|
|||||||
modernDb.beginTransaction();
|
modernDb.beginTransaction();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
copyTable("sms", legacyDb, modernDb, (row) -> {
|
GenericForegroundService.startForegroundTask(context, context.getString(R.string.SQLCipherMigrationHelper_migrating_signal_database));
|
||||||
|
int total = 5000;
|
||||||
|
|
||||||
|
copyTable("sms", legacyDb, modernDb, (row, progress) -> {
|
||||||
Pair<Long, String> plaintext = getPlaintextBody(legacyCipher, legacyAsymmetricCipher,
|
Pair<Long, String> plaintext = getPlaintextBody(legacyCipher, legacyAsymmetricCipher,
|
||||||
row.getAsLong("type"),
|
row.getAsLong("type"),
|
||||||
row.getAsString("body"));
|
row.getAsString("body"));
|
||||||
@ -65,10 +75,14 @@ public class SQLCipherMigrationHelper {
|
|||||||
row.put("body", plaintext.second);
|
row.put("body", plaintext.second);
|
||||||
row.put("type", plaintext.first);
|
row.put("type", plaintext.first);
|
||||||
|
|
||||||
|
if (listener != null && (progress.first % 1000 == 0)) {
|
||||||
|
listener.setProgress(getTotalProgress(0, progress.first, progress.second), total);
|
||||||
|
}
|
||||||
|
|
||||||
return row;
|
return row;
|
||||||
});
|
});
|
||||||
|
|
||||||
copyTable("mms", legacyDb, modernDb, (row) -> {
|
copyTable("mms", legacyDb, modernDb, (row, progress) -> {
|
||||||
Pair<Long, String> plaintext = getPlaintextBody(legacyCipher, legacyAsymmetricCipher,
|
Pair<Long, String> plaintext = getPlaintextBody(legacyCipher, legacyAsymmetricCipher,
|
||||||
row.getAsLong("msg_box"),
|
row.getAsLong("msg_box"),
|
||||||
row.getAsString("body"));
|
row.getAsString("body"));
|
||||||
@ -76,10 +90,14 @@ public class SQLCipherMigrationHelper {
|
|||||||
row.put("body", plaintext.second);
|
row.put("body", plaintext.second);
|
||||||
row.put("msg_box", plaintext.first);
|
row.put("msg_box", plaintext.first);
|
||||||
|
|
||||||
|
if (listener != null && (progress.first % 1000 == 0)) {
|
||||||
|
listener.setProgress(getTotalProgress(1000, progress.first, progress.second), total);
|
||||||
|
}
|
||||||
|
|
||||||
return row;
|
return row;
|
||||||
});
|
});
|
||||||
|
|
||||||
copyTable("part", legacyDb, modernDb, (row) -> {
|
copyTable("part", legacyDb, modernDb, (row, progress) -> {
|
||||||
String fileName = row.getAsString("file_name");
|
String fileName = row.getAsString("file_name");
|
||||||
String mediaKey = row.getAsString("cd");
|
String mediaKey = row.getAsString("cd");
|
||||||
|
|
||||||
@ -107,11 +125,14 @@ public class SQLCipherMigrationHelper {
|
|||||||
Log.w(TAG, e);
|
Log.w(TAG, e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (listener != null && (progress.first % 1000 == 0)) {
|
||||||
|
listener.setProgress(getTotalProgress(2000, progress.first, progress.second), total);
|
||||||
|
}
|
||||||
|
|
||||||
return row;
|
return row;
|
||||||
});
|
});
|
||||||
|
|
||||||
copyTable("thread", legacyDb, modernDb, (row) -> {
|
copyTable("thread", legacyDb, modernDb, (row, progress) -> {
|
||||||
Pair<Long, String> plaintext = getPlaintextBody(legacyCipher, legacyAsymmetricCipher,
|
Pair<Long, String> plaintext = getPlaintextBody(legacyCipher, legacyAsymmetricCipher,
|
||||||
row.getAsLong("snippet_type"),
|
row.getAsLong("snippet_type"),
|
||||||
row.getAsString("snippet"));
|
row.getAsString("snippet"));
|
||||||
@ -119,11 +140,15 @@ public class SQLCipherMigrationHelper {
|
|||||||
row.put("snippet", plaintext.second);
|
row.put("snippet", plaintext.second);
|
||||||
row.put("snippet_type", plaintext.first);
|
row.put("snippet_type", plaintext.first);
|
||||||
|
|
||||||
|
if (listener != null && (progress.first % 1000 == 0)) {
|
||||||
|
listener.setProgress(getTotalProgress(3000, progress.first, progress.second), total);
|
||||||
|
}
|
||||||
|
|
||||||
return row;
|
return row;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
copyTable("drafts", legacyDb, modernDb, (row) -> {
|
copyTable("drafts", legacyDb, modernDb, (row, progress) -> {
|
||||||
String draftType = row.getAsString("type");
|
String draftType = row.getAsString("type");
|
||||||
String draft = row.getAsString("value");
|
String draft = row.getAsString("value");
|
||||||
|
|
||||||
@ -134,24 +159,31 @@ public class SQLCipherMigrationHelper {
|
|||||||
Log.w(TAG, e);
|
Log.w(TAG, e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (listener != null && (progress.first % 1000 == 0)) {
|
||||||
|
listener.setProgress(getTotalProgress(4000, progress.first, progress.second), total);
|
||||||
|
}
|
||||||
|
|
||||||
return row;
|
return row;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
AttachmentSecretProvider.getInstance(context).setClassicKey(context, masterSecret.getEncryptionKey().getEncoded(), masterSecret.getMacKey().getEncoded());
|
||||||
TextSecurePreferences.setNeedsSqlCipherMigration(context, false);
|
TextSecurePreferences.setNeedsSqlCipherMigration(context, false);
|
||||||
modernDb.setTransactionSuccessful();
|
modernDb.setTransactionSuccessful();
|
||||||
} finally {
|
} finally {
|
||||||
modernDb.endTransaction();
|
modernDb.endTransaction();
|
||||||
|
GenericForegroundService.stopForegroundTask(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
AttachmentSecretProvider.getInstance(context).setClassicKey(context, masterSecret.getEncryptionKey().getEncoded(), masterSecret.getMacKey().getEncoded());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void copyTable(@NonNull String tableName,
|
private static void copyTable(@NonNull String tableName,
|
||||||
@NonNull android.database.sqlite.SQLiteDatabase legacyDb,
|
@NonNull android.database.sqlite.SQLiteDatabase legacyDb,
|
||||||
@NonNull net.sqlcipher.database.SQLiteDatabase modernDb,
|
@NonNull net.sqlcipher.database.SQLiteDatabase modernDb,
|
||||||
@Nullable Function<ContentValues, ContentValues> transformer)
|
@Nullable BiFunction<ContentValues, Pair<Integer, Integer>, ContentValues> transformer)
|
||||||
{
|
{
|
||||||
try (Cursor cursor = legacyDb.query(tableName, null, null, null, null, null, null)) {
|
try (Cursor cursor = legacyDb.query(tableName, null, null, null, null, null, null)) {
|
||||||
|
int count = (cursor != null) ? cursor.getCount() : 0;
|
||||||
|
int progress = 1;
|
||||||
|
|
||||||
while (cursor != null && cursor.moveToNext()) {
|
while (cursor != null && cursor.moveToNext()) {
|
||||||
ContentValues row = new ContentValues();
|
ContentValues row = new ContentValues();
|
||||||
|
|
||||||
@ -167,7 +199,7 @@ public class SQLCipherMigrationHelper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (transformer != null) {
|
if (transformer != null) {
|
||||||
row = transformer.apply(row);
|
row = transformer.apply(row, new Pair<>(progress++, count));
|
||||||
}
|
}
|
||||||
|
|
||||||
modernDb.insert(tableName, null, row);
|
modernDb.insert(tableName, null, row);
|
||||||
@ -194,4 +226,9 @@ public class SQLCipherMigrationHelper {
|
|||||||
|
|
||||||
return new Pair<>(type, body);
|
return new Pair<>(type, body);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static int getTotalProgress(int sectionOffset, int sectionProgress, int sectionTotal) {
|
||||||
|
double percentOfSectionComplete = ((double)sectionProgress) / ((double)sectionTotal);
|
||||||
|
return sectionOffset + (int)(((double)1000) * percentOfSectionComplete);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -78,11 +78,11 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
|
|||||||
ClassicOpenHelper legacyHelper = new ClassicOpenHelper(context);
|
ClassicOpenHelper legacyHelper = new ClassicOpenHelper(context);
|
||||||
android.database.sqlite.SQLiteDatabase legacyDb = legacyHelper.getWritableDatabase();
|
android.database.sqlite.SQLiteDatabase legacyDb = legacyHelper.getWritableDatabase();
|
||||||
|
|
||||||
SQLCipherMigrationHelper.migratePlaintext(legacyDb, db);
|
SQLCipherMigrationHelper.migratePlaintext(context, legacyDb, db);
|
||||||
|
|
||||||
MasterSecret masterSecret = KeyCachingService.getMasterSecret(context);
|
MasterSecret masterSecret = KeyCachingService.getMasterSecret(context);
|
||||||
|
|
||||||
if (masterSecret != null) SQLCipherMigrationHelper.migrateCiphertext(context, masterSecret, legacyDb, db);
|
if (masterSecret != null) SQLCipherMigrationHelper.migrateCiphertext(context, masterSecret, legacyDb, db, null);
|
||||||
else TextSecurePreferences.setNeedsSqlCipherMigration(context, true);
|
else TextSecurePreferences.setNeedsSqlCipherMigration(context, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,82 @@
|
|||||||
|
package org.thoughtcrime.securesms.service;
|
||||||
|
|
||||||
|
|
||||||
|
import android.app.PendingIntent;
|
||||||
|
import android.app.Service;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.IBinder;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
import android.support.v4.app.NotificationCompat;
|
||||||
|
|
||||||
|
import org.thoughtcrime.securesms.ConversationListActivity;
|
||||||
|
import org.thoughtcrime.securesms.R;
|
||||||
|
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
public class GenericForegroundService extends Service {
|
||||||
|
|
||||||
|
private static final int NOTIFICATION_ID = 827353982;
|
||||||
|
private static final String EXTRA_TITLE = "extra_title";
|
||||||
|
|
||||||
|
private static final String ACTION_START = "start";
|
||||||
|
private static final String ACTION_STOP = "stop";
|
||||||
|
|
||||||
|
private final AtomicInteger foregroundCount = new AtomicInteger(0);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||||
|
if (intent != null && ACTION_START.equals(intent.getAction())) handleStart(intent);
|
||||||
|
else if (intent != null && ACTION_STOP.equals(intent.getAction())) handleStop();
|
||||||
|
|
||||||
|
return START_NOT_STICKY;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void handleStart(@NonNull Intent intent) {
|
||||||
|
String title = intent.getStringExtra(EXTRA_TITLE);
|
||||||
|
assert title != null;
|
||||||
|
|
||||||
|
if (foregroundCount.getAndIncrement() == 0) {
|
||||||
|
startForeground(NOTIFICATION_ID, new NotificationCompat.Builder(this)
|
||||||
|
.setSmallIcon(R.drawable.ic_signal_grey_24dp)
|
||||||
|
.setContentTitle(title)
|
||||||
|
.setContentIntent(PendingIntent.getActivity(this, 0, new Intent(this, ConversationListActivity.class), 0))
|
||||||
|
.build());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleStop() {
|
||||||
|
if (foregroundCount.decrementAndGet() == 0) {
|
||||||
|
stopForeground(true);
|
||||||
|
stopSelf();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public IBinder onBind(Intent intent) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void startForegroundTask(@NonNull Context context, @NonNull String task) {
|
||||||
|
Intent intent = new Intent(context, GenericForegroundService.class);
|
||||||
|
intent.setAction(ACTION_START);
|
||||||
|
intent.putExtra(EXTRA_TITLE, task);
|
||||||
|
|
||||||
|
context.startService(intent);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void stopForegroundTask(@NonNull Context context) {
|
||||||
|
Intent intent = new Intent(context, GenericForegroundService.class);
|
||||||
|
intent.setAction(ACTION_STOP);
|
||||||
|
|
||||||
|
context.startService(intent);
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user