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