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 package com.android.quickstep.util;
17 
18 import static java.lang.annotation.RetentionPolicy.SOURCE;
19 
20 import androidx.annotation.IntDef;
21 
22 import com.android.launcher3.util.IntArray;
23 
24 import java.lang.annotation.Retention;
25 
26 /**
27  * Helper class for navigating RecentsView grid tasks via arrow keys and tab.
28  */
29 public class TaskGridNavHelper {
30     public static final int CLEAR_ALL_PLACEHOLDER_ID = -1;
31     public static final int INVALID_FOCUSED_TASK_ID = -1;
32 
33     public static final int DIRECTION_UP = 0;
34     public static final int DIRECTION_DOWN = 1;
35     public static final int DIRECTION_LEFT = 2;
36     public static final int DIRECTION_RIGHT = 3;
37     public static final int DIRECTION_TAB = 4;
38 
39     @Retention(SOURCE)
40     @IntDef({DIRECTION_UP, DIRECTION_DOWN, DIRECTION_LEFT, DIRECTION_RIGHT, DIRECTION_TAB})
41     public @interface TASK_NAV_DIRECTION {}
42 
43     private final IntArray mOriginalTopRowIds;
44     private IntArray mTopRowIds;
45     private IntArray mBottomRowIds;
46     private final int mFocusedTaskId;
47 
TaskGridNavHelper(IntArray topIds, IntArray bottomIds, int focusedTaskId)48     public TaskGridNavHelper(IntArray topIds, IntArray bottomIds, int focusedTaskId) {
49         mFocusedTaskId = focusedTaskId;
50         mOriginalTopRowIds = topIds.clone();
51         generateTaskViewIdGrid(topIds, bottomIds);
52     }
53 
generateTaskViewIdGrid(IntArray topRowIdArray, IntArray bottomRowIdArray)54     private void generateTaskViewIdGrid(IntArray topRowIdArray, IntArray bottomRowIdArray) {
55         boolean hasFocusedTask = mFocusedTaskId != INVALID_FOCUSED_TASK_ID;
56         int maxSize =
57                 Math.max(topRowIdArray.size(), bottomRowIdArray.size()) + (hasFocusedTask ? 1 : 0);
58         int minSize =
59                 Math.min(topRowIdArray.size(), bottomRowIdArray.size()) + (hasFocusedTask ? 1 : 0);
60 
61         // Add the focused task to the beginning of both arrays if it exists.
62         if (hasFocusedTask) {
63             topRowIdArray.add(0, mFocusedTaskId);
64             bottomRowIdArray.add(0, mFocusedTaskId);
65         }
66 
67         // Fill in the shorter array with the ids from the longer one.
68         for (int i = minSize; i < maxSize; i++) {
69             if (i >= topRowIdArray.size()) {
70                 topRowIdArray.add(bottomRowIdArray.get(i));
71             } else {
72                 bottomRowIdArray.add(topRowIdArray.get(i));
73             }
74         }
75 
76         // Add the clear all button to the end of both arrays
77         topRowIdArray.add(CLEAR_ALL_PLACEHOLDER_ID);
78         bottomRowIdArray.add(CLEAR_ALL_PLACEHOLDER_ID);
79 
80         mTopRowIds = topRowIdArray;
81         mBottomRowIds = bottomRowIdArray;
82     }
83 
84     /**
85      * Returns the id of the next page in the grid or -1 for the clear all button.
86      */
getNextGridPage(int currentPageTaskViewId, int delta, @TASK_NAV_DIRECTION int direction, boolean cycle)87     public int getNextGridPage(int currentPageTaskViewId, int delta,
88             @TASK_NAV_DIRECTION int direction, boolean cycle) {
89         boolean inTop = mTopRowIds.contains(currentPageTaskViewId);
90         int index = inTop ? mTopRowIds.indexOf(currentPageTaskViewId)
91                 : mBottomRowIds.indexOf(currentPageTaskViewId);
92         int maxSize = Math.max(mTopRowIds.size(), mBottomRowIds.size());
93         int nextIndex = index + delta;
94 
95         switch (direction) {
96             case DIRECTION_UP:
97             case DIRECTION_DOWN: {
98                 return inTop ? mBottomRowIds.get(index) : mTopRowIds.get(index);
99             }
100             case DIRECTION_LEFT: {
101                 int boundedIndex = cycle ? nextIndex % maxSize : Math.min(nextIndex, maxSize - 1);
102                 return inTop ? mTopRowIds.get(boundedIndex)
103                         : mBottomRowIds.get(boundedIndex);
104             }
105             case DIRECTION_RIGHT: {
106                 int boundedIndex =
107                         cycle ? (nextIndex < 0 ? maxSize - 1 : nextIndex) : Math.max(
108                                 nextIndex, 0);
109                 boolean inOriginalTop = mOriginalTopRowIds.contains(currentPageTaskViewId);
110                 return inOriginalTop ? mTopRowIds.get(boundedIndex)
111                         : mBottomRowIds.get(boundedIndex);
112             }
113             case DIRECTION_TAB: {
114                 int boundedIndex =
115                         cycle ? nextIndex < 0 ? maxSize - 1 : nextIndex % maxSize : Math.min(
116                                 nextIndex, maxSize - 1);
117                 if (delta >= 0) {
118                     return inTop && mTopRowIds.get(index) != mBottomRowIds.get(index)
119                             ? mBottomRowIds.get(index)
120                             : mTopRowIds.get(boundedIndex);
121                 } else {
122                     if (mTopRowIds.contains(currentPageTaskViewId)) {
123                         return mBottomRowIds.get(boundedIndex);
124                     } else {
125                         // Go up to top if there is task above
126                         return mTopRowIds.get(index) != mBottomRowIds.get(index)
127                                 ? mTopRowIds.get(index)
128                                 : mBottomRowIds.get(boundedIndex);
129                     }
130                 }
131             }
132             default:
133                 return currentPageTaskViewId;
134         }
135     }
136 }
137