Remove legacy ClassicOpenHelper.

This commit is contained in:
Greyson Parrelli
2026-04-10 05:13:37 -04:00
committed by Cody Henthorne
parent ebccc6db30
commit 2b67b1c44f
15 changed files with 3 additions and 2717 deletions

View File

@@ -26454,61 +26454,6 @@
column="7"/>
</issue>
<issue
id="Recycle"
message="This `Cursor` should be freed up after use with `#close()`"
errorLine1=" Cursor mmsCursor = db.query(&quot;mms&quot;, new String[] {&quot;_id&quot;},"
errorLine2=" ~~~~~">
<location
file="src/main/java/org/thoughtcrime/securesms/database/helpers/ClassicOpenHelper.java"
line="298"
column="38"/>
</issue>
<issue
id="Recycle"
message="This `Cursor` should be freed up after use with `#close()`"
errorLine1=" Cursor partCursor = db.query(&quot;part&quot;, new String[] {&quot;_id&quot;, &quot;ct&quot;, &quot;_data&quot;, &quot;encrypted&quot;},"
errorLine2=" ~~~~~">
<location
file="src/main/java/org/thoughtcrime/securesms/database/helpers/ClassicOpenHelper.java"
line="310"
column="32"/>
</issue>
<issue
id="Recycle"
message="This `Cursor` should be freed up after use with `#close()`"
errorLine1=" Cursor threadCursor = db.query(&quot;thread&quot;, new String[] {&quot;_id&quot;}, null, null, null, null, null);"
errorLine2=" ~~~~~">
<location
file="src/main/java/org/thoughtcrime/securesms/database/helpers/ClassicOpenHelper.java"
line="708"
column="32"/>
</issue>
<issue
id="Recycle"
message="This `Cursor` should be freed up after use with `#close()`"
errorLine1=" Cursor cursor = db.rawQuery(&quot;SELECT DISTINCT date AS date_received, status, &quot; +"
errorLine2=" ~~~~~~~~">
<location
file="src/main/java/org/thoughtcrime/securesms/database/helpers/ClassicOpenHelper.java"
line="713"
column="28"/>
</issue>
<issue
id="Recycle"
message="This `Cursor` should be freed up after use with `#close()`"
errorLine1=" cursor = db.query(&quot;mms&quot;, new String[] {&quot;_id&quot;, &quot;network_failures&quot;}, &quot;network_failures IS NOT NULL&quot;, null, null, null, null);"
errorLine2=" ~~~~~">
<location
file="src/main/java/org/thoughtcrime/securesms/database/helpers/ClassicOpenHelper.java"
line="1037"
column="19"/>
</issue>
<issue
id="ObsoleteSdkInt"
message="Unnecessary; SDK_INT is always >= 21"

View File

