diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java index df09f29241..9f3d445562 100644 --- a/src/com/android/launcher3/LauncherProvider.java +++ b/src/com/android/launcher3/LauncherProvider.java @@ -483,7 +483,7 @@ public class LauncherProvider extends ContentProvider { LauncherSettings.Favorites.CONTAINER + " FROM " + Favorites.TABLE_NAME + ")"; - IntArray folderIds = LauncherDbUtils.queryIntArray(db, Favorites.TABLE_NAME, + IntArray folderIds = LauncherDbUtils.queryIntArray(false, db, Favorites.TABLE_NAME, Favorites._ID, selection, null, null); if (!folderIds.isEmpty()) { db.delete(Favorites.TABLE_NAME, Utilities.createDbSelectionQuery( @@ -835,8 +835,8 @@ public class LauncherProvider extends ContentProvider { case 27: { // Update the favorites table so that the screen ids are ordered based on // workspace page rank. - IntArray finalScreens = LauncherDbUtils.queryIntArray(db, "workspaceScreens", - BaseColumns._ID, null, null, "screenRank"); + IntArray finalScreens = LauncherDbUtils.queryIntArray(false, db, + "workspaceScreens", BaseColumns._ID, null, null, "screenRank"); int[] original = finalScreens.toArray(); Arrays.sort(original); String updatemap = ""; @@ -919,7 +919,7 @@ public class LauncherProvider extends ContentProvider { Log.e(TAG, "getAppWidgetIds not supported", e); return; } - final IntSet validWidgets = IntSet.wrap(LauncherDbUtils.queryIntArray(db, + final IntSet validWidgets = IntSet.wrap(LauncherDbUtils.queryIntArray(false, db, Favorites.TABLE_NAME, Favorites.APPWIDGET_ID, "itemType=" + Favorites.ITEM_TYPE_APPWIDGET, null, null)); for (int widgetId : allWidgets) { diff --git a/src/com/android/launcher3/provider/LauncherDbUtils.java b/src/com/android/launcher3/provider/LauncherDbUtils.java index 6855bb1e69..b5103787d4 100644 --- a/src/com/android/launcher3/provider/LauncherDbUtils.java +++ b/src/com/android/launcher3/provider/LauncherDbUtils.java @@ -31,11 +31,11 @@ import com.android.launcher3.util.IntArray; */ public class LauncherDbUtils { - public static IntArray queryIntArray(SQLiteDatabase db, String tableName, String columnName, - String selection, String groupBy, String orderBy) { + public static IntArray queryIntArray(boolean distinct, SQLiteDatabase db, String tableName, + String columnName, String selection, String groupBy, String orderBy) { IntArray out = new IntArray(); - try (Cursor c = db.query(tableName, new String[] { columnName }, selection, null, - groupBy, null, orderBy)) { + try (Cursor c = db.query(distinct, tableName, new String[] { columnName }, selection, null, + groupBy, null, orderBy, null)) { while (c.moveToNext()) { out.add(c.getInt(0)); } diff --git a/src/com/android/launcher3/provider/RestoreDbTask.java b/src/com/android/launcher3/provider/RestoreDbTask.java index 257d732e4c..8f607a1797 100644 --- a/src/com/android/launcher3/provider/RestoreDbTask.java +++ b/src/com/android/launcher3/provider/RestoreDbTask.java @@ -16,6 +16,7 @@ package com.android.launcher3.provider; +import static com.android.launcher3.model.DeviceGridState.TYPE_MULTI_DISPLAY; import static com.android.launcher3.model.DeviceGridState.TYPE_PHONE; import static com.android.launcher3.provider.LauncherDbUtils.dropTable; @@ -96,7 +97,7 @@ public class RestoreDbTask { try (SQLiteTransaction t = new SQLiteTransaction(db)) { RestoreDbTask task = new RestoreDbTask(); task.backupWorkspace(context, db); - task.sanitizeDB(helper, db, new BackupManager(context)); + task.sanitizeDB(context, helper, db, new BackupManager(context)); task.restoreAppWidgetIdsIfExists(context); t.commit(); return true; @@ -139,7 +140,7 @@ public class RestoreDbTask { GridBackupTable backupTable = new GridBackupTable(context, db, idp.numDatabaseHotseatIcons, idp.numColumns, idp.numRows); if (backupTable.restoreFromRawBackupIfAvailable(getDefaultProfileId(db))) { - int itemsDeleted = sanitizeDB(helper, db, backupManager); + int itemsDeleted = sanitizeDB(context, helper, db, backupManager); LauncherAppState.getInstance(context).getModel().forceReload(); restoreAppWidgetIdsIfExists(context); if (itemsDeleted == 0) { @@ -156,11 +157,12 @@ public class RestoreDbTask { * the restored apps get installed. * 3. If the user serial for any restored profile is different than that of the previous * device, update the entries to the new profile id. + * 4. If restored from a single display backup, remove gaps between screenIds * * @return number of items deleted. */ - private int sanitizeDB(DatabaseHelper helper, SQLiteDatabase db, BackupManager backupManager) - throws Exception { + private int sanitizeDB(Context context, DatabaseHelper helper, SQLiteDatabase db, + BackupManager backupManager) throws Exception { // Primary user ids long myProfileId = helper.getDefaultUserSerial(); long oldProfileId = getDefaultProfileId(db); @@ -236,9 +238,42 @@ public class RestoreDbTask { if (myProfileId != oldProfileId) { changeDefaultColumn(db, myProfileId); } + + // If restored from a single display backup, remove gaps between screenIds + if (Utilities.getPrefs(context).getInt(RESTORED_DEVICE_TYPE, TYPE_PHONE) + != TYPE_MULTI_DISPLAY) { + removeScreenIdGaps(db); + } + return itemsDeleted; } + /** + * Remove gaps between screenIds to make sure no empty pages are left in between. + * + * e.g. [0, 3, 4, 6, 7] -> [0, 1, 2, 3, 4] + */ + protected void removeScreenIdGaps(SQLiteDatabase db) { + FileLog.d(TAG, "Removing gaps between screenIds"); + IntArray distinctScreens = LauncherDbUtils.queryIntArray(true, db, Favorites.TABLE_NAME, + Favorites.SCREEN, Favorites.CONTAINER + " = " + Favorites.CONTAINER_DESKTOP, null, + Favorites.SCREEN); + if (distinctScreens.isEmpty()) { + return; + } + + StringBuilder sql = new StringBuilder("UPDATE ").append(Favorites.TABLE_NAME) + .append(" SET ").append(Favorites.SCREEN).append(" =\nCASE\n"); + int screenId = distinctScreens.contains(0) ? 0 : 1; + for (int i = 0; i < distinctScreens.size(); i++) { + sql.append("WHEN ").append(Favorites.SCREEN).append(" == ") + .append(distinctScreens.get(i)).append(" THEN ").append(screenId++).append("\n"); + } + sql.append("ELSE screen\nEND WHERE ").append(Favorites.CONTAINER).append(" = ") + .append(Favorites.CONTAINER_DESKTOP).append(";"); + db.execSQL(sql.toString()); + } + /** * Updates profile id of all entries from {@param oldProfileId} to {@param newProfileId}. */ diff --git a/tests/src/com/android/launcher3/provider/RestoreDbTaskTest.java b/tests/src/com/android/launcher3/provider/RestoreDbTaskTest.java index 48305eebdf..9c8de1c007 100644 --- a/tests/src/com/android/launcher3/provider/RestoreDbTaskTest.java +++ b/tests/src/com/android/launcher3/provider/RestoreDbTaskTest.java @@ -15,6 +15,7 @@ */ package com.android.launcher3.provider; +import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import android.content.ContentValues; @@ -87,6 +88,56 @@ public class RestoreDbTaskTest { assertEquals(1, getCount(db, "select * from favorites where profileId = 33")); } + @Test + public void testRemoveScreenIdGaps_firstScreenEmpty() { + runRemoveScreenIdGapsTest( + new int[]{1, 2, 5, 6, 6, 7, 9, 9}, + new int[]{1, 2, 3, 4, 4, 5, 6, 6}); + } + + @Test + public void testRemoveScreenIdGaps_firstScreenOccupied() { + runRemoveScreenIdGapsTest( + new int[]{0, 2, 5, 6, 6, 7, 9, 9}, + new int[]{0, 1, 2, 3, 3, 4, 5, 5}); + } + + @Test + public void testRemoveScreenIdGaps_noGap() { + runRemoveScreenIdGapsTest( + new int[]{0, 1, 1, 2, 3, 3, 4, 5}, + new int[]{0, 1, 1, 2, 3, 3, 4, 5}); + } + + private void runRemoveScreenIdGapsTest(int[] screenIds, int[] expectedScreenIds) { + SQLiteDatabase db = new MyDatabaseHelper(42).getWritableDatabase(); + // Add some mock data + for (int i = 0; i < screenIds.length; i++) { + ContentValues values = new ContentValues(); + values.put(Favorites._ID, i); + values.put(Favorites.SCREEN, screenIds[i]); + values.put(Favorites.CONTAINER, Favorites.CONTAINER_DESKTOP); + db.insert(Favorites.TABLE_NAME, null, values); + } + // Verify items are added + assertEquals(screenIds.length, + getCount(db, "select * from favorites where container = -100")); + + new RestoreDbTask().removeScreenIdGaps(db); + + // verify screenId gaps removed + int[] resultScreenIds = new int[screenIds.length]; + try (Cursor c = db.rawQuery( + "select screen from favorites where container = -100 order by screen", null)) { + int i = 0; + while (c.moveToNext()) { + resultScreenIds[i++] = c.getInt(0); + } + } + + assertArrayEquals(expectedScreenIds, resultScreenIds); + } + private int getCount(SQLiteDatabase db, String sql) { try (Cursor c = db.rawQuery(sql, null)) { return c.getCount();