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.P) 44 public class RecentTasksList extends TaskStackChangeListener { 45 46 private final KeyguardManagerCompat mKeyguardManager; 47 private final LooperExecutor mMainThreadExecutor; 48 private final ActivityManagerWrapper mActivityManagerWrapper; 49 50 // The list change id, increments as the task list changes in the system 51 private int mChangeId; 52 // The last change id when the list was last loaded completely, must be <= the list change id 53 private int mLastLoadedId; 54 // The last change id was loaded with keysOnly = true 55 private boolean mLastLoadHadKeysOnly; 56 57 ArrayList<Task> mTasks = new ArrayList<>(); 58 RecentTasksList(LooperExecutor mainThreadExecutor, KeyguardManagerCompat keyguardManager, ActivityManagerWrapper activityManagerWrapper)59 public RecentTasksList(LooperExecutor mainThreadExecutor, 60 KeyguardManagerCompat keyguardManager, ActivityManagerWrapper activityManagerWrapper) { 61 mMainThreadExecutor = mainThreadExecutor; 62 mKeyguardManager = keyguardManager; 63 mChangeId = 1; 64 mActivityManagerWrapper = activityManagerWrapper; 65 mActivityManagerWrapper.registerTaskStackListener(this); 66 } 67 68 /** 69 * Fetches the task keys skipping any local cache. 70 */ getTaskKeys(int numTasks, Consumer<ArrayList<Task>> callback)71 public void getTaskKeys(int numTasks, Consumer<ArrayList<Task>> callback) { 72 // Kick off task loading in the background 73 UI_HELPER_EXECUTOR.execute(() -> { 74 ArrayList<Task> tasks = loadTasksInBackground(numTasks, true /* loadKeysOnly */); 75 mMainThreadExecutor.execute(() -> callback.accept(tasks)); 76 }); 77 } 78 79 /** 80 * Asynchronously fetches the list of recent tasks, reusing cached list if available. 81 * 82 * @param loadKeysOnly Whether to load other associated task data, or just the key 83 * @param callback The callback to receive the list of recent tasks 84 * @return The change id of the current task list 85 */ getTasks(boolean loadKeysOnly, Consumer<ArrayList<Task>> callback)86 public synchronized int getTasks(boolean loadKeysOnly, Consumer<ArrayList<Task>> callback) { 87 final int requestLoadId = mChangeId; 88 Runnable resultCallback = callback == null 89 ? () -> { } 90 : () -> callback.accept(copyOf(mTasks)); 91 92 if (mLastLoadedId == mChangeId && (!mLastLoadHadKeysOnly || loadKeysOnly)) { 93 // The list is up to date, send the callback on the next frame, 94 // so that requestID can be returned first. 95 mMainThreadExecutor.post(resultCallback); 96 return requestLoadId; 97 } 98 99 // Kick off task loading in the background 100 UI_HELPER_EXECUTOR.execute(() -> { 101 ArrayList<Task> tasks = loadTasksInBackground(Integer.MAX_VALUE, loadKeysOnly); 102 103 mMainThreadExecutor.execute(() -> { 104 mTasks = tasks; 105 mLastLoadedId = requestLoadId; 106 mLastLoadHadKeysOnly = loadKeysOnly; 107 resultCallback.run(); 108 }); 109 }); 110 111 return requestLoadId; 112 } 113 114 /** 115 * @return Whether the provided {@param changeId} is the latest recent tasks list id. 116 */ isTaskListValid(int changeId)117 public synchronized boolean isTaskListValid(int changeId) { 118 return mChangeId == changeId; 119 } 120 121 @Override onTaskStackChanged()122 public synchronized void onTaskStackChanged() { 123 mChangeId++; 124 } 125 126 @Override onTaskRemoved(int taskId)127 public void onTaskRemoved(int taskId) { 128 mTasks = loadTasksInBackground(Integer.MAX_VALUE, false); 129 } 130 131 @Override onActivityPinned(String packageName, int userId, int taskId, int stackId)132 public synchronized void onActivityPinned(String packageName, int userId, int taskId, 133 int stackId) { 134 mChangeId++; 135 } 136 137 @Override onActivityUnpinned()138 public synchronized void onActivityUnpinned() { 139 mChangeId++; 140 } 141 142 /** 143 * Loads and creates a list of all the recent tasks. 144 */ 145 @VisibleForTesting loadTasksInBackground(int numTasks, boolean loadKeysOnly)146 ArrayList<Task> loadTasksInBackground(int numTasks, 147 boolean loadKeysOnly) { 148 int currentUserId = Process.myUserHandle().getIdentifier(); 149 ArrayList<Task> allTasks = new ArrayList<>(); 150 List<ActivityManager.RecentTaskInfo> rawTasks = 151 mActivityManagerWrapper.getRecentTasks(numTasks, currentUserId); 152 // The raw tasks are given in most-recent to least-recent order, we need to reverse it 153 Collections.reverse(rawTasks); 154 155 SparseBooleanArray tmpLockedUsers = new SparseBooleanArray() { 156 @Override 157 public boolean get(int key) { 158 if (indexOfKey(key) < 0) { 159 // Fill the cached locked state as we fetch 160 put(key, mKeyguardManager.isDeviceLocked(key)); 161 } 162 return super.get(key); 163 } 164 }; 165 166 int taskCount = rawTasks.size(); 167 for (int i = 0; i < taskCount; i++) { 168 ActivityManager.RecentTaskInfo rawTask = rawTasks.get(i); 169 Task.TaskKey taskKey = new Task.TaskKey(rawTask); 170 Task task; 171 if (!loadKeysOnly) { 172 boolean isLocked = tmpLockedUsers.get(taskKey.userId); 173 task = Task.from(taskKey, rawTask, isLocked); 174 } else { 175 task = new Task(taskKey); 176 } 177 allTasks.add(task); 178 } 179 180 return allTasks; 181 } 182 copyOf(ArrayList<Task> tasks)183 private ArrayList<Task> copyOf(ArrayList<Task> tasks) { 184 ArrayList<Task> newTasks = new ArrayList<>(); 185 for (int i = 0; i < tasks.size(); i++) { 186 Task t = tasks.get(i); 187 newTasks.add(new Task(t.key, t.colorPrimary, t.colorBackground, t.isDockable, 188 t.isLocked, t.taskDescription, t.topActivity)); 189 } 190 return newTasks; 191 } 192 }