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