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