diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java index b58956078d..b0ab35c5b0 100644 --- a/src/com/android/launcher3/LauncherProvider.java +++ b/src/com/android/launcher3/LauncherProvider.java @@ -18,6 +18,7 @@ package com.android.launcher3; import static com.android.launcher3.provider.LauncherDbUtils.dropTable; import static com.android.launcher3.provider.LauncherDbUtils.tableExists; +import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; import android.annotation.TargetApi; import android.app.backup.BackupManager; @@ -45,6 +46,7 @@ import android.net.Uri; import android.os.Binder; import android.os.Build; import android.os.Bundle; +import android.os.Handler; import android.os.Process; import android.os.UserHandle; import android.os.UserManager; @@ -87,6 +89,8 @@ public class LauncherProvider extends ContentProvider { private static final boolean LOGD = false; private static final String DOWNGRADE_SCHEMA_FILE = "downgrade_schema.json"; + private static final String TOKEN_RESTORE_BACKUP_TABLE = "restore_backup_table"; + private static final long RESTORE_BACKUP_TABLE_DELAY = 60000; /** * Represents the schema of the database. Changes in scheme need not be backwards compatible. @@ -388,8 +392,11 @@ public class LauncherProvider extends ContentProvider { return null; } case LauncherSettings.Settings.METHOD_RESTORE_BACKUP_TABLE: { - RestoreDbTask.restoreIfPossible( - getContext(), mOpenHelper, new BackupManager(getContext())); + final Handler handler = MODEL_EXECUTOR.getHandler(); + handler.removeCallbacksAndMessages(TOKEN_RESTORE_BACKUP_TABLE); + handler.postDelayed(() -> RestoreDbTask.restoreIfPossible( + getContext(), mOpenHelper, new BackupManager(getContext())), + TOKEN_RESTORE_BACKUP_TABLE, RESTORE_BACKUP_TABLE_DELAY); return null; } } diff --git a/src/com/android/launcher3/SessionCommitReceiver.java b/src/com/android/launcher3/SessionCommitReceiver.java index f0bae021d5..89f0a3d286 100644 --- a/src/com/android/launcher3/SessionCommitReceiver.java +++ b/src/com/android/launcher3/SessionCommitReceiver.java @@ -78,6 +78,7 @@ public class SessionCommitReceiver extends BroadcastReceiver { } InstallSessionHelper packageInstallerCompat = InstallSessionHelper.INSTANCE.get(context); + packageInstallerCompat.restoreDbIfApplicable(info); if (TextUtils.isEmpty(info.getAppPackageName()) || info.getInstallReason() != PackageManager.INSTALL_REASON_USER || packageInstallerCompat.promiseIconAddedForId(info.getSessionId())) { diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java index 81dcba3a17..75609fe68e 100644 --- a/src/com/android/launcher3/config/FeatureFlags.java +++ b/src/com/android/launcher3/config/FeatureFlags.java @@ -136,6 +136,10 @@ public final class FeatureFlags { public static final TogglableFlag ENABLE_OVERVIEW_ACTIONS = new TogglableFlag( "ENABLE_OVERVIEW_ACTIONS", false, "Show app actions in Overview"); + public static final TogglableFlag ENABLE_DATABASE_RESTORE = new TogglableFlag( + "ENABLE_DATABASE_RESTORE", true, + "Enable database restore when new restore session is created"); + public static void initialize(Context context) { // Avoid the disk read for user builds if (Utilities.IS_DEBUG_DEVICE) { diff --git a/src/com/android/launcher3/pm/InstallSessionHelper.java b/src/com/android/launcher3/pm/InstallSessionHelper.java index 186293f700..976d7ba5a0 100644 --- a/src/com/android/launcher3/pm/InstallSessionHelper.java +++ b/src/com/android/launcher3/pm/InstallSessionHelper.java @@ -29,6 +29,10 @@ import android.os.Process; import android.os.UserHandle; import android.text.TextUtils; +import androidx.annotation.NonNull; +import androidx.annotation.RequiresApi; + +import com.android.launcher3.LauncherSettings; import com.android.launcher3.SessionCommitReceiver; import com.android.launcher3.Utilities; import com.android.launcher3.config.FeatureFlags; @@ -52,6 +56,8 @@ public class InstallSessionHelper { // Set of session ids of promise icons that have been added to the home screen // as FLAG_PROMISE_NEW_INSTALLS. protected static final String PROMISE_ICON_IDS = "promise_icon_ids"; + public static final String KEY_INSTALL_SESSION_CREATED_TIMESTAMP = + "key_install_session_created_timestamp"; private static final boolean DEBUG = false; @@ -159,6 +165,34 @@ public class InstallSessionHelper { return list; } + /** + * Attempt to restore workspace layout if the session is triggered due to device restore and it + * has a newer timestamp. + */ + public boolean restoreDbIfApplicable(@NonNull final SessionInfo info) { + if (!Utilities.ATLEAST_OREO || !FeatureFlags.ENABLE_DATABASE_RESTORE.get()) { + return false; + } + if (isRestore(info) && hasNewerTimestamp(mAppContext, info)) { + LauncherSettings.Settings.call(mAppContext.getContentResolver(), + LauncherSettings.Settings.METHOD_RESTORE_BACKUP_TABLE); + return true; + } + return false; + } + + @RequiresApi(26) + private static boolean isRestore(@NonNull final SessionInfo info) { + return info.getInstallReason() == PackageManager.INSTALL_REASON_DEVICE_RESTORE; + } + + private static boolean hasNewerTimestamp( + @NonNull final Context context, @NonNull final SessionInfo info) { + return PackageManagerHelper.getSessionCreatedTimeInMillis(info) + > Utilities.getDevicePrefs(context).getLong( + KEY_INSTALL_SESSION_CREATED_TIMESTAMP, 0); + } + public boolean promiseIconAddedForId(int sessionId) { return mPromiseIconIds.contains(sessionId); } diff --git a/src/com/android/launcher3/provider/RestoreDbTask.java b/src/com/android/launcher3/provider/RestoreDbTask.java index 7ee30cca77..407ff3153d 100644 --- a/src/com/android/launcher3/provider/RestoreDbTask.java +++ b/src/com/android/launcher3/provider/RestoreDbTask.java @@ -16,6 +16,7 @@ package com.android.launcher3.provider; +import static com.android.launcher3.pm.InstallSessionHelper.KEY_INSTALL_SESSION_CREATED_TIMESTAMP; import static com.android.launcher3.provider.LauncherDbUtils.dropTable; import android.app.backup.BackupManager; @@ -86,6 +87,8 @@ public class RestoreDbTask { */ public static boolean restoreIfPossible(@NonNull Context context, @NonNull DatabaseHelper helper, @NonNull BackupManager backupManager) { + Utilities.getDevicePrefs(context).edit().putLong( + KEY_INSTALL_SESSION_CREATED_TIMESTAMP, System.currentTimeMillis()).apply(); final SQLiteDatabase db = helper.getWritableDatabase(); try (SQLiteTransaction t = new SQLiteTransaction(db)) { RestoreDbTask task = new RestoreDbTask(); diff --git a/src/com/android/launcher3/util/PackageManagerHelper.java b/src/com/android/launcher3/util/PackageManagerHelper.java index 8b2ee36cb9..6c187475d3 100644 --- a/src/com/android/launcher3/util/PackageManagerHelper.java +++ b/src/com/android/launcher3/util/PackageManagerHelper.java @@ -16,6 +16,7 @@ package com.android.launcher3.util; +import static android.content.pm.PackageInstaller.SessionInfo; import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY; import android.app.AppOpsManager; @@ -44,6 +45,8 @@ import android.util.Log; import android.util.Pair; import android.widget.Toast; +import androidx.annotation.NonNull; + import com.android.launcher3.AppInfo; import com.android.launcher3.ItemInfo; import com.android.launcher3.LauncherAppWidgetInfo; @@ -345,4 +348,15 @@ public class PackageManagerHelper { } return false; } + + /** + * Returns the created time in millis of given session info. Returns 0 if not available. + */ + public static long getSessionCreatedTimeInMillis(@NonNull final SessionInfo info) { + try { + return (long) SessionInfo.class.getDeclaredMethod("getCreatedMillis").invoke(info); + } catch (Exception e) { + return 0; + } + } }