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 android.annotation.TargetApi; 20 import android.app.ActivityManager; 21 import android.content.Context; 22 import android.os.Build; 23 import android.os.Process; 24 import android.util.SparseBooleanArray; 25 import com.android.launcher3.MainThreadExecutor; 26 import com.android.systemui.shared.recents.model.Task; 27 import com.android.systemui.shared.system.ActivityManagerWrapper; 28 import com.android.systemui.shared.system.BackgroundExecutor; 29 import com.android.systemui.shared.system.KeyguardManagerCompat; 30 import com.android.systemui.shared.system.RecentTaskInfoCompat; 31 import com.android.systemui.shared.system.TaskDescriptionCompat; 32 import com.android.systemui.shared.system.TaskStackChangeListener; 33 import java.util.ArrayList; 34 import java.util.Collections; 35 import java.util.List; 36 import java.util.function.Consumer; 37 38 /** 39 * Manages the recent task list from the system, caching it as necessary. 40 */ 41 @TargetApi(Build.VERSION_CODES.P) 42 public class RecentTasksList extends TaskStackChangeListener { 43 44 private final KeyguardManagerCompat mKeyguardManager; 45 private final MainThreadExecutor mMainThreadExecutor; 46 private final BackgroundExecutor mBgThreadExecutor; 47 48 // The list change id, increments as the task list changes in the system 49 private int mChangeId; 50 // The last change id when the list was last loaded completely, must be <= the list change id 51 private int mLastLoadedId; 52 // The last change id was loaded with keysOnly = true 53 private boolean mLastLoadHadKeysOnly; 54 55 ArrayList<Task> mTasks = new ArrayList<>(); 56 RecentTasksList(Context context)57 public RecentTasksList(Context context) { 58 mMainThreadExecutor = new MainThreadExecutor(); 59 mBgThreadExecutor = BackgroundExecutor.get(); 60 mKeyguardManager = new KeyguardManagerCompat(context); 61 mChangeId = 1; 62 ActivityManagerWrapper.getInstance().registerTaskStackListener(this); 63 } 64 65 /** 66 * Fetches the task keys skipping any local cache. 67 */ getTaskKeys(int numTasks, Consumer<ArrayList<Task>> callback)68 public void getTaskKeys(int numTasks, Consumer<ArrayList<Task>> callback) { 69 // Kick off task loading in the background 70 mBgThreadExecutor.submit(() -> { 71 ArrayList<Task> tasks = loadTasksInBackground(numTasks, true /* loadKeysOnly */); 72 mMainThreadExecutor.execute(() -> callback.accept(tasks)); 73 }); 74 } 75 76 /** 77 * Asynchronously fetches the list of recent tasks, reusing cached list if available. 78 * 79 * @param loadKeysOnly Whether to load other associated task data, or just the key 80 * @param callback The callback to receive the list of recent tasks 81 * @return The change id of the current task list 82 */ getTasks(boolean loadKeysOnly, Consumer<ArrayList<Task>> callback)83 public synchronized int getTasks(boolean loadKeysOnly, Consumer<ArrayList<Task>> callback) { 84 final int requestLoadId = mChangeId; 85 Runnable resultCallback = callback == null 86 ? () -> { } 87 : () -> callback.accept(copyOf(mTasks)); 88 89 if (mLastLoadedId == mChangeId && (!mLastLoadHadKeysOnly || loadKeysOnly)) { 90 // The list is up to date, callback with the same list 91 mMainThreadExecutor.execute(resultCallback); 92 return requestLoadId; 93 } 94 95 // Kick off task loading in the background 96 mBgThreadExecutor.submit(() -> { 97 ArrayList<Task> tasks = loadTasksInBackground(Integer.MAX_VALUE, loadKeysOnly); 98 99 mMainThreadExecutor.execute(() -> { 100 mTasks = tasks; 101 mLastLoadedId = requestLoadId; 102 mLastLoadHadKeysOnly = loadKeysOnly; 103 resultCallback.run(); 104 }); 105 }); 106 107 return requestLoadId; 108 } 109 110 /** 111 * @return Whether the provided {@param changeId} is the latest recent tasks list id. 112 */ isTaskListValid(int changeId)113 public synchronized boolean isTaskListValid(int changeId) { 114 return mChangeId == changeId; 115 } 116 117 @Override onTaskStackChanged()118 public synchronized void onTaskStackChanged() { 119 mChangeId++; 120 } 121 122 @Override onTaskRemoved(int taskId)123 public void onTaskRemoved(int taskId) { 124 for (int i = mTasks.size() - 1; i >= 0; i--) { 125 if (mTasks.get(i).key.id == taskId) { 126 mTasks.remove(i); 127 return; 128 } 129 } 130 } 131 132 @Override onActivityPinned(String packageName, int userId, int taskId, int stackId)133 public synchronized void onActivityPinned(String packageName, int userId, int taskId, 134 int stackId) { 135 mChangeId++; 136 } 137 138 @Override onActivityUnpinned()139 public synchronized void onActivityUnpinned() { 140 mChangeId++; 141 } 142 143 /** 144 * Loads and creates a list of all the recent tasks. 145 */ loadTasksInBackground(int numTasks, boolean loadKeysOnly)146 private 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 ActivityManagerWrapper.getInstance().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 RecentTaskInfoCompat t = new RecentTaskInfoCompat(rawTask); 170 Task.TaskKey taskKey = new Task.TaskKey(rawTask); 171 Task task; 172 if (!loadKeysOnly) { 173 ActivityManager.TaskDescription rawTd = t.getTaskDescription(); 174 TaskDescriptionCompat td = new TaskDescriptionCompat(rawTd); 175 boolean isLocked = tmpLockedUsers.get(t.getUserId()); 176 task = new Task(taskKey, td.getPrimaryColor(), td.getBackgroundColor(), 177 t.supportsSplitScreenMultiWindow(), isLocked, rawTd, t.getTopActivity()); 178 } else { 179 task = new Task(taskKey); 180 } 181 allTasks.add(task); 182 } 183 184 return allTasks; 185 } 186 copyOf(ArrayList<Task> tasks)187 private ArrayList<Task> copyOf(ArrayList<Task> tasks) { 188 ArrayList<Task> newTasks = new ArrayList<>(); 189 for (int i = 0; i < tasks.size(); i++) { 190 Task t = tasks.get(i); 191 newTasks.add(new Task(t.key, t.colorPrimary, t.colorBackground, t.isDockable, 192 t.isLocked, t.taskDescription, t.topActivity)); 193 } 194 return newTasks; 195 } 196 }