• 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 android.content.ComponentName;
19 import android.content.pm.ActivityInfo;
20 
21 import androidx.annotation.NonNull;
22 import androidx.annotation.Nullable;
23 
24 import com.android.launcher3.R;
25 import com.android.launcher3.statehandlers.DesktopVisibilityController;
26 import com.android.launcher3.taskbar.overlay.TaskbarOverlayContext;
27 import com.android.quickstep.LauncherActivityInterface;
28 import com.android.quickstep.RecentsModel;
29 import com.android.quickstep.util.DesktopTask;
30 import com.android.quickstep.util.GroupTask;
31 import com.android.quickstep.views.DesktopTaskView;
32 import com.android.systemui.shared.recents.model.Task;
33 import com.android.systemui.shared.recents.model.ThumbnailData;
34 
35 import java.io.PrintWriter;
36 import java.util.ArrayList;
37 import java.util.Collections;
38 import java.util.List;
39 import java.util.function.Consumer;
40 import java.util.stream.Collectors;
41 
42 /**
43  * Handles initialization of the {@link KeyboardQuickSwitchViewController}.
44  */
45 public final class KeyboardQuickSwitchController implements
46         TaskbarControllers.LoggableTaskbarController {
47 
48     static final int MAX_TASKS = 6;
49 
50     @NonNull private final ControllerCallbacks mControllerCallbacks = new ControllerCallbacks();
51 
52     // Initialized on init
53     @Nullable private RecentsModel mModel;
54 
55     // Used to keep track of the last requested task list id, so that we do not request to load the
56     // tasks again if we have already requested it and the task list has not changed
57     private int mTaskListChangeId = -1;
58     // Only empty before the recent tasks list has been loaded the first time
59     @NonNull private List<GroupTask> mTasks = new ArrayList<>();
60     private int mNumHiddenTasks = 0;
61 
62     // Initialized in init
63     private TaskbarControllers mControllers;
64 
65     @Nullable private KeyboardQuickSwitchViewController mQuickSwitchViewController;
66 
67     /** Initialize the controller. */
init(@onNull TaskbarControllers controllers)68     public void init(@NonNull TaskbarControllers controllers) {
69         mControllers = controllers;
70         mModel = RecentsModel.INSTANCE.get(controllers.taskbarActivityContext);
71     }
72 
onConfigurationChanged(@ctivityInfo.Config int configChanges)73     void onConfigurationChanged(@ActivityInfo.Config int configChanges) {
74         if (mQuickSwitchViewController == null) {
75             return;
76         }
77         if ((configChanges & (ActivityInfo.CONFIG_KEYBOARD
78                 | ActivityInfo.CONFIG_KEYBOARD_HIDDEN)) != 0) {
79             mQuickSwitchViewController.closeQuickSwitchView(true);
80             return;
81         }
82         int currentFocusedIndex = mQuickSwitchViewController.getCurrentFocusedIndex();
83         onDestroy();
84         if (currentFocusedIndex != -1) {
85             mControllers.taskbarActivityContext.getMainThreadHandler().post(
86                     () -> openQuickSwitchView(currentFocusedIndex));
87         }
88     }
89 
openQuickSwitchView()90     void openQuickSwitchView() {
91         openQuickSwitchView(-1);
92     }
93 
openQuickSwitchView(int currentFocusedIndex)94     private void openQuickSwitchView(int currentFocusedIndex) {
95         if (mQuickSwitchViewController != null) {
96             return;
97         }
98         TaskbarOverlayContext overlayContext =
99                 mControllers.taskbarOverlayController.requestWindow();
100         KeyboardQuickSwitchView keyboardQuickSwitchView =
101                 (KeyboardQuickSwitchView) overlayContext.getLayoutInflater()
102                         .inflate(
103                                 R.layout.keyboard_quick_switch_view,
104                                 overlayContext.getDragLayer(),
105                                 /* attachToRoot= */ false);
106         mQuickSwitchViewController = new KeyboardQuickSwitchViewController(
107                 mControllers, overlayContext, keyboardQuickSwitchView, mControllerCallbacks);
108 
109         DesktopVisibilityController desktopController =
110                 LauncherActivityInterface.INSTANCE.getDesktopVisibilityController();
111         final boolean onDesktop =
112                 DesktopTaskView.DESKTOP_IS_PROTO2_ENABLED
113                         && desktopController != null
114                         && desktopController.areFreeformTasksVisible();
115 
116         if (mModel.isTaskListValid(mTaskListChangeId)) {
117             mQuickSwitchViewController.openQuickSwitchView(mTasks,
118                     mNumHiddenTasks, /* updateTasks= */ false, currentFocusedIndex, onDesktop);
119             return;
120         }
121 
122         mTaskListChangeId = mModel.getTasks((tasks) -> {
123             if (onDesktop) {
124                 processLoadedTasksOnDesktop(tasks);
125             } else {
126                 processLoadedTasks(tasks);
127             }
128             mQuickSwitchViewController.openQuickSwitchView(mTasks,
129                     mNumHiddenTasks, /* updateTasks= */ true, currentFocusedIndex, onDesktop);
130         });
131     }
132 
processLoadedTasks(ArrayList<GroupTask> tasks)133     private void processLoadedTasks(ArrayList<GroupTask> tasks) {
134         // Only store MAX_TASK tasks, from most to least recent
135         Collections.reverse(tasks);
136 
137         // Hide all desktop tasks and show them on the hidden tile
138         int hiddenDesktopTasks = 0;
139         if (DesktopTaskView.DESKTOP_IS_PROTO2_ENABLED) {
140             DesktopTask desktopTask = findDesktopTask(tasks);
141             if (desktopTask != null) {
142                 hiddenDesktopTasks = desktopTask.tasks.size();
143                 tasks = tasks.stream()
144                         .filter(t -> !(t instanceof DesktopTask))
145                         .collect(Collectors.toCollection(ArrayList<GroupTask>::new));
146             }
147         }
148         mTasks = tasks.stream()
149                 .limit(MAX_TASKS)
150                 .collect(Collectors.toList());
151         mNumHiddenTasks = Math.max(0, tasks.size() - MAX_TASKS) + hiddenDesktopTasks;
152     }
153 
processLoadedTasksOnDesktop(ArrayList<GroupTask> tasks)154     private void processLoadedTasksOnDesktop(ArrayList<GroupTask> tasks) {
155         // Find the single desktop task that contains a grouping of desktop tasks
156         DesktopTask desktopTask = findDesktopTask(tasks);
157 
158         if (desktopTask != null) {
159             mTasks = desktopTask.tasks.stream().map(GroupTask::new).collect(Collectors.toList());
160             // All other tasks, apart from the grouped desktop task, are hidden
161             mNumHiddenTasks = Math.max(0, tasks.size() - 1);
162         } else {
163             // Desktop tasks were visible, but the recents entry is missing. Fall back to empty list
164             mTasks = Collections.emptyList();
165             mNumHiddenTasks = tasks.size();
166         }
167     }
168 
169     @Nullable
findDesktopTask(ArrayList<GroupTask> tasks)170     private DesktopTask findDesktopTask(ArrayList<GroupTask> tasks) {
171         return (DesktopTask) tasks.stream()
172                 .filter(t -> t instanceof DesktopTask)
173                 .findFirst()
174                 .orElse(null);
175     }
176 
closeQuickSwitchView()177     void closeQuickSwitchView() {
178         if (mQuickSwitchViewController == null) {
179             return;
180         }
181         mQuickSwitchViewController.closeQuickSwitchView(true);
182     }
183 
184     /**
185      * See {@link TaskbarUIController#launchFocusedTask()}
186      */
launchFocusedTask()187     int launchFocusedTask() {
188         // Return -1 so that the RecentsView is not incorrectly opened when the user closes the
189         // quick switch view by tapping the screen or when there are no recent tasks.
190         return mQuickSwitchViewController == null || mTasks.isEmpty()
191                 ? -1 : mQuickSwitchViewController.launchFocusedTask();
192     }
193 
onDestroy()194     void onDestroy() {
195         if (mQuickSwitchViewController != null) {
196             mQuickSwitchViewController.onDestroy();
197         }
198     }
199 
200     @Override
dumpLogs(String prefix, PrintWriter pw)201     public void dumpLogs(String prefix, PrintWriter pw) {
202         pw.println(prefix + "KeyboardQuickSwitchController:");
203 
204         pw.println(prefix + "\tisOpen=" + (mQuickSwitchViewController != null));
205         pw.println(prefix + "\tmNumHiddenTasks=" + mNumHiddenTasks);
206         pw.println(prefix + "\tmTaskListChangeId=" + mTaskListChangeId);
207         pw.println(prefix + "\tmTasks=[");
208         for (GroupTask task : mTasks) {
209             Task task1 = task.task1;
210             Task task2 = task.task2;
211             ComponentName cn1 = task1.getTopComponent();
212             ComponentName cn2 = task2 != null ? task2.getTopComponent() : null;
213             pw.println(prefix + "\t\tt1: (id=" + task1.key.id
214                     + "; package=" + (cn1 != null ? cn1.getPackageName() + ")" : "no package)")
215                     + " t2: (id=" + (task2 != null ? task2.key.id : "-1")
216                     + "; package=" + (cn2 != null ? cn2.getPackageName() + ")"
217                     : "no package)"));
218         }
219         pw.println(prefix + "\t]");
220 
221         if (mQuickSwitchViewController != null) {
222             mQuickSwitchViewController.dumpLogs(prefix + '\t', pw);
223         }
224     }
225 
226     class ControllerCallbacks {
227 
getTaskCount()228         int getTaskCount() {
229             return mTasks.size() + (mNumHiddenTasks == 0 ? 0 : 1);
230         }
231 
232         @Nullable
getTaskAt(int index)233         GroupTask getTaskAt(int index) {
234             return index < 0 || index >= mTasks.size() ? null : mTasks.get(index);
235         }
236 
updateThumbnailInBackground(Task task, Consumer<ThumbnailData> callback)237         void updateThumbnailInBackground(Task task, Consumer<ThumbnailData> callback) {
238             mModel.getThumbnailCache().updateThumbnailInBackground(task, callback);
239         }
240 
updateIconInBackground(Task task, Consumer<Task> callback)241         void updateIconInBackground(Task task, Consumer<Task> callback) {
242             mModel.getIconCache().updateIconInBackground(task, callback);
243         }
244 
onCloseComplete()245         void onCloseComplete() {
246             mQuickSwitchViewController = null;
247         }
248     }
249 }
250