1 /*
2  * Copyright (C) 2023 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.car.carlauncher.pagination;
18 
19 import static com.android.car.carlauncher.AppGridConstants.AppItemBoundDirection;
20 import static com.android.car.carlauncher.AppGridConstants.PageOrientation;
21 import static com.android.car.carlauncher.AppGridConstants.isHorizontal;
22 
23 import android.view.View;
24 
25 /**
26  * Helper class that handles all page rounding logic.
27  */
28 public class PageIndexingHelper {
29     private final int mNumOfCols;
30     private final int mNumOfRows;
31     @PageOrientation
32     private final int mPageOrientation;
33 
34     private int mLayoutDirection;
35 
PageIndexingHelper(int numOfCols, int numOfRows, @PageOrientation int orientation)36     public PageIndexingHelper(int numOfCols, int numOfRows, @PageOrientation int orientation) {
37         mNumOfCols = numOfCols;
38         mNumOfRows = numOfRows;
39         mPageOrientation = orientation;
40     }
41 
42     /**
43      * Layout direction needs to be reset onResume as to not crash when user switches to another
44      * language with different layout direction.
45      */
setLayoutDirection(int layoutDirection)46     public void setLayoutDirection(int layoutDirection) {
47         mLayoutDirection = layoutDirection;
48     }
49 
50     /**
51      * Returns the direction of the offset to add to the app item at the given grid position.
52      *
53      * For example, when there are 5 app items per column when using horizontal paging, the
54      * 1st column app item should have padding to the left and 4th column app item should have
55      * padding to the right.
56      */
57     @AppItemBoundDirection
getOffsetBoundDirection(int gridPosition)58     public int getOffsetBoundDirection(int gridPosition) {
59         // TODO (b/271628061): rename gridPosition and adapterIndex
60         if (isHorizontal(mPageOrientation)) {
61             int cid = (gridPosition / mNumOfRows) % mNumOfCols;
62             if (cid == 0) {
63                 return AppItemBoundDirection.LEFT;
64             } else if (cid == mNumOfCols - 1) {
65                 return AppItemBoundDirection.RIGHT;
66             }
67         } else {
68             int rid = (gridPosition / mNumOfCols) % mNumOfRows;
69             if (rid == 0) {
70                 return AppItemBoundDirection.TOP;
71             } else if (rid == mNumOfRows - 1) {
72                 return AppItemBoundDirection.BOTTOM;
73             }
74         }
75         return AppItemBoundDirection.NONE;
76     }
77 
78     /**
79      * Grid position refers to the default position in a RecyclerView used to draw out a horizontal
80      * grid layout, shown below.
81      *
82      * This is the value returned by the default ViewHolder.getAbsoluteAdapterPosition().
83      */
gridPositionToAdaptorIndex(int position)84     public int gridPositionToAdaptorIndex(int position) {
85         if (!isHorizontal(mPageOrientation)) {
86             if (mLayoutDirection == View.LAYOUT_DIRECTION_RTL) {
87                 int cid = position % mNumOfCols;
88                 // column order swap
89                 position = (position - cid) + (mNumOfCols - cid - 1);
90             }
91             return position;
92         }
93         int positionOnPage = position % (mNumOfCols * mNumOfRows);
94         // page the item resides on
95         int pid = position / (mNumOfCols * mNumOfRows);
96         // row of the item, in matrix order
97         int rid = positionOnPage % mNumOfRows;
98         // column of the item, in matrix order / LTR order
99         int cid = positionOnPage / mNumOfRows;
100 
101         if (mLayoutDirection == View.LAYOUT_DIRECTION_RTL) {
102             cid = mNumOfCols - cid - 1;
103         }
104         return (pid * mNumOfRows * mNumOfCols) + (rid * mNumOfCols) + cid;
105     }
106 
107     /**
108      * Adapter index refers to the "business logic" index, which is the order which the users will
109      * read the app in their language (either LTR or RTL on each page)
110      */
adaptorIndexToGridPosition(int index)111     public int adaptorIndexToGridPosition(int index) {
112         if (!isHorizontal(mPageOrientation)) {
113             if (mLayoutDirection == View.LAYOUT_DIRECTION_RTL) {
114                 int cid = index % mNumOfCols;
115                 // column order swap
116                 index = (index - cid) + (mNumOfCols - cid - 1);
117             }
118             return index;
119         }
120         int indexOnPage = index % (mNumOfCols * mNumOfRows);
121         // page the item resides on
122         int pid = index / (mNumOfCols * mNumOfRows);
123         // row of the item, in matrix order
124         int rid = indexOnPage / mNumOfCols;
125         // column of the item, in matrix order / LTR order
126         int cid = indexOnPage % mNumOfCols;
127 
128         if (mLayoutDirection == View.LAYOUT_DIRECTION_RTL) {
129             cid =  mNumOfCols - cid - 1;
130         }
131         return (pid * mNumOfRows * mNumOfCols) + (cid * mNumOfRows) + rid;
132     }
133 
134     /**
135      * Returns the grid position of the FIRST item on the page. The result is always the same in RTL
136      * and LTR since the first and last index of every page is always mirrored.
137      *
138      *          * Grid Position example - Horizontal
139      *          *  |[0] 3  6  9  12 |[15] 18 21 24 27 |
140      *          *  | 1  4  7  10 13 | 16  19 22 25 28 |
141      *          *  | 2  5  8  11 14 | 17  20 23 26 29 |
142      *
143      *          * Grid Position example - Vertical
144      *          *  |[1]  2   3   4   5  |
145      *          *  | 6   7   8   9   10 |
146      *          *  | 11  12  13  14  15 |
147      *          *  __________________
148      *          *  |[16] 17  18  19  20 |
149      *          *  | 21  22  23  24  25 |
150      *          *  | 26  27  28  29  30 |
151      */
roundToFirstIndexOnPage(int gridPosition)152     public int roundToFirstIndexOnPage(int gridPosition) {
153         int pidRoundedDown = gridPosition / (mNumOfCols * mNumOfRows);
154         return pidRoundedDown * (mNumOfCols * mNumOfRows);
155     }
156 
157     /**
158      * Returns the grid position of the LAST item on the page. The result is always the same in RTL
159      * and LTR since the first and last index of every page is always mirrored.
160      *
161      *          * Grid position example - horizontal
162      *          *  | 0  3  6  9   12  | 15  18  21  24  27  |
163      *          *  | 1  4  7  10  13  | 16  19  22  25  28  |
164      *          *  | 2  5  8  11 [14] | 17  20  23  26 [29] |
165      *
166      *          * Grid position example - Vertical
167      *          *  | 1   2   3   4   5   |
168      *          *  | 6   7   8   9   10  |
169      *          *  | 11  12  13  14 [15] |
170      *          *
171      *          *  | 16  17  18  19  20  |
172      *          *  | 21  22  23  24  25  |
173      *          *  | 26  27  28  29 [30] |
174      */
roundToLastIndexOnPage(int gridPosition)175     public int roundToLastIndexOnPage(int gridPosition) {
176         int pidRoundedUp = gridPosition / (mNumOfCols * mNumOfRows) + 1;
177         return pidRoundedUp * (mNumOfCols * mNumOfRows) - 1;
178     }
179 }
180