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 android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS; 20 21 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; 22 import static com.android.quickstep.views.DesktopTaskView.DESKTOP_IS_PROTO2_ENABLED; 23 import static com.android.wm.shell.util.GroupedRecentTaskInfo.TYPE_FREEFORM; 24 25 import android.annotation.TargetApi; 26 import android.app.ActivityManager; 27 import android.app.KeyguardManager; 28 import android.app.TaskInfo; 29 import android.content.ComponentName; 30 import android.os.Build; 31 import android.os.Process; 32 import android.os.RemoteException; 33 import android.util.SparseBooleanArray; 34 35 import androidx.annotation.VisibleForTesting; 36 37 import com.android.launcher3.util.LooperExecutor; 38 import com.android.launcher3.util.SplitConfigurationOptions; 39 import com.android.quickstep.util.DesktopTask; 40 import com.android.quickstep.util.GroupTask; 41 import com.android.systemui.shared.recents.model.Task; 42 import com.android.wm.shell.recents.IRecentTasksListener; 43 import com.android.wm.shell.util.GroupedRecentTaskInfo; 44 import com.android.wm.shell.util.SplitBounds; 45 46 import java.io.PrintWriter; 47 import java.util.ArrayList; 48 import java.util.Collections; 49 import java.util.function.Consumer; 50 import java.util.function.Predicate; 51 import java.util.stream.Collectors; 52 53 /** 54 * Manages the recent task list from the system, caching it as necessary. 55 */ 56 @TargetApi(Build.VERSION_CODES.R) 57 public class RecentTasksList { 58 59 private static final TaskLoadResult INVALID_RESULT = new TaskLoadResult(-1, false, 0); 60 61 private final KeyguardManager mKeyguardManager; 62 private final LooperExecutor mMainThreadExecutor; 63 private final SystemUiProxy mSysUiProxy; 64 65 // The list change id, increments as the task list changes in the system 66 private int mChangeId; 67 // Whether we are currently updating the tasks in the background (up to when the result is 68 // posted back on the main thread) 69 private boolean mLoadingTasksInBackground; 70 71 private TaskLoadResult mResultsBg = INVALID_RESULT; 72 private TaskLoadResult mResultsUi = INVALID_RESULT; 73 74 private RecentsModel.RunningTasksListener mRunningTasksListener; 75 // Tasks are stored in order of least recently launched to most recently launched. 76 private ArrayList<ActivityManager.RunningTaskInfo> mRunningTasks; 77 RecentTasksList(LooperExecutor mainThreadExecutor, KeyguardManager keyguardManager, SystemUiProxy sysUiProxy)78 public RecentTasksList(LooperExecutor mainThreadExecutor, KeyguardManager keyguardManager, 79 SystemUiProxy sysUiProxy) { 80 mMainThreadExecutor = mainThreadExecutor; 81 mKeyguardManager = keyguardManager; 82 mChangeId = 1; 83 mSysUiProxy = sysUiProxy; 84 sysUiProxy.registerRecentTasksListener(new IRecentTasksListener.Stub() { 85 @Override 86 public void onRecentTasksChanged() throws RemoteException { 87 mMainThreadExecutor.execute(RecentTasksList.this::onRecentTasksChanged); 88 } 89 90 @Override 91 public void onRunningTaskAppeared(ActivityManager.RunningTaskInfo taskInfo) { 92 mMainThreadExecutor.execute(() -> { 93 RecentTasksList.this.onRunningTaskAppeared(taskInfo); 94 }); 95 } 96 97 @Override 98 public void onRunningTaskVanished(ActivityManager.RunningTaskInfo taskInfo) { 99 mMainThreadExecutor.execute(() -> { 100 RecentTasksList.this.onRunningTaskVanished(taskInfo); 101 }); 102 } 103 }); 104 // We may receive onRunningTaskAppeared events later for tasks which have already been 105 // included in the list returned by mSysUiProxy.getRunningTasks(), or may receive 106 // onRunningTaskVanished for tasks not included in the returned list. These cases will be 107 // addressed when the tasks are added to/removed from mRunningTasks. 108 initRunningTasks(mSysUiProxy.getRunningTasks(Integer.MAX_VALUE)); 109 } 110 111 @VisibleForTesting isLoadingTasksInBackground()112 public boolean isLoadingTasksInBackground() { 113 return mLoadingTasksInBackground; 114 } 115 116 /** 117 * Fetches the task keys skipping any local cache. 118 */ getTaskKeys(int numTasks, Consumer<ArrayList<GroupTask>> callback)119 public void getTaskKeys(int numTasks, Consumer<ArrayList<GroupTask>> callback) { 120 // Kick off task loading in the background 121 UI_HELPER_EXECUTOR.execute(() -> { 122 ArrayList<GroupTask> tasks = loadTasksInBackground(numTasks, -1, 123 true /* loadKeysOnly */); 124 mMainThreadExecutor.execute(() -> callback.accept(tasks)); 125 }); 126 } 127 128 /** 129 * Asynchronously fetches the list of recent tasks, reusing cached list if available. 130 * 131 * @param loadKeysOnly Whether to load other associated task data, or just the key 132 * @param callback The callback to receive the list of recent tasks 133 * @return The change id of the current task list 134 */ getTasks(boolean loadKeysOnly, Consumer<ArrayList<GroupTask>> callback, Predicate<GroupTask> filter)135 public synchronized int getTasks(boolean loadKeysOnly, 136 Consumer<ArrayList<GroupTask>> callback, Predicate<GroupTask> filter) { 137 final int requestLoadId = mChangeId; 138 if (mResultsUi.isValidForRequest(requestLoadId, loadKeysOnly)) { 139 // The list is up to date, send the callback on the next frame, 140 // so that requestID can be returned first. 141 if (callback != null) { 142 // Copy synchronously as the changeId might change by next frame 143 // and filter GroupTasks 144 ArrayList<GroupTask> result = mResultsUi.stream().filter(filter) 145 .map(GroupTask::copy) 146 .collect(Collectors.toCollection(ArrayList<GroupTask>::new)); 147 148 mMainThreadExecutor.post(() -> { 149 callback.accept(result); 150 }); 151 } 152 153 return requestLoadId; 154 } 155 156 // Kick off task loading in the background 157 mLoadingTasksInBackground = true; 158 UI_HELPER_EXECUTOR.execute(() -> { 159 if (!mResultsBg.isValidForRequest(requestLoadId, loadKeysOnly)) { 160 mResultsBg = loadTasksInBackground(Integer.MAX_VALUE, requestLoadId, loadKeysOnly); 161 } 162 TaskLoadResult loadResult = mResultsBg; 163 mMainThreadExecutor.execute(() -> { 164 mLoadingTasksInBackground = false; 165 mResultsUi = loadResult; 166 if (callback != null) { 167 // filter the tasks if needed before passing them into the callback 168 ArrayList<GroupTask> result = mResultsUi.stream().filter(filter) 169 .map(GroupTask::copy) 170 .collect(Collectors.toCollection(ArrayList<GroupTask>::new)); 171 172 callback.accept(result); 173 } 174 }); 175 }); 176 177 return requestLoadId; 178 } 179 180 /** 181 * @return Whether the provided {@param changeId} is the latest recent tasks list id. 182 */ isTaskListValid(int changeId)183 public synchronized boolean isTaskListValid(int changeId) { 184 return mChangeId == changeId; 185 } 186 onRecentTasksChanged()187 public void onRecentTasksChanged() { 188 invalidateLoadedTasks(); 189 } 190 invalidateLoadedTasks()191 private synchronized void invalidateLoadedTasks() { 192 UI_HELPER_EXECUTOR.execute(() -> mResultsBg = INVALID_RESULT); 193 mResultsUi = INVALID_RESULT; 194 mChangeId++; 195 } 196 197 /** 198 * Registers a listener for running tasks 199 */ registerRunningTasksListener(RecentsModel.RunningTasksListener listener)200 public void registerRunningTasksListener(RecentsModel.RunningTasksListener listener) { 201 mRunningTasksListener = listener; 202 } 203 204 /** 205 * Removes the previously registered running tasks listener 206 */ unregisterRunningTasksListener()207 public void unregisterRunningTasksListener() { 208 mRunningTasksListener = null; 209 } 210 initRunningTasks(ArrayList<ActivityManager.RunningTaskInfo> runningTasks)211 private void initRunningTasks(ArrayList<ActivityManager.RunningTaskInfo> runningTasks) { 212 // Tasks are retrieved in order of most recently launched/used to least recently launched. 213 mRunningTasks = new ArrayList<>(runningTasks); 214 Collections.reverse(mRunningTasks); 215 } 216 217 /** 218 * Gets the set of running tasks. 219 */ getRunningTasks()220 public ArrayList<ActivityManager.RunningTaskInfo> getRunningTasks() { 221 return mRunningTasks; 222 } 223 onRunningTaskAppeared(ActivityManager.RunningTaskInfo taskInfo)224 private void onRunningTaskAppeared(ActivityManager.RunningTaskInfo taskInfo) { 225 // Make sure this task is not already in the list 226 for (ActivityManager.RunningTaskInfo existingTask : mRunningTasks) { 227 if (taskInfo.taskId == existingTask.taskId) { 228 return; 229 } 230 } 231 mRunningTasks.add(taskInfo); 232 if (mRunningTasksListener != null) { 233 mRunningTasksListener.onRunningTasksChanged(); 234 } 235 } 236 onRunningTaskVanished(ActivityManager.RunningTaskInfo taskInfo)237 private void onRunningTaskVanished(ActivityManager.RunningTaskInfo taskInfo) { 238 // Find the task from the list of running tasks, if it exists 239 for (ActivityManager.RunningTaskInfo existingTask : mRunningTasks) { 240 if (existingTask.taskId != taskInfo.taskId) continue; 241 242 mRunningTasks.remove(existingTask); 243 if (mRunningTasksListener != null) { 244 mRunningTasksListener.onRunningTasksChanged(); 245 } 246 return; 247 } 248 } 249 250 /** 251 * Loads and creates a list of all the recent tasks. 252 */ 253 @VisibleForTesting loadTasksInBackground(int numTasks, int requestId, boolean loadKeysOnly)254 TaskLoadResult loadTasksInBackground(int numTasks, int requestId, boolean loadKeysOnly) { 255 int currentUserId = Process.myUserHandle().getIdentifier(); 256 ArrayList<GroupedRecentTaskInfo> rawTasks = 257 mSysUiProxy.getRecentTasks(numTasks, currentUserId); 258 // The raw tasks are given in most-recent to least-recent order, we need to reverse it 259 Collections.reverse(rawTasks); 260 261 SparseBooleanArray tmpLockedUsers = new SparseBooleanArray() { 262 @Override 263 public boolean get(int key) { 264 if (indexOfKey(key) < 0) { 265 // Fill the cached locked state as we fetch 266 put(key, mKeyguardManager.isDeviceLocked(key)); 267 } 268 return super.get(key); 269 } 270 }; 271 272 TaskLoadResult allTasks = new TaskLoadResult(requestId, loadKeysOnly, rawTasks.size()); 273 274 int numVisibleTasks = 0; 275 for (GroupedRecentTaskInfo rawTask : rawTasks) { 276 if (DESKTOP_IS_PROTO2_ENABLED && rawTask.getType() == TYPE_FREEFORM) { 277 GroupTask desktopTask = createDesktopTask(rawTask); 278 allTasks.add(desktopTask); 279 continue; 280 } 281 ActivityManager.RecentTaskInfo taskInfo1 = rawTask.getTaskInfo1(); 282 ActivityManager.RecentTaskInfo taskInfo2 = rawTask.getTaskInfo2(); 283 Task.TaskKey task1Key = new Task.TaskKey(taskInfo1); 284 Task task1 = loadKeysOnly 285 ? new Task(task1Key) 286 : Task.from(task1Key, taskInfo1, 287 tmpLockedUsers.get(task1Key.userId) /* isLocked */); 288 task1.setLastSnapshotData(taskInfo1); 289 Task task2 = null; 290 if (taskInfo2 != null) { 291 // Is split task 292 Task.TaskKey task2Key = new Task.TaskKey(taskInfo2); 293 task2 = loadKeysOnly 294 ? new Task(task2Key) 295 : Task.from(task2Key, taskInfo2, 296 tmpLockedUsers.get(task2Key.userId) /* isLocked */); 297 task2.setLastSnapshotData(taskInfo2); 298 } else { 299 // Is fullscreen task 300 if (numVisibleTasks > 0) { 301 boolean isExcluded = (taskInfo1.baseIntent.getFlags() 302 & FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) != 0; 303 if (taskInfo1.isTopActivityTransparent && isExcluded) { 304 // If there are already visible tasks, then ignore the excluded tasks and 305 // don't add them to the returned list 306 continue; 307 } 308 } 309 } 310 if (taskInfo1.isVisible) { 311 numVisibleTasks++; 312 } 313 final SplitConfigurationOptions.SplitBounds launcherSplitBounds = 314 convertSplitBounds(rawTask.getSplitBounds()); 315 allTasks.add(new GroupTask(task1, task2, launcherSplitBounds)); 316 } 317 318 return allTasks; 319 } 320 createDesktopTask(GroupedRecentTaskInfo recentTaskInfo)321 private DesktopTask createDesktopTask(GroupedRecentTaskInfo recentTaskInfo) { 322 ArrayList<Task> tasks = new ArrayList<>(recentTaskInfo.getTaskInfoList().size()); 323 for (ActivityManager.RecentTaskInfo taskInfo : recentTaskInfo.getTaskInfoList()) { 324 Task.TaskKey key = new Task.TaskKey(taskInfo); 325 Task task = Task.from(key, taskInfo, false); 326 task.setLastSnapshotData(taskInfo); 327 task.positionInParent = taskInfo.positionInParent; 328 task.appBounds = taskInfo.configuration.windowConfiguration.getAppBounds(); 329 tasks.add(task); 330 } 331 return new DesktopTask(tasks); 332 } 333 convertSplitBounds( SplitBounds shellSplitBounds)334 private SplitConfigurationOptions.SplitBounds convertSplitBounds( 335 SplitBounds shellSplitBounds) { 336 return shellSplitBounds == null ? 337 null : 338 new SplitConfigurationOptions.SplitBounds( 339 shellSplitBounds.leftTopBounds, shellSplitBounds.rightBottomBounds, 340 shellSplitBounds.leftTopTaskId, shellSplitBounds.rightBottomTaskId); 341 } 342 copyOf(ArrayList<GroupTask> tasks)343 private ArrayList<GroupTask> copyOf(ArrayList<GroupTask> tasks) { 344 ArrayList<GroupTask> newTasks = new ArrayList<>(); 345 for (int i = 0; i < tasks.size(); i++) { 346 newTasks.add(tasks.get(i).copy()); 347 } 348 return newTasks; 349 } 350 dump(String prefix, PrintWriter writer)351 public void dump(String prefix, PrintWriter writer) { 352 writer.println(prefix + "RecentTasksList:"); 353 writer.println(prefix + " mChangeId=" + mChangeId); 354 writer.println(prefix + " mResultsUi=[id=" + mResultsUi.mRequestId + ", tasks="); 355 for (GroupTask task : mResultsUi) { 356 Task task1 = task.task1; 357 Task task2 = task.task2; 358 ComponentName cn1 = task1.getTopComponent(); 359 ComponentName cn2 = task2 != null ? task2.getTopComponent() : null; 360 writer.println(prefix + " t1: (id=" + task1.key.id 361 + "; package=" + (cn1 != null ? cn1.getPackageName() + ")" : "no package)") 362 + " t2: (id=" + (task2 != null ? task2.key.id : "-1") 363 + "; package=" + (cn2 != null ? cn2.getPackageName() + ")" : "no package)")); 364 } 365 writer.println(prefix + " ]"); 366 int currentUserId = Process.myUserHandle().getIdentifier(); 367 ArrayList<GroupedRecentTaskInfo> rawTasks = 368 mSysUiProxy.getRecentTasks(Integer.MAX_VALUE, currentUserId); 369 writer.println(prefix + " rawTasks=["); 370 for (GroupedRecentTaskInfo task : rawTasks) { 371 TaskInfo taskInfo1 = task.getTaskInfo1(); 372 TaskInfo taskInfo2 = task.getTaskInfo2(); 373 ComponentName cn1 = taskInfo1.topActivity; 374 ComponentName cn2 = taskInfo2 != null ? taskInfo2.topActivity : null; 375 writer.println(prefix + " t1: (id=" + taskInfo1.taskId 376 + "; package=" + (cn1 != null ? cn1.getPackageName() + ")" : "no package)") 377 + " t2: (id=" + (taskInfo2 != null ? taskInfo2.taskId : "-1") 378 + "; package=" + (cn2 != null ? cn2.getPackageName() + ")" : "no package)")); 379 } 380 writer.println(prefix + " ]"); 381 } 382 383 private static class TaskLoadResult extends ArrayList<GroupTask> { 384 385 final int mRequestId; 386 387 // If the result was loaded with keysOnly = true 388 final boolean mKeysOnly; 389 TaskLoadResult(int requestId, boolean keysOnly, int size)390 TaskLoadResult(int requestId, boolean keysOnly, int size) { 391 super(size); 392 mRequestId = requestId; 393 mKeysOnly = keysOnly; 394 } 395 isValidForRequest(int requestId, boolean loadKeysOnly)396 boolean isValidForRequest(int requestId, boolean loadKeysOnly) { 397 return mRequestId == requestId && (!mKeysOnly || loadKeysOnly); 398 } 399 } 400 }