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