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

Commit 5cba4763 authored by Sebastian Franco's avatar Sebastian Franco
Browse files

Adding test for the Widgets reordering.

TAPL design chagnes at https://docs.google.com/document/d/1PdJZZIn-85-UMRFGZuqj-tJgruIWcg31lZnrb34iBTY/edit?resourcekey=0-uAZuiLCDFV9YhOtLB7wQHQ

The tests consist of a board representing the widgets on
the CellLayout a position to move the main widget (m) to
and the resulting board.

iiiii    iiiii
-----    --x--
-xxx- -> -xmx-
--m--    -----
-----    -----

Move m to 2,2.

Then whe make sure the device corresponds with the resulting
board.

I had to add the event TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "Widgets.onLongClick");
on ItemLongClickListener because the tests where not expecting
a long press on a widget after is being place on the
workspace.

Also, I needed to add the option to drag a widget
to a specific point instead of the previous option
of only dragging to the workspace.

Fix: 231449779
Test: Run the test and make sure they pass.
Change-Id: I58183b7ce2ca64c999e21073cce5e0ba6e6f3a9e
parent 55ce03ee
Loading
Loading
Loading
Loading
+6 −1
Original line number Diff line number Diff line
@@ -38,6 +38,7 @@ import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.testing.TestLogging;
import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.views.BubbleTextHolder;
import com.android.launcher3.widget.LauncherAppWidgetHostView;

