mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-25 19:29:54 +01:00
Move all files to natural position.
This commit is contained in:
@@ -0,0 +1,53 @@
|
||||
/**
|
||||
* Copyright (C) 2019 Open Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.migrations;
|
||||
|
||||
import android.os.Bundle;
|
||||
|
||||
import org.thoughtcrime.securesms.BaseActivity;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
|
||||
/**
|
||||
* An activity that can be shown to block access to the rest of the app when a long-running or
|
||||
* otherwise blocking application-level migration is happening.
|
||||
*/
|
||||
public class ApplicationMigrationActivity extends BaseActivity {
|
||||
|
||||
private static final String TAG = Log.tag(ApplicationMigrationActivity.class);
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle bundle) {
|
||||
super.onCreate(bundle);
|
||||
|
||||
ApplicationMigrations.getUiBlockingMigrationStatus().observe(this, running -> {
|
||||
if (running == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (running) {
|
||||
Log.i(TAG, "UI-blocking migration is in progress. Showing spinner.");
|
||||
setContentView(R.layout.application_migration_activity);
|
||||
} else {
|
||||
Log.i(TAG, "UI-blocking migration is no-longer in progress. Finishing.");
|
||||
startActivity(getIntent().getParcelableExtra("next_intent"));
|
||||
finish();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,202 @@
|
||||
package org.thoughtcrime.securesms.migrations;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
import org.greenrobot.eventbus.Subscribe;
|
||||
import org.greenrobot.eventbus.ThreadMode;
|
||||
import org.thoughtcrime.securesms.jobmanager.JobManager;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.thoughtcrime.securesms.util.VersionTracker;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Manages application-level migrations.
|
||||
*
|
||||
* Migrations can be slotted to occur based on changes in the canonical version code
|
||||
* (see {@link Util#getCanonicalVersionCode()}).
|
||||
*
|
||||
* Migrations are performed via {@link MigrationJob}s. These jobs are durable and are run before any
|
||||
* other job, allowing you to schedule safe migrations. Furthermore, you may specify that a
|
||||
* migration is UI-blocking, at which point we will show a spinner via
|
||||
* {@link ApplicationMigrationActivity} if the user opens the app while the migration is in
|
||||
* progress.
|
||||
*/
|
||||
public class ApplicationMigrations {
|
||||
|
||||
private static final String TAG = Log.tag(ApplicationMigrations.class);
|
||||
|
||||
private static final MutableLiveData<Boolean> UI_BLOCKING_MIGRATION_RUNNING = new MutableLiveData<>();
|
||||
|
||||
private static final int LEGACY_CANONICAL_VERSION = 455;
|
||||
|
||||
public static final int CURRENT_VERSION = 8;
|
||||
|
||||
private static final class Version {
|
||||
static final int LEGACY = 1;
|
||||
static final int RECIPIENT_ID = 2;
|
||||
static final int RECIPIENT_SEARCH = 3;
|
||||
static final int RECIPIENT_CLEANUP = 4;
|
||||
static final int AVATAR_MIGRATION = 5;
|
||||
static final int UUIDS = 6;
|
||||
static final int CACHED_ATTACHMENTS = 7;
|
||||
static final int STICKERS_LAUNCH = 8;
|
||||
}
|
||||
|
||||
/**
|
||||
* This *must* be called after the {@link JobManager} has been instantiated, but *before* the call
|
||||
* to {@link JobManager#beginJobLoop()}. Otherwise, other non-migration jobs may have started
|
||||
* executing before we add the migration jobs.
|
||||
*/
|
||||
public static void onApplicationCreate(@NonNull Context context, @NonNull JobManager jobManager) {
|
||||
if (isLegacyUpdate(context)) {
|
||||
Log.i(TAG, "Detected the need for a legacy update. Last seen canonical version: " + VersionTracker.getLastSeenVersion(context));
|
||||
TextSecurePreferences.setAppMigrationVersion(context, 0);
|
||||
}
|
||||
|
||||
if (!isUpdate(context)) {
|
||||
Log.d(TAG, "Not an update. Skipping.");
|
||||
return;
|
||||
}
|
||||
|
||||
final int lastSeenVersion = TextSecurePreferences.getAppMigrationVersion(context);
|
||||
Log.d(TAG, "currentVersion: " + CURRENT_VERSION + ", lastSeenVersion: " + lastSeenVersion);
|
||||
|
||||
LinkedHashMap<Integer, MigrationJob> migrationJobs = getMigrationJobs(context, lastSeenVersion);
|
||||
|
||||
if (migrationJobs.size() > 0) {
|
||||
Log.i(TAG, "About to enqueue " + migrationJobs.size() + " migration(s).");
|
||||
|
||||
boolean uiBlocking = true;
|
||||
int uiBlockingVersion = lastSeenVersion;
|
||||
|
||||
for (Map.Entry<Integer, MigrationJob> entry : migrationJobs.entrySet()) {
|
||||
int version = entry.getKey();
|
||||
MigrationJob job = entry.getValue();
|
||||
|
||||
uiBlocking &= job.isUiBlocking();
|
||||
if (uiBlocking) {
|
||||
uiBlockingVersion = version;
|
||||
}
|
||||
|
||||
jobManager.add(job);
|
||||
jobManager.add(new MigrationCompleteJob(version));
|
||||
}
|
||||
|
||||
if (uiBlockingVersion > lastSeenVersion) {
|
||||
Log.i(TAG, "Migration set is UI-blocking through version " + uiBlockingVersion + ".");
|
||||
UI_BLOCKING_MIGRATION_RUNNING.setValue(true);
|
||||
} else {
|
||||
Log.i(TAG, "Migration set is non-UI-blocking.");
|
||||
UI_BLOCKING_MIGRATION_RUNNING.setValue(false);
|
||||
}
|
||||
|
||||
final long startTime = System.currentTimeMillis();
|
||||
final int uiVersion = uiBlockingVersion;
|
||||
|
||||
EventBus.getDefault().register(new Object() {
|
||||
@Subscribe(sticky = true, threadMode = ThreadMode.MAIN)
|
||||
public void onMigrationComplete(MigrationCompleteEvent event) {
|
||||
Log.i(TAG, "Received MigrationCompleteEvent for version " + event.getVersion() + ". (Current: " + CURRENT_VERSION + ")");
|
||||
|
||||
if (event.getVersion() > CURRENT_VERSION) {
|
||||
throw new AssertionError("Received a higher version than the current version? App downgrades are not supported. (received: " + event.getVersion() + ", current: " + CURRENT_VERSION + ")");
|
||||
}
|
||||
|
||||
Log.i(TAG, "Updating last migration version to " + event.getVersion());
|
||||
TextSecurePreferences.setAppMigrationVersion(context, event.getVersion());
|
||||
|
||||
if (event.getVersion() == CURRENT_VERSION) {
|
||||
Log.i(TAG, "Migration complete. Took " + (System.currentTimeMillis() - startTime) + " ms.");
|
||||
EventBus.getDefault().unregister(this);
|
||||
|
||||
VersionTracker.updateLastSeenVersion(context);
|
||||
UI_BLOCKING_MIGRATION_RUNNING.setValue(false);
|
||||
} else if (event.getVersion() >= uiVersion) {
|
||||
Log.i(TAG, "Version is >= the UI-blocking version. Posting 'false'.");
|
||||
UI_BLOCKING_MIGRATION_RUNNING.setValue(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
Log.d(TAG, "No migrations.");
|
||||
TextSecurePreferences.setAppMigrationVersion(context, CURRENT_VERSION);
|
||||
VersionTracker.updateLastSeenVersion(context);
|
||||
UI_BLOCKING_MIGRATION_RUNNING.setValue(false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return A {@link LiveData} object that will update with whether or not a UI blocking migration
|
||||
* is in progress.
|
||||
*/
|
||||
public static LiveData<Boolean> getUiBlockingMigrationStatus() {
|
||||
return UI_BLOCKING_MIGRATION_RUNNING;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return True if a UI blocking migration is running.
|
||||
*/
|
||||
public static boolean isUiBlockingMigrationRunning() {
|
||||
Boolean value = UI_BLOCKING_MIGRATION_RUNNING.getValue();
|
||||
return value != null && value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Whether or not we're in the middle of an update, as determined by the last seen and
|
||||
* current version.
|
||||
*/
|
||||
public static boolean isUpdate(@NonNull Context context) {
|
||||
return isLegacyUpdate(context) || TextSecurePreferences.getAppMigrationVersion(context) < CURRENT_VERSION;
|
||||
}
|
||||
|
||||
private static LinkedHashMap<Integer, MigrationJob> getMigrationJobs(@NonNull Context context, int lastSeenVersion) {
|
||||
LinkedHashMap<Integer, MigrationJob> jobs = new LinkedHashMap<>();
|
||||
|
||||
if (lastSeenVersion < Version.LEGACY) {
|
||||
jobs.put(Version.LEGACY, new LegacyMigrationJob());
|
||||
}
|
||||
|
||||
if (lastSeenVersion < Version.RECIPIENT_ID) {
|
||||
jobs.put(Version.RECIPIENT_ID, new DatabaseMigrationJob());
|
||||
}
|
||||
|
||||
if (lastSeenVersion < Version.RECIPIENT_SEARCH) {
|
||||
jobs.put(Version.RECIPIENT_SEARCH, new RecipientSearchMigrationJob());
|
||||
}
|
||||
|
||||
if (lastSeenVersion < Version.RECIPIENT_CLEANUP) {
|
||||
jobs.put(Version.RECIPIENT_CLEANUP, new DatabaseMigrationJob());
|
||||
}
|
||||
|
||||
if (lastSeenVersion < Version.AVATAR_MIGRATION) {
|
||||
jobs.put(Version.AVATAR_MIGRATION, new AvatarMigrationJob());
|
||||
}
|
||||
|
||||
if (lastSeenVersion < Version.UUIDS) {
|
||||
jobs.put(Version.UUIDS, new UuidMigrationJob());
|
||||
}
|
||||
|
||||
if (lastSeenVersion < Version.CACHED_ATTACHMENTS) {
|
||||
jobs.put(Version.CACHED_ATTACHMENTS, new CachedAttachmentsMigrationJob());
|
||||
}
|
||||
|
||||
if (lastSeenVersion < Version.STICKERS_LAUNCH) {
|
||||
jobs.put(Version.STICKERS_LAUNCH, new StickerLaunchMigrationJob());
|
||||
}
|
||||
|
||||
return jobs;
|
||||
}
|
||||
|
||||
private static boolean isLegacyUpdate(@NonNull Context context) {
|
||||
return VersionTracker.getLastSeenVersion(context) < LEGACY_CANONICAL_VERSION;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
package org.thoughtcrime.securesms.migrations;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.jobmanager.Data;
|
||||
import org.thoughtcrime.securesms.jobmanager.Job;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
import org.thoughtcrime.securesms.phonenumbers.NumberUtil;
|
||||
import org.thoughtcrime.securesms.profiles.AvatarHelper;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.util.GroupUtil;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Previously, we used a recipient's address as the filename for their avatar. We want to use
|
||||
* recipientId's instead in preparation for UUIDs.
|
||||
*/
|
||||
public class AvatarMigrationJob extends MigrationJob {
|
||||
|
||||
public static final String KEY = "AvatarMigrationJob";
|
||||
|
||||
private static final String TAG = Log.tag(AvatarMigrationJob.class);
|
||||
|
||||
private static final Pattern NUMBER_PATTERN = Pattern.compile("^[0-9\\-+]+$");
|
||||
|
||||
AvatarMigrationJob() {
|
||||
this(new Parameters.Builder().build());
|
||||
}
|
||||
|
||||
private AvatarMigrationJob(@NonNull Parameters parameters) {
|
||||
super(parameters);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isUiBlocking() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull String getFactoryKey() {
|
||||
return KEY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void performMigration() {
|
||||
File oldDirectory = new File(context.getFilesDir(), "avatars");
|
||||
File[] files = oldDirectory.listFiles();
|
||||
|
||||
if (files == null) {
|
||||
Log.w(TAG, "Unable to read directory, and therefore unable to migrate any avatars.");
|
||||
return;
|
||||
}
|
||||
|
||||
Log.i(TAG, "Preparing to move " + files.length + " avatars.");
|
||||
|
||||
for (File file : files) {
|
||||
try {
|
||||
if (isValidFileName(file.getName())) {
|
||||
Recipient recipient = Recipient.external(context, file.getName());
|
||||
byte[] data = Util.readFully(new FileInputStream(file));
|
||||
|
||||
AvatarHelper.setAvatar(context, recipient.getId(), data);
|
||||
} else {
|
||||
Log.w(TAG, "Invalid file name! Can't migrate this file. It'll just get deleted.");
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, "Failed to copy avatar file. Skipping it.", e);
|
||||
} finally {
|
||||
file.delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean shouldRetry(@NonNull Exception e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean isValidFileName(@NonNull String name) {
|
||||
return NUMBER_PATTERN.matcher(name).matches() || GroupUtil.isEncodedGroup(name) || NumberUtil.isValidEmail(name);
|
||||
}
|
||||
|
||||
public static class Factory implements Job.Factory<AvatarMigrationJob> {
|
||||
@Override
|
||||
public @NonNull AvatarMigrationJob create(@NonNull Parameters parameters, @NonNull Data data) {
|
||||
return new AvatarMigrationJob(parameters);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package org.thoughtcrime.securesms.migrations;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.thoughtcrime.securesms.jobmanager.Data;
|
||||
import org.thoughtcrime.securesms.jobmanager.Job;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
import org.thoughtcrime.securesms.mms.GlideApp;
|
||||
import org.thoughtcrime.securesms.util.FileUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
public class CachedAttachmentsMigrationJob extends MigrationJob {
|
||||
|
||||
private static final String TAG = Log.tag(CachedAttachmentsMigrationJob.class);
|
||||
|
||||
public static final String KEY = "CachedAttachmentsMigrationJob";
|
||||
|
||||
CachedAttachmentsMigrationJob() {
|
||||
this(new Parameters.Builder().build());
|
||||
}
|
||||
|
||||
private CachedAttachmentsMigrationJob(@NonNull Parameters parameters) {
|
||||
super(parameters);
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean isUiBlocking() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
void performMigration() {
|
||||
File externalCacheDir = context.getExternalCacheDir();
|
||||
|
||||
if (externalCacheDir == null || !externalCacheDir.exists() || !externalCacheDir.isDirectory()) {
|
||||
Log.w(TAG, "External Cache Directory either does not exist or isn't a directory. Skipping.");
|
||||
return;
|
||||
}
|
||||
|
||||
FileUtils.deleteDirectoryContents(context.getExternalCacheDir());
|
||||
GlideApp.get(context).clearDiskCache();
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean shouldRetry(@NonNull Exception e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull String getFactoryKey() {
|
||||
return KEY;
|
||||
}
|
||||
|
||||
public static class Factory implements Job.Factory<CachedAttachmentsMigrationJob> {
|
||||
@Override
|
||||
public @NonNull CachedAttachmentsMigrationJob create(@NonNull Parameters parameters, @NonNull Data data) {
|
||||
return new CachedAttachmentsMigrationJob(parameters);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package org.thoughtcrime.securesms.migrations;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.jobmanager.Data;
|
||||
import org.thoughtcrime.securesms.jobmanager.Job;
|
||||
|
||||
/**
|
||||
* Triggers a database access, forcing the database to upgrade if it hasn't already. Should be used
|
||||
* when you expect a database migration to take a particularly long time.
|
||||
*/
|
||||
public class DatabaseMigrationJob extends MigrationJob {
|
||||
|
||||
public static final String KEY = "DatabaseMigrationJob";
|
||||
|
||||
DatabaseMigrationJob() {
|
||||
this(new Parameters.Builder().build());
|
||||
}
|
||||
|
||||
private DatabaseMigrationJob(@NonNull Parameters parameters) {
|
||||
super(parameters);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isUiBlocking() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull String getFactoryKey() {
|
||||
return KEY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void performMigration() {
|
||||
DatabaseFactory.getInstance(context).triggerDatabaseAccess();
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean shouldRetry(@NonNull Exception e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public static class Factory implements Job.Factory<DatabaseMigrationJob> {
|
||||
@Override
|
||||
public @NonNull DatabaseMigrationJob create(@NonNull Parameters parameters, @NonNull Data data) {
|
||||
return new DatabaseMigrationJob(parameters);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,311 @@
|
||||
package org.thoughtcrime.securesms.migrations;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.preference.PreferenceManager;
|
||||
|
||||
import org.thoughtcrime.securesms.attachments.DatabaseAttachment;
|
||||
import org.thoughtcrime.securesms.color.MaterialColor;
|
||||
import org.thoughtcrime.securesms.contacts.avatars.ContactColorsLegacy;
|
||||
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.database.AttachmentDatabase;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.MmsDatabase;
|
||||
import org.thoughtcrime.securesms.database.MmsDatabase.Reader;
|
||||
import org.thoughtcrime.securesms.database.PushDatabase;
|
||||
import org.thoughtcrime.securesms.database.model.MessageRecord;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.jobmanager.Data;
|
||||
import org.thoughtcrime.securesms.jobmanager.Job;
|
||||
import org.thoughtcrime.securesms.jobmanager.JobManager;
|
||||
import org.thoughtcrime.securesms.jobs.AttachmentDownloadJob;
|
||||
import org.thoughtcrime.securesms.jobs.CreateSignedPreKeyJob;
|
||||
import org.thoughtcrime.securesms.jobs.DirectoryRefreshJob;
|
||||
import org.thoughtcrime.securesms.jobs.PushDecryptMessageJob;
|
||||
import org.thoughtcrime.securesms.jobs.RefreshAttributesJob;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
import org.thoughtcrime.securesms.mms.GlideApp;
|
||||
import org.thoughtcrime.securesms.service.KeyCachingService;
|
||||
import org.thoughtcrime.securesms.transport.RetryLaterException;
|
||||
import org.thoughtcrime.securesms.util.FileUtils;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.thoughtcrime.securesms.util.VersionTracker;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Represents all of the migrations that used to take place in {@link ApplicationMigrationActivity}
|
||||
* (previously known as DatabaseUpgradeActivity). This job should *never* have new versions or
|
||||
* migrations added to it. Instead, create a new {@link MigrationJob} and place it in
|
||||
* {@link ApplicationMigrations}.
|
||||
*/
|
||||
public class LegacyMigrationJob extends MigrationJob {
|
||||
|
||||
public static final String KEY = "LegacyMigrationJob";
|
||||
|
||||
private static final String TAG = Log.tag(LegacyMigrationJob.class);
|
||||
|
||||
public static final int NO_MORE_KEY_EXCHANGE_PREFIX_VERSION = 46;
|
||||
public static final int MMS_BODY_VERSION = 46;
|
||||
public static final int TOFU_IDENTITIES_VERSION = 50;
|
||||
private static final int CURVE25519_VERSION = 63;
|
||||
public static final int ASYMMETRIC_MASTER_SECRET_FIX_VERSION = 73;
|
||||
private static final int NO_V1_VERSION = 83;
|
||||
private static final int SIGNED_PREKEY_VERSION = 83;
|
||||
private static final int NO_DECRYPT_QUEUE_VERSION = 113;
|
||||
private static final int PUSH_DECRYPT_SERIAL_ID_VERSION = 131;
|
||||
private static final int MIGRATE_SESSION_PLAINTEXT = 136;
|
||||
private static final int CONTACTS_ACCOUNT_VERSION = 136;
|
||||
private static final int MEDIA_DOWNLOAD_CONTROLS_VERSION = 151;
|
||||
private static final int REDPHONE_SUPPORT_VERSION = 157;
|
||||
private static final int NO_MORE_CANONICAL_DB_VERSION = 276;
|
||||
private static final int PROFILES = 289;
|
||||
private static final int SCREENSHOTS = 300;
|
||||
private static final int PERSISTENT_BLOBS = 317;
|
||||
private static final int INTERNALIZE_CONTACTS = 317;
|
||||
public static final int SQLCIPHER = 334;
|
||||
private static final int SQLCIPHER_COMPLETE = 352;
|
||||
private static final int REMOVE_JOURNAL = 353;
|
||||
private static final int REMOVE_CACHE = 354;
|
||||
private static final int FULL_TEXT_SEARCH = 358;
|
||||
private static final int BAD_IMPORT_CLEANUP = 373;
|
||||
private static final int IMAGE_CACHE_CLEANUP = 406;
|
||||
private static final int WORKMANAGER_MIGRATION = 408;
|
||||
private static final int COLOR_MIGRATION = 412;
|
||||
private static final int UNIDENTIFIED_DELIVERY = 422;
|
||||
private static final int SIGNALING_KEY_DEPRECATION = 447;
|
||||
private static final int CONVERSATION_SEARCH = 455;
|
||||
|
||||
|
||||
public LegacyMigrationJob() {
|
||||
this(new Parameters.Builder().build());
|
||||
}
|
||||
|
||||
private LegacyMigrationJob(@NonNull Parameters parameters) {
|
||||
super(parameters);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isUiBlocking() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull String getFactoryKey() {
|
||||
return KEY;
|
||||
}
|
||||
|
||||
@Override
|
||||
void performMigration() throws RetryLaterException {
|
||||
Log.i("DatabaseUpgradeActivity", "Running background upgrade..");
|
||||
int lastSeenVersion = VersionTracker.getLastSeenVersion(context);
|
||||
MasterSecret masterSecret = KeyCachingService.getMasterSecret(context);
|
||||
|
||||
if (lastSeenVersion < SQLCIPHER && masterSecret != null) {
|
||||
DatabaseFactory.getInstance(context).onApplicationLevelUpgrade(context, masterSecret, lastSeenVersion, (progress, total) -> {
|
||||
Log.i(TAG, "onApplicationLevelUpgrade: " + progress + "/" + total);
|
||||
});
|
||||
} else if (lastSeenVersion < SQLCIPHER) {
|
||||
throw new RetryLaterException();
|
||||
}
|
||||
|
||||
if (lastSeenVersion < CURVE25519_VERSION) {
|
||||
IdentityKeyUtil.migrateIdentityKeys(context, masterSecret);
|
||||
}
|
||||
|
||||
if (lastSeenVersion < NO_V1_VERSION) {
|
||||
File v1sessions = new File(context.getFilesDir(), "sessions");
|
||||
|
||||
if (v1sessions.exists() && v1sessions.isDirectory()) {
|
||||
File[] contents = v1sessions.listFiles();
|
||||
|
||||
if (contents != null) {
|
||||
for (File session : contents) {
|
||||
session.delete();
|
||||
}
|
||||
}
|
||||
|
||||
v1sessions.delete();
|
||||
}
|
||||
}
|
||||
|
||||
if (lastSeenVersion < SIGNED_PREKEY_VERSION) {
|
||||
ApplicationDependencies.getJobManager().add(new CreateSignedPreKeyJob(context));
|
||||
}
|
||||
|
||||
if (lastSeenVersion < NO_DECRYPT_QUEUE_VERSION) {
|
||||
scheduleMessagesInPushDatabase(context);
|
||||
}
|
||||
|
||||
if (lastSeenVersion < PUSH_DECRYPT_SERIAL_ID_VERSION) {
|
||||
scheduleMessagesInPushDatabase(context);
|
||||
}
|
||||
|
||||
if (lastSeenVersion < MIGRATE_SESSION_PLAINTEXT) {
|
||||
// new TextSecureSessionStore(context, masterSecret).migrateSessions();
|
||||
// new TextSecurePreKeyStore(context, masterSecret).migrateRecords();
|
||||
|
||||
IdentityKeyUtil.migrateIdentityKeys(context, masterSecret);
|
||||
scheduleMessagesInPushDatabase(context);;
|
||||
}
|
||||
|
||||
if (lastSeenVersion < CONTACTS_ACCOUNT_VERSION) {
|
||||
ApplicationDependencies.getJobManager().add(new DirectoryRefreshJob(false));
|
||||
}
|
||||
|
||||
if (lastSeenVersion < MEDIA_DOWNLOAD_CONTROLS_VERSION) {
|
||||
schedulePendingIncomingParts(context);
|
||||
}
|
||||
|
||||
if (lastSeenVersion < REDPHONE_SUPPORT_VERSION) {
|
||||
ApplicationDependencies.getJobManager().add(new RefreshAttributesJob());
|
||||
ApplicationDependencies.getJobManager().add(new DirectoryRefreshJob(false));
|
||||
}
|
||||
|
||||
if (lastSeenVersion < PROFILES) {
|
||||
ApplicationDependencies.getJobManager().add(new DirectoryRefreshJob(false));
|
||||
}
|
||||
|
||||
if (lastSeenVersion < SCREENSHOTS) {
|
||||
boolean screenSecurity = PreferenceManager.getDefaultSharedPreferences(context).getBoolean(TextSecurePreferences.SCREEN_SECURITY_PREF, true);
|
||||
TextSecurePreferences.setScreenSecurityEnabled(context, screenSecurity);
|
||||
}
|
||||
|
||||
if (lastSeenVersion < PERSISTENT_BLOBS) {
|
||||
File externalDir = context.getExternalFilesDir(null);
|
||||
|
||||
if (externalDir != null && externalDir.isDirectory() && externalDir.exists()) {
|
||||
for (File blob : externalDir.listFiles()) {
|
||||
if (blob.exists() && blob.isFile()) blob.delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (lastSeenVersion < INTERNALIZE_CONTACTS) {
|
||||
if (TextSecurePreferences.isPushRegistered(context)) {
|
||||
TextSecurePreferences.setHasSuccessfullyRetrievedDirectory(context, true);
|
||||
}
|
||||
}
|
||||
|
||||
if (lastSeenVersion < SQLCIPHER) {
|
||||
scheduleMessagesInPushDatabase(context);
|
||||
}
|
||||
|
||||
if (lastSeenVersion < SQLCIPHER_COMPLETE) {
|
||||
File file = context.getDatabasePath("messages.db");
|
||||
if (file != null && file.exists()) file.delete();
|
||||
}
|
||||
|
||||
if (lastSeenVersion < REMOVE_JOURNAL) {
|
||||
File file = context.getDatabasePath("messages.db-journal");
|
||||
if (file != null && file.exists()) file.delete();
|
||||
}
|
||||
|
||||
if (lastSeenVersion < REMOVE_CACHE) {
|
||||
FileUtils.deleteDirectoryContents(context.getCacheDir());
|
||||
}
|
||||
|
||||
if (lastSeenVersion < IMAGE_CACHE_CLEANUP) {
|
||||
FileUtils.deleteDirectoryContents(context.getExternalCacheDir());
|
||||
GlideApp.get(context).clearDiskCache();
|
||||
}
|
||||
|
||||
// This migration became unnecessary after switching away from WorkManager
|
||||
// if (lastSeenVersion < WORKMANAGER_MIGRATION) {
|
||||
// Log.i(TAG, "Beginning migration of existing jobs to WorkManager");
|
||||
//
|
||||
// JobManager jobManager = ApplicationContext.getInstance(getApplicationContext()).getJobManager();
|
||||
// PersistentStorage storage = new PersistentStorage(getApplicationContext(), "TextSecureJobs", new JavaJobSerializer());
|
||||
//
|
||||
// for (Job job : storage.getAllUnencrypted()) {
|
||||
// jobManager.add(job);
|
||||
// Log.i(TAG, "Migrated job with class '" + job.getClass().getSimpleName() + "' to run on new JobManager.");
|
||||
// }
|
||||
// }
|
||||
|
||||
if (lastSeenVersion < COLOR_MIGRATION) {
|
||||
long startTime = System.currentTimeMillis();
|
||||
DatabaseFactory.getRecipientDatabase(context).updateSystemContactColors((name, color) -> {
|
||||
if (color != null) {
|
||||
try {
|
||||
return MaterialColor.fromSerialized(color);
|
||||
} catch (MaterialColor.UnknownColorException e) {
|
||||
Log.w(TAG, "Encountered an unknown color during legacy color migration.", e);
|
||||
return ContactColorsLegacy.generateFor(name);
|
||||
}
|
||||
}
|
||||
return ContactColorsLegacy.generateFor(name);
|
||||
});
|
||||
Log.i(TAG, "Color migration took " + (System.currentTimeMillis() - startTime) + " ms");
|
||||
}
|
||||
|
||||
if (lastSeenVersion < UNIDENTIFIED_DELIVERY) {
|
||||
if (TextSecurePreferences.isMultiDevice(context)) {
|
||||
Log.i(TAG, "MultiDevice: Disabling UD (will be re-enabled if possible after pending refresh).");
|
||||
TextSecurePreferences.setIsUnidentifiedDeliveryEnabled(context, false);
|
||||
}
|
||||
|
||||
Log.i(TAG, "Scheduling UD attributes refresh.");
|
||||
ApplicationDependencies.getJobManager().add(new RefreshAttributesJob());
|
||||
}
|
||||
|
||||
if (lastSeenVersion < SIGNALING_KEY_DEPRECATION) {
|
||||
Log.i(TAG, "Scheduling a RefreshAttributesJob to remove the signaling key remotely.");
|
||||
ApplicationDependencies.getJobManager().add(new RefreshAttributesJob());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean shouldRetry(@NonNull Exception e) {
|
||||
return e instanceof RetryLaterException;
|
||||
}
|
||||
|
||||
private void schedulePendingIncomingParts(Context context) {
|
||||
final AttachmentDatabase attachmentDb = DatabaseFactory.getAttachmentDatabase(context);
|
||||
final MmsDatabase mmsDb = DatabaseFactory.getMmsDatabase(context);
|
||||
final List<DatabaseAttachment> pendingAttachments = DatabaseFactory.getAttachmentDatabase(context).getPendingAttachments();
|
||||
|
||||
Log.i(TAG, pendingAttachments.size() + " pending parts.");
|
||||
for (DatabaseAttachment attachment : pendingAttachments) {
|
||||
final Reader reader = mmsDb.readerFor(mmsDb.getMessage(attachment.getMmsId()));
|
||||
final MessageRecord record = reader.getNext();
|
||||
|
||||
if (attachment.hasData()) {
|
||||
Log.i(TAG, "corrected a pending media part " + attachment.getAttachmentId() + "that already had data.");
|
||||
attachmentDb.setTransferState(attachment.getMmsId(), attachment.getAttachmentId(), AttachmentDatabase.TRANSFER_PROGRESS_DONE);
|
||||
} else if (record != null && !record.isOutgoing() && record.isPush()) {
|
||||
Log.i(TAG, "queuing new attachment download job for incoming push part " + attachment.getAttachmentId() + ".");
|
||||
ApplicationDependencies.getJobManager().add(new AttachmentDownloadJob(attachment.getMmsId(), attachment.getAttachmentId(), false));
|
||||
}
|
||||
reader.close();
|
||||
}
|
||||
}
|
||||
|
||||
private static void scheduleMessagesInPushDatabase(@NonNull Context context) {
|
||||
PushDatabase pushDatabase = DatabaseFactory.getPushDatabase(context);
|
||||
JobManager jobManager = ApplicationDependencies.getJobManager();
|
||||
|
||||
try (Cursor pushReader = pushDatabase.getPending()) {
|
||||
while (pushReader != null && pushReader.moveToNext()) {
|
||||
jobManager.add(new PushDecryptMessageJob(context,
|
||||
pushReader.getLong(pushReader.getColumnIndexOrThrow(PushDatabase.ID))));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public interface DatabaseUpgradeListener {
|
||||
void setProgress(int progress, int total);
|
||||
}
|
||||
|
||||
public static final class Factory implements Job.Factory<LegacyMigrationJob> {
|
||||
@Override
|
||||
public @NonNull LegacyMigrationJob create(@NonNull Parameters parameters, @NonNull Data data) {
|
||||
return new LegacyMigrationJob(parameters);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package org.thoughtcrime.securesms.migrations;
|
||||
|
||||
public class MigrationCompleteEvent {
|
||||
|
||||
private final int version;
|
||||
|
||||
public MigrationCompleteEvent(int version) {
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
public int getVersion() {
|
||||
return version;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package org.thoughtcrime.securesms.migrations;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
import org.thoughtcrime.securesms.jobmanager.Data;
|
||||
import org.thoughtcrime.securesms.jobmanager.Job;
|
||||
import org.thoughtcrime.securesms.jobs.BaseJob;
|
||||
|
||||
/**
|
||||
* A job that should be enqueued last in a series of migrations. When this runs, we know that the
|
||||
* current set of migrations has been completed.
|
||||
*
|
||||
* To avoid confusion around the possibility of multiples of these jobs being enqueued as the
|
||||
* result of doing multiple migrations, we associate the canonicalVersionCode with the job and
|
||||
* include that in the event we broadcast out.
|
||||
*/
|
||||
public class MigrationCompleteJob extends BaseJob {
|
||||
|
||||
public static final String KEY = "MigrationCompleteJob";
|
||||
|
||||
private final static String KEY_VERSION = "version";
|
||||
|
||||
private final int version;
|
||||
|
||||
MigrationCompleteJob(int version) {
|
||||
this(new Parameters.Builder()
|
||||
.setQueue(Parameters.MIGRATION_QUEUE_KEY)
|
||||
.setLifespan(Parameters.IMMORTAL)
|
||||
.setMaxAttempts(Parameters.UNLIMITED)
|
||||
.build(),
|
||||
version);
|
||||
}
|
||||
|
||||
private MigrationCompleteJob(@NonNull Job.Parameters parameters, int version) {
|
||||
super(parameters);
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull Data serialize() {
|
||||
return new Data.Builder().putInt(KEY_VERSION, version).build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull String getFactoryKey() {
|
||||
return KEY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCanceled() {
|
||||
throw new AssertionError("This job should never fail.");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onRun() throws Exception {
|
||||
EventBus.getDefault().postSticky(new MigrationCompleteEvent(version));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean onShouldRetry(@NonNull Exception e) {
|
||||
return true;
|
||||
}
|
||||
|
||||
public static class Factory implements Job.Factory<MigrationCompleteJob> {
|
||||
@Override
|
||||
public @NonNull MigrationCompleteJob create(@NonNull Parameters parameters, @NonNull Data data) {
|
||||
return new MigrationCompleteJob(parameters, data.getInt(KEY_VERSION));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
package org.thoughtcrime.securesms.migrations;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.thoughtcrime.securesms.jobmanager.Data;
|
||||
import org.thoughtcrime.securesms.jobmanager.Job;
|
||||
import org.thoughtcrime.securesms.jobmanager.JobLogger;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
|
||||
/**
|
||||
* A base class for jobs that are intended to be used in {@link ApplicationMigrations}. Some
|
||||
* sensible defaults are provided, as well as enforcement that jobs have the correct queue key,
|
||||
* never expire, and have at most one instance (to avoid double-migrating).
|
||||
*
|
||||
* These jobs can never fail, or else the JobManager will skip over them. As a result, if they are
|
||||
* neither successful nor retryable, they will crash the app.
|
||||
*/
|
||||
abstract class MigrationJob extends Job {
|
||||
|
||||
private static final String TAG = Log.tag(MigrationJob.class);
|
||||
|
||||
MigrationJob(@NonNull Parameters parameters) {
|
||||
super(parameters.toBuilder()
|
||||
.setQueue(Parameters.MIGRATION_QUEUE_KEY)
|
||||
.setMaxInstances(1)
|
||||
.setLifespan(Parameters.IMMORTAL)
|
||||
.setMaxAttempts(Parameters.UNLIMITED)
|
||||
.build());
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull Data serialize() {
|
||||
return Data.EMPTY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull Result run() {
|
||||
try {
|
||||
Log.i(TAG, "About to run " + getClass().getSimpleName());
|
||||
performMigration();
|
||||
return Result.success();
|
||||
} catch (RuntimeException e) {
|
||||
Log.w(TAG, JobLogger.format(this, "Encountered a runtime exception."), e);
|
||||
throw new FailedMigrationError(e);
|
||||
} catch (Exception e) {
|
||||
if (shouldRetry(e)) {
|
||||
Log.w(TAG, JobLogger.format(this, "Encountered a retryable exception."), e);
|
||||
return Result.retry();
|
||||
} else {
|
||||
Log.w(TAG, JobLogger.format(this, "Encountered a non-runtime fatal exception."), e);
|
||||
throw new FailedMigrationError(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCanceled() {
|
||||
throw new AssertionError("This job should never fail. " + getClass().getSimpleName());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return True if you want the UI to be blocked by a spinner if the user opens the application
|
||||
* during the migration, otherwise false.
|
||||
*/
|
||||
abstract boolean isUiBlocking();
|
||||
|
||||
/**
|
||||
* Do the actual work of your migration.
|
||||
*/
|
||||
abstract void performMigration() throws Exception;
|
||||
|
||||
/**
|
||||
* @return True if you should retry this job based on the exception type, otherwise false.
|
||||
* Returning false will result in a crash and your job being re-run upon app start.
|
||||
* This could result in a crash loop, but considering that this is for an application
|
||||
* migration, this is likely preferable to skipping it.
|
||||
*/
|
||||
abstract boolean shouldRetry(@NonNull Exception e);
|
||||
|
||||
private static class FailedMigrationError extends Error {
|
||||
FailedMigrationError(Throwable t) {
|
||||
super(t);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package org.thoughtcrime.securesms.migrations;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.thoughtcrime.securesms.jobmanager.Data;
|
||||
import org.thoughtcrime.securesms.jobmanager.Job;
|
||||
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
|
||||
import org.thoughtcrime.securesms.contacts.sync.DirectoryHelper;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* We added a column for keeping track of the phone number type ("mobile", "home", etc) to the
|
||||
* recipient database, and therefore we need to do a directory sync to fill in that column.
|
||||
*/
|
||||
public class RecipientSearchMigrationJob extends MigrationJob {
|
||||
|
||||
public static final String KEY = "RecipientSearchMigrationJob";
|
||||
|
||||
RecipientSearchMigrationJob() {
|
||||
this(new Job.Parameters.Builder().addConstraint(NetworkConstraint.KEY).build());
|
||||
}
|
||||
|
||||
private RecipientSearchMigrationJob(@NonNull Job.Parameters parameters) {
|
||||
super(parameters);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull String getFactoryKey() {
|
||||
return KEY;
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean isUiBlocking() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
void performMigration() throws Exception {
|
||||
DirectoryHelper.refreshDirectory(context, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean shouldRetry(@NonNull Exception e) {
|
||||
return e instanceof IOException;
|
||||
}
|
||||
|
||||
public static class Factory implements Job.Factory<RecipientSearchMigrationJob> {
|
||||
@Override
|
||||
public @NonNull RecipientSearchMigrationJob create(@NonNull Parameters parameters, @NonNull Data data) {
|
||||
return new RecipientSearchMigrationJob(parameters);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
package org.thoughtcrime.securesms.migrations;
|
||||
|
||||
import android.text.TextUtils;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.jobmanager.Data;
|
||||
import org.thoughtcrime.securesms.jobmanager.Job;
|
||||
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
|
||||
import org.thoughtcrime.securesms.jobs.BaseJob;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
import org.thoughtcrime.securesms.util.FeatureFlags;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.whispersystems.signalservice.api.RegistrationLockData;
|
||||
import org.whispersystems.signalservice.internal.contacts.crypto.UnauthenticatedResponseException;
|
||||
import org.whispersystems.signalservice.internal.registrationpin.InvalidPinException;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Deliberately not a {@link MigrationJob} because it is not something that needs to run at app start.
|
||||
* This migration can run at anytime.
|
||||
*/
|
||||
public final class RegistrationPinV2MigrationJob extends BaseJob {
|
||||
|
||||
private static final String TAG = Log.tag(RegistrationPinV2MigrationJob.class);
|
||||
|
||||
public static final String KEY = "RegistrationPinV2MigrationJob";
|
||||
|
||||
public RegistrationPinV2MigrationJob() {
|
||||
this(new Parameters.Builder()
|
||||
.setQueue(KEY)
|
||||
.setMaxInstances(1)
|
||||
.addConstraint(NetworkConstraint.KEY)
|
||||
.setLifespan(Job.Parameters.IMMORTAL)
|
||||
.setMaxAttempts(Job.Parameters.UNLIMITED)
|
||||
.build());
|
||||
}
|
||||
|
||||
private RegistrationPinV2MigrationJob(@NonNull Parameters parameters) {
|
||||
super(parameters);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull Data serialize() {
|
||||
return Data.EMPTY;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onRun() throws IOException, UnauthenticatedResponseException {
|
||||
if (!FeatureFlags.KBS) {
|
||||
Log.i(TAG, "Not migrating pin to KBS");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!TextSecurePreferences.isRegistrationLockEnabled(context)) {
|
||||
Log.i(TAG, "Registration lock disabled");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!TextSecurePreferences.hasOldRegistrationLockPin(context)) {
|
||||
Log.i(TAG, "No old pin to migrate");
|
||||
return;
|
||||
}
|
||||
|
||||
//noinspection deprecation Only acceptable place to read the old pin.
|
||||
String registrationLockPin = TextSecurePreferences.getDeprecatedRegistrationLockPin(context);
|
||||
|
||||
if (registrationLockPin == null | TextUtils.isEmpty(registrationLockPin)) {
|
||||
Log.i(TAG, "No old pin to migrate");
|
||||
return;
|
||||
}
|
||||
|
||||
Log.i(TAG, "Migrating pin to Key Backup Service");
|
||||
|
||||
try {
|
||||
RegistrationLockData registrationPinV2Key = ApplicationDependencies.getKeyBackupService()
|
||||
.newPinChangeSession()
|
||||
.setPin(registrationLockPin);
|
||||
|
||||
TextSecurePreferences.setRegistrationLockMasterKey(context, registrationPinV2Key, System.currentTimeMillis());
|
||||
} catch (InvalidPinException e) {
|
||||
Log.w(TAG, "The V1 pin cannot be migrated.", e);
|
||||
return;
|
||||
}
|
||||
|
||||
Log.i(TAG, "Pin migrated to Key Backup Service");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean onShouldRetry(@NonNull Exception e) {
|
||||
return e instanceof IOException;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull String getFactoryKey() {
|
||||
return KEY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCanceled() {
|
||||
}
|
||||
|
||||
public static class Factory implements Job.Factory<RegistrationPinV2MigrationJob> {
|
||||
@Override
|
||||
public @NonNull RegistrationPinV2MigrationJob create(@NonNull Parameters parameters, @NonNull Data data) {
|
||||
return new RegistrationPinV2MigrationJob(parameters);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package org.thoughtcrime.securesms.migrations;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.StickerDatabase;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.jobmanager.Data;
|
||||
import org.thoughtcrime.securesms.jobmanager.Job;
|
||||
import org.thoughtcrime.securesms.jobmanager.JobManager;
|
||||
import org.thoughtcrime.securesms.jobs.MultiDeviceStickerPackOperationJob;
|
||||
import org.thoughtcrime.securesms.jobs.StickerPackDownloadJob;
|
||||
import org.thoughtcrime.securesms.stickers.BlessedPacks;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
|
||||
public class StickerLaunchMigrationJob extends MigrationJob {
|
||||
|
||||
public static final String KEY = "StickerLaunchMigrationJob";
|
||||
|
||||
StickerLaunchMigrationJob() {
|
||||
this(new Parameters.Builder().build());
|
||||
}
|
||||
|
||||
private StickerLaunchMigrationJob(@NonNull Parameters parameters) {
|
||||
super(parameters);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isUiBlocking() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull String getFactoryKey() {
|
||||
return KEY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void performMigration() {
|
||||
installPack(context, BlessedPacks.ZOZO);
|
||||
installPack(context, BlessedPacks.BANDIT);
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean shouldRetry(@NonNull Exception e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void installPack(@NonNull Context context, @NonNull BlessedPacks.Pack pack) {
|
||||
JobManager jobManager = ApplicationDependencies.getJobManager();
|
||||
StickerDatabase stickerDatabase = DatabaseFactory.getStickerDatabase(context);
|
||||
|
||||
if (stickerDatabase.isPackAvailableAsReference(pack.getPackId())) {
|
||||
stickerDatabase.markPackAsInstalled(pack.getPackId(), false);
|
||||
}
|
||||
|
||||
jobManager.add(StickerPackDownloadJob.forInstall(pack.getPackId(), pack.getPackKey(), false));
|
||||
|
||||
if (TextSecurePreferences.isMultiDevice(context)) {
|
||||
jobManager.add(new MultiDeviceStickerPackOperationJob(pack.getPackId(), pack.getPackKey(), MultiDeviceStickerPackOperationJob.Type.INSTALL));
|
||||
}
|
||||
}
|
||||
|
||||
public static class Factory implements Job.Factory<StickerLaunchMigrationJob> {
|
||||
@Override
|
||||
public @NonNull
|
||||
StickerLaunchMigrationJob create(@NonNull Parameters parameters, @NonNull Data data) {
|
||||
return new StickerLaunchMigrationJob(parameters);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
package org.thoughtcrime.securesms.migrations;
|
||||
|
||||
import android.content.Context;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.jobmanager.Data;
|
||||
import org.thoughtcrime.securesms.jobmanager.Job;
|
||||
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.whispersystems.signalservice.api.SignalServiceAccountManager;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Couple migrations steps need to happen after we move to UUIDS.
|
||||
* - We need to get our own UUID.
|
||||
* - We need to fetch the new UUID sealed sender cert.
|
||||
* - We need to do a directory sync so we can guarantee that all active users have UUIDs.
|
||||
*/
|
||||
public class UuidMigrationJob extends MigrationJob {
|
||||
|
||||
public static final String KEY = "UuidMigrationJob";
|
||||
|
||||
private static final String TAG = Log.tag(UuidMigrationJob.class);
|
||||
|
||||
UuidMigrationJob() {
|
||||
this(new Parameters.Builder().addConstraint(NetworkConstraint.KEY).build());
|
||||
}
|
||||
|
||||
private UuidMigrationJob(@NonNull Parameters parameters) {
|
||||
super(parameters);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull String getFactoryKey() {
|
||||
return KEY;
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean isUiBlocking() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
void performMigration() throws Exception {
|
||||
if (!TextSecurePreferences.isPushRegistered(context) || TextUtils.isEmpty(TextSecurePreferences.getLocalNumber(context))) {
|
||||
Log.w(TAG, "Not registered! Skipping migration, as it wouldn't do anything.");
|
||||
return;
|
||||
}
|
||||
|
||||
ensureSelfRecipientExists(context);
|
||||
fetchOwnUuid(context);
|
||||
rotateSealedSenderCerts(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean shouldRetry(@NonNull Exception e) {
|
||||
return e instanceof IOException;
|
||||
}
|
||||
|
||||
private static void ensureSelfRecipientExists(@NonNull Context context) {
|
||||
DatabaseFactory.getRecipientDatabase(context).getOrInsertFromE164(TextSecurePreferences.getLocalNumber(context));
|
||||
}
|
||||
|
||||
private static void fetchOwnUuid(@NonNull Context context) throws IOException {
|
||||
RecipientId self = Recipient.self().getId();
|
||||
UUID localUuid = ApplicationDependencies.getSignalServiceAccountManager().getOwnUuid();
|
||||
|
||||
DatabaseFactory.getRecipientDatabase(context).markRegistered(self, localUuid);
|
||||
TextSecurePreferences.setLocalUuid(context, localUuid);
|
||||
}
|
||||
|
||||
private static void rotateSealedSenderCerts(@NonNull Context context) throws IOException {
|
||||
SignalServiceAccountManager accountManager = ApplicationDependencies.getSignalServiceAccountManager();
|
||||
byte[] certificate = accountManager.getSenderCertificate();
|
||||
byte[] legacyCertificate = accountManager.getSenderCertificateLegacy();
|
||||
|
||||
TextSecurePreferences.setUnidentifiedAccessCertificate(context, certificate);
|
||||
TextSecurePreferences.setUnidentifiedAccessCertificateLegacy(context, legacyCertificate);
|
||||
}
|
||||
|
||||
|
||||
public static class Factory implements Job.Factory<UuidMigrationJob> {
|
||||
@Override
|
||||
public @NonNull UuidMigrationJob create(@NonNull Parameters parameters, @NonNull Data data) {
|
||||
return new UuidMigrationJob(parameters);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user