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 }