/**
 * Class to handle long-clicks on workspace items and start drag as a result.
@@ -51,7 +52,11 @@ public class ItemLongClickListener {
            ItemLongClickListener::onAllAppsItemLongClick;

    private static boolean onWorkspaceItemLongClick(View v) {
        if (v instanceof LauncherAppWidgetHostView) {
            TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "Widgets.onLongClick");
        } else {
            TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "onWorkspaceItemLongClick");
        }
        Launcher launcher = Launcher.getLauncher(v.getContext());
        if (!canStartDrag(launcher)) return false;
        if (!launcher.isInState(NORMAL) && !launcher.isInState(OVERVIEW)) return false;
+152 −0
Original line number Diff line number Diff line
/*
 * 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.celllayout;

import android.graphics.Point;
import android.graphics.Rect;

import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Set;


public class CellLayoutBoard {

    static final int INFINITE = 99999;

    char[][] mBoard = new char[30][30];

    List<TestBoardWidget> mWidgetsRects = new ArrayList<>();
    Map<Character, TestBoardWidget> mWidgetsMap = new HashMap<>();

    List<TestBoardAppIcon> mIconPoints = new ArrayList<>();
    Map<Character, TestBoardAppIcon> mIconsMap = new HashMap<>();

    Point mMain = new Point();

    CellLayoutBoard() {
        for (int x = 0; x < mBoard.length; x++) {
            for (int y = 0; y < mBoard[0].length; y++) {
                mBoard[x][y] = '-';
            }
        }
    }

    public List<TestBoardWidget> getWidgets() {
        return mWidgetsRects;
    }

    public Point getMain() {
        return mMain;
    }

    public TestBoardWidget getWidgetRect(char c) {
        return mWidgetsMap.get(c);
    }

    public static TestBoardWidget getWidgetRect(int x, int y, Set<Point> used, char[][] board) {
        char type = board[x][y];
        Queue<Point> search = new ArrayDeque<Point>();
        Point current = new Point(x, y);
        search.add(current);
        used.add(current);
        List<Point> neighbors = new ArrayList<>(List.of(
                new Point(-1, 0),
                new Point(0, -1),
                new Point(1, 0),
                new Point(0, 1))
        );
        Rect widgetRect = new Rect(INFINITE, -INFINITE, -INFINITE, INFINITE);
        while (!search.isEmpty()) {
            current = search.poll();
            widgetRect.top = Math.max(widgetRect.top, current.y);
            widgetRect.right = Math.max(widgetRect.right, current.x);
            widgetRect.bottom = Math.min(widgetRect.bottom, current.y);
            widgetRect.left = Math.min(widgetRect.left, current.x);
            for (Point p : neighbors) {
                Point next = new Point(current.x + p.x, current.y + p.y);
                if (next.x < 0 || next.x >= board.length) continue;
                if (next.y < 0 || next.y >= board[0].length) continue;
                if (board[next.x][next.y] == type && !used.contains(next)) {
                    used.add(next);
                    search.add(next);
                }
            }
        }
        return new TestBoardWidget(type, widgetRect);
    }

    public static boolean isWidget(char type) {
        return type != 'i' && type != '-';
    }

    public static boolean isIcon(char type) {
        return type == 'i';
    }

    private static List<TestBoardWidget> getRects(char[][] board) {
        Set<Point> used = new HashSet<>();
        List<TestBoardWidget> widgetsRects = new ArrayList<>();
        for (int x = 0; x < board.length; x++) {
            for (int y = 0; y < board[0].length; y++) {
                if (!used.contains(new Point(x, y)) && isWidget(board[x][y])) {
                    widgetsRects.add(getWidgetRect(x, y, used, board));
                }
            }
        }
        return widgetsRects;
    }

    private static List<TestBoardAppIcon> getIconPoints(char[][] board) {
        List<TestBoardAppIcon> iconPoints = new ArrayList<>();
        for (int x = 0; x < board.length; x++) {
            for (int y = 0; y < board[0].length; y++) {
                if (isIcon(board[x][y])) {
                    iconPoints.add(new TestBoardAppIcon(new Point(x, y), board[x][y]));
                }
            }
        }
        return iconPoints;
    }

    public static CellLayoutBoard boardFromString(String boardStr) {
        String[] lines = boardStr.split("\n");
        CellLayoutBoard board = new CellLayoutBoard();

        for (int y = 0; y < lines.length; y++) {
            String line = lines[y];
            for (int x = 0; x < line.length(); x++) {
                char c = line.charAt(x);
                if (c == 'm') {
                    board.mMain = new Point(x, y);
                }
                if (c != '-') {
                    board.mBoard[x][y] = line.charAt(x);
                }
            }
        }
        board.mWidgetsRects = getRects(board.mBoard);
        board.mWidgetsRects.forEach(
                widgetRect -> board.mWidgetsMap.put(widgetRect.mType, widgetRect));
        board.mIconPoints = getIconPoints(board.mBoard);
        return board;
    }
}
+197 −0
Original line number Diff line number Diff line
/*
 * 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.celllayout;

import static com.android.launcher3.util.WidgetUtils.createWidgetInfo;

import static org.junit.Assert.assertTrue;

import android.graphics.Point;
import android.graphics.Rect;
import android.util.Log;
import android.view.View;

import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;

import com.android.launcher3.CellLayout;
import com.android.launcher3.celllayout.testcases.FullReorderCase;
import com.android.launcher3.celllayout.testcases.MoveOutReorderCase;
import com.android.launcher3.celllayout.testcases.PushReorderCase;
import com.android.launcher3.celllayout.testcases.ReorderTestCase;
import com.android.launcher3.celllayout.testcases.SimpleReorderCase;
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
import com.android.launcher3.ui.AbstractLauncherUiTest;
import com.android.launcher3.ui.TaplTestsLauncher3;
import com.android.launcher3.ui.TestViewHelpers;
import com.android.launcher3.util.rule.ShellCommandRule;
import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;

import org.junit.Assume;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;

import java.util.Map;


@SmallTest
@RunWith(AndroidJUnit4.class)
public class ReorderWidgets extends AbstractLauncherUiTest {

    @Rule
    public ShellCommandRule mGrantWidgetRule = ShellCommandRule.grantWidgetBind();

    private static final String TAG = ReorderWidgets.class.getSimpleName();

    private View getViewAt(int cellX, int cellY) {
        return getFromLauncher(l -> l.getWorkspace().getScreenWithId(
                l.getWorkspace().getScreenIdForPageIndex(0)).getChildAt(cellX, cellY));
    }

    private Point getCellDimensions() {
        return getFromLauncher(l -> {
            CellLayout cellLayout = l.getWorkspace().getScreenWithId(
                    l.getWorkspace().getScreenIdForPageIndex(0));
            return new Point(cellLayout.getWidth() / cellLayout.getCountX(),
                    cellLayout.getHeight() / cellLayout.getCountY());
        });
    }

    @Before
    public void setup() throws Throwable {
        TaplTestsLauncher3.initialize(this);
        clearHomescreen();
    }

    /**
     * Validate if the given board represent the current CellLayout
     **/
    private boolean validateBoard(CellLayoutBoard board) {
        boolean match = true;
        Point cellDimensions = getCellDimensions();
        for (TestBoardWidget widgetRect: board.getWidgets()) {
            if (widgetRect.shouldIgnore()) {
                continue;
            }
            View widget = getViewAt(widgetRect.getCellX(), widgetRect.getCellY());
            match &= widgetRect.getSpanX()
                    == Math.round(widget.getWidth() / (float) cellDimensions.x);
            match &= widgetRect.getSpanY()
                    == Math.round(widget.getHeight() / (float) cellDimensions.y);
            if (!match) return match;
        }
        return match;
    }

    /**
     * Fills the given rect in WidgetRect with 1x1 widgets. This is useful to equalize cases.
     */
    private void fillWithWidgets(TestBoardWidget widgetRect) {
        int initX = widgetRect.getCellX();
        int initY = widgetRect.getCellY();
        for (int x = 0; x < widgetRect.getSpanX(); x++) {
            for (int y = 0; y < widgetRect.getSpanY(); y++) {
                int auxX = initX + x;
                int auxY = initY + y;
                try {
                    // this widgets are filling, we don't care if we can't place them
                    addWidgetInCell(
                            new TestBoardWidget('x',
                                    new Rect(auxX, auxY, auxX, auxY))
                    );
                } catch (Exception e) {
                    Log.d(TAG, "Unable to place filling widget at " + auxX + "," + auxY);
                }
            }
        }
    }

    private void addWidgetInCell(TestBoardWidget widgetRect) {
        LauncherAppWidgetProviderInfo info = TestViewHelpers.findWidgetProvider(this, false);
        LauncherAppWidgetInfo item = createWidgetInfo(info,
                ApplicationProvider.getApplicationContext(), true);
        item.cellX = widgetRect.getCellX();
        item.cellY = widgetRect.getCellY();

        item.spanX = widgetRect.getSpanX();
        item.spanY = widgetRect.getSpanY();
        addItemToScreen(item);
    }

    private void addCorrespondingWidgetRect(TestBoardWidget widgetRect) {
        if (widgetRect.mType == 'x') {
            fillWithWidgets(widgetRect);
        } else {
            addWidgetInCell(widgetRect);
        }
    }

    private void runTestCase(ReorderTestCase testCase) {
        Point mainWidgetCellPos = testCase.mStart.getMain();

        testCase.mStart.getWidgets().forEach(this::addCorrespondingWidgetRect);

        mLauncher.getWorkspace()
                .getWidgetAtCell(mainWidgetCellPos.x, mainWidgetCellPos.y)
                .dragWidgetToWorkspace(testCase.moveMainTo.x, testCase.moveMainTo.y)
                .dismiss(); // dismiss resize frame

        boolean isValid = false;
        for (CellLayoutBoard board : testCase.mEnd) {
            isValid |= validateBoard(board);
        }
        assertTrue("None of the valid boards match with the current state", isValid);
    }

    /**
     * Run only the test define for the current grid size if such test exist
     *
     * @param testCaseMap map containing all the tests per grid size (Point)
     */
    private void runTestCaseMap(Map<Point, ReorderTestCase> testCaseMap, String testName) {
        Point iconGridDimensions = mLauncher.getWorkspace().getIconGridDimensions();
        Log.d(TAG, "Running test " + testName + " for grid " + iconGridDimensions);
        Assume.assumeTrue(
                "The test " + testName + " doesn't support " + iconGridDimensions + " grid layout",
                testCaseMap.containsKey(iconGridDimensions));
        runTestCase(testCaseMap.get(iconGridDimensions));
    }

    @Test
    public void simpleReorder() {
        runTestCaseMap(SimpleReorderCase.TEST_BY_GRID_SIZE,
                SimpleReorderCase.class.getSimpleName());
    }

    @Test
    public void pushTest() {
        runTestCaseMap(PushReorderCase.TEST_BY_GRID_SIZE, PushReorderCase.class.getSimpleName());
    }

    @Test
    public void fullReorder() {
        runTestCaseMap(FullReorderCase.TEST_BY_GRID_SIZE, FullReorderCase.class.getSimpleName());
    }

    @Test
    public void moveOutReorder() {
        runTestCaseMap(MoveOutReorderCase.TEST_BY_GRID_SIZE,
                MoveOutReorderCase.class.getSimpleName());
    }
}
+44 −0
Original line number Diff line number Diff line
/*
 * 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.celllayout;

import android.graphics.Point;

public class TestBoardAppIcon {
    public Point coord;
    public char mType;

    public TestBoardAppIcon(Point coord, char type) {
        this.coord = coord;
        mType = type;
    }

    public char getType() {
        return mType;
    }

    public void setType(char type) {
        mType = type;
    }

    public Point getCoord() {
        return coord;
    }

    public void setCoord(Point coord) {
        this.coord = coord;
    }
}
+48 −0
Original line number Diff line number Diff line
/*
 * 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.celllayout;

import android.graphics.Rect;

public class TestBoardWidget {
    public char mType;
    public Rect mBounds;

    TestBoardWidget(char type, Rect bounds) {
        this.mType = type;
        this.mBounds = bounds;
    }

    int getSpanX() {
        return mBounds.right - mBounds.left + 1;
    }

    int getSpanY() {
        return mBounds.top - mBounds.bottom + 1;
    }

    int getCellX() {
        return mBounds.left;
    }

    int getCellY() {
        return mBounds.bottom;
    }

    boolean shouldIgnore() {
        return this.mType == 'x';
    }
}
Loading