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