• 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.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