• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2014 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 
17 package com.android.quickstep;
18 
19 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
20 import static com.android.quickstep.views.DesktopTaskView.DESKTOP_IS_PROTO2_ENABLED;
21 import static com.android.wm.shell.util.GroupedRecentTaskInfo.TYPE_FREEFORM;
22 
23 import android.annotation.TargetApi;
24 import android.app.ActivityManager;
25 import android.app.KeyguardManager;
26 import android.app.TaskInfo;
27 import android.content.ComponentName;
28 import android.os.Build;
29 import android.os.Process;
30 import android.os.RemoteException;
31 import android.util.SparseBooleanArray;
32 
33 import androidx.annotation.VisibleForTesting;
34 
35 import com.android.launcher3.util.LooperExecutor;
36 import com.android.launcher3.util.SplitConfigurationOptions;
37 import com.android.quickstep.util.DesktopTask;
38 import com.android.quickstep.util.GroupTask;
39 import com.android.systemui.shared.recents.model.Task;
40 import com.android.wm.shell.recents.IRecentTasksListener;
41 import com.android.wm.shell.util.GroupedRecentTaskInfo;
42 import com.android.wm.shell.util.SplitBounds;
43 
44 import java.io.PrintWriter;
45 import java.util.ArrayList;
46 import java.util.Collections;
47 import java.util.function.Consumer;
48 import java.util.function.Predicate;
49 import java.util.stream.Collectors;
50 
51 /**
52  * Manages the recent task list from the system, caching it as necessary.
53  */
54 @TargetApi(Build.VERSION_CODES.R)
55 public class RecentTasksList {
56 
57     private static final TaskLoadResult INVALID_RESULT = new TaskLoadResult(-1, false, 0);
58 
59     private final KeyguardManager mKeyguardManager;
60     private final LooperExecutor mMainThreadExecutor;
61     private final SystemUiProxy mSysUiProxy;
62 
63     // The list change id, increments as the task list changes in the system
64     private int mChangeId;
65     // Whether we are currently updating the tasks in the background (up to when the result is
66     // posted back on the main thread)
67     private boolean mLoadingTasksInBackground;
68 
69     private TaskLoadResult mResultsBg = INVALID_RESULT;
70     private TaskLoadResult mResultsUi = INVALID_RESULT;
71 
72     private RecentsModel.RunningTasksListener mRunningTasksListener;
73     // Tasks are stored in order of least recently launched to most recently launched.
74     private ArrayList<ActivityManager.RunningTaskInfo> mRunningTasks;
75 
RecentTasksList(LooperExecutor mainThreadExecutor, KeyguardManager keyguardManager, SystemUiProxy sysUiProxy)76     public RecentTasksList(LooperExecutor mainThreadExecutor, KeyguardManager keyguardManager,
77             SystemUiProxy sysUiProxy) {
78         mMainThreadExecutor = mainThreadExecutor;
79         mKeyguardManager = keyguardManager;
80         mChangeId = 1;
81         mSysUiProxy = sysUiProxy;
82         sysUiProxy.registerRecentTasksListener(new IRecentTasksListener.Stub() {
83             @Override
84             public void onRecentTasksChanged() throws RemoteException {
85                 mMainThreadExecutor.execute(RecentTasksList.this::onRecentTasksChanged);
86             }
87 
88             @Override
89             public void onRunningTaskAppeared(ActivityManager.RunningTaskInfo taskInfo) {
90                 mMainThreadExecutor.execute(() -> {
91                     RecentTasksList.this.onRunningTaskAppeared(taskInfo);
92                 });
93             }
94 
95             @Override
96             public void onRunningTaskVanished(ActivityManager.RunningTaskInfo taskInfo) {
97                 mMainThreadExecutor.execute(() -> {
98                     RecentTasksList.this.onRunningTaskVanished(taskInfo);
99                 });
100             }
101         });
102         // We may receive onRunningTaskAppeared events later for tasks which have already been
103         // included in the list returned by mSysUiProxy.getRunningTasks(), or may receive
104         // onRunningTaskVanished for tasks not included in the returned list. These cases will be
105         // addressed when the tasks are added to/removed from mRunningTasks.
106         initRunningTasks(mSysUiProxy.getRunningTasks(Integer.MAX_VALUE));
107     }
108 
109     @VisibleForTesting
isLoadingTasksInBackground()110     public boolean isLoadingTasksInBackground() {
111         return mLoadingTasksInBackground;
112     }
113 
114     /**
115      * Fetches the task keys skipping any local cache.
116      */
getTaskKeys(int numTasks, Consumer<ArrayList<GroupTask>> callback)117     public void getTaskKeys(int numTasks, Consumer<ArrayList<GroupTask>> callback) {
118         // Kick off task loading in the background
119         UI_HELPER_EXECUTOR.execute(() -> {
120             ArrayList<GroupTask> tasks = loadTasksInBackground(numTasks, -1,
121                     true /* loadKeysOnly */);
122             mMainThreadExecutor.execute(() -> callback.accept(tasks));
123         });
124     }
125 
126     /**
127      * Asynchronously fetches the list of recent tasks, reusing cached list if available.
128      *
129      * @param loadKeysOnly Whether to load other associated task data, or just the key
130      * @param callback The callback to receive the list of recent tasks
131      * @return The change id of the current task list
132      */
getTasks(boolean loadKeysOnly, Consumer<ArrayList<GroupTask>> callback, Predicate<GroupTask> filter)133     public synchronized int getTasks(boolean loadKeysOnly,
134             Consumer<ArrayList<GroupTask>> callback, Predicate<GroupTask> filter) {
135         final int requestLoadId = mChangeId;
136         if (mResultsUi.isValidForRequest(requestLoadId, loadKeysOnly)) {
137             // The list is up to date, send the callback on the next frame,
138             // so that requestID can be returned first.
139             if (callback != null) {
140                 // Copy synchronously as the changeId might change by next frame
141                 // and filter GroupTasks
142                 ArrayList<GroupTask> result = mResultsUi.stream().filter(filter)
143                         .map(GroupTask::copy)
144                         .collect(Collectors.toCollection(ArrayList<GroupTask>::new));
145 
146                 mMainThreadExecutor.post(() -> {
147                     callback.accept(result);
148                 });
149             }
150 
151             return requestLoadId;
152         }
153 
154         // Kick off task loading in the background
155         mLoadingTasksInBackground = true;
156         UI_HELPER_EXECUTOR.execute(() -> {
157             if (!mResultsBg.isValidForRequest(requestLoadId, loadKeysOnly)) {
158                 mResultsBg = loadTasksInBackground(Integer.MAX_VALUE, requestLoadId, loadKeysOnly);
159             }
160             TaskLoadResult loadResult = mResultsBg;
161             mMainThreadExecutor.execute(() -> {
162                 mLoadingTasksInBackground = false;
163                 mResultsUi = loadResult;
164                 if (callback != null) {
165                     // filter the tasks if needed before passing them into the callback
166                     ArrayList<GroupTask> result = mResultsUi.stream().filter(filter)
167                             .map(GroupTask::copy)
168                             .collect(Collectors.toCollection(ArrayList<GroupTask>::new));
169 
170                     callback.accept(result);
171                 }
172             });
173         });
174 
175         return requestLoadId;
176     }
177 
178     /**
179      * @return Whether the provided {@param changeId} is the latest recent tasks list id.
180      */
isTaskListValid(int changeId)181     public synchronized boolean isTaskListValid(int changeId) {
182         return mChangeId == changeId;
183     }
184 
onRecentTasksChanged()185     public void onRecentTasksChanged() {
186         invalidateLoadedTasks();
187     }
188 
invalidateLoadedTasks()189     private synchronized void invalidateLoadedTasks() {
190         UI_HELPER_EXECUTOR.execute(() -> mResultsBg = INVALID_RESULT);
191         mResultsUi = INVALID_RESULT;
192         mChangeId++;
193     }
194 
195      /**
196      * Registers a listener for running tasks
197      */
registerRunningTasksListener(RecentsModel.RunningTasksListener listener)198     public void registerRunningTasksListener(RecentsModel.RunningTasksListener listener) {
199         mRunningTasksListener = listener;
200     }
201 
202     /**
203      * Removes the previously registered running tasks listener
204      */
unregisterRunningTasksListener()205     public void unregisterRunningTasksListener() {
206         mRunningTasksListener = null;
207     }
208 
initRunningTasks(ArrayList<ActivityManager.RunningTaskInfo> runningTasks)209     private void initRunningTasks(ArrayList<ActivityManager.RunningTaskInfo> runningTasks) {
210         // Tasks are retrieved in order of most recently launched/used to least recently launched.
211         mRunningTasks = new ArrayList<>(runningTasks);
212         Collections.reverse(mRunningTasks);
213     }
214 
215     /**
216      * Gets the set of running tasks.
217      */
getRunningTasks()218     public ArrayList<ActivityManager.RunningTaskInfo> getRunningTasks() {
219         return mRunningTasks;
220     }
221 
onRunningTaskAppeared(ActivityManager.RunningTaskInfo taskInfo)222     private void onRunningTaskAppeared(ActivityManager.RunningTaskInfo taskInfo) {
223         // Make sure this task is not already in the list
224         for (ActivityManager.RunningTaskInfo existingTask : mRunningTasks) {
225             if (taskInfo.taskId == existingTask.taskId) {
226                 return;
227             }
228         }
229         mRunningTasks.add(taskInfo);
230         if (mRunningTasksListener != null) {
231             mRunningTasksListener.onRunningTasksChanged();
232         }
233     }
234 
onRunningTaskVanished(ActivityManager.RunningTaskInfo taskInfo)235     private void onRunningTaskVanished(ActivityManager.RunningTaskInfo taskInfo) {
236         // Find the task from the list of running tasks, if it exists
237         for (ActivityManager.RunningTaskInfo existingTask : mRunningTasks) {
238             if (existingTask.taskId != taskInfo.taskId) continue;
239 
240             mRunningTasks.remove(existingTask);
241             if (mRunningTasksListener != null) {
242                 mRunningTasksListener.onRunningTasksChanged();
243             }
244             return;
245         }
246     }
247 
248     /**
249      * Loads and creates a list of all the recent tasks.
250      */
251     @VisibleForTesting
loadTasksInBackground(int numTasks, int requestId, boolean loadKeysOnly)252     TaskLoadResult loadTasksInBackground(int numTasks, int requestId, boolean loadKeysOnly) {
253         int currentUserId = Process.myUserHandle().getIdentifier();
254         ArrayList<GroupedRecentTaskInfo> rawTasks =
255                 mSysUiProxy.getRecentTasks(numTasks, currentUserId);
256         // The raw tasks are given in most-recent to least-recent order, we need to reverse it
257         Collections.reverse(rawTasks);
258 
259         SparseBooleanArray tmpLockedUsers = new SparseBooleanArray() {
260             @Override
261             public boolean get(int key) {
262                 if (indexOfKey(key) < 0) {
263                     // Fill the cached locked state as we fetch
264                     put(key, mKeyguardManager.isDeviceLocked(key));
265                 }
266                 return super.get(key);
267             }
268         };
269 
270         TaskLoadResult allTasks = new TaskLoadResult(requestId, loadKeysOnly, rawTasks.size());
271 
272         for (GroupedRecentTaskInfo rawTask : rawTasks) {
273             if (DESKTOP_IS_PROTO2_ENABLED && rawTask.getType() == TYPE_FREEFORM) {
274                 GroupTask desktopTask = createDesktopTask(rawTask);
275                 allTasks.add(desktopTask);
276                 continue;
277             }
278             ActivityManager.RecentTaskInfo taskInfo1 = rawTask.getTaskInfo1();
279             ActivityManager.RecentTaskInfo taskInfo2 = rawTask.getTaskInfo2();
280             Task.TaskKey task1Key = new Task.TaskKey(taskInfo1);
281             Task task1 = loadKeysOnly
282                     ? new Task(task1Key)
283                     : Task.from(task1Key, taskInfo1,
284                             tmpLockedUsers.get(task1Key.userId) /* isLocked */);
285             task1.setLastSnapshotData(taskInfo1);
286             Task task2 = null;
287             if (taskInfo2 != null) {
288                 Task.TaskKey task2Key = new Task.TaskKey(taskInfo2);
289                 task2 = loadKeysOnly
290                         ? new Task(task2Key)
291                         : Task.from(task2Key, taskInfo2,
292                                 tmpLockedUsers.get(task2Key.userId) /* isLocked */);
293                 task2.setLastSnapshotData(taskInfo2);
294             }
295             final SplitConfigurationOptions.SplitBounds launcherSplitBounds =
296                     convertSplitBounds(rawTask.getSplitBounds());
297             allTasks.add(new GroupTask(task1, task2, launcherSplitBounds));
298         }
299 
300         return allTasks;
301     }
302 
createDesktopTask(GroupedRecentTaskInfo recentTaskInfo)303     private DesktopTask createDesktopTask(GroupedRecentTaskInfo recentTaskInfo) {
304         ArrayList<Task> tasks = new ArrayList<>(recentTaskInfo.getTaskInfoList().size());
305         for (ActivityManager.RecentTaskInfo taskInfo : recentTaskInfo.getTaskInfoList()) {
306             Task.TaskKey key = new Task.TaskKey(taskInfo);
307             Task task = Task.from(key, taskInfo, false);
308             task.setLastSnapshotData(taskInfo);
309             task.positionInParent = taskInfo.positionInParent;
310             task.appBounds = taskInfo.configuration.windowConfiguration.getAppBounds();
311             // TODO(b/244348395): tasks should be sorted from oldest to most recently used
312             tasks.add(task);
313         }
314         return new DesktopTask(tasks);
315     }
316 
convertSplitBounds( SplitBounds shellSplitBounds)317     private SplitConfigurationOptions.SplitBounds convertSplitBounds(
318             SplitBounds shellSplitBounds) {
319         return shellSplitBounds == null ?
320                 null :
321                 new SplitConfigurationOptions.SplitBounds(
322                         shellSplitBounds.leftTopBounds, shellSplitBounds.rightBottomBounds,
323                         shellSplitBounds.leftTopTaskId, shellSplitBounds.rightBottomTaskId);
324     }
325 
copyOf(ArrayList<GroupTask> tasks)326     private ArrayList<GroupTask> copyOf(ArrayList<GroupTask> tasks) {
327         ArrayList<GroupTask> newTasks = new ArrayList<>();
328         for (int i = 0; i < tasks.size(); i++) {
329             newTasks.add(tasks.get(i).copy());
330         }
331         return newTasks;
332     }
333 
dump(String prefix, PrintWriter writer)334     public void dump(String prefix, PrintWriter writer) {
335         writer.println(prefix + "RecentTasksList:");
336         writer.println(prefix + "  mChangeId=" + mChangeId);
337         writer.println(prefix + "  mResultsUi=[id=" + mResultsUi.mRequestId + ", tasks=");
338         for (GroupTask task : mResultsUi) {
339             Task task1 = task.task1;
340             Task task2 = task.task2;
341             ComponentName cn1 = task1.getTopComponent();
342             ComponentName cn2 = task2 != null ? task2.getTopComponent() : null;
343             writer.println(prefix + "    t1: (id=" + task1.key.id
344                     + "; package=" + (cn1 != null ? cn1.getPackageName() + ")" : "no package)")
345                     + " t2: (id=" + (task2 != null ? task2.key.id : "-1")
346                     + "; package=" + (cn2 != null ? cn2.getPackageName() + ")" : "no package)"));
347         }
348         writer.println(prefix + "  ]");
349         int currentUserId = Process.myUserHandle().getIdentifier();
350         ArrayList<GroupedRecentTaskInfo> rawTasks =
351                 mSysUiProxy.getRecentTasks(Integer.MAX_VALUE, currentUserId);
352         writer.println(prefix + "  rawTasks=[");
353         for (GroupedRecentTaskInfo task : rawTasks) {
354             TaskInfo taskInfo1 = task.getTaskInfo1();
355             TaskInfo taskInfo2 = task.getTaskInfo2();
356             ComponentName cn1 = taskInfo1.topActivity;
357             ComponentName cn2 = taskInfo2 != null ? taskInfo2.topActivity : null;
358             writer.println(prefix + "    t1: (id=" + taskInfo1.taskId
359                     + "; package=" + (cn1 != null ? cn1.getPackageName() + ")" : "no package)")
360                     + " t2: (id=" + (taskInfo2 != null ? taskInfo2.taskId : "-1")
361                     + "; package=" + (cn2 != null ? cn2.getPackageName() + ")" : "no package)"));
362         }
363         writer.println(prefix + "  ]");
364     }
365 
366     private static class TaskLoadResult extends ArrayList<GroupTask> {
367 
368         final int mRequestId;
369 
370         // If the result was loaded with keysOnly  = true
371         final boolean mKeysOnly;
372 
TaskLoadResult(int requestId, boolean keysOnly, int size)373         TaskLoadResult(int requestId, boolean keysOnly, int size) {
374             super(size);
375             mRequestId = requestId;
376             mKeysOnly = keysOnly;
377         }
378 
isValidForRequest(int requestId, boolean loadKeysOnly)379         boolean isValidForRequest(int requestId, boolean loadKeysOnly) {
380             return mRequestId == requestId && (!mKeysOnly || loadKeysOnly);
381         }
382     }
383 }