Merge "Removing remaining code around ENABLE_DATABASE_RESTORE" into udc-dev
This commit is contained in:
@@ -21,7 +21,6 @@ import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import com.android.launcher3.InvariantDeviceProfile;
|
||||
import com.android.launcher3.LauncherAppState;
|
||||
import com.android.launcher3.LauncherSettings;
|
||||
import com.android.launcher3.model.GridBackupTable;
|
||||
@@ -42,10 +41,7 @@ public class HotseatRestoreHelper {
|
||||
context.getContentResolver(),
|
||||
LauncherSettings.Settings.METHOD_NEW_TRANSACTION)
|
||||
.getBinder(LauncherSettings.Settings.EXTRA_VALUE)) {
|
||||
InvariantDeviceProfile idp = LauncherAppState.getIDP(context);
|
||||
GridBackupTable backupTable = new GridBackupTable(context,
|
||||
transaction.getDb(), idp.numDatabaseHotseatIcons, idp.numColumns,
|
||||
idp.numRows);
|
||||
GridBackupTable backupTable = new GridBackupTable(context, transaction.getDb());
|
||||
backupTable.createCustomBackupTable(HYBRID_HOTSEAT_BACKUP_TABLE);
|
||||
transaction.commit();
|
||||
LauncherSettings.Settings.call(context.getContentResolver(),
|
||||
@@ -67,10 +63,7 @@ public class HotseatRestoreHelper {
|
||||
if (!tableExists(transaction.getDb(), HYBRID_HOTSEAT_BACKUP_TABLE)) {
|
||||
return;
|
||||
}
|
||||
InvariantDeviceProfile idp = LauncherAppState.getIDP(context);
|
||||
GridBackupTable backupTable = new GridBackupTable(context,
|
||||
transaction.getDb(), idp.numDatabaseHotseatIcons, idp.numColumns,
|
||||
idp.numRows);
|
||||
GridBackupTable backupTable = new GridBackupTable(context, transaction.getDb());
|
||||
backupTable.restoreFromCustomBackupTable(HYBRID_HOTSEAT_BACKUP_TABLE, true);
|
||||
transaction.commit();
|
||||
LauncherAppState.getInstance(context).getModel().forceReload();
|
||||
|
||||
@@ -107,12 +107,6 @@ public class AppWidgetsRestoredReceiver extends BroadcastReceiver {
|
||||
cursor.close();
|
||||
}
|
||||
}
|
||||
// attempt to update widget id in backup table as well
|
||||
new ContentWriter(context, ContentWriter.CommitParams.backupCommitParams(
|
||||
"appWidgetId=? and profileId=?", args))
|
||||
.put(LauncherSettings.Favorites.APPWIDGET_ID, newWidgetIds[i])
|
||||
.put(LauncherSettings.Favorites.RESTORED, state)
|
||||
.commit();
|
||||
}
|
||||
|
||||
LauncherAppState app = LauncherAppState.getInstanceNoCreate();
|
||||
|
||||
@@ -138,11 +138,6 @@ public class LauncherSettings {
|
||||
|
||||
public static final String TABLE_NAME = "favorites";
|
||||
|
||||
/**
|
||||
* Backup table created when the favorites table is modified during grid migration
|
||||
*/
|
||||
public static final String BACKUP_TABLE_NAME = "favorites_bakup";
|
||||
|
||||
/**
|
||||
* Backup table created when user hotseat is moved to workspace for hybrid hotseat
|
||||
*/
|
||||
@@ -164,12 +159,6 @@ public class LauncherSettings {
|
||||
public static final Uri CONTENT_URI = Uri.parse("content://"
|
||||
+ LauncherProvider.AUTHORITY + "/" + TABLE_NAME);
|
||||
|
||||
/**
|
||||
* The content:// style URL for "favorites_bakup" table
|
||||
*/
|
||||
public static final Uri BACKUP_CONTENT_URI = Uri.parse("content://"
|
||||
+ LauncherProvider.AUTHORITY + "/" + BACKUP_TABLE_NAME);
|
||||
|
||||
/**
|
||||
* The content:// style URL for "favorites_preview" table
|
||||
*/
|
||||
|
||||
@@ -15,18 +15,12 @@
|
||||
*/
|
||||
package com.android.launcher3.model;
|
||||
|
||||
import static com.android.launcher3.LauncherSettings.Favorites.BACKUP_TABLE_NAME;
|
||||
import static com.android.launcher3.provider.LauncherDbUtils.dropTable;
|
||||
import static com.android.launcher3.provider.LauncherDbUtils.tableExists;
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.os.Process;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.IntDef;
|
||||
|
||||
import com.android.launcher3.LauncherSettings.Favorites;
|
||||
import com.android.launcher3.pm.UserCache;
|
||||
@@ -36,50 +30,13 @@ import com.android.launcher3.pm.UserCache;
|
||||
* within the same data base.
|
||||
*/
|
||||
public class GridBackupTable {
|
||||
private static final String TAG = "GridBackupTable";
|
||||
|
||||
private static final int ID_PROPERTY = -1;
|
||||
|
||||
private static final String KEY_HOTSEAT_SIZE = Favorites.SCREEN;
|
||||
private static final String KEY_GRID_X_SIZE = Favorites.SPANX;
|
||||
private static final String KEY_GRID_Y_SIZE = Favorites.SPANY;
|
||||
private static final String KEY_DB_VERSION = Favorites.RANK;
|
||||
|
||||
public static final int OPTION_REQUIRES_SANITIZATION = 1;
|
||||
|
||||
/** STATE_NOT_FOUND indicates backup doesn't exist in the db. */
|
||||
private static final int STATE_NOT_FOUND = 0;
|
||||
/**
|
||||
* STATE_RAW indicates the backup has not yet been sanitized. This implies it might still
|
||||
* posses app info that doesn't exist in the workspace and needed to be sanitized before
|
||||
* put into use.
|
||||
*/
|
||||
private static final int STATE_RAW = 1;
|
||||
/** STATE_SANITIZED indicates the backup has already been sanitized, thus can be used as-is. */
|
||||
private static final int STATE_SANITIZED = 2;
|
||||
|
||||
private final Context mContext;
|
||||
private final SQLiteDatabase mDb;
|
||||
|
||||
private final int mOldHotseatSize;
|
||||
private final int mOldGridX;
|
||||
private final int mOldGridY;
|
||||
|
||||
private int mRestoredHotseatSize;
|
||||
private int mRestoredGridX;
|
||||
private int mRestoredGridY;
|
||||
|
||||
@IntDef({STATE_NOT_FOUND, STATE_RAW, STATE_SANITIZED})
|
||||
private @interface BackupState { }
|
||||
|
||||
public GridBackupTable(Context context, SQLiteDatabase db, int hotseatSize, int gridX,
|
||||
int gridY) {
|
||||
public GridBackupTable(Context context, SQLiteDatabase db) {
|
||||
mContext = context;
|
||||
mDb = db;
|
||||
|
||||
mOldHotseatSize = hotseatSize;
|
||||
mOldGridX = gridX;
|
||||
mOldGridY = gridY;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -89,7 +46,6 @@ public class GridBackupTable {
|
||||
long profileId = UserCache.INSTANCE.get(mContext).getSerialNumberForUser(
|
||||
Process.myUserHandle());
|
||||
copyTable(mDb, Favorites.TABLE_NAME, tableName, profileId);
|
||||
encodeDBProperties(0);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -114,78 +70,6 @@ public class GridBackupTable {
|
||||
private static void copyTable(SQLiteDatabase db, String from, String to, long userSerial) {
|
||||
dropTable(db, to);
|
||||
Favorites.addTableToDb(db, userSerial, false, to);
|
||||
db.execSQL("INSERT INTO " + to + " SELECT * FROM " + from + " where _id > " + ID_PROPERTY);
|
||||
}
|
||||
|
||||
private void encodeDBProperties(int options) {
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(Favorites._ID, ID_PROPERTY);
|
||||
values.put(KEY_DB_VERSION, mDb.getVersion());
|
||||
values.put(KEY_GRID_X_SIZE, mOldGridX);
|
||||
values.put(KEY_GRID_Y_SIZE, mOldGridY);
|
||||
values.put(KEY_HOTSEAT_SIZE, mOldHotseatSize);
|
||||
values.put(Favorites.OPTIONS, options);
|
||||
mDb.insert(BACKUP_TABLE_NAME, null, values);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load DB properties from grid backup table.
|
||||
*/
|
||||
public @BackupState int loadDBProperties() {
|
||||
try (Cursor c = mDb.query(BACKUP_TABLE_NAME, new String[] {
|
||||
KEY_DB_VERSION, // 0
|
||||
KEY_GRID_X_SIZE, // 1
|
||||
KEY_GRID_Y_SIZE, // 2
|
||||
KEY_HOTSEAT_SIZE, // 3
|
||||
Favorites.OPTIONS}, // 4
|
||||
"_id=" + ID_PROPERTY, null, null, null, null)) {
|
||||
if (!c.moveToNext()) {
|
||||
Log.e(TAG, "Meta data not found in backup table");
|
||||
return STATE_NOT_FOUND;
|
||||
}
|
||||
if (!validateDBVersion(mDb.getVersion(), c.getInt(0))) {
|
||||
return STATE_NOT_FOUND;
|
||||
}
|
||||
|
||||
mRestoredGridX = c.getInt(1);
|
||||
mRestoredGridY = c.getInt(2);
|
||||
mRestoredHotseatSize = c.getInt(3);
|
||||
boolean isSanitized = (c.getInt(4) & OPTION_REQUIRES_SANITIZATION) == 0;
|
||||
return isSanitized ? STATE_SANITIZED : STATE_RAW;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore workspace from raw backup if available.
|
||||
*/
|
||||
public boolean restoreFromRawBackupIfAvailable(long oldProfileId) {
|
||||
if (!tableExists(mDb, Favorites.BACKUP_TABLE_NAME)
|
||||
|| loadDBProperties() != STATE_RAW
|
||||
|| mOldHotseatSize != mRestoredHotseatSize
|
||||
|| mOldGridX != mRestoredGridX
|
||||
|| mOldGridY != mRestoredGridY) {
|
||||
// skip restore if dimensions in backup table differs from current setup.
|
||||
return false;
|
||||
}
|
||||
copyTable(mDb, Favorites.BACKUP_TABLE_NAME, Favorites.TABLE_NAME, oldProfileId);
|
||||
Log.d(TAG, "Backup restored");
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a backup on the workspace layout.
|
||||
*/
|
||||
public void doBackup(long profileId, int options) {
|
||||
copyTable(mDb, Favorites.TABLE_NAME, Favorites.BACKUP_TABLE_NAME, profileId);
|
||||
encodeDBProperties(options);
|
||||
}
|
||||
|
||||
private static boolean validateDBVersion(int expected, int actual) {
|
||||
if (expected != actual) {
|
||||
Log.e(TAG, String.format("Launcher.db version mismatch, expecting %d but %d was found",
|
||||
expected, actual));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
db.execSQL("INSERT INTO " + to + " SELECT * FROM " + from);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,21 +39,19 @@ import android.util.LongSparseArray;
|
||||
import android.util.SparseLongArray;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
|
||||
import com.android.launcher3.AppWidgetsRestoredReceiver;
|
||||
import com.android.launcher3.InvariantDeviceProfile;
|
||||
import com.android.launcher3.LauncherAppState;
|
||||
import com.android.launcher3.LauncherPrefs;
|
||||
import com.android.launcher3.LauncherSettings.Favorites;
|
||||
import com.android.launcher3.Utilities;
|
||||
import com.android.launcher3.logging.FileLog;
|
||||
import com.android.launcher3.model.DatabaseHelper;
|
||||
import com.android.launcher3.model.DeviceGridState;
|
||||
import com.android.launcher3.model.GridBackupTable;
|
||||
import com.android.launcher3.model.data.AppInfo;
|
||||
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
|
||||
import com.android.launcher3.model.data.WorkspaceItemInfo;
|
||||
import com.android.launcher3.pm.UserCache;
|
||||
import com.android.launcher3.provider.LauncherDbUtils.SQLiteTransaction;
|
||||
import com.android.launcher3.uioverrides.ApiWrapper;
|
||||
import com.android.launcher3.util.IntArray;
|
||||
@@ -108,7 +106,6 @@ public class RestoreDbTask {
|
||||
SQLiteDatabase db = helper.getWritableDatabase();
|
||||
try (SQLiteTransaction t = new SQLiteTransaction(db)) {
|
||||
RestoreDbTask task = new RestoreDbTask();
|
||||
task.backupWorkspace(context, db);
|
||||
task.sanitizeDB(context, helper, db, new BackupManager(context));
|
||||
task.restoreAppWidgetIdsIfExists(context);
|
||||
t.commit();
|
||||
@@ -119,49 +116,6 @@ public class RestoreDbTask {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore the workspace if backup is available.
|
||||
*/
|
||||
public static boolean restoreIfPossible(@NonNull Context context,
|
||||
@NonNull DatabaseHelper helper, @NonNull BackupManager backupManager) {
|
||||
final SQLiteDatabase db = helper.getWritableDatabase();
|
||||
try (SQLiteTransaction t = new SQLiteTransaction(db)) {
|
||||
RestoreDbTask task = new RestoreDbTask();
|
||||
task.restoreWorkspace(context, db, helper, backupManager);
|
||||
t.commit();
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
FileLog.e(TAG, "Failed to restore db", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Backup the workspace so that if things go south in restore, we can recover these entries.
|
||||
*/
|
||||
private void backupWorkspace(Context context, SQLiteDatabase db) throws Exception {
|
||||
InvariantDeviceProfile idp = LauncherAppState.getIDP(context);
|
||||
new GridBackupTable(context, db, idp.numDatabaseHotseatIcons, idp.numColumns, idp.numRows)
|
||||
.doBackup(getDefaultProfileId(db), GridBackupTable.OPTION_REQUIRES_SANITIZATION);
|
||||
}
|
||||
|
||||
private void restoreWorkspace(@NonNull Context context, @NonNull SQLiteDatabase db,
|
||||
@NonNull DatabaseHelper helper, @NonNull BackupManager backupManager)
|
||||
throws Exception {
|
||||
final InvariantDeviceProfile idp = LauncherAppState.getIDP(context);
|
||||
GridBackupTable backupTable = new GridBackupTable(context, db, idp.numDatabaseHotseatIcons,
|
||||
idp.numColumns, idp.numRows);
|
||||
if (backupTable.restoreFromRawBackupIfAvailable(getDefaultProfileId(db))) {
|
||||
int itemsDeleted = sanitizeDB(context, helper, db, backupManager);
|
||||
LauncherAppState.getInstance(context).getModel().forceReload();
|
||||
restoreAppWidgetIdsIfExists(context);
|
||||
if (itemsDeleted == 0) {
|
||||
// all the items are restored, we no longer need the backup table
|
||||
dropTable(db, Favorites.BACKUP_TABLE_NAME);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes the following changes in the provider DB.
|
||||
* 1. Removes all entries belonging to any profiles that were not restored.
|
||||
@@ -174,7 +128,8 @@ public class RestoreDbTask {
|
||||
*
|
||||
* @return number of items deleted.
|
||||
*/
|
||||
private int sanitizeDB(Context context, DatabaseHelper helper, SQLiteDatabase db,
|
||||
@VisibleForTesting
|
||||
protected int sanitizeDB(Context context, DatabaseHelper helper, SQLiteDatabase db,
|
||||
BackupManager backupManager) throws Exception {
|
||||
// Primary user ids
|
||||
long myProfileId = helper.getDefaultUserSerial();
|
||||
@@ -258,7 +213,7 @@ public class RestoreDbTask {
|
||||
}
|
||||
|
||||
// Override shortcuts
|
||||
maybeOverrideShortcuts(context, db, myProfileId);
|
||||
maybeOverrideShortcuts(context, helper, db, myProfileId);
|
||||
|
||||
return itemsDeleted;
|
||||
}
|
||||
@@ -388,8 +343,8 @@ public class RestoreDbTask {
|
||||
APP_WIDGET_IDS.to(IntArray.wrap(newIds).toConcatString()));
|
||||
}
|
||||
|
||||
protected static void maybeOverrideShortcuts(Context context, SQLiteDatabase db,
|
||||
long currentUser) {
|
||||
protected static void maybeOverrideShortcuts(Context context, DatabaseHelper helper,
|
||||
SQLiteDatabase db, long currentUser) {
|
||||
Map<String, LauncherActivityInfo> activityOverrides = ApiWrapper.getActivityOverrides(
|
||||
context);
|
||||
|
||||
@@ -412,8 +367,7 @@ public class RestoreDbTask {
|
||||
if (override != null) {
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(Favorites.PROFILE_ID,
|
||||
UserCache.INSTANCE.get(context).getSerialNumberForUser(
|
||||
override.getUser()));
|
||||
helper.getSerialNumberForUser(override.getUser()));
|
||||
values.put(Favorites.INTENT, AppInfo.makeLaunchIntent(override).toUri(0));
|
||||
db.update(Favorites.TABLE_NAME, values, String.format("%s=?", Favorites._ID),
|
||||
new String[]{String.valueOf(c.getInt(idIndex))});
|
||||
|
||||
@@ -118,21 +118,9 @@ public class ContentWriter {
|
||||
final String[] mSelectionArgs;
|
||||
|
||||
public CommitParams(String where, String[] selectionArgs) {
|
||||
this(LauncherSettings.Favorites.CONTENT_URI, where, selectionArgs);
|
||||
}
|
||||
|
||||
private CommitParams(Uri uri, String where, String[] selectionArgs) {
|
||||
mUri = uri;
|
||||
mUri = LauncherSettings.Favorites.CONTENT_URI;
|
||||
mWhere = where;
|
||||
mSelectionArgs = selectionArgs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates commit params for backup table.
|
||||
*/
|
||||
public static CommitParams backupCommitParams(String where, String[] selectionArgs) {
|
||||
return new CommitParams(
|
||||
LauncherSettings.Favorites.BACKUP_CONTENT_URI, where, selectionArgs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,211 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2020 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.launcher3.model;
|
||||
|
||||
import static android.content.pm.PackageManager.INSTALL_REASON_DEVICE_RESTORE;
|
||||
import static android.os.Process.myUserHandle;
|
||||
|
||||
import static com.android.launcher3.LauncherSettings.Favorites.BACKUP_TABLE_NAME;
|
||||
import static com.android.launcher3.LauncherSettings.Favorites.TABLE_NAME;
|
||||
import static com.android.launcher3.LauncherSettings.Favorites.addTableToDb;
|
||||
import static com.android.launcher3.provider.LauncherDbUtils.dropTable;
|
||||
import static com.android.launcher3.provider.LauncherDbUtils.tableExists;
|
||||
import static com.android.launcher3.util.LauncherModelHelper.APP_ICON;
|
||||
import static com.android.launcher3.util.LauncherModelHelper.NO__ICON;
|
||||
import static com.android.launcher3.util.LauncherModelHelper.SHORTCUT;
|
||||
import static com.android.launcher3.util.ReflectionHelpers.getField;
|
||||
import static com.android.launcher3.util.ReflectionHelpers.setField;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.spy;
|
||||
|
||||
import android.app.backup.BackupManager;
|
||||
import android.content.pm.PackageInstaller;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.os.UserHandle;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.LongSparseArray;
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import androidx.test.filters.SmallTest;
|
||||
|
||||
import com.android.launcher3.InvariantDeviceProfile;
|
||||
import com.android.launcher3.pm.UserCache;
|
||||
import com.android.launcher3.provider.RestoreDbTask;
|
||||
import com.android.launcher3.util.LauncherModelHelper;
|
||||
import com.android.launcher3.util.SafeCloseable;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
/**
|
||||
* Tests to verify backup and restore flow.
|
||||
*/
|
||||
@SmallTest
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class BackupRestoreTest {
|
||||
|
||||
private static final int PER_USER_RANGE = 200000;
|
||||
|
||||
|
||||
private long mCurrentMyProfileId;
|
||||
private long mOldMyProfileId;
|
||||
|
||||
private long mCurrentWorkProfileId;
|
||||
private long mOldWorkProfileId;
|
||||
|
||||
private BackupManager mBackupManager;
|
||||
private LauncherModelHelper mModelHelper;
|
||||
private SQLiteDatabase mDb;
|
||||
private InvariantDeviceProfile mIdp;
|
||||
|
||||
private UserHandle mWorkUserHandle;
|
||||
|
||||
private SafeCloseable mUserChangeListener;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
mModelHelper = new LauncherModelHelper();
|
||||
|
||||
mCurrentMyProfileId = mModelHelper.defaultProfileId;
|
||||
mOldMyProfileId = mCurrentMyProfileId + 1;
|
||||
mCurrentWorkProfileId = mOldMyProfileId + 1;
|
||||
mOldWorkProfileId = mCurrentWorkProfileId + 1;
|
||||
|
||||
mWorkUserHandle = UserHandle.getUserHandleForUid(PER_USER_RANGE);
|
||||
mUserChangeListener = UserCache.INSTANCE.get(mModelHelper.sandboxContext)
|
||||
.addUserChangeListener(() -> { });
|
||||
|
||||
setupUserManager();
|
||||
setupBackupManager();
|
||||
RestoreDbTask.setPending(mModelHelper.sandboxContext);
|
||||
mDb = mModelHelper.provider.getDb();
|
||||
mIdp = InvariantDeviceProfile.INSTANCE.get(mModelHelper.sandboxContext);
|
||||
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
mUserChangeListener.close();
|
||||
mModelHelper.destroy();
|
||||
}
|
||||
|
||||
private void setupUserManager() {
|
||||
UserCache cache = UserCache.INSTANCE.get(mModelHelper.sandboxContext);
|
||||
synchronized (cache) {
|
||||
LongSparseArray<UserHandle> users = getField(cache, "mUsers");
|
||||
users.clear();
|
||||
users.put(mCurrentMyProfileId, myUserHandle());
|
||||
users.put(mCurrentWorkProfileId, mWorkUserHandle);
|
||||
|
||||
ArrayMap<UserHandle, Long> userMap = getField(cache, "mUserToSerialMap");
|
||||
userMap.clear();
|
||||
userMap.put(myUserHandle(), mCurrentMyProfileId);
|
||||
userMap.put(mWorkUserHandle, mCurrentWorkProfileId);
|
||||
}
|
||||
}
|
||||
|
||||
private void setupBackupManager() {
|
||||
mBackupManager = spy(new BackupManager(mModelHelper.sandboxContext));
|
||||
doReturn(myUserHandle()).when(mBackupManager)
|
||||
.getUserForAncestralSerialNumber(eq(mOldMyProfileId));
|
||||
doReturn(mWorkUserHandle).when(mBackupManager)
|
||||
.getUserForAncestralSerialNumber(eq(mOldWorkProfileId));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnCreateDbIfNotExists_CreatesBackup() {
|
||||
assertTrue(tableExists(mDb, BACKUP_TABLE_NAME));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnRestoreSessionWithValidCondition_PerformsRestore() throws Exception {
|
||||
setupBackup();
|
||||
verifyTableIsFilled(BACKUP_TABLE_NAME, false);
|
||||
verifyTableIsEmpty(TABLE_NAME);
|
||||
createRestoreSession();
|
||||
verifyTableIsFilled(TABLE_NAME, true);
|
||||
}
|
||||
|
||||
private void setupBackup() {
|
||||
createTableUsingOldProfileId();
|
||||
// setup grid for main user on first screen
|
||||
mModelHelper.createGrid(new int[][][]{{
|
||||
{ APP_ICON, APP_ICON, SHORTCUT, SHORTCUT},
|
||||
{ SHORTCUT, SHORTCUT, NO__ICON, NO__ICON},
|
||||
{ NO__ICON, NO__ICON, SHORTCUT, SHORTCUT},
|
||||
{ APP_ICON, SHORTCUT, SHORTCUT, APP_ICON},
|
||||
}}, 1, mOldMyProfileId);
|
||||
// setup grid for work profile on second screen
|
||||
mModelHelper.createGrid(new int[][][]{{
|
||||
{ NO__ICON, APP_ICON, SHORTCUT, SHORTCUT},
|
||||
{ SHORTCUT, SHORTCUT, NO__ICON, NO__ICON},
|
||||
{ NO__ICON, NO__ICON, SHORTCUT, SHORTCUT},
|
||||
{ APP_ICON, SHORTCUT, SHORTCUT, NO__ICON},
|
||||
}}, 2, mOldWorkProfileId);
|
||||
// simulates the creation of backup upon restore
|
||||
new GridBackupTable(mModelHelper.sandboxContext, mDb, mIdp.numDatabaseHotseatIcons,
|
||||
mIdp.numColumns, mIdp.numRows).doBackup(
|
||||
mOldMyProfileId, GridBackupTable.OPTION_REQUIRES_SANITIZATION);
|
||||
// reset favorites table
|
||||
createTableUsingOldProfileId();
|
||||
}
|
||||
|
||||
private void verifyTableIsEmpty(String tableName) {
|
||||
assertEquals(0, getCount(mDb, "SELECT * FROM " + tableName));
|
||||
}
|
||||
|
||||
private void verifyTableIsFilled(String tableName, boolean sanitized) {
|
||||
assertEquals(sanitized ? 12 : 13, getCount(mDb,
|
||||
"SELECT * FROM " + tableName + " WHERE profileId = "
|
||||
+ (sanitized ? mCurrentMyProfileId : mOldMyProfileId)));
|
||||
assertEquals(10, getCount(mDb, "SELECT * FROM " + tableName + " WHERE profileId = "
|
||||
+ (sanitized ? mCurrentWorkProfileId : mOldWorkProfileId)));
|
||||
}
|
||||
|
||||
private void createTableUsingOldProfileId() {
|
||||
// simulates the creation of favorites table on old device
|
||||
dropTable(mDb, TABLE_NAME);
|
||||
addTableToDb(mDb, mOldMyProfileId, false);
|
||||
}
|
||||
|
||||
private void createRestoreSession() throws Exception {
|
||||
final PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
|
||||
PackageInstaller.SessionParams.MODE_FULL_INSTALL);
|
||||
final PackageInstaller installer = mModelHelper.sandboxContext.getPackageManager()
|
||||
.getPackageInstaller();
|
||||
final int sessionId = installer.createSession(params);
|
||||
final PackageInstaller.SessionInfo info = installer.getSessionInfo(sessionId);
|
||||
setField(info, "installReason", INSTALL_REASON_DEVICE_RESTORE);
|
||||
// TODO: (b/148410677) we should verify the following call instead
|
||||
// InstallSessionHelper.INSTANCE.get(getContext()).restoreDbIfApplicable(info);
|
||||
RestoreDbTask.restoreIfPossible(mModelHelper.sandboxContext,
|
||||
mModelHelper.provider.getHelper(), mBackupManager);
|
||||
}
|
||||
|
||||
private static int getCount(SQLiteDatabase db, String sql) {
|
||||
try (Cursor c = db.rawQuery(sql, null)) {
|
||||
return c.getCount();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,17 +15,35 @@
|
||||
*/
|
||||
package com.android.launcher3.provider;
|
||||
|
||||
import static android.os.Process.myUserHandle;
|
||||
|
||||
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
|
||||
|
||||
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP;
|
||||
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
|
||||
import static com.android.launcher3.LauncherSettings.Favorites.TABLE_NAME;
|
||||
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.spy;
|
||||
|
||||
import android.app.backup.BackupManager;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.os.UserHandle;
|
||||
import android.os.UserManager;
|
||||
import android.util.LongSparseArray;
|
||||
|
||||
import androidx.test.InstrumentationRegistry;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import androidx.test.filters.SmallTest;
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import com.android.launcher3.LauncherAppState;
|
||||
import com.android.launcher3.LauncherSettings;
|
||||
import com.android.launcher3.LauncherSettings.Favorites;
|
||||
import com.android.launcher3.model.DatabaseHelper;
|
||||
|
||||
@@ -39,6 +57,10 @@ import org.junit.runner.RunWith;
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class RestoreDbTaskTest {
|
||||
|
||||
private static final int PER_USER_RANGE = 200000;
|
||||
|
||||
private final UserHandle mWorkUser = UserHandle.getUserHandleForUid(PER_USER_RANGE);
|
||||
|
||||
@Test
|
||||
public void testGetProfileId() throws Exception {
|
||||
SQLiteDatabase db = new MyDatabaseHelper(23).getWritableDatabase();
|
||||
@@ -88,6 +110,90 @@ public class RestoreDbTaskTest {
|
||||
assertEquals(1, getCount(db, "select * from favorites where profileId = 33"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSanitizeDB_bothProfiles() throws Exception {
|
||||
Context context = getInstrumentation().getTargetContext();
|
||||
UserHandle myUser = myUserHandle();
|
||||
long myProfileId = context.getSystemService(UserManager.class)
|
||||
.getSerialNumberForUser(myUser);
|
||||
long myProfileId_old = myProfileId + 1;
|
||||
long workProfileId = myProfileId + 2;
|
||||
long workProfileId_old = myProfileId + 3;
|
||||
|
||||
MyDatabaseHelper helper = new MyDatabaseHelper(myProfileId);
|
||||
SQLiteDatabase db = helper.getWritableDatabase();
|
||||
BackupManager bm = spy(new BackupManager(context));
|
||||
doReturn(myUserHandle()).when(bm).getUserForAncestralSerialNumber(eq(myProfileId_old));
|
||||
doReturn(mWorkUser).when(bm).getUserForAncestralSerialNumber(eq(workProfileId_old));
|
||||
helper.users.put(workProfileId, mWorkUser);
|
||||
|
||||
addIconsBulk(helper, 10, 1, myProfileId_old);
|
||||
addIconsBulk(helper, 6, 2, workProfileId_old);
|
||||
assertEquals(10, getItemCountForProfile(db, myProfileId_old));
|
||||
assertEquals(6, getItemCountForProfile(db, workProfileId_old));
|
||||
|
||||
RestoreDbTask task = new RestoreDbTask();
|
||||
task.sanitizeDB(context, helper, helper.getWritableDatabase(), bm);
|
||||
|
||||
// All the data has been migrated to the new user ids
|
||||
assertEquals(0, getItemCountForProfile(db, myProfileId_old));
|
||||
assertEquals(0, getItemCountForProfile(db, workProfileId_old));
|
||||
assertEquals(10, getItemCountForProfile(db, myProfileId));
|
||||
assertEquals(6, getItemCountForProfile(db, workProfileId));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSanitizeDB_workItemsRemoved() throws Exception {
|
||||
Context context = getInstrumentation().getTargetContext();
|
||||
UserHandle myUser = myUserHandle();
|
||||
long myProfileId = context.getSystemService(UserManager.class)
|
||||
.getSerialNumberForUser(myUser);
|
||||
long myProfileId_old = myProfileId + 1;
|
||||
long workProfileId_old = myProfileId + 3;
|
||||
|
||||
MyDatabaseHelper helper = new MyDatabaseHelper(myProfileId);
|
||||
SQLiteDatabase db = helper.getWritableDatabase();
|
||||
BackupManager bm = spy(new BackupManager(context));
|
||||
doReturn(myUserHandle()).when(bm).getUserForAncestralSerialNumber(eq(myProfileId_old));
|
||||
// Work profile is not migrated
|
||||
doReturn(null).when(bm).getUserForAncestralSerialNumber(eq(workProfileId_old));
|
||||
|
||||
addIconsBulk(helper, 10, 1, myProfileId_old);
|
||||
addIconsBulk(helper, 6, 2, workProfileId_old);
|
||||
assertEquals(10, getItemCountForProfile(db, myProfileId_old));
|
||||
assertEquals(6, getItemCountForProfile(db, workProfileId_old));
|
||||
|
||||
RestoreDbTask task = new RestoreDbTask();
|
||||
task.sanitizeDB(context, helper, helper.getWritableDatabase(), bm);
|
||||
|
||||
// All the data has been migrated to the new user ids
|
||||
assertEquals(0, getItemCountForProfile(db, myProfileId_old));
|
||||
assertEquals(0, getItemCountForProfile(db, workProfileId_old));
|
||||
assertEquals(10, getItemCountForProfile(db, myProfileId));
|
||||
assertEquals(10, getCount(db, "select * from favorites"));
|
||||
}
|
||||
|
||||
private void addIconsBulk(DatabaseHelper helper, int count, int screen, long profileId) {
|
||||
int columns = LauncherAppState.getIDP(getInstrumentation().getTargetContext()).numColumns;
|
||||
String packageName = getInstrumentation().getContext().getPackageName();
|
||||
for (int i = 0; i < count; i++) {
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(LauncherSettings.Favorites._ID, helper.generateNewItemId());
|
||||
values.put(LauncherSettings.Favorites.CONTAINER, CONTAINER_DESKTOP);
|
||||
values.put(LauncherSettings.Favorites.SCREEN, screen);
|
||||
values.put(LauncherSettings.Favorites.CELLX, i % columns);
|
||||
values.put(LauncherSettings.Favorites.CELLY, i / columns);
|
||||
values.put(LauncherSettings.Favorites.SPANX, 1);
|
||||
values.put(LauncherSettings.Favorites.SPANY, 1);
|
||||
values.put(LauncherSettings.Favorites.PROFILE_ID, profileId);
|
||||
values.put(LauncherSettings.Favorites.ITEM_TYPE, ITEM_TYPE_APPLICATION);
|
||||
values.put(LauncherSettings.Favorites.INTENT,
|
||||
new Intent(Intent.ACTION_MAIN).setPackage(packageName).toUri(0));
|
||||
helper.getWritableDatabase().insert(TABLE_NAME, null, values);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testRemoveScreenIdGaps_firstScreenEmpty() {
|
||||
runRemoveScreenIdGapsTest(
|
||||
@@ -116,7 +222,7 @@ public class RestoreDbTaskTest {
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(Favorites._ID, i);
|
||||
values.put(Favorites.SCREEN, screenIds[i]);
|
||||
values.put(Favorites.CONTAINER, Favorites.CONTAINER_DESKTOP);
|
||||
values.put(Favorites.CONTAINER, CONTAINER_DESKTOP);
|
||||
db.insert(Favorites.TABLE_NAME, null, values);
|
||||
}
|
||||
// Verify items are added
|
||||
@@ -138,6 +244,10 @@ public class RestoreDbTaskTest {
|
||||
assertArrayEquals(expectedScreenIds, resultScreenIds);
|
||||
}
|
||||
|
||||
public int getItemCountForProfile(SQLiteDatabase db, long profileId) {
|
||||
return getCount(db, "select * from favorites where profileId = " + profileId);
|
||||
}
|
||||
|
||||
private int getCount(SQLiteDatabase db, String sql) {
|
||||
try (Cursor c = db.rawQuery(sql, null)) {
|
||||
return c.getCount();
|
||||
@@ -146,16 +256,18 @@ public class RestoreDbTaskTest {
|
||||
|
||||
private class MyDatabaseHelper extends DatabaseHelper {
|
||||
|
||||
private final long mProfileId;
|
||||
public final LongSparseArray<UserHandle> users;
|
||||
|
||||
MyDatabaseHelper(long profileId) {
|
||||
super(InstrumentationRegistry.getInstrumentation().getTargetContext(), null, false);
|
||||
mProfileId = profileId;
|
||||
super(getInstrumentation().getTargetContext(), null, false);
|
||||
users = new LongSparseArray<>();
|
||||
users.put(profileId, myUserHandle());
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getDefaultUserSerial() {
|
||||
return mProfileId;
|
||||
public long getSerialNumberForUser(UserHandle user) {
|
||||
int index = users.indexOfValue(user);
|
||||
return index >= 0 ? users.keyAt(index) : -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -61,7 +61,6 @@ import com.android.launcher3.LauncherSettings;
|
||||
import com.android.launcher3.model.AllAppsList;
|
||||
import com.android.launcher3.model.BgDataModel;
|
||||
import com.android.launcher3.model.BgDataModel.Callbacks;
|
||||
import com.android.launcher3.model.DatabaseHelper;
|
||||
import com.android.launcher3.model.ItemInstallQueue;
|
||||
import com.android.launcher3.model.ModelDbController;
|
||||
import com.android.launcher3.model.data.AppInfo;
|
||||
@@ -371,54 +370,6 @@ public class LauncherModelHelper {
|
||||
sandboxContext.getContentResolver().delete(uri, null, null);
|
||||
}
|
||||
|
||||
public int[][][] createGrid(int[][][] typeArray) {
|
||||
return createGrid(typeArray, 1);
|
||||
}
|
||||
|
||||
public int[][][] createGrid(int[][][] typeArray, int startScreen) {
|
||||
LauncherSettings.Settings.call(sandboxContext.getContentResolver(),
|
||||
LauncherSettings.Settings.METHOD_CREATE_EMPTY_DB);
|
||||
LauncherSettings.Settings.call(sandboxContext.getContentResolver(),
|
||||
LauncherSettings.Settings.METHOD_CLEAR_EMPTY_DB_FLAG);
|
||||
return createGrid(typeArray, startScreen, defaultProfileId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the DB with mock elements to represent the provided grid structure.
|
||||
* @param typeArray A 3d array of item types. {@see #addItem(int, long, long, int, int)} for
|
||||
* type definitions. The first dimension represents the screens and the next
|
||||
* two represent the workspace grid.
|
||||
* @param startScreen First screen id from where the icons will be added.
|
||||
* @return the same grid representation where each entry is the corresponding item id.
|
||||
*/
|
||||
public int[][][] createGrid(int[][][] typeArray, int startScreen, long profileId) {
|
||||
int[][][] ids = new int[typeArray.length][][];
|
||||
for (int i = 0; i < typeArray.length; i++) {
|
||||
// Add screen to DB
|
||||
int screenId = startScreen + i;
|
||||
|
||||
// Keep the screen id counter up to date
|
||||
LauncherSettings.Settings.call(sandboxContext.getContentResolver(),
|
||||
LauncherSettings.Settings.METHOD_NEW_SCREEN_ID);
|
||||
|
||||
ids[i] = new int[typeArray[i].length][];
|
||||
for (int y = 0; y < typeArray[i].length; y++) {
|
||||
ids[i][y] = new int[typeArray[i][y].length];
|
||||
for (int x = 0; x < typeArray[i][y].length; x++) {
|
||||
if (typeArray[i][y][x] < 0) {
|
||||
// Empty cell
|
||||
ids[i][y][x] = -1;
|
||||
} else {
|
||||
ids[i][y][x] = addItem(
|
||||
typeArray[i][y][x], screenId, DESKTOP, x, y, profileId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ids;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up a mock provider to load the provided layout by default, next time the layout loads
|
||||
*/
|
||||
@@ -479,10 +430,6 @@ public class LauncherModelHelper {
|
||||
public SQLiteDatabase getDb() {
|
||||
return mModelDbController.getDatabaseHelper().getWritableDatabase();
|
||||
}
|
||||
|
||||
public DatabaseHelper getHelper() {
|
||||
return mModelDbController.getDatabaseHelper();
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean deleteContents(File dir) {
|
||||
|
||||
Reference in New Issue
Block a user