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