@@ -10,18 +10,8 @@ import org.signal.core.util.logging.Log
import org.signal.core.util.withinTransaction
import org.thoughtcrime.securesms.crypto.AttachmentSecret
import org.thoughtcrime.securesms.crypto.DatabaseSecret
import org.thoughtcrime.securesms.crypto.MasterSecret
import org.thoughtcrime.securesms.database.helpers.ClassicOpenHelper
import org.thoughtcrime.securesms.database.helpers.PreKeyMigrationHelper
import org.thoughtcrime.securesms.database.helpers.SQLCipherMigrationHelper
import org.thoughtcrime.securesms.database.helpers.SessionStoreMigrationHelper
import org.thoughtcrime.securesms.database.helpers.SignalDatabaseMigrations
import org.thoughtcrime.securesms.database.model.AvatarPickerDatabase
import org.thoughtcrime.securesms.jobs.PreKeysSyncJob
import org.thoughtcrime.securesms.migrations.LegacyMigrationJob
import org.thoughtcrime.securesms.migrations.LegacyMigrationJob.DatabaseUpgradeListener
import org.thoughtcrime.securesms.service.KeyCachingService
import org.thoughtcrime.securesms.util.TextSecurePreferences
import java.io.File
import org.thoughtcrime.securesms.database.SQLiteDatabase as SignalSQLiteDatabase
@@ -95,19 +85,6 @@ open class SignalDatabase(private val context: Application, databaseSecret: Data
// Requires FTS5
executeStatements(signalDb, SearchTable.CREATE_TABLE)
executeStatements(signalDb, SearchTable.CREATE_TRIGGERS)
if (context.getDatabasePath(ClassicOpenHelper.NAME).exists()) {
val legacyHelper = ClassicOpenHelper(context)
val legacyDb = legacyHelper.writableDatabase
SQLCipherMigrationHelper.migratePlaintext(context, legacyDb, db)
val masterSecret = KeyCachingService.getMasterSecret(context)
if (masterSecret != null) SQLCipherMigrationHelper.migrateCiphertext(context, masterSecret, legacyDb, db, null) else TextSecurePreferences.setNeedsSqlCipherMigration(context, true)
if (!PreKeyMigrationHelper.migratePreKeys(context, db)) {
PreKeysSyncJob.enqueue()
}
SessionStoreMigrationHelper.migrateSessions(context, db)
PreKeyMigrationHelper.cleanUpPreKeys(context)
}
}
@VisibleForTesting
@@ -348,36 +325,6 @@ open class SignalDatabase(private val context: Application, databaseSecret: Data
instance!!.signalWritableDatabase
}
@Deprecated("Only used for a legacy migration.")
@JvmStatic
fun onApplicationLevelUpgrade(
context: Context,
masterSecret: MasterSecret,
fromVersion: Int,
listener: DatabaseUpgradeListener?
) {
instance!!.signalWritableDatabase
var legacyOpenHelper: ClassicOpenHelper? = null
if (fromVersion < LegacyMigrationJob.ASYMMETRIC_MASTER_SECRET_FIX_VERSION) {
legacyOpenHelper = ClassicOpenHelper(context)
legacyOpenHelper.onApplicationLevelUpgrade(context, masterSecret, fromVersion, listener)
}
if (fromVersion < LegacyMigrationJob.SQLCIPHER && TextSecurePreferences.getNeedsSqlCipherMigration(context)) {
if (legacyOpenHelper == null) {
legacyOpenHelper = ClassicOpenHelper(context)
}
SQLCipherMigrationHelper.migrateCiphertext(
context,
masterSecret,
legacyOpenHelper.writableDatabase,
instance!!.rawWritableDatabase,
listener
)
}
}
@JvmStatic
fun <T> runInTransaction(block: (SignalSQLiteDatabase) -> T): T {
return instance!!.signalWritableDatabase.withinTransaction {

View File

@@ -1,227 +0,0 @@
package org.thoughtcrime.securesms.database.helpers;
import android.content.ContentValues;
import android.content.Context;
import androidx.annotation.NonNull;
import com.fasterxml.jackson.annotation.JsonProperty;
import net.zetetic.database.sqlcipher.SQLiteDatabase;
import org.signal.core.util.Conversions;
import org.signal.core.util.logging.Log;
import org.signal.libsignal.protocol.InvalidKeyException;
import org.signal.libsignal.protocol.InvalidMessageException;
import org.signal.libsignal.protocol.state.PreKeyRecord;
import org.signal.libsignal.protocol.state.SignedPreKeyRecord;
import org.thoughtcrime.securesms.database.OneTimePreKeyTable;
import org.thoughtcrime.securesms.database.SignedPreKeyTable;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.signal.core.util.Base64;
import org.thoughtcrime.securesms.util.JsonUtils;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
public final class PreKeyMigrationHelper {
private static final String PREKEY_DIRECTORY = "prekeys";
private static final String SIGNED_PREKEY_DIRECTORY = "signed_prekeys";
private static final int PLAINTEXT_VERSION = 2;
private static final int CURRENT_VERSION_MARKER = 2;
private static final String TAG = Log.tag(PreKeyMigrationHelper.class);
public static boolean migratePreKeys(Context context, SQLiteDatabase database) {
File[] preKeyFiles = getPreKeyDirectory(context).listFiles();
boolean clean = true;
if (preKeyFiles != null) {
for (File preKeyFile : preKeyFiles) {
if (!"index.dat".equals(preKeyFile.getName())) {
try {
PreKeyRecord preKey = new PreKeyRecord(loadSerializedRecord(preKeyFile));
ContentValues contentValues = new ContentValues();
contentValues.put(OneTimePreKeyTable.KEY_ID, preKey.getId());
contentValues.put(OneTimePreKeyTable.PUBLIC_KEY, Base64.encodeWithPadding(preKey.getKeyPair().getPublicKey().serialize()));
contentValues.put(OneTimePreKeyTable.PRIVATE_KEY, Base64.encodeWithPadding(preKey.getKeyPair().getPrivateKey().serialize()));
database.insert(OneTimePreKeyTable.TABLE_NAME, null, contentValues);
Log.i(TAG, "Migrated one-time prekey: " + preKey.getId());
} catch (IOException | InvalidMessageException | InvalidKeyException e) {
Log.w(TAG, e);
clean = false;
}
}
}
}
File[] signedPreKeyFiles = getSignedPreKeyDirectory(context).listFiles();
if (signedPreKeyFiles != null) {
for (File signedPreKeyFile : signedPreKeyFiles) {
if (!"index.dat".equals(signedPreKeyFile.getName())) {
try {
SignedPreKeyRecord signedPreKey = new SignedPreKeyRecord(loadSerializedRecord(signedPreKeyFile));
ContentValues contentValues = new ContentValues();
contentValues.put(SignedPreKeyTable.KEY_ID, signedPreKey.getId());
contentValues.put(SignedPreKeyTable.PUBLIC_KEY, Base64.encodeWithPadding(signedPreKey.getKeyPair().getPublicKey().serialize()));
contentValues.put(SignedPreKeyTable.PRIVATE_KEY, Base64.encodeWithPadding(signedPreKey.getKeyPair().getPrivateKey().serialize()));
contentValues.put(SignedPreKeyTable.SIGNATURE, Base64.encodeWithPadding(signedPreKey.getSignature()));
contentValues.put(SignedPreKeyTable.TIMESTAMP, signedPreKey.getTimestamp());
database.insert(SignedPreKeyTable.TABLE_NAME, null, contentValues);
Log.i(TAG, "Migrated signed prekey: " + signedPreKey.getId());
} catch (IOException | InvalidMessageException | InvalidKeyException e) {
Log.w(TAG, e);
clean = false;
}
}
}
}
File oneTimePreKeyIndex = new File(getPreKeyDirectory(context), PreKeyIndex.FILE_NAME);
File signedPreKeyIndex = new File(getSignedPreKeyDirectory(context), SignedPreKeyIndex.FILE_NAME);
if (oneTimePreKeyIndex.exists()) {
try {
InputStreamReader reader = new InputStreamReader(new FileInputStream(oneTimePreKeyIndex));
PreKeyIndex index = JsonUtils.fromJson(reader, PreKeyIndex.class);
reader.close();
Log.i(TAG, "Setting next prekey id: " + index.nextPreKeyId);
SignalStore.account().aciPreKeys().setNextEcOneTimePreKeyId(index.nextPreKeyId);
} catch (IOException e) {
Log.w(TAG, e);
}
}
if (signedPreKeyIndex.exists()) {
try {
InputStreamReader reader = new InputStreamReader(new FileInputStream(signedPreKeyIndex));
SignedPreKeyIndex index = JsonUtils.fromJson(reader, SignedPreKeyIndex.class);
reader.close();
Log.i(TAG, "Setting next signed prekey id: " + index.nextSignedPreKeyId);
Log.i(TAG, "Setting active signed prekey id: " + index.activeSignedPreKeyId);
SignalStore.account().aciPreKeys().setNextSignedPreKeyId(index.nextSignedPreKeyId);
SignalStore.account().aciPreKeys().setActiveSignedPreKeyId(index.activeSignedPreKeyId);
} catch (IOException e) {
Log.w(TAG, e);
}
}
return clean;
}
public static void cleanUpPreKeys(@NonNull Context context) {
File preKeyDirectory = getPreKeyDirectory(context);
File[] preKeyFiles = preKeyDirectory.listFiles();
if (preKeyFiles != null) {
for (File preKeyFile : preKeyFiles) {
Log.i(TAG, "Deleting: " + preKeyFile.getAbsolutePath());
preKeyFile.delete();
}
Log.i(TAG, "Deleting: " + preKeyDirectory.getAbsolutePath());
preKeyDirectory.delete();
}
File signedPreKeyDirectory = getSignedPreKeyDirectory(context);
File[] signedPreKeyFiles = signedPreKeyDirectory.listFiles();
if (signedPreKeyFiles != null) {
for (File signedPreKeyFile : signedPreKeyFiles) {
Log.i(TAG, "Deleting: " + signedPreKeyFile.getAbsolutePath());
signedPreKeyFile.delete();
}
Log.i(TAG, "Deleting: " + signedPreKeyDirectory.getAbsolutePath());
signedPreKeyDirectory.delete();
}
}
private static byte[] loadSerializedRecord(File recordFile)
throws IOException, InvalidMessageException
{
FileInputStream fin = new FileInputStream(recordFile);
int recordVersion = readInteger(fin);
if (recordVersion > CURRENT_VERSION_MARKER) {
throw new IOException("Invalid version: " + recordVersion);
}
byte[] serializedRecord = readBlob(fin);
if (recordVersion < PLAINTEXT_VERSION) {
throw new IOException("Migration didn't happen! " + recordFile.getAbsolutePath() + ", " + recordVersion);
}
fin.close();
return serializedRecord;
}
private static File getPreKeyDirectory(Context context) {
return getRecordsDirectory(context, PREKEY_DIRECTORY);
}
private static File getSignedPreKeyDirectory(Context context) {
return getRecordsDirectory(context, SIGNED_PREKEY_DIRECTORY);
}
private static File getRecordsDirectory(Context context, String directoryName) {
File directory = new File(context.getFilesDir(), directoryName);
if (!directory.exists()) {
if (!directory.mkdirs()) {
Log.w(TAG, "PreKey directory creation failed!");
}
}
return directory;
}
private static byte[] readBlob(FileInputStream in) throws IOException {
int length = readInteger(in);
byte[] blobBytes = new byte[length];
in.read(blobBytes, 0, blobBytes.length);
return blobBytes;
}
private static int readInteger(FileInputStream in) throws IOException {
byte[] integer = new byte[4];
in.read(integer, 0, integer.length);
return Conversions.byteArrayToInt(integer);
}
private static class PreKeyIndex {
static final String FILE_NAME = "index.dat";
@JsonProperty
private int nextPreKeyId;
public PreKeyIndex() {}
}
private static class SignedPreKeyIndex {
static final String FILE_NAME = "index.dat";
@JsonProperty
private int nextSignedPreKeyId;
@JsonProperty
private int activeSignedPreKeyId = -1;
public SignedPreKeyIndex() {}
}
}

View File

@@ -1,91 +0,0 @@
package org.thoughtcrime.securesms.database.helpers;
import android.database.Cursor;
import android.text.TextUtils;
import androidx.annotation.NonNull;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.database.SQLiteDatabase;
import org.thoughtcrime.securesms.util.DelimiterUtil;
import java.util.HashSet;
import java.util.Set;
import java.util.regex.Pattern;
public class RecipientIdCleanupHelper {
private static final String TAG = Log.tag(RecipientIdCleanupHelper.class);
public static void execute(@NonNull SQLiteDatabase db) {
Log.i(TAG, "Beginning migration.");
long startTime = System.currentTimeMillis();
Pattern pattern = Pattern.compile("^[0-9\\-+]+$");
Set<String> deletionCandidates = new HashSet<>();
try (Cursor cursor = db.query("recipient", new String[] { "_id", "phone" }, "group_id IS NULL AND email IS NULL", null, null, null, null)) {
while (cursor != null && cursor.moveToNext()) {
String id = cursor.getString(cursor.getColumnIndexOrThrow("_id"));
String phone = cursor.getString(cursor.getColumnIndexOrThrow("phone"));
if (TextUtils.isEmpty(phone) || !pattern.matcher(phone).matches()) {
Log.i(TAG, "Recipient ID " + id + " has non-numeric characters and can potentially be deleted.");
if (!isIdUsed(db, "identities", "address", id) &&
!isIdUsed(db, "sessions", "address", id) &&
!isIdUsed(db, "thread", "recipient_ids", id) &&
!isIdUsed(db, "sms", "address", id) &&
!isIdUsed(db, "mms", "address", id) &&
!isIdUsed(db, "mms", "quote_author", id) &&
!isIdUsed(db, "group_receipts", "address", id) &&
!isIdUsed(db, "groups", "recipient_id", id))
{
Log.i(TAG, "Determined ID " + id + " is unused in non-group membership. Marking for potential deletion.");
deletionCandidates.add(id);
} else {
Log.i(TAG, "Found that ID " + id + " is actually used in another table.");
}
}
}
}
Set<String> deletions = findUnusedInGroupMembership(db, deletionCandidates);
for (String deletion : deletions) {
Log.i(TAG, "Deleting ID " + deletion);
db.delete("recipient", "_id = ?", new String[] { String.valueOf(deletion) });
}
Log.i(TAG, "Migration took " + (System.currentTimeMillis() - startTime) + " ms.");
}
private static boolean isIdUsed(@NonNull SQLiteDatabase db, @NonNull String tableName, @NonNull String columnName, String id) {
try (Cursor cursor = db.query(tableName, new String[] { columnName }, columnName + " = ?", new String[] { id }, null, null, null, "1")) {
boolean used = cursor != null && cursor.moveToFirst();
if (used) {
Log.i(TAG, "Recipient " + id + " was used in (" + tableName + ", " + columnName + ")");
}
return used;
}
}
private static Set<String> findUnusedInGroupMembership(@NonNull SQLiteDatabase db, Set<String> candidates) {
Set<String> unused = new HashSet<>(candidates);
try (Cursor cursor = db.rawQuery("SELECT members FROM groups", null)) {
while (cursor != null && cursor.moveToNext()) {
String serializedMembers = cursor.getString(cursor.getColumnIndexOrThrow("members"));
String[] members = DelimiterUtil.split(serializedMembers, ',');
for (String member : members) {
if (unused.remove(member)) {
Log.i(TAG, "Recipient " + member + " was found in a group membership list.");
}
}
}
}
return unused;
}
}

View File

@@ -1,282 +0,0 @@
package org.thoughtcrime.securesms.database.helpers;
import android.content.ContentValues;
import android.database.Cursor;
import android.text.TextUtils;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import net.zetetic.database.sqlcipher.SQLiteDatabase;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.groups.GroupId;
import org.thoughtcrime.securesms.phonenumbers.NumberUtil;
import org.thoughtcrime.securesms.util.DelimiterUtil;
import org.signal.core.util.Util;
import java.util.HashSet;
import java.util.Set;
public class RecipientIdMigrationHelper {
private static final String TAG = Log.tag(RecipientIdMigrationHelper.class);
public static void execute(SQLiteDatabase db) {
Log.i(TAG, "Starting the recipient ID migration.");
long insertStart = System.currentTimeMillis();
Log.i(TAG, "Starting inserts for missing recipients.");
db.execSQL(buildInsertMissingRecipientStatement("identities", "address"));
db.execSQL(buildInsertMissingRecipientStatement("sessions", "address"));
db.execSQL(buildInsertMissingRecipientStatement("thread", "recipient_ids"));
db.execSQL(buildInsertMissingRecipientStatement("sms", "address"));
db.execSQL(buildInsertMissingRecipientStatement("mms", "address"));
db.execSQL(buildInsertMissingRecipientStatement("mms", "quote_author"));
db.execSQL(buildInsertMissingRecipientStatement("group_receipts", "address"));
db.execSQL(buildInsertMissingRecipientStatement("groups", "group_id"));
Log.i(TAG, "Finished inserts for missing recipients in " + (System.currentTimeMillis() - insertStart) + " ms.");
long updateMissingStart = System.currentTimeMillis();
Log.i(TAG, "Starting updates for invalid or missing addresses.");
db.execSQL(buildMissingAddressUpdateStatement("sms", "address"));
db.execSQL(buildMissingAddressUpdateStatement("mms", "address"));
db.execSQL(buildMissingAddressUpdateStatement("mms", "quote_author"));
Log.i(TAG, "Finished updates for invalid or missing addresses in " + (System.currentTimeMillis() - updateMissingStart) + " ms.");
db.execSQL("ALTER TABLE groups ADD COLUMN recipient_id INTEGER DEFAULT 0");
long updateStart = System.currentTimeMillis();
Log.i(TAG, "Starting recipient ID updates.");
db.execSQL(buildUpdateAddressToRecipientIdStatement("identities", "address"));
db.execSQL(buildUpdateAddressToRecipientIdStatement("sessions", "address"));
db.execSQL(buildUpdateAddressToRecipientIdStatement("thread", "recipient_ids"));
db.execSQL(buildUpdateAddressToRecipientIdStatement("sms", "address"));
db.execSQL(buildUpdateAddressToRecipientIdStatement("mms", "address"));
db.execSQL(buildUpdateAddressToRecipientIdStatement("mms", "quote_author"));
db.execSQL(buildUpdateAddressToRecipientIdStatement("group_receipts", "address"));
db.execSQL("UPDATE groups SET recipient_id = (SELECT _id FROM recipient_preferences WHERE recipient_preferences.recipient_ids = groups.group_id)");
Log.i(TAG, "Finished recipient ID updates in " + (System.currentTimeMillis() - updateStart) + " ms.");
// NOTE: Because there's an open cursor on the same table, inserts and updates aren't visible
// until afterwards, which is why this group stuff is split into multiple loops
long findGroupStart = System.currentTimeMillis();
Log.i(TAG, "Starting to find missing group recipients.");
Set<String> missingGroupMembers = new HashSet<>();
try (Cursor cursor = db.rawQuery("SELECT members FROM groups", null)) {
while (cursor != null && cursor.moveToNext()) {
String serializedMembers = cursor.getString(cursor.getColumnIndexOrThrow("members"));
String[] members = DelimiterUtil.split(serializedMembers, ',');
for (String rawMember : members) {
String member = DelimiterUtil.unescape(rawMember, ',');
if (!TextUtils.isEmpty(member) && !recipientExists(db, member)) {
missingGroupMembers.add(member);
}
}
}
}
Log.i(TAG, "Finished finding " + missingGroupMembers.size() + " missing group recipients in " + (System.currentTimeMillis() - findGroupStart) + " ms.");
long insertGroupStart = System.currentTimeMillis();
Log.i(TAG, "Starting the insert of missing group recipients.");
for (String member : missingGroupMembers) {
ContentValues values = new ContentValues();
values.put("recipient_ids", member);
db.insert("recipient_preferences", null, values);
}
Log.i(TAG, "Finished inserting missing group recipients in " + (System.currentTimeMillis() - insertGroupStart) + " ms.");
long updateGroupStart = System.currentTimeMillis();
Log.i(TAG, "Starting group recipient ID updates.");
try (Cursor cursor = db.rawQuery("SELECT _id, members FROM groups", null)) {
while (cursor != null && cursor.moveToNext()) {
long groupId = cursor.getLong(cursor.getColumnIndexOrThrow("_id"));
String serializedMembers = cursor.getString(cursor.getColumnIndexOrThrow("members"));
String[] members = DelimiterUtil.split(serializedMembers, ',');
long[] memberIds = new long[members.length];
for (int i = 0; i < members.length; i++) {
String member = DelimiterUtil.unescape(members[i], ',');
memberIds[i] = requireRecipientId(db, member);
}
String serializedMemberIds = Util.join(memberIds, ",");
db.execSQL("UPDATE groups SET members = ? WHERE _id = ?", new String[]{ serializedMemberIds, String.valueOf(groupId) });
}
}
db.execSQL("CREATE UNIQUE INDEX IF NOT EXISTS group_recipient_id_index ON groups (recipient_id)");
Log.i(TAG, "Finished group recipient ID updates in " + (System.currentTimeMillis() - updateGroupStart) + " ms.");
long tableCopyStart = System.currentTimeMillis();
Log.i(TAG, "Starting to copy the recipient table.");
db.execSQL("CREATE TABLE recipient (_id INTEGER PRIMARY KEY AUTOINCREMENT, " +
"uuid TEXT UNIQUE DEFAULT NULL, " +
"phone TEXT UNIQUE DEFAULT NULL, " +
"email TEXT UNIQUE DEFAULT NULL, " +
"group_id TEXT UNIQUE DEFAULT NULL, " +
"blocked INTEGER DEFAULT 0, " +
"message_ringtone TEXT DEFAULT NULL, " +
"message_vibrate INTEGER DEFAULT 0, " +
"call_ringtone TEXT DEFAULT NULL, " +
"call_vibrate INTEGER DEFAULT 0, " +
"notification_channel TEXT DEFAULT NULL, " +
"mute_until INTEGER DEFAULT 0, " +
"color TEXT DEFAULT NULL, " +
"seen_invite_reminder INTEGER DEFAULT 0, " +
"default_subscription_id INTEGER DEFAULT -1, " +
"message_expiration_time INTEGER DEFAULT 0, " +
"registered INTEGER DEFAULT 0, " +
"system_display_name TEXT DEFAULT NULL, " +
"system_photo_uri TEXT DEFAULT NULL, " +
"system_phone_label TEXT DEFAULT NULL, " +
"system_contact_uri TEXT DEFAULT NULL, " +
"profile_key TEXT DEFAULT NULL, " +
"signal_profile_name TEXT DEFAULT NULL, " +
"signal_profile_avatar TEXT DEFAULT NULL, " +
"profile_sharing INTEGER DEFAULT 0, " +
"unidentified_access_mode INTEGER DEFAULT 0, " +
"force_sms_selection INTEGER DEFAULT 0)");
try (Cursor cursor = db.query("recipient_preferences", null, null, null, null, null, null)) {
while (cursor != null && cursor.moveToNext()) {
String address = cursor.getString(cursor.getColumnIndexOrThrow("recipient_ids"));
boolean isGroup = GroupId.isEncodedGroup(address);
boolean isEmail = !isGroup && NumberUtil.isValidEmail(address);
boolean isPhone = !isGroup && !isEmail;
ContentValues values = new ContentValues();
values.put("_id", cursor.getLong(cursor.getColumnIndexOrThrow("_id")));
values.put("uuid", (String) null);
values.put("phone", isPhone ? address : null);
values.put("email", isEmail ? address : null);
values.put("group_id", isGroup ? address : null);
values.put("blocked", cursor.getInt(cursor.getColumnIndexOrThrow("block")));
values.put("message_ringtone", cursor.getString(cursor.getColumnIndexOrThrow("notification")));
values.put("message_vibrate", cursor.getString(cursor.getColumnIndexOrThrow("vibrate")));
values.put("call_ringtone", cursor.getString(cursor.getColumnIndexOrThrow("call_ringtone")));
values.put("call_vibrate", cursor.getString(cursor.getColumnIndexOrThrow("call_vibrate")));
values.put("notification_channel", cursor.getString(cursor.getColumnIndexOrThrow("notification_channel")));
values.put("mute_until", cursor.getLong(cursor.getColumnIndexOrThrow("mute_until")));
values.put("color", cursor.getString(cursor.getColumnIndexOrThrow("color")));
values.put("seen_invite_reminder", cursor.getInt(cursor.getColumnIndexOrThrow("seen_invite_reminder")));
values.put("default_subscription_id", cursor.getInt(cursor.getColumnIndexOrThrow("default_subscription_id")));
values.put("message_expiration_time", cursor.getInt(cursor.getColumnIndexOrThrow("expire_messages")));
values.put("registered", cursor.getInt(cursor.getColumnIndexOrThrow("registered")));
values.put("system_display_name", cursor.getString(cursor.getColumnIndexOrThrow("system_display_name")));
values.put("system_photo_uri", cursor.getString(cursor.getColumnIndexOrThrow("system_contact_photo")));
values.put("system_phone_label", cursor.getString(cursor.getColumnIndexOrThrow("system_phone_label")));
values.put("system_contact_uri", cursor.getString(cursor.getColumnIndexOrThrow("system_contact_uri")));
values.put("profile_key", cursor.getString(cursor.getColumnIndexOrThrow("profile_key")));
values.put("signal_profile_name", cursor.getString(cursor.getColumnIndexOrThrow("signal_profile_name")));
values.put("signal_profile_avatar", cursor.getString(cursor.getColumnIndexOrThrow("signal_profile_avatar")));
values.put("profile_sharing", cursor.getInt(cursor.getColumnIndexOrThrow("profile_sharing_approval")));
values.put("unidentified_access_mode", cursor.getInt(cursor.getColumnIndexOrThrow("unidentified_access_mode")));
values.put("force_sms_selection", cursor.getInt(cursor.getColumnIndexOrThrow("force_sms_selection")));
db.insert("recipient", null, values);
}
}
db.execSQL("DROP TABLE recipient_preferences");
Log.i(TAG, "Finished copying the recipient table in " + (System.currentTimeMillis() - tableCopyStart) + " ms.");
long sanityCheckStart = System.currentTimeMillis();
Log.i(TAG, "Starting DB integrity sanity checks.");
assertEmptyQuery(db, "identities", buildSanityCheckQuery("identities", "address"));
assertEmptyQuery(db, "sessions", buildSanityCheckQuery("sessions", "address"));
assertEmptyQuery(db, "groups", buildSanityCheckQuery("groups", "recipient_id"));
assertEmptyQuery(db, "thread", buildSanityCheckQuery("thread", "recipient_ids"));
assertEmptyQuery(db, "sms", buildSanityCheckQuery("sms", "address"));
assertEmptyQuery(db, "mms -- address", buildSanityCheckQuery("mms", "address"));
assertEmptyQuery(db, "mms -- quote_author", buildSanityCheckQuery("mms", "quote_author"));
assertEmptyQuery(db, "group_receipts", buildSanityCheckQuery("group_receipts", "address"));
Log.i(TAG, "Finished DB integrity sanity checks in " + (System.currentTimeMillis() - sanityCheckStart) + " ms.");
Log.i(TAG, "Finished recipient ID migration in " + (System.currentTimeMillis() - insertStart) + " ms.");
}
private static String buildUpdateAddressToRecipientIdStatement(@NonNull String table, @NonNull String addressColumn) {
return "UPDATE " + table + " SET " + addressColumn + "=(SELECT _id " +
"FROM recipient_preferences " +
"WHERE recipient_preferences.recipient_ids = " + table + "." + addressColumn + ")";
}
private static String buildInsertMissingRecipientStatement(@NonNull String table, @NonNull String addressColumn) {
return "INSERT INTO recipient_preferences(recipient_ids) SELECT DISTINCT " + addressColumn + " " +
"FROM " + table + " " +
"WHERE " + addressColumn + " != '' AND " +
addressColumn + " != 'insert-address-column' AND " +
addressColumn + " NOT NULL AND " +
addressColumn + " NOT IN (SELECT recipient_ids FROM recipient_preferences)";
}
private static String buildMissingAddressUpdateStatement(@NonNull String table, @NonNull String addressColumn) {
return "UPDATE " + table + " SET " + addressColumn + " = -1 " +
"WHERE " + addressColumn + " = '' OR " +
addressColumn + " IS NULL OR " +
addressColumn + " = 'insert-address-token'";
}
private static boolean recipientExists(@NonNull SQLiteDatabase db, @NonNull String address) {
return getRecipientId(db, address) != null;
}
private static @Nullable Long getRecipientId(@NonNull SQLiteDatabase db, @NonNull String address) {
try (Cursor cursor = db.rawQuery("SELECT _id FROM recipient_preferences WHERE recipient_ids = ?", new String[]{ address })) {
if (cursor != null && cursor.moveToFirst()) {
return cursor.getLong(cursor.getColumnIndexOrThrow("_id"));
} else {
return null;
}
}
}
private static long requireRecipientId(@NonNull SQLiteDatabase db, @NonNull String address) {
Long id = getRecipientId(db, address);
if (id != null) {
return id;
} else {
throw new MissingRecipientError(address);
}
}
private static String buildSanityCheckQuery(@NonNull String table, @NonNull String idColumn) {
return "SELECT " + idColumn + " FROM " + table + " WHERE " + idColumn + " != -1 AND " + idColumn + " NOT IN (SELECT _id FROM recipient)";
}
private static void assertEmptyQuery(@NonNull SQLiteDatabase db, @NonNull String tag, @NonNull String query) {
try (Cursor cursor = db.rawQuery(query, null)) {
if (cursor != null && cursor.moveToFirst()) {
throw new FailedSanityCheckError(tag);
}
}
}
private static final class MissingRecipientError extends AssertionError {
MissingRecipientError(@NonNull String address) {
super("Could not find recipient with address " + address);
}
}
private static final class FailedSanityCheckError extends AssertionError {
FailedSanityCheckError(@NonNull String tableName) {
super("Sanity check failed for tag '" + tableName + "'");
}
}
}

View File

@@ -1,259 +0,0 @@
package org.thoughtcrime.securesms.database.helpers;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.text.TextUtils;
import kotlin.Pair;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.annimon.stream.function.BiFunction;
import net.zetetic.database.sqlcipher.SQLiteDatabase;
import org.signal.core.util.logging.Log;
import org.signal.libsignal.protocol.InvalidMessageException;
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.jobs.UnableToStartException;
import org.thoughtcrime.securesms.migrations.LegacyMigrationJob;
import org.thoughtcrime.securesms.service.GenericForegroundService;
import org.thoughtcrime.securesms.service.NotificationController;
import org.signal.core.util.Base64;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import java.io.IOException;
import java.util.HashSet;
import java.util.Set;
public class SQLCipherMigrationHelper {
private static final String TAG = Log.tag(SQLCipherMigrationHelper.class);
private static final long ENCRYPTION_SYMMETRIC_BIT = 0x80000000;
private static final long ENCRYPTION_ASYMMETRIC_BIT = 0x40000000;
public static void migratePlaintext(@NonNull Context context,
@NonNull android.database.sqlite.SQLiteDatabase legacyDb,
@NonNull SQLiteDatabase modernDb)
{
modernDb.beginTransaction();
try (NotificationController controller = 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);
copyTable("recipient_preferences", legacyDb, modernDb, null);
copyTable("group_receipts", legacyDb, modernDb, null);
modernDb.setTransactionSuccessful();
} catch (UnableToStartException e) {
throw new IllegalStateException(e);
} finally {
modernDb.endTransaction();
}
}
public static void migrateCiphertext(@NonNull Context context,
@NonNull MasterSecret masterSecret,
@NonNull android.database.sqlite.SQLiteDatabase legacyDb,
@NonNull SQLiteDatabase modernDb,
@Nullable LegacyMigrationJob.DatabaseUpgradeListener listener)
{
MasterCipher legacyCipher = new MasterCipher(masterSecret);
AsymmetricMasterCipher legacyAsymmetricCipher = new AsymmetricMasterCipher(MasterSecretUtil.getAsymmetricMasterSecret(context, masterSecret));
modernDb.beginTransaction();
try (NotificationController controller = 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"));
row.put("body", plaintext.getSecond());
row.put("type", plaintext.getFirst());
if (listener != null && (progress.getFirst() % 1000 == 0)) {
listener.setProgress(getTotalProgress(0, progress.getFirst(), progress.getSecond()), total);
}
return row;
});
copyTable("mms", legacyDb, modernDb, (row, progress) -> {
Pair<Long, String> plaintext = getPlaintextBody(legacyCipher, legacyAsymmetricCipher,
row.getAsLong("msg_box"),
row.getAsString("body"));
row.put("body", plaintext.getSecond());
row.put("msg_box", plaintext.getFirst());
if (listener != null && (progress.getFirst() % 1000 == 0)) {
listener.setProgress(getTotalProgress(1000, progress.getFirst(), progress.getSecond()), total);
}
return row;
});
copyTable("part", legacyDb, modernDb, (row, progress) -> {
String fileName = row.getAsString("file_name");
String mediaKey = row.getAsString("cd");
try {
if (!TextUtils.isEmpty(fileName)) {
row.put("file_name", legacyCipher.decryptBody(fileName));
}
} catch (InvalidMessageException e) {
Log.w(TAG, e);
}
try {
if (!TextUtils.isEmpty(mediaKey)) {
byte[] plaintext;
if (mediaKey.startsWith("?ASYNC-")) {
plaintext = legacyAsymmetricCipher.decryptBytes(Base64.decode(mediaKey.substring("?ASYNC-".length())));
} else {
plaintext = legacyCipher.decryptBytes(Base64.decode(mediaKey));
}
row.put("cd", Base64.encodeWithPadding(plaintext));
}
} catch (IOException | InvalidMessageException e) {
Log.w(TAG, e);
}
if (listener != null && (progress.getFirst() % 1000 == 0)) {
listener.setProgress(getTotalProgress(2000, progress.getFirst(), progress.getSecond()), total);
}
return row;
});
copyTable("thread", legacyDb, modernDb, (row, progress) -> {
Long snippetType = row.getAsLong("snippet_type");
if (snippetType == null) snippetType = 0L;
Pair<Long, String> plaintext = getPlaintextBody(legacyCipher, legacyAsymmetricCipher,
snippetType, row.getAsString("snippet"));
row.put("snippet", plaintext.getSecond());
row.put("snippet_type", plaintext.getFirst());
if (listener != null && (progress.getFirst() % 1000 == 0)) {
listener.setProgress(getTotalProgress(3000, progress.getFirst(), progress.getSecond()), total);
}
return row;
});
copyTable("drafts", legacyDb, modernDb, (row, progress) -> {
String draftType = row.getAsString("type");
String draft = row.getAsString("value");
try {
if (!TextUtils.isEmpty(draftType)) row.put("type", legacyCipher.decryptBody(draftType));
if (!TextUtils.isEmpty(draft)) row.put("value", legacyCipher.decryptBody(draft));
} catch (InvalidMessageException e) {
Log.w(TAG, e);
}
if (listener != null && (progress.getFirst() % 1000 == 0)) {
listener.setProgress(getTotalProgress(4000, progress.getFirst(), progress.getSecond()), total);
}
return row;
});
AttachmentSecretProvider.getInstance(context).setClassicKey(context, masterSecret.getEncryptionKey().getEncoded(), masterSecret.getMacKey().getEncoded());
TextSecurePreferences.setNeedsSqlCipherMigration(context, false);
modernDb.setTransactionSuccessful();
} catch (UnableToStartException e) {
throw new IllegalStateException(e);
} finally {
modernDb.endTransaction();
}
}
private static void copyTable(@NonNull String tableName,
@NonNull android.database.sqlite.SQLiteDatabase legacyDb,
@NonNull SQLiteDatabase modernDb,
@Nullable BiFunction<ContentValues, Pair<Integer, Integer>, ContentValues> transformer)
{
Set<String> destinationColumns = getTableColumns(tableName, modernDb);
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();
for (int i=0;i<cursor.getColumnCount();i++) {
String columnName = cursor.getColumnName(i);
if (destinationColumns.contains(columnName)) {
switch (cursor.getType(i)) {
case Cursor.FIELD_TYPE_STRING: row.put(columnName, cursor.getString(i)); break;
case Cursor.FIELD_TYPE_FLOAT: row.put(columnName, cursor.getFloat(i)); break;
case Cursor.FIELD_TYPE_INTEGER: row.put(columnName, cursor.getLong(i)); break;
case Cursor.FIELD_TYPE_BLOB: row.put(columnName, cursor.getBlob(i)); break;
}
}
}
if (transformer != null) {
row = transformer.apply(row, new Pair<>(progress++, count));
}
modernDb.insert(tableName, null, row);
}
}
}
private static Pair<Long, String> getPlaintextBody(@NonNull MasterCipher legacyCipher,
@NonNull AsymmetricMasterCipher legacyAsymmetricCipher,
long type,
@Nullable String body)
{
try {
if (!TextUtils.isEmpty(body)) {
if ((type & ENCRYPTION_SYMMETRIC_BIT) != 0) body = legacyCipher.decryptBody(body);
else if ((type & ENCRYPTION_ASYMMETRIC_BIT) != 0) body = legacyAsymmetricCipher.decryptBody(body);
}
} catch (InvalidMessageException | IOException e) {
Log.w(TAG, e);
}
type &= ~(ENCRYPTION_SYMMETRIC_BIT);
type &= ~(ENCRYPTION_ASYMMETRIC_BIT);
return new Pair<>(type, body);
}
private static Set<String> getTableColumns(String tableName, SQLiteDatabase database) {
Set<String> results = new HashSet<>();
try (Cursor cursor = database.rawQuery("PRAGMA table_info(" + tableName + ")", null)) {
while (cursor != null && cursor.moveToNext()) {
results.add(cursor.getString(1));
}
}
return results;
}
private static int getTotalProgress(int sectionOffset, int sectionProgress, int sectionTotal) {
double percentOfSectionComplete = ((double)sectionProgress) / ((double)sectionTotal);
return sectionOffset + (int)(((double)1000) * percentOfSectionComplete);
}
}

