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