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 
21 import android.annotation.TargetApi;
22 import android.app.ActivityManager;
23 import android.os.Build;
24 import android.os.Process;
25 import android.util.SparseBooleanArray;
26 
27 import androidx.annotation.VisibleForTesting;
28 
29 import com.android.launcher3.util.LooperExecutor;
30 import com.android.systemui.shared.recents.model.Task;
31 import com.android.systemui.shared.system.ActivityManagerWrapper;
32 import com.android.systemui.shared.system.KeyguardManagerCompat;
33 import com.android.systemui.shared.system.TaskStackChangeListener;
34 
35 import java.util.ArrayList;
36 import java.util.Collections;
37 import java.util.List;
38 import java.util.function.Consumer;
39 
40 /**
41  * Manages the recent task list from the system, caching it as necessary.
42  */
43 @TargetApi(Build.VERSION_CODES.R)
44 public class RecentTasksList extends TaskStackChangeListener {
45 
46     private static final TaskLoadResult INVALID_RESULT = new TaskLoadResult(-1, false, 0);
47 
48     private final KeyguardManagerCompat mKeyguardManager;
49     private final LooperExecutor mMainThreadExecutor;
50     private final ActivityManagerWrapper mActivityManagerWrapper;
51 
52     // The list change id, increments as the task list changes in the system
53     private int mChangeId;
54 
55     private TaskLoadResult mResultsBg = INVALID_RESULT;
56     private TaskLoadResult mResultsUi = INVALID_RESULT;
57 
RecentTasksList(LooperExecutor mainThreadExecutor, KeyguardManagerCompat keyguardManager, ActivityManagerWrapper activityManagerWrapper)58     public RecentTasksList(LooperExecutor mainThreadExecutor,
59             KeyguardManagerCompat keyguardManager, ActivityManagerWrapper activityManagerWrapper) {
60         mMainThreadExecutor = mainThreadExecutor;
61         mKeyguardManager = keyguardManager;
62         mChangeId = 1;
63         mActivityManagerWrapper = activityManagerWrapper;
64         mActivityManagerWrapper.registerTaskStackListener(this);
65     }
66 
67     /**
68      * Fetches the task keys skipping any local cache.
69      */
getTaskKeys(int numTasks, Consumer<ArrayList<Task>> callback)70     public void getTaskKeys(int numTasks, Consumer<ArrayList<Task>> callback) {
71         // Kick off task loading in the background
72         UI_HELPER_EXECUTOR.execute(() -> {
73             ArrayList<Task> tasks = loadTasksInBackground(numTasks, -1, true /* loadKeysOnly */);
74             mMainThreadExecutor.execute(() -> callback.accept(tasks));
75         });
76     }
77 
78     /**
79      * Asynchronously fetches the list of recent tasks, reusing cached list if available.
80      *
81      * @param loadKeysOnly Whether to load other associated task data, or just the key
82      * @param callback The callback to receive the list of recent tasks
83      * @return The change id of the current task list
84      */
getTasks(boolean loadKeysOnly, Consumer<ArrayList<Task>> callback)85     public synchronized int getTasks(boolean loadKeysOnly, Consumer<ArrayList<Task>> callback) {
86         final int requestLoadId = mChangeId;
87         if (mResultsUi.isValidForRequest(requestLoadId, loadKeysOnly)) {
88             // The list is up to date, send the callback on the next frame,
89             // so that requestID can be returned first.
90             if (callback != null) {
91                 // Copy synchronously as the changeId might change by next frame
92                 ArrayList<Task> result = copyOf(mResultsUi);
93                 mMainThreadExecutor.post(() -> callback.accept(result));
94             }
95 
96             return requestLoadId;
97         }
98 
99         // Kick off task loading in the background
100         UI_HELPER_EXECUTOR.execute(() -> {
101             if (!mResultsBg.isValidForRequest(requestLoadId, loadKeysOnly)) {
102                 mResultsBg = loadTasksInBackground(Integer.MAX_VALUE, requestLoadId, loadKeysOnly);
103             }
104             TaskLoadResult loadResult = mResultsBg;
105             mMainThreadExecutor.execute(() -> {
106                 mResultsUi = loadResult;
107                 if (callback != null) {
108                     ArrayList<Task> result = copyOf(mResultsUi);
109                     callback.accept(result);
110                 }
111             });
112         });
113 
114         return requestLoadId;
115     }
116 
117     /**
118      * @return Whether the provided {@param changeId} is the latest recent tasks list id.
119      */
isTaskListValid(int changeId)120     public synchronized boolean isTaskListValid(int changeId) {
121         return mChangeId == changeId;
122     }
123 
124     @Override
onTaskStackChanged()125     public void onTaskStackChanged() {
126         invalidateLoadedTasks();
127     }
128 
129     @Override
onRecentTaskListUpdated()130     public void onRecentTaskListUpdated() {
131         // In some cases immediately after booting, the tasks in the system recent task list may be
132         // loaded, but not in the active task hierarchy in the system.  These tasks are displayed in
133         // overview, but removing them don't result in a onTaskStackChanged() nor a onTaskRemoved()
134         // callback (those are for changes to the active tasks), but the task list is still updated,
135         // so we should also invalidate the change id to ensure we load a new list instead of
136         // reusing a stale list.
137         invalidateLoadedTasks();
138     }
139 
140     @Override
onTaskRemoved(int taskId)141     public void onTaskRemoved(int taskId) {
142         invalidateLoadedTasks();
143     }
144 
145 
146     @Override
onActivityPinned(String packageName, int userId, int taskId, int stackId)147     public void onActivityPinned(String packageName, int userId, int taskId, int stackId) {
148         invalidateLoadedTasks();
149     }
150 
151     @Override
onActivityUnpinned()152     public synchronized void onActivityUnpinned() {
153         invalidateLoadedTasks();
154     }
155 
invalidateLoadedTasks()156     private synchronized void invalidateLoadedTasks() {
157         UI_HELPER_EXECUTOR.execute(() -> mResultsBg = INVALID_RESULT);
158         mResultsUi = INVALID_RESULT;
159         mChangeId++;
160     }
161 
162     /**
163      * Loads and creates a list of all the recent tasks.
164      */
165     @VisibleForTesting
loadTasksInBackground(int numTasks, int requestId, boolean loadKeysOnly)166     TaskLoadResult loadTasksInBackground(int numTasks, int requestId, boolean loadKeysOnly) {
167         int currentUserId = Process.myUserHandle().getIdentifier();
168         List<ActivityManager.RecentTaskInfo> rawTasks =
169                 mActivityManagerWrapper.getRecentTasks(numTasks, currentUserId);
170         // The raw tasks are given in most-recent to least-recent order, we need to reverse it
171         Collections.reverse(rawTasks);
172 
173         SparseBooleanArray tmpLockedUsers = new SparseBooleanArray() {
174             @Override
175             public boolean get(int key) {
176                 if (indexOfKey(key) < 0) {
177                     // Fill the cached locked state as we fetch
178                     put(key, mKeyguardManager.isDeviceLocked(key));
179                 }
180                 return super.get(key);
181             }
182         };
183 
184         TaskLoadResult allTasks = new TaskLoadResult(requestId, loadKeysOnly, rawTasks.size());
185         for (ActivityManager.RecentTaskInfo rawTask : rawTasks) {
186             Task.TaskKey taskKey = new Task.TaskKey(rawTask);
187             Task task;
188             if (!loadKeysOnly) {
189                 boolean isLocked = tmpLockedUsers.get(taskKey.userId);
190                 task = Task.from(taskKey, rawTask, isLocked);
191             } else {
192                 task = new Task(taskKey);
193             }
194             allTasks.add(task);
195         }
196 
197         return allTasks;
198     }
199 
copyOf(ArrayList<Task> tasks)200     private ArrayList<Task> copyOf(ArrayList<Task> tasks) {
201         ArrayList<Task> newTasks = new ArrayList<>();
202         for (int i = 0; i < tasks.size(); i++) {
203             Task t = tasks.get(i);
204             newTasks.add(new Task(t.key, t.colorPrimary, t.colorBackground, t.isDockable,
205                     t.isLocked, t.taskDescription, t.topActivity));
206         }
207         return newTasks;
208     }
209 
210     private static class TaskLoadResult extends ArrayList<Task> {
211 
212         final int mId;
213 
214         // If the result was loaded with keysOnly  = true
215         final boolean mKeysOnly;
216 
TaskLoadResult(int id, boolean keysOnly, int size)217         TaskLoadResult(int id, boolean keysOnly, int size) {
218             super(size);
219             mId = id;
220             mKeysOnly = keysOnly;
221         }
222 
isValidForRequest(int requestId, boolean loadKeysOnly)223         boolean isValidForRequest(int requestId, boolean loadKeysOnly) {
224             return mId == requestId && (!mKeysOnly || loadKeysOnly);
225         }
226     }
227 }