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