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.launcher3.taskbar; 17 18 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; 19 20 import android.animation.Animator; 21 import android.view.KeyEvent; 22 import android.view.View; 23 24 import androidx.annotation.NonNull; 25 import androidx.annotation.Nullable; 26 27 import com.android.launcher3.anim.AnimationSuccessListener; 28 import com.android.launcher3.taskbar.overlay.TaskbarOverlayContext; 29 import com.android.launcher3.taskbar.overlay.TaskbarOverlayDragLayer; 30 import com.android.quickstep.SystemUiProxy; 31 import com.android.quickstep.util.GroupTask; 32 import com.android.systemui.shared.recents.model.Task; 33 import com.android.systemui.shared.recents.model.ThumbnailData; 34 import com.android.systemui.shared.system.ActivityManagerWrapper; 35 36 import java.io.PrintWriter; 37 import java.util.List; 38 import java.util.function.Consumer; 39 40 /** 41 * Handles initialization of the {@link KeyboardQuickSwitchView} and supplies it with the list of 42 * tasks. 43 */ 44 public class KeyboardQuickSwitchViewController { 45 46 @NonNull private final ViewCallbacks mViewCallbacks = new ViewCallbacks(); 47 @NonNull private final TaskbarControllers mControllers; 48 @NonNull private final TaskbarOverlayContext mOverlayContext; 49 @NonNull private final KeyboardQuickSwitchView mKeyboardQuickSwitchView; 50 @NonNull private final KeyboardQuickSwitchController.ControllerCallbacks mControllerCallbacks; 51 52 @Nullable private Animator mCloseAnimation; 53 54 private int mCurrentFocusIndex = -1; 55 56 private boolean mOnDesktop; 57 KeyboardQuickSwitchViewController( @onNull TaskbarControllers controllers, @NonNull TaskbarOverlayContext overlayContext, @NonNull KeyboardQuickSwitchView keyboardQuickSwitchView, @NonNull KeyboardQuickSwitchController.ControllerCallbacks controllerCallbacks)58 protected KeyboardQuickSwitchViewController( 59 @NonNull TaskbarControllers controllers, 60 @NonNull TaskbarOverlayContext overlayContext, 61 @NonNull KeyboardQuickSwitchView keyboardQuickSwitchView, 62 @NonNull KeyboardQuickSwitchController.ControllerCallbacks controllerCallbacks) { 63 mControllers = controllers; 64 mOverlayContext = overlayContext; 65 mKeyboardQuickSwitchView = keyboardQuickSwitchView; 66 mControllerCallbacks = controllerCallbacks; 67 } 68 getCurrentFocusedIndex()69 protected int getCurrentFocusedIndex() { 70 return mCurrentFocusIndex; 71 } 72 openQuickSwitchView( @onNull List<GroupTask> tasks, int numHiddenTasks, boolean updateTasks, int currentFocusIndexOverride, boolean onDesktop)73 protected void openQuickSwitchView( 74 @NonNull List<GroupTask> tasks, 75 int numHiddenTasks, 76 boolean updateTasks, 77 int currentFocusIndexOverride, 78 boolean onDesktop) { 79 TaskbarOverlayDragLayer dragLayer = mOverlayContext.getDragLayer(); 80 dragLayer.addView(mKeyboardQuickSwitchView); 81 dragLayer.runOnClickOnce(v -> closeQuickSwitchView(true)); 82 mOnDesktop = onDesktop; 83 84 mKeyboardQuickSwitchView.applyLoadPlan( 85 mOverlayContext, 86 tasks, 87 numHiddenTasks, 88 updateTasks, 89 currentFocusIndexOverride, 90 mViewCallbacks); 91 } 92 closeQuickSwitchView(boolean animate)93 protected void closeQuickSwitchView(boolean animate) { 94 if (mCloseAnimation != null) { 95 if (animate) { 96 // Let currently-running animation finish. 97 return; 98 } else { 99 mCloseAnimation.cancel(); 100 } 101 } 102 if (!animate) { 103 mCloseAnimation = null; 104 onCloseComplete(); 105 return; 106 } 107 mCloseAnimation = mKeyboardQuickSwitchView.getCloseAnimation(); 108 109 mCloseAnimation.addListener(new AnimationSuccessListener() { 110 @Override 111 public void onAnimationSuccess(Animator animator) { 112 mCloseAnimation = null; 113 onCloseComplete(); 114 } 115 }); 116 mCloseAnimation.start(); 117 } 118 119 /** 120 * Launched the currently-focused task. 121 * 122 * Returns index -1 iff the RecentsView shouldn't be opened. 123 * 124 * If the index is not -1, then the {@link com.android.quickstep.views.TaskView} at the returned 125 * index will be focused. 126 */ launchFocusedTask()127 protected int launchFocusedTask() { 128 // Launch the second-most recent task if the user quick switches too quickly, if possible. 129 return launchTaskAt(mCurrentFocusIndex == -1 130 ? (mControllerCallbacks.getTaskCount() > 1 ? 1 : 0) : mCurrentFocusIndex); 131 } 132 launchTaskAt(int index)133 private int launchTaskAt(int index) { 134 if (mCloseAnimation != null) { 135 // Ignore taps on task views and alt key unpresses while the close animation is running. 136 return -1; 137 } 138 // Even with a valid index, this can be null if the user tries to quick switch before the 139 // views have been added in the KeyboardQuickSwitchView. 140 View taskView = mKeyboardQuickSwitchView.getTaskAt(index); 141 GroupTask task = mControllerCallbacks.getTaskAt(index); 142 if (task == null) { 143 return Math.max(0, index); 144 } else if (mOnDesktop) { 145 UI_HELPER_EXECUTOR.execute(() -> 146 SystemUiProxy.INSTANCE.get(mKeyboardQuickSwitchView.getContext()) 147 .showDesktopApp(task.task1.key.id)); 148 } else if (task.task2 == null) { 149 UI_HELPER_EXECUTOR.execute(() -> 150 ActivityManagerWrapper.getInstance().startActivityFromRecents( 151 task.task1.key, 152 mControllers.taskbarActivityContext.getActivityLaunchOptions( 153 taskView == null ? mKeyboardQuickSwitchView : taskView, null) 154 .options)); 155 } else { 156 mControllers.uiController.launchSplitTasks( 157 taskView == null ? mKeyboardQuickSwitchView : taskView, task); 158 } 159 return -1; 160 } 161 onCloseComplete()162 private void onCloseComplete() { 163 mOverlayContext.getDragLayer().removeView(mKeyboardQuickSwitchView); 164 mControllerCallbacks.onCloseComplete(); 165 } 166 onDestroy()167 protected void onDestroy() { 168 closeQuickSwitchView(false); 169 } 170 dumpLogs(String prefix, PrintWriter pw)171 public void dumpLogs(String prefix, PrintWriter pw) { 172 pw.println(prefix + "KeyboardQuickSwitchViewController:"); 173 174 pw.println(prefix + "\thasFocus=" + mKeyboardQuickSwitchView.hasFocus()); 175 pw.println(prefix + "\tcloseAnimationRunning=" + (mCloseAnimation != null)); 176 pw.println(prefix + "\tmCurrentFocusIndex=" + mCurrentFocusIndex); 177 } 178 179 class ViewCallbacks { 180 onKeyUp(int keyCode, KeyEvent event, boolean isRTL, boolean allowTraversal)181 boolean onKeyUp(int keyCode, KeyEvent event, boolean isRTL, boolean allowTraversal) { 182 if (keyCode != KeyEvent.KEYCODE_TAB 183 && keyCode != KeyEvent.KEYCODE_DPAD_RIGHT 184 && keyCode != KeyEvent.KEYCODE_DPAD_LEFT 185 && keyCode != KeyEvent.KEYCODE_GRAVE 186 && keyCode != KeyEvent.KEYCODE_ESCAPE) { 187 return false; 188 } 189 if (keyCode == KeyEvent.KEYCODE_GRAVE || keyCode == KeyEvent.KEYCODE_ESCAPE) { 190 closeQuickSwitchView(true); 191 return true; 192 } 193 if (!allowTraversal) { 194 return false; 195 } 196 boolean traverseBackwards = (keyCode == KeyEvent.KEYCODE_TAB && event.isShiftPressed()) 197 || (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT && isRTL) 198 || (keyCode == KeyEvent.KEYCODE_DPAD_LEFT && !isRTL); 199 int taskCount = mControllerCallbacks.getTaskCount(); 200 int toIndex = mCurrentFocusIndex == -1 201 // Focus the second-most recent app if possible 202 ? (taskCount > 1 ? 1 : 0) 203 : (traverseBackwards 204 // focus a more recent task or loop back to the opposite end 205 ? Math.max(0, mCurrentFocusIndex == 0 206 ? taskCount - 1 : mCurrentFocusIndex - 1) 207 // focus a less recent app or loop back to the opposite end 208 : ((mCurrentFocusIndex + 1) % taskCount)); 209 210 if (mCurrentFocusIndex == toIndex) { 211 return true; 212 } 213 mKeyboardQuickSwitchView.animateFocusMove(mCurrentFocusIndex, toIndex); 214 215 return true; 216 } 217 updateCurrentFocusIndex(int index)218 void updateCurrentFocusIndex(int index) { 219 mCurrentFocusIndex = index; 220 } 221 launchTappedTask(int index)222 void launchTappedTask(int index) { 223 KeyboardQuickSwitchViewController.this.launchTaskAt(index); 224 closeQuickSwitchView(true); 225 } 226 updateThumbnailInBackground(Task task, Consumer<ThumbnailData> callback)227 void updateThumbnailInBackground(Task task, Consumer<ThumbnailData> callback) { 228 mControllerCallbacks.updateThumbnailInBackground(task, callback); 229 } 230 updateIconInBackground(Task task, Consumer<Task> callback)231 void updateIconInBackground(Task task, Consumer<Task> callback) { 232 mControllerCallbacks.updateIconInBackground(task, callback); 233 } 234 } 235 } 236