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