Merge "Removing remaining code around ENABLE_DATABASE_RESTORE" into udc-dev

This commit is contained in:
Sunny Goyal
2023-04-13 19:51:04 +00:00
committed by Android (Google) Code Review
9 changed files with 132 additions and 482 deletions
@@ -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) {