TaskStack.java revision 9756755db76aeda2065322aa3c26e1a19578d45f
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 17package com.android.systemui.recents.model; 18 19import android.animation.ObjectAnimator; 20import android.content.ComponentName; 21import android.content.Context; 22import android.graphics.Color; 23import android.graphics.Rect; 24import android.graphics.RectF; 25import android.graphics.drawable.ColorDrawable; 26import android.util.SparseArray; 27import com.android.systemui.R; 28import com.android.systemui.recents.Recents; 29import com.android.systemui.recents.RecentsDebugFlags; 30import com.android.systemui.recents.misc.NamedCounter; 31import com.android.systemui.recents.misc.SystemServicesProxy; 32import com.android.systemui.recents.misc.Utilities; 33import com.android.systemui.recents.views.DropTarget; 34 35import java.util.ArrayList; 36import java.util.Collections; 37import java.util.Comparator; 38import java.util.HashMap; 39import java.util.HashSet; 40import java.util.List; 41import java.util.Random; 42 43import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT; 44import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT; 45import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID; 46import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID; 47 48 49/** 50 * An interface for a task filter to query whether a particular task should show in a stack. 51 */ 52interface TaskFilter { 53 /** Returns whether the filter accepts the specified task */ 54 public boolean acceptTask(SparseArray<Task> taskIdMap, Task t, int index); 55} 56 57/** 58 * A list of filtered tasks. 59 */ 60class FilteredTaskList { 61 62 private static final String TAG = "FilteredTaskList"; 63 private static final boolean DEBUG = false; 64 65 ArrayList<Task> mTasks = new ArrayList<>(); 66 ArrayList<Task> mFilteredTasks = new ArrayList<>(); 67 HashMap<Task.TaskKey, Integer> mTaskIndices = new HashMap<>(); 68 TaskFilter mFilter; 69 70 /** Sets the task filter, saving the current touch state */ 71 boolean setFilter(TaskFilter filter) { 72 ArrayList<Task> prevFilteredTasks = new ArrayList<Task>(mFilteredTasks); 73 mFilter = filter; 74 updateFilteredTasks(); 75 if (!prevFilteredTasks.equals(mFilteredTasks)) { 76 return true; 77 } else { 78 return false; 79 } 80 } 81 82 /** 83 * Resets the task list, but does not remove the filter. 84 */ 85 void reset() { 86 mTasks.clear(); 87 mFilteredTasks.clear(); 88 mTaskIndices.clear(); 89 } 90 91 /** Removes the task filter and returns the previous touch state */ 92 void removeFilter() { 93 mFilter = null; 94 updateFilteredTasks(); 95 } 96 97 /** Adds a new task to the task list */ 98 void add(Task t) { 99 mTasks.add(t); 100 updateFilteredTasks(); 101 } 102 103 /** 104 * Moves the given task. 105 */ 106 public void moveTaskToStack(Task task, int insertIndex, int newStackId) { 107 int taskIndex = indexOf(task); 108 if (taskIndex != insertIndex) { 109 mTasks.remove(taskIndex); 110 if (taskIndex < insertIndex) { 111 insertIndex--; 112 } 113 mTasks.add(insertIndex, task); 114 } 115 116 // Update the stack id now, after we've moved the task, and before we update the 117 // filtered tasks 118 task.setStackId(newStackId); 119 updateFilteredTasks(); 120 } 121 122 /** Sets the list of tasks */ 123 void set(List<Task> tasks) { 124 mTasks.clear(); 125 mTasks.addAll(tasks); 126 updateFilteredTasks(); 127 } 128 129 /** Removes a task from the base list only if it is in the filtered list */ 130 boolean remove(Task t) { 131 if (mFilteredTasks.contains(t)) { 132 boolean removed = mTasks.remove(t); 133 updateFilteredTasks(); 134 return removed; 135 } 136 return false; 137 } 138 139 /** Returns the index of this task in the list of filtered tasks */ 140 int indexOf(Task t) { 141 if (t != null && mTaskIndices.containsKey(t.key)) { 142 return mTaskIndices.get(t.key); 143 } 144 return -1; 145 } 146 147 /** Returns the size of the list of filtered tasks */ 148 int size() { 149 return mFilteredTasks.size(); 150 } 151 152 /** Returns whether the filtered list contains this task */ 153 boolean contains(Task t) { 154 return mTaskIndices.containsKey(t.key); 155 } 156 157 /** Updates the list of filtered tasks whenever the base task list changes */ 158 private void updateFilteredTasks() { 159 mFilteredTasks.clear(); 160 if (mFilter != null) { 161 // Create a sparse array from task id to Task 162 SparseArray<Task> taskIdMap = new SparseArray<>(); 163 int taskCount = mTasks.size(); 164 for (int i = 0; i < taskCount; i++) { 165 Task t = mTasks.get(i); 166 taskIdMap.put(t.key.id, t); 167 } 168 169 for (int i = 0; i < taskCount; i++) { 170 Task t = mTasks.get(i); 171 if (mFilter.acceptTask(taskIdMap, t, i)) { 172 mFilteredTasks.add(t); 173 } 174 } 175 } else { 176 mFilteredTasks.addAll(mTasks); 177 } 178 updateFilteredTaskIndices(); 179 } 180 181 /** Updates the mapping of tasks to indices. */ 182 private void updateFilteredTaskIndices() { 183 mTaskIndices.clear(); 184 int taskCount = mFilteredTasks.size(); 185 for (int i = 0; i < taskCount; i++) { 186 Task t = mFilteredTasks.get(i); 187 mTaskIndices.put(t.key, i); 188 } 189 } 190 191 /** Returns whether this task list is filtered */ 192 boolean hasFilter() { 193 return (mFilter != null); 194 } 195 196 /** Returns the list of filtered tasks */ 197 ArrayList<Task> getTasks() { 198 return mFilteredTasks; 199 } 200} 201 202/** 203 * The task stack contains a list of multiple tasks. 204 */ 205public class TaskStack { 206 207 /** Task stack callbacks */ 208 public interface TaskStackCallbacks { 209 /** 210 * Notifies when a new task has been added to the stack. 211 */ 212 void onStackTaskAdded(TaskStack stack, Task newTask); 213 214 /** 215 * Notifies when a task has been removed from the stack. 216 */ 217 void onStackTaskRemoved(TaskStack stack, Task removedTask, boolean wasFrontMostTask, 218 Task newFrontMostTask); 219 220 /** 221 * Notifies when a task has been removed from the history. 222 */ 223 void onHistoryTaskRemoved(TaskStack stack, Task removedTask); 224 } 225 226 /** 227 * The various possible dock states when dragging and dropping a task. 228 */ 229 public static class DockState implements DropTarget { 230 231 private static final int DOCK_AREA_ALPHA = 192; 232 public static final DockState NONE = new DockState(-1, 96, null, null); 233 public static final DockState LEFT = new DockState( 234 DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT, DOCK_AREA_ALPHA, 235 new RectF(0, 0, 0.25f, 1), new RectF(0, 0, 0.25f, 1)); 236 public static final DockState TOP = new DockState( 237 DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT, DOCK_AREA_ALPHA, 238 new RectF(0, 0, 1, 0.25f), new RectF(0, 0, 1, 0.25f)); 239 public static final DockState RIGHT = new DockState( 240 DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT, DOCK_AREA_ALPHA, 241 new RectF(0.75f, 0, 1, 1), new RectF(0.75f, 0, 1, 1)); 242 public static final DockState BOTTOM = new DockState( 243 DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT, DOCK_AREA_ALPHA, 244 new RectF(0, 0.75f, 1, 1), new RectF(0, 0.75f, 1, 1)); 245 246 @Override 247 public boolean acceptsDrop(int x, int y, int width, int height) { 248 return touchAreaContainsPoint(width, height, x, y); 249 } 250 251 // Represents the view state of this dock state 252 public class ViewState { 253 public final int dockAreaAlpha; 254 public final ColorDrawable dockAreaOverlay; 255 private ObjectAnimator dockAreaOverlayAnimator; 256 257 private ViewState(int alpha) { 258 dockAreaAlpha = alpha; 259 dockAreaOverlay = new ColorDrawable(0xFFffffff); 260 dockAreaOverlay.setAlpha(0); 261 } 262 263 /** 264 * Creates a new alpha animation. 265 */ 266 public void startAlphaAnimation(int alpha, int duration) { 267 if (dockAreaOverlay.getAlpha() != alpha) { 268 if (dockAreaOverlayAnimator != null) { 269 dockAreaOverlayAnimator.cancel(); 270 } 271 dockAreaOverlayAnimator = ObjectAnimator.ofInt(dockAreaOverlay, "alpha", alpha); 272 dockAreaOverlayAnimator.setDuration(duration); 273 dockAreaOverlayAnimator.start(); 274 } 275 } 276 } 277 278 public final int createMode; 279 public final ViewState viewState; 280 private final RectF dockArea; 281 private final RectF touchArea; 282 283 /** 284 * @param createMode used to pass to ActivityManager to dock the task 285 * @param touchArea the area in which touch will initiate this dock state 286 * @param dockArea the visible dock area 287 */ 288 DockState(int createMode, int dockAreaAlpha, RectF touchArea, RectF dockArea) { 289 this.createMode = createMode; 290 this.viewState = new ViewState(dockAreaAlpha); 291 this.dockArea = dockArea; 292 this.touchArea = touchArea; 293 } 294 295 /** 296 * Returns whether {@param x} and {@param y} are contained in the touch area scaled to the 297 * given {@param width} and {@param height}. 298 */ 299 public boolean touchAreaContainsPoint(int width, int height, float x, float y) { 300 int left = (int) (touchArea.left * width); 301 int top = (int) (touchArea.top * height); 302 int right = (int) (touchArea.right * width); 303 int bottom = (int) (touchArea.bottom * height); 304 return x >= left && y >= top && x <= right && y <= bottom; 305 } 306 307 /** 308 * Returns the docked task bounds with the given {@param width} and {@param height}. 309 */ 310 public Rect getDockedBounds(int width, int height) { 311 return new Rect((int) (dockArea.left * width), (int) (dockArea.top * height), 312 (int) (dockArea.right * width), (int) (dockArea.bottom * height)); 313 } 314 } 315 316 // A comparator that sorts tasks by their last active time 317 private Comparator<Task> LAST_ACTIVE_TIME_COMPARATOR = new Comparator<Task>() { 318 @Override 319 public int compare(Task o1, Task o2) { 320 return Long.compare(o1.key.lastActiveTime, o2.key.lastActiveTime); 321 } 322 }; 323 324 // The task offset to apply to a task id as a group affiliation 325 static final int IndividualTaskIdOffset = 1 << 16; 326 327 ArrayList<Task> mRawTaskList = new ArrayList<>(); 328 FilteredTaskList mStackTaskList = new FilteredTaskList(); 329 FilteredTaskList mHistoryTaskList = new FilteredTaskList(); 330 TaskStackCallbacks mCb; 331 332 ArrayList<TaskGrouping> mGroups = new ArrayList<>(); 333 HashMap<Integer, TaskGrouping> mAffinitiesGroups = new HashMap<>(); 334 335 public TaskStack() { 336 // Ensure that we only show non-docked tasks 337 mStackTaskList.setFilter(new TaskFilter() { 338 @Override 339 public boolean acceptTask(SparseArray<Task> taskIdMap, Task t, int index) { 340 if (t.isAffiliatedTask()) { 341 // If this task is affiliated with another parent in the stack, then the historical state of this 342 // task depends on the state of the parent task 343 Task parentTask = taskIdMap.get(t.taskAffiliationId); 344 if (parentTask != null) { 345 t = parentTask; 346 } 347 } 348 return !t.isHistorical && !SystemServicesProxy.isDockedStack(t.key.stackId); 349 } 350 }); 351 mHistoryTaskList.setFilter(new TaskFilter() { 352 @Override 353 public boolean acceptTask(SparseArray<Task> taskIdMap, Task t, int index) { 354 if (t.isAffiliatedTask()) { 355 // If this task is affiliated with another parent in the stack, then the historical state of this 356 // task depends on the state of the parent task 357 Task parentTask = taskIdMap.get(t.taskAffiliationId); 358 if (parentTask != null) { 359 t = parentTask; 360 } 361 } 362 return t.isHistorical && !SystemServicesProxy.isDockedStack(t.key.stackId); 363 } 364 }); 365 } 366 367 /** Sets the callbacks for this task stack. */ 368 public void setCallbacks(TaskStackCallbacks cb) { 369 mCb = cb; 370 } 371 372 /** Resets this TaskStack. */ 373 public void reset() { 374 mCb = null; 375 mStackTaskList.reset(); 376 mHistoryTaskList.reset(); 377 mGroups.clear(); 378 mAffinitiesGroups.clear(); 379 } 380 381 /** 382 * Moves the given task to either the front of the freeform workspace or the stack. 383 */ 384 public void moveTaskToStack(Task task, int newStackId) { 385 // Find the index to insert into 386 ArrayList<Task> taskList = mStackTaskList.getTasks(); 387 int taskCount = taskList.size(); 388 if (!task.isFreeformTask() && (newStackId == FREEFORM_WORKSPACE_STACK_ID)) { 389 // Insert freeform tasks at the front 390 mStackTaskList.moveTaskToStack(task, taskCount, newStackId); 391 } else if (task.isFreeformTask() && (newStackId == FULLSCREEN_WORKSPACE_STACK_ID)) { 392 // Insert after the first stacked task 393 int insertIndex = 0; 394 for (int i = taskCount - 1; i >= 0; i--) { 395 if (!taskList.get(i).isFreeformTask()) { 396 insertIndex = i + 1; 397 break; 398 } 399 } 400 mStackTaskList.moveTaskToStack(task, insertIndex, newStackId); 401 } 402 } 403 404 /** Does the actual work associated with removing the task. */ 405 void removeTaskImpl(FilteredTaskList taskList, Task t) { 406 // Remove the task from the list 407 taskList.remove(t); 408 // Remove it from the group as well, and if it is empty, remove the group 409 TaskGrouping group = t.group; 410 if (group != null) { 411 group.removeTask(t); 412 if (group.getTaskCount() == 0) { 413 removeGroup(group); 414 } 415 } 416 // Update the lock-to-app state 417 t.lockToThisTask = false; 418 } 419 420 /** Removes a task */ 421 public void removeTask(Task t) { 422 if (mStackTaskList.contains(t)) { 423 boolean wasFrontMostTask = (getStackFrontMostTask() == t); 424 removeTaskImpl(mStackTaskList, t); 425 Task newFrontMostTask = getStackFrontMostTask(); 426 if (newFrontMostTask != null && newFrontMostTask.lockToTaskEnabled) { 427 newFrontMostTask.lockToThisTask = true; 428 } 429 if (mCb != null) { 430 // Notify that a task has been removed 431 mCb.onStackTaskRemoved(this, t, wasFrontMostTask, newFrontMostTask); 432 } 433 } else if (mHistoryTaskList.contains(t)) { 434 removeTaskImpl(mHistoryTaskList, t); 435 if (mCb != null) { 436 // Notify that a task has been removed 437 mCb.onHistoryTaskRemoved(this, t); 438 } 439 } 440 } 441 442 /** 443 * Sets a few tasks in one go, without calling any callbacks. 444 * 445 * @param tasks the new set of tasks to replace the current set. 446 * @param notifyStackChanges whether or not to callback on specific changes to the list of tasks. 447 */ 448 public void setTasks(List<Task> tasks, boolean notifyStackChanges) { 449 // Compute a has set for each of the tasks 450 HashMap<Task.TaskKey, Task> currentTasksMap = createTaskKeyMapFromList(mRawTaskList); 451 HashMap<Task.TaskKey, Task> newTasksMap = createTaskKeyMapFromList(tasks); 452 453 ArrayList<Task> newTasks = new ArrayList<>(); 454 455 // Disable notifications if there are no callbacks 456 if (mCb == null) { 457 notifyStackChanges = false; 458 } 459 460 // Remove any tasks that no longer exist 461 int taskCount = mRawTaskList.size(); 462 for (int i = 0; i < taskCount; i++) { 463 Task task = mRawTaskList.get(i); 464 if (!newTasksMap.containsKey(task.key)) { 465 if (notifyStackChanges) { 466 mCb.onStackTaskRemoved(this, task, i == (taskCount - 1), null); 467 } 468 } 469 task.setGroup(null); 470 } 471 472 // Add any new tasks 473 taskCount = tasks.size(); 474 for (int i = 0; i < taskCount; i++) { 475 Task task = tasks.get(i); 476 if (!currentTasksMap.containsKey(task.key)) { 477 if (notifyStackChanges) { 478 mCb.onStackTaskAdded(this, task); 479 } 480 newTasks.add(task); 481 } else { 482 newTasks.add(currentTasksMap.get(task.key)); 483 } 484 } 485 486 // Sort all the tasks to ensure they are ordered correctly 487 Collections.sort(newTasks, LAST_ACTIVE_TIME_COMPARATOR); 488 489 // TODO: Update screen pinning for the new front-most task post refactoring lockToTask out 490 // of the Task 491 492 // Filter out the historical tasks from this new list 493 ArrayList<Task> stackTasks = new ArrayList<>(); 494 ArrayList<Task> historyTasks = new ArrayList<>(); 495 int newTaskCount = newTasks.size(); 496 for (int i = 0; i < newTaskCount; i++) { 497 Task task = newTasks.get(i); 498 if (task.isHistorical) { 499 historyTasks.add(task); 500 } else { 501 stackTasks.add(task); 502 } 503 } 504 505 mStackTaskList.set(stackTasks); 506 mHistoryTaskList.set(historyTasks); 507 mRawTaskList.clear(); 508 mRawTaskList.addAll(newTasks); 509 mGroups.clear(); 510 mAffinitiesGroups.clear(); 511 } 512 513 /** Gets the front task */ 514 public Task getStackFrontMostTask() { 515 if (mStackTaskList.size() == 0) return null; 516 return mStackTaskList.getTasks().get(mStackTaskList.size() - 1); 517 } 518 519 /** Gets the task keys */ 520 public ArrayList<Task.TaskKey> getTaskKeys() { 521 ArrayList<Task.TaskKey> taskKeys = new ArrayList<>(); 522 ArrayList<Task> tasks = computeAllTasksList(); 523 int taskCount = tasks.size(); 524 for (int i = 0; i < taskCount; i++) { 525 Task task = tasks.get(i); 526 taskKeys.add(task.key); 527 } 528 return taskKeys; 529 } 530 531 /** 532 * Returns the set of "active" (non-historical) tasks in the stack that have been used recently. 533 */ 534 public ArrayList<Task> getStackTasks() { 535 return mStackTaskList.getTasks(); 536 } 537 538 /** 539 * Returns the set of tasks that are inactive. These tasks will be presented in a separate 540 * history view. 541 */ 542 public ArrayList<Task> getHistoricalTasks() { 543 return mHistoryTaskList.getTasks(); 544 } 545 546 /** 547 * Computes a set of all the active and historical tasks ordered by their last active time. 548 */ 549 public ArrayList<Task> computeAllTasksList() { 550 ArrayList<Task> tasks = new ArrayList<>(); 551 tasks.addAll(mStackTaskList.getTasks()); 552 tasks.addAll(mHistoryTaskList.getTasks()); 553 Collections.sort(tasks, LAST_ACTIVE_TIME_COMPARATOR); 554 return tasks; 555 } 556 557 /** 558 * Returns the number of tasks in the active stack. 559 */ 560 public int getStackTaskCount() { 561 return mStackTaskList.size(); 562 } 563 564 /** 565 * Returns the number of freeform tasks in the active stack. 566 */ 567 public int getStackTaskFreeformCount() { 568 ArrayList<Task> tasks = mStackTaskList.getTasks(); 569 int freeformCount = 0; 570 int taskCount = tasks.size(); 571 for (int i = 0; i < taskCount; i++) { 572 Task task = tasks.get(i); 573 if (task.isFreeformTask()) { 574 freeformCount++; 575 } 576 } 577 return freeformCount; 578 } 579 580 /** 581 * Returns the task in stack tasks which is the launch target. 582 */ 583 public Task getLaunchTarget() { 584 ArrayList<Task> tasks = mStackTaskList.getTasks(); 585 int taskCount = tasks.size(); 586 for (int i = 0; i < taskCount; i++) { 587 Task task = tasks.get(i); 588 if (task.isLaunchTarget) { 589 return task; 590 } 591 } 592 return null; 593 } 594 595 /** Returns the index of this task in this current task stack */ 596 public int indexOfStackTask(Task t) { 597 return mStackTaskList.indexOf(t); 598 } 599 600 /** Finds the task with the specified task id. */ 601 public Task findTaskWithId(int taskId) { 602 ArrayList<Task> tasks = computeAllTasksList(); 603 for (Task task : tasks) { 604 if (task.key.id == taskId) { 605 return task; 606 } 607 } 608 return null; 609 } 610 611 /******** Grouping ********/ 612 613 /** Adds a group to the set */ 614 public void addGroup(TaskGrouping group) { 615 mGroups.add(group); 616 mAffinitiesGroups.put(group.affiliation, group); 617 } 618 619 public void removeGroup(TaskGrouping group) { 620 mGroups.remove(group); 621 mAffinitiesGroups.remove(group.affiliation); 622 } 623 624 /** Returns the group with the specified affiliation. */ 625 public TaskGrouping getGroupWithAffiliation(int affiliation) { 626 return mAffinitiesGroups.get(affiliation); 627 } 628 629 /** 630 * Temporary: This method will simulate affiliation groups by 631 */ 632 public void createAffiliatedGroupings(Context context) { 633 if (RecentsDebugFlags.Static.EnableSimulatedTaskGroups) { 634 HashMap<Task.TaskKey, Task> taskMap = new HashMap<Task.TaskKey, Task>(); 635 // Sort all tasks by increasing firstActiveTime of the task 636 ArrayList<Task> tasks = mStackTaskList.getTasks(); 637 Collections.sort(tasks, new Comparator<Task>() { 638 @Override 639 public int compare(Task task, Task task2) { 640 return Long.compare(task.key.firstActiveTime, task2.key.firstActiveTime); 641 } 642 }); 643 // Create groups when sequential packages are the same 644 NamedCounter counter = new NamedCounter("task-group", ""); 645 int taskCount = tasks.size(); 646 String prevPackage = ""; 647 int prevAffiliation = -1; 648 Random r = new Random(); 649 int groupCountDown = RecentsDebugFlags.Static.TaskAffiliationsGroupCount; 650 for (int i = 0; i < taskCount; i++) { 651 Task t = tasks.get(i); 652 String packageName = t.key.getComponent().getPackageName(); 653 packageName = "pkg"; 654 TaskGrouping group; 655 if (packageName.equals(prevPackage) && groupCountDown > 0) { 656 group = getGroupWithAffiliation(prevAffiliation); 657 groupCountDown--; 658 } else { 659 int affiliation = IndividualTaskIdOffset + t.key.id; 660 group = new TaskGrouping(affiliation); 661 addGroup(group); 662 prevAffiliation = affiliation; 663 prevPackage = packageName; 664 groupCountDown = RecentsDebugFlags.Static.TaskAffiliationsGroupCount; 665 } 666 group.addTask(t); 667 taskMap.put(t.key, t); 668 } 669 // Sort groups by increasing latestActiveTime of the group 670 Collections.sort(mGroups, new Comparator<TaskGrouping>() { 671 @Override 672 public int compare(TaskGrouping taskGrouping, TaskGrouping taskGrouping2) { 673 return (int) (taskGrouping.latestActiveTimeInGroup - 674 taskGrouping2.latestActiveTimeInGroup); 675 } 676 }); 677 // Sort group tasks by increasing firstActiveTime of the task, and also build a new list 678 // of tasks 679 int taskIndex = 0; 680 int groupCount = mGroups.size(); 681 for (int i = 0; i < groupCount; i++) { 682 TaskGrouping group = mGroups.get(i); 683 Collections.sort(group.mTaskKeys, new Comparator<Task.TaskKey>() { 684 @Override 685 public int compare(Task.TaskKey taskKey, Task.TaskKey taskKey2) { 686 return (int) (taskKey.firstActiveTime - taskKey2.firstActiveTime); 687 } 688 }); 689 ArrayList<Task.TaskKey> groupTasks = group.mTaskKeys; 690 int groupTaskCount = groupTasks.size(); 691 for (int j = 0; j < groupTaskCount; j++) { 692 tasks.set(taskIndex, taskMap.get(groupTasks.get(j))); 693 taskIndex++; 694 } 695 } 696 mStackTaskList.set(tasks); 697 } else { 698 // Create the task groups 699 HashMap<Task.TaskKey, Task> tasksMap = new HashMap<>(); 700 ArrayList<Task> tasks = mStackTaskList.getTasks(); 701 int taskCount = tasks.size(); 702 for (int i = 0; i < taskCount; i++) { 703 Task t = tasks.get(i); 704 TaskGrouping group; 705 int affiliation = t.taskAffiliationId > 0 ? t.taskAffiliationId : 706 IndividualTaskIdOffset + t.key.id; 707 if (mAffinitiesGroups.containsKey(affiliation)) { 708 group = getGroupWithAffiliation(affiliation); 709 } else { 710 group = new TaskGrouping(affiliation); 711 addGroup(group); 712 } 713 group.addTask(t); 714 tasksMap.put(t.key, t); 715 } 716 // Update the task colors for each of the groups 717 float minAlpha = context.getResources().getFloat( 718 R.dimen.recents_task_affiliation_color_min_alpha_percentage); 719 int taskGroupCount = mGroups.size(); 720 for (int i = 0; i < taskGroupCount; i++) { 721 TaskGrouping group = mGroups.get(i); 722 taskCount = group.getTaskCount(); 723 // Ignore the groups that only have one task 724 if (taskCount <= 1) continue; 725 // Calculate the group color distribution 726 int affiliationColor = tasksMap.get(group.mTaskKeys.get(0)).taskAffiliationColor; 727 float alphaStep = (1f - minAlpha) / taskCount; 728 float alpha = 1f; 729 for (int j = 0; j < taskCount; j++) { 730 Task t = tasksMap.get(group.mTaskKeys.get(j)); 731 t.colorPrimary = Utilities.getColorWithOverlay(affiliationColor, Color.WHITE, 732 alpha); 733 alpha -= alphaStep; 734 } 735 } 736 } 737 } 738 739 /** 740 * Computes the components of tasks in this stack that have been removed as a result of a change 741 * in the specified package. 742 */ 743 public HashSet<ComponentName> computeComponentsRemoved(String packageName, int userId) { 744 // Identify all the tasks that should be removed as a result of the package being removed. 745 // Using a set to ensure that we callback once per unique component. 746 SystemServicesProxy ssp = Recents.getSystemServices(); 747 HashSet<ComponentName> existingComponents = new HashSet<>(); 748 HashSet<ComponentName> removedComponents = new HashSet<>(); 749 ArrayList<Task.TaskKey> taskKeys = getTaskKeys(); 750 for (Task.TaskKey t : taskKeys) { 751 // Skip if this doesn't apply to the current user 752 if (t.userId != userId) continue; 753 754 ComponentName cn = t.getComponent(); 755 if (cn.getPackageName().equals(packageName)) { 756 if (existingComponents.contains(cn)) { 757 // If we know that the component still exists in the package, then skip 758 continue; 759 } 760 if (ssp.getActivityInfo(cn, userId) != null) { 761 existingComponents.add(cn); 762 } else { 763 removedComponents.add(cn); 764 } 765 } 766 } 767 return removedComponents; 768 } 769 770 @Override 771 public String toString() { 772 String str = "Stack Tasks:\n"; 773 for (Task t : mStackTaskList.getTasks()) { 774 str += " " + t.toString() + "\n"; 775 } 776 str += "Historical Tasks:\n"; 777 for (Task t : mHistoryTaskList.getTasks()) { 778 str += " " + t.toString() + "\n"; 779 } 780 return str; 781 } 782 783 /** 784 * Given a list of tasks, returns a map of each task's key to the task. 785 */ 786 private HashMap<Task.TaskKey, Task> createTaskKeyMapFromList(List<Task> tasks) { 787 HashMap<Task.TaskKey, Task> map = new HashMap<>(); 788 int taskCount = tasks.size(); 789 for (int i = 0; i < taskCount; i++) { 790 Task task = tasks.get(i); 791 map.put(task.key, task); 792 } 793 return map; 794 } 795} 796