TaskStackView.java revision 7ae4b7e1965951ac74fc10204bb238ff174b52c6
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 // 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 int childCount = getChildCount(); 567 if (childCount > 0) { 568 TaskView backMostTask = (TaskView) getChildAt(0); 569 TaskView frontMostTask = (TaskView) getChildAt(childCount - 1); 570 event.setFromIndex(mStack.indexOfTask(backMostTask.getTask())); 571 event.setToIndex(mStack.indexOfTask(frontMostTask.getTask())); 572 event.setContentDescription(frontMostTask.getTask().activityLabel); 573 } 574 event.setItemCount(mStack.getTaskCount()); 575 event.setScrollY(mStackScroller.mScroller.getCurrY()); 576 event.setMaxScrollY(mStackScroller.progressToScrollRange(mLayoutAlgorithm.mMaxScrollP)); 577 } 578 579 @Override 580 public boolean onInterceptTouchEvent(MotionEvent ev) { 581 return mTouchHandler.onInterceptTouchEvent(ev); 582 } 583 584 @Override 585 public boolean onTouchEvent(MotionEvent ev) { 586 return mTouchHandler.onTouchEvent(ev); 587 } 588 589 @Override 590 public boolean onGenericMotionEvent(MotionEvent ev) { 591 return mTouchHandler.onGenericMotionEvent(ev); 592 } 593 594 @Override 595 public void computeScroll() { 596 if (mStack == null) return; 597 598 mStackScroller.computeScroll(); 599 // Synchronize the views 600 synchronizeStackViewsWithModel(); 601 clipTaskViews(); 602 // Notify accessibility 603 sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SCROLLED); 604 } 605 606 /** Computes the stack and task rects */ 607 public void computeRects(int windowWidth, int windowHeight, Rect taskStackBounds, 608 boolean launchedWithAltTab, boolean launchedFromHome) { 609 // Compute the rects in the stack algorithm 610 mLayoutAlgorithm.computeRects(windowWidth, windowHeight, taskStackBounds); 611 612 // Update the scroll bounds 613 updateMinMaxScroll(false, launchedWithAltTab, launchedFromHome); 614 } 615 616 /** 617 * This is ONLY used from AlternateRecentsComponent to update the dummy stack view for purposes 618 * of getting the task rect to animate to. 619 */ 620 public void updateMinMaxScrollForStack(TaskStack stack, boolean launchedWithAltTab, 621 boolean launchedFromHome) { 622 mStack = stack; 623 updateMinMaxScroll(false, launchedWithAltTab, launchedFromHome); 624 } 625 626 /** 627 * This is called with the full window width and height to allow stack view children to 628 * perform the full screen transition down. 629 */ 630 @Override 631 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 632 int width = MeasureSpec.getSize(widthMeasureSpec); 633 int height = MeasureSpec.getSize(heightMeasureSpec); 634 635 // Compute our stack/task rects 636 Rect taskStackBounds = new Rect(mTaskStackBounds); 637 taskStackBounds.bottom -= mConfig.systemInsets.bottom; 638 computeRects(width, height, taskStackBounds, mConfig.launchedWithAltTab, 639 mConfig.launchedFromHome); 640 641 // If this is the first layout, then scroll to the front of the stack and synchronize the 642 // stack views immediately to load all the views 643 if (mAwaitingFirstLayout) { 644 mStackScroller.setStackScrollToInitialState(); 645 requestSynchronizeStackViewsWithModel(); 646 synchronizeStackViewsWithModel(); 647 } 648 649 // Measure each of the TaskViews 650 int childCount = getChildCount(); 651 for (int i = 0; i < childCount; i++) { 652 TaskView tv = (TaskView) getChildAt(i); 653 if (tv.getBackground() != null) { 654 tv.getBackground().getPadding(mTmpRect); 655 } else { 656 mTmpRect.setEmpty(); 657 } 658 tv.measure( 659 MeasureSpec.makeMeasureSpec( 660 mLayoutAlgorithm.mTaskRect.width() + mTmpRect.left + mTmpRect.right, 661 MeasureSpec.EXACTLY), 662 MeasureSpec.makeMeasureSpec( 663 mLayoutAlgorithm.mTaskRect.height() + mTmpRect.top + mTmpRect.bottom, 664 MeasureSpec.EXACTLY)); 665 } 666 667 setMeasuredDimension(width, height); 668 } 669 670 /** 671 * This is called with the size of the space not including the top or right insets, or the 672 * search bar height in portrait (but including the search bar width in landscape, since we want 673 * to draw under it. 674 */ 675 @Override 676 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 677 // Layout each of the children 678 int childCount = getChildCount(); 679 for (int i = 0; i < childCount; i++) { 680 TaskView tv = (TaskView) getChildAt(i); 681 if (tv.getBackground() != null) { 682 tv.getBackground().getPadding(mTmpRect); 683 } else { 684 mTmpRect.setEmpty(); 685 } 686 tv.layout(mLayoutAlgorithm.mTaskRect.left - mTmpRect.left, 687 mLayoutAlgorithm.mTaskRect.top - mTmpRect.top, 688 mLayoutAlgorithm.mTaskRect.right + mTmpRect.right, 689 mLayoutAlgorithm.mTaskRect.bottom + mTmpRect.bottom); 690 } 691 692 if (mAwaitingFirstLayout) { 693 mAwaitingFirstLayout = false; 694 onFirstLayout(); 695 } 696 } 697 698 /** Handler for the first layout. */ 699 void onFirstLayout() { 700 int offscreenY = mLayoutAlgorithm.mViewRect.bottom - 701 (mLayoutAlgorithm.mTaskRect.top - mLayoutAlgorithm.mViewRect.top); 702 703 // Find the launch target task 704 Task launchTargetTask = null; 705 int childCount = getChildCount(); 706 for (int i = childCount - 1; i >= 0; i--) { 707 TaskView tv = (TaskView) getChildAt(i); 708 Task task = tv.getTask(); 709 if (task.isLaunchTarget) { 710 launchTargetTask = task; 711 break; 712 } 713 } 714 715 // Prepare the first view for its enter animation 716 for (int i = childCount - 1; i >= 0; i--) { 717 TaskView tv = (TaskView) getChildAt(i); 718 Task task = tv.getTask(); 719 boolean occludesLaunchTarget = (launchTargetTask != null) && 720 launchTargetTask.group.isTaskAboveTask(task, launchTargetTask); 721 tv.prepareEnterRecentsAnimation(task.isLaunchTarget, occludesLaunchTarget, offscreenY); 722 } 723 724 // If the enter animation started already and we haven't completed a layout yet, do the 725 // enter animation now 726 if (mStartEnterAnimationRequestedAfterLayout) { 727 startEnterRecentsAnimation(mStartEnterAnimationContext); 728 mStartEnterAnimationRequestedAfterLayout = false; 729 mStartEnterAnimationContext = null; 730 } 731 732 // When Alt-Tabbing, we scroll to and focus the previous task 733 if (mConfig.launchedWithAltTab) { 734 if (mConfig.launchedFromHome) { 735 focusTask(Math.max(0, mStack.getTaskCount() - 1), false, true); 736 } else { 737 focusTask(Math.max(0, mStack.getTaskCount() - 2), false, true); 738 } 739 } 740 } 741 742 /** Requests this task stacks to start it's enter-recents animation */ 743 public void startEnterRecentsAnimation(ViewAnimation.TaskViewEnterContext ctx) { 744 // If we are still waiting to layout, then just defer until then 745 if (mAwaitingFirstLayout) { 746 mStartEnterAnimationRequestedAfterLayout = true; 747 mStartEnterAnimationContext = ctx; 748 return; 749 } 750 751 if (mStack.getTaskCount() > 0) { 752 // Find the launch target task 753 Task launchTargetTask = null; 754 int childCount = getChildCount(); 755 for (int i = childCount - 1; i >= 0; i--) { 756 TaskView tv = (TaskView) getChildAt(i); 757 Task task = tv.getTask(); 758 if (task.isLaunchTarget) { 759 launchTargetTask = task; 760 break; 761 } 762 } 763 764 // Animate all the task views into view 765 for (int i = childCount - 1; i >= 0; i--) { 766 TaskView tv = (TaskView) getChildAt(i); 767 Task task = tv.getTask(); 768 ctx.currentTaskTransform = new TaskViewTransform(); 769 ctx.currentStackViewIndex = i; 770 ctx.currentStackViewCount = childCount; 771 ctx.currentTaskRect = mLayoutAlgorithm.mTaskRect; 772 ctx.currentTaskOccludesLaunchTarget = (launchTargetTask != null) && 773 launchTargetTask.group.isTaskAboveTask(task, launchTargetTask); 774 ctx.updateListener = mRequestUpdateClippingListener; 775 mLayoutAlgorithm.getStackTransform(task, mStackScroller.getStackScroll(), ctx.currentTaskTransform, null); 776 tv.startEnterRecentsAnimation(ctx); 777 } 778 779 // Add a runnable to the post animation ref counter to clear all the views 780 ctx.postAnimationTrigger.addLastDecrementRunnable(new Runnable() { 781 @Override 782 public void run() { 783 mStartEnterAnimationCompleted = true; 784 // Start dozing 785 mUIDozeTrigger.startDozing(); 786 // Focus the first view if accessibility is enabled 787 RecentsTaskLoader loader = RecentsTaskLoader.getInstance(); 788 SystemServicesProxy ssp = loader.getSystemServicesProxy(); 789 int childCount = getChildCount(); 790 if (childCount > 0 && ssp.isTouchExplorationEnabled()) { 791 TaskView tv = ((TaskView) getChildAt(childCount - 1)); 792 tv.requestAccessibilityFocus(); 793 mPrevAccessibilityFocusedIndex = mStack.indexOfTask(tv.getTask()); 794 } 795 } 796 }); 797 } 798 } 799 800 /** Requests this task stacks to start it's exit-recents animation. */ 801 public void startExitToHomeAnimation(ViewAnimation.TaskViewExitContext ctx) { 802 // Stop any scrolling 803 mStackScroller.stopScroller(); 804 mStackScroller.stopBoundScrollAnimation(); 805 // Animate all the task views out of view 806 ctx.offscreenTranslationY = mLayoutAlgorithm.mViewRect.bottom - 807 (mLayoutAlgorithm.mTaskRect.top - mLayoutAlgorithm.mViewRect.top); 808 int childCount = getChildCount(); 809 for (int i = 0; i < childCount; i++) { 810 TaskView tv = (TaskView) getChildAt(i); 811 tv.startExitToHomeAnimation(ctx); 812 } 813 } 814 815 /** Animates a task view in this stack as it launches. */ 816 public void startLaunchTaskAnimation(TaskView tv, Runnable r, boolean lockToTask) { 817 Task launchTargetTask = tv.getTask(); 818 int childCount = getChildCount(); 819 for (int i = 0; i < childCount; i++) { 820 TaskView t = (TaskView) getChildAt(i); 821 if (t == tv) { 822 t.setClipViewInStack(false); 823 t.startLaunchTaskAnimation(r, true, true, lockToTask); 824 } else { 825 boolean occludesLaunchTarget = launchTargetTask.group.isTaskAboveTask(t.getTask(), 826 launchTargetTask); 827 t.startLaunchTaskAnimation(null, false, occludesLaunchTarget, lockToTask); 828 } 829 } 830 } 831 832 /** Final callback after Recents is finally hidden. */ 833 void onRecentsHidden() { 834 reset(); 835 setStack(null); 836 } 837 838 public boolean isTransformedTouchPointInView(float x, float y, View child) { 839 return isTransformedTouchPointInView(x, y, child, null); 840 } 841 842 /** Pokes the dozer on user interaction. */ 843 void onUserInteraction() { 844 // Poke the doze trigger if it is dozing 845 mUIDozeTrigger.poke(); 846 } 847 848 /**** TaskStackCallbacks Implementation ****/ 849 850 @Override 851 public void onStackTaskAdded(TaskStack stack, Task t) { 852 requestSynchronizeStackViewsWithModel(); 853 } 854 855 @Override 856 public void onStackTaskRemoved(TaskStack stack, Task removedTask, Task newFrontMostTask) { 857 // Remove the view associated with this task, we can't rely on updateTransforms 858 // to work here because the task is no longer in the list 859 TaskView tv = getChildViewForTask(removedTask); 860 if (tv != null) { 861 mViewPool.returnViewToPool(tv); 862 } 863 864 // Notify the callback that we've removed the task and it can clean up after it 865 mCb.onTaskViewDismissed(removedTask); 866 867 // Get the stack scroll of the task to anchor to (since we are removing something, the front 868 // most task will be our anchor task) 869 Task anchorTask = null; 870 float prevAnchorTaskScroll = 0; 871 boolean pullStackForward = stack.getTaskCount() > 0; 872 if (pullStackForward) { 873 anchorTask = mStack.getFrontMostTask(); 874 prevAnchorTaskScroll = mLayoutAlgorithm.getStackScrollForTask(anchorTask); 875 } 876 877 // Update the min/max scroll and animate other task views into their new positions 878 updateMinMaxScroll(true, mConfig.launchedWithAltTab, mConfig.launchedFromHome); 879 880 // Offset the stack by as much as the anchor task would otherwise move back 881 if (pullStackForward) { 882 float anchorTaskScroll = mLayoutAlgorithm.getStackScrollForTask(anchorTask); 883 mStackScroller.setStackScroll(mStackScroller.getStackScroll() + (anchorTaskScroll 884 - prevAnchorTaskScroll)); 885 mStackScroller.boundScroll(); 886 } 887 888 // Animate all the tasks into place 889 requestSynchronizeStackViewsWithModel(200); 890 891 // Update the new front most task 892 if (newFrontMostTask != null) { 893 TaskView frontTv = getChildViewForTask(newFrontMostTask); 894 if (frontTv != null) { 895 frontTv.onTaskBound(newFrontMostTask); 896 frontTv.fadeInActionButton(false); 897 } 898 } 899 900 // If there are no remaining tasks, then either unfilter the current stack, or just close 901 // the activity if there are no filtered stacks 902 if (mStack.getTaskCount() == 0) { 903 boolean shouldFinishActivity = true; 904 if (mStack.hasFilteredTasks()) { 905 mStack.unfilterTasks(); 906 shouldFinishActivity = (mStack.getTaskCount() == 0); 907 } 908 if (shouldFinishActivity) { 909 mCb.onAllTaskViewsDismissed(); 910 } 911 } 912 } 913 914 @Override 915 public void onStackFiltered(TaskStack newStack, final ArrayList<Task> curTasks, 916 Task filteredTask) { 917 /* 918 // Stash the scroll and filtered task for us to restore to when we unfilter 919 mStashedScroll = getStackScroll(); 920 921 // Calculate the current task transforms 922 ArrayList<TaskViewTransform> curTaskTransforms = 923 getStackTransforms(curTasks, getStackScroll(), null, true); 924 925 // Update the task offsets 926 mLayoutAlgorithm.updateTaskOffsets(mStack.getTasks()); 927 928 // Scroll the item to the top of the stack (sans-peek) rect so that we can see it better 929 updateMinMaxScroll(false); 930 float overlapHeight = mLayoutAlgorithm.getTaskOverlapHeight(); 931 setStackScrollRaw((int) (newStack.indexOfTask(filteredTask) * overlapHeight)); 932 boundScrollRaw(); 933 934 // Compute the transforms of the items in the new stack after setting the new scroll 935 final ArrayList<Task> tasks = mStack.getTasks(); 936 final ArrayList<TaskViewTransform> taskTransforms = 937 getStackTransforms(mStack.getTasks(), getStackScroll(), null, true); 938 939 // Animate 940 mFilterAlgorithm.startFilteringAnimation(curTasks, curTaskTransforms, tasks, taskTransforms); 941 942 // Notify any callbacks 943 mCb.onTaskStackFilterTriggered(); 944 */ 945 } 946 947 @Override 948 public void onStackUnfiltered(TaskStack newStack, final ArrayList<Task> curTasks) { 949 /* 950 // Calculate the current task transforms 951 final ArrayList<TaskViewTransform> curTaskTransforms = 952 getStackTransforms(curTasks, getStackScroll(), null, true); 953 954 // Update the task offsets 955 mLayoutAlgorithm.updateTaskOffsets(mStack.getTasks()); 956 957 // Restore the stashed scroll 958 updateMinMaxScroll(false); 959 setStackScrollRaw(mStashedScroll); 960 boundScrollRaw(); 961 962 // Compute the transforms of the items in the new stack after restoring the stashed scroll 963 final ArrayList<Task> tasks = mStack.getTasks(); 964 final ArrayList<TaskViewTransform> taskTransforms = 965 getStackTransforms(tasks, getStackScroll(), null, true); 966 967 // Animate 968 mFilterAlgorithm.startFilteringAnimation(curTasks, curTaskTransforms, tasks, taskTransforms); 969 970 // Clear the saved vars 971 mStashedScroll = 0; 972 973 // Notify any callbacks 974 mCb.onTaskStackUnfilterTriggered(); 975 */ 976 } 977 978 /**** ViewPoolConsumer Implementation ****/ 979 980 @Override 981 public TaskView createView(Context context) { 982 return (TaskView) mInflater.inflate(R.layout.recents_task_view, this, false); 983 } 984 985 @Override 986 public void prepareViewToEnterPool(TaskView tv) { 987 Task task = tv.getTask(); 988 989 // Clear the accessibility focus for that view 990 if (tv.isAccessibilityFocused()) { 991 tv.clearAccessibilityFocus(); 992 } 993 994 // Report that this tasks's data is no longer being used 995 RecentsTaskLoader.getInstance().unloadTaskData(task); 996 997 // Detach the view from the hierarchy 998 detachViewFromParent(tv); 999 1000 // Reset the view properties 1001 tv.resetViewProperties(); 1002 1003 // Reset the clip state of the task view 1004 tv.setClipViewInStack(false); 1005 } 1006 1007 @Override 1008 public void prepareViewToLeavePool(TaskView tv, Task task, boolean isNewView) { 1009 // It is possible for a view to be returned to the view pool before it is laid out, 1010 // which means that we will need to relayout the view when it is first used next. 1011 boolean requiresRelayout = tv.getWidth() <= 0 && !isNewView; 1012 1013 // Rebind the task and request that this task's data be filled into the TaskView 1014 tv.onTaskBound(task); 1015 1016 // Load the task data 1017 RecentsTaskLoader.getInstance().loadTaskData(task); 1018 1019 // If the doze trigger has already fired, then update the state for this task view 1020 if (mUIDozeTrigger.hasTriggered()) { 1021 tv.setNoUserInteractionState(); 1022 } 1023 1024 // If we've finished the start animation, then ensure we always enable the focus animations 1025 if (mStartEnterAnimationCompleted) { 1026 tv.enableFocusAnimations(); 1027 } 1028 1029 // Find the index where this task should be placed in the stack 1030 int insertIndex = -1; 1031 int taskIndex = mStack.indexOfTask(task); 1032 if (taskIndex != -1) { 1033 int childCount = getChildCount(); 1034 for (int i = 0; i < childCount; i++) { 1035 Task tvTask = ((TaskView) getChildAt(i)).getTask(); 1036 if (taskIndex < mStack.indexOfTask(tvTask)) { 1037 insertIndex = i; 1038 break; 1039 } 1040 } 1041 } 1042 1043 // Add/attach the view to the hierarchy 1044 if (isNewView) { 1045 addView(tv, insertIndex); 1046 } else { 1047 attachViewToParent(tv, insertIndex, tv.getLayoutParams()); 1048 if (requiresRelayout) { 1049 tv.requestLayout(); 1050 } 1051 } 1052 1053 // Set the new state for this view, including the callbacks and view clipping 1054 tv.setCallbacks(this); 1055 tv.setTouchEnabled(true); 1056 tv.setClipViewInStack(true); 1057 } 1058 1059 @Override 1060 public boolean hasPreferredData(TaskView tv, Task preferredData) { 1061 return (tv.getTask() == preferredData); 1062 } 1063 1064 /**** TaskViewCallbacks Implementation ****/ 1065 1066 @Override 1067 public void onTaskViewAppIconClicked(TaskView tv) { 1068 if (Constants.DebugFlags.App.EnableTaskFiltering) { 1069 if (mStack.hasFilteredTasks()) { 1070 mStack.unfilterTasks(); 1071 } else { 1072 mStack.filterTasks(tv.getTask()); 1073 } 1074 } 1075 } 1076 1077 @Override 1078 public void onTaskViewAppInfoClicked(TaskView tv) { 1079 if (mCb != null) { 1080 mCb.onTaskViewAppInfoClicked(tv.getTask()); 1081 } 1082 } 1083 1084 @Override 1085 public void onTaskViewClicked(TaskView tv, Task task, boolean lockToTask) { 1086 // Cancel any doze triggers 1087 mUIDozeTrigger.stopDozing(); 1088 1089 if (mCb != null) { 1090 mCb.onTaskViewClicked(this, tv, mStack, task, lockToTask); 1091 } 1092 } 1093 1094 @Override 1095 public void onTaskViewDismissed(TaskView tv) { 1096 Task task = tv.getTask(); 1097 int taskIndex = mStack.indexOfTask(task); 1098 boolean taskWasFocused = tv.isFocusedTask(); 1099 // Announce for accessibility 1100 tv.announceForAccessibility(getContext().getString(R.string.accessibility_recents_item_dismissed, 1101 tv.getTask().activityLabel)); 1102 // Remove the task from the view 1103 mStack.removeTask(task); 1104 // If the dismissed task was focused, then we should focus the new task in the same index 1105 if (taskWasFocused) { 1106 ArrayList<Task> tasks = mStack.getTasks(); 1107 int nextTaskIndex = Math.min(tasks.size() - 1, taskIndex - 1); 1108 if (nextTaskIndex >= 0) { 1109 Task nextTask = tasks.get(nextTaskIndex); 1110 TaskView nextTv = getChildViewForTask(nextTask); 1111 if (nextTv != null) { 1112 // Focus the next task, and only animate the visible state if we are launched 1113 // from Alt-Tab 1114 nextTv.setFocusedTask(mConfig.launchedWithAltTab); 1115 } 1116 } 1117 } 1118 } 1119 1120 @Override 1121 public void onTaskViewClipStateChanged(TaskView tv) { 1122 if (!mStackViewsDirty) { 1123 invalidate(); 1124 } 1125 } 1126 1127 @Override 1128 public void onTaskViewFocusChanged(TaskView tv, boolean focused) { 1129 if (focused) { 1130 mFocusedTaskIndex = mStack.indexOfTask(tv.getTask()); 1131 } 1132 } 1133 1134 /**** TaskStackViewScroller.TaskStackViewScrollerCallbacks ****/ 1135 1136 @Override 1137 public void onScrollChanged(float p) { 1138 mUIDozeTrigger.poke(); 1139 requestSynchronizeStackViewsWithModel(); 1140 postInvalidateOnAnimation(); 1141 } 1142 1143 /**** RecentsPackageMonitor.PackageCallbacks Implementation ****/ 1144 1145 @Override 1146 public void onPackagesChanged(RecentsPackageMonitor monitor, String packageName, int userId) { 1147 // Compute which components need to be removed 1148 HashSet<ComponentName> removedComponents = monitor.computeComponentsRemoved( 1149 mStack.getTaskKeys(), packageName, userId); 1150 1151 // For other tasks, just remove them directly if they no longer exist 1152 ArrayList<Task> tasks = mStack.getTasks(); 1153 for (int i = tasks.size() - 1; i >= 0; i--) { 1154 final Task t = tasks.get(i); 1155 if (removedComponents.contains(t.key.baseIntent.getComponent())) { 1156 TaskView tv = getChildViewForTask(t); 1157 if (tv != null) { 1158 // For visible children, defer removing the task until after the animation 1159 tv.startDeleteTaskAnimation(new Runnable() { 1160 @Override 1161 public void run() { 1162 mStack.removeTask(t); 1163 } 1164 }); 1165 } else { 1166 // Otherwise, remove the task from the stack immediately 1167 mStack.removeTask(t); 1168 } 1169 } 1170 } 1171 } 1172} 1173