Signal-Android/app/src/main/java/org/thoughtcrime/securesms/migrations/ApplicationMigrations.java

272 lines
10 KiB
Java

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.stickers.BlessedPacks;
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 = 21;
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;
//static final int TEST_ARGON2 = 9;
static final int SWOON_STICKERS = 10;
static final int STORAGE_SERVICE = 11;
//static final int STORAGE_KEY_ROTATE = 12;
static final int REMOVE_AVATAR_ID = 13;
static final int STORAGE_CAPABILITY = 14;
static final int PIN_REMINDER = 15;
static final int VERSIONED_PROFILE = 16;
static final int PIN_OPT_OUT = 17;
static final int TRIM_SETTINGS = 18;
static final int THUMBNAIL_CLEANUP = 19;
static final int GV2 = 20;
static final int GV2_2 = 21;
}
/**
* 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.");
VersionTracker.updateLastSeenVersion(context);
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());
}
// This migration only triggered a test we aren't interested in any more.
// if (lastSeenVersion < Version.TEST_ARGON2) {
// jobs.put(Version.TEST_ARGON2, new Argon2TestMigrationJob());
// }
if (lastSeenVersion < Version.SWOON_STICKERS) {
jobs.put(Version.SWOON_STICKERS, new StickerAdditionMigrationJob(BlessedPacks.SWOON_HANDS, BlessedPacks.SWOON_FACES));
}
if (lastSeenVersion < Version.STORAGE_SERVICE) {
jobs.put(Version.STORAGE_SERVICE, new StorageServiceMigrationJob());
}
// Superceded by StorageCapabilityMigrationJob
// if (lastSeenVersion < Version.STORAGE_KEY_ROTATE) {
// jobs.put(Version.STORAGE_KEY_ROTATE, new StorageKeyRotationMigrationJob());
// }
if (lastSeenVersion < Version.REMOVE_AVATAR_ID) {
jobs.put(Version.REMOVE_AVATAR_ID, new AvatarIdRemovalMigrationJob());
}
if (lastSeenVersion < Version.STORAGE_CAPABILITY) {
jobs.put(Version.STORAGE_CAPABILITY, new StorageCapabilityMigrationJob());
}
if (lastSeenVersion < Version.PIN_REMINDER) {
jobs.put(Version.PIN_REMINDER, new PinReminderMigrationJob());
}
if (lastSeenVersion < Version.VERSIONED_PROFILE) {
jobs.put(Version.VERSIONED_PROFILE, new ProfileMigrationJob());
}
if (lastSeenVersion < Version.PIN_OPT_OUT) {
jobs.put(Version.PIN_OPT_OUT, new PinOptOutMigration());
}
if (lastSeenVersion < Version.TRIM_SETTINGS) {
jobs.put(Version.TRIM_SETTINGS, new TrimByLengthSettingsMigrationJob());
}
if (lastSeenVersion < Version.THUMBNAIL_CLEANUP) {
jobs.put(Version.THUMBNAIL_CLEANUP, new DatabaseMigrationJob());
}
if (lastSeenVersion < Version.GV2) {
jobs.put(Version.GV2, new AttributesMigrationJob());
}
if (lastSeenVersion < Version.GV2_2) {
jobs.put(Version.GV2_2, new AttributesMigrationJob());
}
return jobs;
}
private static boolean isLegacyUpdate(@NonNull Context context) {
return VersionTracker.getLastSeenVersion(context) < LEGACY_CANONICAL_VERSION;
}
}