TaskStackView.java revision 653b0d6a5938f1eb1e656a63f84f9b3aab8163ab
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.util.Pair; 30import android.view.LayoutInflater; 31import android.view.MotionEvent; 32import android.view.VelocityTracker; 33import android.view.View; 34import android.view.ViewConfiguration; 35import android.view.ViewParent; 36import android.widget.FrameLayout; 37import android.widget.OverScroller; 38import com.android.systemui.R; 39import com.android.systemui.recents.Console; 40import com.android.systemui.recents.Constants; 41import com.android.systemui.recents.DozeTrigger; 42import com.android.systemui.recents.RecentsConfiguration; 43import com.android.systemui.recents.RecentsPackageMonitor; 44import com.android.systemui.recents.RecentsTaskLoader; 45import com.android.systemui.recents.ReferenceCountedTrigger; 46import com.android.systemui.recents.Utilities; 47import com.android.systemui.recents.model.Task; 48import com.android.systemui.recents.model.TaskStack; 49 50import java.util.ArrayList; 51import java.util.HashMap; 52import java.util.Set; 53 54 55/* The visual representation of a task stack view */ 56public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCallbacks, 57 TaskView.TaskViewCallbacks, ViewPool.ViewPoolConsumer<TaskView, Task>, 58 View.OnClickListener, RecentsPackageMonitor.PackageCallbacks { 59 60 /** The TaskView callbacks */ 61 interface TaskStackViewCallbacks { 62 public void onTaskLaunched(TaskStackView stackView, TaskView tv, TaskStack stack, Task t); 63 public void onTaskAppInfoLaunched(Task t); 64 public void onTaskRemoved(Task t); 65 } 66 67 RecentsConfiguration mConfig; 68 69 TaskStack mStack; 70 TaskStackViewTouchHandler mTouchHandler; 71 TaskStackViewCallbacks mCb; 72 ViewPool<TaskView, Task> mViewPool; 73 ArrayList<TaskViewTransform> mTaskTransforms = new ArrayList<TaskViewTransform>(); 74 DozeTrigger mDozeTrigger; 75 76 // The various rects that define the stack view 77 Rect mRect = new Rect(); 78 Rect mStackRect = new Rect(); 79 Rect mStackRectSansPeek = new Rect(); 80 Rect mTaskRect = new Rect(); 81 82 // The virtual stack scroll that we use for the card layout 83 int mStackScroll; 84 int mMinScroll; 85 int mMaxScroll; 86 int mStashedScroll; 87 int mFocusedTaskIndex = -1; 88 OverScroller mScroller; 89 ObjectAnimator mScrollAnimator; 90 91 // Optimizations 92 ReferenceCountedTrigger mHwLayersTrigger; 93 int mStackViewsAnimationDuration; 94 boolean mStackViewsDirty = true; 95 boolean mAwaitingFirstLayout = true; 96 boolean mStartEnterAnimationRequestedAfterLayout; 97 ViewAnimation.TaskViewEnterContext mStartEnterAnimationContext; 98 int[] mTmpVisibleRange = new int[2]; 99 Rect mTmpRect = new Rect(); 100 Rect mTmpRect2 = new Rect(); 101 LayoutInflater mInflater; 102 103 Runnable mReturnAllViewsToPoolRunnable = new Runnable() { 104 @Override 105 public void run() { 106 int childCount = getChildCount(); 107 for (int i = childCount - 1; i >= 0; i--) { 108 mViewPool.returnViewToPool((TaskView) getChildAt(i)); 109 } 110 } 111 }; 112 113 public TaskStackView(Context context, TaskStack stack) { 114 super(context); 115 mConfig = RecentsConfiguration.getInstance(); 116 mStack = stack; 117 mStack.setCallbacks(this); 118 mScroller = new OverScroller(context); 119 mTouchHandler = new TaskStackViewTouchHandler(context, this); 120 mViewPool = new ViewPool<TaskView, Task>(context, this); 121 mInflater = LayoutInflater.from(context); 122 mDozeTrigger = new DozeTrigger(mConfig.taskBarDismissDozeDelaySeconds, new Runnable() { 123 @Override 124 public void run() { 125 // Show the task bar dismiss buttons 126 int childCount = getChildCount(); 127 for (int i = 0; i < childCount; i++) { 128 TaskView tv = (TaskView) getChildAt(i); 129 tv.startNoUserInteractionAnimation(); 130 } 131 } 132 }); 133 mHwLayersTrigger = new ReferenceCountedTrigger(getContext(), new Runnable() { 134 @Override 135 public void run() { 136 // Enable hw layers on each of the children 137 int childCount = getChildCount(); 138 for (int i = 0; i < childCount; i++) { 139 TaskView tv = (TaskView) getChildAt(i); 140 tv.enableHwLayers(); 141 } 142 } 143 }, new Runnable() { 144 @Override 145 public void run() { 146 // Disable hw layers on each of the children 147 int childCount = getChildCount(); 148 for (int i = 0; i < childCount; i++) { 149 TaskView tv = (TaskView) getChildAt(i); 150 tv.disableHwLayers(); 151 } 152 } 153 }, new Runnable() { 154 @Override 155 public void run() { 156 new Throwable("Invalid hw layers ref count").printStackTrace(); 157 Console.logError(getContext(), "Invalid HW layers ref count"); 158 } 159 }); 160 } 161 162 /** Sets the callbacks */ 163 void setCallbacks(TaskStackViewCallbacks cb) { 164 mCb = cb; 165 } 166 167 /** Requests that the views be synchronized with the model */ 168 void requestSynchronizeStackViewsWithModel() { 169 requestSynchronizeStackViewsWithModel(0); 170 } 171 void requestSynchronizeStackViewsWithModel(int duration) { 172 if (Console.Enabled) { 173 Console.log(Constants.Log.TaskStack.SynchronizeViewsWithModel, 174 "[TaskStackView|requestSynchronize]", "" + duration + "ms", Console.AnsiYellow); 175 } 176 if (!mStackViewsDirty) { 177 invalidate(mStackRect); 178 } 179 if (mAwaitingFirstLayout) { 180 // Skip the animation if we are awaiting first layout 181 mStackViewsAnimationDuration = 0; 182 } else { 183 mStackViewsAnimationDuration = Math.max(mStackViewsAnimationDuration, duration); 184 } 185 mStackViewsDirty = true; 186 } 187 188 /** Finds the child view given a specific task */ 189 private TaskView getChildViewForTask(Task t) { 190 int childCount = getChildCount(); 191 for (int i = 0; i < childCount; i++) { 192 TaskView tv = (TaskView) getChildAt(i); 193 if (tv.getTask() == t) { 194 return tv; 195 } 196 } 197 return null; 198 } 199 200 /** Update/get the transform (creates a new TaskViewTransform) */ 201 public TaskViewTransform getStackTransform(int indexInStack, int stackScroll) { 202 TaskViewTransform transform = new TaskViewTransform(); 203 return getStackTransform(indexInStack, stackScroll, transform); 204 } 205 206 /** Update/get the transform */ 207 public TaskViewTransform getStackTransform(int indexInStack, int stackScroll, 208 TaskViewTransform transformOut) { 209 // Return early if we have an invalid index 210 if (indexInStack < 0) { 211 transformOut.reset(); 212 return transformOut; 213 } 214 215 // Map the items to an continuous position relative to the specified scroll 216 int numPeekCards = Constants.Values.TaskStackView.StackPeekNumCards; 217 float overlapHeight = Constants.Values.TaskStackView.StackOverlapPct * mTaskRect.height(); 218 float peekHeight = Constants.Values.TaskStackView.StackPeekHeightPct * mStackRect.height(); 219 float t = ((indexInStack * overlapHeight) - stackScroll) / overlapHeight; 220 float boundedT = Math.max(t, -(numPeekCards + 1)); 221 222 // Set the scale relative to its position 223 int numFrontScaledCards = 3; 224 float minScale = Constants.Values.TaskStackView.StackPeekMinScale; 225 float scaleRange = 1f - minScale; 226 float scaleInc = scaleRange / (numPeekCards + numFrontScaledCards); 227 float scale = Math.max(minScale, Math.min(1f, minScale + 228 ((boundedT + (numPeekCards + 1)) * scaleInc))); 229 float scaleYOffset = ((1f - scale) * mTaskRect.height()) / 2; 230 transformOut.scale = scale; 231 232 // Set the y translation 233 if (boundedT < 0f) { 234 transformOut.translationY = (int) ((Math.max(-numPeekCards, boundedT) / 235 numPeekCards) * peekHeight - scaleYOffset); 236 } else { 237 transformOut.translationY = (int) (boundedT * overlapHeight - scaleYOffset); 238 } 239 240 // Set the z translation 241 int minZ = mConfig.taskViewTranslationZMinPx; 242 int incZ = mConfig.taskViewTranslationZIncrementPx; 243 transformOut.translationZ = (int) Math.max(minZ, minZ + ((boundedT + numPeekCards) * incZ)); 244 245 // Set the alphas 246 transformOut.dismissAlpha = Math.max(-1f, Math.min(0f, t + 1)) + 1f; 247 248 // Update the rect and visibility 249 transformOut.rect.set(mTaskRect); 250 if (t < -(numPeekCards + 1)) { 251 transformOut.visible = false; 252 } else { 253 transformOut.rect.offset(0, transformOut.translationY); 254 Utilities.scaleRectAboutCenter(transformOut.rect, transformOut.scale); 255 transformOut.visible = Rect.intersects(mRect, transformOut.rect); 256 } 257 transformOut.t = t; 258 return transformOut; 259 } 260 261 /** 262 * Gets the stack transforms of a list of tasks, and returns the visible range of tasks. 263 */ 264 private void updateStackTransforms(ArrayList<TaskViewTransform> taskTransforms, 265 ArrayList<Task> tasks, 266 int stackScroll, 267 int[] visibleRangeOut, 268 boolean boundTranslationsToRect) { 269 // XXX: Optimization: Use binary search to find the visible range 270 271 int taskTransformCount = taskTransforms.size(); 272 int taskCount = tasks.size(); 273 int firstVisibleIndex = -1; 274 int lastVisibleIndex = -1; 275 276 // We can reuse the task transforms where possible to reduce object allocation 277 if (taskTransformCount < taskCount) { 278 // If there are less transforms than tasks, then add as many transforms as necessary 279 for (int i = taskTransformCount; i < taskCount; i++) { 280 taskTransforms.add(new TaskViewTransform()); 281 } 282 } else if (taskTransformCount > taskCount) { 283 // If there are more transforms than tasks, then just subset the transform list 284 taskTransforms.subList(0, taskCount); 285 } 286 287 // Update the stack transforms 288 for (int i = 0; i < taskCount; i++) { 289 TaskViewTransform transform = getStackTransform(i, stackScroll, taskTransforms.get(i)); 290 if (transform.visible) { 291 if (firstVisibleIndex < 0) { 292 firstVisibleIndex = i; 293 } 294 lastVisibleIndex = i; 295 } 296 297 if (boundTranslationsToRect) { 298 transform.translationY = Math.min(transform.translationY, mRect.bottom); 299 } 300 } 301 if (visibleRangeOut != null) { 302 visibleRangeOut[0] = firstVisibleIndex; 303 visibleRangeOut[1] = lastVisibleIndex; 304 } 305 } 306 307 /** 308 * Gets the stack transforms of a list of tasks, and returns the visible range of tasks. This 309 * call is less optimal than calling updateStackTransforms directly. 310 */ 311 private ArrayList<TaskViewTransform> getStackTransforms(ArrayList<Task> tasks, 312 int stackScroll, 313 int[] visibleRangeOut, 314 boolean boundTranslationsToRect) { 315 ArrayList<TaskViewTransform> taskTransforms = new ArrayList<TaskViewTransform>(); 316 updateStackTransforms(taskTransforms, tasks, stackScroll, visibleRangeOut, 317 boundTranslationsToRect); 318 return taskTransforms; 319 } 320 321 /** Synchronizes the views with the model */ 322 void synchronizeStackViewsWithModel() { 323 if (Console.Enabled) { 324 Console.log(Constants.Log.TaskStack.SynchronizeViewsWithModel, 325 "[TaskStackView|synchronizeViewsWithModel]", 326 "mStackViewsDirty: " + mStackViewsDirty, Console.AnsiYellow); 327 } 328 if (mStackViewsDirty) { 329 // XXX: Consider using TaskViewTransform pool to prevent allocations 330 // XXX: Iterate children views, update transforms and remove all that are not visible 331 // For all remaining tasks, update transforms and if visible add the view 332 333 // Get all the task transforms 334 int[] visibleRange = mTmpVisibleRange; 335 int stackScroll = getStackScroll(); 336 ArrayList<Task> tasks = mStack.getTasks(); 337 updateStackTransforms(mTaskTransforms, tasks, stackScroll, visibleRange, false); 338 339 // Update the visible state of all the tasks 340 int taskCount = tasks.size(); 341 for (int i = 0; i < taskCount; i++) { 342 Task task = tasks.get(i); 343 TaskViewTransform transform = mTaskTransforms.get(i); 344 TaskView tv = getChildViewForTask(task); 345 346 if (transform.visible) { 347 if (tv == null) { 348 tv = mViewPool.pickUpViewFromPool(task, task); 349 // When we are picking up a new view from the view pool, prepare it for any 350 // following animation by putting it in a reasonable place 351 if (mStackViewsAnimationDuration > 0 && i != 0) { 352 int fromIndex = (transform.t < 0) ? (visibleRange[0] - 1) : 353 (visibleRange[1] + 1); 354 tv.updateViewPropertiesToTaskTransform( 355 getStackTransform(fromIndex, stackScroll), 0); 356 } 357 } 358 } else { 359 if (tv != null) { 360 mViewPool.returnViewToPool(tv); 361 } 362 } 363 } 364 365 // Update all the remaining view children 366 // NOTE: We have to iterate in reverse where because we are removing views directly 367 int childCount = getChildCount(); 368 for (int i = childCount - 1; i >= 0; i--) { 369 TaskView tv = (TaskView) getChildAt(i); 370 Task task = tv.getTask(); 371 int taskIndex = mStack.indexOfTask(task); 372 if (taskIndex < 0 || !mTaskTransforms.get(taskIndex).visible) { 373 mViewPool.returnViewToPool(tv); 374 } else { 375 tv.updateViewPropertiesToTaskTransform(mTaskTransforms.get(taskIndex), 376 mStackViewsAnimationDuration); 377 } 378 } 379 380 if (Console.Enabled) { 381 Console.log(Constants.Log.TaskStack.SynchronizeViewsWithModel, 382 " [TaskStackView|viewChildren]", "" + getChildCount()); 383 } 384 385 mStackViewsAnimationDuration = 0; 386 mStackViewsDirty = false; 387 } 388 } 389 390 /** Sets the current stack scroll */ 391 public void setStackScroll(int value) { 392 mStackScroll = value; 393 requestSynchronizeStackViewsWithModel(); 394 } 395 /** Sets the current stack scroll without synchronizing the stack view with the model */ 396 public void setStackScrollRaw(int value) { 397 mStackScroll = value; 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 mDozeTrigger.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 // If the dozer is not running, then either we have not yet laid out, or it has already 935 // fallen asleep, so just let it rest. 936 if (mDozeTrigger.isDozing()) { 937 mDozeTrigger.poke(); 938 } 939 } 940 941 /**** TaskStackCallbacks Implementation ****/ 942 943 @Override 944 public void onStackTaskAdded(TaskStack stack, Task t) { 945 requestSynchronizeStackViewsWithModel(); 946 } 947 948 @Override 949 public void onStackTaskRemoved(TaskStack stack, Task t) { 950 // Remove the view associated with this task, we can't rely on updateTransforms 951 // to work here because the task is no longer in the list 952 TaskView tv = getChildViewForTask(t); 953 if (tv != null) { 954 mViewPool.returnViewToPool(tv); 955 } 956 957 // Notify the callback that we've removed the task and it can clean up after it 958 mCb.onTaskRemoved(t); 959 960 // Update the min/max scroll and animate other task views into their new positions 961 updateMinMaxScroll(true); 962 int movement = (int) (Constants.Values.TaskStackView.StackOverlapPct * mTaskRect.height()); 963 requestSynchronizeStackViewsWithModel(Utilities.calculateTranslationAnimationDuration(movement)); 964 965 // If there are no remaining tasks, then either unfilter the current stack, or just close 966 // the activity if there are no filtered stacks 967 if (mStack.getTaskCount() == 0) { 968 boolean shouldFinishActivity = true; 969 if (mStack.hasFilteredTasks()) { 970 mStack.unfilterTasks(); 971 shouldFinishActivity = (mStack.getTaskCount() == 0); 972 } 973 if (shouldFinishActivity) { 974 Activity activity = (Activity) getContext(); 975 activity.finish(); 976 } 977 } 978 } 979 980 /** 981 * Creates the animations for all the children views that need to be removed or to move views 982 * to their un/filtered position when we are un/filtering a stack, and returns the duration 983 * for these animations. 984 */ 985 int getExitTransformsForFilterAnimation(ArrayList<Task> curTasks, 986 ArrayList<TaskViewTransform> curTaskTransforms, 987 ArrayList<Task> tasks, ArrayList<TaskViewTransform> taskTransforms, 988 HashMap<TaskView, TaskViewTransform> childViewTransformsOut, 989 ArrayList<TaskView> childrenToRemoveOut) { 990 // Animate all of the existing views out of view (if they are not in the visible range in 991 // the new stack) or to their final positions in the new stack 992 int offset = 0; 993 int movement = 0; 994 int childCount = getChildCount(); 995 for (int i = 0; i < childCount; i++) { 996 TaskView tv = (TaskView) getChildAt(i); 997 Task task = tv.getTask(); 998 int taskIndex = tasks.indexOf(task); 999 TaskViewTransform toTransform; 1000 1001 // If the view is no longer visible, then we should just animate it out 1002 boolean willBeInvisible = taskIndex < 0 || !taskTransforms.get(taskIndex).visible; 1003 if (willBeInvisible) { 1004 if (taskIndex < 0) { 1005 toTransform = curTaskTransforms.get(curTasks.indexOf(task)); 1006 } else { 1007 toTransform = new TaskViewTransform(taskTransforms.get(taskIndex)); 1008 } 1009 tv.prepareTaskTransformForFilterTaskVisible(toTransform); 1010 childrenToRemoveOut.add(tv); 1011 } else { 1012 toTransform = taskTransforms.get(taskIndex); 1013 // Use the movement of the visible views to calculate the duration of the animation 1014 movement = Math.max(movement, Math.abs(toTransform.translationY - 1015 (int) tv.getTranslationY())); 1016 } 1017 1018 toTransform.startDelay = offset * Constants.Values.TaskStackView.FilterStartDelay; 1019 childViewTransformsOut.put(tv, toTransform); 1020 offset++; 1021 } 1022 return mConfig.filteringCurrentViewsAnimDuration; 1023 } 1024 1025 /** 1026 * Creates the animations for all the children views that need to be animated in when we are 1027 * un/filtering a stack, and returns the duration for these animations. 1028 */ 1029 int getEnterTransformsForFilterAnimation(ArrayList<Task> tasks, 1030 ArrayList<TaskViewTransform> taskTransforms, 1031 HashMap<TaskView, TaskViewTransform> childViewTransformsOut) { 1032 int offset = 0; 1033 int movement = 0; 1034 int taskCount = tasks.size(); 1035 for (int i = taskCount - 1; i >= 0; i--) { 1036 Task task = tasks.get(i); 1037 TaskViewTransform toTransform = taskTransforms.get(i); 1038 if (toTransform.visible) { 1039 TaskView tv = getChildViewForTask(task); 1040 if (tv == null) { 1041 // For views that are not already visible, animate them in 1042 tv = mViewPool.pickUpViewFromPool(task, task); 1043 1044 // Compose a new transform to fade and slide the new task in 1045 TaskViewTransform fromTransform = new TaskViewTransform(toTransform); 1046 tv.prepareTaskTransformForFilterTaskHidden(fromTransform); 1047 tv.updateViewPropertiesToTaskTransform(fromTransform, 0); 1048 1049 toTransform.startDelay = offset * Constants.Values.TaskStackView.FilterStartDelay; 1050 childViewTransformsOut.put(tv, toTransform); 1051 1052 // Use the movement of the new views to calculate the duration of the animation 1053 movement = Math.max(movement, 1054 Math.abs(toTransform.translationY - fromTransform.translationY)); 1055 offset++; 1056 } 1057 } 1058 } 1059 return mConfig.filteringNewViewsAnimDuration; 1060 } 1061 1062 /** Orchestrates the animations of the current child views and any new views. */ 1063 void doFilteringAnimation(ArrayList<Task> curTasks, 1064 ArrayList<TaskViewTransform> curTaskTransforms, 1065 final ArrayList<Task> tasks, 1066 final ArrayList<TaskViewTransform> taskTransforms) { 1067 // Calculate the transforms to animate out all the existing views if they are not in the 1068 // new visible range (or to their final positions in the stack if they are) 1069 final ArrayList<TaskView> childrenToRemove = new ArrayList<TaskView>(); 1070 final HashMap<TaskView, TaskViewTransform> childViewTransforms = 1071 new HashMap<TaskView, TaskViewTransform>(); 1072 int duration = getExitTransformsForFilterAnimation(curTasks, curTaskTransforms, tasks, 1073 taskTransforms, childViewTransforms, childrenToRemove); 1074 1075 // If all the current views are in the visible range of the new stack, then don't wait for 1076 // views to animate out and animate all the new views into their place 1077 final boolean unifyNewViewAnimation = childrenToRemove.isEmpty(); 1078 if (unifyNewViewAnimation) { 1079 int inDuration = getEnterTransformsForFilterAnimation(tasks, taskTransforms, 1080 childViewTransforms); 1081 duration = Math.max(duration, inDuration); 1082 } 1083 1084 // Animate all the views to their final transforms 1085 for (final TaskView tv : childViewTransforms.keySet()) { 1086 TaskViewTransform t = childViewTransforms.get(tv); 1087 tv.animate().cancel(); 1088 tv.animate() 1089 .withEndAction(new Runnable() { 1090 @Override 1091 public void run() { 1092 childViewTransforms.remove(tv); 1093 if (childViewTransforms.isEmpty()) { 1094 // Return all the removed children to the view pool 1095 for (TaskView tv : childrenToRemove) { 1096 mViewPool.returnViewToPool(tv); 1097 } 1098 1099 if (!unifyNewViewAnimation) { 1100 // For views that are not already visible, animate them in 1101 childViewTransforms.clear(); 1102 int duration = getEnterTransformsForFilterAnimation(tasks, 1103 taskTransforms, childViewTransforms); 1104 for (final TaskView tv : childViewTransforms.keySet()) { 1105 TaskViewTransform t = childViewTransforms.get(tv); 1106 tv.updateViewPropertiesToTaskTransform(t, duration); 1107 } 1108 } 1109 } 1110 } 1111 }); 1112 tv.updateViewPropertiesToTaskTransform(t, duration); 1113 } 1114 } 1115 1116 @Override 1117 public void onStackFiltered(TaskStack newStack, final ArrayList<Task> curTasks, 1118 Task filteredTask) { 1119 // Stash the scroll and filtered task for us to restore to when we unfilter 1120 mStashedScroll = getStackScroll(); 1121 1122 // Calculate the current task transforms 1123 ArrayList<TaskViewTransform> curTaskTransforms = 1124 getStackTransforms(curTasks, getStackScroll(), null, true); 1125 1126 // Scroll the item to the top of the stack (sans-peek) rect so that we can see it better 1127 updateMinMaxScroll(false); 1128 float overlapHeight = Constants.Values.TaskStackView.StackOverlapPct * mTaskRect.height(); 1129 setStackScrollRaw((int) (newStack.indexOfTask(filteredTask) * overlapHeight)); 1130 boundScrollRaw(); 1131 1132 // Compute the transforms of the items in the new stack after setting the new scroll 1133 final ArrayList<Task> tasks = mStack.getTasks(); 1134 final ArrayList<TaskViewTransform> taskTransforms = 1135 getStackTransforms(mStack.getTasks(), getStackScroll(), null, true); 1136 1137 // Animate 1138 doFilteringAnimation(curTasks, curTaskTransforms, tasks, taskTransforms); 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 1164 /**** ViewPoolConsumer Implementation ****/ 1165 1166 @Override 1167 public TaskView createView(Context context) { 1168 if (Console.Enabled) { 1169 Console.log(Constants.Log.ViewPool.PoolCallbacks, "[TaskStackView|createPoolView]"); 1170 } 1171 return (TaskView) mInflater.inflate(R.layout.recents_task_view, this, false); 1172 } 1173 1174 @Override 1175 public void prepareViewToEnterPool(TaskView tv) { 1176 Task task = tv.getTask(); 1177 tv.resetViewProperties(); 1178 if (Console.Enabled) { 1179 Console.log(Constants.Log.ViewPool.PoolCallbacks, "[TaskStackView|returnToPool]", 1180 tv.getTask() + " tv: " + tv); 1181 } 1182 1183 // Report that this tasks's data is no longer being used 1184 RecentsTaskLoader loader = RecentsTaskLoader.getInstance(); 1185 loader.unloadTaskData(task); 1186 1187 // Detach the view from the hierarchy 1188 detachViewFromParent(tv); 1189 1190 // Disable hw layers on this view 1191 tv.disableHwLayers(); 1192 } 1193 1194 @Override 1195 public void prepareViewToLeavePool(TaskView tv, Task prepareData, boolean isNewView) { 1196 if (Console.Enabled) { 1197 Console.log(Constants.Log.ViewPool.PoolCallbacks, "[TaskStackView|leavePool]", 1198 "isNewView: " + isNewView); 1199 } 1200 1201 // Setup and attach the view to the window 1202 Task task = prepareData; 1203 // We try and rebind the task (this MUST be done before the task filled) 1204 tv.onTaskBound(task); 1205 // Request that this tasks's data be filled 1206 RecentsTaskLoader loader = RecentsTaskLoader.getInstance(); 1207 loader.loadTaskData(task); 1208 // Find the index where this task should be placed in the children 1209 int insertIndex = -1; 1210 int childCount = getChildCount(); 1211 for (int i = 0; i < childCount; i++) { 1212 Task tvTask = ((TaskView) getChildAt(i)).getTask(); 1213 if (mStack.containsTask(task) && (mStack.indexOfTask(task) < mStack.indexOfTask(tvTask))) { 1214 insertIndex = i; 1215 break; 1216 } 1217 } 1218 1219 // Sanity check, the task view should always be clipping against the stack at this point, 1220 // but just in case, re-enable it here 1221 tv.setClipViewInStack(true); 1222 1223 // If the doze trigger has already fired, then update the state for this task view 1224 if (mDozeTrigger.hasTriggered()) { 1225 tv.setNoUserInteractionState(); 1226 } 1227 1228 // Add/attach the view to the hierarchy 1229 if (Console.Enabled) { 1230 Console.log(Constants.Log.ViewPool.PoolCallbacks, " [TaskStackView|insertIndex]", 1231 "" + insertIndex); 1232 } 1233 if (isNewView) { 1234 addView(tv, insertIndex); 1235 1236 // Set the callbacks and listeners for this new view 1237 tv.setOnClickListener(this); 1238 tv.setCallbacks(this); 1239 } else { 1240 attachViewToParent(tv, insertIndex, tv.getLayoutParams()); 1241 } 1242 1243 // Enable hw layers on this view if hw layers are enabled on the stack 1244 if (mHwLayersTrigger.getCount() > 0) { 1245 tv.enableHwLayers(); 1246 } 1247 } 1248 1249 @Override 1250 public boolean hasPreferredData(TaskView tv, Task preferredData) { 1251 return (tv.getTask() == preferredData); 1252 } 1253 1254 /**** TaskViewCallbacks Implementation ****/ 1255 1256 @Override 1257 public void onTaskIconClicked(TaskView tv) { 1258 if (Console.Enabled) { 1259 Console.log(Constants.Log.UI.ClickEvents, "[TaskStack|Clicked|Icon]", 1260 tv.getTask() + " is currently filtered: " + mStack.hasFilteredTasks(), 1261 Console.AnsiCyan); 1262 } 1263 if (Constants.DebugFlags.App.EnableTaskFiltering) { 1264 if (mStack.hasFilteredTasks()) { 1265 mStack.unfilterTasks(); 1266 } else { 1267 mStack.filterTasks(tv.getTask()); 1268 } 1269 } 1270 } 1271 1272 @Override 1273 public void onTaskAppInfoClicked(TaskView tv) { 1274 if (mCb != null) { 1275 mCb.onTaskAppInfoLaunched(tv.getTask()); 1276 } 1277 } 1278 1279 @Override 1280 public void onTaskFocused(TaskView tv) { 1281 // Do nothing 1282 } 1283 1284 @Override 1285 public void onTaskDismissed(TaskView tv) { 1286 Task task = tv.getTask(); 1287 // Remove the task from the view 1288 mStack.removeTask(task); 1289 } 1290 1291 /**** View.OnClickListener Implementation ****/ 1292 1293 @Override 1294 public void onClick(View v) { 1295 TaskView tv = (TaskView) v; 1296 Task task = tv.getTask(); 1297 if (Console.Enabled) { 1298 Console.log(Constants.Log.UI.ClickEvents, "[TaskStack|Clicked|Thumbnail]", 1299 task + " cb: " + mCb); 1300 } 1301 1302 if (mCb != null) { 1303 mCb.onTaskLaunched(this, tv, mStack, task); 1304 } 1305 } 1306 1307 /**** RecentsPackageMonitor.PackageCallbacks Implementation ****/ 1308 1309 @Override 1310 public void onComponentRemoved(Set<ComponentName> cns) { 1311 // For other tasks, just remove them directly if they no longer exist 1312 ArrayList<Task> tasks = mStack.getTasks(); 1313 for (int i = tasks.size() - 1; i >= 0; i--) { 1314 final Task t = tasks.get(i); 1315 if (cns.contains(t.key.baseIntent.getComponent())) { 1316 TaskView tv = getChildViewForTask(t); 1317 if (tv != null) { 1318 // For visible children, defer removing the task until after the animation 1319 tv.startDeleteTaskAnimation(new Runnable() { 1320 @Override 1321 public void run() { 1322 mStack.removeTask(t); 1323 } 1324 }); 1325 } else { 1326 // Otherwise, remove the task from the stack immediately 1327 mStack.removeTask(t); 1328 } 1329 } 1330 } 1331 } 1332} 1333 1334/* Handles touch events */ 1335class TaskStackViewTouchHandler implements SwipeHelper.Callback { 1336 static int INACTIVE_POINTER_ID = -1; 1337 1338 TaskStackView mSv; 1339 VelocityTracker mVelocityTracker; 1340 1341 boolean mIsScrolling; 1342 1343 int mInitialMotionX, mInitialMotionY; 1344 int mLastMotionX, mLastMotionY; 1345 int mActivePointerId = INACTIVE_POINTER_ID; 1346 TaskView mActiveTaskView = null; 1347 1348 int mTotalScrollMotion; 1349 int mMinimumVelocity; 1350 int mMaximumVelocity; 1351 // The scroll touch slop is used to calculate when we start scrolling 1352 int mScrollTouchSlop; 1353 // The page touch slop is used to calculate when we start swiping 1354 float mPagingTouchSlop; 1355 1356 SwipeHelper mSwipeHelper; 1357 boolean mInterceptedBySwipeHelper; 1358 1359 public TaskStackViewTouchHandler(Context context, TaskStackView sv) { 1360 ViewConfiguration configuration = ViewConfiguration.get(context); 1361 mMinimumVelocity = configuration.getScaledMinimumFlingVelocity(); 1362 mMaximumVelocity = configuration.getScaledMaximumFlingVelocity(); 1363 mScrollTouchSlop = configuration.getScaledTouchSlop(); 1364 mPagingTouchSlop = configuration.getScaledPagingTouchSlop(); 1365 mSv = sv; 1366 1367 1368 float densityScale = context.getResources().getDisplayMetrics().density; 1369 mSwipeHelper = new SwipeHelper(SwipeHelper.X, this, densityScale, mPagingTouchSlop); 1370 mSwipeHelper.setMinAlpha(1f); 1371 } 1372 1373 /** Velocity tracker helpers */ 1374 void initOrResetVelocityTracker() { 1375 if (mVelocityTracker == null) { 1376 mVelocityTracker = VelocityTracker.obtain(); 1377 } else { 1378 mVelocityTracker.clear(); 1379 } 1380 } 1381 void initVelocityTrackerIfNotExists() { 1382 if (mVelocityTracker == null) { 1383 mVelocityTracker = VelocityTracker.obtain(); 1384 } 1385 } 1386 void recycleVelocityTracker() { 1387 if (mVelocityTracker != null) { 1388 mVelocityTracker.recycle(); 1389 mVelocityTracker = null; 1390 } 1391 } 1392 1393 /** Returns the view at the specified coordinates */ 1394 TaskView findViewAtPoint(int x, int y) { 1395 int childCount = mSv.getChildCount(); 1396 for (int i = childCount - 1; i >= 0; i--) { 1397 TaskView tv = (TaskView) mSv.getChildAt(i); 1398 if (tv.getVisibility() == View.VISIBLE) { 1399 if (mSv.isTransformedTouchPointInView(x, y, tv)) { 1400 return tv; 1401 } 1402 } 1403 } 1404 return null; 1405 } 1406 1407 /** Touch preprocessing for handling below */ 1408 public boolean onInterceptTouchEvent(MotionEvent ev) { 1409 if (Console.Enabled) { 1410 Console.log(Constants.Log.UI.TouchEvents, 1411 "[TaskStackViewTouchHandler|interceptTouchEvent]", 1412 Console.motionEventActionToString(ev.getAction()), Console.AnsiBlue); 1413 } 1414 1415 // Return early if we have no children 1416 boolean hasChildren = (mSv.getChildCount() > 0); 1417 if (!hasChildren) { 1418 return false; 1419 } 1420 1421 // Pass through to swipe helper if we are swiping 1422 mInterceptedBySwipeHelper = mSwipeHelper.onInterceptTouchEvent(ev); 1423 if (mInterceptedBySwipeHelper) { 1424 return true; 1425 } 1426 1427 boolean wasScrolling = !mSv.mScroller.isFinished() || 1428 (mSv.mScrollAnimator != null && mSv.mScrollAnimator.isRunning()); 1429 int action = ev.getAction(); 1430 switch (action & MotionEvent.ACTION_MASK) { 1431 case MotionEvent.ACTION_DOWN: { 1432 // Save the touch down info 1433 mInitialMotionX = mLastMotionX = (int) ev.getX(); 1434 mInitialMotionY = mLastMotionY = (int) ev.getY(); 1435 mActivePointerId = ev.getPointerId(0); 1436 mActiveTaskView = findViewAtPoint(mLastMotionX, mLastMotionY); 1437 // Stop the current scroll if it is still flinging 1438 mSv.abortScroller(); 1439 mSv.abortBoundScrollAnimation(); 1440 // Initialize the velocity tracker 1441 initOrResetVelocityTracker(); 1442 mVelocityTracker.addMovement(ev); 1443 // Check if the scroller is finished yet 1444 mIsScrolling = !mSv.mScroller.isFinished(); 1445 break; 1446 } 1447 case MotionEvent.ACTION_MOVE: { 1448 if (mActivePointerId == INACTIVE_POINTER_ID) break; 1449 1450 int activePointerIndex = ev.findPointerIndex(mActivePointerId); 1451 int y = (int) ev.getY(activePointerIndex); 1452 int x = (int) ev.getX(activePointerIndex); 1453 if (Math.abs(y - mInitialMotionY) > mScrollTouchSlop) { 1454 // Save the touch move info 1455 mIsScrolling = true; 1456 // Initialize the velocity tracker if necessary 1457 initVelocityTrackerIfNotExists(); 1458 mVelocityTracker.addMovement(ev); 1459 // Disallow parents from intercepting touch events 1460 final ViewParent parent = mSv.getParent(); 1461 if (parent != null) { 1462 parent.requestDisallowInterceptTouchEvent(true); 1463 } 1464 // Enable HW layers 1465 mSv.addHwLayersRefCount("stackScroll"); 1466 } 1467 1468 mLastMotionX = x; 1469 mLastMotionY = y; 1470 break; 1471 } 1472 case MotionEvent.ACTION_CANCEL: 1473 case MotionEvent.ACTION_UP: { 1474 // Animate the scroll back if we've cancelled 1475 mSv.animateBoundScroll(); 1476 // Disable HW layers 1477 if (mIsScrolling) { 1478 mSv.decHwLayersRefCount("stackScroll"); 1479 } 1480 // Reset the drag state and the velocity tracker 1481 mIsScrolling = false; 1482 mActivePointerId = INACTIVE_POINTER_ID; 1483 mActiveTaskView = null; 1484 mTotalScrollMotion = 0; 1485 recycleVelocityTracker(); 1486 break; 1487 } 1488 } 1489 1490 return wasScrolling || mIsScrolling; 1491 } 1492 1493 /** Handles touch events once we have intercepted them */ 1494 public boolean onTouchEvent(MotionEvent ev) { 1495 if (Console.Enabled) { 1496 Console.log(Constants.Log.UI.TouchEvents, 1497 "[TaskStackViewTouchHandler|touchEvent]", 1498 Console.motionEventActionToString(ev.getAction()), Console.AnsiBlue); 1499 } 1500 1501 // Short circuit if we have no children 1502 boolean hasChildren = (mSv.getChildCount() > 0); 1503 if (!hasChildren) { 1504 return false; 1505 } 1506 1507 // Pass through to swipe helper if we are swiping 1508 if (mInterceptedBySwipeHelper && mSwipeHelper.onTouchEvent(ev)) { 1509 return true; 1510 } 1511 1512 // Update the velocity tracker 1513 initVelocityTrackerIfNotExists(); 1514 mVelocityTracker.addMovement(ev); 1515 1516 int action = ev.getAction(); 1517 switch (action & MotionEvent.ACTION_MASK) { 1518 case MotionEvent.ACTION_DOWN: { 1519 // Save the touch down info 1520 mInitialMotionX = mLastMotionX = (int) ev.getX(); 1521 mInitialMotionY = mLastMotionY = (int) ev.getY(); 1522 mActivePointerId = ev.getPointerId(0); 1523 mActiveTaskView = findViewAtPoint(mLastMotionX, mLastMotionY); 1524 // Stop the current scroll if it is still flinging 1525 mSv.abortScroller(); 1526 mSv.abortBoundScrollAnimation(); 1527 // Initialize the velocity tracker 1528 initOrResetVelocityTracker(); 1529 mVelocityTracker.addMovement(ev); 1530 // Disallow parents from intercepting touch events 1531 final ViewParent parent = mSv.getParent(); 1532 if (parent != null) { 1533 parent.requestDisallowInterceptTouchEvent(true); 1534 } 1535 break; 1536 } 1537 case MotionEvent.ACTION_POINTER_DOWN: { 1538 final int index = ev.getActionIndex(); 1539 mActivePointerId = ev.getPointerId(index); 1540 mLastMotionX = (int) ev.getX(index); 1541 mLastMotionY = (int) ev.getY(index); 1542 break; 1543 } 1544 case MotionEvent.ACTION_MOVE: { 1545 if (mActivePointerId == INACTIVE_POINTER_ID) break; 1546 1547 int activePointerIndex = ev.findPointerIndex(mActivePointerId); 1548 int x = (int) ev.getX(activePointerIndex); 1549 int y = (int) ev.getY(activePointerIndex); 1550 int yTotal = Math.abs(y - mInitialMotionY); 1551 int deltaY = mLastMotionY - y; 1552 if (!mIsScrolling) { 1553 if (yTotal > mScrollTouchSlop) { 1554 mIsScrolling = true; 1555 // Initialize the velocity tracker 1556 initOrResetVelocityTracker(); 1557 mVelocityTracker.addMovement(ev); 1558 // Disallow parents from intercepting touch events 1559 final ViewParent parent = mSv.getParent(); 1560 if (parent != null) { 1561 parent.requestDisallowInterceptTouchEvent(true); 1562 } 1563 // Enable HW layers 1564 mSv.addHwLayersRefCount("stackScroll"); 1565 } 1566 } 1567 if (mIsScrolling) { 1568 int curStackScroll = mSv.getStackScroll(); 1569 int overScrollAmount = mSv.getScrollAmountOutOfBounds(curStackScroll + deltaY); 1570 if (overScrollAmount != 0) { 1571 // Bound the overscroll to a fixed amount, and inversely scale the y-movement 1572 // relative to how close we are to the max overscroll 1573 float maxOverScroll = mSv.mTaskRect.height() / 3f; 1574 deltaY = Math.round(deltaY * (1f - (Math.min(maxOverScroll, overScrollAmount) 1575 / maxOverScroll))); 1576 } 1577 mSv.setStackScroll(curStackScroll + deltaY); 1578 if (mSv.isScrollOutOfBounds()) { 1579 mVelocityTracker.clear(); 1580 } 1581 } 1582 mLastMotionX = x; 1583 mLastMotionY = y; 1584 mTotalScrollMotion += Math.abs(deltaY); 1585 break; 1586 } 1587 case MotionEvent.ACTION_UP: { 1588 final VelocityTracker velocityTracker = mVelocityTracker; 1589 velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); 1590 int velocity = (int) velocityTracker.getYVelocity(mActivePointerId); 1591 1592 if (mIsScrolling && (Math.abs(velocity) > mMinimumVelocity)) { 1593 // Enable HW layers on the stack 1594 mSv.addHwLayersRefCount("flingScroll"); 1595 // XXX: Make this animation a function of the velocity AND distance 1596 int overscrollRange = (int) (Math.min(1f, 1597 Math.abs((float) velocity / mMaximumVelocity)) * 1598 Constants.Values.TaskStackView.TaskStackOverscrollRange); 1599 1600 if (Console.Enabled) { 1601 Console.log(Constants.Log.UI.TouchEvents, 1602 "[TaskStackViewTouchHandler|fling]", 1603 "scroll: " + mSv.getStackScroll() + " velocity: " + velocity + 1604 " maxVelocity: " + mMaximumVelocity + 1605 " overscrollRange: " + overscrollRange, 1606 Console.AnsiGreen); 1607 } 1608 1609 // Fling scroll 1610 mSv.mScroller.fling(0, mSv.getStackScroll(), 1611 0, -velocity, 1612 0, 0, 1613 mSv.mMinScroll, mSv.mMaxScroll, 1614 0, overscrollRange); 1615 // Invalidate to kick off computeScroll 1616 mSv.invalidate(mSv.mStackRect); 1617 } else if (mSv.isScrollOutOfBounds()) { 1618 // Animate the scroll back into bounds 1619 // XXX: Make this animation a function of the velocity OR distance 1620 mSv.animateBoundScroll(); 1621 } 1622 1623 if (mIsScrolling) { 1624 // Disable HW layers 1625 mSv.decHwLayersRefCount("stackScroll"); 1626 } 1627 mActivePointerId = INACTIVE_POINTER_ID; 1628 mIsScrolling = false; 1629 mTotalScrollMotion = 0; 1630 recycleVelocityTracker(); 1631 break; 1632 } 1633 case MotionEvent.ACTION_POINTER_UP: { 1634 int pointerIndex = ev.getActionIndex(); 1635 int pointerId = ev.getPointerId(pointerIndex); 1636 if (pointerId == mActivePointerId) { 1637 // Select a new active pointer id and reset the motion state 1638 final int newPointerIndex = (pointerIndex == 0) ? 1 : 0; 1639 mActivePointerId = ev.getPointerId(newPointerIndex); 1640 mLastMotionX = (int) ev.getX(newPointerIndex); 1641 mLastMotionY = (int) ev.getY(newPointerIndex); 1642 mVelocityTracker.clear(); 1643 } 1644 break; 1645 } 1646 case MotionEvent.ACTION_CANCEL: { 1647 if (mIsScrolling) { 1648 // Disable HW layers 1649 mSv.decHwLayersRefCount("stackScroll"); 1650 } 1651 if (mSv.isScrollOutOfBounds()) { 1652 // Animate the scroll back into bounds 1653 // XXX: Make this animation a function of the velocity OR distance 1654 mSv.animateBoundScroll(); 1655 } 1656 mActivePointerId = INACTIVE_POINTER_ID; 1657 mIsScrolling = false; 1658 mTotalScrollMotion = 0; 1659 recycleVelocityTracker(); 1660 break; 1661 } 1662 } 1663 return true; 1664 } 1665 1666 /**** SwipeHelper Implementation ****/ 1667 1668 @Override 1669 public View getChildAtPosition(MotionEvent ev) { 1670 return findViewAtPoint((int) ev.getX(), (int) ev.getY()); 1671 } 1672 1673 @Override 1674 public boolean canChildBeDismissed(View v) { 1675 return true; 1676 } 1677 1678 @Override 1679 public void onBeginDrag(View v) { 1680 // Enable HW layers 1681 mSv.addHwLayersRefCount("swipeBegin"); 1682 // Disable clipping with the stack while we are swiping 1683 TaskView tv = (TaskView) v; 1684 tv.setClipViewInStack(false); 1685 // Disallow parents from intercepting touch events 1686 final ViewParent parent = mSv.getParent(); 1687 if (parent != null) { 1688 parent.requestDisallowInterceptTouchEvent(true); 1689 } 1690 } 1691 1692 @Override 1693 public void onSwipeChanged(View v, float delta) { 1694 // Do nothing 1695 } 1696 1697 @Override 1698 public void onChildDismissed(View v) { 1699 TaskView tv = (TaskView) v; 1700 mSv.onTaskDismissed(tv); 1701 1702 // Re-enable clipping with the stack (we will reuse this view) 1703 tv.setClipViewInStack(true); 1704 1705 // Disable HW layers 1706 mSv.decHwLayersRefCount("swipeComplete"); 1707 } 1708 1709 @Override 1710 public void onSnapBackCompleted(View v) { 1711 // Re-enable clipping with the stack 1712 TaskView tv = (TaskView) v; 1713 tv.setClipViewInStack(true); 1714 } 1715 1716 @Override 1717 public void onDragCancelled(View v) { 1718 // Disable HW layers 1719 mSv.decHwLayersRefCount("swipeCancelled"); 1720 } 1721} 1722