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

Commit efab2489 authored by Sebastian Franco's avatar Sebastian Franco
Browse files

Generate grid migration cases and test if they are valid

This makes sure the grid migration logic always produces
valid results.

Bug: 313900847
Bug: 323525592
Flag: NA
Test: ValidGridMigrationUnitTest.kt
Change-Id: I76b19e1fa315f8a997afad34e5a4df7cc465b0c2
parent 09ec3e26
Loading
Loading
Loading
Loading
+0 −5
Original line number Diff line number Diff line
@@ -397,11 +397,6 @@ public final class FeatureFlags {
                    + "waiting for SystemUI and then merging the SystemUI progress whenever we "
                    + "start receiving the events");

    // TODO(Block 24): Clean up flags
    public static final BooleanFlag ENABLE_NEW_MIGRATION_LOGIC = getDebugFlag(270393455,
            "ENABLE_NEW_MIGRATION_LOGIC", ENABLED,
            "Enable the new grid migration logic, keeping pages when src < dest");

    // TODO(Block 25): Clean up flags
    public static final BooleanFlag ENABLE_NEW_GESTURE_NAV_TUTORIAL = getDebugFlag(270396257,
            "ENABLE_NEW_GESTURE_NAV_TUTORIAL", ENABLED,
+2 −2
Original line number Diff line number Diff line
@@ -156,11 +156,11 @@ public class DeviceGridState implements Comparable<DeviceGridState> {
    }

    public Integer getColumns() {
        return Integer.parseInt(String.valueOf(mGridSizeString.charAt(0)));
        return Integer.parseInt(String.valueOf(mGridSizeString.split(",")[0]));
    }

    public Integer getRows() {
        return Integer.parseInt(String.valueOf(mGridSizeString.charAt(2)));
        return Integer.parseInt(String.valueOf(mGridSizeString.split(",")[1]));
    }

    @Override
+7 −14
Original line number Diff line number Diff line
@@ -223,19 +223,13 @@ public class GridSizeMigrationUtil {
            screens.add(screenId);
        }

        boolean preservePages = false;
        if (screens.isEmpty() && FeatureFlags.ENABLE_NEW_MIGRATION_LOGIC.get()) {
            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);
            }
            solveGridPlacement(helper, srcReader,
                    destReader, screenId, trgX, trgY, workspaceToBeAdded, false);
                    destReader, screenId, trgX, trgY, workspaceToBeAdded);
            if (workspaceToBeAdded.isEmpty()) {
                break;
            }
@@ -245,8 +239,8 @@ public class GridSizeMigrationUtil {
        // 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 (!workspaceToBeAdded.isEmpty()) {
            solveGridPlacement(helper, srcReader,
                    destReader, screenId, trgX, trgY, workspaceToBeAdded, preservePages);
            solveGridPlacement(helper, srcReader, destReader, screenId, trgX, trgY,
                    workspaceToBeAdded);
            screenId++;
        }

