diff options
Diffstat (limited to 'tests/src/com/android/launcher3/celllayout/board')
8 files changed, 932 insertions, 0 deletions
diff --git a/tests/src/com/android/launcher3/celllayout/board/CellLayoutBoard.java b/tests/src/com/android/launcher3/celllayout/board/CellLayoutBoard.java new file mode 100644 index 0000000000..dbbdcf519a --- /dev/null +++ b/tests/src/com/android/launcher3/celllayout/board/CellLayoutBoard.java @@ -0,0 +1,415 @@ +/* + * 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.board; + +import android.graphics.Point; +import android.graphics.Rect; + +import androidx.annotation.NonNull; + +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Queue; +import java.util.Set; +import java.util.stream.Collectors; + + +public class CellLayoutBoard implements Comparable<CellLayoutBoard> { + + public static final Comparator<CellLayoutBoard> COMPARATOR = new IdenticalBoardComparator(); + + @Override + public int compareTo(@NonNull CellLayoutBoard cellLayoutBoard) { + return COMPARATOR.compare(this, cellLayoutBoard); + } + + private HashSet<Character> mUsedWidgetTypes = new HashSet<>(); + + static final int INFINITE = 99999; + + char[][] mWidget = new char[30][30]; + + List<WidgetRect> mWidgetsRects = new ArrayList<>(); + Map<Character, WidgetRect> mWidgetsMap = new HashMap<>(); + + List<IconPoint> mIconPoints = new ArrayList<>(); + List<FolderPoint> mFolderPoints = new ArrayList<>(); + + WidgetRect mMain = null; + + int mWidth, mHeight; + + public CellLayoutBoard() { + for (int x = 0; x < mWidget.length; x++) { + for (int y = 0; y < mWidget[0].length; y++) { + mWidget[x][y] = CellType.EMPTY; + } + } + } + + public CellLayoutBoard(int width, int height) { + mWidget = new char[width + 1][height + 1]; + this.mWidth = width; + this.mHeight = height; + for (int x = 0; x < mWidget.length; x++) { + for (int y = 0; y < mWidget[0].length; y++) { + mWidget[x][y] = CellType.EMPTY; + } + } + } + + public boolean pointInsideRect(int x, int y, WidgetRect rect) { + Boolean isXInRect = x >= rect.getCellX() && x < rect.getCellX() + rect.getSpanX(); + Boolean isYInRect = y >= rect.getCellY() && y < rect.getCellY() + rect.getSpanY(); + return isXInRect && isYInRect; + } + + public WidgetRect getWidgetAt(Point p) { + return getWidgetAt(p.x, p.y); + } + + public WidgetRect getWidgetOfType(char type) { + return mWidgetsRects.stream() + .filter(widgetRect -> widgetRect.mType == type).findFirst().orElse(null); + } + + public WidgetRect getWidgetAt(int x, int y) { + return mWidgetsRects.stream() + .filter(widgetRect -> pointInsideRect(x, y, widgetRect)).findFirst().orElse(null); + } + + public List<WidgetRect> getWidgets() { + return mWidgetsRects; + } + + public List<IconPoint> getIcons() { + return mIconPoints; + } + + public List<FolderPoint> getFolders() { + return mFolderPoints; + } + + public WidgetRect getMain() { + return mMain; + } + + public WidgetRect getWidgetRect(char c) { + return mWidgetsMap.get(c); + } + + private void removeWidgetFromBoard(WidgetRect widget) { + for (int xi = widget.mBounds.left; xi <= widget.mBounds.right; xi++) { + for (int yi = widget.mBounds.bottom; yi <= widget.mBounds.top; yi++) { + mWidget[xi][yi] = '-'; + } + } + } + + private void removeOverlappingItems(Rect rect) { + // Remove overlapping widgets and remove them from the board + mWidgetsRects = mWidgetsRects.stream().filter(widget -> { + if (rect.intersect(widget.mBounds)) { + removeWidgetFromBoard(widget); + return false; + } + return true; + }).collect(Collectors.toList()); + // Remove overlapping icons and remove them from the board + mIconPoints = mIconPoints.stream().filter(iconPoint -> { + int x = iconPoint.coord.x; + int y = iconPoint.coord.y; + if (rect.contains(x, y)) { + mWidget[x][y] = '-'; + return false; + } + return true; + }).collect(Collectors.toList()); + + // Remove overlapping folders and remove them from the board + mFolderPoints = mFolderPoints.stream().filter(folderPoint -> { + int x = folderPoint.coord.x; + int y = folderPoint.coord.y; + if (rect.contains(x, y)) { + mWidget[x][y] = '-'; + return false; + } + return true; + }).collect(Collectors.toList()); + } + + private void removeOverlappingItems(Point p) { + // Remove overlapping widgets and remove them from the board + mWidgetsRects = mWidgetsRects.stream().filter(widget -> { + if (IdenticalBoardComparator.Companion.touchesPoint(widget.mBounds, p)) { + removeWidgetFromBoard(widget); + return false; + } + return true; + }).collect(Collectors.toList()); + // Remove overlapping icons and remove them from the board + mIconPoints = mIconPoints.stream().filter(iconPoint -> { + int x = iconPoint.coord.x; + int y = iconPoint.coord.y; + if (p.x == x && p.y == y) { + mWidget[x][y] = '-'; + return false; + } + return true; + }).collect(Collectors.toList()); + + // Remove overlapping folders and remove them from the board + mFolderPoints = mFolderPoints.stream().filter(folderPoint -> { + int x = folderPoint.coord.x; + int y = folderPoint.coord.y; + if (p.x == x && p.y == y) { + mWidget[x][y] = '-'; + return false; + } + return true; + }).collect(Collectors.toList()); + } + + private char getNextWidgetType() { + for (char type = 'a'; type < 'z'; type++) { + if (type == CellType.ICON) continue; + if (type == CellType.IGNORE) continue; + if (mUsedWidgetTypes.contains(type)) continue; + mUsedWidgetTypes.add(type); + return type; + } + return 'z'; + } + + public void addWidget(int x, int y, int spanX, int spanY, char type) { + Rect rect = new Rect(x, y + spanY - 1, x + spanX - 1, y); + removeOverlappingItems(rect); + WidgetRect widgetRect = new WidgetRect(type, rect); + mWidgetsRects.add(widgetRect); + for (int xi = rect.left; xi < rect.right + 1; xi++) { + for (int yi = rect.bottom; yi < rect.top + 1; yi++) { + mWidget[xi][yi] = type; + } + } + } + + public void removeItem(char type) { + mWidgetsRects.stream() + .filter(widgetRect -> widgetRect.mType == type) + .forEach(widgetRect -> removeOverlappingItems( + new Point(widgetRect.getCellX(), widgetRect.getCellY()))); + } + + public void removeItem(Point p) { + removeOverlappingItems(p); + } + + public void addWidget(int x, int y, int spanX, int spanY) { + addWidget(x, y, spanX, spanY, getNextWidgetType()); + } + + public void addIcon(int x, int y) { + Point iconCoord = new Point(x, y); + removeOverlappingItems(iconCoord); + mIconPoints.add(new IconPoint(iconCoord, CellType.ICON)); + mWidget[x][y] = 'i'; + } + + public static WidgetRect 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 WidgetRect(type, widgetRect); + } + + public static boolean isFolder(char type) { + return type >= 'A' && type <= 'Z'; + } + + public static boolean isWidget(char type) { + return type != CellType.ICON && type != CellType.EMPTY && (type >= 'a' && type <= 'z'); + } + + public static boolean isIcon(char type) { + return type == CellType.ICON; + } + + private static List<WidgetRect> getRects(char[][] board) { + Set<Point> used = new HashSet<>(); + List<WidgetRect> 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<IconPoint> getIconPoints(char[][] board) { + List<IconPoint> 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 IconPoint(new Point(x, y), board[x][y])); + } + } + } + return iconPoints; + } + + private static List<FolderPoint> getFolderPoints(char[][] board) { + List<FolderPoint> folderPoints = new ArrayList<>(); + for (int x = 0; x < board.length; x++) { + for (int y = 0; y < board[0].length; y++) { + if (isFolder(board[x][y])) { + folderPoints.add(new FolderPoint(new Point(x, y), board[x][y])); + } + } + } + return folderPoints; + } + + public static WidgetRect getMainFromList(List<CellLayoutBoard> boards) { + for (CellLayoutBoard board : boards) { + WidgetRect main = board.getMain(); + if (main != null) { + return main; + } + } + return null; + } + + public static WidgetRect getWidgetIn(List<CellLayoutBoard> boards, int x, int y) { + for (CellLayoutBoard board : boards) { + WidgetRect main = board.getWidgetAt(x, y); + if (main != null) { + return main; + } + x -= board.mWidth; + } + return null; + } + + 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 != CellType.EMPTY) { + board.mWidget[x][y] = line.charAt(x); + } + } + } + board.mHeight = lines.length; + board.mWidth = lines[0].length(); + board.mWidgetsRects = getRects(board.mWidget); + board.mWidgetsRects.forEach(widgetRect -> { + if (widgetRect.mType == CellType.MAIN_WIDGET) { + board.mMain = widgetRect; + } + board.mWidgetsMap.put(widgetRect.mType, widgetRect); + }); + board.mIconPoints = getIconPoints(board.mWidget); + board.mFolderPoints = getFolderPoints(board.mWidget); + return board; + } + + public String toString(int maxX, int maxY) { + StringBuilder s = new StringBuilder(); + s.append("board: "); + s.append(maxX); + s.append("x"); + s.append(maxY); + s.append("\n"); + maxX = Math.min(maxX, mWidget.length); + maxY = Math.min(maxY, mWidget[0].length); + for (int y = 0; y <= maxY; y++) { + for (int x = 0; x <= maxX; x++) { + s.append(mWidget[x][y]); + } + s.append('\n'); + } + return s.toString(); + } + + @Override + public String toString() { + return toString(mWidth, mHeight); + } + + public static List<CellLayoutBoard> boardListFromString(String boardsStr) { + String[] lines = boardsStr.split("\n"); + ArrayList<String> individualBoards = new ArrayList<>(); + ArrayList<CellLayoutBoard> boards = new ArrayList<>(); + for (String line : lines) { + String[] boardSegment = line.split("\\|"); + for (int i = 0; i < boardSegment.length; i++) { + if (i >= individualBoards.size()) { + individualBoards.add(boardSegment[i]); + } else { + individualBoards.set(i, individualBoards.get(i) + "\n" + boardSegment[i]); + } + } + } + for (String board : individualBoards) { + boards.add(CellLayoutBoard.boardFromString(board)); + } + return boards; + } + + public int getWidth() { + return mWidth; + } + + public int getHeight() { + return mHeight; + } +} diff --git a/tests/src/com/android/launcher3/celllayout/board/CellType.java b/tests/src/com/android/launcher3/celllayout/board/CellType.java new file mode 100644 index 0000000000..49c146b32a --- /dev/null +++ b/tests/src/com/android/launcher3/celllayout/board/CellType.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2023 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.board; + +public class CellType { + // The cells marked by this will be filled by 1x1 widgets and will be ignored when + // validating + public static final char IGNORE = 'x'; + // The cells marked by this will be filled by app icons + public static final char ICON = 'i'; + // The cells marked by FOLDER will be filled by folders with 27 app icons inside + public static final char FOLDER = 'Z'; + // Empty space + public static final char EMPTY = '-'; + // Widget that will be saved as "main widget" for easier retrieval + public static final char MAIN_WIDGET = 'm'; + // Everything else will be consider a widget +} diff --git a/tests/src/com/android/launcher3/celllayout/board/FolderPoint.java b/tests/src/com/android/launcher3/celllayout/board/FolderPoint.java new file mode 100644 index 0000000000..39ba434dc0 --- /dev/null +++ b/tests/src/com/android/launcher3/celllayout/board/FolderPoint.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2023 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.board; + +import android.graphics.Point; + +public class FolderPoint { + public Point coord; + public char mType; + + public FolderPoint(Point coord, char type) { + this.coord = coord; + mType = type; + } + + /** + * [A-Z]: Represents a folder and number of icons in the folder is represented by + * the order of letter in the alphabet, A=2, B=3, C=4 ... etc. + */ + public int getNumberIconsInside() { + return (mType - 'A') + 2; + } +} diff --git a/tests/src/com/android/launcher3/celllayout/board/IconPoint.java b/tests/src/com/android/launcher3/celllayout/board/IconPoint.java new file mode 100644 index 0000000000..d3d297003d --- /dev/null +++ b/tests/src/com/android/launcher3/celllayout/board/IconPoint.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2023 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.board; + +import android.graphics.Point; + +public class IconPoint { + public Point coord; + public char mType; + + public IconPoint(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; + } +} diff --git a/tests/src/com/android/launcher3/celllayout/board/IdenticalBoardComparator.kt b/tests/src/com/android/launcher3/celllayout/board/IdenticalBoardComparator.kt new file mode 100644 index 0000000000..a4a420cf59 --- /dev/null +++ b/tests/src/com/android/launcher3/celllayout/board/IdenticalBoardComparator.kt @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2023 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.board + +import android.graphics.Point +import android.graphics.Rect + +/** + * Compares two [CellLayoutBoard] and returns 0 if they are identical, meaning they have the same + * widget and icons in the same place, they can be different letters tough. + */ +class IdenticalBoardComparator : Comparator<CellLayoutBoard> { + + /** Converts a list of WidgetRect into a map of the count of different widget.bounds */ + private fun widgetsToBoundsMap(widgets: List<WidgetRect>) = + widgets.groupingBy { it.mBounds }.eachCount() + + /** Converts a list of IconPoint into a map of the count of different icon.coord */ + private fun iconsToPosCountMap(widgets: List<IconPoint>) = + widgets.groupingBy { it.getCoord() }.eachCount() + + override fun compare( + cellLayoutBoard: CellLayoutBoard, + otherCellLayoutBoard: CellLayoutBoard + ): Int { + // to be equal they need to have the same number of widgets and the same dimensions + // their order can be different + val widgetsMap: Map<Rect, Int> = + widgetsToBoundsMap(cellLayoutBoard.widgets.filter { !it.shouldIgnore() }) + val ignoredRectangles: Map<Rect, Int> = + widgetsToBoundsMap(cellLayoutBoard.widgets.filter { it.shouldIgnore() }) + + val otherWidgetMap: Map<Rect, Int> = + widgetsToBoundsMap( + otherCellLayoutBoard.widgets + .filter { !it.shouldIgnore() } + .filter { !overlapsWithIgnored(ignoredRectangles, it.mBounds) } + ) + + if (widgetsMap != otherWidgetMap) { + return -1 + } + + // to be equal they need to have the same number of icons their order can be different + return if ( + iconsToPosCountMap(cellLayoutBoard.icons) == + iconsToPosCountMap(otherCellLayoutBoard.icons) + ) { + 0 + } else { + 1 + } + } + + private fun overlapsWithIgnored(ignoredRectangles: Map<Rect, Int>, rect: Rect): Boolean { + for (ignoredRect in ignoredRectangles.keys) { + // Using the built in intersects doesn't work because it doesn't account for area 0 + if (touches(ignoredRect, rect)) { + return true + } + } + return false + } + + companion object { + /** + * Similar function to {@link Rect#intersects} but this one returns true if the rectangles + * are intersecting or touching whereas {@link Rect#intersects} doesn't return true when + * they are touching. + */ + fun touches(r1: Rect, r2: Rect): Boolean { + // If one rectangle is on left side of other + return if (r1.left > r2.right || r2.left > r1.right) { + false + } else r1.bottom <= r2.top && r2.bottom <= r1.top + + // If one rectangle is above other + } + + /** + * Similar function to {@link Rect#contains} but this one returns true if {link @Point} is + * intersecting or touching the {@link Rect}. Similar to {@link touches}. + */ + fun touchesPoint(r1: Rect, p: Point): Boolean { + return r1.left <= p.x && p.x <= r1.right && r1.bottom <= p.y && p.y <= r1.top + } + } +} diff --git a/tests/src/com/android/launcher3/celllayout/board/PermutedBoardComparator.kt b/tests/src/com/android/launcher3/celllayout/board/PermutedBoardComparator.kt new file mode 100644 index 0000000000..c3d13a5701 --- /dev/null +++ b/tests/src/com/android/launcher3/celllayout/board/PermutedBoardComparator.kt @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2023 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.board + +import android.graphics.Point + +/** + * Compares two [CellLayoutBoard] and returns 0 if they contain the same widgets and icons even if + * they are in different positions i.e. in a different permutation. + */ +class PermutedBoardComparator : Comparator<CellLayoutBoard> { + + /** + * The key for the set is the span since the widgets could change location but shouldn't change + * size + */ + private fun boardToSpanCountMap(widgets: List<WidgetRect>) = + widgets.groupingBy { Point(it.spanX, it.spanY) }.eachCount() + override fun compare( + cellLayoutBoard: CellLayoutBoard, + otherCellLayoutBoard: CellLayoutBoard + ): Int { + return if ( + boardToSpanCountMap(cellLayoutBoard.widgets) != + boardToSpanCountMap(otherCellLayoutBoard.widgets) + ) { + 1 + } else cellLayoutBoard.icons.size.compareTo(otherCellLayoutBoard.icons.size) + } +} diff --git a/tests/src/com/android/launcher3/celllayout/board/TestWorkspaceBuilder.java b/tests/src/com/android/launcher3/celllayout/board/TestWorkspaceBuilder.java new file mode 100644 index 0000000000..06a7db2ae0 --- /dev/null +++ b/tests/src/com/android/launcher3/celllayout/board/TestWorkspaceBuilder.java @@ -0,0 +1,200 @@ +/* + * Copyright (C) 2023 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.board; + +import static androidx.test.core.app.ApplicationProvider.getApplicationContext; +import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; + +import static com.android.launcher3.ui.TestViewHelpers.findWidgetProvider; +import static com.android.launcher3.util.WidgetUtils.createWidgetInfo; + +import android.content.ComponentName; +import android.content.ContentResolver; +import android.content.Context; +import android.graphics.Rect; +import android.os.Process; +import android.os.UserHandle; +import android.util.Log; + +import com.android.launcher3.InvariantDeviceProfile; +import com.android.launcher3.LauncherSettings; +import com.android.launcher3.celllayout.FavoriteItemsTransaction; +import com.android.launcher3.celllayout.board.CellLayoutBoard; +import com.android.launcher3.celllayout.board.CellType; +import com.android.launcher3.celllayout.board.FolderPoint; +import com.android.launcher3.celllayout.board.IconPoint; +import com.android.launcher3.celllayout.board.WidgetRect; +import com.android.launcher3.model.data.AppInfo; +import com.android.launcher3.model.data.FolderInfo; +import com.android.launcher3.model.data.ItemInfo; +import com.android.launcher3.model.data.LauncherAppWidgetInfo; +import com.android.launcher3.model.data.WorkspaceItemInfo; +import com.android.launcher3.widget.LauncherAppWidgetProviderInfo; + +import java.util.function.Supplier; +import java.util.stream.IntStream; + +public class TestWorkspaceBuilder { + + private static final String TAG = "CellLayoutBoardBuilder"; + private static final String TEST_ACTIVITY_PACKAGE_PREFIX = "com.android.launcher3.tests."; + private ComponentName mAppComponentName = new ComponentName( + "com.google.android.calculator", "com.android.calculator2.Calculator"); + private UserHandle mMyUser; + + private Context mContext; + private ContentResolver mResolver; + + public TestWorkspaceBuilder(Context context) { + mMyUser = Process.myUserHandle(); + mContext = context; + mResolver = mContext.getContentResolver(); + } + + /** + * Fills the given rect in WidgetRect with 1x1 widgets. This is useful to equalize cases. + */ + private FavoriteItemsTransaction fillWithWidgets(WidgetRect widgetRect, + FavoriteItemsTransaction transaction, int screenId) { + int initX = widgetRect.getCellX(); + int initY = widgetRect.getCellY(); + for (int x = initX; x < initX + widgetRect.getSpanX(); x++) { + for (int y = initY; y < initY + widgetRect.getSpanY(); y++) { + try { + // this widgets are filling, we don't care if we can't place them + transaction.addItem(createWidgetInCell( + new WidgetRect(CellType.IGNORE, + new Rect(x, y, x, y)), screenId)); + } catch (Exception e) { + Log.d(TAG, "Unable to place filling widget at " + x + "," + y); + } + } + } + return transaction; + } + + private AppInfo getApp() { + return new AppInfo(mAppComponentName, "test icon", mMyUser, + AppInfo.makeLaunchIntent(mAppComponentName)); + } + + /** + * Helper to set the app to use for the test workspace, + * using activity-alias from AndroidManifest-common. + * @param testAppName the android:name field of the test app activity-alias to use + */ + public void setTestAppActivityAlias(String testAppName) { + this.mAppComponentName = new ComponentName( + getInstrumentation().getContext().getPackageName(), + TEST_ACTIVITY_PACKAGE_PREFIX + testAppName + ); + } + + private void addCorrespondingWidgetRect(WidgetRect widgetRect, + FavoriteItemsTransaction transaction, int screenId) { + if (widgetRect.mType == 'x') { + fillWithWidgets(widgetRect, transaction, screenId); + } else { + transaction.addItem(createWidgetInCell(widgetRect, screenId)); + } + } + + /** + * Builds the given board into the transaction + */ + public FavoriteItemsTransaction buildFromBoard(CellLayoutBoard board, + FavoriteItemsTransaction transaction, final int screenId) { + board.getWidgets().forEach( + (widgetRect) -> addCorrespondingWidgetRect(widgetRect, transaction, screenId)); + board.getIcons().forEach((iconPoint) -> + transaction.addItem(() -> createIconInCell(iconPoint, screenId)) + ); + board.getFolders().forEach((folderPoint) -> + transaction.addItem(() -> createFolderInCell(folderPoint, screenId)) + ); + return transaction; + } + + /** + * Fills the hotseat row with apps instead of suggestions, for this to work the workspace should + * be clean otherwise this doesn't overrides the existing icons. + */ + public FavoriteItemsTransaction fillHotseatIcons(FavoriteItemsTransaction transaction) { + IntStream.range(0, InvariantDeviceProfile.INSTANCE.get(mContext).numDatabaseHotseatIcons) + .forEach(i -> transaction.addItem(() -> getHotseatValues(i))); + return transaction; + } + + private Supplier<ItemInfo> createWidgetInCell( + WidgetRect widgetRect, int screenId) { + // Create the widget lazily since the appWidgetId can get lost during setup + return () -> { + LauncherAppWidgetProviderInfo info = findWidgetProvider(false); + LauncherAppWidgetInfo item = createWidgetInfo(info, getApplicationContext(), true); + item.cellX = widgetRect.getCellX(); + item.cellY = widgetRect.getCellY(); + item.spanX = widgetRect.getSpanX(); + item.spanY = widgetRect.getSpanY(); + item.screenId = screenId; + return item; + }; + } + + public FolderInfo createFolderInCell(FolderPoint folderPoint, int screenId) { + FolderInfo folderInfo = new FolderInfo(); + folderInfo.screenId = screenId; + folderInfo.container = LauncherSettings.Favorites.CONTAINER_DESKTOP; + folderInfo.cellX = folderPoint.coord.x; + folderInfo.cellY = folderPoint.coord.y; + folderInfo.minSpanY = folderInfo.minSpanX = folderInfo.spanX = folderInfo.spanY = 1; + folderInfo.setOption(FolderInfo.FLAG_MULTI_PAGE_ANIMATION, true, null); + + for (int i = 0; i < folderPoint.getNumberIconsInside(); i++) { + folderInfo.add(getDefaultWorkspaceItem(screenId), false); + } + + return folderInfo; + } + + private WorkspaceItemInfo getDefaultWorkspaceItem(int screenId) { + WorkspaceItemInfo item = new WorkspaceItemInfo(getApp()); + item.screenId = screenId; + item.minSpanY = item.minSpanX = item.spanX = item.spanY = 1; + item.container = LauncherSettings.Favorites.CONTAINER_DESKTOP; + return item; + } + + private ItemInfo createIconInCell(IconPoint iconPoint, int screenId) { + WorkspaceItemInfo item = new WorkspaceItemInfo(getApp()); + item.screenId = screenId; + item.cellX = iconPoint.getCoord().x; + item.cellY = iconPoint.getCoord().y; + item.minSpanY = item.minSpanX = item.spanX = item.spanY = 1; + item.container = LauncherSettings.Favorites.CONTAINER_DESKTOP; + return item; + } + + private ItemInfo getHotseatValues(int x) { + WorkspaceItemInfo item = new WorkspaceItemInfo(getApp()); + item.cellX = x; + item.cellY = 0; + item.minSpanY = item.minSpanX = item.spanX = item.spanY = 1; + item.rank = x; + item.screenId = x; + item.container = LauncherSettings.Favorites.CONTAINER_HOTSEAT; + return item; + } +} diff --git a/tests/src/com/android/launcher3/celllayout/board/WidgetRect.java b/tests/src/com/android/launcher3/celllayout/board/WidgetRect.java new file mode 100644 index 0000000000..c90ce8504f --- /dev/null +++ b/tests/src/com/android/launcher3/celllayout/board/WidgetRect.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2023 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.board; + +import android.graphics.Rect; + +public class WidgetRect { + public char mType; + public Rect mBounds; + + public WidgetRect(char type, Rect bounds) { + this.mType = type; + this.mBounds = bounds; + } + + public int getSpanX() { + return mBounds.right - mBounds.left + 1; + } + + public int getSpanY() { + return mBounds.top - mBounds.bottom + 1; + } + + public int getCellX() { + return mBounds.left; + } + + public int getCellY() { + return mBounds.bottom; + } + + boolean shouldIgnore() { + return this.mType == CellType.IGNORE; + } + + boolean contains(int x, int y) { + return mBounds.contains(x, y); + } + + @Override + public String toString() { + return "WidgetRect type = " + mType + " x = " + getCellX() + " | y " + getCellY() + + " xs = " + getSpanX() + " ys = " + getSpanY(); + } +} |