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