@@ -348,7 +342,7 @@ public class GridSizeMigrationUtil {
    private static void solveGridPlacement(@NonNull final DatabaseHelper helper,
            @NonNull final DbReader srcReader, @NonNull final DbReader destReader,
            final int screenId, final int trgX, final int trgY,
            @NonNull final List<DbEntry> sortedItemsToPlace, final boolean matchingScreenIdOnly) {
            @NonNull final List<DbEntry> sortedItemsToPlace) {
        final GridOccupancy occupied = new GridOccupancy(trgX, trgY);
        final Point trg = new Point(trgX, trgY);
        final Point next = new Point(0, screenId == 0
@@ -366,8 +360,6 @@ public class GridSizeMigrationUtil {
        Iterator<DbEntry> iterator = sortedItemsToPlace.iterator();
        while (iterator.hasNext()) {
            final DbEntry entry = iterator.next();
            if (matchingScreenIdOnly && entry.screenId < screenId) continue;
            if (matchingScreenIdOnly && entry.screenId > screenId) break;
            if (entry.minSpanX > trgX || entry.minSpanY > trgY) {
                iterator.remove();
                continue;
@@ -435,7 +427,8 @@ public class GridSizeMigrationUtil {
        }
    }

    protected static class DbReader {
    @VisibleForTesting
    public static class DbReader {

        private final SQLiteDatabase mDb;
        private final String mTableName;
@@ -446,7 +439,7 @@ public class GridSizeMigrationUtil {
        private final Map<Integer, ArrayList<DbEntry>> mWorkspaceEntriesByScreenId =
                new ArrayMap<>();

        DbReader(SQLiteDatabase db, String tableName, Context context,
        public DbReader(SQLiteDatabase db, String tableName, Context context,
                Set<String> validPackages) {
            mDb = db;
            mTableName = tableName;
+18 −4
Original line number Diff line number Diff line
@@ -21,6 +21,13 @@ import java.util.Random

/** Generates a random CellLayoutBoard. */
open class RandomBoardGenerator(generator: Random) : DeterministicRandomGenerator(generator) {

    companion object {
        // This is the max number of widgets because we encode the widgets as letters A-Z and we
        // already have some of those letter used by other things so 22 is a safe number
        val MAX_NUMBER_OF_WIDGETS = 22
    }

    /**
     * @param remainingEmptySpaces the maximum number of spaces we will fill with icons and widgets
     *   meaning that if the number is 100 we will try to fill the board with at most 100 spaces
@@ -45,11 +52,18 @@ open class RandomBoardGenerator(generator: Random) : DeterministicRandomGenerato
        val y = area.top + getRandom(0, area.height() - height)
        if (remainingEmptySpaces > 0) {
            remainingEmptySpaces -= width * height
        } else if (board.widgets.size <= 22 && width * height > 1) {
        }

        if (board.widgets.size <= MAX_NUMBER_OF_WIDGETS && width * height > 1) {
            board.addWidget(x, y, width, height)
        } else {
            board.addIcon(x, y)
        }

        if (remainingEmptySpaces < 0) {
            // optimization, no need to keep going
            return board
        }
        fillBoard(board, Rect(area.left, area.top, area.right, y), remainingEmptySpaces)
        fillBoard(board, Rect(area.left, y, x, area.bottom), remainingEmptySpaces)
        fillBoard(board, Rect(x, y + height, area.right, area.bottom), remainingEmptySpaces)
+160 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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.celllayout.testgenerator

import android.graphics.Point
import com.android.launcher3.LauncherSettings
import com.android.launcher3.celllayout.board.CellLayoutBoard
import com.android.launcher3.model.data.LauncherAppWidgetInfo
import com.android.launcher3.model.gridmigration.WorkspaceItem
import java.util.Random
import java.util.concurrent.atomic.AtomicInteger

/**
 * Generate a list of WorkspaceItem's for the given test case.
 *
 * @param repeatAfter a number after which we would repeat the same number of icons and widgets to
 *   account for cases where the user have the same item multiple times.
 */
fun generateItemsForTest(
    testCase: GridMigrationUnitTestCase,
    repeatAfter: Int
): List<WorkspaceItem> {
    val id = AtomicInteger(0)
    val widgetId = AtomicInteger(LauncherAppWidgetInfo.CUSTOM_WIDGET_ID - 1)
    val boards = testCase.boards
    // Repeat the same appWidgetProvider and intent to have repeating widgets and icons and test
    // that case too
    val getIntent = { i: Int -> "Intent ${i % repeatAfter}" }
    val getProvider = { i: Int -> "com.test/test.Provider${i % repeatAfter}" }
    val hotseatEntries =
        (0 until boards[0].width).map {
            WorkspaceItem(
                x = it,
                y = 0,
                spanX = 1,
                spanY = 1,
                id = id.getAndAdd(1),
                screenId = it,
                title = "Hotseat ${id.get()}",
                appWidgetId = -1,
                appWidgetProvider = "Hotseat icons don't have a provider",
                intent = getIntent(id.get()),
                type = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION,
                container = LauncherSettings.Favorites.CONTAINER_HOTSEAT
            )
        }
    var widgetEntries =
        boards
            .flatMapIndexed { i, board -> board.widgets.map { Pair(i, it) } }
            .map {
                WorkspaceItem(
                    x = it.second.cellX,
                    y = it.second.cellY,
                    spanX = it.second.spanX,
                    spanY = it.second.spanY,
                    id = id.getAndAdd(1),
                    screenId = it.first,
                    title = "Title Widget ${id.get()}",
                    appWidgetId = widgetId.getAndAdd(-1),
                    appWidgetProvider = getProvider(id.get()),
                    intent = "Widgets don't have intent",
                    type = LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET,
                    container = LauncherSettings.Favorites.CONTAINER_DESKTOP
                )
            }
    widgetEntries = widgetEntries.filter { it.appWidgetProvider.contains("Provider4") }
    val iconEntries =
        boards
            .flatMapIndexed { i, board -> board.icons.map { Pair(i, it) } }
            .map {
                WorkspaceItem(
                    x = it.second.coord.x,
                    y = it.second.coord.y,
                    spanX = 1,
                    spanY = 1,
                    id = id.getAndAdd(1),
                    screenId = it.first,
                    title = "Title Icon ${id.get()}",
                    appWidgetId = -1,
                    appWidgetProvider = "Icons don't have providers",
                    intent = getIntent(id.get()),
                    type = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION,
                    container = LauncherSettings.Favorites.CONTAINER_DESKTOP
                )
            }
    return widgetEntries + hotseatEntries // + iconEntries
}

data class GridMigrationUnitTestCase(
    val boards: List<CellLayoutBoard>,
    val srcSize: Point,
    val targetSize: Point,
    val seed: Long
)

class ValidGridMigrationTestCaseGenerator(private val generator: Random) :
    DeterministicRandomGenerator(generator) {

    companion object {
        const val MAX_BOARD_SIZE = 12
        const val MAX_BOARD_COUNT = 10
        const val SEED = 10342
    }

    private fun generateBoards(
        boardGenerator: RandomBoardGenerator,
        width: Int,
        height: Int,
        boardCount: Int
    ): List<CellLayoutBoard> {
        val boards = mutableListOf<CellLayoutBoard>()
        for (i in 0 until boardCount) {
            boards.add(
                boardGenerator.generateBoard(
                    width,
                    height,
                    boardGenerator.getRandom(0, width * height)
                )
            )
        }
        return boards
    }

    fun generateTestCase(): GridMigrationUnitTestCase {
        var seed = generator.nextLong()
        val randomBoardGenerator = RandomBoardGenerator(Random(seed))
        val width = randomBoardGenerator.getRandom(3, MAX_BOARD_SIZE)
        val height = randomBoardGenerator.getRandom(3, MAX_BOARD_SIZE)
        return GridMigrationUnitTestCase(
            boards =
                generateBoards(
                    boardGenerator = randomBoardGenerator,
                    width = width,
                    height = height,
                    boardCount = randomBoardGenerator.getRandom(3, MAX_BOARD_COUNT)
                ),
            srcSize = Point(width, height),
            targetSize =
                Point(
                    randomBoardGenerator.getRandom(3, MAX_BOARD_SIZE),
                    randomBoardGenerator.getRandom(3, MAX_BOARD_SIZE)
                ),
            seed = seed
        )
    }
}
Loading