• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.launcher3.celllayout;
17 
18 import android.graphics.Point;
19 import android.graphics.Rect;
20 
21 import java.util.ArrayDeque;
22 import java.util.ArrayList;
23 import java.util.HashMap;
24 import java.util.HashSet;
25 import java.util.List;
26 import java.util.Map;
27 import java.util.Queue;
28 import java.util.Set;
29 import java.util.stream.Collectors;
30 
31 
32 public class CellLayoutBoard implements Comparable<CellLayoutBoard> {
33 
intersects(Rect r1, Rect r2)34     private boolean intersects(Rect r1, Rect r2) {
35         // If one rectangle is on left side of other
36         if (r1.left > r2.right || r2.left > r1.right) {
37             return false;
38         }
39 
40         // If one rectangle is above other
41         if (r1.bottom > r2.top || r2.bottom > r1.top) {
42             return false;
43         }
44 
45         return true;
46     }
47 
overlapsWithIgnored(Set<Rect> ignoredRectangles, Rect rect)48     private boolean overlapsWithIgnored(Set<Rect> ignoredRectangles, Rect rect) {
49         for (Rect ignoredRect : ignoredRectangles) {
50             // Using the built in intersects doesn't work because it doesn't account for area 0
51             if (intersects(ignoredRect, rect)) {
52                 return true;
53             }
54         }
55         return false;
56     }
57 
58     @Override
compareTo(CellLayoutBoard cellLayoutBoard)59     public int compareTo(CellLayoutBoard cellLayoutBoard) {
60         // to be equal they need to have the same number of widgets and the same dimensions
61         // their order can be different
62         Set<Rect> widgetsSet = new HashSet<>();
63         Set<Rect> ignoredRectangles = new HashSet<>();
64         for (WidgetRect rect : mWidgetsRects) {
65             if (rect.shouldIgnore()) {
66                 ignoredRectangles.add(rect.mBounds);
67             } else {
68                 widgetsSet.add(rect.mBounds);
69             }
70         }
71         for (WidgetRect rect : cellLayoutBoard.mWidgetsRects) {
72             // ignore rectangles overlapping with the area marked by x
73             if (overlapsWithIgnored(ignoredRectangles, rect.mBounds)) {
74                 continue;
75             }
76             if (!widgetsSet.contains(rect.mBounds)) {
77                 return -1;
78             }
79             widgetsSet.remove(rect.mBounds);
80         }
81         if (!widgetsSet.isEmpty()) {
82             return 1;
83         }
84 
85         // to be equal they need to have the same number of icons their order can be different
86         Set<Point> iconsSet = new HashSet<>();
87         mIconPoints.forEach(icon -> iconsSet.add(icon.getCoord()));
88         for (IconPoint icon : cellLayoutBoard.mIconPoints) {
89             if (!iconsSet.contains(icon.getCoord())) {
90                 return -1;
91             }
92             iconsSet.remove(icon.getCoord());
93         }
94         if (!iconsSet.isEmpty()) {
95             return 1;
96         }
97         return 0;
98     }
99 
100     public static class CellType {
101         // The cells marked by this will be filled by 1x1 widgets and will be ignored when
102         // validating
103         public static final char IGNORE = 'x';
104         // The cells marked by this will be filled by app icons
105         public static final char ICON = 'i';
106         // The cells marked by FOLDER will be filled by folders with 27 app icons inside
107         public static final char FOLDER = 'Z';
108         // Empty space
109         public static final char EMPTY = '-';
110         // Widget that will be saved as "main widget" for easier retrieval
111         public static final char MAIN_WIDGET = 'm';
112         // Everything else will be consider a widget
113     }
114 
115     public static class WidgetRect {
116         public char mType;
117         public Rect mBounds;
118 
WidgetRect(char type, Rect bounds)119         WidgetRect(char type, Rect bounds) {
120             this.mType = type;
121             this.mBounds = bounds;
122         }
123 
getSpanX()124         int getSpanX() {
125             return mBounds.right - mBounds.left + 1;
126         }
127 
getSpanY()128         int getSpanY() {
129             return mBounds.top - mBounds.bottom + 1;
130         }
131 
getCellX()132         int getCellX() {
133             return mBounds.left;
134         }
135 
getCellY()136         int getCellY() {
137             return mBounds.bottom;
138         }
139 
shouldIgnore()140         boolean shouldIgnore() {
141             return this.mType == CellType.IGNORE;
142         }
143 
contains(int x, int y)144         boolean contains(int x, int y) {
145             return mBounds.contains(x, y);
146         }
147 
148         @Override
toString()149         public String toString() {
150             return "WidgetRect type = " + mType + " x = " + getCellX() + " | y " + getCellY()
151                     + " xs = " + getSpanX() + " ys = " + getSpanY();
152         }
153     }
154 
155     public static class IconPoint {
156         public Point coord;
157         public char mType;
158 
IconPoint(Point coord, char type)159         public IconPoint(Point coord, char type) {
160             this.coord = coord;
161             mType = type;
162         }
163 
getType()164         public char getType() {
165             return mType;
166         }
167 
setType(char type)168         public void setType(char type) {
169             mType = type;
170         }
171 
getCoord()172         public Point getCoord() {
173             return coord;
174         }
175 
setCoord(Point coord)176         public void setCoord(Point coord) {
177             this.coord = coord;
178         }
179     }
180 
181     public static class FolderPoint {
182         public Point coord;
183         public char mType;
184 
FolderPoint(Point coord, char type)185         public FolderPoint(Point coord, char type) {
186             this.coord = coord;
187             mType = type;
188         }
189 
190         /**
191          * [A-Z]: Represents a folder and number of icons in the folder is represented by
192          * the order of letter in the alphabet, A=2, B=3, C=4 ... etc.
193          */
getNumberIconsInside()194         public int getNumberIconsInside() {
195             return (mType - 'A') + 2;
196         }
197     }
198 
199 
200     private HashSet<Character> mUsedWidgetTypes = new HashSet<>();
201 
202     static final int INFINITE = 99999;
203 
204     char[][] mWidget = new char[30][30];
205 
206     List<WidgetRect> mWidgetsRects = new ArrayList<>();
207     Map<Character, WidgetRect> mWidgetsMap = new HashMap<>();
208 
209     List<IconPoint> mIconPoints = new ArrayList<>();
210     List<FolderPoint> mFolderPoints = new ArrayList<>();
211 
212     WidgetRect mMain = null;
213 
214     int mWidth, mHeight;
215 
CellLayoutBoard()216     CellLayoutBoard() {
217         for (int x = 0; x < mWidget.length; x++) {
218             for (int y = 0; y < mWidget[0].length; y++) {
219                 mWidget[x][y] = CellType.EMPTY;
220             }
221         }
222     }
223 
CellLayoutBoard(int width, int height)224     CellLayoutBoard(int width, int height) {
225         mWidget = new char[width][height];
226         this.mWidth = width;
227         this.mHeight = height;
228         for (int x = 0; x < mWidget.length; x++) {
229             for (int y = 0; y < mWidget[0].length; y++) {
230                 mWidget[x][y] = CellType.EMPTY;
231             }
232         }
233     }
234 
pointInsideRect(int x, int y, WidgetRect rect)235     public boolean pointInsideRect(int x, int y, WidgetRect rect) {
236         Boolean isXInRect = x >= rect.getCellX() && x < rect.getCellX() + rect.getSpanX();
237         Boolean isYInRect = y >= rect.getCellY() && y < rect.getCellY() + rect.getSpanY();
238         return isXInRect && isYInRect;
239     }
240 
241     public WidgetRect getWidgetAt(int x, int y) {
242         return mWidgetsRects.stream()
243                 .filter(widgetRect -> pointInsideRect(x, y, widgetRect)).findFirst().orElse(null);
244     }
245 
getWidgets()246     public List<WidgetRect> getWidgets() {
247         return mWidgetsRects;
248     }
249 
getIcons()250     public List<IconPoint> getIcons() {
251         return mIconPoints;
252     }
253 
getFolders()254     public List<FolderPoint> getFolders() {
255         return mFolderPoints;
256     }
257 
getMain()258     public WidgetRect getMain() {
259         return mMain;
260     }
261 
getWidgetRect(char c)262     public WidgetRect getWidgetRect(char c) {
263         return mWidgetsMap.get(c);
264     }
265 
removeWidgetFromBoard(WidgetRect widget)266     private void removeWidgetFromBoard(WidgetRect widget) {
267         for (int xi = widget.mBounds.left; xi < widget.mBounds.right; xi++) {
268             for (int yi = widget.mBounds.top; yi < widget.mBounds.bottom; yi++) {
269                 mWidget[xi][yi] = '-';
270             }
271         }
272     }
273 
removeOverlappingItems(Rect rect)274     private void removeOverlappingItems(Rect rect) {
275         // Remove overlapping widgets and remove them from the board
276         mWidgetsRects = mWidgetsRects.stream().filter(widget -> {
277             if (rect.intersect(widget.mBounds)) {
278                 removeWidgetFromBoard(widget);
279                 return false;
280             }
281             return true;
282         }).collect(Collectors.toList());
283         // Remove overlapping icons and remove them from the board
284         mIconPoints = mIconPoints.stream().filter(iconPoint -> {
285             int x = iconPoint.coord.x;
286             int y = iconPoint.coord.y;
287             if (rect.contains(x, y)) {
288                 mWidget[x][y] = '-';
289                 return false;
290             }
291             return true;
292         }).collect(Collectors.toList());
293 
294         // Remove overlapping folders and remove them from the board
295         mFolderPoints = mFolderPoints.stream().filter(folderPoint -> {
296             int x = folderPoint.coord.x;
297             int y = folderPoint.coord.y;
298             if (rect.contains(x, y)) {
299                 mWidget[x][y] = '-';
300                 return false;
301             }
302             return true;
303         }).collect(Collectors.toList());
304     }
305 
removeOverlappingItems(Point p)306     private void removeOverlappingItems(Point p) {
307         // Remove overlapping widgets and remove them from the board
308         mWidgetsRects = mWidgetsRects.stream().filter(widget -> {
309             if (widget.mBounds.contains(p.x, p.y)) {
310                 removeWidgetFromBoard(widget);
311                 return false;
312             }
313             return true;
314         }).collect(Collectors.toList());
315         // Remove overlapping icons and remove them from the board
316         mIconPoints = mIconPoints.stream().filter(iconPoint -> {
317             int x = iconPoint.coord.x;
318             int y = iconPoint.coord.y;
319             if (p.x == x && p.y == y) {
320                 mWidget[x][y] = '-';
321                 return false;
322             }
323             return true;
324         }).collect(Collectors.toList());
325 
326         // Remove overlapping folders and remove them from the board
327         mFolderPoints = mFolderPoints.stream().filter(folderPoint -> {
328             int x = folderPoint.coord.x;
329             int y = folderPoint.coord.y;
330             if (p.x == x && p.y == y) {
331                 mWidget[x][y] = '-';
332                 return false;
333             }
334             return true;
335         }).collect(Collectors.toList());
336     }
337 
getNextWidgetType()338     private char getNextWidgetType() {
339         for (char type = 'a'; type <= 'z'; type++) {
340             if (type == 'i') continue;
341             if (mUsedWidgetTypes.contains(type)) continue;
342             mUsedWidgetTypes.add(type);
343             return type;
344         }
345         return 'z';
346     }
347 
addWidget(int x, int y, int spanX, int spanY, char type)348     public void addWidget(int x, int y, int spanX, int spanY, char type) {
349         Rect rect = new Rect(x, y + spanY - 1, x + spanX - 1, y);
350         removeOverlappingItems(rect);
351         WidgetRect widgetRect = new WidgetRect(type, rect);
352         mWidgetsRects.add(widgetRect);
353         for (int xi = rect.left; xi < rect.right + 1; xi++) {
354             for (int yi = rect.bottom; yi < rect.top + 1; yi++) {
355                 mWidget[xi][yi] = type;
356             }
357         }
358     }
359 
addWidget(int x, int y, int spanX, int spanY)360     public void addWidget(int x, int y, int spanX, int spanY) {
361         addWidget(x, y, spanX, spanY, getNextWidgetType());
362     }
363 
addIcon(int x, int y)364     public void addIcon(int x, int y) {
365         Point iconCoord = new Point(x, y);
366         removeOverlappingItems(iconCoord);
367         mIconPoints.add(new IconPoint(iconCoord, CellType.ICON));
368         mWidget[x][y] = 'i';
369     }
370 
getWidgetRect(int x, int y, Set<Point> used, char[][] board)371     public static WidgetRect getWidgetRect(int x, int y, Set<Point> used, char[][] board) {
372         char type = board[x][y];
373         Queue<Point> search = new ArrayDeque<Point>();
374         Point current = new Point(x, y);
375         search.add(current);
376         used.add(current);
377         List<Point> neighbors = new ArrayList<>(List.of(
378                 new Point(-1, 0),
379                 new Point(0, -1),
380                 new Point(1, 0),
381                 new Point(0, 1))
382         );
383         Rect widgetRect = new Rect(INFINITE, -INFINITE, -INFINITE, INFINITE);
384         while (!search.isEmpty()) {
385             current = search.poll();
386             widgetRect.top = Math.max(widgetRect.top, current.y);
387             widgetRect.right = Math.max(widgetRect.right, current.x);
388             widgetRect.bottom = Math.min(widgetRect.bottom, current.y);
389             widgetRect.left = Math.min(widgetRect.left, current.x);
390             for (Point p : neighbors) {
391                 Point next = new Point(current.x + p.x, current.y + p.y);
392                 if (next.x < 0 || next.x >= board.length) continue;
393                 if (next.y < 0 || next.y >= board[0].length) continue;
394                 if (board[next.x][next.y] == type && !used.contains(next)) {
395                     used.add(next);
396                     search.add(next);
397                 }
398             }
399         }
400         return new WidgetRect(type, widgetRect);
401     }
402 
isFolder(char type)403     public static boolean isFolder(char type) {
404         return type >= 'A' && type <= 'Z';
405     }
406 
isWidget(char type)407     public static boolean isWidget(char type) {
408         return type != CellType.ICON && type != CellType.EMPTY && (type >= 'a' && type <= 'z');
409     }
410 
isIcon(char type)411     public static boolean isIcon(char type) {
412         return type == CellType.ICON;
413     }
414 
getRects(char[][] board)415     private static List<WidgetRect> getRects(char[][] board) {
416         Set<Point> used = new HashSet<>();
417         List<WidgetRect> widgetsRects = new ArrayList<>();
418         for (int x = 0; x < board.length; x++) {
419             for (int y = 0; y < board[0].length; y++) {
420                 if (!used.contains(new Point(x, y)) && isWidget(board[x][y])) {
421                     widgetsRects.add(getWidgetRect(x, y, used, board));
422                 }
423             }
424         }
425         return widgetsRects;
426     }
427 
getIconPoints(char[][] board)428     private static List<IconPoint> getIconPoints(char[][] board) {
429         List<IconPoint> iconPoints = new ArrayList<>();
430         for (int x = 0; x < board.length; x++) {
431             for (int y = 0; y < board[0].length; y++) {
432                 if (isIcon(board[x][y])) {
433                     iconPoints.add(new IconPoint(new Point(x, y), board[x][y]));
434                 }
435             }
436         }
437         return iconPoints;
438     }
439 
getFolderPoints(char[][] board)440     private static List<FolderPoint> getFolderPoints(char[][] board) {
441         List<FolderPoint> folderPoints = new ArrayList<>();
442         for (int x = 0; x < board.length; x++) {
443             for (int y = 0; y < board[0].length; y++) {
444                 if (isFolder(board[x][y])) {
445                     folderPoints.add(new FolderPoint(new Point(x, y), board[x][y]));
446                 }
447             }
448         }
449         return folderPoints;
450     }
451 
getMainFromList(List<CellLayoutBoard> boards)452     public static WidgetRect getMainFromList(List<CellLayoutBoard> boards) {
453         for (CellLayoutBoard board : boards) {
454             WidgetRect main = board.getMain();
455             if (main != null) {
456                 return main;
457             }
458         }
459         return null;
460     }
461 
getWidgetIn(List<CellLayoutBoard> boards, int x, int y)462     public static WidgetRect getWidgetIn(List<CellLayoutBoard> boards, int x, int y) {
463         for (CellLayoutBoard board : boards) {
464             WidgetRect main = board.getWidgetAt(x, y);
465             if (main != null) {
466                 return main;
467             }
468             x -= board.mWidth;
469         }
470         return null;
471     }
472 
boardFromString(String boardStr)473     public static CellLayoutBoard boardFromString(String boardStr) {
474         String[] lines = boardStr.split("\n");
475         CellLayoutBoard board = new CellLayoutBoard();
476 
477         for (int y = 0; y < lines.length; y++) {
478             String line = lines[y];
479             for (int x = 0; x < line.length(); x++) {
480                 char c = line.charAt(x);
481                 if (c != CellType.EMPTY) {
482                     board.mWidget[x][y] = line.charAt(x);
483                 }
484             }
485         }
486         board.mHeight = lines.length;
487         board.mWidth = lines[0].length();
488         board.mWidgetsRects = getRects(board.mWidget);
489         board.mWidgetsRects.forEach(widgetRect -> {
490             if (widgetRect.mType == CellType.MAIN_WIDGET) {
491                 board.mMain = widgetRect;
492             }
493             board.mWidgetsMap.put(widgetRect.mType, widgetRect);
494         });
495         board.mIconPoints = getIconPoints(board.mWidget);
496         board.mFolderPoints = getFolderPoints(board.mWidget);
497         return board;
498     }
499 
toString(int maxX, int maxY)500     public String toString(int maxX, int maxY) {
501         StringBuilder s = new StringBuilder();
502         s.append("board: ");
503         s.append(maxX);
504         s.append("x");
505         s.append(maxY);
506         s.append("\n");
507         maxX = Math.min(maxX, mWidget.length);
508         maxY = Math.min(maxY, mWidget[0].length);
509         for (int y = 0; y < maxY; y++) {
510             for (int x = 0; x < maxX; x++) {
511                 s.append(mWidget[x][y]);
512             }
513             s.append('\n');
514         }
515         return s.toString();
516     }
517 
518     @Override
toString()519     public String toString() {
520         return toString(mWidth, mHeight);
521     }
522 
boardListFromString(String boardsStr)523     public static List<CellLayoutBoard> boardListFromString(String boardsStr) {
524         String[] lines = boardsStr.split("\n");
525         ArrayList<String> individualBoards = new ArrayList<>();
526         ArrayList<CellLayoutBoard> boards = new ArrayList<>();
527         for (String line : lines) {
528             String[] boardSegment = line.split("\\|");
529             for (int i = 0; i < boardSegment.length; i++) {
530                 if (i >= individualBoards.size()) {
531                     individualBoards.add(boardSegment[i]);
532                 } else {
533                     individualBoards.set(i, individualBoards.get(i) + "\n" + boardSegment[i]);
534                 }
535             }
536         }
537         for (String board : individualBoards) {
538             boards.add(CellLayoutBoard.boardFromString(board));
539         }
540         return boards;
541     }
542 
getWidth()543     public int getWidth() {
544         return mWidth;
545     }
546 
getHeight()547     public int getHeight() {
548         return mHeight;
549     }
550 }
551