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