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