Create new logic for grid migration
Fixes 217564863
Test: manual, changing grids from Wallpaper & Style and checking against spec
Change-Id: I94cf77111b37810282527f1a212b6e4126d3eba1
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index a949e11..6116c66 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -256,6 +256,10 @@
"ENABLE_SPLIT_FROM_WORKSPACE", true,
"Enable initiating split screen from workspace.");
+ public static final BooleanFlag ENABLE_NEW_MIGRATION_LOGIC = getDebugFlag(
+ "ENABLE_NEW_MIGRATION_LOGIC", true,
+ "Enable the new grid migration logic, keeping pages when src < dest");
+
public static void initialize(Context context) {
synchronized (sDebugFlags) {
for (DebugFlag flag : sDebugFlags) {
diff --git a/src/com/android/launcher3/model/DeviceGridState.java b/src/com/android/launcher3/model/DeviceGridState.java
index 08c3149..3e49d79 100644
--- a/src/com/android/launcher3/model/DeviceGridState.java
+++ b/src/com/android/launcher3/model/DeviceGridState.java
@@ -38,7 +38,7 @@
/**
* Utility class representing persisted grid properties.
*/
-public class DeviceGridState {
+public class DeviceGridState implements Comparable<DeviceGridState> {
public static final String KEY_WORKSPACE_SIZE = "migration_src_workspace_size";
public static final String KEY_HOTSEAT_COUNT = "migration_src_hotseat_count";
@@ -84,16 +84,16 @@
*/
public LauncherEvent getWorkspaceSizeEvent() {
if (!TextUtils.isEmpty(mGridSizeString)) {
- switch (mGridSizeString.charAt(0)) {
- case '6':
+ switch (getColumns()) {
+ case 6:
return LAUNCHER_GRID_SIZE_6;
- case '5':
+ case 5:
return LAUNCHER_GRID_SIZE_5;
- case '4':
+ case 4:
return LAUNCHER_GRID_SIZE_4;
- case '3':
+ case 3:
return LAUNCHER_GRID_SIZE_3;
- case '2':
+ case 2:
return LAUNCHER_GRID_SIZE_2;
}
}
@@ -119,4 +119,21 @@
return mNumHotseat == other.mNumHotseat
&& Objects.equals(mGridSizeString, other.mGridSizeString);
}
+
+ public Integer getColumns() {
+ return Integer.parseInt(String.valueOf(mGridSizeString.charAt(0)));
+ }
+
+ public Integer getRows() {
+ return Integer.parseInt(String.valueOf(mGridSizeString.charAt(2)));
+ }
+
+ @Override
+ public int compareTo(DeviceGridState other) {
+ Integer size = getColumns() * getRows();
+ Integer otherSize = other.getColumns() * other.getRows();
+
+ return size.compareTo(otherSize);
+ }
+
}
diff --git a/src/com/android/launcher3/model/GridSizeMigrationTaskV2.java b/src/com/android/launcher3/model/GridSizeMigrationTaskV2.java
index ca680b7..74b0a6f 100644
--- a/src/com/android/launcher3/model/GridSizeMigrationTaskV2.java
+++ b/src/com/android/launcher3/model/GridSizeMigrationTaskV2.java
@@ -38,6 +38,7 @@
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.Utilities;
+import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.graphics.LauncherPreviewRenderer;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.pm.InstallSessionHelper;
@@ -225,13 +226,21 @@
screens.add(screenId);
}
+ boolean preservePages = false;
+ if (screens.isEmpty() && FeatureFlags.ENABLE_NEW_MIGRATION_LOGIC.get()) {
+ DeviceGridState srcDeviceState = new DeviceGridState(mContext);
+ DeviceGridState destDeviceState = new DeviceGridState(idp);
+ preservePages = destDeviceState.compareTo(srcDeviceState) >= 0
+ && destDeviceState.getColumns() - srcDeviceState.getColumns() <= 2;
+ }
+
// Then we place the items on the screens
for (int screenId : screens) {
if (DEBUG) {
Log.d(TAG, "Migrating " + screenId);
}
GridPlacementSolution workspaceSolution = new GridPlacementSolution(mDb, mSrcReader,
- mDestReader, mContext, screenId, mTrgX, mTrgY, mWorkspaceDiff);
+ mDestReader, mContext, screenId, mTrgX, mTrgY, mWorkspaceDiff, false);
workspaceSolution.find();
if (mWorkspaceDiff.isEmpty()) {
break;
@@ -243,10 +252,12 @@
int screenId = mDestReader.mLastScreenId + 1;
while (!mWorkspaceDiff.isEmpty()) {
GridPlacementSolution workspaceSolution = new GridPlacementSolution(mDb, mSrcReader,
- mDestReader, mContext, screenId, mTrgX, mTrgY, mWorkspaceDiff);
+ mDestReader, mContext, screenId, mTrgX, mTrgY, mWorkspaceDiff,
+ preservePages);
workspaceSolution.find();
screenId++;
}
+
return true;
}
@@ -363,13 +374,15 @@
private final int mScreenId;
private final int mTrgX;
private final int mTrgY;
- private final List<DbEntry> mItemsToPlace;
+ private final List<DbEntry> mSortedItemsToPlace;
+ private final boolean mMatchingScreenIdOnly;
private int mNextStartX;
private int mNextStartY;
GridPlacementSolution(SQLiteDatabase db, DbReader srcReader, DbReader destReader,
- Context context, int screenId, int trgX, int trgY, List<DbEntry> itemsToPlace) {
+ Context context, int screenId, int trgX, int trgY, List<DbEntry> sortedItemsToPlace,
+ boolean matchingScreenIdOnly) {
mDb = db;
mSrcReader = srcReader;
mDestReader = destReader;
@@ -386,13 +399,16 @@
mOccupied.markCells(entry, true);
}
}
- mItemsToPlace = itemsToPlace;
+ mSortedItemsToPlace = sortedItemsToPlace;
+ mMatchingScreenIdOnly = matchingScreenIdOnly;
}
public void find() {
- Iterator<DbEntry> iterator = mItemsToPlace.iterator();
+ Iterator<DbEntry> iterator = mSortedItemsToPlace.iterator();
while (iterator.hasNext()) {
final DbEntry entry = iterator.next();
+ if (mMatchingScreenIdOnly && entry.screenId < mScreenId) continue;
+ if (mMatchingScreenIdOnly && entry.screenId > mScreenId) break;
if (entry.minSpanX > mTrgX || entry.minSpanY > mTrgY) {
iterator.remove();
continue;
@@ -494,7 +510,7 @@
private final SQLiteDatabase mDb;
private final String mTableName;
private final Context mContext;
- private final HashSet<String> mValidPackages;
+ private final Set<String> mValidPackages;
private int mLastScreenId = -1;
private final ArrayList<DbEntry> mHotseatEntries = new ArrayList<>();
@@ -503,7 +519,7 @@
new ArrayMap<>();
DbReader(SQLiteDatabase db, String tableName, Context context,
- HashSet<String> validPackages) {
+ Set<String> validPackages) {
mDb = db;
mTableName = tableName;
mContext = context;
diff --git a/tests/src/com/android/launcher3/model/GridSizeMigrationTaskV2Test.java b/tests/src/com/android/launcher3/model/GridSizeMigrationTaskV2Test.java
deleted file mode 100644
index 005389e..0000000
--- a/tests/src/com/android/launcher3/model/GridSizeMigrationTaskV2Test.java
+++ /dev/null
@@ -1,278 +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 com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP;
-import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT;
-import static com.android.launcher3.LauncherSettings.Favorites.TMP_CONTENT_URI;
-import static com.android.launcher3.provider.LauncherDbUtils.dropTable;
-import static com.android.launcher3.util.LauncherModelHelper.APP_ICON;
-import static com.android.launcher3.util.LauncherModelHelper.DESKTOP;
-import static com.android.launcher3.util.LauncherModelHelper.HOTSEAT;
-import static com.android.launcher3.util.LauncherModelHelper.SHORTCUT;
-import static com.android.launcher3.util.LauncherModelHelper.TEST_PACKAGE;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-
-import android.content.Context;
-import android.content.Intent;
-import android.database.Cursor;
-import android.database.sqlite.SQLiteDatabase;
-import android.graphics.Point;
-import android.os.Process;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-
-import com.android.launcher3.InvariantDeviceProfile;
-import com.android.launcher3.LauncherSettings;
-import com.android.launcher3.pm.UserCache;
-import com.android.launcher3.util.LauncherModelHelper;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.HashMap;
-import java.util.HashSet;
-
-/** Unit tests for {@link GridSizeMigrationTaskV2} */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class GridSizeMigrationTaskV2Test {
-
- private LauncherModelHelper mModelHelper;
- private Context mContext;
- private SQLiteDatabase mDb;
-
- private HashSet<String> mValidPackages;
- private InvariantDeviceProfile mIdp;
-
- private final String testPackage1 = "com.android.launcher3.validpackage1";
- private final String testPackage2 = "com.android.launcher3.validpackage2";
- private final String testPackage3 = "com.android.launcher3.validpackage3";
- private final String testPackage4 = "com.android.launcher3.validpackage4";
- private final String testPackage5 = "com.android.launcher3.validpackage5";
- private final String testPackage6 = "com.android.launcher3.validpackage6";
- private final String testPackage7 = "com.android.launcher3.validpackage7";
- private final String testPackage8 = "com.android.launcher3.validpackage8";
- private final String testPackage9 = "com.android.launcher3.validpackage9";
- private final String testPackage10 = "com.android.launcher3.validpackage10";
-
- @Before
- public void setUp() {
- mModelHelper = new LauncherModelHelper();
- mContext = mModelHelper.sandboxContext;
- mDb = mModelHelper.provider.getDb();
-
- mValidPackages = new HashSet<>();
- mValidPackages.add(TEST_PACKAGE);
- mValidPackages.add(testPackage1);
- mValidPackages.add(testPackage2);
- mValidPackages.add(testPackage3);
- mValidPackages.add(testPackage4);
- mValidPackages.add(testPackage5);
- mValidPackages.add(testPackage6);
- mValidPackages.add(testPackage7);
- mValidPackages.add(testPackage8);
- mValidPackages.add(testPackage9);
- mValidPackages.add(testPackage10);
-
- mIdp = InvariantDeviceProfile.INSTANCE.get(mContext);
-
- long userSerial = UserCache.INSTANCE.get(mContext).getSerialNumberForUser(
- Process.myUserHandle());
- dropTable(mDb, LauncherSettings.Favorites.TMP_TABLE);
- LauncherSettings.Favorites.addTableToDb(mDb, userSerial, false,
- LauncherSettings.Favorites.TMP_TABLE);
- }
-
- @After
- public void tearDown() {
- mModelHelper.destroy();
- }
-
- @Test
- public void testMigration() throws Exception {
- int[] srcHotseatItems = {
- mModelHelper.addItem(APP_ICON, 0, HOTSEAT, 0, 0, testPackage1, 1, TMP_CONTENT_URI),
- mModelHelper.addItem(SHORTCUT, 1, HOTSEAT, 0, 0, testPackage2, 2, TMP_CONTENT_URI),
- -1,
- mModelHelper.addItem(SHORTCUT, 3, HOTSEAT, 0, 0, testPackage3, 3, TMP_CONTENT_URI),
- mModelHelper.addItem(APP_ICON, 4, HOTSEAT, 0, 0, testPackage4, 4, TMP_CONTENT_URI),
- };
- mModelHelper.addItem(APP_ICON, 0, DESKTOP, 2, 2, testPackage5, 5, TMP_CONTENT_URI);
- mModelHelper.addItem(APP_ICON, 0, DESKTOP, 2, 3, testPackage6, 6, TMP_CONTENT_URI);
- mModelHelper.addItem(APP_ICON, 0, DESKTOP, 4, 1, testPackage8, 8, TMP_CONTENT_URI);
- mModelHelper.addItem(APP_ICON, 0, DESKTOP, 4, 2, testPackage9, 9, TMP_CONTENT_URI);
- mModelHelper.addItem(APP_ICON, 0, DESKTOP, 4, 3, testPackage10, 10, TMP_CONTENT_URI);
-
- int[] destHotseatItems = {
- -1,
- mModelHelper.addItem(SHORTCUT, 1, HOTSEAT, 0, 0, testPackage2),
- -1,
- };
- mModelHelper.addItem(APP_ICON, 0, DESKTOP, 2, 2, testPackage7);
-
- mIdp.numDatabaseHotseatIcons = 4;
- mIdp.numColumns = 4;
- mIdp.numRows = 4;
- GridSizeMigrationTaskV2.DbReader srcReader = new GridSizeMigrationTaskV2.DbReader(mDb,
- LauncherSettings.Favorites.TMP_TABLE, mContext, mValidPackages);
- GridSizeMigrationTaskV2.DbReader destReader = new GridSizeMigrationTaskV2.DbReader(mDb,
- LauncherSettings.Favorites.TABLE_NAME, mContext, mValidPackages);
- GridSizeMigrationTaskV2 task = new GridSizeMigrationTaskV2(mContext, mDb, srcReader,
- destReader, mIdp.numDatabaseHotseatIcons, new Point(mIdp.numColumns, mIdp.numRows));
- task.migrate(mIdp);
-
- // Check hotseat items
- Cursor c = mContext.getContentResolver().query(LauncherSettings.Favorites.CONTENT_URI,
- new String[]{LauncherSettings.Favorites.SCREEN, LauncherSettings.Favorites.INTENT},
- "container=" + CONTAINER_HOTSEAT, null, LauncherSettings.Favorites.SCREEN, null);
- assertEquals(c.getCount(), mIdp.numDatabaseHotseatIcons);
- int screenIndex = c.getColumnIndex(LauncherSettings.Favorites.SCREEN);
- int intentIndex = c.getColumnIndex(LauncherSettings.Favorites.INTENT);
- c.moveToNext();
- assertEquals(c.getInt(screenIndex), 0);
- assertTrue(c.getString(intentIndex).contains(testPackage1));
- c.moveToNext();
- assertEquals(c.getInt(screenIndex), 1);
- assertTrue(c.getString(intentIndex).contains(testPackage2));
- c.moveToNext();
- assertEquals(c.getInt(screenIndex), 2);
- assertTrue(c.getString(intentIndex).contains(testPackage3));
- c.moveToNext();
- assertEquals(c.getInt(screenIndex), 3);
- assertTrue(c.getString(intentIndex).contains(testPackage4));
- c.close();
-
- // Check workspace items
- c = mContext.getContentResolver().query(LauncherSettings.Favorites.CONTENT_URI,
- new String[]{LauncherSettings.Favorites.CELLX, LauncherSettings.Favorites.CELLY,
- LauncherSettings.Favorites.INTENT},
- "container=" + CONTAINER_DESKTOP, null, null, null);
- intentIndex = c.getColumnIndex(LauncherSettings.Favorites.INTENT);
- int cellXIndex = c.getColumnIndex(LauncherSettings.Favorites.CELLX);
- int cellYIndex = c.getColumnIndex(LauncherSettings.Favorites.CELLY);
-
- HashMap<String, Point> locMap = new HashMap<>();
- while (c.moveToNext()) {
- locMap.put(
- Intent.parseUri(c.getString(intentIndex), 0).getPackage(),
- new Point(c.getInt(cellXIndex), c.getInt(cellYIndex)));
- }
- c.close();
-
- assertEquals(locMap.size(), 6);
- assertEquals(new Point(0, 2), locMap.get(testPackage8));
- assertEquals(new Point(0, 3), locMap.get(testPackage6));
- assertEquals(new Point(1, 3), locMap.get(testPackage10));
- assertEquals(new Point(2, 3), locMap.get(testPackage5));
- assertEquals(new Point(3, 3), locMap.get(testPackage9));
- }
-
- @Test
- public void migrateToLargerHotseat() {
- int[] srcHotseatItems = {
- mModelHelper.addItem(APP_ICON, 0, HOTSEAT, 0, 0, testPackage1, 1, TMP_CONTENT_URI),
- mModelHelper.addItem(SHORTCUT, 1, HOTSEAT, 0, 0, testPackage2, 2, TMP_CONTENT_URI),
- mModelHelper.addItem(APP_ICON, 2, HOTSEAT, 0, 0, testPackage3, 3, TMP_CONTENT_URI),
- mModelHelper.addItem(SHORTCUT, 3, HOTSEAT, 0, 0, testPackage4, 4, TMP_CONTENT_URI),
- };
-
- int numSrcDatabaseHotseatIcons = srcHotseatItems.length;
- mIdp.numDatabaseHotseatIcons = 6;
- mIdp.numColumns = 4;
- mIdp.numRows = 4;
- GridSizeMigrationTaskV2.DbReader srcReader = new GridSizeMigrationTaskV2.DbReader(mDb,
- LauncherSettings.Favorites.TMP_TABLE, mContext, mValidPackages);
- GridSizeMigrationTaskV2.DbReader destReader = new GridSizeMigrationTaskV2.DbReader(mDb,
- LauncherSettings.Favorites.TABLE_NAME, mContext, mValidPackages);
- GridSizeMigrationTaskV2 task = new GridSizeMigrationTaskV2(mContext, mDb, srcReader,
- destReader, mIdp.numDatabaseHotseatIcons, new Point(mIdp.numColumns, mIdp.numRows));
- task.migrate(mIdp);
-
- // Check hotseat items
- Cursor c = mContext.getContentResolver().query(LauncherSettings.Favorites.CONTENT_URI,
- new String[]{LauncherSettings.Favorites.SCREEN, LauncherSettings.Favorites.INTENT},
- "container=" + CONTAINER_HOTSEAT, null, LauncherSettings.Favorites.SCREEN, null);
- assertEquals(c.getCount(), numSrcDatabaseHotseatIcons);
- int screenIndex = c.getColumnIndex(LauncherSettings.Favorites.SCREEN);
- int intentIndex = c.getColumnIndex(LauncherSettings.Favorites.INTENT);
- c.moveToNext();
- assertEquals(c.getInt(screenIndex), 0);
- assertTrue(c.getString(intentIndex).contains(testPackage1));
- c.moveToNext();
- assertEquals(c.getInt(screenIndex), 1);
- assertTrue(c.getString(intentIndex).contains(testPackage2));
- c.moveToNext();
- assertEquals(c.getInt(screenIndex), 2);
- assertTrue(c.getString(intentIndex).contains(testPackage3));
- c.moveToNext();
- assertEquals(c.getInt(screenIndex), 3);
- assertTrue(c.getString(intentIndex).contains(testPackage4));
-
- c.close();
- }
-
- @Test
- public void migrateFromLargerHotseat() {
- int[] srcHotseatItems = {
- mModelHelper.addItem(APP_ICON, 0, HOTSEAT, 0, 0, testPackage1, 1, TMP_CONTENT_URI),
- -1,
- mModelHelper.addItem(SHORTCUT, 2, HOTSEAT, 0, 0, testPackage2, 2, TMP_CONTENT_URI),
- mModelHelper.addItem(APP_ICON, 3, HOTSEAT, 0, 0, testPackage3, 3, TMP_CONTENT_URI),
- mModelHelper.addItem(SHORTCUT, 4, HOTSEAT, 0, 0, testPackage4, 4, TMP_CONTENT_URI),
- mModelHelper.addItem(APP_ICON, 5, HOTSEAT, 0, 0, testPackage5, 5, TMP_CONTENT_URI),
- };
-
- mIdp.numDatabaseHotseatIcons = 4;
- mIdp.numColumns = 4;
- mIdp.numRows = 4;
- GridSizeMigrationTaskV2.DbReader srcReader = new GridSizeMigrationTaskV2.DbReader(mDb,
- LauncherSettings.Favorites.TMP_TABLE, mContext, mValidPackages);
- GridSizeMigrationTaskV2.DbReader destReader = new GridSizeMigrationTaskV2.DbReader(mDb,
- LauncherSettings.Favorites.TABLE_NAME, mContext, mValidPackages);
- GridSizeMigrationTaskV2 task = new GridSizeMigrationTaskV2(mContext, mDb, srcReader,
- destReader, mIdp.numDatabaseHotseatIcons, new Point(mIdp.numColumns, mIdp.numRows));
- task.migrate(mIdp);
-
- // Check hotseat items
- Cursor c = mContext.getContentResolver().query(LauncherSettings.Favorites.CONTENT_URI,
- new String[]{LauncherSettings.Favorites.SCREEN, LauncherSettings.Favorites.INTENT},
- "container=" + CONTAINER_HOTSEAT, null, LauncherSettings.Favorites.SCREEN, null);
- assertEquals(c.getCount(), mIdp.numDatabaseHotseatIcons);
- int screenIndex = c.getColumnIndex(LauncherSettings.Favorites.SCREEN);
- int intentIndex = c.getColumnIndex(LauncherSettings.Favorites.INTENT);
- c.moveToNext();
- assertEquals(c.getInt(screenIndex), 0);
- assertTrue(c.getString(intentIndex).contains(testPackage1));
- c.moveToNext();
- assertEquals(c.getInt(screenIndex), 1);
- assertTrue(c.getString(intentIndex).contains(testPackage2));
- c.moveToNext();
- assertEquals(c.getInt(screenIndex), 2);
- assertTrue(c.getString(intentIndex).contains(testPackage3));
- c.moveToNext();
- assertEquals(c.getInt(screenIndex), 3);
- assertTrue(c.getString(intentIndex).contains(testPackage4));
-
- c.close();
- }
-}
diff --git a/tests/src/com/android/launcher3/model/GridSizeMigrationTaskV2Test.kt b/tests/src/com/android/launcher3/model/GridSizeMigrationTaskV2Test.kt
new file mode 100644
index 0000000..239e092
--- /dev/null
+++ b/tests/src/com/android/launcher3/model/GridSizeMigrationTaskV2Test.kt
@@ -0,0 +1,500 @@
+/*
+ * Copyright (C) 2022 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 android.content.Context
+import android.content.Intent
+import android.database.sqlite.SQLiteDatabase
+import android.graphics.Point
+import android.os.Process
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.launcher3.InvariantDeviceProfile
+import com.android.launcher3.LauncherFiles
+import com.android.launcher3.LauncherSettings.Favorites.*
+import com.android.launcher3.config.FeatureFlags
+import com.android.launcher3.model.GridSizeMigrationTaskV2.DbReader
+import com.android.launcher3.pm.UserCache
+import com.android.launcher3.provider.LauncherDbUtils
+import com.android.launcher3.util.LauncherModelHelper
+import com.android.launcher3.util.LauncherModelHelper.*
+import com.google.common.truth.Truth.assertThat
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/** Unit tests for [GridSizeMigrationTaskV2] */
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class GridSizeMigrationTaskV2Test {
+ private lateinit var modelHelper: LauncherModelHelper
+ private lateinit var context: Context
+ private lateinit var db: SQLiteDatabase
+ private lateinit var validPackages: Set<String>
+ private lateinit var idp: InvariantDeviceProfile
+ private val testPackage1 = "com.android.launcher3.validpackage1"
+ private val testPackage2 = "com.android.launcher3.validpackage2"
+ private val testPackage3 = "com.android.launcher3.validpackage3"
+ private val testPackage4 = "com.android.launcher3.validpackage4"
+ private val testPackage5 = "com.android.launcher3.validpackage5"
+ private val testPackage6 = "com.android.launcher3.validpackage6"
+ private val testPackage7 = "com.android.launcher3.validpackage7"
+ private val testPackage8 = "com.android.launcher3.validpackage8"
+ private val testPackage9 = "com.android.launcher3.validpackage9"
+ private val testPackage10 = "com.android.launcher3.validpackage10"
+
+ @Before
+ fun setUp() {
+ modelHelper = LauncherModelHelper()
+ context = modelHelper.sandboxContext
+ db = modelHelper.provider.db
+
+ validPackages = setOf(
+ TEST_PACKAGE,
+ testPackage1,
+ testPackage2,
+ testPackage3,
+ testPackage4,
+ testPackage5,
+ testPackage6,
+ testPackage7,
+ testPackage8,
+ testPackage9,
+ testPackage10
+ )
+
+ idp = InvariantDeviceProfile.INSTANCE[context]
+ val userSerial = UserCache.INSTANCE[context].getSerialNumberForUser(Process.myUserHandle())
+ LauncherDbUtils.dropTable(db, TMP_TABLE)
+ addTableToDb(db, userSerial, false, TMP_TABLE)
+ }
+
+ @After
+ fun tearDown() {
+ modelHelper.destroy()
+ }
+
+ /**
+ * Old migration logic, should be modified once [FeatureFlags.ENABLE_NEW_MIGRATION_LOGIC] is
+ * not needed anymore
+ */
+ @Test
+ @Throws(Exception::class)
+ fun testMigration() {
+ // Src Hotseat icons
+ modelHelper.addItem(APP_ICON, 0, HOTSEAT, 0, 0, testPackage1, 1, TMP_CONTENT_URI)
+ modelHelper.addItem(SHORTCUT, 1, HOTSEAT, 0, 0, testPackage2, 2, TMP_CONTENT_URI)
+ modelHelper.addItem(SHORTCUT, 3, HOTSEAT, 0, 0, testPackage3, 3, TMP_CONTENT_URI)
+ modelHelper.addItem(APP_ICON, 4, HOTSEAT, 0, 0, testPackage4, 4, TMP_CONTENT_URI)
+ // Src grid icons
+ modelHelper.addItem(APP_ICON, 0, DESKTOP, 2, 2, testPackage5, 5, TMP_CONTENT_URI)
+ modelHelper.addItem(APP_ICON, 0, DESKTOP, 2, 3, testPackage6, 6, TMP_CONTENT_URI)
+ modelHelper.addItem(APP_ICON, 0, DESKTOP, 4, 1, testPackage8, 8, TMP_CONTENT_URI)
+ modelHelper.addItem(APP_ICON, 0, DESKTOP, 4, 2, testPackage9, 9, TMP_CONTENT_URI)
+ modelHelper.addItem(APP_ICON, 0, DESKTOP, 4, 3, testPackage10, 10, TMP_CONTENT_URI)
+
+ // Dest hotseat icons
+ modelHelper.addItem(SHORTCUT, 1, HOTSEAT, 0, 0, testPackage2)
+ // Dest grid icons
+ modelHelper.addItem(APP_ICON, 0, DESKTOP, 2, 2, testPackage7)
+
+ idp.numDatabaseHotseatIcons = 4
+ idp.numColumns = 4
+ idp.numRows = 4
+ val srcReader = DbReader(db, TMP_TABLE, context, validPackages)
+ val destReader = DbReader(db, TABLE_NAME, context, validPackages)
+ val task = GridSizeMigrationTaskV2(
+ context,
+ db,
+ srcReader,
+ destReader,
+ idp.numDatabaseHotseatIcons,
+ Point(idp.numColumns, idp.numRows)
+ )
+ task.migrate(idp)
+
+ // Check hotseat items
+ var c = context.contentResolver.query(
+ CONTENT_URI,
+ arrayOf(SCREEN, INTENT),
+ "container=$CONTAINER_HOTSEAT",
+ null,
+ SCREEN,
+ null
+ ) ?: throw IllegalStateException()
+
+ assertThat(c.count).isEqualTo(idp.numDatabaseHotseatIcons)
+
+ val screenIndex = c.getColumnIndex(SCREEN)
+ var intentIndex = c.getColumnIndex(INTENT)
+ c.moveToNext()
+ assertThat(c.getInt(screenIndex).toLong()).isEqualTo(0)
+ assertThat(c.getString(intentIndex)).contains(testPackage1)
+ c.moveToNext()
+ assertThat(c.getInt(screenIndex).toLong()).isEqualTo(1)
+ assertThat(c.getString(intentIndex)).contains(testPackage2)
+ c.moveToNext()
+ assertThat(c.getInt(screenIndex).toLong()).isEqualTo(2)
+ assertThat(c.getString(intentIndex)).contains(testPackage3)
+ c.moveToNext()
+ assertThat(c.getInt(screenIndex).toLong()).isEqualTo(3)
+ assertThat(c.getString(intentIndex)).contains(testPackage4)
+ c.close()
+
+ // Check workspace items
+ c = context.contentResolver.query(
+ CONTENT_URI,
+ arrayOf(CELLX, CELLY, INTENT),
+ "container=$CONTAINER_DESKTOP",
+ null,
+ null,
+ null
+ ) ?: throw IllegalStateException()
+
+ intentIndex = c.getColumnIndex(INTENT)
+ val cellXIndex = c.getColumnIndex(CELLX)
+ val cellYIndex = c.getColumnIndex(CELLY)
+ val locMap = HashMap<String, Point>()
+ while (c.moveToNext()) {
+ locMap[Intent.parseUri(c.getString(intentIndex), 0).getPackage()] =
+ Point(c.getInt(cellXIndex), c.getInt(cellYIndex))
+ }
+ c.close()
+ assertThat(locMap.size.toLong()).isEqualTo(6)
+ assertThat(locMap[testPackage8]).isEqualTo(Point(0, 2))
+ assertThat(locMap[testPackage6]).isEqualTo(Point(0, 3))
+ assertThat(locMap[testPackage10]).isEqualTo(Point(1, 3))
+ assertThat(locMap[testPackage7]).isEqualTo(Point(2, 2))
+ assertThat(locMap[testPackage5]).isEqualTo(Point(2, 3))
+ assertThat(locMap[testPackage9]).isEqualTo(Point(3, 3))
+ }
+
+ @Test
+ fun migrateToLargerHotseat() {
+ val srcHotseatItems = intArrayOf(
+ modelHelper.addItem(APP_ICON, 0, HOTSEAT, 0, 0, testPackage1, 1, TMP_CONTENT_URI),
+ modelHelper.addItem(SHORTCUT, 1, HOTSEAT, 0, 0, testPackage2, 2, TMP_CONTENT_URI),
+ modelHelper.addItem(APP_ICON, 2, HOTSEAT, 0, 0, testPackage3, 3, TMP_CONTENT_URI),
+ modelHelper.addItem(SHORTCUT, 3, HOTSEAT, 0, 0, testPackage4, 4, TMP_CONTENT_URI)
+ )
+ val numSrcDatabaseHotseatIcons = srcHotseatItems.size
+ idp.numDatabaseHotseatIcons = 6
+ idp.numColumns = 4
+ idp.numRows = 4
+ val srcReader = DbReader(db, TMP_TABLE, context, validPackages)
+ val destReader = DbReader(db, TABLE_NAME, context, validPackages)
+ val task = GridSizeMigrationTaskV2(
+ context,
+ db,
+ srcReader,
+ destReader,
+ idp.numDatabaseHotseatIcons,
+ Point(idp.numColumns, idp.numRows)
+ )
+ task.migrate(idp)
+
+ // Check hotseat items
+ val c = context.contentResolver.query(
+ CONTENT_URI,
+ arrayOf(SCREEN, INTENT),
+ "container=$CONTAINER_HOTSEAT",
+ null,
+ SCREEN,
+ null
+ ) ?: throw IllegalStateException()
+
+ assertThat(c.count.toLong()).isEqualTo(numSrcDatabaseHotseatIcons.toLong())
+ val screenIndex = c.getColumnIndex(SCREEN)
+ val intentIndex = c.getColumnIndex(INTENT)
+ c.moveToNext()
+ assertThat(c.getInt(screenIndex)).isEqualTo(0)
+ assertThat(c.getString(intentIndex)).contains(testPackage1)
+
+ c.moveToNext()
+ assertThat(c.getInt(screenIndex)).isEqualTo(1)
+ assertThat(c.getString(intentIndex)).contains(testPackage2)
+
+ c.moveToNext()
+ assertThat(c.getInt(screenIndex)).isEqualTo(2)
+ assertThat(c.getString(intentIndex)).contains(testPackage3)
+
+ c.moveToNext()
+ assertThat(c.getInt(screenIndex)).isEqualTo(3)
+ assertThat(c.getString(intentIndex)).contains(testPackage4)
+
+ c.close()
+ }
+
+ @Test
+ fun migrateFromLargerHotseat() {
+ modelHelper.addItem(APP_ICON, 0, HOTSEAT, 0, 0, testPackage1, 1, TMP_CONTENT_URI)
+ modelHelper.addItem(SHORTCUT, 2, HOTSEAT, 0, 0, testPackage2, 2, TMP_CONTENT_URI)
+ modelHelper.addItem(APP_ICON, 3, HOTSEAT, 0, 0, testPackage3, 3, TMP_CONTENT_URI)
+ modelHelper.addItem(SHORTCUT, 4, HOTSEAT, 0, 0, testPackage4, 4, TMP_CONTENT_URI)
+ modelHelper.addItem(APP_ICON, 5, HOTSEAT, 0, 0, testPackage5, 5, TMP_CONTENT_URI)
+
+ idp.numDatabaseHotseatIcons = 4
+ idp.numColumns = 4
+ idp.numRows = 4
+ val srcReader = DbReader(db, TMP_TABLE, context, validPackages)
+ val destReader = DbReader(db, TABLE_NAME, context, validPackages)
+ val task = GridSizeMigrationTaskV2(
+ context,
+ db,
+ srcReader,
+ destReader,
+ idp.numDatabaseHotseatIcons,
+ Point(idp.numColumns, idp.numRows)
+ )
+ task.migrate(idp)
+
+ // Check hotseat items
+ val c = context.contentResolver.query(
+ CONTENT_URI,
+ arrayOf(SCREEN, INTENT),
+ "container=$CONTAINER_HOTSEAT",
+ null,
+ SCREEN,
+ null
+ ) ?: throw IllegalStateException()
+
+ assertThat(c.count.toLong()).isEqualTo(idp.numDatabaseHotseatIcons.toLong())
+ val screenIndex = c.getColumnIndex(SCREEN)
+ val intentIndex = c.getColumnIndex(INTENT)
+
+ c.moveToNext()
+ assertThat(c.getInt(screenIndex)).isEqualTo(0)
+ assertThat(c.getString(intentIndex)).contains(testPackage1)
+
+ c.moveToNext()
+ assertThat(c.getInt(screenIndex)).isEqualTo(1)
+ assertThat(c.getString(intentIndex)).contains(testPackage2)
+
+ c.moveToNext()
+ assertThat(c.getInt(screenIndex)).isEqualTo(2)
+ assertThat(c.getString(intentIndex)).contains(testPackage3)
+
+ c.moveToNext()
+ assertThat(c.getInt(screenIndex)).isEqualTo(3)
+ assertThat(c.getString(intentIndex)).contains(testPackage4)
+
+ c.close()
+ }
+
+ /**
+ * Migrating from a smaller grid to a large one should keep the pages
+ * if the column difference is less than 2
+ */
+ @Test
+ @Throws(Exception::class)
+ fun migrateFromSmallerGridSmallDifference() {
+ enableNewMigrationLogic("4,4")
+
+ // Setup src grid
+ modelHelper.addItem(APP_ICON, 0, DESKTOP, 2, 2, testPackage1, 5, TMP_CONTENT_URI)
+ modelHelper.addItem(APP_ICON, 0, DESKTOP, 2, 3, testPackage2, 6, TMP_CONTENT_URI)
+ modelHelper.addItem(APP_ICON, 1, DESKTOP, 3, 1, testPackage3, 7, TMP_CONTENT_URI)
+ modelHelper.addItem(APP_ICON, 1, DESKTOP, 3, 2, testPackage4, 8, TMP_CONTENT_URI)
+ modelHelper.addItem(APP_ICON, 2, DESKTOP, 3, 3, testPackage5, 9, TMP_CONTENT_URI)
+
+ idp.numDatabaseHotseatIcons = 4
+ idp.numColumns = 6
+ idp.numRows = 5
+
+ val srcReader = DbReader(db, TMP_TABLE, context, validPackages)
+ val destReader = DbReader(db, TABLE_NAME, context, validPackages)
+ val task = GridSizeMigrationTaskV2(
+ context,
+ db,
+ srcReader,
+ destReader,
+ idp.numDatabaseHotseatIcons,
+ Point(idp.numColumns, idp.numRows)
+ )
+ task.migrate(idp)
+
+ // Get workspace items
+ val c = context.contentResolver.query(
+ CONTENT_URI,
+ arrayOf(INTENT, SCREEN),
+ "container=$CONTAINER_DESKTOP",
+ null,
+ null,
+ null
+ ) ?: throw IllegalStateException()
+ val intentIndex = c.getColumnIndex(INTENT)
+ val screenIndex = c.getColumnIndex(SCREEN)
+
+ // Get in which screen the icon is
+ val locMap = HashMap<String, Int>()
+ while (c.moveToNext()) {
+ locMap[Intent.parseUri(c.getString(intentIndex), 0).getPackage()] =
+ c.getInt(screenIndex)
+ }
+ c.close()
+ assertThat(locMap.size).isEqualTo(5)
+ assertThat(locMap[testPackage1]).isEqualTo(0)
+ assertThat(locMap[testPackage2]).isEqualTo(0)
+ assertThat(locMap[testPackage3]).isEqualTo(1)
+ assertThat(locMap[testPackage4]).isEqualTo(1)
+ assertThat(locMap[testPackage5]).isEqualTo(2)
+
+ disableNewMigrationLogic()
+ }
+
+ /**
+ * Migrating from a smaller grid to a large one should reflow the pages
+ * if the column difference is more than 2
+ */
+ @Test
+ @Throws(Exception::class)
+ fun migrateFromSmallerGridBigDifference() {
+ enableNewMigrationLogic("2,2")
+
+ // Setup src grid
+ modelHelper.addItem(APP_ICON, 0, DESKTOP, 0, 1, testPackage1, 5, TMP_CONTENT_URI)
+ modelHelper.addItem(APP_ICON, 0, DESKTOP, 1, 1, testPackage2, 6, TMP_CONTENT_URI)
+ modelHelper.addItem(APP_ICON, 1, DESKTOP, 0, 0, testPackage3, 7, TMP_CONTENT_URI)
+ modelHelper.addItem(APP_ICON, 1, DESKTOP, 1, 0, testPackage4, 8, TMP_CONTENT_URI)
+ modelHelper.addItem(APP_ICON, 2, DESKTOP, 0, 0, testPackage5, 9, TMP_CONTENT_URI)
+
+ idp.numDatabaseHotseatIcons = 4
+ idp.numColumns = 5
+ idp.numRows = 5
+ val srcReader = DbReader(db, TMP_TABLE, context, validPackages)
+ val destReader = DbReader(db, TABLE_NAME, context, validPackages)
+ val task = GridSizeMigrationTaskV2(
+ context,
+ db,
+ srcReader,
+ destReader,
+ idp.numDatabaseHotseatIcons,
+ Point(idp.numColumns, idp.numRows)
+ )
+ task.migrate(idp)
+
+ // Get workspace items
+ val c = context.contentResolver.query(
+ CONTENT_URI,
+ arrayOf(INTENT, SCREEN),
+ "container=$CONTAINER_DESKTOP",
+ null,
+ null,
+ null
+ ) ?: throw IllegalStateException()
+
+ val intentIndex = c.getColumnIndex(INTENT)
+ val screenIndex = c.getColumnIndex(SCREEN)
+
+ // Get in which screen the icon is
+ val locMap = HashMap<String, Int>()
+ while (c.moveToNext()) {
+ locMap[Intent.parseUri(c.getString(intentIndex), 0).getPackage()] =
+ c.getInt(screenIndex)
+ }
+ c.close()
+
+ // All icons fit the first screen
+ assertThat(locMap.size).isEqualTo(5)
+ assertThat(locMap[testPackage1]).isEqualTo(0)
+ assertThat(locMap[testPackage2]).isEqualTo(0)
+ assertThat(locMap[testPackage3]).isEqualTo(0)
+ assertThat(locMap[testPackage4]).isEqualTo(0)
+ assertThat(locMap[testPackage5]).isEqualTo(0)
+ disableNewMigrationLogic()
+ }
+
+ /**
+ * Migrating from a larger grid to a smaller, we reflow from page 0
+ */
+ @Test
+ @Throws(Exception::class)
+ fun migrateFromLargerGrid() {
+ enableNewMigrationLogic("5,5")
+
+ // Setup src grid
+ modelHelper.addItem(APP_ICON, 0, DESKTOP, 0, 1, testPackage1, 5, TMP_CONTENT_URI)
+ modelHelper.addItem(APP_ICON, 0, DESKTOP, 1, 1, testPackage2, 6, TMP_CONTENT_URI)
+ modelHelper.addItem(APP_ICON, 1, DESKTOP, 0, 0, testPackage3, 7, TMP_CONTENT_URI)
+ modelHelper.addItem(APP_ICON, 1, DESKTOP, 1, 0, testPackage4, 8, TMP_CONTENT_URI)
+ modelHelper.addItem(APP_ICON, 2, DESKTOP, 0, 0, testPackage5, 9, TMP_CONTENT_URI)
+
+ idp.numDatabaseHotseatIcons = 4
+ idp.numColumns = 4
+ idp.numRows = 4
+ val srcReader = DbReader(db, TMP_TABLE, context, validPackages)
+ val destReader = DbReader(db, TABLE_NAME, context, validPackages)
+ val task = GridSizeMigrationTaskV2(
+ context,
+ db,
+ srcReader,
+ destReader,
+ idp.numDatabaseHotseatIcons,
+ Point(idp.numColumns, idp.numRows)
+ )
+ task.migrate(idp)
+
+ // Get workspace items
+ val c = context.contentResolver.query(
+ CONTENT_URI,
+ arrayOf(INTENT, SCREEN),
+ "container=$CONTAINER_DESKTOP",
+ null,
+ null,
+ null
+ ) ?: throw IllegalStateException()
+ val intentIndex = c.getColumnIndex(INTENT)
+ val screenIndex = c.getColumnIndex(SCREEN)
+
+ // Get in which screen the icon is
+ val locMap = HashMap<String, Int>()
+ while (c.moveToNext()) {
+ locMap[Intent.parseUri(c.getString(intentIndex), 0).getPackage()] =
+ c.getInt(screenIndex)
+ }
+ c.close()
+
+ // All icons fit the first screen
+ assertThat(locMap.size).isEqualTo(5)
+ assertThat(locMap[testPackage1]).isEqualTo(0)
+ assertThat(locMap[testPackage2]).isEqualTo(0)
+ assertThat(locMap[testPackage3]).isEqualTo(0)
+ assertThat(locMap[testPackage4]).isEqualTo(0)
+ assertThat(locMap[testPackage5]).isEqualTo(0)
+
+ disableNewMigrationLogic()
+ }
+
+ private fun enableNewMigrationLogic(srcGridSize: String) {
+ context.getSharedPreferences(FeatureFlags.FLAGS_PREF_NAME, Context.MODE_PRIVATE)
+ .edit()
+ .putBoolean(FeatureFlags.ENABLE_NEW_MIGRATION_LOGIC.key, true)
+ .commit()
+ context.getSharedPreferences(LauncherFiles.SHARED_PREFERENCES_KEY, Context.MODE_PRIVATE)
+ .edit()
+ .putString(DeviceGridState.KEY_WORKSPACE_SIZE, srcGridSize)
+ .commit()
+ FeatureFlags.initialize(context)
+ }
+
+ private fun disableNewMigrationLogic() {
+ context.getSharedPreferences(FeatureFlags.FLAGS_PREF_NAME, Context.MODE_PRIVATE)
+ .edit()
+ .putBoolean(FeatureFlags.ENABLE_NEW_MIGRATION_LOGIC.key, false)
+ .commit()
+ }
+}
\ No newline at end of file