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 }