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