1 /*
2  * Copyright (C) 2019 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 
17 package com.android.launcher3.folder;
18 
19 import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.MAX_NUM_ITEMS_IN_PREVIEW;
20 
21 import android.graphics.Point;
22 
23 import com.android.launcher3.DeviceProfile;
24 import com.android.launcher3.model.data.FolderInfo;
25 import com.android.launcher3.model.data.ItemInfo;
26 
27 import java.util.ArrayList;
28 import java.util.List;
29 
30 /**
31  * Utility class for managing item positions in a folder based on rank
32  */
33 public class FolderGridOrganizer {
34 
35     private final Point mPoint = new Point();
36     private final int mMaxCountX;
37     private final int mMaxCountY;
38     private final int mMaxItemsPerPage;
39 
40     private int mNumItemsInFolder;
41     private int mCountX;
42     private int mCountY;
43     private boolean mDisplayingUpperLeftQuadrant = false;
44     private static final int PREVIEW_MAX_ROWS = 2;
45     private static final int PREVIEW_MAX_COLUMNS = 2;
46 
47     /**
48      * Note: must call {@link #setFolderInfo(FolderInfo)} manually for verifier to work.
49      */
FolderGridOrganizer(DeviceProfile profile)50     public FolderGridOrganizer(DeviceProfile profile) {
51         mMaxCountX = profile.numFolderColumns;
52         mMaxCountY = profile.numFolderRows;
53         mMaxItemsPerPage = mMaxCountX * mMaxCountY;
54     }
55 
56     /**
57      * Updates the organizer with the provided folder info
58      */
setFolderInfo(FolderInfo info)59     public FolderGridOrganizer setFolderInfo(FolderInfo info) {
60         return setContentSize(info.getContents().size());
61     }
62 
63     /**
64      * Updates the organizer to reflect the content size
65      */
setContentSize(int contentSize)66     public FolderGridOrganizer setContentSize(int contentSize) {
67         if (contentSize != mNumItemsInFolder) {
68             calculateGridSize(contentSize);
69 
70             mDisplayingUpperLeftQuadrant = contentSize > MAX_NUM_ITEMS_IN_PREVIEW;
71             mNumItemsInFolder = contentSize;
72         }
73         return this;
74     }
75 
getCountX()76     public int getCountX() {
77         return mCountX;
78     }
79 
getCountY()80     public int getCountY() {
81         return mCountY;
82     }
83 
getMaxItemsPerPage()84     public int getMaxItemsPerPage() {
85         return mMaxItemsPerPage;
86     }
87 
88     /**
89      * Calculates the grid size such that {@param count} items can fit in the grid.
90      * The grid size is calculated such that countY <= countX and countX = ceil(sqrt(count)) while
91      * maintaining the restrictions of {@link #mMaxCountX} &amp; {@link #mMaxCountY}.
92      */
calculateGridSize(int count)93     private void calculateGridSize(int count) {
94         boolean done;
95         int gridCountX = mCountX;
96         int gridCountY = mCountY;
97 
98         if (count >= mMaxItemsPerPage) {
99             gridCountX = mMaxCountX;
100             gridCountY = mMaxCountY;
101             done = true;
102         } else {
103             done = false;
104         }
105 
106         while (!done) {
107             int oldCountX = gridCountX;
108             int oldCountY = gridCountY;
109             if (gridCountX * gridCountY < count) {
110                 // Current grid is too small, expand it
111                 if ((gridCountX <= gridCountY || gridCountY == mMaxCountY)
112                         && gridCountX < mMaxCountX) {
113                     gridCountX++;
114                 } else if (gridCountY < mMaxCountY) {
115                     gridCountY++;
116                 }
117                 if (gridCountY == 0) gridCountY++;
118             } else if ((gridCountY - 1) * gridCountX >= count && gridCountY >= gridCountX) {
119                 gridCountY = Math.max(0, gridCountY - 1);
120             } else if ((gridCountX - 1) * gridCountY >= count) {
121                 gridCountX = Math.max(0, gridCountX - 1);
122             }
123             done = gridCountX == oldCountX && gridCountY == oldCountY;
124         }
125 
126         mCountX = gridCountX;
127         mCountY = gridCountY;
128     }
129 
130     /**
131      * Updates the item's cellX, cellY and rank corresponding to the provided rank.
132      *
133      * @return true if there was any change
134      */
updateRankAndPos(ItemInfo item, int rank)135     public boolean updateRankAndPos(ItemInfo item, int rank) {
136         Point pos = getPosForRank(rank);
137         if (!pos.equals(item.cellX, item.cellY) || rank != item.rank) {
138             item.rank = rank;
139             item.cellX = pos.x;
140             item.cellY = pos.y;
141             return true;
142         }
143         return false;
144     }
145 
146     /**
147      * Returns the position of the item in the grid
148      */
getPosForRank(int rank)149     public Point getPosForRank(int rank) {
150         int pagePos = rank % mMaxItemsPerPage;
151         mPoint.x = pagePos % mCountX;
152         mPoint.y = pagePos / mCountX;
153         return mPoint;
154     }
155 
156     /**
157      * Returns the preview items for the provided pageNo using the full list of contents
158      */
previewItemsForPage(int page, List<T> contents)159     public <T, R extends T> ArrayList<R> previewItemsForPage(int page, List<T> contents) {
160         ArrayList<R> result = new ArrayList<>();
161         int itemsPerPage = mCountX * mCountY;
162         int start = itemsPerPage * page;
163         int end = Math.min(start + itemsPerPage, contents.size());
164 
165         for (int i = start, rank = 0; i < end; i++, rank++) {
166             if (isItemInPreview(page, rank)) {
167                 result.add((R) contents.get(i));
168             }
169 
170             if (result.size() == MAX_NUM_ITEMS_IN_PREVIEW) {
171                 break;
172             }
173         }
174         return result;
175     }
176 
177     /**
178      * Returns whether the item with rank is in the default Folder icon preview.
179      */
isItemInPreview(int rank)180     public boolean isItemInPreview(int rank) {
181         return isItemInPreview(0, rank);
182     }
183 
184     /**
185      * @param page The page the item is on.
186      * @param rank The rank of the item.
187      * @return True iff the icon is in the 2x2 upper left quadrant of the Folder.
188      */
isItemInPreview(int page, int rank)189     public boolean isItemInPreview(int page, int rank) {
190         // First page items are laid out such that the first 4 items are always in the upper
191         // left quadrant. For all other pages, we need to check the row and col.
192         if (page > 0 || mDisplayingUpperLeftQuadrant) {
193             int col = rank % mCountX;
194             int row = rank / mCountX;
195             return col < PREVIEW_MAX_COLUMNS && row < PREVIEW_MAX_ROWS;
196         }
197         return rank < MAX_NUM_ITEMS_IN_PREVIEW;
198     }
199 }
200