TaskStackView.java revision c6011deffc80eaee6dfb1c7975e050bc71e5ea96
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 // Prepare the first view for its enter animation 751 int offsetTopAlign = -mStackAlgorithm.mTaskRect.top; 752 int offscreenY = mStackAlgorithm.mRect.bottom - 753 (mStackAlgorithm.mTaskRect.top - mStackAlgorithm.mRect.top); 754 for (int i = childCount - 1; i >= 0; i--) { 755 TaskView tv = (TaskView) getChildAt(i); 756 tv.prepareEnterRecentsAnimation((i == (getChildCount() - 1)), offsetTopAlign, 757 offscreenY, mStackAlgorithm.mTaskRect); 758 } 759 760 // If the enter animation started already and we haven't completed a layout yet, do the 761 // enter animation now 762 if (mStartEnterAnimationRequestedAfterLayout) { 763 startEnterRecentsAnimation(mStartEnterAnimationContext); 764 mStartEnterAnimationRequestedAfterLayout = false; 765 mStartEnterAnimationContext = null; 766 } 767 768 // Update the focused task index to be the next item to the top task 769 if (mConfig.launchedWithAltTab) { 770 // When alt-tabbing, we focus the next previous task 771 focusTask(Math.max(0, mStack.getTaskCount() - 2), false); 772 } else { 773 // Normally we just focus the front task 774 focusTask(Math.max(0, mStack.getTaskCount() - 1), false); 775 } 776 } 777 } 778 779 /** Requests this task stacks to start it's enter-recents animation */ 780 public void startEnterRecentsAnimation(ViewAnimation.TaskViewEnterContext ctx) { 781 // If we are still waiting to layout, then just defer until then 782 if (mAwaitingFirstLayout) { 783 mStartEnterAnimationRequestedAfterLayout = true; 784 mStartEnterAnimationContext = ctx; 785 return; 786 } 787 788 // Animate all the task views into view 789 TaskViewTransform transform = mStackAlgorithm.getStackTransform(mStack.getTaskCount() - 1, 790 getInitialStackScroll()); 791 ctx.taskRect = transform.rect; 792 ctx.stackRectSansPeek = mStackAlgorithm.mStackRectSansPeek; 793 int childCount = getChildCount(); 794 for (int i = childCount - 1; i >= 0; i--) { 795 TaskView tv = (TaskView) getChildAt(i); 796 ctx.stackViewIndex = i; 797 ctx.stackViewCount = childCount; 798 ctx.isFrontMost = (i == (getChildCount() - 1)); 799 ctx.transform = mStackAlgorithm.getStackTransform( 800 mStack.indexOfTask(tv.getTask()), getStackScroll()); 801 tv.startEnterRecentsAnimation(ctx); 802 } 803 804 // Add a runnable to the post animation ref counter to clear all the views 805 ctx.postAnimationTrigger.addLastDecrementRunnable(new Runnable() { 806 @Override 807 public void run() { 808 // Start dozing 809 mUIDozeTrigger.startDozing(); 810 } 811 }); 812 } 813 814 /** Requests this task stacks to start it's exit-recents animation. */ 815 public void startExitToHomeAnimation(ViewAnimation.TaskViewExitContext ctx) { 816 // Animate all the task views into view 817 ctx.offscreenTranslationY = mStackAlgorithm.mRect.bottom - 818 (mStackAlgorithm.mTaskRect.top - mStackAlgorithm.mRect.top); 819 int childCount = getChildCount(); 820 for (int i = 0; i < childCount; i++) { 821 TaskView tv = (TaskView) getChildAt(i); 822 tv.startExitToHomeAnimation(ctx); 823 } 824 825 // Add a runnable to the post animation ref counter to clear all the views 826 ctx.postAnimationTrigger.addLastDecrementRunnable(mReturnAllViewsToPoolRunnable); 827 } 828 829 @Override 830 protected void onScrollChanged(int l, int t, int oldl, int oldt) { 831 super.onScrollChanged(l, t, oldl, oldt); 832 requestSynchronizeStackViewsWithModel(); 833 } 834 835 public boolean isTransformedTouchPointInView(float x, float y, View child) { 836 return isTransformedTouchPointInView(x, y, child, null); 837 } 838 839 /** Pokes the dozer on user interaction. */ 840 void onUserInteraction() { 841 // Poke the doze trigger if it is dozing 842 mUIDozeTrigger.poke(); 843 } 844 845 /** Disables handling touch on this task view. */ 846 void setTouchOnTaskView(TaskView tv, boolean enabled) { 847 tv.setOnClickListener(enabled ? this : null); 848 } 849 850 /**** TaskStackCallbacks Implementation ****/ 851 852 @Override 853 public void onStackTaskAdded(TaskStack stack, Task t) { 854 requestSynchronizeStackViewsWithModel(); 855 } 856 857 @Override 858 public void onStackTaskRemoved(TaskStack stack, Task t) { 859 // Remove the view associated with this task, we can't rely on updateTransforms 860 // to work here because the task is no longer in the list 861 TaskView tv = getChildViewForTask(t); 862 if (tv != null) { 863 mViewPool.returnViewToPool(tv); 864 } 865 866 // Notify the callback that we've removed the task and it can clean up after it 867 mCb.onTaskRemoved(t); 868 869 // Update the min/max scroll and animate other task views into their new positions 870 updateMinMaxScroll(true); 871 int movement = (int) mStackAlgorithm.getTaskOverlapHeight(); 872 requestSynchronizeStackViewsWithModel(Utilities.calculateTranslationAnimationDuration(movement)); 873 874 // If there are no remaining tasks, then either unfilter the current stack, or just close 875 // the activity if there are no filtered stacks 876 if (mStack.getTaskCount() == 0) { 877 boolean shouldFinishActivity = true; 878 if (mStack.hasFilteredTasks()) { 879 mStack.unfilterTasks(); 880 shouldFinishActivity = (mStack.getTaskCount() == 0); 881 } 882 if (shouldFinishActivity) { 883 mCb.onLastTaskRemoved(); 884 } 885 } 886 } 887 888 @Override 889 public void onStackFiltered(TaskStack newStack, final ArrayList<Task> curTasks, 890 Task filteredTask) { 891 // Stash the scroll and filtered task for us to restore to when we unfilter 892 mStashedScroll = getStackScroll(); 893 894 // Calculate the current task transforms 895 ArrayList<TaskViewTransform> curTaskTransforms = 896 getStackTransforms(curTasks, getStackScroll(), null, true); 897 898 // Scroll the item to the top of the stack (sans-peek) rect so that we can see it better 899 updateMinMaxScroll(false); 900 float overlapHeight = mStackAlgorithm.getTaskOverlapHeight(); 901 setStackScrollRaw((int) (newStack.indexOfTask(filteredTask) * overlapHeight)); 902 boundScrollRaw(); 903 904 // Compute the transforms of the items in the new stack after setting the new scroll 905 final ArrayList<Task> tasks = mStack.getTasks(); 906 final ArrayList<TaskViewTransform> taskTransforms = 907 getStackTransforms(mStack.getTasks(), getStackScroll(), null, true); 908 909 // Animate 910 mFilterAlgorithm.startFilteringAnimation(curTasks, curTaskTransforms, tasks, taskTransforms); 911 912 // Notify any callbacks 913 mCb.onTaskStackFilterTriggered(); 914 } 915 916 @Override 917 public void onStackUnfiltered(TaskStack newStack, final ArrayList<Task> curTasks) { 918 // Calculate the current task transforms 919 final ArrayList<TaskViewTransform> curTaskTransforms = 920 getStackTransforms(curTasks, getStackScroll(), null, true); 921 922 // Restore the stashed scroll 923 updateMinMaxScroll(false); 924 setStackScrollRaw(mStashedScroll); 925 boundScrollRaw(); 926 927 // Compute the transforms of the items in the new stack after restoring the stashed scroll 928 final ArrayList<Task> tasks = mStack.getTasks(); 929 final ArrayList<TaskViewTransform> taskTransforms = 930 getStackTransforms(tasks, getStackScroll(), null, true); 931 932 // Animate 933 mFilterAlgorithm.startFilteringAnimation(curTasks, curTaskTransforms, tasks, taskTransforms); 934 935 // Clear the saved vars 936 mStashedScroll = 0; 937 938 // Notify any callbacks 939 mCb.onTaskStackUnfilterTriggered(); 940 } 941 942 /**** ViewPoolConsumer Implementation ****/ 943 944 @Override 945 public TaskView createView(Context context) { 946 if (Console.Enabled) { 947 Console.log(Constants.Log.ViewPool.PoolCallbacks, "[TaskStackView|createPoolView]"); 948 } 949 return (TaskView) mInflater.inflate(R.layout.recents_task_view, this, false); 950 } 951 952 @Override 953 public void prepareViewToEnterPool(TaskView tv) { 954 Task task = tv.getTask(); 955 if (Console.Enabled) { 956 Console.log(Constants.Log.ViewPool.PoolCallbacks, "[TaskStackView|returnToPool]", 957 tv.getTask() + " tv: " + tv); 958 } 959 960 // Report that this tasks's data is no longer being used 961 RecentsTaskLoader.getInstance().unloadTaskData(task); 962 963 // Detach the view from the hierarchy 964 detachViewFromParent(tv); 965 966 // Disable HW layers 967 tv.disableHwLayers(); 968 969 // Reset the view properties 970 tv.resetViewProperties(); 971 } 972 973 @Override 974 public void prepareViewToLeavePool(TaskView tv, Task task, boolean isNewView) { 975 if (Console.Enabled) { 976 Console.log(Constants.Log.ViewPool.PoolCallbacks, "[TaskStackView|leavePool]", 977 "isNewView: " + isNewView); 978 } 979 980 // Rebind the task and request that this task's data be filled into the TaskView 981 tv.onTaskBound(task); 982 RecentsTaskLoader.getInstance().loadTaskData(task); 983 984 // Sanity check, the task view should always be clipping against the stack at this point, 985 // but just in case, re-enable it here 986 tv.setClipViewInStack(true); 987 988 // If the doze trigger has already fired, then update the state for this task view 989 if (mUIDozeTrigger.hasTriggered()) { 990 tv.setNoUserInteractionState(); 991 } 992 993 // Find the index where this task should be placed in the stack 994 int insertIndex = -1; 995 int taskIndex = mStack.indexOfTask(task); 996 if (taskIndex != -1) { 997 int childCount = getChildCount(); 998 for (int i = 0; i < childCount; i++) { 999 Task tvTask = ((TaskView) getChildAt(i)).getTask(); 1000 if (taskIndex < mStack.indexOfTask(tvTask)) { 1001 insertIndex = i; 1002 break; 1003 } 1004 } 1005 } 1006 1007 // Add/attach the view to the hierarchy 1008 if (Console.Enabled) { 1009 Console.log(Constants.Log.ViewPool.PoolCallbacks, " [TaskStackView|insertIndex]", 1010 "" + insertIndex); 1011 } 1012 if (isNewView) { 1013 addView(tv, insertIndex); 1014 1015 // Set the callbacks and listeners for this new view 1016 setTouchOnTaskView(tv, true); 1017 tv.setCallbacks(this); 1018 } else { 1019 attachViewToParent(tv, insertIndex, tv.getLayoutParams()); 1020 } 1021 1022 // Enable hw layers on this view if hw layers are enabled on the stack 1023 if (mHwLayersTrigger.getCount() > 0) { 1024 tv.enableHwLayers(); 1025 } 1026 } 1027 1028 @Override 1029 public boolean hasPreferredData(TaskView tv, Task preferredData) { 1030 return (tv.getTask() == preferredData); 1031 } 1032 1033 /**** TaskViewCallbacks Implementation ****/ 1034 1035 @Override 1036 public void onTaskIconClicked(TaskView tv) { 1037 if (Console.Enabled) { 1038 Console.log(Constants.Log.UI.ClickEvents, "[TaskStack|Clicked|Icon]", 1039 tv.getTask() + " is currently filtered: " + mStack.hasFilteredTasks(), 1040 Console.AnsiCyan); 1041 } 1042 if (Constants.DebugFlags.App.EnableTaskFiltering) { 1043 if (mStack.hasFilteredTasks()) { 1044 mStack.unfilterTasks(); 1045 } else { 1046 mStack.filterTasks(tv.getTask()); 1047 } 1048 } 1049 } 1050 1051 @Override 1052 public void onTaskAppInfoClicked(TaskView tv) { 1053 if (mCb != null) { 1054 mCb.onTaskAppInfoLaunched(tv.getTask()); 1055 } 1056 } 1057 1058 @Override 1059 public void onTaskFocused(TaskView tv) { 1060 // Do nothing 1061 } 1062 1063 @Override 1064 public void onTaskDismissed(TaskView tv) { 1065 Task task = tv.getTask(); 1066 // Remove the task from the view 1067 mStack.removeTask(task); 1068 } 1069 1070 /**** View.OnClickListener Implementation ****/ 1071 1072 @Override 1073 public void onClick(View v) { 1074 TaskView tv = (TaskView) v; 1075 Task task = tv.getTask(); 1076 if (Console.Enabled) { 1077 Console.log(Constants.Log.UI.ClickEvents, "[TaskStack|Clicked|Thumbnail]", 1078 task + " cb: " + mCb); 1079 } 1080 1081 // Cancel any doze triggers 1082 mUIDozeTrigger.stopDozing(); 1083 1084 if (mCb != null) { 1085 mCb.onTaskLaunched(this, tv, mStack, task); 1086 } 1087 } 1088 1089 /**** RecentsPackageMonitor.PackageCallbacks Implementation ****/ 1090 1091 @Override 1092 public void onComponentRemoved(Set<ComponentName> cns) { 1093 // For other tasks, just remove them directly if they no longer exist 1094 ArrayList<Task> tasks = mStack.getTasks(); 1095 for (int i = tasks.size() - 1; i >= 0; i--) { 1096 final Task t = tasks.get(i); 1097 if (cns.contains(t.key.baseIntent.getComponent())) { 1098 TaskView tv = getChildViewForTask(t); 1099 if (tv != null) { 1100 // For visible children, defer removing the task until after the animation 1101 tv.startDeleteTaskAnimation(new Runnable() { 1102 @Override 1103 public void run() { 1104 mStack.removeTask(t); 1105 } 1106 }); 1107 } else { 1108 // Otherwise, remove the task from the stack immediately 1109 mStack.removeTask(t); 1110 } 1111 } 1112 } 1113 } 1114}