RecentsView.java revision e1e20e11a758ee82753e8052773664823ca74746
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.views; 18 19import android.app.ActivityOptions; 20import android.app.TaskStackBuilder; 21import android.content.Context; 22import android.content.Intent; 23import android.graphics.Bitmap; 24import android.graphics.Canvas; 25import android.graphics.Rect; 26import android.net.Uri; 27import android.os.UserHandle; 28import android.provider.Settings; 29import android.util.AttributeSet; 30import android.view.LayoutInflater; 31import android.view.View; 32import android.view.ViewStub; 33import android.view.WindowInsets; 34import android.widget.FrameLayout; 35import com.android.systemui.R; 36import com.android.systemui.recents.Constants; 37import com.android.systemui.recents.RecentsAppWidgetHostView; 38import com.android.systemui.recents.RecentsConfiguration; 39import com.android.systemui.recents.misc.SystemServicesProxy; 40import com.android.systemui.recents.model.RecentsPackageMonitor; 41import com.android.systemui.recents.model.RecentsTaskLoader; 42import com.android.systemui.recents.model.Task; 43import com.android.systemui.recents.model.TaskStack; 44 45import java.util.ArrayList; 46import java.util.List; 47 48/** 49 * This view is the the top level layout that contains TaskStacks (which are laid out according 50 * to their SpaceNode bounds. 51 */ 52public class RecentsView extends FrameLayout implements TaskStackView.TaskStackViewCallbacks, 53 RecentsPackageMonitor.PackageCallbacks { 54 55 /** The RecentsView callbacks */ 56 public interface RecentsViewCallbacks { 57 public void onTaskViewClicked(); 58 public void onTaskLaunchFailed(); 59 public void onAllTaskViewsDismissed(); 60 public void onExitToHomeAnimationTriggered(); 61 public void onScreenPinningRequest(); 62 63 public void onTaskResize(Task t); 64 } 65 66 RecentsConfiguration mConfig; 67 LayoutInflater mInflater; 68 DebugOverlayView mDebugOverlay; 69 RecentsViewLayoutAlgorithm mLayoutAlgorithm; 70 71 ArrayList<TaskStack> mStacks; 72 List<TaskStackView> mTaskStackViews = new ArrayList<>(); 73 RecentsAppWidgetHostView mSearchBar; 74 RecentsViewCallbacks mCb; 75 76 public RecentsView(Context context) { 77 super(context); 78 } 79 80 public RecentsView(Context context, AttributeSet attrs) { 81 this(context, attrs, 0); 82 } 83 84 public RecentsView(Context context, AttributeSet attrs, int defStyleAttr) { 85 this(context, attrs, defStyleAttr, 0); 86 } 87 88 public RecentsView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { 89 super(context, attrs, defStyleAttr, defStyleRes); 90 mConfig = RecentsConfiguration.getInstance(); 91 mInflater = LayoutInflater.from(context); 92 mLayoutAlgorithm = new RecentsViewLayoutAlgorithm(mConfig); 93 } 94 95 /** Sets the callbacks */ 96 public void setCallbacks(RecentsViewCallbacks cb) { 97 mCb = cb; 98 } 99 100 /** Sets the debug overlay */ 101 public void setDebugOverlay(DebugOverlayView overlay) { 102 mDebugOverlay = overlay; 103 } 104 105 /** Set/get the bsp root node */ 106 public void setTaskStacks(ArrayList<TaskStack> stacks) { 107 int numStacks = stacks.size(); 108 109 // Remove all/extra stack views 110 int numTaskStacksToKeep = 0; // Keep no tasks if we are recreating the layout 111 if (mConfig.launchedReuseTaskStackViews) { 112 numTaskStacksToKeep = Math.min(mTaskStackViews.size(), numStacks); 113 } 114 for (int i = mTaskStackViews.size() - 1; i >= numTaskStacksToKeep; i--) { 115 removeView(mTaskStackViews.remove(i)); 116 } 117 118 // Update the stack views that we are keeping 119 for (int i = 0; i < numTaskStacksToKeep; i++) { 120 TaskStackView tsv = mTaskStackViews.get(i); 121 // If onRecentsHidden is not triggered, we need to the stack view again here 122 tsv.reset(); 123 tsv.setStack(stacks.get(i)); 124 } 125 126 // Add remaining/recreate stack views 127 mStacks = stacks; 128 for (int i = mTaskStackViews.size(); i < numStacks; i++) { 129 TaskStack stack = stacks.get(i); 130 TaskStackView stackView = new TaskStackView(getContext(), stack); 131 stackView.setCallbacks(this); 132 addView(stackView); 133 mTaskStackViews.add(stackView); 134 } 135 136 // Enable debug mode drawing on all the stacks if necessary 137 if (mConfig.debugModeEnabled) { 138 for (int i = mTaskStackViews.size() - 1; i >= 0; i--) { 139 TaskStackView stackView = mTaskStackViews.get(i); 140 stackView.setDebugOverlay(mDebugOverlay); 141 } 142 } 143 144 // Trigger a new layout 145 requestLayout(); 146 } 147 148 /** Gets the list of task views */ 149 List<TaskStackView> getTaskStackViews() { 150 return mTaskStackViews; 151 } 152 153 /** Gets the next task in the stack - or if the last - the top task */ 154 public Task getNextTaskOrTopTask(Task taskToSearch) { 155 Task returnTask = null; 156 boolean found = false; 157 List<TaskStackView> stackViews = getTaskStackViews(); 158 int stackCount = stackViews.size(); 159 for (int i = stackCount - 1; i >= 0; --i) { 160 TaskStack stack = stackViews.get(i).getStack(); 161 ArrayList<Task> taskList = stack.getTasks(); 162 // Iterate the stack views and try and find the focused task 163 for (int j = taskList.size() - 1; j >= 0; --j) { 164 Task task = taskList.get(j); 165 // Return the next task in the line. 166 if (found) 167 return task; 168 // Remember the first possible task as the top task. 169 if (returnTask == null) 170 returnTask = task; 171 if (task == taskToSearch) 172 found = true; 173 } 174 } 175 return returnTask; 176 } 177 178 /** Launches the focused task from the first stack if possible */ 179 public boolean launchFocusedTask() { 180 // Get the first stack view 181 List<TaskStackView> stackViews = getTaskStackViews(); 182 int stackCount = stackViews.size(); 183 for (int i = 0; i < stackCount; i++) { 184 TaskStackView stackView = stackViews.get(i); 185 TaskStack stack = stackView.getStack(); 186 // Iterate the stack views and try and find the focused task 187 List<TaskView> taskViews = stackView.getTaskViews(); 188 int taskViewCount = taskViews.size(); 189 for (int j = 0; j < taskViewCount; j++) { 190 TaskView tv = taskViews.get(j); 191 Task task = tv.getTask(); 192 if (tv.isFocusedTask()) { 193 onTaskViewClicked(stackView, tv, stack, task, false); 194 return true; 195 } 196 } 197 } 198 return false; 199 } 200 201 /** Launches a given task. */ 202 public boolean launchTask(Task task) { 203 // Get the first stack view 204 List<TaskStackView> stackViews = getTaskStackViews(); 205 int stackCount = stackViews.size(); 206 for (int i = 0; i < stackCount; i++) { 207 TaskStackView stackView = stackViews.get(i); 208 TaskStack stack = stackView.getStack(); 209 // Iterate the stack views and try and find the given task. 210 List<TaskView> taskViews = stackView.getTaskViews(); 211 int taskViewCount = taskViews.size(); 212 for (int j = 0; j < taskViewCount; j++) { 213 TaskView tv = taskViews.get(j); 214 if (tv.getTask() == task) { 215 onTaskViewClicked(stackView, tv, stack, task, false); 216 return true; 217 } 218 } 219 } 220 return false; 221 } 222 223 /** Launches the task that Recents was launched from, if possible */ 224 public boolean launchPreviousTask() { 225 // Get the first stack view 226 List<TaskStackView> stackViews = getTaskStackViews(); 227 int stackCount = stackViews.size(); 228 for (int i = 0; i < stackCount; i++) { 229 TaskStackView stackView = stackViews.get(i); 230 TaskStack stack = stackView.getStack(); 231 ArrayList<Task> tasks = stack.getTasks(); 232 233 // Find the launch task in the stack 234 if (!tasks.isEmpty()) { 235 int taskCount = tasks.size(); 236 for (int j = 0; j < taskCount; j++) { 237 if (tasks.get(j).isLaunchTarget) { 238 Task task = tasks.get(j); 239 TaskView tv = stackView.getChildViewForTask(task); 240 onTaskViewClicked(stackView, tv, stack, task, false); 241 return true; 242 } 243 } 244 } 245 } 246 return false; 247 } 248 249 /** Requests all task stacks to start their enter-recents animation */ 250 public void startEnterRecentsAnimation(ViewAnimation.TaskViewEnterContext ctx) { 251 // We have to increment/decrement the post animation trigger in case there are no children 252 // to ensure that it runs 253 ctx.postAnimationTrigger.increment(); 254 255 List<TaskStackView> stackViews = getTaskStackViews(); 256 int stackCount = stackViews.size(); 257 for (int i = 0; i < stackCount; i++) { 258 TaskStackView stackView = stackViews.get(i); 259 stackView.startEnterRecentsAnimation(ctx); 260 } 261 ctx.postAnimationTrigger.decrement(); 262 } 263 264 /** Requests all task stacks to start their exit-recents animation */ 265 public void startExitToHomeAnimation(ViewAnimation.TaskViewExitContext ctx) { 266 // We have to increment/decrement the post animation trigger in case there are no children 267 // to ensure that it runs 268 ctx.postAnimationTrigger.increment(); 269 List<TaskStackView> stackViews = getTaskStackViews(); 270 int stackCount = stackViews.size(); 271 for (int i = 0; i < stackCount; i++) { 272 TaskStackView stackView = stackViews.get(i); 273 stackView.startExitToHomeAnimation(ctx); 274 } 275 ctx.postAnimationTrigger.decrement(); 276 277 // Notify of the exit animation 278 mCb.onExitToHomeAnimationTriggered(); 279 } 280 281 /** Adds the search bar */ 282 public void setSearchBar(RecentsAppWidgetHostView searchBar) { 283 // Create the search bar (and hide it if we have no recent tasks) 284 if (Constants.DebugFlags.App.EnableSearchLayout) { 285 // Remove the previous search bar if one exists 286 if (mSearchBar != null && indexOfChild(mSearchBar) > -1) { 287 removeView(mSearchBar); 288 } 289 // Add the new search bar 290 if (searchBar != null) { 291 mSearchBar = searchBar; 292 addView(mSearchBar); 293 } 294 } 295 } 296 297 /** Returns whether there is currently a search bar */ 298 public boolean hasValidSearchBar() { 299 return mSearchBar != null && !mSearchBar.isReinflateRequired(); 300 } 301 302 /** Sets the visibility of the search bar */ 303 public void setSearchBarVisibility(int visibility) { 304 if (mSearchBar != null) { 305 mSearchBar.setVisibility(visibility); 306 // Always bring the search bar to the top 307 mSearchBar.bringToFront(); 308 } 309 } 310 311 /** 312 * This is called with the full size of the window since we are handling our own insets. 313 */ 314 @Override 315 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 316 int width = MeasureSpec.getSize(widthMeasureSpec); 317 int height = MeasureSpec.getSize(heightMeasureSpec); 318 319 // Get the search bar bounds and measure the search bar layout 320 if (mSearchBar != null) { 321 Rect searchBarSpaceBounds = new Rect(); 322 mConfig.getSearchBarBounds(width, height, mConfig.systemInsets.top, searchBarSpaceBounds); 323 mSearchBar.measure( 324 MeasureSpec.makeMeasureSpec(searchBarSpaceBounds.width(), MeasureSpec.EXACTLY), 325 MeasureSpec.makeMeasureSpec(searchBarSpaceBounds.height(), MeasureSpec.EXACTLY)); 326 } 327 328 Rect taskStackBounds = new Rect(); 329 mConfig.getAvailableTaskStackBounds(width, height, mConfig.systemInsets.top, 330 mConfig.systemInsets.right, taskStackBounds); 331 332 // Measure each TaskStackView with the full width and height of the window since the 333 // transition view is a child of that stack view 334 List<TaskStackView> stackViews = getTaskStackViews(); 335 List<Rect> stackViewsBounds = mLayoutAlgorithm.computeStackRects(stackViews, 336 taskStackBounds); 337 int stackCount = stackViews.size(); 338 for (int i = 0; i < stackCount; i++) { 339 TaskStackView stackView = stackViews.get(i); 340 if (stackView.getVisibility() != GONE) { 341 // We are going to measure the TaskStackView with the whole RecentsView dimensions, 342 // but the actual stack is going to be inset to the bounds calculated by the layout 343 // algorithm 344 stackView.setStackInsetRect(stackViewsBounds.get(i)); 345 stackView.measure(widthMeasureSpec, heightMeasureSpec); 346 } 347 } 348 349 setMeasuredDimension(width, height); 350 } 351 352 /** 353 * This is called with the full size of the window since we are handling our own insets. 354 */ 355 @Override 356 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 357 // Get the search bar bounds so that we lay it out 358 if (mSearchBar != null) { 359 Rect searchBarSpaceBounds = new Rect(); 360 mConfig.getSearchBarBounds(getMeasuredWidth(), getMeasuredHeight(), 361 mConfig.systemInsets.top, searchBarSpaceBounds); 362 mSearchBar.layout(searchBarSpaceBounds.left, searchBarSpaceBounds.top, 363 searchBarSpaceBounds.right, searchBarSpaceBounds.bottom); 364 } 365 366 // Layout each TaskStackView with the full width and height of the window since the 367 // transition view is a child of that stack view 368 List<TaskStackView> stackViews = getTaskStackViews(); 369 int stackCount = stackViews.size(); 370 for (int i = 0; i < stackCount; i++) { 371 TaskStackView stackView = stackViews.get(i); 372 if (stackView.getVisibility() != GONE) { 373 stackView.layout(left, top, left + stackView.getMeasuredWidth(), 374 top + stackView.getMeasuredHeight()); 375 } 376 } 377 } 378 379 @Override 380 public WindowInsets onApplyWindowInsets(WindowInsets insets) { 381 // Update the configuration with the latest system insets and trigger a relayout 382 mConfig.updateSystemInsets(insets.getSystemWindowInsets()); 383 requestLayout(); 384 return insets.consumeSystemWindowInsets(); 385 } 386 387 /** Notifies each task view of the user interaction. */ 388 public void onUserInteraction() { 389 // Get the first stack view 390 List<TaskStackView> stackViews = getTaskStackViews(); 391 int stackCount = stackViews.size(); 392 for (int i = 0; i < stackCount; i++) { 393 TaskStackView stackView = stackViews.get(i); 394 stackView.onUserInteraction(); 395 } 396 } 397 398 /** Focuses the next task in the first stack view */ 399 public void focusNextTask(boolean forward) { 400 // Get the first stack view 401 List<TaskStackView> stackViews = getTaskStackViews(); 402 if (!stackViews.isEmpty()) { 403 stackViews.get(0).focusNextTask(forward, true); 404 } 405 } 406 407 /** Dismisses the focused task. */ 408 public void dismissFocusedTask() { 409 // Get the first stack view 410 List<TaskStackView> stackViews = getTaskStackViews(); 411 if (!stackViews.isEmpty()) { 412 stackViews.get(0).dismissFocusedTask(); 413 } 414 } 415 416 /** Unfilters any filtered stacks */ 417 public boolean unfilterFilteredStacks() { 418 if (mStacks != null) { 419 // Check if there are any filtered stacks and unfilter them before we back out of Recents 420 boolean stacksUnfiltered = false; 421 int numStacks = mStacks.size(); 422 for (int i = 0; i < numStacks; i++) { 423 TaskStack stack = mStacks.get(i); 424 if (stack.hasFilteredTasks()) { 425 stack.unfilterTasks(); 426 stacksUnfiltered = true; 427 } 428 } 429 return stacksUnfiltered; 430 } 431 return false; 432 } 433 434 /**** TaskStackView.TaskStackCallbacks Implementation ****/ 435 436 @Override 437 public void onTaskViewClicked(final TaskStackView stackView, final TaskView tv, 438 final TaskStack stack, final Task task, final boolean lockToTask) { 439 // Notify any callbacks of the launching of a new task 440 if (mCb != null) { 441 mCb.onTaskViewClicked(); 442 } 443 444 // Upfront the processing of the thumbnail 445 TaskViewTransform transform = new TaskViewTransform(); 446 View sourceView; 447 int offsetX = 0; 448 int offsetY = 0; 449 float stackScroll = stackView.getScroller().getStackScroll(); 450 if (tv == null) { 451 // If there is no actual task view, then use the stack view as the source view 452 // and then offset to the expected transform rect, but bound this to just 453 // outside the display rect (to ensure we don't animate from too far away) 454 sourceView = stackView; 455 transform = stackView.getStackAlgorithm().getStackTransform(task, stackScroll, transform, null); 456 offsetX = transform.rect.left; 457 offsetY = mConfig.displayRect.height(); 458 } else { 459 sourceView = tv.mThumbnailView; 460 transform = stackView.getStackAlgorithm().getStackTransform(task, stackScroll, transform, null); 461 } 462 463 // Compute the thumbnail to scale up from 464 final SystemServicesProxy ssp = 465 RecentsTaskLoader.getInstance().getSystemServicesProxy(); 466 ActivityOptions opts = null; 467 if (task.thumbnail != null && task.thumbnail.getWidth() > 0 && 468 task.thumbnail.getHeight() > 0) { 469 Bitmap b; 470 if (tv != null) { 471 // Disable any focused state before we draw the header 472 if (tv.isFocusedTask()) { 473 tv.unsetFocusedTask(); 474 } 475 476 float scale = tv.getScaleX(); 477 int fromHeaderWidth = (int) (tv.mHeaderView.getMeasuredWidth() * scale); 478 int fromHeaderHeight = (int) (tv.mHeaderView.getMeasuredHeight() * scale); 479 b = Bitmap.createBitmap(fromHeaderWidth, fromHeaderHeight, 480 Bitmap.Config.ARGB_8888); 481 if (Constants.DebugFlags.App.EnableTransitionThumbnailDebugMode) { 482 b.eraseColor(0xFFff0000); 483 } else { 484 Canvas c = new Canvas(b); 485 c.scale(tv.getScaleX(), tv.getScaleY()); 486 tv.mHeaderView.draw(c); 487 c.setBitmap(null); 488 } 489 } else { 490 // Notify the system to skip the thumbnail layer by using an ALPHA_8 bitmap 491 b = Bitmap.createBitmap(1, 1, Bitmap.Config.ALPHA_8); 492 } 493 Bitmap bImmut = b.createAshmemBitmap(); 494 ActivityOptions.OnAnimationStartedListener animStartedListener = null; 495 if (lockToTask) { 496 animStartedListener = new ActivityOptions.OnAnimationStartedListener() { 497 boolean mTriggered = false; 498 @Override 499 public void onAnimationStarted() { 500 if (!mTriggered) { 501 postDelayed(new Runnable() { 502 @Override 503 public void run() { 504 mCb.onScreenPinningRequest(); 505 } 506 }, 350); 507 mTriggered = true; 508 } 509 } 510 }; 511 } 512 if (mConfig.multiStackEnabled) { 513 opts = ActivityOptions.makeCustomAnimation(sourceView.getContext(), 514 R.anim.recents_from_unknown_enter, 515 R.anim.recents_from_unknown_exit, 516 sourceView.getHandler(), animStartedListener); 517 } else { 518 opts = ActivityOptions.makeThumbnailAspectScaleUpAnimation(sourceView, 519 bImmut, offsetX, offsetY, transform.rect.width(), transform.rect.height(), 520 sourceView.getHandler(), animStartedListener); 521 } 522 } 523 524 final ActivityOptions launchOpts = opts; 525 final Runnable launchRunnable = new Runnable() { 526 @Override 527 public void run() { 528 if (task.isActive) { 529 // Bring an active task to the foreground 530 ssp.moveTaskToFront(task.key.id, launchOpts); 531 } else { 532 if (ssp.startActivityFromRecents(getContext(), task.key.id, 533 task.activityLabel, launchOpts)) { 534 if (launchOpts == null && lockToTask) { 535 mCb.onScreenPinningRequest(); 536 } 537 } else { 538 // Dismiss the task and return the user to home if we fail to 539 // launch the task 540 onTaskViewDismissed(task); 541 if (mCb != null) { 542 mCb.onTaskLaunchFailed(); 543 } 544 } 545 } 546 } 547 }; 548 549 // Launch the app right away if there is no task view, otherwise, animate the icon out first 550 if (tv == null) { 551 launchRunnable.run(); 552 } else { 553 if (task.group != null && !task.group.isFrontMostTask(task)) { 554 // For affiliated tasks that are behind other tasks, we must animate the front cards 555 // out of view before starting the task transition 556 stackView.startLaunchTaskAnimation(tv, launchRunnable, lockToTask); 557 } else { 558 // Otherwise, we can start the task transition immediately 559 stackView.startLaunchTaskAnimation(tv, null, lockToTask); 560 launchRunnable.run(); 561 } 562 } 563 } 564 565 @Override 566 public void onTaskViewAppInfoClicked(Task t) { 567 // Create a new task stack with the application info details activity 568 Intent baseIntent = t.key.baseIntent; 569 Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, 570 Uri.fromParts("package", baseIntent.getComponent().getPackageName(), null)); 571 intent.setComponent(intent.resolveActivity(getContext().getPackageManager())); 572 TaskStackBuilder.create(getContext()) 573 .addNextIntentWithParentStack(intent).startActivities(null, 574 new UserHandle(t.key.userId)); 575 } 576 577 @Override 578 public void onTaskViewDismissed(Task t) { 579 // Remove any stored data from the loader. We currently don't bother notifying the views 580 // that the data has been unloaded because at the point we call onTaskViewDismissed(), the views 581 // either don't need to be updated, or have already been removed. 582 RecentsTaskLoader loader = RecentsTaskLoader.getInstance(); 583 loader.deleteTaskData(t, false); 584 585 // Remove the old task from activity manager 586 loader.getSystemServicesProxy().removeTask(t.key.id); 587 } 588 589 @Override 590 public void onAllTaskViewsDismissed(ArrayList<Task> removedTasks) { 591 if (removedTasks != null) { 592 int taskCount = removedTasks.size(); 593 for (int i = 0; i < taskCount; i++) { 594 onTaskViewDismissed(removedTasks.get(i)); 595 } 596 } 597 598 mCb.onAllTaskViewsDismissed(); 599 } 600 601 /** Final callback after Recents is finally hidden. */ 602 public void onRecentsHidden() { 603 // Notify each task stack view 604 List<TaskStackView> stackViews = getTaskStackViews(); 605 int stackCount = stackViews.size(); 606 for (int i = 0; i < stackCount; i++) { 607 TaskStackView stackView = stackViews.get(i); 608 stackView.onRecentsHidden(); 609 } 610 } 611 612 @Override 613 public void onTaskStackFilterTriggered() { 614 // Hide the search bar 615 if (mSearchBar != null) { 616 mSearchBar.animate() 617 .alpha(0f) 618 .setStartDelay(0) 619 .setInterpolator(mConfig.fastOutSlowInInterpolator) 620 .setDuration(mConfig.filteringCurrentViewsAnimDuration) 621 .withLayer() 622 .start(); 623 } 624 } 625 626 @Override 627 public void onTaskStackUnfilterTriggered() { 628 // Show the search bar 629 if (mSearchBar != null) { 630 mSearchBar.animate() 631 .alpha(1f) 632 .setStartDelay(0) 633 .setInterpolator(mConfig.fastOutSlowInInterpolator) 634 .setDuration(mConfig.filteringNewViewsAnimDuration) 635 .withLayer() 636 .start(); 637 } 638 } 639 640 @Override 641 public void onTaskResize(Task t) { 642 if (mCb != null) { 643 mCb.onTaskResize(t); 644 } 645 } 646 647 /**** RecentsPackageMonitor.PackageCallbacks Implementation ****/ 648 649 @Override 650 public void onPackagesChanged(RecentsPackageMonitor monitor, String packageName, int userId) { 651 // Propagate this event down to each task stack view 652 List<TaskStackView> stackViews = getTaskStackViews(); 653 int stackCount = stackViews.size(); 654 for (int i = 0; i < stackCount; i++) { 655 TaskStackView stackView = stackViews.get(i); 656 stackView.onPackagesChanged(monitor, packageName, userId); 657 } 658 } 659} 660