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