TaskStackView.java revision 2f2ca08baa072376b3dfd60506625496aa05903f
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, 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 mMinScroll = Math.min(stackHeight, maxScrollHeight) - stackHeight; 392 mMaxScroll = maxScrollHeight - stackHeight; 393 394 // Debug logging 395 if (Constants.DebugFlags.UI.MeasureAndLayout) { 396 Console.log(" [TaskStack|minScroll] " + mMinScroll); 397 Console.log(" [TaskStack|maxScroll] " + mMaxScroll); 398 } 399 400 if (boundScrollToNewMinMax) { 401 boundScroll(); 402 } 403 } 404 405 /** Enables the hw layers and increments the hw layer requirement ref count */ 406 void addHwLayersRefCount(String reason) { 407 Console.log(Constants.DebugFlags.UI.HwLayers, 408 "[TaskStackView|addHwLayersRefCount] refCount: " + 409 mHwLayersRefCount + "->" + (mHwLayersRefCount + 1) + " " + reason); 410 if (mHwLayersRefCount == 0) { 411 // Enable hw layers on each of the children 412 int childCount = getChildCount(); 413 for (int i = 0; i < childCount; i++) { 414 TaskView tv = (TaskView) getChildAt(i); 415 tv.enableHwLayers(); 416 } 417 } 418 mHwLayersRefCount++; 419 } 420 421 /** Decrements the hw layer requirement ref count and disables the hw layers when we don't 422 need them anymore. */ 423 void decHwLayersRefCount(String reason) { 424 Console.log(Constants.DebugFlags.UI.HwLayers, 425 "[TaskStackView|decHwLayersRefCount] refCount: " + 426 mHwLayersRefCount + "->" + (mHwLayersRefCount - 1) + " " + reason); 427 mHwLayersRefCount--; 428 if (mHwLayersRefCount == 0) { 429 // Disable hw layers on each of the children 430 int childCount = getChildCount(); 431 for (int i = 0; i < childCount; i++) { 432 TaskView tv = (TaskView) getChildAt(i); 433 tv.disableHwLayers(); 434 } 435 } else if (mHwLayersRefCount < 0) { 436 new Throwable("Invalid hw layers ref count").printStackTrace(); 437 Console.logError(getContext(), "Invalid HW layers ref count"); 438 } 439 } 440 441 @Override 442 public void computeScroll() { 443 if (mScroller.computeScrollOffset()) { 444 setStackScroll(mScroller.getCurrY()); 445 invalidate(); 446 447 // If we just finished scrolling, then disable the hw layers 448 if (mScroller.isFinished()) { 449 decHwLayersRefCount("finishedFlingScroll"); 450 } 451 } 452 } 453 454 @Override 455 public boolean onInterceptTouchEvent(MotionEvent ev) { 456 return mTouchHandler.onInterceptTouchEvent(ev); 457 } 458 459 @Override 460 public boolean onTouchEvent(MotionEvent ev) { 461 return mTouchHandler.onTouchEvent(ev); 462 } 463 464 @Override 465 public void dispatchDraw(Canvas canvas) { 466 Console.log(Constants.DebugFlags.UI.Draw, "[TaskStackView|dispatchDraw]", "", 467 Console.AnsiPurple); 468 synchronizeStackViewsWithModel(); 469 super.dispatchDraw(canvas); 470 } 471 472 @Override 473 protected boolean drawChild(Canvas canvas, View child, long drawingTime) { 474 if (Constants.DebugFlags.App.EnableTaskStackClipping) { 475 TaskView tv = (TaskView) child; 476 TaskView nextTv = null; 477 int curIndex = indexOfChild(tv); 478 if ((curIndex > -1) && (curIndex < (getChildCount() - 1))) { 479 // Clip against the next view (if we aren't animating its alpha) 480 nextTv = (TaskView) getChildAt(curIndex + 1); 481 if (nextTv.getAlpha() == 1f) { 482 Rect curRect = tv.getClippingRect(mTmpRect, false); 483 Rect nextRect = nextTv.getClippingRect(mTmpRect2, true); 484 RecentsConfiguration config = RecentsConfiguration.getInstance(); 485 // The hit rects are relative to the task view, which needs to be offset by the 486 // system bar height 487 curRect.offset(0, config.systemInsets.top); 488 nextRect.offset(0, config.systemInsets.top); 489 // Compute the clip region 490 Region clipRegion = new Region(); 491 clipRegion.op(curRect, Region.Op.UNION); 492 clipRegion.op(nextRect, Region.Op.DIFFERENCE); 493 // Clip the canvas 494 int saveCount = canvas.save(Canvas.CLIP_SAVE_FLAG); 495 canvas.clipRegion(clipRegion); 496 boolean invalidate = super.drawChild(canvas, child, drawingTime); 497 canvas.restoreToCount(saveCount); 498 return invalidate; 499 } 500 } 501 } 502 return super.drawChild(canvas, child, drawingTime); 503 } 504 505 /** Computes the stack and task rects */ 506 public void computeRects(int width, int height, int insetBottom) { 507 // Note: We let the stack view be the full height because we want the cards to go under the 508 // navigation bar if possible. However, the stack rects which we use to calculate 509 // max scroll, etc. need to take the nav bar into account 510 511 // Compute the stack rects 512 mRect.set(0, 0, width, height); 513 mStackRect.set(mRect); 514 mStackRect.bottom -= insetBottom; 515 516 int smallestDimension = Math.min(width, height); 517 int padding = (int) (Constants.Values.TaskStackView.StackPaddingPct * smallestDimension / 2f); 518 mStackRect.inset(padding, padding); 519 mStackRectSansPeek.set(mStackRect); 520 mStackRectSansPeek.top += Constants.Values.TaskStackView.StackPeekHeightPct * mStackRect.height(); 521 522 // Compute the task rect 523 int minHeight = (int) (mStackRect.height() - 524 (Constants.Values.TaskStackView.StackPeekHeightPct * mStackRect.height())); 525 int size = Math.min(minHeight, Math.min(mStackRect.width(), mStackRect.height())); 526 int centerX = mStackRect.centerX(); 527 mTaskRect.set(centerX - size / 2, mStackRectSansPeek.top, 528 centerX + size / 2, mStackRectSansPeek.top + size); 529 530 // Update the scroll bounds 531 updateMinMaxScroll(false); 532 } 533 534 @Override 535 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 536 int width = MeasureSpec.getSize(widthMeasureSpec); 537 int height = MeasureSpec.getSize(heightMeasureSpec); 538 Console.log(Constants.DebugFlags.UI.MeasureAndLayout, "[TaskStackView|measure]", 539 "width: " + width + " height: " + height + 540 " awaitingFirstLayout: " + mAwaitingFirstLayout, Console.AnsiGreen); 541 542 // Compute our stack/task rects 543 RecentsConfiguration config = RecentsConfiguration.getInstance(); 544 computeRects(width, height, config.systemInsets.bottom); 545 546 // Debug logging 547 if (Constants.DebugFlags.UI.MeasureAndLayout) { 548 Console.log(" [TaskStack|fullRect] " + mRect); 549 Console.log(" [TaskStack|stackRect] " + mStackRect); 550 Console.log(" [TaskStack|stackRectSansPeek] " + mStackRectSansPeek); 551 Console.log(" [TaskStack|taskRect] " + mTaskRect); 552 } 553 554 // If this is the first layout, then scroll to the front of the stack and synchronize the 555 // stack views immediately 556 if (mAwaitingFirstLayout) { 557 setStackScroll(mMaxScroll); 558 requestSynchronizeStackViewsWithModel(); 559 synchronizeStackViewsWithModel(); 560 561 // Animate the icon of the first task view 562 if (Constants.Values.TaskView.AnimateFrontTaskIconOnEnterRecents) { 563 TaskView tv = (TaskView) getChildAt(getChildCount() - 1); 564 if (tv != null) { 565 tv.animateOnEnterRecents(); 566 } 567 } 568 } 569 570 // Measure each of the children 571 int childCount = getChildCount(); 572 for (int i = 0; i < childCount; i++) { 573 TaskView t = (TaskView) getChildAt(i); 574 t.measure(MeasureSpec.makeMeasureSpec(mTaskRect.width(), MeasureSpec.EXACTLY), 575 MeasureSpec.makeMeasureSpec(mTaskRect.height(), MeasureSpec.EXACTLY)); 576 } 577 578 setMeasuredDimension(width, height); 579 } 580 581 @Override 582 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 583 Console.log(Constants.DebugFlags.UI.MeasureAndLayout, "[TaskStackView|layout]", 584 "" + new Rect(left, top, right, bottom), Console.AnsiGreen); 585 586 // Debug logging 587 if (Constants.DebugFlags.UI.MeasureAndLayout) { 588 Console.log(" [TaskStack|fullRect] " + mRect); 589 Console.log(" [TaskStack|stackRect] " + mStackRect); 590 Console.log(" [TaskStack|stackRectSansPeek] " + mStackRectSansPeek); 591 Console.log(" [TaskStack|taskRect] " + mTaskRect); 592 } 593 594 // Layout each of the children 595 int childCount = getChildCount(); 596 for (int i = 0; i < childCount; i++) { 597 TaskView t = (TaskView) getChildAt(i); 598 t.layout(mTaskRect.left, mStackRectSansPeek.top, 599 mTaskRect.right, mStackRectSansPeek.top + mTaskRect.height()); 600 } 601 602 if (mAwaitingFirstLayout) { 603 mAwaitingFirstLayout = false; 604 } 605 } 606 607 @Override 608 protected void onScrollChanged(int l, int t, int oldl, int oldt) { 609 super.onScrollChanged(l, t, oldl, oldt); 610 requestSynchronizeStackViewsWithModel(); 611 } 612 613 public boolean isTransformedTouchPointInView(float x, float y, View child) { 614 return isTransformedTouchPointInView(x, y, child, null); 615 } 616 617 /**** TaskStackCallbacks Implementation ****/ 618 619 @Override 620 public void onStackTaskAdded(TaskStack stack, Task t) { 621 requestSynchronizeStackViewsWithModel(); 622 } 623 624 @Override 625 public void onStackTaskRemoved(TaskStack stack, Task t) { 626 // Remove the view associated with this task, we can't rely on updateTransforms 627 // to work here because the task is no longer in the list 628 int childCount = getChildCount(); 629 for (int i = childCount - 1; i >= 0; i--) { 630 TaskView tv = (TaskView) getChildAt(i); 631 if (tv.getTask() == t) { 632 mViewPool.returnViewToPool(tv); 633 break; 634 } 635 } 636 637 updateMinMaxScroll(true); 638 int movement = (int) (Constants.Values.TaskStackView.StackOverlapPct * mTaskRect.height()); 639 requestSynchronizeStackViewsWithModel(Utilities.calculateTranslationAnimationDuration(movement)); 640 } 641 642 @Override 643 public void onStackFiltered(TaskStack newStack, final ArrayList<Task> curStack, 644 Task filteredTask) { 645 // NOTE: This code assumes that the current (unfiltered) stack is a superset of the new 646 // (filtered) stack 647 // XXX: Use HW Layers 648 649 // Stash the scroll and filtered task for us to restore to when we unfilter 650 mStashedScroll = getStackScroll(); 651 652 // Compute the transforms of the items in the current stack 653 final ArrayList<TaskViewTransform> curTaskTransforms = 654 getStackTransforms(curStack, mStashedScroll, null, true); 655 656 // Scroll the item to the top of the stack (sans-peek) rect so that we can see it better 657 updateMinMaxScroll(false); 658 float overlapHeight = Constants.Values.TaskStackView.StackOverlapPct * mTaskRect.height(); 659 setStackScrollRaw((int) (newStack.indexOfTask(filteredTask) * overlapHeight)); 660 boundScrollRaw(); 661 662 // Compute the transforms of the items in the new stack after setting the new scroll 663 final ArrayList<TaskViewTransform> taskTransforms = 664 getStackTransforms(mStack.getTasks(), getStackScroll(), null, true); 665 666 // Animate all of the existing views on screen either out of view (if they are not visible 667 // in the new stack) or to their final positions in the new stack 668 final ArrayList<TaskView> childrenToReturnToPool = new ArrayList<TaskView>(); 669 final ArrayList<Task> tasks = mStack.getTasks(); 670 ArrayList<Animator> childViewAnims = new ArrayList<Animator>(); 671 int childCount = getChildCount(); 672 int movement = 0; 673 for (int i = 0; i < childCount; i++) { 674 TaskView tv = (TaskView) getChildAt(i); 675 Task task = tv.getTask(); 676 TaskViewTransform toTransform; 677 int taskIndex = tasks.indexOf(task); 678 679 boolean willBeInvisible = (taskIndex < 0) || !taskTransforms.get(taskIndex).visible; 680 if (willBeInvisible) { 681 // Compose a new transform that fades and slides the task out of view 682 TaskViewTransform fromTransform = curTaskTransforms.get(curStack.indexOf(task)); 683 toTransform = new TaskViewTransform(fromTransform); 684 tv.updateViewPropertiesToTaskTransform(null, fromTransform, 0); 685 tv.prepareTaskTransformForFilterTaskHidden(toTransform); 686 687 childrenToReturnToPool.add(tv); 688 } else { 689 toTransform = taskTransforms.get(taskIndex); 690 691 // Use the movement of the visible views to calculate the duration of the animation 692 movement = Math.max(movement, 693 Math.abs(toTransform.translationY - (int) tv.getTranslationY())); 694 } 695 childViewAnims.add(tv.getAnimatorToTaskTransform(toTransform)); 696 } 697 698 // Cancel the previous animation 699 if (mFilterChildrenAnimator != null) { 700 mFilterChildrenAnimator.cancel(); 701 mFilterChildrenAnimator.removeAllListeners(); 702 } 703 704 // Create a new animation for the existing children 705 final RecentsConfiguration config = RecentsConfiguration.getInstance(); 706 mFilterChildrenAnimator = new AnimatorSet(); 707 mFilterChildrenAnimator.setDuration( 708 Utilities.calculateTranslationAnimationDuration(movement, 709 config.filteringCurrentViewsMinAnimDuration)); 710 mFilterChildrenAnimator.setInterpolator(BakedBezierInterpolator.INSTANCE); 711 mFilterChildrenAnimator.addListener(new AnimatorListenerAdapter() { 712 boolean isCancelled; 713 714 @Override 715 public void onAnimationCancel(Animator animation) { 716 isCancelled = true; 717 } 718 719 @Override 720 public void onAnimationEnd(Animator animation) { 721 if (isCancelled) return; 722 723 // Return all the removed children to the view pool 724 for (TaskView tv : childrenToReturnToPool) { 725 mViewPool.returnViewToPool(tv); 726 } 727 728 // For views that are not already visible, animate them in 729 ArrayList<Animator> newViewsAnims = new ArrayList<Animator>(); 730 int taskCount = tasks.size(); 731 int movement = 0; 732 int offset = 0; 733 for (int i = 0; i < taskCount; i++) { 734 Task task = tasks.get(i); 735 TaskViewTransform toTransform = taskTransforms.get(i); 736 if (toTransform.visible) { 737 TaskViewTransform fromTransform = 738 curTaskTransforms.get(curStack.indexOf(task)); 739 TaskView tv = getChildViewForTask(task); 740 if (tv == null) { 741 tv = mViewPool.pickUpViewFromPool(task, task); 742 // Compose a new transform that fades and slides the new task in 743 fromTransform = new TaskViewTransform(toTransform); 744 tv.prepareTaskTransformForFilterTaskHidden(fromTransform); 745 tv.updateViewPropertiesToTaskTransform(null, fromTransform, 0); 746 747 Animator anim = tv.getAnimatorToTaskTransform(toTransform); 748 anim.setStartDelay(offset * 749 Constants.Values.TaskStackView.FilterStartDelay); 750 newViewsAnims.add(anim); 751 752 // Use the movement of the newly visible views to calculate the duration 753 // of the animation 754 movement = Math.max(movement, Math.abs(toTransform.translationY - 755 fromTransform.translationY)); 756 offset++; 757 } 758 } 759 760 // Animate the new views in 761 mFilterChildrenAnimator = new AnimatorSet(); 762 mFilterChildrenAnimator.setDuration( 763 Utilities.calculateTranslationAnimationDuration(movement, 764 config.filteringNewViewsMinAnimDuration)); 765 mFilterChildrenAnimator.setInterpolator(BakedBezierInterpolator.INSTANCE); 766 mFilterChildrenAnimator.playTogether(newViewsAnims); 767 mFilterChildrenAnimator.start(); 768 } 769 invalidate(); 770 } 771 }); 772 mFilterChildrenAnimator.playTogether(childViewAnims); 773 mFilterChildrenAnimator.start(); 774 } 775 776 @Override 777 public void onStackUnfiltered(TaskStack newStack, final ArrayList<Task> curStack) { 778 // Compute the transforms of the items in the current stack 779 final int curScroll = getStackScroll(); 780 final ArrayList<TaskViewTransform> curTaskTransforms = 781 getStackTransforms(curStack, curScroll, null, true); 782 783 // Restore the stashed scroll 784 updateMinMaxScroll(false); 785 setStackScrollRaw(mStashedScroll); 786 boundScrollRaw(); 787 788 // Compute the transforms of the items in the new stack after restoring the stashed scroll 789 final ArrayList<TaskViewTransform> taskTransforms = 790 getStackTransforms(mStack.getTasks(), getStackScroll(), null, true); 791 792 // Animate all of the existing views out of view (if they are not in the visible range in 793 // the new stack) or to their final positions in the new stack 794 final ArrayList<TaskView> childrenToRemove = new ArrayList<TaskView>(); 795 final ArrayList<Task> tasks = mStack.getTasks(); 796 ArrayList<Animator> childViewAnims = new ArrayList<Animator>(); 797 int childCount = getChildCount(); 798 int movement = 0; 799 for (int i = 0; i < childCount; i++) { 800 TaskView tv = (TaskView) getChildAt(i); 801 Task task = tv.getTask(); 802 int taskIndex = tasks.indexOf(task); 803 TaskViewTransform toTransform; 804 805 // If the view is no longer visible, then we should just animate it out 806 boolean willBeInvisible = taskIndex < 0 || !taskTransforms.get(taskIndex).visible; 807 if (willBeInvisible) { 808 toTransform = new TaskViewTransform(taskTransforms.get(taskIndex)); 809 tv.prepareTaskTransformForFilterTaskVisible(toTransform); 810 childrenToRemove.add(tv); 811 } else { 812 toTransform = taskTransforms.get(taskIndex); 813 // Use the movement of the visible views to calculate the duration of the animation 814 movement = Math.max(movement, Math.abs(toTransform.translationY - 815 (int) tv.getTranslationY())); 816 } 817 818 Animator anim = tv.getAnimatorToTaskTransform(toTransform); 819 childViewAnims.add(anim); 820 } 821 822 // Cancel the previous animation 823 if (mFilterChildrenAnimator != null) { 824 mFilterChildrenAnimator.cancel(); 825 mFilterChildrenAnimator.removeAllListeners(); 826 } 827 828 // Create a new animation for the existing children 829 final RecentsConfiguration config = RecentsConfiguration.getInstance(); 830 mFilterChildrenAnimator = new AnimatorSet(); 831 mFilterChildrenAnimator.setDuration( 832 Utilities.calculateTranslationAnimationDuration(movement, 833 config.filteringCurrentViewsMinAnimDuration)); 834 mFilterChildrenAnimator.setInterpolator(BakedBezierInterpolator.INSTANCE); 835 mFilterChildrenAnimator.addListener(new AnimatorListenerAdapter() { 836 boolean isCancelled; 837 838 @Override 839 public void onAnimationCancel(Animator animation) { 840 isCancelled = true; 841 } 842 843 @Override 844 public void onAnimationEnd(Animator animation) { 845 if (isCancelled) return; 846 847 // Return all the removed children to the view pool 848 for (TaskView tv : childrenToRemove) { 849 mViewPool.returnViewToPool(tv); 850 } 851 852 // Increment the hw layers ref count 853 addHwLayersRefCount("unfilteredNewViews"); 854 855 // For views that are not already visible, animate them in 856 ArrayList<Animator> newViewAnims = new ArrayList<Animator>(); 857 int taskCount = tasks.size(); 858 int movement = 0; 859 int offset = 0; 860 for (int i = 0; i < taskCount; i++) { 861 Task task = tasks.get(i); 862 TaskViewTransform toTransform = taskTransforms.get(i); 863 if (toTransform.visible) { 864 TaskView tv = getChildViewForTask(task); 865 if (tv == null) { 866 // For views that are not already visible, animate them in 867 tv = mViewPool.pickUpViewFromPool(task, task); 868 869 // Compose a new transform to fade and slide the new task in 870 TaskViewTransform fromTransform = new TaskViewTransform(toTransform); 871 tv.prepareTaskTransformForFilterTaskHidden(fromTransform); 872 tv.updateViewPropertiesToTaskTransform(null, fromTransform, 0); 873 874 Animator anim = tv.getAnimatorToTaskTransform(toTransform); 875 anim.setStartDelay(offset * 876 Constants.Values.TaskStackView.FilterStartDelay); 877 newViewAnims.add(anim); 878 // Use the movement of the newly visible views to calculate the duration 879 // of the animation 880 movement = Math.max(movement, 881 Math.abs(toTransform.translationY - fromTransform.translationY)); 882 offset++; 883 } 884 } 885 } 886 887 // Run the animation 888 mFilterChildrenAnimator = new AnimatorSet(); 889 mFilterChildrenAnimator.setDuration( 890 Utilities.calculateTranslationAnimationDuration(movement, 891 config.filteringNewViewsMinAnimDuration)); 892 mFilterChildrenAnimator.playTogether(newViewAnims); 893 mFilterChildrenAnimator.addListener(new AnimatorListenerAdapter() { 894 @Override 895 public void onAnimationEnd(Animator animation) { 896 // Decrement the hw layers ref count 897 decHwLayersRefCount("unfilteredNewViews"); 898 } 899 }); 900 mFilterChildrenAnimator.start(); 901 invalidate(); 902 } 903 }); 904 mFilterChildrenAnimator.playTogether(childViewAnims); 905 mFilterChildrenAnimator.start(); 906 907 // Clear the saved vars 908 mStashedScroll = 0; 909 } 910 911 /**** ViewPoolConsumer Implementation ****/ 912 913 @Override 914 public TaskView createView(Context context) { 915 Console.log(Constants.DebugFlags.ViewPool.PoolCallbacks, "[TaskStackView|createPoolView]"); 916 return (TaskView) mInflater.inflate(R.layout.recents_task_view, this, false); 917 } 918 919 @Override 920 public void prepareViewToEnterPool(TaskView tv) { 921 Task task = tv.getTask(); 922 tv.resetViewProperties(); 923 Console.log(Constants.DebugFlags.ViewPool.PoolCallbacks, "[TaskStackView|returnToPool]", 924 tv.getTask() + " tv: " + tv); 925 926 // Report that this tasks's data is no longer being used 927 RecentsTaskLoader loader = RecentsTaskLoader.getInstance(); 928 loader.unloadTaskData(task); 929 930 // Detach the view from the hierarchy 931 detachViewFromParent(tv); 932 933 // Disable hw layers on this view 934 tv.disableHwLayers(); 935 } 936 937 @Override 938 public void prepareViewToLeavePool(TaskView tv, Task prepareData, boolean isNewView) { 939 Console.log(Constants.DebugFlags.ViewPool.PoolCallbacks, "[TaskStackView|leavePool]", 940 "isNewView: " + isNewView); 941 942 // Setup and attach the view to the window 943 Task task = prepareData; 944 // We try and rebind the task (this MUST be done before the task filled) 945 tv.onTaskBound(task); 946 // Request that this tasks's data be filled 947 RecentsTaskLoader loader = RecentsTaskLoader.getInstance(); 948 loader.loadTaskData(task); 949 950 // Find the index where this task should be placed in the children 951 int insertIndex = -1; 952 int childCount = getChildCount(); 953 for (int i = 0; i < childCount; i++) { 954 Task tvTask = ((TaskView) getChildAt(i)).getTask(); 955 if (mStack.containsTask(task) && (mStack.indexOfTask(task) < mStack.indexOfTask(tvTask))) { 956 insertIndex = i; 957 break; 958 } 959 } 960 961 // Add/attach the view to the hierarchy 962 Console.log(Constants.DebugFlags.ViewPool.PoolCallbacks, " [TaskStackView|insertIndex]", 963 "" + insertIndex); 964 if (isNewView) { 965 addView(tv, insertIndex); 966 967 // Set the callbacks and listeners for this new view 968 tv.setOnClickListener(this); 969 tv.setCallbacks(this); 970 } else { 971 attachViewToParent(tv, insertIndex, tv.getLayoutParams()); 972 } 973 974 // Enable hw layers on this view if hw layers are enabled on the stack 975 if (mHwLayersRefCount > 0) { 976 tv.enableHwLayers(); 977 } 978 } 979 980 @Override 981 public boolean hasPreferredData(TaskView tv, Task preferredData) { 982 return (tv.getTask() == preferredData); 983 } 984 985 /**** TaskViewCallbacks Implementation ****/ 986 987 @Override 988 public void onTaskIconClicked(TaskView tv) { 989 Console.log(Constants.DebugFlags.UI.ClickEvents, "[TaskStack|Clicked|Icon]", 990 tv.getTask() + " is currently filtered: " + mStack.hasFilteredTasks(), 991 Console.AnsiCyan); 992 if (Constants.DebugFlags.App.EnableTaskFiltering) { 993 if (mStack.hasFilteredTasks()) { 994 mStack.unfilterTasks(); 995 } else { 996 mStack.filterTasks(tv.getTask()); 997 } 998 } else { 999 Console.logError(getContext(), "Task Filtering TBD"); 1000 } 1001 } 1002 1003 /**** View.OnClickListener Implementation ****/ 1004 1005 @Override 1006 public void onClick(View v) { 1007 TaskView tv = (TaskView) v; 1008 Task task = tv.getTask(); 1009 Console.log(Constants.DebugFlags.UI.ClickEvents, "[TaskStack|Clicked|Thumbnail]", 1010 task + " cb: " + mCb); 1011 1012 if (mCb != null) { 1013 mCb.onTaskLaunched(this, tv, mStack, task); 1014 } 1015 } 1016} 1017 1018/* Handles touch events */ 1019class TaskStackViewTouchHandler implements SwipeHelper.Callback { 1020 static int INACTIVE_POINTER_ID = -1; 1021 1022 TaskStackView mSv; 1023 VelocityTracker mVelocityTracker; 1024 1025 boolean mIsScrolling; 1026 1027 int mInitialMotionX, mInitialMotionY; 1028 int mLastMotionX, mLastMotionY; 1029 int mActivePointerId = INACTIVE_POINTER_ID; 1030 TaskView mActiveTaskView = null; 1031 1032 int mTotalScrollMotion; 1033 int mMinimumVelocity; 1034 int mMaximumVelocity; 1035 // The scroll touch slop is used to calculate when we start scrolling 1036 int mScrollTouchSlop; 1037 // The page touch slop is used to calculate when we start swiping 1038 float mPagingTouchSlop; 1039 1040 SwipeHelper mSwipeHelper; 1041 boolean mInterceptedBySwipeHelper; 1042 1043 public TaskStackViewTouchHandler(Context context, TaskStackView sv) { 1044 ViewConfiguration configuration = ViewConfiguration.get(context); 1045 mMinimumVelocity = configuration.getScaledMinimumFlingVelocity(); 1046 mMaximumVelocity = configuration.getScaledMaximumFlingVelocity(); 1047 mScrollTouchSlop = configuration.getScaledTouchSlop(); 1048 mPagingTouchSlop = configuration.getScaledPagingTouchSlop(); 1049 mSv = sv; 1050 1051 1052 float densityScale = context.getResources().getDisplayMetrics().density; 1053 mSwipeHelper = new SwipeHelper(SwipeHelper.X, this, densityScale, mPagingTouchSlop); 1054 mSwipeHelper.setMinAlpha(1f); 1055 } 1056 1057 /** Velocity tracker helpers */ 1058 void initOrResetVelocityTracker() { 1059 if (mVelocityTracker == null) { 1060 mVelocityTracker = VelocityTracker.obtain(); 1061 } else { 1062 mVelocityTracker.clear(); 1063 } 1064 } 1065 void initVelocityTrackerIfNotExists() { 1066 if (mVelocityTracker == null) { 1067 mVelocityTracker = VelocityTracker.obtain(); 1068 } 1069 } 1070 void recycleVelocityTracker() { 1071 if (mVelocityTracker != null) { 1072 mVelocityTracker.recycle(); 1073 mVelocityTracker = null; 1074 } 1075 } 1076 1077 /** Returns the view at the specified coordinates */ 1078 TaskView findViewAtPoint(int x, int y) { 1079 int childCount = mSv.getChildCount(); 1080 for (int i = childCount - 1; i >= 0; i--) { 1081 TaskView tv = (TaskView) mSv.getChildAt(i); 1082 if (tv.getVisibility() == View.VISIBLE) { 1083 if (mSv.isTransformedTouchPointInView(x, y, tv)) { 1084 return tv; 1085 } 1086 } 1087 } 1088 return null; 1089 } 1090 1091 /** Touch preprocessing for handling below */ 1092 public boolean onInterceptTouchEvent(MotionEvent ev) { 1093 Console.log(Constants.DebugFlags.UI.TouchEvents, 1094 "[TaskStackViewTouchHandler|interceptTouchEvent]", 1095 Console.motionEventActionToString(ev.getAction()), Console.AnsiBlue); 1096 1097 // Return early if we have no children 1098 boolean hasChildren = (mSv.getChildCount() > 0); 1099 if (!hasChildren) { 1100 return false; 1101 } 1102 1103 // Pass through to swipe helper if we are swiping 1104 mInterceptedBySwipeHelper = mSwipeHelper.onInterceptTouchEvent(ev); 1105 if (mInterceptedBySwipeHelper) { 1106 return true; 1107 } 1108 1109 boolean wasScrolling = !mSv.mScroller.isFinished() || 1110 (mSv.mScrollAnimator != null && mSv.mScrollAnimator.isRunning()); 1111 int action = ev.getAction(); 1112 switch (action & MotionEvent.ACTION_MASK) { 1113 case MotionEvent.ACTION_DOWN: { 1114 // Save the touch down info 1115 mInitialMotionX = mLastMotionX = (int) ev.getX(); 1116 mInitialMotionY = mLastMotionY = (int) ev.getY(); 1117 mActivePointerId = ev.getPointerId(0); 1118 mActiveTaskView = findViewAtPoint(mLastMotionX, mLastMotionY); 1119 // Stop the current scroll if it is still flinging 1120 mSv.abortScroller(); 1121 mSv.abortBoundScrollAnimation(); 1122 // Initialize the velocity tracker 1123 initOrResetVelocityTracker(); 1124 mVelocityTracker.addMovement(ev); 1125 // Check if the scroller is finished yet 1126 mIsScrolling = !mSv.mScroller.isFinished(); 1127 break; 1128 } 1129 case MotionEvent.ACTION_MOVE: { 1130 if (mActivePointerId == INACTIVE_POINTER_ID) break; 1131 1132 int activePointerIndex = ev.findPointerIndex(mActivePointerId); 1133 int y = (int) ev.getY(activePointerIndex); 1134 int x = (int) ev.getX(activePointerIndex); 1135 if (Math.abs(y - mInitialMotionY) > mScrollTouchSlop) { 1136 // Save the touch move info 1137 mIsScrolling = true; 1138 // Initialize the velocity tracker if necessary 1139 initVelocityTrackerIfNotExists(); 1140 mVelocityTracker.addMovement(ev); 1141 // Disallow parents from intercepting touch events 1142 final ViewParent parent = mSv.getParent(); 1143 if (parent != null) { 1144 parent.requestDisallowInterceptTouchEvent(true); 1145 } 1146 // Enable HW layers 1147 mSv.addHwLayersRefCount("stackScroll"); 1148 } 1149 1150 mLastMotionX = x; 1151 mLastMotionY = y; 1152 break; 1153 } 1154 case MotionEvent.ACTION_CANCEL: 1155 case MotionEvent.ACTION_UP: { 1156 // Animate the scroll back if we've cancelled 1157 mSv.animateBoundScroll(); 1158 // Disable HW layers 1159 if (mIsScrolling) { 1160 mSv.decHwLayersRefCount("stackScroll"); 1161 } 1162 // Reset the drag state and the velocity tracker 1163 mIsScrolling = false; 1164 mActivePointerId = INACTIVE_POINTER_ID; 1165 mActiveTaskView = null; 1166 mTotalScrollMotion = 0; 1167 recycleVelocityTracker(); 1168 break; 1169 } 1170 } 1171 1172 return wasScrolling || mIsScrolling; 1173 } 1174 1175 /** Handles touch events once we have intercepted them */ 1176 public boolean onTouchEvent(MotionEvent ev) { 1177 Console.log(Constants.DebugFlags.UI.TouchEvents, 1178 "[TaskStackViewTouchHandler|touchEvent]", 1179 Console.motionEventActionToString(ev.getAction()), Console.AnsiBlue); 1180 1181 // Short circuit if we have no children 1182 boolean hasChildren = (mSv.getChildCount() > 0); 1183 if (!hasChildren) { 1184 return false; 1185 } 1186 1187 // Pass through to swipe helper if we are swiping 1188 if (mInterceptedBySwipeHelper && mSwipeHelper.onTouchEvent(ev)) { 1189 return true; 1190 } 1191 1192 // Update the velocity tracker 1193 initVelocityTrackerIfNotExists(); 1194 mVelocityTracker.addMovement(ev); 1195 1196 int action = ev.getAction(); 1197 switch (action & MotionEvent.ACTION_MASK) { 1198 case MotionEvent.ACTION_DOWN: { 1199 // Save the touch down info 1200 mInitialMotionX = mLastMotionX = (int) ev.getX(); 1201 mInitialMotionY = mLastMotionY = (int) ev.getY(); 1202 mActivePointerId = ev.getPointerId(0); 1203 mActiveTaskView = findViewAtPoint(mLastMotionX, mLastMotionY); 1204 // Stop the current scroll if it is still flinging 1205 mSv.abortScroller(); 1206 mSv.abortBoundScrollAnimation(); 1207 // Initialize the velocity tracker 1208 initOrResetVelocityTracker(); 1209 mVelocityTracker.addMovement(ev); 1210 // Disallow parents from intercepting touch events 1211 final ViewParent parent = mSv.getParent(); 1212 if (parent != null) { 1213 parent.requestDisallowInterceptTouchEvent(true); 1214 } 1215 break; 1216 } 1217 case MotionEvent.ACTION_POINTER_DOWN: { 1218 final int index = ev.getActionIndex(); 1219 mActivePointerId = ev.getPointerId(index); 1220 mLastMotionX = (int) ev.getX(index); 1221 mLastMotionY = (int) ev.getY(index); 1222 break; 1223 } 1224 case MotionEvent.ACTION_MOVE: { 1225 if (mActivePointerId == INACTIVE_POINTER_ID) break; 1226 1227 int activePointerIndex = ev.findPointerIndex(mActivePointerId); 1228 int x = (int) ev.getX(activePointerIndex); 1229 int y = (int) ev.getY(activePointerIndex); 1230 int deltaY = mLastMotionY - y; 1231 if (!mIsScrolling) { 1232 if (Math.abs(y - mInitialMotionY) > mScrollTouchSlop) { 1233 mIsScrolling = true; 1234 // Initialize the velocity tracker 1235 initOrResetVelocityTracker(); 1236 mVelocityTracker.addMovement(ev); 1237 // Disallow parents from intercepting touch events 1238 final ViewParent parent = mSv.getParent(); 1239 if (parent != null) { 1240 parent.requestDisallowInterceptTouchEvent(true); 1241 } 1242 // Enable HW layers 1243 mSv.addHwLayersRefCount("stackScroll"); 1244 } 1245 } 1246 if (mIsScrolling) { 1247 mSv.setStackScroll(mSv.getStackScroll() + deltaY); 1248 if (mSv.isScrollOutOfBounds()) { 1249 mVelocityTracker.clear(); 1250 } 1251 } 1252 mLastMotionX = x; 1253 mLastMotionY = y; 1254 mTotalScrollMotion += Math.abs(deltaY); 1255 break; 1256 } 1257 case MotionEvent.ACTION_UP: { 1258 final VelocityTracker velocityTracker = mVelocityTracker; 1259 velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); 1260 int velocity = (int) velocityTracker.getYVelocity(mActivePointerId); 1261 1262 if (mIsScrolling && (Math.abs(velocity) > mMinimumVelocity)) { 1263 // Enable HW layers on the stack 1264 mSv.addHwLayersRefCount("flingScroll"); 1265 int overscrollRange = (int) (Math.min(1f, 1266 Math.abs((float) velocity / mMaximumVelocity)) * 1267 Constants.Values.TaskStackView.TaskStackOverscrollRange); 1268 1269 Console.log(Constants.DebugFlags.UI.TouchEvents, 1270 "[TaskStackViewTouchHandler|fling]", 1271 "scroll: " + mSv.getStackScroll() + " velocity: " + velocity + 1272 " maxVelocity: " + mMaximumVelocity + 1273 " overscrollRange: " + overscrollRange, 1274 Console.AnsiGreen); 1275 1276 // Fling scroll 1277 mSv.mScroller.fling(0, mSv.getStackScroll(), 1278 0, -velocity, 1279 0, 0, 1280 mSv.mMinScroll, mSv.mMaxScroll, 1281 0, overscrollRange); 1282 // Invalidate to kick off computeScroll 1283 mSv.invalidate(); 1284 } else if (mSv.isScrollOutOfBounds()) { 1285 // Animate the scroll back into bounds 1286 // XXX: Make this animation a function of the velocity OR distance 1287 mSv.animateBoundScroll(); 1288 } 1289 1290 if (mIsScrolling) { 1291 // Disable HW layers 1292 mSv.decHwLayersRefCount("stackScroll"); 1293 } 1294 mActivePointerId = INACTIVE_POINTER_ID; 1295 mIsScrolling = false; 1296 mTotalScrollMotion = 0; 1297 recycleVelocityTracker(); 1298 break; 1299 } 1300 case MotionEvent.ACTION_POINTER_UP: { 1301 int pointerIndex = ev.getActionIndex(); 1302 int pointerId = ev.getPointerId(pointerIndex); 1303 if (pointerId == mActivePointerId) { 1304 // Select a new active pointer id and reset the motion state 1305 final int newPointerIndex = (pointerIndex == 0) ? 1 : 0; 1306 mActivePointerId = ev.getPointerId(newPointerIndex); 1307 mLastMotionX = (int) ev.getX(newPointerIndex); 1308 mLastMotionY = (int) ev.getY(newPointerIndex); 1309 mVelocityTracker.clear(); 1310 } 1311 break; 1312 } 1313 case MotionEvent.ACTION_CANCEL: { 1314 if (mIsScrolling) { 1315 // Disable HW layers 1316 mSv.decHwLayersRefCount("stackScroll"); 1317 } 1318 if (mSv.isScrollOutOfBounds()) { 1319 // Animate the scroll back into bounds 1320 // XXX: Make this animation a function of the velocity OR distance 1321 mSv.animateBoundScroll(); 1322 } 1323 mActivePointerId = INACTIVE_POINTER_ID; 1324 mIsScrolling = false; 1325 mTotalScrollMotion = 0; 1326 recycleVelocityTracker(); 1327 break; 1328 } 1329 } 1330 return true; 1331 } 1332 1333 /**** SwipeHelper Implementation ****/ 1334 1335 @Override 1336 public View getChildAtPosition(MotionEvent ev) { 1337 return findViewAtPoint((int) ev.getX(), (int) ev.getY()); 1338 } 1339 1340 @Override 1341 public boolean canChildBeDismissed(View v) { 1342 return true; 1343 } 1344 1345 @Override 1346 public void onBeginDrag(View v) { 1347 // Enable HW layers 1348 mSv.addHwLayersRefCount("swipeBegin"); 1349 // Disallow parents from intercepting touch events 1350 final ViewParent parent = mSv.getParent(); 1351 if (parent != null) { 1352 parent.requestDisallowInterceptTouchEvent(true); 1353 } 1354 } 1355 1356 @Override 1357 public void onChildDismissed(View v) { 1358 TaskView tv = (TaskView) v; 1359 Task task = tv.getTask(); 1360 Activity activity = (Activity) mSv.getContext(); 1361 1362 // Remove the task from the view 1363 mSv.mStack.removeTask(task); 1364 1365 // Remove any stored data from the loader 1366 RecentsTaskLoader loader = RecentsTaskLoader.getInstance(); 1367 loader.deleteTaskData(task); 1368 1369 // Remove the task from activity manager 1370 RecentsTaskLoader.getInstance().getSystemServicesProxy().removeTask(tv.getTask().key.id); 1371 1372 // If there are no remaining tasks, then either unfilter the current stack, or just close 1373 // the activity if there are no filtered stacks 1374 if (mSv.mStack.getTaskCount() == 0) { 1375 boolean shouldFinishActivity = true; 1376 if (mSv.mStack.hasFilteredTasks()) { 1377 mSv.mStack.unfilterTasks(); 1378 shouldFinishActivity = (mSv.mStack.getTaskCount() == 0); 1379 } 1380 if (shouldFinishActivity) { 1381 activity.finish(); 1382 } 1383 } 1384 1385 // Disable HW layers 1386 mSv.decHwLayersRefCount("swipeComplete"); 1387 } 1388 1389 @Override 1390 public void onSnapBackCompleted(View v) { 1391 // Do Nothing 1392 } 1393 1394 @Override 1395 public void onDragCancelled(View v) { 1396 // Disable HW layers 1397 mSv.decHwLayersRefCount("swipeCancelled"); 1398 } 1399} 1400