View File

@@ -1,104 +0,0 @@
package org.thoughtcrime.securesms.database.helpers;
import android.content.ContentValues;
import android.content.Context;
import net.zetetic.database.sqlcipher.SQLiteDatabase;
import org.signal.core.util.Conversions;
import org.signal.core.util.logging.Log;
import org.signal.libsignal.protocol.InvalidMessageException;
import org.signal.libsignal.protocol.state.SessionRecord;
import org.thoughtcrime.securesms.database.SessionTable;
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
public final class SessionStoreMigrationHelper {
private static final String TAG = Log.tag(SessionStoreMigrationHelper.class);
private static final String SESSIONS_DIRECTORY_V2 = "sessions-v2";
private static final Object FILE_LOCK = new Object();
private static final int SINGLE_STATE_VERSION = 1;
private static final int ARCHIVE_STATES_VERSION = 2;
private static final int PLAINTEXT_VERSION = 3;
private static final int CURRENT_VERSION = 3;
public static void migrateSessions(Context context, SQLiteDatabase database) {
File directory = new File(context.getFilesDir(), SESSIONS_DIRECTORY_V2);
if (directory.exists()) {
File[] sessionFiles = directory.listFiles();
if (sessionFiles != null) {
for (File sessionFile : sessionFiles) {
try {
String[] parts = sessionFile.getName().split("[.]");
String address = parts[0];
int deviceId;
if (parts.length > 1) deviceId = Integer.parseInt(parts[1]);
else deviceId = SignalServiceAddress.DEFAULT_DEVICE_ID;
FileInputStream in = new FileInputStream(sessionFile);
int versionMarker = readInteger(in);
if (versionMarker > CURRENT_VERSION) {
throw new AssertionError("Unknown version: " + versionMarker + ", " + sessionFile.getAbsolutePath());
}
byte[] serialized = readBlob(in);
in.close();
if (versionMarker < PLAINTEXT_VERSION) {
throw new AssertionError("Not plaintext: " + versionMarker + ", " + sessionFile.getAbsolutePath());
}
SessionRecord sessionRecord;
if (versionMarker == SINGLE_STATE_VERSION) {
Log.i(TAG, "Migrating single state version: " + sessionFile.getAbsolutePath());
sessionRecord = new SessionRecord(serialized);
} else if (versionMarker >= ARCHIVE_STATES_VERSION) {
Log.i(TAG, "Migrating session: " + sessionFile.getAbsolutePath());
sessionRecord = new SessionRecord(serialized);
} else {
throw new AssertionError("Unknown version: " + versionMarker + ", " + sessionFile.getAbsolutePath());
}
ContentValues contentValues = new ContentValues();
contentValues.put(SessionTable.ADDRESS, address);
contentValues.put(SessionTable.DEVICE, deviceId);
contentValues.put(SessionTable.RECORD, sessionRecord.serialize());
database.insert(SessionTable.TABLE_NAME, null, contentValues);
} catch (NumberFormatException | IOException | InvalidMessageException e) {
Log.w(TAG, e);
}
}
}
}
}
private static byte[] readBlob(FileInputStream in) throws IOException {
int length = readInteger(in);
byte[] blobBytes = new byte[length];
in.read(blobBytes, 0, blobBytes.length);
return blobBytes;
}
private static int readInteger(FileInputStream in) throws IOException {
byte[] integer = new byte[4];
in.read(integer, 0, integer.length);
return Conversions.byteArrayToInt(integer);
}
}

