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