TaskStackView.java revision 1907cd478209f01a4215d9a3a76294c2c77c9a63
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.Animator; 20import android.animation.AnimatorListenerAdapter; 21import android.animation.ObjectAnimator; 22import android.animation.ValueAnimator; 23import android.content.ComponentName; 24import android.content.Context; 25import android.graphics.Canvas; 26import android.graphics.Rect; 27import android.view.LayoutInflater; 28import android.view.MotionEvent; 29import android.view.View; 30import android.widget.FrameLayout; 31import android.widget.OverScroller; 32import com.android.systemui.R; 33import com.android.systemui.recents.Constants; 34import com.android.systemui.recents.RecentsConfiguration; 35import com.android.systemui.recents.misc.Console; 36import com.android.systemui.recents.misc.DozeTrigger; 37import com.android.systemui.recents.misc.ReferenceCountedTrigger; 38import com.android.systemui.recents.misc.Utilities; 39import com.android.systemui.recents.model.RecentsPackageMonitor; 40import com.android.systemui.recents.model.RecentsTaskLoader; 41import com.android.systemui.recents.model.Task; 42import com.android.systemui.recents.model.TaskStack; 43 44import java.util.ArrayList; 45import java.util.HashMap; 46import java.util.HashSet; 47 48 49/* The visual representation of a task stack view */ 50public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCallbacks, 51 TaskView.TaskViewCallbacks, ViewPool.ViewPoolConsumer<TaskView, Task>, 52 RecentsPackageMonitor.PackageCallbacks { 53 54 /** The TaskView callbacks */ 55 interface TaskStackViewCallbacks { 56 public void onTaskViewClicked(TaskStackView stackView, TaskView tv, TaskStack stack, Task t, 57 boolean lockToTask); 58 public void onTaskViewAppInfoClicked(Task t); 59 public void onTaskViewDismissed(Task t); 60 public void onAllTaskViewsDismissed(); 61 public void onTaskStackFilterTriggered(); 62 public void onTaskStackUnfilterTriggered(); 63 } 64 65 RecentsConfiguration mConfig; 66 67 TaskStack mStack; 68 TaskStackViewLayoutAlgorithm mStackAlgorithm; 69 TaskStackViewFilterAlgorithm mFilterAlgorithm; 70 TaskStackViewTouchHandler mTouchHandler; 71 TaskStackViewCallbacks mCb; 72 ViewPool<TaskView, Task> mViewPool; 73 ArrayList<TaskViewTransform> mCurrentTaskTransforms = new ArrayList<TaskViewTransform>(); 74 DozeTrigger mUIDozeTrigger; 75 DebugOverlayView mDebugOverlay; 76 Rect mTaskStackBounds = new Rect(); 77 78 // The virtual stack scroll that we use for the card layout 79 int mStackScroll; 80 int mMinScroll; 81 int mMaxScroll; 82 int mStashedScroll; 83 int mFocusedTaskIndex = -1; 84 OverScroller mScroller; 85 ObjectAnimator mScrollAnimator; 86 87 // Optimizations 88 ReferenceCountedTrigger mHwLayersTrigger; 89 int mStackViewsAnimationDuration; 90 boolean mStackViewsDirty = true; 91 boolean mAwaitingFirstLayout = true; 92 boolean mStartEnterAnimationRequestedAfterLayout; 93 ViewAnimation.TaskViewEnterContext mStartEnterAnimationContext; 94 int[] mTmpVisibleRange = new int[2]; 95 TaskViewTransform mTmpTransform = new TaskViewTransform(); 96 HashMap<Task, TaskView> mTmpTaskViewMap = new HashMap<Task, TaskView>(); 97 LayoutInflater mInflater; 98 99 // A convenience runnable to return all views to the pool 100 Runnable mReturnAllViewsToPoolRunnable = new Runnable() { 101 @Override 102 public void run() { 103 int childCount = getChildCount(); 104 for (int i = childCount - 1; i >= 0; i--) { 105 TaskView tv = (TaskView) getChildAt(i); 106 mViewPool.returnViewToPool(tv); 107 // Also hide the view since we don't need it anymore 108 tv.setVisibility(View.INVISIBLE); 109 } 110 } 111 }; 112 113 public TaskStackView(Context context, TaskStack stack) { 114 super(context); 115 mConfig = RecentsConfiguration.getInstance(); 116 mStack = stack; 117 mStack.setCallbacks(this); 118 mScroller = new OverScroller(context); 119 mTouchHandler = new TaskStackViewTouchHandler(context, this); 120 mViewPool = new ViewPool<TaskView, Task>(context, this); 121 mInflater = LayoutInflater.from(context); 122 mStackAlgorithm = new TaskStackViewLayoutAlgorithm(mConfig); 123 mFilterAlgorithm = new TaskStackViewFilterAlgorithm(mConfig, this, mViewPool); 124 mUIDozeTrigger = new DozeTrigger(mConfig.taskBarDismissDozeDelaySeconds, new Runnable() { 125 @Override 126 public void run() { 127 // Show the task bar dismiss buttons 128 int childCount = getChildCount(); 129 for (int i = 0; i < childCount; i++) { 130 TaskView tv = (TaskView) getChildAt(i); 131 tv.startNoUserInteractionAnimation(); 132 } 133 } 134 }); 135 } 136 137 /** Sets the callbacks */ 138 void setCallbacks(TaskStackViewCallbacks cb) { 139 mCb = cb; 140 } 141 142 /** Sets the debug overlay */ 143 public void setDebugOverlay(DebugOverlayView overlay) { 144 mDebugOverlay = overlay; 145 } 146 147 /** Requests that the views be synchronized with the model */ 148 void requestSynchronizeStackViewsWithModel() { 149 requestSynchronizeStackViewsWithModel(0); 150 } 151 void requestSynchronizeStackViewsWithModel(int duration) { 152 if (!mStackViewsDirty) { 153 invalidate(mStackAlgorithm.mStackRect); 154 mStackViewsDirty = true; 155 } 156 if (mAwaitingFirstLayout) { 157 // Skip the animation if we are awaiting first layout 158 mStackViewsAnimationDuration = 0; 159 } else { 160 mStackViewsAnimationDuration = Math.max(mStackViewsAnimationDuration, duration); 161 } 162 } 163 164 /** Finds the child view given a specific task. */ 165 public TaskView getChildViewForTask(Task t) { 166 int childCount = getChildCount(); 167 for (int i = 0; i < childCount; i++) { 168 TaskView tv = (TaskView) getChildAt(i); 169 if (tv.getTask() == t) { 170 return tv; 171 } 172 } 173 return null; 174 } 175 176 /** Returns the stack algorithm for this task stack. */ 177 public TaskStackViewLayoutAlgorithm getStackAlgorithm() { 178 return mStackAlgorithm; 179 } 180 181 /** 182 * Gets the stack transforms of a list of tasks, and returns the visible range of tasks. 183 */ 184 private boolean updateStackTransforms(ArrayList<TaskViewTransform> taskTransforms, 185 ArrayList<Task> tasks, 186 int stackScroll, 187 int[] visibleRangeOut, 188 boolean boundTranslationsToRect) { 189 // XXX: We should be intelligent about where to look for the visible stack range using the 190 // current stack scroll. 191 // XXX: We should log extra cases like the ones below where we don't expect to hit very often 192 // XXX: Print out approximately how many indices we have to go through to find the first visible transform 193 194 int taskTransformCount = taskTransforms.size(); 195 int taskCount = tasks.size(); 196 int frontMostVisibleIndex = -1; 197 int backMostVisibleIndex = -1; 198 199 // We can reuse the task transforms where possible to reduce object allocation 200 if (taskTransformCount < taskCount) { 201 // If there are less transforms than tasks, then add as many transforms as necessary 202 for (int i = taskTransformCount; i < taskCount; i++) { 203 taskTransforms.add(new TaskViewTransform()); 204 } 205 } else if (taskTransformCount > taskCount) { 206 // If there are more transforms than tasks, then just subset the transform list 207 taskTransforms.subList(0, taskCount); 208 } 209 210 // Update the stack transforms 211 for (int i = taskCount - 1; i >= 0; i--) { 212 TaskViewTransform transform = mStackAlgorithm.getStackTransform(tasks.get(i), stackScroll, taskTransforms.get(i)); 213 if (transform.visible) { 214 if (frontMostVisibleIndex < 0) { 215 frontMostVisibleIndex = i; 216 } 217 backMostVisibleIndex = i; 218 } else { 219 if (backMostVisibleIndex != -1) { 220 // We've reached the end of the visible range, so going down the rest of the 221 // stack, we can just reset the transforms accordingly 222 while (i >= 0) { 223 taskTransforms.get(i).reset(); 224 i--; 225 } 226 break; 227 } 228 } 229 230 if (boundTranslationsToRect) { 231 transform.translationY = Math.min(transform.translationY, 232 mStackAlgorithm.mViewRect.bottom); 233 } 234 } 235 if (visibleRangeOut != null) { 236 visibleRangeOut[0] = frontMostVisibleIndex; 237 visibleRangeOut[1] = backMostVisibleIndex; 238 } 239 return frontMostVisibleIndex != -1 && backMostVisibleIndex != -1; 240 } 241 242 /** 243 * Gets the stack transforms of a list of tasks, and returns the visible range of tasks. This 244 * call is less optimal than calling updateStackTransforms directly. 245 */ 246 private ArrayList<TaskViewTransform> getStackTransforms(ArrayList<Task> tasks, 247 int stackScroll, 248 int[] visibleRangeOut, 249 boolean boundTranslationsToRect) { 250 ArrayList<TaskViewTransform> taskTransforms = new ArrayList<TaskViewTransform>(); 251 updateStackTransforms(taskTransforms, tasks, stackScroll, visibleRangeOut, 252 boundTranslationsToRect); 253 return taskTransforms; 254 } 255 256 /** Synchronizes the views with the model */ 257 boolean synchronizeStackViewsWithModel() { 258 if (mStackViewsDirty) { 259 // Get all the task transforms 260 ArrayList<Task> tasks = mStack.getTasks(); 261 int stackScroll = getStackScroll(); 262 int[] visibleRange = mTmpVisibleRange; 263 boolean isValidVisibleRange = updateStackTransforms(mCurrentTaskTransforms, tasks, stackScroll, visibleRange, false); 264 265 // Return all the invisible children to the pool 266 mTmpTaskViewMap.clear(); 267 int childCount = getChildCount(); 268 for (int i = childCount - 1; i >= 0; i--) { 269 TaskView tv = (TaskView) getChildAt(i); 270 Task task = tv.getTask(); 271 int taskIndex = mStack.indexOfTask(task); 272 if (visibleRange[1] <= taskIndex && taskIndex <= visibleRange[0]) { 273 mTmpTaskViewMap.put(task, tv); 274 } else { 275 mViewPool.returnViewToPool(tv); 276 } 277 } 278 279 // Pick up all the newly visible children and update all the existing children 280 for (int i = visibleRange[0]; isValidVisibleRange && i >= visibleRange[1]; i--) { 281 Task task = tasks.get(i); 282 TaskViewTransform transform = mCurrentTaskTransforms.get(i); 283 TaskView tv = mTmpTaskViewMap.get(task); 284 int taskIndex = mStack.indexOfTask(task); 285 286 if (tv == null) { 287 tv = mViewPool.pickUpViewFromPool(task, task); 288 289 if (mStackViewsAnimationDuration > 0) { 290 // For items in the list, put them in start animating them from the 291 // approriate ends of the list where they are expected to appear 292 if (transform.t < 0) { 293 mTmpTransform = mStackAlgorithm.getStackTransform(tasks.get(0), stackScroll, mTmpTransform); 294 } else { 295 int nextTaskStackScroll = mStackAlgorithm.getStackScrollForTaskIndex(task, 1); 296 mStackAlgorithm.getStackTransform(nextTaskStackScroll, stackScroll, mTmpTransform); 297 } 298 tv.updateViewPropertiesToTaskTransform(mTmpTransform, 0); 299 } 300 } 301 302 // Animate the task into place 303 tv.updateViewPropertiesToTaskTransform(mCurrentTaskTransforms.get(taskIndex), 304 mStackViewsAnimationDuration); 305 } 306 307 // Reset the request-synchronize params 308 mStackViewsAnimationDuration = 0; 309 mStackViewsDirty = false; 310 return true; 311 } 312 return false; 313 } 314 315 /** Updates the clip for each of the task views. */ 316 void clipTaskViews() { 317 // Update the clip on each task child 318 if (Constants.DebugFlags.App.EnableTaskStackClipping) { 319 int childCount = getChildCount(); 320 for (int i = 0; i < childCount - 1; i++) { 321 TaskView tv = (TaskView) getChildAt(i); 322 TaskView nextTv = null; 323 TaskView tmpTv = null; 324 int clipBottom = 0; 325 if (tv.shouldClipViewInStack()) { 326 // Find the next view to clip against 327 int nextIndex = i; 328 while (nextIndex < getChildCount()) { 329 tmpTv = (TaskView) getChildAt(++nextIndex); 330 if (tmpTv != null && tmpTv.shouldClipViewInStack()) { 331 nextTv = tmpTv; 332 break; 333 } 334 } 335 336 // Clip against the next view, this is just an approximation since we are 337 // stacked and we can make assumptions about the visibility of the this 338 // task relative to the ones in front of it. 339 if (nextTv != null) { 340 // We can reuse the current task transforms to find the task rects 341 TaskViewTransform transform = mCurrentTaskTransforms.get(mStack.indexOfTask(tv.getTask())); 342 TaskViewTransform nextTransform = mCurrentTaskTransforms.get(mStack.indexOfTask(nextTv.getTask())); 343 clipBottom = transform.rect.bottom - nextTransform.rect.top - 200; 344 } 345 } 346 tv.getViewBounds().setClipBottom(clipBottom); 347 } 348 if (getChildCount() > 0) { 349 // The front most task should never be clipped 350 TaskView tv = (TaskView) getChildAt(getChildCount() - 1); 351 tv.getViewBounds().setClipBottom(0); 352 } 353 } 354 } 355 356 /** The stack insets to apply to the stack contents */ 357 public void setStackInsetRect(Rect r) { 358 mTaskStackBounds.set(r); 359 } 360 361 /** Sets the current stack scroll */ 362 public void setStackScroll(int value) { 363 mStackScroll = value; 364 mUIDozeTrigger.poke(); 365 requestSynchronizeStackViewsWithModel(); 366 } 367 368 /** Sets the current stack scroll without synchronizing the stack view with the model */ 369 public void setStackScrollRaw(int value) { 370 mStackScroll = value; 371 mUIDozeTrigger.poke(); 372 } 373 /** Sets the current stack scroll to the initial state when you first enter recents */ 374 public void setStackScrollToInitialState() { 375 setStackScroll(getInitialStackScroll()); 376 } 377 /** Computes the initial stack scroll for the stack. */ 378 int getInitialStackScroll() { 379 if (mStack.getTaskCount() > 2) { 380 return Math.max(mMinScroll, mMaxScroll - (int) (mStackAlgorithm.mTaskRect.height() * (3f/4f))); 381 } 382 return mMaxScroll; 383 } 384 385 /** Gets the current stack scroll */ 386 public int getStackScroll() { 387 return mStackScroll; 388 } 389 390 /** Animates the stack scroll into bounds */ 391 ObjectAnimator animateBoundScroll() { 392 int curScroll = getStackScroll(); 393 int newScroll = Math.max(mMinScroll, Math.min(mMaxScroll, curScroll)); 394 if (newScroll != curScroll) { 395 // Start a new scroll animation 396 animateScroll(curScroll, newScroll, null); 397 } 398 return mScrollAnimator; 399 } 400 401 /** Animates the stack scroll */ 402 void animateScroll(int curScroll, int newScroll, final Runnable postRunnable) { 403 // Abort any current animations 404 abortScroller(); 405 abortBoundScrollAnimation(); 406 407 mScrollAnimator = ObjectAnimator.ofInt(this, "stackScroll", curScroll, newScroll); 408 mScrollAnimator.setDuration(Utilities.calculateTranslationAnimationDuration(newScroll - 409 curScroll, 250)); 410 mScrollAnimator.setInterpolator(mConfig.fastOutSlowInInterpolator); 411 mScrollAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 412 @Override 413 public void onAnimationUpdate(ValueAnimator animation) { 414 setStackScroll((Integer) animation.getAnimatedValue()); 415 } 416 }); 417 mScrollAnimator.addListener(new AnimatorListenerAdapter() { 418 @Override 419 public void onAnimationEnd(Animator animation) { 420 if (postRunnable != null) { 421 postRunnable.run(); 422 } 423 mScrollAnimator.removeAllListeners(); 424 } 425 }); 426 mScrollAnimator.start(); 427 } 428 429 /** Aborts any current stack scrolls */ 430 void abortBoundScrollAnimation() { 431 if (mScrollAnimator != null) { 432 mScrollAnimator.cancel(); 433 } 434 } 435 436 /** Aborts the scroller and any current fling */ 437 void abortScroller() { 438 if (!mScroller.isFinished()) { 439 // Abort the scroller 440 mScroller.abortAnimation(); 441 } 442 } 443 444 /** Bounds the current scroll if necessary */ 445 public boolean boundScroll() { 446 int curScroll = getStackScroll(); 447 int newScroll = Math.max(mMinScroll, Math.min(mMaxScroll, curScroll)); 448 if (newScroll != curScroll) { 449 setStackScroll(newScroll); 450 return true; 451 } 452 return false; 453 } 454 455 /** 456 * Bounds the current scroll if necessary, but does not synchronize the stack view with the 457 * model. 458 */ 459 public boolean boundScrollRaw() { 460 int curScroll = getStackScroll(); 461 int newScroll = Math.max(mMinScroll, Math.min(mMaxScroll, curScroll)); 462 if (newScroll != curScroll) { 463 setStackScrollRaw(newScroll); 464 return true; 465 } 466 return false; 467 } 468 469 470 /** Returns the amount that the scroll is out of bounds */ 471 int getScrollAmountOutOfBounds(int scroll) { 472 if (scroll < mMinScroll) { 473 return mMinScroll - scroll; 474 } else if (scroll > mMaxScroll) { 475 return scroll - mMaxScroll; 476 } 477 return 0; 478 } 479 480 /** Returns whether the specified scroll is out of bounds */ 481 boolean isScrollOutOfBounds() { 482 return getScrollAmountOutOfBounds(mStackScroll) != 0; 483 } 484 485 /** Updates the min and max virtual scroll bounds */ 486 void updateMinMaxScroll(boolean boundScrollToNewMinMax) { 487 // Compute the min and max scroll values 488 mStackAlgorithm.computeMinMaxScroll(mStack.getTasks()); 489 mMinScroll = mStackAlgorithm.mMinScroll; 490 mMaxScroll = mStackAlgorithm.mMaxScroll; 491 492 // Debug logging 493 if (boundScrollToNewMinMax) { 494 boundScroll(); 495 } 496 } 497 498 /** Focuses the task at the specified index in the stack */ 499 void focusTask(int taskIndex, boolean scrollToNewPosition) { 500 if (0 <= taskIndex && taskIndex < mStack.getTaskCount()) { 501 mFocusedTaskIndex = taskIndex; 502 503 // Focus the view if possible, otherwise, focus the view after we scroll into position 504 Task t = mStack.getTasks().get(taskIndex); 505 TaskView tv = getChildViewForTask(t); 506 Runnable postScrollRunnable = null; 507 if (tv != null) { 508 tv.setFocusedTask(); 509 } else { 510 postScrollRunnable = new Runnable() { 511 @Override 512 public void run() { 513 Task t = mStack.getTasks().get(mFocusedTaskIndex); 514 TaskView tv = getChildViewForTask(t); 515 if (tv != null) { 516 tv.setFocusedTask(); 517 } 518 } 519 }; 520 } 521 522 if (scrollToNewPosition) { 523 // Scroll the view into position 524 int newScroll = Math.max(mMinScroll, Math.min(mMaxScroll, 525 mStackAlgorithm.getStackScrollForTaskIndex(t))); 526 527 animateScroll(getStackScroll(), newScroll, postScrollRunnable); 528 } else { 529 if (postScrollRunnable != null) { 530 postScrollRunnable.run(); 531 } 532 } 533 } 534 } 535 536 /** Focuses the next task in the stack */ 537 void focusNextTask(boolean forward) { 538 // Find the next index to focus 539 int numTasks = mStack.getTaskCount(); 540 if (mFocusedTaskIndex < 0) { 541 mFocusedTaskIndex = numTasks - 1; 542 } 543 if (0 <= mFocusedTaskIndex && mFocusedTaskIndex < numTasks) { 544 mFocusedTaskIndex = Math.max(0, Math.min(numTasks - 1, 545 mFocusedTaskIndex + (forward ? -1 : 1))); 546 } 547 focusTask(mFocusedTaskIndex, true); 548 } 549 550 @Override 551 public boolean onInterceptTouchEvent(MotionEvent ev) { 552 return mTouchHandler.onInterceptTouchEvent(ev); 553 } 554 555 @Override 556 public boolean onTouchEvent(MotionEvent ev) { 557 return mTouchHandler.onTouchEvent(ev); 558 } 559 560 @Override 561 public void computeScroll() { 562 if (mScroller.computeScrollOffset()) { 563 setStackScroll(mScroller.getCurrY()); 564 invalidate(); 565 } 566 } 567 568 @Override 569 public void dispatchDraw(Canvas canvas) { 570 if (synchronizeStackViewsWithModel()) { 571 clipTaskViews(); 572 } 573 super.dispatchDraw(canvas); 574 } 575 576 /** Computes the stack and task rects */ 577 public void computeRects(int windowWidth, int windowHeight, Rect taskStackBounds) { 578 // Compute the rects in the stack algorithm 579 mStackAlgorithm.computeRects(mStack.getTasks(), windowWidth, windowHeight, taskStackBounds); 580 581 // Update the scroll bounds 582 updateMinMaxScroll(false); 583 } 584 585 /** 586 * This is called with the full window width and height to allow stack view children to 587 * perform the full screen transition down. 588 */ 589 @Override 590 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 591 int width = MeasureSpec.getSize(widthMeasureSpec); 592 int height = MeasureSpec.getSize(heightMeasureSpec); 593 594 // Compute our stack/task rects 595 Rect taskStackBounds = new Rect(mTaskStackBounds); 596 taskStackBounds.bottom -= mConfig.systemInsets.bottom; 597 computeRects(width, height, taskStackBounds); 598 599 // If this is the first layout, then scroll to the front of the stack and synchronize the 600 // stack views immediately to load all the views 601 if (mAwaitingFirstLayout) { 602 setStackScrollToInitialState(); 603 requestSynchronizeStackViewsWithModel(); 604 synchronizeStackViewsWithModel(); 605 } 606 607 // Measure each of the TaskViews 608 int childCount = getChildCount(); 609 for (int i = 0; i < childCount; i++) { 610 TaskView tv = (TaskView) getChildAt(i); 611 if (tv.isFullScreenView()) { 612 tv.measure(widthMeasureSpec, heightMeasureSpec); 613 } else { 614 tv.measure( 615 MeasureSpec.makeMeasureSpec(mStackAlgorithm.mTaskRect.width(), 616 MeasureSpec.EXACTLY), 617 MeasureSpec.makeMeasureSpec(mStackAlgorithm.mTaskRect.height() + 618 tv.getMaxFooterHeight(), MeasureSpec.EXACTLY)); 619 } 620 } 621 622 setMeasuredDimension(width, height); 623 } 624 625 /** 626 * This is called with the size of the space not including the top or right insets, or the 627 * search bar height in portrait (but including the search bar width in landscape, since we want 628 * to draw under it. 629 */ 630 @Override 631 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 632 // Layout each of the children 633 int childCount = getChildCount(); 634 for (int i = 0; i < childCount; i++) { 635 TaskView tv = (TaskView) getChildAt(i); 636 if (tv.isFullScreenView()) { 637 tv.layout(left, top, left + tv.getMeasuredWidth(), top + tv.getMeasuredHeight()); 638 } else { 639 tv.layout(mStackAlgorithm.mTaskRect.left, mStackAlgorithm.mTaskRect.top, 640 mStackAlgorithm.mTaskRect.right, mStackAlgorithm.mTaskRect.bottom + 641 tv.getMaxFooterHeight()); 642 } 643 } 644 645 if (mAwaitingFirstLayout) { 646 mAwaitingFirstLayout = false; 647 onFirstLayout(); 648 } 649 } 650 651 /** Handler for the first layout. */ 652 void onFirstLayout() { 653 int offscreenY = mStackAlgorithm.mViewRect.bottom - 654 (mStackAlgorithm.mTaskRect.top - mStackAlgorithm.mViewRect.top); 655 656 // Find the launch target task 657 Task launchTargetTask = null; 658 int childCount = getChildCount(); 659 for (int i = childCount - 1; i >= 0; i--) { 660 TaskView tv = (TaskView) getChildAt(i); 661 Task task = tv.getTask(); 662 if (task.isLaunchTarget) { 663 launchTargetTask = task; 664 break; 665 } 666 } 667 668 // Prepare the first view for its enter animation 669 for (int i = childCount - 1; i >= 0; i--) { 670 TaskView tv = (TaskView) getChildAt(i); 671 Task task = tv.getTask(); 672 boolean occludesLaunchTarget = (launchTargetTask != null) && 673 launchTargetTask.group.isTaskAboveTask(task, launchTargetTask); 674 tv.prepareEnterRecentsAnimation(task.isLaunchTarget, occludesLaunchTarget, offscreenY); 675 } 676 677 // If the enter animation started already and we haven't completed a layout yet, do the 678 // enter animation now 679 if (mStartEnterAnimationRequestedAfterLayout) { 680 startEnterRecentsAnimation(mStartEnterAnimationContext); 681 mStartEnterAnimationRequestedAfterLayout = false; 682 mStartEnterAnimationContext = null; 683 } 684 685 // Update the focused task index to be the next item to the top task 686 if (mConfig.launchedWithAltTab) { 687 // When alt-tabbing, we focus the next previous task 688 focusTask(Math.max(0, mStack.getTaskCount() - 2), false); 689 } 690 } 691 692 /** Requests this task stacks to start it's enter-recents animation */ 693 public void startEnterRecentsAnimation(ViewAnimation.TaskViewEnterContext ctx) { 694 // If we are still waiting to layout, then just defer until then 695 if (mAwaitingFirstLayout) { 696 mStartEnterAnimationRequestedAfterLayout = true; 697 mStartEnterAnimationContext = ctx; 698 return; 699 } 700 701 if (mStack.getTaskCount() > 0) { 702 // Find the launch target task 703 Task launchTargetTask = null; 704 int childCount = getChildCount(); 705 for (int i = childCount - 1; i >= 0; i--) { 706 TaskView tv = (TaskView) getChildAt(i); 707 Task task = tv.getTask(); 708 if (task.isLaunchTarget) { 709 launchTargetTask = task; 710 break; 711 } 712 } 713 714 // Animate all the task views into view 715 for (int i = childCount - 1; i >= 0; i--) { 716 TaskView tv = (TaskView) getChildAt(i); 717 Task task = tv.getTask(); 718 ctx.currentTaskTransform = new TaskViewTransform(); 719 ctx.currentStackViewIndex = i; 720 ctx.currentStackViewCount = childCount; 721 ctx.currentTaskRect = mStackAlgorithm.mTaskRect; 722 ctx.currentTaskOccludesLaunchTarget = (launchTargetTask != null) && 723 launchTargetTask.group.isTaskAboveTask(task, launchTargetTask); 724 mStackAlgorithm.getStackTransform(task, getStackScroll(), ctx.currentTaskTransform); 725 tv.startEnterRecentsAnimation(ctx); 726 } 727 728 // Add a runnable to the post animation ref counter to clear all the views 729 ctx.postAnimationTrigger.addLastDecrementRunnable(new Runnable() { 730 @Override 731 public void run() { 732 // Start dozing 733 mUIDozeTrigger.startDozing(); 734 } 735 }); 736 } 737 } 738 739 /** Requests this task stacks to start it's exit-recents animation. */ 740 public void startExitToHomeAnimation(ViewAnimation.TaskViewExitContext ctx) { 741 // Animate all the task views into view 742 ctx.offscreenTranslationY = mStackAlgorithm.mViewRect.bottom - 743 (mStackAlgorithm.mTaskRect.top - mStackAlgorithm.mViewRect.top); 744 int childCount = getChildCount(); 745 for (int i = 0; i < childCount; i++) { 746 TaskView tv = (TaskView) getChildAt(i); 747 tv.startExitToHomeAnimation(ctx); 748 } 749 750 // Add a runnable to the post animation ref counter to clear all the views 751 ctx.postAnimationTrigger.addLastDecrementRunnable(mReturnAllViewsToPoolRunnable); 752 } 753 754 /** Animates a task view in this stack as it launches. */ 755 public void startLaunchTaskAnimation(TaskView tv, final Runnable r) { 756 Task launchTargetTask = tv.getTask(); 757 int offscreenTranslationY = mStackAlgorithm.mViewRect.bottom - 758 (mStackAlgorithm.mTaskRect.top - mStackAlgorithm.mViewRect.top); 759 int childCount = getChildCount(); 760 for (int i = 0; i < childCount; i++) { 761 TaskView t = (TaskView) getChildAt(i); 762 if (t == tv) { 763 t.startLaunchTaskAnimation(r, true, true); 764 } else { 765 boolean occludesLaunchTarget = launchTargetTask.group.isTaskAboveTask(t.getTask(), 766 launchTargetTask); 767 t.startLaunchTaskAnimation(null, false, occludesLaunchTarget); 768 } 769 } 770 } 771 772 @Override 773 protected void onScrollChanged(int l, int t, int oldl, int oldt) { 774 super.onScrollChanged(l, t, oldl, oldt); 775 requestSynchronizeStackViewsWithModel(); 776 } 777 778 public boolean isTransformedTouchPointInView(float x, float y, View child) { 779 return isTransformedTouchPointInView(x, y, child, null); 780 } 781 782 /** Pokes the dozer on user interaction. */ 783 void onUserInteraction() { 784 // Poke the doze trigger if it is dozing 785 mUIDozeTrigger.poke(); 786 } 787 788 /**** TaskStackCallbacks Implementation ****/ 789 790 @Override 791 public void onStackTaskAdded(TaskStack stack, Task t) { 792 // Update the task offsets 793 mStackAlgorithm.updateTaskOffsets(mStack.getTasks()); 794 795 requestSynchronizeStackViewsWithModel(); 796 } 797 798 @Override 799 public void onStackTaskRemoved(TaskStack stack, Task removedTask, Task newFrontMostTask) { 800 // Update the task offsets 801 mStackAlgorithm.updateTaskOffsets(mStack.getTasks()); 802 803 // Remove the view associated with this task, we can't rely on updateTransforms 804 // to work here because the task is no longer in the list 805 TaskView tv = getChildViewForTask(removedTask); 806 if (tv != null) { 807 mViewPool.returnViewToPool(tv); 808 } 809 810 // Notify the callback that we've removed the task and it can clean up after it 811 mCb.onTaskViewDismissed(removedTask); 812 813 // Update the min/max scroll and animate other task views into their new positions 814 updateMinMaxScroll(true); 815 int movement = (int) mStackAlgorithm.getTaskOverlapHeight(); 816 requestSynchronizeStackViewsWithModel(Utilities.calculateTranslationAnimationDuration(movement)); 817 818 // Update the new front most task 819 if (newFrontMostTask != null) { 820 TaskView frontTv = getChildViewForTask(newFrontMostTask); 821 if (frontTv != null) { 822 frontTv.onTaskBound(newFrontMostTask); 823 } 824 } 825 826 // If there are no remaining tasks, then either unfilter the current stack, or just close 827 // the activity if there are no filtered stacks 828 if (mStack.getTaskCount() == 0) { 829 boolean shouldFinishActivity = true; 830 if (mStack.hasFilteredTasks()) { 831 mStack.unfilterTasks(); 832 shouldFinishActivity = (mStack.getTaskCount() == 0); 833 } 834 if (shouldFinishActivity) { 835 mCb.onAllTaskViewsDismissed(); 836 } 837 } 838 } 839 840 @Override 841 public void onStackFiltered(TaskStack newStack, final ArrayList<Task> curTasks, 842 Task filteredTask) { 843 // Stash the scroll and filtered task for us to restore to when we unfilter 844 mStashedScroll = getStackScroll(); 845 846 // Calculate the current task transforms 847 ArrayList<TaskViewTransform> curTaskTransforms = 848 getStackTransforms(curTasks, getStackScroll(), null, true); 849 850 // Update the task offsets 851 mStackAlgorithm.updateTaskOffsets(mStack.getTasks()); 852 853 // Scroll the item to the top of the stack (sans-peek) rect so that we can see it better 854 updateMinMaxScroll(false); 855 float overlapHeight = mStackAlgorithm.getTaskOverlapHeight(); 856 setStackScrollRaw((int) (newStack.indexOfTask(filteredTask) * overlapHeight)); 857 boundScrollRaw(); 858 859 // Compute the transforms of the items in the new stack after setting the new scroll 860 final ArrayList<Task> tasks = mStack.getTasks(); 861 final ArrayList<TaskViewTransform> taskTransforms = 862 getStackTransforms(mStack.getTasks(), getStackScroll(), null, true); 863 864 // Animate 865 mFilterAlgorithm.startFilteringAnimation(curTasks, curTaskTransforms, tasks, taskTransforms); 866 867 // Notify any callbacks 868 mCb.onTaskStackFilterTriggered(); 869 } 870 871 @Override 872 public void onStackUnfiltered(TaskStack newStack, final ArrayList<Task> curTasks) { 873 // Calculate the current task transforms 874 final ArrayList<TaskViewTransform> curTaskTransforms = 875 getStackTransforms(curTasks, getStackScroll(), null, true); 876 877 // Update the task offsets 878 mStackAlgorithm.updateTaskOffsets(mStack.getTasks()); 879 880 // Restore the stashed scroll 881 updateMinMaxScroll(false); 882 setStackScrollRaw(mStashedScroll); 883 boundScrollRaw(); 884 885 // Compute the transforms of the items in the new stack after restoring the stashed scroll 886 final ArrayList<Task> tasks = mStack.getTasks(); 887 final ArrayList<TaskViewTransform> taskTransforms = 888 getStackTransforms(tasks, getStackScroll(), null, true); 889 890 // Animate 891 mFilterAlgorithm.startFilteringAnimation(curTasks, curTaskTransforms, tasks, taskTransforms); 892 893 // Clear the saved vars 894 mStashedScroll = 0; 895 896 // Notify any callbacks 897 mCb.onTaskStackUnfilterTriggered(); 898 } 899 900 /**** ViewPoolConsumer Implementation ****/ 901 902 @Override 903 public TaskView createView(Context context) { 904 return (TaskView) mInflater.inflate(R.layout.recents_task_view, this, false); 905 } 906 907 @Override 908 public void prepareViewToEnterPool(TaskView tv) { 909 Task task = tv.getTask(); 910 911 // Report that this tasks's data is no longer being used 912 RecentsTaskLoader.getInstance().unloadTaskData(task); 913 914 // Detach the view from the hierarchy 915 detachViewFromParent(tv); 916 917 // Reset the view properties 918 tv.resetViewProperties(); 919 } 920 921 @Override 922 public void prepareViewToLeavePool(TaskView tv, Task task, boolean isNewView) { 923 // Rebind the task and request that this task's data be filled into the TaskView 924 tv.onTaskBound(task); 925 926 // Mark the launch task as fullscreen 927 if (Constants.DebugFlags.App.EnableScreenshotAppTransition && mAwaitingFirstLayout) { 928 if (task.isLaunchTarget) { 929 tv.setIsFullScreen(true); 930 } 931 } 932 933 // Load the task data 934 RecentsTaskLoader.getInstance().loadTaskData(task); 935 936 // Sanity check, the task view should always be clipping against the stack at this point, 937 // but just in case, re-enable it here 938 tv.setClipViewInStack(true); 939 940 // If the doze trigger has already fired, then update the state for this task view 941 if (mUIDozeTrigger.hasTriggered()) { 942 tv.setNoUserInteractionState(); 943 } 944 945 // Find the index where this task should be placed in the stack 946 int insertIndex = -1; 947 int taskIndex = mStack.indexOfTask(task); 948 if (taskIndex != -1) { 949 int childCount = getChildCount(); 950 for (int i = 0; i < childCount; i++) { 951 Task tvTask = ((TaskView) getChildAt(i)).getTask(); 952 if (taskIndex < mStack.indexOfTask(tvTask)) { 953 insertIndex = i; 954 break; 955 } 956 } 957 } 958 959 // Add/attach the view to the hierarchy 960 if (isNewView) { 961 addView(tv, insertIndex); 962 963 // Set the callbacks and listeners for this new view 964 tv.setTouchEnabled(true); 965 tv.setCallbacks(this); 966 } else { 967 attachViewToParent(tv, insertIndex, tv.getLayoutParams()); 968 } 969 } 970 971 @Override 972 public boolean hasPreferredData(TaskView tv, Task preferredData) { 973 return (tv.getTask() == preferredData); 974 } 975 976 /**** TaskViewCallbacks Implementation ****/ 977 978 @Override 979 public void onTaskViewAppIconClicked(TaskView tv) { 980 if (Constants.DebugFlags.App.EnableTaskFiltering) { 981 if (mStack.hasFilteredTasks()) { 982 mStack.unfilterTasks(); 983 } else { 984 mStack.filterTasks(tv.getTask()); 985 } 986 } 987 } 988 989 @Override 990 public void onTaskViewAppInfoClicked(TaskView tv) { 991 if (mCb != null) { 992 mCb.onTaskViewAppInfoClicked(tv.getTask()); 993 } 994 } 995 996 @Override 997 public void onTaskViewClicked(TaskView tv, Task task, boolean lockToTask) { 998 // Cancel any doze triggers 999 mUIDozeTrigger.stopDozing(); 1000 1001 if (mCb != null) { 1002 mCb.onTaskViewClicked(this, tv, mStack, task, lockToTask); 1003 } 1004 } 1005 1006 @Override 1007 public void onTaskViewDismissed(TaskView tv) { 1008 Task task = tv.getTask(); 1009 // Remove the task from the view 1010 mStack.removeTask(task); 1011 } 1012 1013 @Override 1014 public void onTaskViewClipStateChanged(TaskView tv) { 1015 invalidate(mStackAlgorithm.mStackRect); 1016 } 1017 1018 @Override 1019 public void onTaskViewFullScreenTransitionCompleted() { 1020 requestSynchronizeStackViewsWithModel(); 1021 } 1022 1023 /**** RecentsPackageMonitor.PackageCallbacks Implementation ****/ 1024 1025 @Override 1026 public void onComponentRemoved(HashSet<ComponentName> cns) { 1027 // For other tasks, just remove them directly if they no longer exist 1028 ArrayList<Task> tasks = mStack.getTasks(); 1029 for (int i = tasks.size() - 1; i >= 0; i--) { 1030 final Task t = tasks.get(i); 1031 if (cns.contains(t.key.baseIntent.getComponent())) { 1032 TaskView tv = getChildViewForTask(t); 1033 if (tv != null) { 1034 // For visible children, defer removing the task until after the animation 1035 tv.startDeleteTaskAnimation(new Runnable() { 1036 @Override 1037 public void run() { 1038 mStack.removeTask(t); 1039 } 1040 }); 1041 } else { 1042 // Otherwise, remove the task from the stack immediately 1043 mStack.removeTask(t); 1044 } 1045 } 1046 } 1047 } 1048}