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.os.RemoteException; 26 import android.util.SparseBooleanArray; 27 28 import androidx.annotation.VisibleForTesting; 29 30 import com.android.quickstep.util.GroupTask; 31 import com.android.launcher3.util.LooperExecutor; 32 import com.android.launcher3.util.SplitConfigurationOptions; 33 import com.android.systemui.shared.recents.model.Task; 34 import com.android.systemui.shared.system.KeyguardManagerCompat; 35 import com.android.wm.shell.recents.IRecentTasksListener; 36 import com.android.wm.shell.util.GroupedRecentTaskInfo; 37 import com.android.wm.shell.util.StagedSplitBounds; 38 39 import java.io.PrintWriter; 40 import java.util.ArrayList; 41 import java.util.Collections; 42 import java.util.function.Consumer; 43 44 /** 45 * Manages the recent task list from the system, caching it as necessary. 46 */ 47 @TargetApi(Build.VERSION_CODES.R) 48 public class RecentTasksList { 49 50 private static final TaskLoadResult INVALID_RESULT = new TaskLoadResult(-1, false, 0); 51 52 private final KeyguardManagerCompat mKeyguardManager; 53 private final LooperExecutor mMainThreadExecutor; 54 private final SystemUiProxy mSysUiProxy; 55 56 // The list change id, increments as the task list changes in the system 57 private int mChangeId; 58 // Whether we are currently updating the tasks in the background (up to when the result is 59 // posted back on the main thread) 60 private boolean mLoadingTasksInBackground; 61 62 private TaskLoadResult mResultsBg = INVALID_RESULT; 63 private TaskLoadResult mResultsUi = INVALID_RESULT; 64 RecentTasksList(LooperExecutor mainThreadExecutor, KeyguardManagerCompat keyguardManager, SystemUiProxy sysUiProxy)65 public RecentTasksList(LooperExecutor mainThreadExecutor, 66 KeyguardManagerCompat keyguardManager, SystemUiProxy sysUiProxy) { 67 mMainThreadExecutor = mainThreadExecutor; 68 mKeyguardManager = keyguardManager; 69 mChangeId = 1; 70 mSysUiProxy = sysUiProxy; 71 sysUiProxy.registerRecentTasksListener(new IRecentTasksListener.Stub() { 72 @Override 73 public void onRecentTasksChanged() throws RemoteException { 74 mMainThreadExecutor.execute(RecentTasksList.this::onRecentTasksChanged); 75 } 76 }); 77 } 78 79 @VisibleForTesting isLoadingTasksInBackground()80 public boolean isLoadingTasksInBackground() { 81 return mLoadingTasksInBackground; 82 } 83 84 /** 85 * Fetches the task keys skipping any local cache. 86 */ getTaskKeys(int numTasks, Consumer<ArrayList<GroupTask>> callback)87 public void getTaskKeys(int numTasks, Consumer<ArrayList<GroupTask>> callback) { 88 // Kick off task loading in the background 89 UI_HELPER_EXECUTOR.execute(() -> { 90 ArrayList<GroupTask> tasks = loadTasksInBackground(numTasks, -1, 91 true /* loadKeysOnly */); 92 mMainThreadExecutor.execute(() -> callback.accept(tasks)); 93 }); 94 } 95 96 /** 97 * Asynchronously fetches the list of recent tasks, reusing cached list if available. 98 * 99 * @param loadKeysOnly Whether to load other associated task data, or just the key 100 * @param callback The callback to receive the list of recent tasks 101 * @return The change id of the current task list 102 */ getTasks(boolean loadKeysOnly, Consumer<ArrayList<GroupTask>> callback)103 public synchronized int getTasks(boolean loadKeysOnly, 104 Consumer<ArrayList<GroupTask>> callback) { 105 final int requestLoadId = mChangeId; 106 if (mResultsUi.isValidForRequest(requestLoadId, loadKeysOnly)) { 107 // The list is up to date, send the callback on the next frame, 108 // so that requestID can be returned first. 109 if (callback != null) { 110 // Copy synchronously as the changeId might change by next frame 111 ArrayList<GroupTask> result = copyOf(mResultsUi); 112 mMainThreadExecutor.post(() -> { 113 callback.accept(result); 114 }); 115 } 116 117 return requestLoadId; 118 } 119 120 // Kick off task loading in the background 121 mLoadingTasksInBackground = true; 122 UI_HELPER_EXECUTOR.execute(() -> { 123 if (!mResultsBg.isValidForRequest(requestLoadId, loadKeysOnly)) { 124 mResultsBg = loadTasksInBackground(Integer.MAX_VALUE, requestLoadId, loadKeysOnly); 125 } 126 TaskLoadResult loadResult = mResultsBg; 127 mMainThreadExecutor.execute(() -> { 128 mLoadingTasksInBackground = false; 129 mResultsUi = loadResult; 130 if (callback != null) { 131 ArrayList<GroupTask> result = copyOf(mResultsUi); 132 callback.accept(result); 133 } 134 }); 135 }); 136 137 return requestLoadId; 138 } 139 140 /** 141 * @return Whether the provided {@param changeId} is the latest recent tasks list id. 142 */ isTaskListValid(int changeId)143 public synchronized boolean isTaskListValid(int changeId) { 144 return mChangeId == changeId; 145 } 146 onRecentTasksChanged()147 public void onRecentTasksChanged() { 148 invalidateLoadedTasks(); 149 } 150 invalidateLoadedTasks()151 private synchronized void invalidateLoadedTasks() { 152 UI_HELPER_EXECUTOR.execute(() -> mResultsBg = INVALID_RESULT); 153 mResultsUi = INVALID_RESULT; 154 mChangeId++; 155 } 156 157 /** 158 * Loads and creates a list of all the recent tasks. 159 */ 160 @VisibleForTesting loadTasksInBackground(int numTasks, int requestId, boolean loadKeysOnly)161 TaskLoadResult loadTasksInBackground(int numTasks, int requestId, boolean loadKeysOnly) { 162 int currentUserId = Process.myUserHandle().getIdentifier(); 163 ArrayList<GroupedRecentTaskInfo> rawTasks = 164 mSysUiProxy.getRecentTasks(numTasks, currentUserId); 165 // The raw tasks are given in most-recent to least-recent order, we need to reverse it 166 Collections.reverse(rawTasks); 167 168 SparseBooleanArray tmpLockedUsers = new SparseBooleanArray() { 169 @Override 170 public boolean get(int key) { 171 if (indexOfKey(key) < 0) { 172 // Fill the cached locked state as we fetch 173 put(key, mKeyguardManager.isDeviceLocked(key)); 174 } 175 return super.get(key); 176 } 177 }; 178 179 TaskLoadResult allTasks = new TaskLoadResult(requestId, loadKeysOnly, rawTasks.size()); 180 for (GroupedRecentTaskInfo rawTask : rawTasks) { 181 ActivityManager.RecentTaskInfo taskInfo1 = rawTask.mTaskInfo1; 182 ActivityManager.RecentTaskInfo taskInfo2 = rawTask.mTaskInfo2; 183 Task.TaskKey task1Key = new Task.TaskKey(taskInfo1); 184 Task task1 = loadKeysOnly 185 ? new Task(task1Key) 186 : Task.from(task1Key, taskInfo1, 187 tmpLockedUsers.get(task1Key.userId) /* isLocked */); 188 task1.setLastSnapshotData(taskInfo1); 189 Task task2 = null; 190 if (taskInfo2 != null) { 191 Task.TaskKey task2Key = new Task.TaskKey(taskInfo2); 192 task2 = loadKeysOnly 193 ? new Task(task2Key) 194 : Task.from(task2Key, taskInfo2, 195 tmpLockedUsers.get(task2Key.userId) /* isLocked */); 196 task2.setLastSnapshotData(taskInfo2); 197 } 198 final SplitConfigurationOptions.StagedSplitBounds launcherSplitBounds = 199 convertSplitBounds(rawTask.mStagedSplitBounds); 200 allTasks.add(new GroupTask(task1, task2, launcherSplitBounds)); 201 } 202 203 return allTasks; 204 } 205 convertSplitBounds( StagedSplitBounds shellSplitBounds)206 private SplitConfigurationOptions.StagedSplitBounds convertSplitBounds( 207 StagedSplitBounds shellSplitBounds) { 208 return shellSplitBounds == null ? 209 null : 210 new SplitConfigurationOptions.StagedSplitBounds( 211 shellSplitBounds.leftTopBounds, shellSplitBounds.rightBottomBounds, 212 shellSplitBounds.leftTopTaskId, shellSplitBounds.rightBottomTaskId); 213 } 214 copyOf(ArrayList<GroupTask> tasks)215 private ArrayList<GroupTask> copyOf(ArrayList<GroupTask> tasks) { 216 ArrayList<GroupTask> newTasks = new ArrayList<>(); 217 for (int i = 0; i < tasks.size(); i++) { 218 newTasks.add(new GroupTask(tasks.get(i))); 219 } 220 return newTasks; 221 } 222 dump(String prefix, PrintWriter writer)223 public void dump(String prefix, PrintWriter writer) { 224 writer.println(prefix + "RecentTasksList:"); 225 writer.println(prefix + " mChangeId=" + mChangeId); 226 writer.println(prefix + " mResultsUi=[id=" + mResultsUi.mRequestId + ", tasks="); 227 for (GroupTask task : mResultsUi) { 228 writer.println(prefix + " t1=" + task.task1.key.id 229 + " t2=" + (task.hasMultipleTasks() ? task.task2.key.id : "-1")); 230 } 231 writer.println(prefix + " ]"); 232 int currentUserId = Process.myUserHandle().getIdentifier(); 233 ArrayList<GroupedRecentTaskInfo> rawTasks = 234 mSysUiProxy.getRecentTasks(Integer.MAX_VALUE, currentUserId); 235 writer.println(prefix + " rawTasks=["); 236 for (GroupedRecentTaskInfo task : rawTasks) { 237 writer.println(prefix + " t1=" + task.mTaskInfo1.taskId 238 + " t2=" + (task.mTaskInfo2 != null ? task.mTaskInfo2.taskId : "-1")); 239 } 240 writer.println(prefix + " ]"); 241 } 242 243 private static class TaskLoadResult extends ArrayList<GroupTask> { 244 245 final int mRequestId; 246 247 // If the result was loaded with keysOnly = true 248 final boolean mKeysOnly; 249 TaskLoadResult(int requestId, boolean keysOnly, int size)250 TaskLoadResult(int requestId, boolean keysOnly, int size) { 251 super(size); 252 mRequestId = requestId; 253 mKeysOnly = keysOnly; 254 } 255 isValidForRequest(int requestId, boolean loadKeysOnly)256 boolean isValidForRequest(int requestId, boolean loadKeysOnly) { 257 return mRequestId == requestId && (!mKeysOnly || loadKeysOnly); 258 } 259 } 260 }