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.systemui.shared.recents.model; 18 19 import android.content.ComponentName; 20 import android.util.ArrayMap; 21 import android.util.ArraySet; 22 23 import com.android.systemui.shared.recents.model.Task.TaskKey; 24 import com.android.systemui.shared.recents.utilities.AnimationProps; 25 import com.android.systemui.shared.system.PackageManagerWrapper; 26 27 import java.io.PrintWriter; 28 import java.util.ArrayList; 29 import java.util.List; 30 31 32 /** 33 * The task stack contains a list of multiple tasks. 34 */ 35 public class TaskStack { 36 37 private static final String TAG = "TaskStack"; 38 39 /** Task stack callbacks */ 40 public interface TaskStackCallbacks { 41 /** 42 * Notifies when a new task has been added to the stack. 43 */ onStackTaskAdded(TaskStack stack, Task newTask)44 void onStackTaskAdded(TaskStack stack, Task newTask); 45 46 /** 47 * Notifies when a task has been removed from the stack. 48 */ onStackTaskRemoved(TaskStack stack, Task removedTask, Task newFrontMostTask, AnimationProps animation, boolean fromDockGesture, boolean dismissRecentsIfAllRemoved)49 void onStackTaskRemoved(TaskStack stack, Task removedTask, Task newFrontMostTask, 50 AnimationProps animation, boolean fromDockGesture, 51 boolean dismissRecentsIfAllRemoved); 52 53 /** 54 * Notifies when all tasks have been removed from the stack. 55 */ onStackTasksRemoved(TaskStack stack)56 void onStackTasksRemoved(TaskStack stack); 57 58 /** 59 * Notifies when tasks in the stack have been updated. 60 */ onStackTasksUpdated(TaskStack stack)61 void onStackTasksUpdated(TaskStack stack); 62 } 63 64 private final ArrayList<Task> mRawTaskList = new ArrayList<>(); 65 private final FilteredTaskList mStackTaskList = new FilteredTaskList(); 66 private TaskStackCallbacks mCb; 67 TaskStack()68 public TaskStack() { 69 // Ensure that we only show stack tasks 70 mStackTaskList.setFilter((taskIdMap, t, index) -> t.isStackTask); 71 } 72 73 /** Sets the callbacks for this task stack. */ setCallbacks(TaskStackCallbacks cb)74 public void setCallbacks(TaskStackCallbacks cb) { 75 mCb = cb; 76 } 77 78 /** 79 * Removes a task from the stack, with an additional {@param animation} hint to the callbacks on 80 * how they should update themselves. 81 */ removeTask(Task t, AnimationProps animation, boolean fromDockGesture)82 public void removeTask(Task t, AnimationProps animation, boolean fromDockGesture) { 83 removeTask(t, animation, fromDockGesture, true /* dismissRecentsIfAllRemoved */); 84 } 85 86 /** 87 * Removes a task from the stack, with an additional {@param animation} hint to the callbacks on 88 * how they should update themselves. 89 */ removeTask(Task t, AnimationProps animation, boolean fromDockGesture, boolean dismissRecentsIfAllRemoved)90 public void removeTask(Task t, AnimationProps animation, boolean fromDockGesture, 91 boolean dismissRecentsIfAllRemoved) { 92 if (mStackTaskList.contains(t)) { 93 mStackTaskList.remove(t); 94 Task newFrontMostTask = getFrontMostTask(); 95 if (mCb != null) { 96 // Notify that a task has been removed 97 mCb.onStackTaskRemoved(this, t, newFrontMostTask, animation, 98 fromDockGesture, dismissRecentsIfAllRemoved); 99 } 100 } 101 mRawTaskList.remove(t); 102 } 103 104 /** 105 * Removes all tasks from the stack. 106 */ removeAllTasks(boolean notifyStackChanges)107 public void removeAllTasks(boolean notifyStackChanges) { 108 ArrayList<Task> tasks = mStackTaskList.getTasks(); 109 for (int i = tasks.size() - 1; i >= 0; i--) { 110 Task t = tasks.get(i); 111 mStackTaskList.remove(t); 112 mRawTaskList.remove(t); 113 } 114 if (mCb != null && notifyStackChanges) { 115 // Notify that all tasks have been removed 116 mCb.onStackTasksRemoved(this); 117 } 118 } 119 120 121 /** 122 * @see #setTasks(List, boolean) 123 */ setTasks(TaskStack stack, boolean notifyStackChanges)124 public void setTasks(TaskStack stack, boolean notifyStackChanges) { 125 setTasks(stack.mRawTaskList, notifyStackChanges); 126 } 127 128 /** 129 * Sets a few tasks in one go, without calling any callbacks. 130 * 131 * @param tasks the new set of tasks to replace the current set. 132 * @param notifyStackChanges whether or not to callback on specific changes to the list of tasks. 133 */ setTasks(List<Task> tasks, boolean notifyStackChanges)134 public void setTasks(List<Task> tasks, boolean notifyStackChanges) { 135 // Compute a has set for each of the tasks 136 ArrayMap<TaskKey, Task> currentTasksMap = createTaskKeyMapFromList(mRawTaskList); 137 ArrayMap<TaskKey, Task> newTasksMap = createTaskKeyMapFromList(tasks); 138 ArrayList<Task> addedTasks = new ArrayList<>(); 139 ArrayList<Task> removedTasks = new ArrayList<>(); 140 ArrayList<Task> allTasks = new ArrayList<>(); 141 142 // Disable notifications if there are no callbacks 143 if (mCb == null) { 144 notifyStackChanges = false; 145 } 146 147 // Remove any tasks that no longer exist 148 int taskCount = mRawTaskList.size(); 149 for (int i = taskCount - 1; i >= 0; i--) { 150 Task task = mRawTaskList.get(i); 151 if (!newTasksMap.containsKey(task.key)) { 152 if (notifyStackChanges) { 153 removedTasks.add(task); 154 } 155 } 156 } 157 158 // Add any new tasks 159 taskCount = tasks.size(); 160 for (int i = 0; i < taskCount; i++) { 161 Task newTask = tasks.get(i); 162 Task currentTask = currentTasksMap.get(newTask.key); 163 if (currentTask == null && notifyStackChanges) { 164 addedTasks.add(newTask); 165 } else if (currentTask != null) { 166 // The current task has bound callbacks, so just copy the data from the new task 167 // state and add it back into the list 168 currentTask.copyFrom(newTask); 169 newTask = currentTask; 170 } 171 allTasks.add(newTask); 172 } 173 174 // Sort all the tasks to ensure they are ordered correctly 175 for (int i = allTasks.size() - 1; i >= 0; i--) { 176 allTasks.get(i).temporarySortIndexInStack = i; 177 } 178 179 mStackTaskList.set(allTasks); 180 mRawTaskList.clear(); 181 mRawTaskList.addAll(allTasks); 182 183 // Only callback for the removed tasks after the stack has updated 184 int removedTaskCount = removedTasks.size(); 185 Task newFrontMostTask = getFrontMostTask(); 186 for (int i = 0; i < removedTaskCount; i++) { 187 mCb.onStackTaskRemoved(this, removedTasks.get(i), newFrontMostTask, 188 AnimationProps.IMMEDIATE, false /* fromDockGesture */, 189 true /* dismissRecentsIfAllRemoved */); 190 } 191 192 // Only callback for the newly added tasks after this stack has been updated 193 int addedTaskCount = addedTasks.size(); 194 for (int i = 0; i < addedTaskCount; i++) { 195 mCb.onStackTaskAdded(this, addedTasks.get(i)); 196 } 197 198 // Notify that the task stack has been updated 199 if (notifyStackChanges) { 200 mCb.onStackTasksUpdated(this); 201 } 202 } 203 204 /** 205 * Gets the front-most task in the stack. 206 */ getFrontMostTask()207 public Task getFrontMostTask() { 208 ArrayList<Task> stackTasks = mStackTaskList.getTasks(); 209 if (stackTasks.isEmpty()) { 210 return null; 211 } 212 return stackTasks.get(stackTasks.size() - 1); 213 } 214 215 /** Gets the task keys */ getTaskKeys()216 public ArrayList<TaskKey> getTaskKeys() { 217 ArrayList<TaskKey> taskKeys = new ArrayList<>(); 218 ArrayList<Task> tasks = computeAllTasksList(); 219 int taskCount = tasks.size(); 220 for (int i = 0; i < taskCount; i++) { 221 Task task = tasks.get(i); 222 taskKeys.add(task.key); 223 } 224 return taskKeys; 225 } 226 227 /** 228 * Returns the set of "active" (non-historical) tasks in the stack that have been used recently. 229 */ getTasks()230 public ArrayList<Task> getTasks() { 231 return mStackTaskList.getTasks(); 232 } 233 234 /** 235 * Computes a set of all the active and historical tasks. 236 */ computeAllTasksList()237 public ArrayList<Task> computeAllTasksList() { 238 ArrayList<Task> tasks = new ArrayList<>(); 239 tasks.addAll(mStackTaskList.getTasks()); 240 return tasks; 241 } 242 243 /** 244 * Returns the number of stack tasks. 245 */ getTaskCount()246 public int getTaskCount() { 247 return mStackTaskList.size(); 248 } 249 250 /** 251 * Returns the task in stack tasks which is the launch target. 252 */ getLaunchTarget()253 public Task getLaunchTarget() { 254 ArrayList<Task> tasks = mStackTaskList.getTasks(); 255 int taskCount = tasks.size(); 256 for (int i = 0; i < taskCount; i++) { 257 Task task = tasks.get(i); 258 if (task.isLaunchTarget) { 259 return task; 260 } 261 } 262 return null; 263 } 264 265 /** 266 * Returns whether the next launch target should actually be the PiP task. 267 */ isNextLaunchTargetPip(long lastPipTime)268 public boolean isNextLaunchTargetPip(long lastPipTime) { 269 Task launchTarget = getLaunchTarget(); 270 Task nextLaunchTarget = getNextLaunchTargetRaw(); 271 if (nextLaunchTarget != null && lastPipTime > 0) { 272 // If the PiP time is more recent than the next launch target, then launch the PiP task 273 return lastPipTime > nextLaunchTarget.key.lastActiveTime; 274 } else if (launchTarget != null && lastPipTime > 0 && getTaskCount() == 1) { 275 // Otherwise, if there is no next launch target, but there is a PiP, then launch 276 // the PiP task 277 return true; 278 } 279 return false; 280 } 281 282 /** 283 * Returns the task in stack tasks which should be launched next if Recents are toggled 284 * again, or null if there is no task to be launched. Callers should check 285 * {@link #isNextLaunchTargetPip(long)} before fetching the next raw launch target from the 286 * stack. 287 */ getNextLaunchTarget()288 public Task getNextLaunchTarget() { 289 Task nextLaunchTarget = getNextLaunchTargetRaw(); 290 if (nextLaunchTarget != null) { 291 return nextLaunchTarget; 292 } 293 return getTasks().get(getTaskCount() - 1); 294 } 295 getNextLaunchTargetRaw()296 private Task getNextLaunchTargetRaw() { 297 int taskCount = getTaskCount(); 298 if (taskCount == 0) { 299 return null; 300 } 301 int launchTaskIndex = indexOfTask(getLaunchTarget()); 302 if (launchTaskIndex != -1 && launchTaskIndex > 0) { 303 return getTasks().get(launchTaskIndex - 1); 304 } 305 return null; 306 } 307 308 /** Returns the index of this task in this current task stack */ indexOfTask(Task t)309 public int indexOfTask(Task t) { 310 return mStackTaskList.indexOf(t); 311 } 312 313 /** Finds the task with the specified task id. */ findTaskWithId(int taskId)314 public Task findTaskWithId(int taskId) { 315 ArrayList<Task> tasks = computeAllTasksList(); 316 int taskCount = tasks.size(); 317 for (int i = 0; i < taskCount; i++) { 318 Task task = tasks.get(i); 319 if (task.key.id == taskId) { 320 return task; 321 } 322 } 323 return null; 324 } 325 326 /** 327 * Computes the components of tasks in this stack that have been removed as a result of a change 328 * in the specified package. 329 */ computeComponentsRemoved(String packageName, int userId)330 public ArraySet<ComponentName> computeComponentsRemoved(String packageName, int userId) { 331 // Identify all the tasks that should be removed as a result of the package being removed. 332 // Using a set to ensure that we callback once per unique component. 333 ArraySet<ComponentName> existingComponents = new ArraySet<>(); 334 ArraySet<ComponentName> removedComponents = new ArraySet<>(); 335 ArrayList<TaskKey> taskKeys = getTaskKeys(); 336 int taskKeyCount = taskKeys.size(); 337 for (int i = 0; i < taskKeyCount; i++) { 338 TaskKey t = taskKeys.get(i); 339 340 // Skip if this doesn't apply to the current user 341 if (t.userId != userId) continue; 342 343 ComponentName cn = t.getComponent(); 344 if (cn.getPackageName().equals(packageName)) { 345 if (existingComponents.contains(cn)) { 346 // If we know that the component still exists in the package, then skip 347 continue; 348 } 349 if (PackageManagerWrapper.getInstance().getActivityInfo(cn, userId) != null) { 350 existingComponents.add(cn); 351 } else { 352 removedComponents.add(cn); 353 } 354 } 355 } 356 return removedComponents; 357 } 358 359 @Override toString()360 public String toString() { 361 String str = "Stack Tasks (" + mStackTaskList.size() + "):\n"; 362 ArrayList<Task> tasks = mStackTaskList.getTasks(); 363 int taskCount = tasks.size(); 364 for (int i = 0; i < taskCount; i++) { 365 str += " " + tasks.get(i).toString() + "\n"; 366 } 367 return str; 368 } 369 370 /** 371 * Given a list of tasks, returns a map of each task's key to the task. 372 */ createTaskKeyMapFromList(List<Task> tasks)373 private ArrayMap<TaskKey, Task> createTaskKeyMapFromList(List<Task> tasks) { 374 ArrayMap<TaskKey, Task> map = new ArrayMap<>(tasks.size()); 375 int taskCount = tasks.size(); 376 for (int i = 0; i < taskCount; i++) { 377 Task task = tasks.get(i); 378 map.put(task.key, task); 379 } 380 return map; 381 } 382 dump(String prefix, PrintWriter writer)383 public void dump(String prefix, PrintWriter writer) { 384 String innerPrefix = prefix + " "; 385 386 writer.print(prefix); writer.print(TAG); 387 writer.print(" numStackTasks="); writer.print(mStackTaskList.size()); 388 writer.println(); 389 ArrayList<Task> tasks = mStackTaskList.getTasks(); 390 int taskCount = tasks.size(); 391 for (int i = 0; i < taskCount; i++) { 392 tasks.get(i).dump(innerPrefix, writer); 393 } 394 } 395 } 396