mirror of
https://github.com/oxen-io/session-android.git
synced 2024-11-23 18:15:22 +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>
|
||||
</service>
|
||||
|
||||
<service android:name=".service.GenericForegroundService"/>
|
||||
|
||||
<receiver android:name=".gcm.GcmBroadcastReceiver" android:permission="com.google.android.c2dm.permission.SEND" >
|
||||
<intent-filter>
|
||||
<action android:name="com.google.android.c2dm.intent.RECEIVE" />
|
||||
|
@ -34,7 +34,7 @@
|
||||
android:layout_gravity="center"/>
|
||||
|
||||
<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_height="wrap_content"
|
||||
android:indeterminate="false"
|
||||
|
@ -1317,6 +1317,7 @@
|
||||
<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="conversation_activity__enable_signal_messages">ENABLE SIGNAL MESSAGES</string>
|
||||
<string name="SQLCipherMigrationHelper_migrating_signal_database">Migrating Signal database</string>
|
||||
|
||||
|
||||
<!-- EOF -->
|
||||
|
@ -156,7 +156,8 @@ public class DatabaseFactory {
|
||||
|
||||
SQLCipherMigrationHelper.migrateCiphertext(context, masterSecret,
|
||||
legacyOpenHelper.getWritableDatabase(),
|
||||
databaseHelper.getWritableDatabase());
|
||||
databaseHelper.getWritableDatabase(),
|
||||
listener);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -10,13 +10,16 @@ import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
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.AttachmentSecretProvider;
|
||||
import org.thoughtcrime.securesms.crypto.MasterCipher;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecretUtil;
|
||||
import org.thoughtcrime.securesms.service.GenericForegroundService;
|
||||
import org.thoughtcrime.securesms.util.Base64;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
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_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)
|
||||
{
|
||||
modernDb.beginTransaction();
|
||||
try {
|
||||
GenericForegroundService.startForegroundTask(context, context.getString(R.string.SQLCipherMigrationHelper_migrating_signal_database));
|
||||
copyTable("identities", legacyDb, modernDb, null);
|
||||
copyTable("push", legacyDb, modernDb, null);
|
||||
copyTable("groups", legacyDb, modernDb, null);
|
||||
@ -43,13 +48,15 @@ public class SQLCipherMigrationHelper {
|
||||
modernDb.setTransactionSuccessful();
|
||||
} finally {
|
||||
modernDb.endTransaction();
|
||||
GenericForegroundService.stopForegroundTask(context);
|
||||
}
|
||||
}
|
||||
|
||||
public static void migrateCiphertext(@NonNull Context context,
|
||||
@NonNull MasterSecret masterSecret,
|
||||
@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);
|
||||
AsymmetricMasterCipher legacyAsymmetricCipher = new AsymmetricMasterCipher(MasterSecretUtil.getAsymmetricMasterSecret(context, masterSecret));
|
||||
@ -57,7 +64,10 @@ public class SQLCipherMigrationHelper {
|
||||
modernDb.beginTransaction();
|
||||
|
||||
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,
|
||||
row.getAsLong("type"),
|
||||
row.getAsString("body"));
|
||||
@ -65,10 +75,14 @@ public class SQLCipherMigrationHelper {
|
||||
row.put("body", plaintext.second);
|
||||
row.put("type", plaintext.first);
|
||||
|
||||
if (listener != null && (progress.first % 1000 == 0)) {
|
||||
listener.setProgress(getTotalProgress(0, progress.first, progress.second), total);
|
||||
}
|
||||
|
||||
return row;
|
||||
});
|
||||
|
||||
copyTable("mms", legacyDb, modernDb, (row) -> {
|
||||
copyTable("mms", legacyDb, modernDb, (row, progress) -> {
|
||||
Pair<Long, String> plaintext = getPlaintextBody(legacyCipher, legacyAsymmetricCipher,
|
||||
row.getAsLong("msg_box"),
|
||||
row.getAsString("body"));
|
||||
@ -76,10 +90,14 @@ public class SQLCipherMigrationHelper {
|
||||
row.put("body", plaintext.second);
|
||||
row.put("msg_box", plaintext.first);
|
||||
|
||||
if (listener != null && (progress.first % 1000 == 0)) {
|
||||
listener.setProgress(getTotalProgress(1000, progress.first, progress.second), total);
|
||||
}
|
||||
|
||||
return row;
|
||||
});
|
||||
|
||||
copyTable("part", legacyDb, modernDb, (row) -> {
|
||||
copyTable("part", legacyDb, modernDb, (row, progress) -> {
|
||||
String fileName = row.getAsString("file_name");
|
||||
String mediaKey = row.getAsString("cd");
|
||||
|
||||
@ -107,11 +125,14 @@ public class SQLCipherMigrationHelper {
|
||||
Log.w(TAG, e);
|
||||
}
|
||||
|
||||
if (listener != null && (progress.first % 1000 == 0)) {
|
||||
listener.setProgress(getTotalProgress(2000, progress.first, progress.second), total);
|
||||
}
|
||||
|
||||
return row;
|
||||
});
|
||||
|
||||
copyTable("thread", legacyDb, modernDb, (row) -> {
|
||||
copyTable("thread", legacyDb, modernDb, (row, progress) -> {
|
||||
Pair<Long, String> plaintext = getPlaintextBody(legacyCipher, legacyAsymmetricCipher,
|
||||
row.getAsLong("snippet_type"),
|
||||
row.getAsString("snippet"));
|
||||
@ -119,11 +140,15 @@ public class SQLCipherMigrationHelper {
|
||||
row.put("snippet", plaintext.second);
|
||||
row.put("snippet_type", plaintext.first);
|
||||
|
||||
if (listener != null && (progress.first % 1000 == 0)) {
|
||||
listener.setProgress(getTotalProgress(3000, progress.first, progress.second), total);
|
||||
}
|
||||
|
||||
return row;
|
||||
});
|
||||
|
||||
|
||||
copyTable("drafts", legacyDb, modernDb, (row) -> {
|
||||
copyTable("drafts", legacyDb, modernDb, (row, progress) -> {
|
||||
String draftType = row.getAsString("type");
|
||||
String draft = row.getAsString("value");
|
||||
|
||||
@ -134,24 +159,31 @@ public class SQLCipherMigrationHelper {
|
||||
Log.w(TAG, e);
|
||||
}
|
||||
|
||||
if (listener != null && (progress.first % 1000 == 0)) {
|
||||
listener.setProgress(getTotalProgress(4000, progress.first, progress.second), total);
|
||||
}
|
||||
|
||||
return row;
|
||||
});
|
||||
|
||||
AttachmentSecretProvider.getInstance(context).setClassicKey(context, masterSecret.getEncryptionKey().getEncoded(), masterSecret.getMacKey().getEncoded());
|
||||
TextSecurePreferences.setNeedsSqlCipherMigration(context, false);
|
||||
modernDb.setTransactionSuccessful();
|
||||
} finally {
|
||||
modernDb.endTransaction();
|
||||
GenericForegroundService.stopForegroundTask(context);
|
||||
}
|
||||
|
||||
AttachmentSecretProvider.getInstance(context).setClassicKey(context, masterSecret.getEncryptionKey().getEncoded(), masterSecret.getMacKey().getEncoded());
|
||||
}
|
||||
|
||||
private static void copyTable(@NonNull String tableName,
|
||||
@NonNull android.database.sqlite.SQLiteDatabase legacyDb,
|
||||
@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)) {
|
||||
int count = (cursor != null) ? cursor.getCount() : 0;
|
||||
int progress = 1;
|
||||
|
||||
while (cursor != null && cursor.moveToNext()) {
|
||||
ContentValues row = new ContentValues();
|
||||
|
||||
@ -167,7 +199,7 @@ public class SQLCipherMigrationHelper {
|
||||
}
|
||||
|
||||
if (transformer != null) {
|
||||
row = transformer.apply(row);
|
||||
row = transformer.apply(row, new Pair<>(progress++, count));
|
||||
}
|
||||
|
||||
modernDb.insert(tableName, null, row);
|
||||
@ -194,4 +226,9 @@ public class SQLCipherMigrationHelper {
|
||||
|
||||
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);
|
||||
android.database.sqlite.SQLiteDatabase legacyDb = legacyHelper.getWritableDatabase();
|
||||
|
||||
SQLCipherMigrationHelper.migratePlaintext(legacyDb, db);
|
||||
SQLCipherMigrationHelper.migratePlaintext(context, legacyDb, db);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -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…
Reference in New Issue
Block a user