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