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.views; 18 19 import android.animation.ValueAnimator; 20 import android.content.ComponentName; 21 import android.content.Context; 22 import android.graphics.Matrix; 23 import android.graphics.Rect; 24 import android.view.LayoutInflater; 25 import android.view.MotionEvent; 26 import android.view.View; 27 import android.view.accessibility.AccessibilityEvent; 28 import android.widget.FrameLayout; 29 import com.android.systemui.R; 30 import com.android.systemui.recents.Constants; 31 import com.android.systemui.recents.RecentsConfiguration; 32 import com.android.systemui.recents.misc.DozeTrigger; 33 import com.android.systemui.recents.misc.SystemServicesProxy; 34 import com.android.systemui.recents.misc.Utilities; 35 import com.android.systemui.recents.model.RecentsPackageMonitor; 36 import com.android.systemui.recents.model.RecentsTaskLoader; 37 import com.android.systemui.recents.model.Task; 38 import com.android.systemui.recents.model.TaskStack; 39 40 import java.util.ArrayList; 41 import java.util.HashMap; 42 import java.util.HashSet; 43 import java.util.Iterator; 44 45 46 /* The visual representation of a task stack view */ 47 public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCallbacks, 48 TaskView.TaskViewCallbacks, TaskStackViewScroller.TaskStackViewScrollerCallbacks, 49 ViewPool.ViewPoolConsumer<TaskView, Task>, RecentsPackageMonitor.PackageCallbacks { 50 51 /** The TaskView callbacks */ 52 interface TaskStackViewCallbacks { onTaskViewClicked(TaskStackView stackView, TaskView tv, TaskStack stack, Task t, boolean lockToTask)53 public void onTaskViewClicked(TaskStackView stackView, TaskView tv, TaskStack stack, Task t, 54 boolean lockToTask); onTaskViewAppInfoClicked(Task t)55 public void onTaskViewAppInfoClicked(Task t); onTaskViewDismissed(Task t)56 public void onTaskViewDismissed(Task t); onAllTaskViewsDismissed()57 public void onAllTaskViewsDismissed(); onTaskStackFilterTriggered()58 public void onTaskStackFilterTriggered(); onTaskStackUnfilterTriggered()59 public void onTaskStackUnfilterTriggered(); 60 } 61 62 RecentsConfiguration mConfig; 63 64 TaskStack mStack; 65 TaskStackViewLayoutAlgorithm mLayoutAlgorithm; 66 TaskStackViewFilterAlgorithm mFilterAlgorithm; 67 TaskStackViewScroller mStackScroller; 68 TaskStackViewTouchHandler mTouchHandler; 69 TaskStackViewCallbacks mCb; 70 ViewPool<TaskView, Task> mViewPool; 71 ArrayList<TaskViewTransform> mCurrentTaskTransforms = new ArrayList<TaskViewTransform>(); 72 DozeTrigger mUIDozeTrigger; 73 DebugOverlayView mDebugOverlay; 74 Rect mTaskStackBounds = new Rect(); 75 int mFocusedTaskIndex = -1; 76 int mPrevAccessibilityFocusedIndex = -1; 77 78 // Optimizations 79 int mStackViewsAnimationDuration; 80 boolean mStackViewsDirty = true; 81 boolean mStackViewsClipDirty = true; 82 boolean mAwaitingFirstLayout = true; 83 boolean mStartEnterAnimationRequestedAfterLayout; 84 boolean mStartEnterAnimationCompleted; 85 ViewAnimation.TaskViewEnterContext mStartEnterAnimationContext; 86 int[] mTmpVisibleRange = new int[2]; 87 float[] mTmpCoord = new float[2]; 88 Matrix mTmpMatrix = new Matrix(); 89 Rect mTmpRect = new Rect(); 90 TaskViewTransform mTmpTransform = new TaskViewTransform(); 91 HashMap<Task, TaskView> mTmpTaskViewMap = new HashMap<Task, TaskView>(); 92 LayoutInflater mInflater; 93 94 // A convenience update listener to request updating clipping of tasks 95 ValueAnimator.AnimatorUpdateListener mRequestUpdateClippingListener = 96 new ValueAnimator.AnimatorUpdateListener() { 97 @Override 98 public void onAnimationUpdate(ValueAnimator animation) { 99 requestUpdateStackViewsClip(); 100 } 101 }; 102 TaskStackView(Context context, TaskStack stack)103 public TaskStackView(Context context, TaskStack stack) { 104 super(context); 105 // Set the stack first 106 setStack(stack); 107 mConfig = RecentsConfiguration.getInstance(); 108 mViewPool = new ViewPool<TaskView, Task>(context, this); 109 mInflater = LayoutInflater.from(context); 110 mLayoutAlgorithm = new TaskStackViewLayoutAlgorithm(mConfig); 111 mFilterAlgorithm = new TaskStackViewFilterAlgorithm(mConfig, this, mViewPool); 112 mStackScroller = new TaskStackViewScroller(context, mConfig, mLayoutAlgorithm); 113 mStackScroller.setCallbacks(this); 114 mTouchHandler = new TaskStackViewTouchHandler(context, this, mConfig, mStackScroller); 115 mUIDozeTrigger = new DozeTrigger(mConfig.taskBarDismissDozeDelaySeconds, new Runnable() { 116 @Override 117 public void run() { 118 // Show the task bar dismiss buttons 119 int childCount = getChildCount(); 120 for (int i = 0; i < childCount; i++) { 121 TaskView tv = (TaskView) getChildAt(i); 122 tv.startNoUserInteractionAnimation(); 123 } 124 } 125 }); 126 } 127 128 /** Sets the callbacks */ setCallbacks(TaskStackViewCallbacks cb)129 void setCallbacks(TaskStackViewCallbacks cb) { 130 mCb = cb; 131 } 132 133 /** Sets the task stack */ setStack(TaskStack stack)134 void setStack(TaskStack stack) { 135 // Set the new stack 136 mStack = stack; 137 if (mStack != null) { 138 mStack.setCallbacks(this); 139 } 140 // Layout again with the new stack 141 requestLayout(); 142 } 143 144 /** Sets the debug overlay */ setDebugOverlay(DebugOverlayView overlay)145 public void setDebugOverlay(DebugOverlayView overlay) { 146 mDebugOverlay = overlay; 147 } 148 149 /** Resets this TaskStackView for reuse. */ reset()150 void reset() { 151 // Reset the focused task 152 resetFocusedTask(); 153 154 // Return all the views to the pool 155 int childCount = getChildCount(); 156 for (int i = childCount - 1; i >= 0; i--) { 157 TaskView tv = (TaskView) getChildAt(i); 158 mViewPool.returnViewToPool(tv); 159 } 160 161 // Mark each task view for relayout 162 if (mViewPool != null) { 163 Iterator<TaskView> iter = mViewPool.poolViewIterator(); 164 if (iter != null) { 165 while (iter.hasNext()) { 166 TaskView tv = iter.next(); 167 tv.reset(); 168 } 169 } 170 } 171 172 // Reset the stack state 173 mStack.reset(); 174 mStackViewsDirty = true; 175 mStackViewsClipDirty = true; 176 mAwaitingFirstLayout = true; 177 mPrevAccessibilityFocusedIndex = -1; 178 if (mUIDozeTrigger != null) { 179 mUIDozeTrigger.stopDozing(); 180 mUIDozeTrigger.resetTrigger(); 181 } 182 mStackScroller.reset(); 183 } 184 185 /** Requests that the views be synchronized with the model */ requestSynchronizeStackViewsWithModel()186 void requestSynchronizeStackViewsWithModel() { 187 requestSynchronizeStackViewsWithModel(0); 188 } requestSynchronizeStackViewsWithModel(int duration)189 void requestSynchronizeStackViewsWithModel(int duration) { 190 if (!mStackViewsDirty) { 191 invalidate(); 192 mStackViewsDirty = true; 193 } 194 if (mAwaitingFirstLayout) { 195 // Skip the animation if we are awaiting first layout 196 mStackViewsAnimationDuration = 0; 197 } else { 198 mStackViewsAnimationDuration = Math.max(mStackViewsAnimationDuration, duration); 199 } 200 } 201 202 /** Requests that the views clipping be updated. */ requestUpdateStackViewsClip()203 void requestUpdateStackViewsClip() { 204 if (!mStackViewsClipDirty) { 205 invalidate(); 206 mStackViewsClipDirty = true; 207 } 208 } 209 210 /** Finds the child view given a specific task. */ getChildViewForTask(Task t)211 public TaskView getChildViewForTask(Task t) { 212 int childCount = getChildCount(); 213 for (int i = 0; i < childCount; i++) { 214 TaskView tv = (TaskView) getChildAt(i); 215 if (tv.getTask() == t) { 216 return tv; 217 } 218 } 219 return null; 220 } 221 222 /** Returns the stack algorithm for this task stack. */ getStackAlgorithm()223 public TaskStackViewLayoutAlgorithm getStackAlgorithm() { 224 return mLayoutAlgorithm; 225 } 226 227 /** 228 * Gets the stack transforms of a list of tasks, and returns the visible range of tasks. 229 */ updateStackTransforms(ArrayList<TaskViewTransform> taskTransforms, ArrayList<Task> tasks, float stackScroll, int[] visibleRangeOut, boolean boundTranslationsToRect)230 private boolean updateStackTransforms(ArrayList<TaskViewTransform> taskTransforms, 231 ArrayList<Task> tasks, 232 float stackScroll, 233 int[] visibleRangeOut, 234 boolean boundTranslationsToRect) { 235 int taskTransformCount = taskTransforms.size(); 236 int taskCount = tasks.size(); 237 int frontMostVisibleIndex = -1; 238 int backMostVisibleIndex = -1; 239 240 // We can reuse the task transforms where possible to reduce object allocation 241 if (taskTransformCount < taskCount) { 242 // If there are less transforms than tasks, then add as many transforms as necessary 243 for (int i = taskTransformCount; i < taskCount; i++) { 244 taskTransforms.add(new TaskViewTransform()); 245 } 246 } else if (taskTransformCount > taskCount) { 247 // If there are more transforms than tasks, then just subset the transform list 248 taskTransforms.subList(0, taskCount); 249 } 250 251 // Update the stack transforms 252 TaskViewTransform prevTransform = null; 253 for (int i = taskCount - 1; i >= 0; i--) { 254 TaskViewTransform transform = mLayoutAlgorithm.getStackTransform(tasks.get(i), 255 stackScroll, taskTransforms.get(i), prevTransform); 256 if (transform.visible) { 257 if (frontMostVisibleIndex < 0) { 258 frontMostVisibleIndex = i; 259 } 260 backMostVisibleIndex = i; 261 } else { 262 if (backMostVisibleIndex != -1) { 263 // We've reached the end of the visible range, so going down the rest of the 264 // stack, we can just reset the transforms accordingly 265 while (i >= 0) { 266 taskTransforms.get(i).reset(); 267 i--; 268 } 269 break; 270 } 271 } 272 273 if (boundTranslationsToRect) { 274 transform.translationY = Math.min(transform.translationY, 275 mLayoutAlgorithm.mViewRect.bottom); 276 } 277 prevTransform = transform; 278 } 279 if (visibleRangeOut != null) { 280 visibleRangeOut[0] = frontMostVisibleIndex; 281 visibleRangeOut[1] = backMostVisibleIndex; 282 } 283 return frontMostVisibleIndex != -1 && backMostVisibleIndex != -1; 284 } 285 286 /** Synchronizes the views with the model */ synchronizeStackViewsWithModel()287 boolean synchronizeStackViewsWithModel() { 288 if (mStackViewsDirty) { 289 RecentsTaskLoader loader = RecentsTaskLoader.getInstance(); 290 SystemServicesProxy ssp = loader.getSystemServicesProxy(); 291 292 // Get all the task transforms 293 ArrayList<Task> tasks = mStack.getTasks(); 294 float stackScroll = mStackScroller.getStackScroll(); 295 int[] visibleRange = mTmpVisibleRange; 296 boolean isValidVisibleRange = updateStackTransforms(mCurrentTaskTransforms, tasks, 297 stackScroll, visibleRange, false); 298 if (mDebugOverlay != null) { 299 mDebugOverlay.setText("vis[" + visibleRange[1] + "-" + visibleRange[0] + "]"); 300 } 301 302 // Return all the invisible children to the pool 303 mTmpTaskViewMap.clear(); 304 int childCount = getChildCount(); 305 for (int i = childCount - 1; i >= 0; i--) { 306 TaskView tv = (TaskView) getChildAt(i); 307 Task task = tv.getTask(); 308 int taskIndex = mStack.indexOfTask(task); 309 if (visibleRange[1] <= taskIndex && taskIndex <= visibleRange[0]) { 310 mTmpTaskViewMap.put(task, tv); 311 } else { 312 mViewPool.returnViewToPool(tv); 313 } 314 } 315 316 // Pick up all the newly visible children and update all the existing children 317 for (int i = visibleRange[0]; isValidVisibleRange && i >= visibleRange[1]; i--) { 318 Task task = tasks.get(i); 319 TaskViewTransform transform = mCurrentTaskTransforms.get(i); 320 TaskView tv = mTmpTaskViewMap.get(task); 321 int taskIndex = mStack.indexOfTask(task); 322 323 if (tv == null) { 324 tv = mViewPool.pickUpViewFromPool(task, task); 325 326 if (mStackViewsAnimationDuration > 0) { 327 // For items in the list, put them in start animating them from the 328 // approriate ends of the list where they are expected to appear 329 if (Float.compare(transform.p, 0f) <= 0) { 330 mLayoutAlgorithm.getStackTransform(0f, 0f, mTmpTransform, null); 331 } else { 332 mLayoutAlgorithm.getStackTransform(1f, 0f, mTmpTransform, null); 333 } 334 tv.updateViewPropertiesToTaskTransform(mTmpTransform, 0); 335 } 336 } 337 338 // Animate the task into place 339 tv.updateViewPropertiesToTaskTransform(mCurrentTaskTransforms.get(taskIndex), 340 mStackViewsAnimationDuration, mRequestUpdateClippingListener); 341 342 // Request accessibility focus on the next view if we removed the task 343 // that previously held accessibility focus 344 childCount = getChildCount(); 345 if (childCount > 0 && ssp.isTouchExplorationEnabled()) { 346 TaskView atv = (TaskView) getChildAt(childCount - 1); 347 int indexOfTask = mStack.indexOfTask(atv.getTask()); 348 if (mPrevAccessibilityFocusedIndex != indexOfTask) { 349 tv.requestAccessibilityFocus(); 350 mPrevAccessibilityFocusedIndex = indexOfTask; 351 } 352 } 353 } 354 355 // Reset the request-synchronize params 356 mStackViewsAnimationDuration = 0; 357 mStackViewsDirty = false; 358 mStackViewsClipDirty = true; 359 return true; 360 } 361 return false; 362 } 363 364 /** Updates the clip for each of the task views. */ clipTaskViews()365 void clipTaskViews() { 366 // Update the clip on each task child 367 if (Constants.DebugFlags.App.EnableTaskStackClipping) { 368 int childCount = getChildCount(); 369 for (int i = 0; i < childCount - 1; i++) { 370 TaskView tv = (TaskView) getChildAt(i); 371 TaskView nextTv = null; 372 TaskView tmpTv = null; 373 int clipBottom = 0; 374 if (tv.shouldClipViewInStack()) { 375 // Find the next view to clip against 376 int nextIndex = i; 377 while (nextIndex < getChildCount()) { 378 tmpTv = (TaskView) getChildAt(++nextIndex); 379 if (tmpTv != null && tmpTv.shouldClipViewInStack()) { 380 nextTv = tmpTv; 381 break; 382 } 383 } 384 385 // Clip against the next view, this is just an approximation since we are 386 // stacked and we can make assumptions about the visibility of the this 387 // task relative to the ones in front of it. 388 if (nextTv != null) { 389 // Map the top edge of next task view into the local space of the current 390 // task view to find the clip amount in local space 391 mTmpCoord[0] = mTmpCoord[1] = 0; 392 Utilities.mapCoordInDescendentToSelf(nextTv, this, mTmpCoord, false); 393 Utilities.mapCoordInSelfToDescendent(tv, this, mTmpCoord, mTmpMatrix); 394 clipBottom = (int) Math.floor(tv.getMeasuredHeight() - mTmpCoord[1] 395 - nextTv.getPaddingTop() - 1); 396 } 397 } 398 tv.getViewBounds().setClipBottom(clipBottom); 399 } 400 if (getChildCount() > 0) { 401 // The front most task should never be clipped 402 TaskView tv = (TaskView) getChildAt(getChildCount() - 1); 403 tv.getViewBounds().setClipBottom(0); 404 } 405 } 406 mStackViewsClipDirty = false; 407 } 408 409 /** The stack insets to apply to the stack contents */ setStackInsetRect(Rect r)410 public void setStackInsetRect(Rect r) { 411 mTaskStackBounds.set(r); 412 } 413 414 /** Updates the min and max virtual scroll bounds */ updateMinMaxScroll(boolean boundScrollToNewMinMax, boolean launchedWithAltTab, boolean launchedFromHome)415 void updateMinMaxScroll(boolean boundScrollToNewMinMax, boolean launchedWithAltTab, 416 boolean launchedFromHome) { 417 // Compute the min and max scroll values 418 mLayoutAlgorithm.computeMinMaxScroll(mStack.getTasks(), launchedWithAltTab, launchedFromHome); 419 420 // Debug logging 421 if (boundScrollToNewMinMax) { 422 mStackScroller.boundScroll(); 423 } 424 } 425 426 /** Returns the scroller. */ getScroller()427 public TaskStackViewScroller getScroller() { 428 return mStackScroller; 429 } 430 431 /** Focuses the task at the specified index in the stack */ focusTask(int taskIndex, boolean scrollToNewPosition, final boolean animateFocusedState)432 void focusTask(int taskIndex, boolean scrollToNewPosition, final boolean animateFocusedState) { 433 // Return early if the task is already focused 434 if (taskIndex == mFocusedTaskIndex) return; 435 436 if (0 <= taskIndex && taskIndex < mStack.getTaskCount()) { 437 mFocusedTaskIndex = taskIndex; 438 439 // Focus the view if possible, otherwise, focus the view after we scroll into position 440 Task t = mStack.getTasks().get(taskIndex); 441 TaskView tv = getChildViewForTask(t); 442 Runnable postScrollRunnable = null; 443 if (tv != null) { 444 tv.setFocusedTask(animateFocusedState); 445 } else { 446 postScrollRunnable = new Runnable() { 447 @Override 448 public void run() { 449 Task t = mStack.getTasks().get(mFocusedTaskIndex); 450 TaskView tv = getChildViewForTask(t); 451 if (tv != null) { 452 tv.setFocusedTask(animateFocusedState); 453 } 454 } 455 }; 456 } 457 458 // Scroll the view into position (just center it in the curve) 459 if (scrollToNewPosition) { 460 float newScroll = mLayoutAlgorithm.getStackScrollForTask(t) - 0.5f; 461 newScroll = mStackScroller.getBoundedStackScroll(newScroll); 462 mStackScroller.animateScroll(mStackScroller.getStackScroll(), newScroll, postScrollRunnable); 463 } else { 464 if (postScrollRunnable != null) { 465 postScrollRunnable.run(); 466 } 467 } 468 469 } 470 } 471 472 /** 473 * Ensures that there is a task focused, if nothing is focused, then we will use the task 474 * at the center of the visible stack. 475 */ ensureFocusedTask()476 public boolean ensureFocusedTask() { 477 if (mFocusedTaskIndex < 0) { 478 // If there is no task focused, then find the task that is closes to the center 479 // of the screen and use that as the currently focused task 480 int x = mLayoutAlgorithm.mStackVisibleRect.centerX(); 481 int y = mLayoutAlgorithm.mStackVisibleRect.centerY(); 482 int childCount = getChildCount(); 483 for (int i = childCount - 1; i >= 0; i--) { 484 TaskView tv = (TaskView) getChildAt(i); 485 tv.getHitRect(mTmpRect); 486 if (mTmpRect.contains(x, y)) { 487 mFocusedTaskIndex = mStack.indexOfTask(tv.getTask()); 488 break; 489 } 490 } 491 // If we can't find the center task, then use the front most index 492 if (mFocusedTaskIndex < 0 && childCount > 0) { 493 mFocusedTaskIndex = childCount - 1; 494 } 495 } 496 return mFocusedTaskIndex >= 0; 497 } 498 499 /** 500 * Focuses the next task in the stack. 501 * @param animateFocusedState determines whether to actually draw the highlight along with 502 * the change in focus, as well as whether to scroll to fit the 503 * task into view. 504 */ focusNextTask(boolean forward, boolean animateFocusedState)505 public void focusNextTask(boolean forward, boolean animateFocusedState) { 506 // Find the next index to focus 507 int numTasks = mStack.getTaskCount(); 508 if (numTasks == 0) return; 509 510 int direction = (forward ? -1 : 1); 511 int newIndex = mFocusedTaskIndex + direction; 512 if (newIndex >= 0 && newIndex <= (numTasks - 1)) { 513 newIndex = Math.max(0, Math.min(numTasks - 1, newIndex)); 514 focusTask(newIndex, true, animateFocusedState); 515 } 516 } 517 518 /** Dismisses the focused task. */ dismissFocusedTask()519 public void dismissFocusedTask() { 520 // Return early if the focused task index is invalid 521 if (mFocusedTaskIndex < 0 || mFocusedTaskIndex >= mStack.getTaskCount()) { 522 mFocusedTaskIndex = -1; 523 return; 524 } 525 526 Task t = mStack.getTasks().get(mFocusedTaskIndex); 527 TaskView tv = getChildViewForTask(t); 528 tv.dismissTask(); 529 } 530 531 /** Resets the focused task. */ resetFocusedTask()532 void resetFocusedTask() { 533 if ((0 <= mFocusedTaskIndex) && (mFocusedTaskIndex < mStack.getTaskCount())) { 534 Task t = mStack.getTasks().get(mFocusedTaskIndex); 535 TaskView tv = getChildViewForTask(t); 536 if (tv != null) { 537 tv.unsetFocusedTask(); 538 } 539 } 540 mFocusedTaskIndex = -1; 541 } 542 543 @Override onInitializeAccessibilityEvent(AccessibilityEvent event)544 public void onInitializeAccessibilityEvent(AccessibilityEvent event) { 545 super.onInitializeAccessibilityEvent(event); 546 int childCount = getChildCount(); 547 if (childCount > 0) { 548 TaskView backMostTask = (TaskView) getChildAt(0); 549 TaskView frontMostTask = (TaskView) getChildAt(childCount - 1); 550 event.setFromIndex(mStack.indexOfTask(backMostTask.getTask())); 551 event.setToIndex(mStack.indexOfTask(frontMostTask.getTask())); 552 event.setContentDescription(frontMostTask.getTask().activityLabel); 553 } 554 event.setItemCount(mStack.getTaskCount()); 555 event.setScrollY(mStackScroller.mScroller.getCurrY()); 556 event.setMaxScrollY(mStackScroller.progressToScrollRange(mLayoutAlgorithm.mMaxScrollP)); 557 } 558 559 @Override onInterceptTouchEvent(MotionEvent ev)560 public boolean onInterceptTouchEvent(MotionEvent ev) { 561 return mTouchHandler.onInterceptTouchEvent(ev); 562 } 563 564 @Override onTouchEvent(MotionEvent ev)565 public boolean onTouchEvent(MotionEvent ev) { 566 return mTouchHandler.onTouchEvent(ev); 567 } 568 569 @Override onGenericMotionEvent(MotionEvent ev)570 public boolean onGenericMotionEvent(MotionEvent ev) { 571 return mTouchHandler.onGenericMotionEvent(ev); 572 } 573 574 @Override computeScroll()575 public void computeScroll() { 576 mStackScroller.computeScroll(); 577 // Synchronize the views 578 synchronizeStackViewsWithModel(); 579 clipTaskViews(); 580 // Notify accessibility 581 sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SCROLLED); 582 } 583 584 /** Computes the stack and task rects */ computeRects(int windowWidth, int windowHeight, Rect taskStackBounds, boolean launchedWithAltTab, boolean launchedFromHome)585 public void computeRects(int windowWidth, int windowHeight, Rect taskStackBounds, 586 boolean launchedWithAltTab, boolean launchedFromHome) { 587 // Compute the rects in the stack algorithm 588 mLayoutAlgorithm.computeRects(windowWidth, windowHeight, taskStackBounds); 589 590 // Update the scroll bounds 591 updateMinMaxScroll(false, launchedWithAltTab, launchedFromHome); 592 } 593 594 /** 595 * This is ONLY used from AlternateRecentsComponent to update the dummy stack view for purposes 596 * of getting the task rect to animate to. 597 */ updateMinMaxScrollForStack(TaskStack stack, boolean launchedWithAltTab, boolean launchedFromHome)598 public void updateMinMaxScrollForStack(TaskStack stack, boolean launchedWithAltTab, 599 boolean launchedFromHome) { 600 mStack = stack; 601 updateMinMaxScroll(false, launchedWithAltTab, launchedFromHome); 602 } 603 604 /** 605 * Computes the maximum number of visible tasks and thumbnails. Requires that 606 * updateMinMaxScrollForStack() is called first. 607 */ computeStackVisibilityReport()608 public TaskStackViewLayoutAlgorithm.VisibilityReport computeStackVisibilityReport() { 609 return mLayoutAlgorithm.computeStackVisibilityReport(mStack.getTasks()); 610 } 611 612 /** 613 * This is called with the full window width and height to allow stack view children to 614 * perform the full screen transition down. 615 */ 616 @Override onMeasure(int widthMeasureSpec, int heightMeasureSpec)617 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 618 int width = MeasureSpec.getSize(widthMeasureSpec); 619 int height = MeasureSpec.getSize(heightMeasureSpec); 620 621 // Compute our stack/task rects 622 Rect taskStackBounds = new Rect(mTaskStackBounds); 623 taskStackBounds.bottom -= mConfig.systemInsets.bottom; 624 computeRects(width, height, taskStackBounds, mConfig.launchedWithAltTab, 625 mConfig.launchedFromHome); 626 627 // If this is the first layout, then scroll to the front of the stack and synchronize the 628 // stack views immediately to load all the views 629 if (mAwaitingFirstLayout) { 630 mStackScroller.setStackScrollToInitialState(); 631 requestSynchronizeStackViewsWithModel(); 632 synchronizeStackViewsWithModel(); 633 } 634 635 // Measure each of the TaskViews 636 int childCount = getChildCount(); 637 for (int i = 0; i < childCount; i++) { 638 TaskView tv = (TaskView) getChildAt(i); 639 if (tv.getBackground() != null) { 640 tv.getBackground().getPadding(mTmpRect); 641 } else { 642 mTmpRect.setEmpty(); 643 } 644 tv.measure( 645 MeasureSpec.makeMeasureSpec( 646 mLayoutAlgorithm.mTaskRect.width() + mTmpRect.left + mTmpRect.right, 647 MeasureSpec.EXACTLY), 648 MeasureSpec.makeMeasureSpec( 649 mLayoutAlgorithm.mTaskRect.height() + mTmpRect.top + mTmpRect.bottom, 650 MeasureSpec.EXACTLY)); 651 } 652 653 setMeasuredDimension(width, height); 654 } 655 656 /** 657 * This is called with the size of the space not including the top or right insets, or the 658 * search bar height in portrait (but including the search bar width in landscape, since we want 659 * to draw under it. 660 */ 661 @Override onLayout(boolean changed, int left, int top, int right, int bottom)662 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 663 // Layout each of the children 664 int childCount = getChildCount(); 665 for (int i = 0; i < childCount; i++) { 666 TaskView tv = (TaskView) getChildAt(i); 667 if (tv.getBackground() != null) { 668 tv.getBackground().getPadding(mTmpRect); 669 } else { 670 mTmpRect.setEmpty(); 671 } 672 tv.layout(mLayoutAlgorithm.mTaskRect.left - mTmpRect.left, 673 mLayoutAlgorithm.mTaskRect.top - mTmpRect.top, 674 mLayoutAlgorithm.mTaskRect.right + mTmpRect.right, 675 mLayoutAlgorithm.mTaskRect.bottom + mTmpRect.bottom); 676 } 677 678 if (mAwaitingFirstLayout) { 679 mAwaitingFirstLayout = false; 680 onFirstLayout(); 681 } 682 } 683 684 /** Handler for the first layout. */ onFirstLayout()685 void onFirstLayout() { 686 int offscreenY = mLayoutAlgorithm.mViewRect.bottom - 687 (mLayoutAlgorithm.mTaskRect.top - mLayoutAlgorithm.mViewRect.top); 688 689 // Find the launch target task 690 Task launchTargetTask = null; 691 int childCount = getChildCount(); 692 for (int i = childCount - 1; i >= 0; i--) { 693 TaskView tv = (TaskView) getChildAt(i); 694 Task task = tv.getTask(); 695 if (task.isLaunchTarget) { 696 launchTargetTask = task; 697 break; 698 } 699 } 700 701 // Prepare the first view for its enter animation 702 for (int i = childCount - 1; i >= 0; i--) { 703 TaskView tv = (TaskView) getChildAt(i); 704 Task task = tv.getTask(); 705 boolean occludesLaunchTarget = (launchTargetTask != null) && 706 launchTargetTask.group.isTaskAboveTask(task, launchTargetTask); 707 tv.prepareEnterRecentsAnimation(task.isLaunchTarget, occludesLaunchTarget, offscreenY); 708 } 709 710 // If the enter animation started already and we haven't completed a layout yet, do the 711 // enter animation now 712 if (mStartEnterAnimationRequestedAfterLayout) { 713 startEnterRecentsAnimation(mStartEnterAnimationContext); 714 mStartEnterAnimationRequestedAfterLayout = false; 715 mStartEnterAnimationContext = null; 716 } 717 718 // When Alt-Tabbing, focus the previous task (but leave the animation until we finish the 719 // enter animation). 720 if (mConfig.launchedWithAltTab) { 721 if (mConfig.launchedFromAppWithThumbnail) { 722 focusTask(Math.max(0, mStack.getTaskCount() - 2), false, 723 mConfig.launchedHasConfigurationChanged); 724 } else { 725 focusTask(Math.max(0, mStack.getTaskCount() - 1), false, 726 mConfig.launchedHasConfigurationChanged); 727 } 728 } 729 730 // Start dozing 731 mUIDozeTrigger.startDozing(); 732 } 733 734 /** Requests this task stacks to start it's enter-recents animation */ startEnterRecentsAnimation(ViewAnimation.TaskViewEnterContext ctx)735 public void startEnterRecentsAnimation(ViewAnimation.TaskViewEnterContext ctx) { 736 // If we are still waiting to layout, then just defer until then 737 if (mAwaitingFirstLayout) { 738 mStartEnterAnimationRequestedAfterLayout = true; 739 mStartEnterAnimationContext = ctx; 740 return; 741 } 742 743 if (mStack.getTaskCount() > 0) { 744 // Find the launch target task 745 Task launchTargetTask = null; 746 int childCount = getChildCount(); 747 for (int i = childCount - 1; i >= 0; i--) { 748 TaskView tv = (TaskView) getChildAt(i); 749 Task task = tv.getTask(); 750 if (task.isLaunchTarget) { 751 launchTargetTask = task; 752 break; 753 } 754 } 755 756 // Animate all the task views into view 757 for (int i = childCount - 1; i >= 0; i--) { 758 TaskView tv = (TaskView) getChildAt(i); 759 Task task = tv.getTask(); 760 ctx.currentTaskTransform = new TaskViewTransform(); 761 ctx.currentStackViewIndex = i; 762 ctx.currentStackViewCount = childCount; 763 ctx.currentTaskRect = mLayoutAlgorithm.mTaskRect; 764 ctx.currentTaskOccludesLaunchTarget = (launchTargetTask != null) && 765 launchTargetTask.group.isTaskAboveTask(task, launchTargetTask); 766 ctx.updateListener = mRequestUpdateClippingListener; 767 mLayoutAlgorithm.getStackTransform(task, mStackScroller.getStackScroll(), ctx.currentTaskTransform, null); 768 tv.startEnterRecentsAnimation(ctx); 769 } 770 771 // Add a runnable to the post animation ref counter to clear all the views 772 ctx.postAnimationTrigger.addLastDecrementRunnable(new Runnable() { 773 @Override 774 public void run() { 775 mStartEnterAnimationCompleted = true; 776 // Poke the dozer to restart the trigger after the animation completes 777 mUIDozeTrigger.poke(); 778 779 RecentsTaskLoader loader = RecentsTaskLoader.getInstance(); 780 SystemServicesProxy ssp = loader.getSystemServicesProxy(); 781 int childCount = getChildCount(); 782 if (childCount > 0) { 783 // Focus the first view if accessibility is enabled 784 if (ssp.isTouchExplorationEnabled()) { 785 TaskView tv = ((TaskView) getChildAt(childCount - 1)); 786 tv.requestAccessibilityFocus(); 787 mPrevAccessibilityFocusedIndex = mStack.indexOfTask(tv.getTask()); 788 } 789 } 790 791 // Start the focus animation when alt-tabbing 792 if (mConfig.launchedWithAltTab && !mConfig.launchedHasConfigurationChanged) { 793 View tv = getChildAt(mFocusedTaskIndex); 794 if (tv != null) { 795 ((TaskView) tv).setFocusedTask(true); 796 } 797 } 798 } 799 }); 800 } 801 } 802 803 /** Requests this task stacks to start it's exit-recents animation. */ startExitToHomeAnimation(ViewAnimation.TaskViewExitContext ctx)804 public void startExitToHomeAnimation(ViewAnimation.TaskViewExitContext ctx) { 805 // Stop any scrolling 806 mStackScroller.stopScroller(); 807 mStackScroller.stopBoundScrollAnimation(); 808 // Animate all the task views out of view 809 ctx.offscreenTranslationY = mLayoutAlgorithm.mViewRect.bottom - 810 (mLayoutAlgorithm.mTaskRect.top - mLayoutAlgorithm.mViewRect.top); 811 int childCount = getChildCount(); 812 for (int i = 0; i < childCount; i++) { 813 TaskView tv = (TaskView) getChildAt(i); 814 tv.startExitToHomeAnimation(ctx); 815 } 816 } 817 818 /** Animates a task view in this stack as it launches. */ startLaunchTaskAnimation(TaskView tv, Runnable r, boolean lockToTask)819 public void startLaunchTaskAnimation(TaskView tv, Runnable r, boolean lockToTask) { 820 Task launchTargetTask = tv.getTask(); 821 int childCount = getChildCount(); 822 for (int i = 0; i < childCount; i++) { 823 TaskView t = (TaskView) getChildAt(i); 824 if (t == tv) { 825 t.setClipViewInStack(false); 826 t.startLaunchTaskAnimation(r, true, true, lockToTask); 827 } else { 828 boolean occludesLaunchTarget = launchTargetTask.group.isTaskAboveTask(t.getTask(), 829 launchTargetTask); 830 t.startLaunchTaskAnimation(null, false, occludesLaunchTarget, lockToTask); 831 } 832 } 833 } 834 835 /** Final callback after Recents is finally hidden. */ onRecentsHidden()836 void onRecentsHidden() { 837 reset(); 838 } 839 isTransformedTouchPointInView(float x, float y, View child)840 public boolean isTransformedTouchPointInView(float x, float y, View child) { 841 return isTransformedTouchPointInView(x, y, child, null); 842 } 843 844 /** Pokes the dozer on user interaction. */ onUserInteraction()845 void onUserInteraction() { 846 // Poke the doze trigger if it is dozing 847 mUIDozeTrigger.poke(); 848 } 849 850 /**** TaskStackCallbacks Implementation ****/ 851 852 @Override onStackTaskAdded(TaskStack stack, Task t)853 public void onStackTaskAdded(TaskStack stack, Task t) { 854 requestSynchronizeStackViewsWithModel(); 855 } 856 857 @Override onStackTaskRemoved(TaskStack stack, Task removedTask, Task newFrontMostTask)858 public void onStackTaskRemoved(TaskStack stack, Task removedTask, Task newFrontMostTask) { 859 // Remove the view associated with this task, we can't rely on updateTransforms 860 // to work here because the task is no longer in the list 861 TaskView tv = getChildViewForTask(removedTask); 862 if (tv != null) { 863 mViewPool.returnViewToPool(tv); 864 } 865 866 // Notify the callback that we've removed the task and it can clean up after it 867 mCb.onTaskViewDismissed(removedTask); 868 869 // Get the stack scroll of the task to anchor to (since we are removing something, the front 870 // most task will be our anchor task) 871 Task anchorTask = null; 872 float prevAnchorTaskScroll = 0; 873 boolean pullStackForward = stack.getTaskCount() > 0; 874 if (pullStackForward) { 875 anchorTask = mStack.getFrontMostTask(); 876 prevAnchorTaskScroll = mLayoutAlgorithm.getStackScrollForTask(anchorTask); 877 } 878 879 // Update the min/max scroll and animate other task views into their new positions 880 updateMinMaxScroll(true, mConfig.launchedWithAltTab, mConfig.launchedFromHome); 881 882 // Offset the stack by as much as the anchor task would otherwise move back 883 if (pullStackForward) { 884 float anchorTaskScroll = mLayoutAlgorithm.getStackScrollForTask(anchorTask); 885 mStackScroller.setStackScroll(mStackScroller.getStackScroll() + (anchorTaskScroll 886 - prevAnchorTaskScroll)); 887 mStackScroller.boundScroll(); 888 } 889 890 // Animate all the tasks into place 891 requestSynchronizeStackViewsWithModel(200); 892 893 // Update the new front most task 894 if (newFrontMostTask != null) { 895 TaskView frontTv = getChildViewForTask(newFrontMostTask); 896 if (frontTv != null) { 897 frontTv.onTaskBound(newFrontMostTask); 898 frontTv.fadeInActionButton(0, mConfig.taskViewEnterFromAppDuration); 899 } 900 } 901 902 // If there are no remaining tasks, then either unfilter the current stack, or just close 903 // the activity if there are no filtered stacks 904 if (mStack.getTaskCount() == 0) { 905 boolean shouldFinishActivity = true; 906 if (mStack.hasFilteredTasks()) { 907 mStack.unfilterTasks(); 908 shouldFinishActivity = (mStack.getTaskCount() == 0); 909 } 910 if (shouldFinishActivity) { 911 mCb.onAllTaskViewsDismissed(); 912 } 913 } 914 } 915 916 @Override onStackFiltered(TaskStack newStack, final ArrayList<Task> curTasks, Task filteredTask)917 public void onStackFiltered(TaskStack newStack, final ArrayList<Task> curTasks, 918 Task filteredTask) { 919 /* 920 // Stash the scroll and filtered task for us to restore to when we unfilter 921 mStashedScroll = getStackScroll(); 922 923 // Calculate the current task transforms 924 ArrayList<TaskViewTransform> curTaskTransforms = 925 getStackTransforms(curTasks, getStackScroll(), null, true); 926 927 // Update the task offsets 928 mLayoutAlgorithm.updateTaskOffsets(mStack.getTasks()); 929 930 // Scroll the item to the top of the stack (sans-peek) rect so that we can see it better 931 updateMinMaxScroll(false); 932 float overlapHeight = mLayoutAlgorithm.getTaskOverlapHeight(); 933 setStackScrollRaw((int) (newStack.indexOfTask(filteredTask) * overlapHeight)); 934 boundScrollRaw(); 935 936 // Compute the transforms of the items in the new stack after setting the new scroll 937 final ArrayList<Task> tasks = mStack.getTasks(); 938 final ArrayList<TaskViewTransform> taskTransforms = 939 getStackTransforms(mStack.getTasks(), getStackScroll(), null, true); 940 941 // Animate 942 mFilterAlgorithm.startFilteringAnimation(curTasks, curTaskTransforms, tasks, taskTransforms); 943 944 // Notify any callbacks 945 mCb.onTaskStackFilterTriggered(); 946 */ 947 } 948 949 @Override onStackUnfiltered(TaskStack newStack, final ArrayList<Task> curTasks)950 public void onStackUnfiltered(TaskStack newStack, final ArrayList<Task> curTasks) { 951 /* 952 // Calculate the current task transforms 953 final ArrayList<TaskViewTransform> curTaskTransforms = 954 getStackTransforms(curTasks, getStackScroll(), null, true); 955 956 // Update the task offsets 957 mLayoutAlgorithm.updateTaskOffsets(mStack.getTasks()); 958 959 // Restore the stashed scroll 960 updateMinMaxScroll(false); 961 setStackScrollRaw(mStashedScroll); 962 boundScrollRaw(); 963 964 // Compute the transforms of the items in the new stack after restoring the stashed scroll 965 final ArrayList<Task> tasks = mStack.getTasks(); 966 final ArrayList<TaskViewTransform> taskTransforms = 967 getStackTransforms(tasks, getStackScroll(), null, true); 968 969 // Animate 970 mFilterAlgorithm.startFilteringAnimation(curTasks, curTaskTransforms, tasks, taskTransforms); 971 972 // Clear the saved vars 973 mStashedScroll = 0; 974 975 // Notify any callbacks 976 mCb.onTaskStackUnfilterTriggered(); 977 */ 978 } 979 980 /**** ViewPoolConsumer Implementation ****/ 981 982 @Override createView(Context context)983 public TaskView createView(Context context) { 984 return (TaskView) mInflater.inflate(R.layout.recents_task_view, this, false); 985 } 986 987 @Override prepareViewToEnterPool(TaskView tv)988 public void prepareViewToEnterPool(TaskView tv) { 989 Task task = tv.getTask(); 990 991 // Clear the accessibility focus for that view 992 if (tv.isAccessibilityFocused()) { 993 tv.clearAccessibilityFocus(); 994 } 995 996 // Report that this tasks's data is no longer being used 997 RecentsTaskLoader.getInstance().unloadTaskData(task); 998 999 // Detach the view from the hierarchy 1000 detachViewFromParent(tv); 1001 1002 // Reset the view properties 1003 tv.resetViewProperties(); 1004 1005 // Reset the clip state of the task view 1006 tv.setClipViewInStack(false); 1007 } 1008 1009 @Override prepareViewToLeavePool(TaskView tv, Task task, boolean isNewView)1010 public void prepareViewToLeavePool(TaskView tv, Task task, boolean isNewView) { 1011 // It is possible for a view to be returned to the view pool before it is laid out, 1012 // which means that we will need to relayout the view when it is first used next. 1013 boolean requiresRelayout = tv.getWidth() <= 0 && !isNewView; 1014 1015 // Rebind the task and request that this task's data be filled into the TaskView 1016 tv.onTaskBound(task); 1017 1018 // Load the task data 1019 RecentsTaskLoader.getInstance().loadTaskData(task); 1020 1021 // If the doze trigger has already fired, then update the state for this task view 1022 if (mUIDozeTrigger.hasTriggered()) { 1023 tv.setNoUserInteractionState(); 1024 } 1025 1026 // If we've finished the start animation, then ensure we always enable the focus animations 1027 if (mStartEnterAnimationCompleted) { 1028 tv.enableFocusAnimations(); 1029 } 1030 1031 // Find the index where this task should be placed in the stack 1032 int insertIndex = -1; 1033 int taskIndex = mStack.indexOfTask(task); 1034 if (taskIndex != -1) { 1035 int childCount = getChildCount(); 1036 for (int i = 0; i < childCount; i++) { 1037 Task tvTask = ((TaskView) getChildAt(i)).getTask(); 1038 if (taskIndex < mStack.indexOfTask(tvTask)) { 1039 insertIndex = i; 1040 break; 1041 } 1042 } 1043 } 1044 1045 // Add/attach the view to the hierarchy 1046 if (isNewView) { 1047 addView(tv, insertIndex); 1048 } else { 1049 attachViewToParent(tv, insertIndex, tv.getLayoutParams()); 1050 if (requiresRelayout) { 1051 tv.requestLayout(); 1052 } 1053 } 1054 1055 // Set the new state for this view, including the callbacks and view clipping 1056 tv.setCallbacks(this); 1057 tv.setTouchEnabled(true); 1058 tv.setClipViewInStack(true); 1059 } 1060 1061 @Override hasPreferredData(TaskView tv, Task preferredData)1062 public boolean hasPreferredData(TaskView tv, Task preferredData) { 1063 return (tv.getTask() == preferredData); 1064 } 1065 1066 /**** TaskViewCallbacks Implementation ****/ 1067 1068 @Override onTaskViewAppIconClicked(TaskView tv)1069 public void onTaskViewAppIconClicked(TaskView tv) { 1070 if (Constants.DebugFlags.App.EnableTaskFiltering) { 1071 if (mStack.hasFilteredTasks()) { 1072 mStack.unfilterTasks(); 1073 } else { 1074 mStack.filterTasks(tv.getTask()); 1075 } 1076 } 1077 } 1078 1079 @Override onTaskViewAppInfoClicked(TaskView tv)1080 public void onTaskViewAppInfoClicked(TaskView tv) { 1081 if (mCb != null) { 1082 mCb.onTaskViewAppInfoClicked(tv.getTask()); 1083 } 1084 } 1085 1086 @Override onTaskViewClicked(TaskView tv, Task task, boolean lockToTask)1087 public void onTaskViewClicked(TaskView tv, Task task, boolean lockToTask) { 1088 // Cancel any doze triggers 1089 mUIDozeTrigger.stopDozing(); 1090 1091 if (mCb != null) { 1092 mCb.onTaskViewClicked(this, tv, mStack, task, lockToTask); 1093 } 1094 } 1095 1096 @Override onTaskViewDismissed(TaskView tv)1097 public void onTaskViewDismissed(TaskView tv) { 1098 Task task = tv.getTask(); 1099 int taskIndex = mStack.indexOfTask(task); 1100 boolean taskWasFocused = tv.isFocusedTask(); 1101 // Announce for accessibility 1102 tv.announceForAccessibility(getContext().getString(R.string.accessibility_recents_item_dismissed, 1103 tv.getTask().activityLabel)); 1104 // Remove the task from the view 1105 mStack.removeTask(task); 1106 // If the dismissed task was focused, then we should focus the new task in the same index 1107 if (taskWasFocused) { 1108 ArrayList<Task> tasks = mStack.getTasks(); 1109 int nextTaskIndex = Math.min(tasks.size() - 1, taskIndex - 1); 1110 if (nextTaskIndex >= 0) { 1111 Task nextTask = tasks.get(nextTaskIndex); 1112 TaskView nextTv = getChildViewForTask(nextTask); 1113 if (nextTv != null) { 1114 // Focus the next task, and only animate the visible state if we are launched 1115 // from Alt-Tab 1116 nextTv.setFocusedTask(mConfig.launchedWithAltTab); 1117 } 1118 } 1119 } 1120 } 1121 1122 @Override onTaskViewClipStateChanged(TaskView tv)1123 public void onTaskViewClipStateChanged(TaskView tv) { 1124 if (!mStackViewsDirty) { 1125 invalidate(); 1126 } 1127 } 1128 1129 @Override onTaskViewFocusChanged(TaskView tv, boolean focused)1130 public void onTaskViewFocusChanged(TaskView tv, boolean focused) { 1131 if (focused) { 1132 mFocusedTaskIndex = mStack.indexOfTask(tv.getTask()); 1133 } 1134 } 1135 1136 /**** TaskStackViewScroller.TaskStackViewScrollerCallbacks ****/ 1137 1138 @Override onScrollChanged(float p)1139 public void onScrollChanged(float p) { 1140 mUIDozeTrigger.poke(); 1141 requestSynchronizeStackViewsWithModel(); 1142 postInvalidateOnAnimation(); 1143 } 1144 1145 /**** RecentsPackageMonitor.PackageCallbacks Implementation ****/ 1146 1147 @Override onPackagesChanged(RecentsPackageMonitor monitor, String packageName, int userId)1148 public void onPackagesChanged(RecentsPackageMonitor monitor, String packageName, int userId) { 1149 // Compute which components need to be removed 1150 HashSet<ComponentName> removedComponents = monitor.computeComponentsRemoved( 1151 mStack.getTaskKeys(), packageName, userId); 1152 1153 // For other tasks, just remove them directly if they no longer exist 1154 ArrayList<Task> tasks = mStack.getTasks(); 1155 for (int i = tasks.size() - 1; i >= 0; i--) { 1156 final Task t = tasks.get(i); 1157 if (removedComponents.contains(t.key.baseIntent.getComponent())) { 1158 TaskView tv = getChildViewForTask(t); 1159 if (tv != null) { 1160 // For visible children, defer removing the task until after the animation 1161 tv.startDeleteTaskAnimation(new Runnable() { 1162 @Override 1163 public void run() { 1164 mStack.removeTask(t); 1165 } 1166 }); 1167 } else { 1168 // Otherwise, remove the task from the stack immediately 1169 mStack.removeTask(t); 1170 } 1171 } 1172 } 1173 } 1174 } 1175