View File

@@ -392,8 +392,5 @@ object SignalDatabaseMigrations {
@JvmStatic
fun migratePostTransaction(context: Context, oldVersion: Int) {
if (oldVersion < V149_LegacyMigrations.MIGRATE_PREKEYS_VERSION) {
PreKeyMigrationHelper.cleanUpPreKeys(context)
}
}
}

View File

@@ -31,10 +31,6 @@ import org.thoughtcrime.securesms.conversation.colors.ChatColorsMapper
import org.thoughtcrime.securesms.database.KeyValueDatabase
import org.thoughtcrime.securesms.database.RecipientTable
import org.thoughtcrime.securesms.database.SQLiteDatabase
import org.thoughtcrime.securesms.database.helpers.PreKeyMigrationHelper
import org.thoughtcrime.securesms.database.helpers.RecipientIdCleanupHelper
import org.thoughtcrime.securesms.database.helpers.RecipientIdMigrationHelper
import org.thoughtcrime.securesms.database.helpers.SessionStoreMigrationHelper
import org.thoughtcrime.securesms.database.helpers.SignalDatabaseMigrations
import org.thoughtcrime.securesms.database.model.databaseprotos.ReactionList
import org.thoughtcrime.securesms.groups.GroupId
@@ -221,14 +217,11 @@ object V149_LegacyMigrations : SignalDatabaseMigration {
db.execSQL("CREATE TABLE signed_prekeys (_id INTEGER PRIMARY KEY, key_id INTEGER UNIQUE, public_key TEXT NOT NULL, private_key TEXT NOT NULL, signature TEXT NOT NULL, timestamp INTEGER DEFAULT 0)")
db.execSQL("CREATE TABLE one_time_prekeys (_id INTEGER PRIMARY KEY, key_id INTEGER UNIQUE, public_key TEXT NOT NULL, private_key TEXT NOT NULL)")
if (!PreKeyMigrationHelper.migratePreKeys(context, db.sqlCipherDatabase)) {
PreKeysSyncJob.enqueue()
}
PreKeysSyncJob.enqueue()
}
if (oldVersion < MIGRATE_SESSIONS_VERSION) {
db.execSQL("CREATE TABLE sessions (_id INTEGER PRIMARY KEY, address TEXT NOT NULL, device INTEGER NOT NULL, record BLOB NOT NULL, UNIQUE(address, device) ON CONFLICT REPLACE)")
SessionStoreMigrationHelper.migrateSessions(context, db.sqlCipherDatabase)
}
if (oldVersion < NO_MORE_IMAGE_THUMBNAILS_VERSION) {
@@ -615,7 +608,7 @@ object V149_LegacyMigrations : SignalDatabaseMigration {
}
if (oldVersion < RECIPIENT_IDS) {
RecipientIdMigrationHelper.execute(db.sqlCipherDatabase)
// RecipientIdMigrationHelper was removed -- migration from this old version is no longer supported
}
if (oldVersion < RECIPIENT_SEARCH) {
@@ -638,7 +631,7 @@ object V149_LegacyMigrations : SignalDatabaseMigration {
}
if (oldVersion < RECIPIENT_CLEANUP) {
RecipientIdCleanupHelper.execute(db)
// RecipientIdCleanupHelper was removed -- migration from this old version is no longer supported
}
if (oldVersion < MMS_RECIPIENT_CLEANUP) {

View File

@@ -1,49 +0,0 @@
package org.thoughtcrime.securesms.jobmanager.impl;
import android.app.Application;
import android.app.job.JobInfo;
import androidx.annotation.NonNull;
import org.thoughtcrime.securesms.jobmanager.Constraint;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
public class SqlCipherMigrationConstraint implements Constraint {
public static final String KEY = "SqlCipherMigrationConstraint";
private final Application application;
private SqlCipherMigrationConstraint(@NonNull Application application) {
this.application = application;
}
@Override
public boolean isMet() {
return !TextSecurePreferences.getNeedsSqlCipherMigration(application);
}
@NonNull
@Override
public String getFactoryKey() {
return KEY;
}
@Override
public void applyToJobInfo(@NonNull JobInfo.Builder jobInfoBuilder) {
}
public static final class Factory implements Constraint.Factory<SqlCipherMigrationConstraint> {
private final Application application;
public Factory(@NonNull Application application) {
this.application = application;
}
@Override
public SqlCipherMigrationConstraint create() {
return new SqlCipherMigrationConstraint(application);
}
}
}

View File

@@ -1,33 +0,0 @@
package org.thoughtcrime.securesms.jobmanager.impl;
import androidx.annotation.NonNull;
import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.jobmanager.ConstraintObserver;
public class SqlCipherMigrationConstraintObserver implements ConstraintObserver {
private static final String REASON = Log.tag(SqlCipherMigrationConstraintObserver.class);
private Notifier notifier;
public SqlCipherMigrationConstraintObserver() {
EventBus.getDefault().register(this);
}
@Override
public void register(@NonNull Notifier notifier) {
this.notifier = notifier;
}
@Subscribe(threadMode = ThreadMode.MAIN)
public void onEvent(SqlCipherNeedsMigrationEvent event) {
if (notifier != null) notifier.onConstraintMet(REASON);
}
public static class SqlCipherNeedsMigrationEvent {
}
}

View File

@@ -34,8 +34,6 @@ import org.thoughtcrime.securesms.jobmanager.impl.NotInCallConstraintObserver;
import org.thoughtcrime.securesms.jobmanager.impl.RegisteredConstraint;
import org.thoughtcrime.securesms.jobmanager.impl.RestoreAttachmentConstraint;
import org.thoughtcrime.securesms.jobmanager.impl.RestoreAttachmentConstraintObserver;
import org.thoughtcrime.securesms.jobmanager.impl.SqlCipherMigrationConstraint;
import org.thoughtcrime.securesms.jobmanager.impl.SqlCipherMigrationConstraintObserver;
import org.thoughtcrime.securesms.jobmanager.impl.StickersNotDownloadingConstraint;
import org.thoughtcrime.securesms.jobmanager.impl.WifiConstraint;
import org.thoughtcrime.securesms.jobmanager.migrations.DeprecatedJobMigration;
@@ -454,7 +452,6 @@ public final class JobManagerFactories {
put(RegisteredConstraint.KEY, new RegisteredConstraint.Factory());
put(RestoreAttachmentConstraint.KEY, new RestoreAttachmentConstraint.Factory(application));
put(SealedSenderConstraint.KEY, new SealedSenderConstraint.Factory());
put(SqlCipherMigrationConstraint.KEY, new SqlCipherMigrationConstraint.Factory(application));
put(StickersNotDownloadingConstraint.KEY, new StickersNotDownloadingConstraint.Factory());
put(WifiConstraint.KEY, new WifiConstraint.Factory(application));
}};
@@ -464,7 +461,6 @@ public final class JobManagerFactories {
return Arrays.asList(CellServiceConstraintObserver.getInstance(application),
new ChargingAndBatteryIsNotLowConstraintObserver(application),
new NetworkConstraintObserver(application),
new SqlCipherMigrationConstraintObserver(),
new DecryptionsDrainedConstraintObserver(),
new NotInCallConstraintObserver(),
ChangeNumberConstraintObserver.INSTANCE,

View File

@@ -10,7 +10,6 @@ import com.bumptech.glide.Glide;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.attachments.DatabaseAttachment;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.database.AttachmentTable;
import org.thoughtcrime.securesms.database.MessageTable;
import org.thoughtcrime.securesms.database.MessageTable.MmsReader;
@@ -23,7 +22,6 @@ import org.thoughtcrime.securesms.jobs.DirectoryRefreshJob;
import org.thoughtcrime.securesms.jobs.PreKeysSyncJob;
import org.thoughtcrime.securesms.jobs.RefreshAttributesJob;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.service.KeyCachingService;
import org.thoughtcrime.securesms.transport.RetryLaterException;
import org.thoughtcrime.securesms.util.FileUtils;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
@@ -98,15 +96,6 @@ public class LegacyMigrationJob extends MigrationJob {
void performMigration() throws RetryLaterException {
Log.i(TAG, "Running background upgrade..");
int lastSeenVersion = VersionTracker.getLastSeenVersion(context);
MasterSecret masterSecret = KeyCachingService.getMasterSecret(context);
if (lastSeenVersion < SQLCIPHER && masterSecret != null) {
SignalDatabase.onApplicationLevelUpgrade(context, masterSecret, lastSeenVersion, (progress, total) -> {
Log.i(TAG, "onApplicationLevelUpgrade: " + progress + "/" + total);
});
} else if (lastSeenVersion < SQLCIPHER) {
throw new RetryLaterException();
}
if (lastSeenVersion < NO_V1_VERSION) {
File v1sessions = new File(context.getFilesDir(), "sessions");

View File

@@ -26,7 +26,6 @@ import org.thoughtcrime.securesms.backup.proto.SharedPreference;
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil;
import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.dependencies.AppDependencies;
import org.thoughtcrime.securesms.jobmanager.impl.SqlCipherMigrationConstraintObserver;
import org.thoughtcrime.securesms.keyvalue.SettingsValues;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.lock.RegistrationLockReminders;
@@ -97,7 +96,6 @@ public class TextSecurePreferences {
private static final String DATABASE_UNENCRYPTED_SECRET = "pref_database_unencrypted_secret";
private static final String ATTACHMENT_ENCRYPTED_SECRET = "pref_attachment_encrypted_secret";
private static final String ATTACHMENT_UNENCRYPTED_SECRET = "pref_attachment_unencrypted_secret";
private static final String NEEDS_SQLCIPHER_MIGRATION = "pref_needs_sql_cipher_migration";
public static final String CALL_NOTIFICATIONS_PREF = "pref_call_notifications";
public static final String CALL_RINGTONE_PREF = "pref_call_ringtone";
@@ -336,15 +334,6 @@ public class TextSecurePreferences {
return getLongPreference(context, BACKUP_TIME, -1);
}
public static void setNeedsSqlCipherMigration(@NonNull Context context, boolean value) {
setBooleanPreference(context, NEEDS_SQLCIPHER_MIGRATION, value);
EventBus.getDefault().post(new SqlCipherMigrationConstraintObserver.SqlCipherNeedsMigrationEvent());
}
public static boolean getNeedsSqlCipherMigration(@NonNull Context context) {
return getBooleanPreference(context, NEEDS_SQLCIPHER_MIGRATION, false);
}
public static void setAttachmentEncryptedSecret(@NonNull Context context, @NonNull String secret) {
setStringPreference(context, ATTACHMENT_ENCRYPTED_SECRET, secret);
}