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