2019-07-29 19:02:40 -04:00
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 ;
2019-09-05 14:39:18 -04:00
import org.thoughtcrime.securesms.util.TextSecurePreferences ;
2019-07-29 19:02:40 -04:00
import org.thoughtcrime.securesms.util.Util ;
import org.thoughtcrime.securesms.util.VersionTracker ;
2019-09-05 14:39:18 -04:00
import java.util.LinkedHashMap ;
import java.util.Map ;
2019-07-29 19:02:40 -04:00
/ * *
* 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 < > ( ) ;
2019-09-05 14:39:18 -04:00
private static final int LEGACY_CANONICAL_VERSION = 455 ;
public static final int CURRENT_VERSION = 3 ;
2019-07-29 19:02:40 -04:00
private static final class Version {
2019-09-05 14:39:18 -04:00
static final int LEGACY = 1 ;
static final int RECIPIENT_ID = 2 ;
static final int RECIPIENT_SEARCH = 3 ;
2019-07-29 19:02:40 -04:00
}
/ * *
* 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 ) {
2019-09-05 14:39:18 -04:00
if ( isLegacyUpdate ( context ) ) {
Log . i ( TAG , " Detected the need for a legacy update. Last seen canonical version: " + VersionTracker . getLastSeenVersion ( context ) ) ;
TextSecurePreferences . setAppMigrationVersion ( context , 0 ) ;
}
2019-07-29 19:02:40 -04:00
if ( ! isUpdate ( context ) ) {
Log . d ( TAG , " Not an update. Skipping. " ) ;
return ;
}
2019-09-05 14:39:18 -04:00
final int lastSeenVersion = TextSecurePreferences . getAppMigrationVersion ( context ) ;
Log . d ( TAG , " currentVersion: " + CURRENT_VERSION + " , lastSeenVersion: " + lastSeenVersion ) ;
2019-07-29 19:02:40 -04:00
2019-09-05 14:39:18 -04:00
LinkedHashMap < Integer , MigrationJob > migrationJobs = getMigrationJobs ( context , lastSeenVersion ) ;
2019-07-29 19:02:40 -04:00
if ( migrationJobs . size ( ) > 0 ) {
Log . i ( TAG , " About to enqueue " + migrationJobs . size ( ) + " migration(s). " ) ;
2019-09-05 14:39:18 -04:00
boolean uiBlocking = true ;
int uiBlockingVersion = lastSeenVersion ;
2019-07-29 19:02:40 -04:00
2019-09-05 14:39:18 -04:00
for ( Map . Entry < Integer , MigrationJob > entry : migrationJobs . entrySet ( ) ) {
int version = entry . getKey ( ) ;
MigrationJob job = entry . getValue ( ) ;
uiBlocking & = job . isUiBlocking ( ) ;
if ( uiBlocking ) {
uiBlockingVersion = version ;
}
2019-07-29 19:02:40 -04:00
jobManager . add ( job ) ;
2019-09-05 14:39:18 -04:00
jobManager . add ( new MigrationCompleteJob ( version ) ) ;
2019-07-29 19:02:40 -04:00
}
2019-09-05 14:39:18 -04:00
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 ) ;
}
2019-07-29 19:02:40 -04:00
final long startTime = System . currentTimeMillis ( ) ;
2019-09-05 14:39:18 -04:00
final int uiVersion = uiBlockingVersion ;
2019-07-29 19:02:40 -04:00
EventBus . getDefault ( ) . register ( new Object ( ) {
@Subscribe ( sticky = true , threadMode = ThreadMode . MAIN )
public void onMigrationComplete ( MigrationCompleteEvent event ) {
2019-09-05 14:39:18 -04:00
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 ( ) ) ;
2019-07-29 19:02:40 -04:00
2019-09-05 14:39:18 -04:00
if ( event . getVersion ( ) = = CURRENT_VERSION ) {
2019-07-29 19:02:40 -04:00
Log . i ( TAG , " Migration complete. Took " + ( System . currentTimeMillis ( ) - startTime ) + " ms. " ) ;
EventBus . getDefault ( ) . unregister ( this ) ;
VersionTracker . updateLastSeenVersion ( context ) ;
2019-09-05 14:39:18 -04:00
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 ) ;
2019-07-29 19:02:40 -04:00
}
}
} ) ;
} else {
Log . d ( TAG , " No migrations. " ) ;
2019-09-05 14:39:18 -04:00
TextSecurePreferences . setAppMigrationVersion ( context , CURRENT_VERSION ) ;
2019-07-29 19:02:40 -04:00
VersionTracker . updateLastSeenVersion ( context ) ;
2019-09-05 14:39:18 -04:00
UI_BLOCKING_MIGRATION_RUNNING . setValue ( false ) ;
2019-07-29 19:02:40 -04:00
}
}
/ * *
* @return A { @link LiveData } object that will update with whether or not a UI blocking migration
* is in progress .
* /
2019-09-05 14:39:18 -04:00
public static LiveData < Boolean > getUiBlockingMigrationStatus ( ) {
2019-07-29 19:02:40 -04:00
return UI_BLOCKING_MIGRATION_RUNNING ;
}
2019-09-05 14:39:18 -04:00
/ * *
* @return True if a UI blocking migration is running .
* /
public static boolean isUiBlockingMigrationRunning ( ) {
Boolean value = UI_BLOCKING_MIGRATION_RUNNING . getValue ( ) ;
return value ! = null & & value ;
}
2019-07-29 19:02:40 -04:00
/ * *
* @return Whether or not we ' re in the middle of an update , as determined by the last seen and
* current version .
* /
2019-09-05 14:39:18 -04:00
public static boolean isUpdate ( @NonNull Context context ) {
return isLegacyUpdate ( context ) | | TextSecurePreferences . getAppMigrationVersion ( context ) < CURRENT_VERSION ;
2019-07-29 19:02:40 -04:00
}
2019-09-05 14:39:18 -04:00
private static LinkedHashMap < Integer , MigrationJob > getMigrationJobs ( @NonNull Context context , int lastSeenVersion ) {
LinkedHashMap < Integer , MigrationJob > jobs = new LinkedHashMap < > ( ) ;
2019-07-29 19:02:40 -04:00
if ( lastSeenVersion < Version . LEGACY ) {
2019-09-05 14:39:18 -04:00
jobs . put ( Version . LEGACY , new LegacyMigrationJob ( ) ) ;
2019-07-29 19:02:40 -04:00
}
2019-08-07 14:22:51 -04:00
if ( lastSeenVersion < Version . RECIPIENT_ID ) {
2019-09-05 14:39:18 -04:00
jobs . put ( Version . RECIPIENT_ID , new DatabaseMigrationJob ( ) ) ;
2019-08-07 14:22:51 -04:00
}
2019-08-26 18:09:01 -04:00
if ( lastSeenVersion < Version . RECIPIENT_SEARCH ) {
2019-09-05 14:39:18 -04:00
jobs . put ( Version . RECIPIENT_SEARCH , new RecipientSearchMigrationJob ( ) ) ;
2019-08-26 18:09:01 -04:00
}
2019-07-29 19:02:40 -04:00
return jobs ;
}
2019-09-05 14:39:18 -04:00
private static boolean isLegacyUpdate ( @NonNull Context context ) {
return VersionTracker . getLastSeenVersion ( context ) < LEGACY_CANONICAL_VERSION ;
}
2019-07-29 19:02:40 -04:00
}