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