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