Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 33c348f5 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Make grid size migration less confusing for users." into tm-qpr-dev

parents 7b467add cc90d1b7
Loading
Loading
Loading
Loading
+63 −41
Original line number Diff line number Diff line
@@ -66,7 +66,7 @@ import java.util.stream.Collectors;
public class GridSizeMigrationUtil {

    private static final String TAG = "GridSizeMigrationUtil";
    private static final boolean DEBUG = false;
    private static final boolean DEBUG = true;

    private GridSizeMigrationUtil() {
        // Util class should not be instantiated
@@ -188,27 +188,54 @@ public class GridSizeMigrationUtil {
            @NonNull final DeviceGridState srcDeviceState,
            @NonNull final DeviceGridState destDeviceState) {

        final List<DbEntry> hotseatItems = destReader.loadHotseatEntries();
        final List<DbEntry> workspaceItems = destReader.loadAllWorkspaceEntries();
        final List<DbEntry> hotseatDiff =
                calcDiff(srcReader.loadHotseatEntries(), hotseatItems);
        final List<DbEntry> workspaceDiff =
                calcDiff(srcReader.loadAllWorkspaceEntries(), workspaceItems);
        final List<DbEntry> srcHotseatItems = srcReader.loadHotseatEntries();
        final List<DbEntry> srcWorkspaceItems = srcReader.loadAllWorkspaceEntries();
        final List<DbEntry> dstHotseatItems = destReader.loadHotseatEntries();
        final List<DbEntry> dstWorkspaceItems = destReader.loadAllWorkspaceEntries();
        final List<DbEntry> hotseatToBeAdded = new ArrayList<>(1);
        final List<DbEntry> workspaceToBeAdded = new ArrayList<>(1);
        final IntArray toBeRemoved = new IntArray();

        calcDiff(srcHotseatItems, dstHotseatItems, hotseatToBeAdded, toBeRemoved);
        calcDiff(srcWorkspaceItems, dstWorkspaceItems, workspaceToBeAdded, toBeRemoved);

        final int trgX = targetSize.x;
        final int trgY = targetSize.y;

        if (hotseatDiff.isEmpty() && workspaceDiff.isEmpty()) {
        if (DEBUG) {
            Log.d(TAG, "Start migration:"
                    + "\n Source Device:"
                    + srcWorkspaceItems.stream().map(DbEntry::toString).collect(
                    Collectors.joining(",\n", "[", "]"))
                    + "\n Target Device:"
                    + dstWorkspaceItems.stream().map(DbEntry::toString).collect(
                    Collectors.joining(",\n", "[", "]"))
                    + "\n Removing Items:"
                    + dstWorkspaceItems.stream().filter(entry ->
                            toBeRemoved.contains(entry.id)).map(DbEntry::toString).collect(
                    Collectors.joining(",\n", "[", "]"))
                    + "\n Adding Workspace Items:"
                    + workspaceToBeAdded.stream().map(DbEntry::toString).collect(
                    Collectors.joining(",\n", "[", "]"))
                    + "\n Adding Hotseat Items:"
                    + hotseatToBeAdded.stream().map(DbEntry::toString).collect(
                    Collectors.joining(",\n", "[", "]"))
            );
        }
        if (!toBeRemoved.isEmpty()) {
            removeEntryFromDb(destReader.mDb, destReader.mTableName, toBeRemoved);
        }
        if (hotseatToBeAdded.isEmpty() && workspaceToBeAdded.isEmpty()) {
            return false;
        }

        // Sort the items by the reading order.
        Collections.sort(hotseatDiff);
        Collections.sort(workspaceDiff);
        Collections.sort(hotseatToBeAdded);
        Collections.sort(workspaceToBeAdded);

        // Migrate hotseat
        solveHotseatPlacement(db, srcReader,
                destReader, context, destHotseatSize, hotseatItems, hotseatDiff);
                destReader, context, destHotseatSize, dstHotseatItems, hotseatToBeAdded);

        // Migrate workspace.
        // First we create a collection of the screens
@@ -229,8 +256,8 @@ public class GridSizeMigrationUtil {
                Log.d(TAG, "Migrating " + screenId);
            }
            solveGridPlacement(db, srcReader,
                    destReader, context, screenId, trgX, trgY, workspaceDiff, false);
            if (workspaceDiff.isEmpty()) {
                    destReader, context, screenId, trgX, trgY, workspaceToBeAdded, false);
            if (workspaceToBeAdded.isEmpty()) {
                break;
            }
        }
@@ -238,42 +265,37 @@ public class GridSizeMigrationUtil {
        // In case the new grid is smaller, there might be some leftover items that don't fit on
        // any of the screens, in this case we add them to new screens until all of them are placed.
        int screenId = destReader.mLastScreenId + 1;
        while (!workspaceDiff.isEmpty()) {
        while (!workspaceToBeAdded.isEmpty()) {
            solveGridPlacement(db, srcReader,
                    destReader, context, screenId, trgX, trgY, workspaceDiff, preservePages);
                    destReader, context, screenId, trgX, trgY, workspaceToBeAdded, preservePages);
            screenId++;
        }

        return true;
    }

    /** Return what's in the src but not in the dest */
    private static List<DbEntry> calcDiff(List<DbEntry> src, List<DbEntry> dest) {
        Map<String, Integer> destIdSet = new HashMap<>();
        for (DbEntry entry : dest) {
            String entryID = entry.getEntryMigrationId();
            if (destIdSet.containsKey(entryID)) {
                destIdSet.put(entryID, destIdSet.get(entryID) + 1);
            } else {
                destIdSet.put(entryID, 1);
            }
        }
        List<DbEntry> diff = new ArrayList<>();
        for (DbEntry entry : src) {
            String entryID = entry.getEntryMigrationId();
            if (destIdSet.containsKey(entryID)) {
                Integer count = destIdSet.get(entryID);
                if (count <= 0) {
                    diff.add(entry);
                    destIdSet.remove(entryID);
                } else {
                    destIdSet.put(entryID, count - 1);
                }
            } else {
                diff.add(entry);
    /**
     * Calculate the differences between {@code src} (denoted by A) and {@code dest}
     * (denoted by B).
     * All DbEntry in A - B will be added to {@code toBeAdded}
     * All DbEntry.id in B - A will be added to {@code toBeRemoved}
     */
    private static void calcDiff(@NonNull final List<DbEntry> src,
            @NonNull final List<DbEntry> dest, @NonNull final List<DbEntry> toBeAdded,
            @NonNull final IntArray toBeRemoved) {
        src.forEach(entry -> {
            if (!dest.contains(entry)) {
                toBeAdded.add(entry);
            }
        });
        dest.forEach(entry -> {
            if (!src.contains(entry)) {
                toBeRemoved.add(entry.id);
                if (entry.itemType == LauncherSettings.Favorites.ITEM_TYPE_FOLDER) {
                    entry.mFolderItems.values().forEach(ids -> ids.forEach(toBeRemoved::add));
                }
            }
        return diff;
        });
    }

    private static void insertEntryInDb(SQLiteDatabase db, Context context, DbEntry entry,
@@ -682,12 +704,12 @@ public class GridSizeMigrationUtil {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            DbEntry entry = (DbEntry) o;
            return Objects.equals(mIntent, entry.mIntent);
            return Objects.equals(getEntryMigrationId(), entry.getEntryMigrationId());
        }

        @Override
        public int hashCode() {
            return Objects.hash(mIntent);
            return Objects.hash(getEntryMigrationId());
        }

        public void updateContentValues(ContentValues values) {
+221 −3
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@ package com.android.launcher3.model

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
@@ -183,15 +184,232 @@ class GridSizeMigrationUtilTest {
        // Expected dest grid icons
        // _ _ _ _
        // 5 6 7 8
        // 9 _ 10_
        // 9 _ _ _
        // _ _ _ _
        assertThat(locMap.size.toLong()).isEqualTo(6)
        assertThat(locMap.size.toLong()).isEqualTo(5)
        assertThat(locMap[testPackage5]).isEqualTo(Point(0, 1))
        assertThat(locMap[testPackage6]).isEqualTo(Point(1, 1))
        assertThat(locMap[testPackage7]).isEqualTo(Point(2, 1))
        assertThat(locMap[testPackage8]).isEqualTo(Point(3, 1))
        assertThat(locMap[testPackage9]).isEqualTo(Point(0, 2))
        assertThat(locMap[testPackage10]).isEqualTo(Point(2, 2))
    }

    /**
     * Old migration logic, should be modified once [FeatureFlags.ENABLE_NEW_MIGRATION_LOGIC] is
     * not needed anymore
     */
    @Test
    @Throws(Exception::class)
    fun testMigrationBackAndForth() {
        // Hotseat items in grid A
        // 1 2 _ 3 4
        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)
        // Workspace items in grid A
        // _ _ _ _ _
        // _ _ _ _ 5
        // _ _ 6 _ 7
        // _ _ 8 _ _
        // _ _ _ _ _
        modelHelper.addItem(APP_ICON, 0, DESKTOP, 4, 1, testPackage5, 5, TMP_CONTENT_URI)
        modelHelper.addItem(APP_ICON, 0, DESKTOP, 2, 2, testPackage6, 6, TMP_CONTENT_URI)
        modelHelper.addItem(APP_ICON, 0, DESKTOP, 4, 2, testPackage7, 7, TMP_CONTENT_URI)
        modelHelper.addItem(APP_ICON, 0, DESKTOP, 2, 3, testPackage8, 8, TMP_CONTENT_URI)

        // Hotseat items in grid B
        // 2 _ _ _
        modelHelper.addItem(SHORTCUT, 0, HOTSEAT, 0, 0, testPackage2)
        // Workspace items in grid B
        // _ _ _ _
        // _ _ _ 10
        // _ _ _ _
        // _ _ _ _
        modelHelper.addItem(APP_ICON, 0, DESKTOP, 1, 3, testPackage10)

        idp.numDatabaseHotseatIcons = 4
        idp.numColumns = 4
        idp.numRows = 4
        val readerGridA = DbReader(db, TMP_TABLE, context, validPackages)
        val readerGridB = DbReader(db, TABLE_NAME, context, validPackages)
        // migrate from A -> B
        GridSizeMigrationUtil.migrate(
                context,
                db,
                readerGridA,
                readerGridB,
                idp.numDatabaseHotseatIcons,
                Point(idp.numColumns, idp.numRows),
                DeviceGridState(context),
                DeviceGridState(idp)
        )

        // Check hotseat items in grid B
        var c = context.contentResolver.query(
                CONTENT_URI,
                arrayOf(SCREEN, INTENT),
                "container=$CONTAINER_HOTSEAT",
                null,
                SCREEN,
                null
        ) ?: throw IllegalStateException()
        // Expected hotseat items in grid B
        // 2 1 3 4
        verifyHotseat(c, idp,
                mutableListOf(testPackage2, testPackage1, testPackage3, testPackage4).toList())

        // Check workspace items in grid B
        c = context.contentResolver.query(
                CONTENT_URI,
                arrayOf(SCREEN, CELLX, CELLY, INTENT),
                "container=$CONTAINER_DESKTOP",
                null,
                null,
                null
        ) ?: throw IllegalStateException()
        var locMap = parseLocMap(context, c)
        // Expected items in grid B
        // _ _ _ _
        // 5 6 7 8
        // _ _ _ _
        // _ _ _ _
        assertThat(locMap.size.toLong()).isEqualTo(4)
        assertThat(locMap[testPackage5]).isEqualTo(Triple(0, 0, 1))
        assertThat(locMap[testPackage6]).isEqualTo(Triple(0, 1, 1))
        assertThat(locMap[testPackage7]).isEqualTo(Triple(0, 2, 1))
        assertThat(locMap[testPackage8]).isEqualTo(Triple(0, 3, 1))

        // add item in B
        modelHelper.addItem(APP_ICON, 0, DESKTOP, 0, 2, testPackage9)

        // migrate from B -> A
        GridSizeMigrationUtil.migrate(
                context,
                db,
                readerGridB,
                readerGridA,
                5,
                Point(5, 5),
                DeviceGridState(idp),
                DeviceGridState(context)
        )
        // Check hotseat items in grid A
        c = context.contentResolver.query(
                TMP_CONTENT_URI,
                arrayOf(SCREEN, INTENT),
                "container=$CONTAINER_HOTSEAT",
                null,
                SCREEN,
                null
        ) ?: throw IllegalStateException()
        // Expected hotseat items in grid A
        // 1 2 _ 3 4
        verifyHotseat(c, idp, mutableListOf(
                testPackage1, testPackage2, null, testPackage3, testPackage4).toList())

        // Check workspace items in grid A
        c = context.contentResolver.query(
                TMP_CONTENT_URI,
                arrayOf(SCREEN, CELLX, CELLY, INTENT),
                "container=$CONTAINER_DESKTOP",
                null,
                null,
                null
        ) ?: throw IllegalStateException()
        locMap = parseLocMap(context, c)
        // Expected workspace items in grid A
        // _ _ _ _ _
        // _ _ _ _ 5
        // 9 _ 6 _ 7
        // _ _ 8 _ _
        // _ _ _ _ _
        assertThat(locMap.size.toLong()).isEqualTo(5)
        // Verify items that existed in grid A remains in same position
        assertThat(locMap[testPackage5]).isEqualTo(Triple(0, 4, 1))
        assertThat(locMap[testPackage6]).isEqualTo(Triple(0, 2, 2))
        assertThat(locMap[testPackage7]).isEqualTo(Triple(0, 4, 2))
        assertThat(locMap[testPackage8]).isEqualTo(Triple(0, 2, 3))
        // Verify items that didn't exist in grid A are added in new screen
        assertThat(locMap[testPackage9]).isEqualTo(Triple(0, 0, 2))

        // remove item from B
        modelHelper.deleteItem(7, TMP_TABLE)

        // migrate from A -> B
        GridSizeMigrationUtil.migrate(
                context,
                db,
                readerGridA,
                readerGridB,
                idp.numDatabaseHotseatIcons,
                Point(idp.numColumns, idp.numRows),
                DeviceGridState(context),
                DeviceGridState(idp)
        )

        // Check hotseat items in grid B
        c = context.contentResolver.query(
                CONTENT_URI,
                arrayOf(SCREEN, INTENT),
                "container=$CONTAINER_HOTSEAT",
                null,
                SCREEN,
                null
        ) ?: throw IllegalStateException()
        // Expected hotseat items in grid B
        // 2 1 3 4
        verifyHotseat(c, idp,
                mutableListOf(testPackage2, testPackage1, testPackage3, testPackage4).toList())

        // Check workspace items in grid B
        c = context.contentResolver.query(
                CONTENT_URI,
                arrayOf(SCREEN, CELLX, CELLY, INTENT),
                "container=$CONTAINER_DESKTOP",
                null,
                null,
                null
        ) ?: throw IllegalStateException()
        locMap = parseLocMap(context, c)
        // Expected workspace items in grid B
        // _ _ _ _
        // 5 6 _ 8
        // 9 _ _ _
        // _ _ _ _
        assertThat(locMap.size.toLong()).isEqualTo(4)
        assertThat(locMap[testPackage5]).isEqualTo(Triple(0, 0, 1))
        assertThat(locMap[testPackage6]).isEqualTo(Triple(0, 1, 1))
        assertThat(locMap[testPackage8]).isEqualTo(Triple(0, 3, 1))
        assertThat(locMap[testPackage9]).isEqualTo(Triple(0, 0, 2))
    }

    private fun verifyHotseat(c: Cursor, idp: InvariantDeviceProfile, expected: List<String?>) {
        assertThat(c.count).isEqualTo(idp.numDatabaseHotseatIcons)
        val screenIndex = c.getColumnIndex(SCREEN)
        val intentIndex = c.getColumnIndex(INTENT)
        expected.forEachIndexed { idx, pkg ->
            if (pkg == null) return@forEachIndexed
            c.moveToNext()
            assertThat(c.getInt(screenIndex).toLong()).isEqualTo(idx)
            assertThat(c.getString(intentIndex)).contains(pkg)
        }
        c.close()
    }

    private fun parseLocMap(context: Context, c: Cursor): Map<String, Triple<Int, Int, Int>> {
        // Check workspace items
        val intentIndex = c.getColumnIndex(INTENT)
        val screenIndex = c.getColumnIndex(SCREEN)
        val cellXIndex = c.getColumnIndex(CELLX)
        val cellYIndex = c.getColumnIndex(CELLY)
        val locMap = mutableMapOf<String, Triple<Int, Int, Int>>()
        while (c.moveToNext()) {
            locMap[Intent.parseUri(c.getString(intentIndex), 0).getPackage()] =
                    Triple(c.getInt(screenIndex), c.getInt(cellXIndex), c.getInt(cellYIndex))
        }
        c.close()
        return locMap.toMap()
    }

    @Test
+6 −0
Original line number Diff line number Diff line
@@ -362,6 +362,12 @@ public class LauncherModelHelper {
        sandboxContext.getContentResolver().insert(contentUri, values);
    }

    public void deleteItem(int itemId, @NonNull final String tableName) {
        final Uri uri = Uri.parse("content://"
                + LauncherProvider.AUTHORITY + "/" + tableName + "/" + itemId);
        sandboxContext.getContentResolver().delete(uri, null, null);
    }

    public int[][][] createGrid(int[][][] typeArray) {
        return createGrid(typeArray, 1);
    }