TaskStackView.java revision 814086db674d8eb298541b7e601e29c5c68e2074
1/* 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.systemui.recents.views; 18 19import android.animation.Animator; 20import android.animation.AnimatorListenerAdapter; 21import android.animation.ObjectAnimator; 22import android.animation.ValueAnimator; 23import android.app.Activity; 24import android.content.Context; 25import android.graphics.Canvas; 26import android.graphics.Rect; 27import android.graphics.Region; 28import android.util.Pair; 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; 48import java.util.HashMap; 49 50 51/* The visual representation of a task stack view */ 52public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCallbacks, 53 TaskView.TaskViewCallbacks, ViewPool.ViewPoolConsumer<TaskView, Task>, 54 View.OnClickListener, View.OnLongClickListener { 55 56 /** The TaskView callbacks */ 57 interface TaskStackViewCallbacks { 58 public void onTaskLaunched(TaskStackView stackView, TaskView tv, TaskStack stack, Task t); 59 public void onTaskAppInfoLaunched(Task t); 60 } 61 62 TaskStack mStack; 63 TaskStackViewTouchHandler mTouchHandler; 64 TaskStackViewCallbacks mCb; 65 ViewPool<TaskView, Task> mViewPool; 66 67 // The various rects that define the stack view 68 Rect mRect = new Rect(); 69 Rect mStackRect = new Rect(); 70 Rect mStackRectSansPeek = new Rect(); 71 Rect mTaskRect = new Rect(); 72 73 // The virtual stack scroll that we use for the card layout 74 int mStackScroll; 75 int mMinScroll; 76 int mMaxScroll; 77 int mStashedScroll; 78 int mLastInfoPaneStackScroll; 79 OverScroller mScroller; 80 ObjectAnimator mScrollAnimator; 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.Log.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.Log.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.Log.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 // Close any open info panes if the user has scrolled away from them 287 boolean isAnimatingScroll = (mScrollAnimator != null && mScrollAnimator.isRunning()); 288 if (mLastInfoPaneStackScroll > -1 && !isAnimatingScroll) { 289 RecentsConfiguration config = RecentsConfiguration.getInstance(); 290 if (Math.abs(mStackScroll - mLastInfoPaneStackScroll) > 291 config.taskStackScrollDismissInfoPaneDistance) { 292 // Close any open info panes 293 closeOpenInfoPanes(); 294 } 295 } 296 } 297 /** Sets the current stack scroll without synchronizing the stack view with the model */ 298 public void setStackScrollRaw(int value) { 299 mStackScroll = value; 300 } 301 302 /** Gets the current stack scroll */ 303 public int getStackScroll() { 304 return mStackScroll; 305 } 306 307 /** Animates the stack scroll into bounds */ 308 ObjectAnimator animateBoundScroll() { 309 int curScroll = getStackScroll(); 310 int newScroll = Math.max(mMinScroll, Math.min(mMaxScroll, curScroll)); 311 if (newScroll != curScroll) { 312 // Enable hw layers on the stack 313 addHwLayersRefCount("animateBoundScroll"); 314 315 // Start a new scroll animation 316 animateScroll(curScroll, newScroll, new Runnable() { 317 @Override 318 public void run() { 319 // Disable hw layers on the stack 320 decHwLayersRefCount("animateBoundScroll"); 321 } 322 }); 323 } 324 return mScrollAnimator; 325 } 326 327 /** Animates the stack scroll */ 328 void animateScroll(int curScroll, int newScroll, final Runnable postRunnable) { 329 // Abort any current animations 330 abortScroller(); 331 abortBoundScrollAnimation(); 332 333 mScrollAnimator = ObjectAnimator.ofInt(this, "stackScroll", curScroll, newScroll); 334 mScrollAnimator.setDuration(Utilities.calculateTranslationAnimationDuration(newScroll - 335 curScroll, 250)); 336 mScrollAnimator.setInterpolator(BakedBezierInterpolator.INSTANCE); 337 mScrollAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 338 @Override 339 public void onAnimationUpdate(ValueAnimator animation) { 340 setStackScroll((Integer) animation.getAnimatedValue()); 341 } 342 }); 343 mScrollAnimator.addListener(new AnimatorListenerAdapter() { 344 @Override 345 public void onAnimationEnd(Animator animation) { 346 if (postRunnable != null) { 347 postRunnable.run(); 348 } 349 mScrollAnimator.removeAllListeners(); 350 } 351 }); 352 mScrollAnimator.start(); 353 } 354 355 /** Aborts any current stack scrolls */ 356 void abortBoundScrollAnimation() { 357 if (mScrollAnimator != null) { 358 mScrollAnimator.cancel(); 359 } 360 } 361 362 /** Aborts the scroller and any current fling */ 363 void abortScroller() { 364 if (!mScroller.isFinished()) { 365 // Abort the scroller 366 mScroller.abortAnimation(); 367 // And disable hw layers on the stack 368 decHwLayersRefCount("flingScroll"); 369 } 370 } 371 372 /** Bounds the current scroll if necessary */ 373 public boolean boundScroll() { 374 int curScroll = getStackScroll(); 375 int newScroll = Math.max(mMinScroll, Math.min(mMaxScroll, curScroll)); 376 if (newScroll != curScroll) { 377 setStackScroll(newScroll); 378 return true; 379 } 380 return false; 381 } 382 383 /** 384 * Bounds the current scroll if necessary, but does not synchronize the stack view with the 385 * model. 386 */ 387 public boolean boundScrollRaw() { 388 int curScroll = getStackScroll(); 389 int newScroll = Math.max(mMinScroll, Math.min(mMaxScroll, curScroll)); 390 if (newScroll != curScroll) { 391 setStackScrollRaw(newScroll); 392 return true; 393 } 394 return false; 395 } 396 397 398 /** Returns the amount that the scroll is out of bounds */ 399 int getScrollAmountOutOfBounds(int scroll) { 400 if (scroll < mMinScroll) { 401 return mMinScroll - scroll; 402 } else if (scroll > mMaxScroll) { 403 return scroll - mMaxScroll; 404 } 405 return 0; 406 } 407 408 /** Returns whether the specified scroll is out of bounds */ 409 boolean isScrollOutOfBounds() { 410 return getScrollAmountOutOfBounds(getStackScroll()) != 0; 411 } 412 413 /** Updates the min and max virtual scroll bounds */ 414 void updateMinMaxScroll(boolean boundScrollToNewMinMax) { 415 // Compute the min and max scroll values 416 int numTasks = Math.max(1, mStack.getTaskCount()); 417 int taskHeight = mTaskRect.height(); 418 int stackHeight = mStackRectSansPeek.height(); 419 int maxScrollHeight = taskHeight + (int) ((numTasks - 1) * 420 Constants.Values.TaskStackView.StackOverlapPct * taskHeight); 421 422 if (numTasks <= 1) { 423 // If there is only one task, then center the task in the stack rect (sans peek) 424 mMinScroll = mMaxScroll = -(stackHeight - taskHeight) / 2; 425 } else { 426 mMinScroll = Math.min(stackHeight, maxScrollHeight) - stackHeight; 427 mMaxScroll = maxScrollHeight - stackHeight; 428 } 429 430 // Debug logging 431 if (Constants.Log.UI.MeasureAndLayout) { 432 Console.log(" [TaskStack|minScroll] " + mMinScroll); 433 Console.log(" [TaskStack|maxScroll] " + mMaxScroll); 434 } 435 436 if (boundScrollToNewMinMax) { 437 boundScroll(); 438 } 439 } 440 441 /** Closes any open info panes. */ 442 boolean closeOpenInfoPanes() { 443 if (!Constants.DebugFlags.App.EnableInfoPane) return false; 444 445 int childCount = getChildCount(); 446 for (int i = 0; i < childCount; i++) { 447 TaskView tv = (TaskView) getChildAt(i); 448 if (tv.isInfoPaneVisible()) { 449 tv.hideInfoPane(); 450 return true; 451 } 452 } 453 return false; 454 } 455 456 /** Enables the hw layers and increments the hw layer requirement ref count */ 457 void addHwLayersRefCount(String reason) { 458 Console.log(Constants.Log.UI.HwLayers, 459 "[TaskStackView|addHwLayersRefCount] refCount: " + 460 mHwLayersRefCount + "->" + (mHwLayersRefCount + 1) + " " + reason); 461 if (mHwLayersRefCount == 0) { 462 // Enable hw layers on each of the children 463 int childCount = getChildCount(); 464 for (int i = 0; i < childCount; i++) { 465 TaskView tv = (TaskView) getChildAt(i); 466 tv.enableHwLayers(); 467 } 468 } 469 mHwLayersRefCount++; 470 } 471 472 /** Decrements the hw layer requirement ref count and disables the hw layers when we don't 473 need them anymore. */ 474 void decHwLayersRefCount(String reason) { 475 Console.log(Constants.Log.UI.HwLayers, 476 "[TaskStackView|decHwLayersRefCount] refCount: " + 477 mHwLayersRefCount + "->" + (mHwLayersRefCount - 1) + " " + reason); 478 mHwLayersRefCount--; 479 if (mHwLayersRefCount == 0) { 480 // Disable hw layers on each of the children 481 int childCount = getChildCount(); 482 for (int i = 0; i < childCount; i++) { 483 TaskView tv = (TaskView) getChildAt(i); 484 tv.disableHwLayers(); 485 } 486 } else if (mHwLayersRefCount < 0) { 487 new Throwable("Invalid hw layers ref count").printStackTrace(); 488 Console.logError(getContext(), "Invalid HW layers ref count"); 489 } 490 } 491 492 @Override 493 public void computeScroll() { 494 if (mScroller.computeScrollOffset()) { 495 setStackScroll(mScroller.getCurrY()); 496 invalidate(); 497 498 // If we just finished scrolling, then disable the hw layers 499 if (mScroller.isFinished()) { 500 decHwLayersRefCount("finishedFlingScroll"); 501 } 502 } 503 } 504 505 @Override 506 public boolean onInterceptTouchEvent(MotionEvent ev) { 507 return mTouchHandler.onInterceptTouchEvent(ev); 508 } 509 510 @Override 511 public boolean onTouchEvent(MotionEvent ev) { 512 return mTouchHandler.onTouchEvent(ev); 513 } 514 515 @Override 516 public void dispatchDraw(Canvas canvas) { 517 Console.log(Constants.Log.UI.Draw, "[TaskStackView|dispatchDraw]", "", 518 Console.AnsiPurple); 519 synchronizeStackViewsWithModel(); 520 super.dispatchDraw(canvas); 521 } 522 523 @Override 524 protected boolean drawChild(Canvas canvas, View child, long drawingTime) { 525 if (Constants.DebugFlags.App.EnableTaskStackClipping) { 526 TaskView tv = (TaskView) child; 527 TaskView nextTv = null; 528 int curIndex = indexOfChild(tv); 529 if ((curIndex > -1) && (curIndex < (getChildCount() - 1))) { 530 // Clip against the next view (if we aren't animating its alpha) 531 nextTv = (TaskView) getChildAt(curIndex + 1); 532 if (nextTv.getAlpha() == 1f) { 533 Rect curRect = tv.getClippingRect(mTmpRect); 534 Rect nextRect = nextTv.getClippingRect(mTmpRect2); 535 RecentsConfiguration config = RecentsConfiguration.getInstance(); 536 // The hit rects are relative to the task view, which needs to be offset by the 537 // system bar height 538 curRect.offset(0, config.systemInsets.top); 539 nextRect.offset(0, config.systemInsets.top); 540 // Compute the clip region 541 Region clipRegion = new Region(); 542 clipRegion.op(curRect, Region.Op.UNION); 543 clipRegion.op(nextRect, Region.Op.DIFFERENCE); 544 // Clip the canvas 545 int saveCount = canvas.save(Canvas.CLIP_SAVE_FLAG); 546 canvas.clipRegion(clipRegion); 547 boolean invalidate = super.drawChild(canvas, child, drawingTime); 548 canvas.restoreToCount(saveCount); 549 return invalidate; 550 } 551 } 552 } 553 return super.drawChild(canvas, child, drawingTime); 554 } 555 556 /** Computes the stack and task rects */ 557 public void computeRects(int width, int height, int insetLeft, int insetBottom) { 558 // Note: We let the stack view be the full height because we want the cards to go under the 559 // navigation bar if possible. However, the stack rects which we use to calculate 560 // max scroll, etc. need to take the nav bar into account 561 562 // Compute the stack rects 563 mRect.set(0, 0, width, height); 564 mStackRect.set(mRect); 565 mStackRect.left += insetLeft; 566 mStackRect.bottom -= insetBottom; 567 568 int smallestDimension = Math.min(width, height); 569 int padding = (int) (Constants.Values.TaskStackView.StackPaddingPct * smallestDimension / 2f); 570 if (Constants.DebugFlags.App.EnableSearchLayout) { 571 mStackRect.top += padding; 572 mStackRect.left += padding; 573 mStackRect.right -= padding; 574 mStackRect.bottom -= padding; 575 } else { 576 mStackRect.inset(padding, padding); 577 } 578 mStackRectSansPeek.set(mStackRect); 579 mStackRectSansPeek.top += Constants.Values.TaskStackView.StackPeekHeightPct * mStackRect.height(); 580 581 // Compute the task rect 582 int minHeight = (int) (mStackRect.height() - 583 (Constants.Values.TaskStackView.StackPeekHeightPct * mStackRect.height())); 584 int size = Math.min(minHeight, Math.min(mStackRect.width(), mStackRect.height())); 585 int left = mStackRect.left + (mStackRect.width() - size) / 2; 586 mTaskRect.set(left, mStackRectSansPeek.top, 587 left + size, mStackRectSansPeek.top + size); 588 589 // Update the scroll bounds 590 updateMinMaxScroll(false); 591 } 592 593 /** 594 * This is called with the size of the space not including the top or right insets, or the 595 * search bar height in portrait (but including the search bar width in landscape, since we want 596 * to draw under it. 597 */ 598 @Override 599 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 600 int width = MeasureSpec.getSize(widthMeasureSpec); 601 int height = MeasureSpec.getSize(heightMeasureSpec); 602 Console.log(Constants.Log.UI.MeasureAndLayout, "[TaskStackView|measure]", 603 "width: " + width + " height: " + height + 604 " awaitingFirstLayout: " + mAwaitingFirstLayout, Console.AnsiGreen); 605 606 // Compute our stack/task rects 607 RecentsConfiguration config = RecentsConfiguration.getInstance(); 608 Rect taskStackBounds = new Rect(); 609 config.getTaskStackBounds(width, height, taskStackBounds); 610 computeRects(width, height, taskStackBounds.left, config.systemInsets.bottom); 611 612 // Debug logging 613 if (Constants.Log.UI.MeasureAndLayout) { 614 Console.log(" [TaskStack|fullRect] " + mRect); 615 Console.log(" [TaskStack|stackRect] " + mStackRect); 616 Console.log(" [TaskStack|stackRectSansPeek] " + mStackRectSansPeek); 617 Console.log(" [TaskStack|taskRect] " + mTaskRect); 618 } 619 620 // If this is the first layout, then scroll to the front of the stack and synchronize the 621 // stack views immediately 622 if (mAwaitingFirstLayout) { 623 setStackScroll(mMaxScroll); 624 requestSynchronizeStackViewsWithModel(); 625 synchronizeStackViewsWithModel(); 626 627 // Animate the task bar of the first task view 628 if (config.launchedWithThumbnailAnimation) { 629 TaskView tv = (TaskView) getChildAt(getChildCount() - 1); 630 if (tv != null) { 631 tv.animateOnEnterRecents(); 632 } 633 } 634 } 635 636 // Measure each of the children 637 int childCount = getChildCount(); 638 for (int i = 0; i < childCount; i++) { 639 TaskView t = (TaskView) getChildAt(i); 640 t.measure(MeasureSpec.makeMeasureSpec(mTaskRect.width(), MeasureSpec.EXACTLY), 641 MeasureSpec.makeMeasureSpec(mTaskRect.height(), MeasureSpec.EXACTLY)); 642 } 643 644 setMeasuredDimension(width, height); 645 } 646 647 /** 648 * This is called with the size of the space not including the top or right insets, or the 649 * search bar height in portrait (but including the search bar width in landscape, since we want 650 * to draw under it. 651 */ 652 @Override 653 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 654 Console.log(Constants.Log.UI.MeasureAndLayout, "[TaskStackView|layout]", 655 "" + new Rect(left, top, right, bottom), Console.AnsiGreen); 656 657 // Debug logging 658 if (Constants.Log.UI.MeasureAndLayout) { 659 Console.log(" [TaskStack|fullRect] " + mRect); 660 Console.log(" [TaskStack|stackRect] " + mStackRect); 661 Console.log(" [TaskStack|stackRectSansPeek] " + mStackRectSansPeek); 662 Console.log(" [TaskStack|taskRect] " + mTaskRect); 663 } 664 665 // Layout each of the children 666 int childCount = getChildCount(); 667 for (int i = 0; i < childCount; i++) { 668 TaskView t = (TaskView) getChildAt(i); 669 t.layout(mTaskRect.left, mStackRectSansPeek.top, 670 mTaskRect.right, mStackRectSansPeek.top + mTaskRect.height()); 671 } 672 673 if (mAwaitingFirstLayout) { 674 mAwaitingFirstLayout = false; 675 } 676 } 677 678 @Override 679 protected void onScrollChanged(int l, int t, int oldl, int oldt) { 680 super.onScrollChanged(l, t, oldl, oldt); 681 requestSynchronizeStackViewsWithModel(); 682 } 683 684 public boolean isTransformedTouchPointInView(float x, float y, View child) { 685 return isTransformedTouchPointInView(x, y, child, null); 686 } 687 688 /**** TaskStackCallbacks Implementation ****/ 689 690 @Override 691 public void onStackTaskAdded(TaskStack stack, Task t) { 692 requestSynchronizeStackViewsWithModel(); 693 } 694 695 @Override 696 public void onStackTaskRemoved(TaskStack stack, Task t) { 697 // Remove the view associated with this task, we can't rely on updateTransforms 698 // to work here because the task is no longer in the list 699 int childCount = getChildCount(); 700 for (int i = childCount - 1; i >= 0; i--) { 701 TaskView tv = (TaskView) getChildAt(i); 702 if (tv.getTask() == t) { 703 mViewPool.returnViewToPool(tv); 704 break; 705 } 706 } 707 708 updateMinMaxScroll(true); 709 int movement = (int) (Constants.Values.TaskStackView.StackOverlapPct * mTaskRect.height()); 710 requestSynchronizeStackViewsWithModel(Utilities.calculateTranslationAnimationDuration(movement)); 711 } 712 713 /** 714 * Creates the animations for all the children views that need to be removed or to move views 715 * to their un/filtered position when we are un/filtering a stack, and returns the duration 716 * for these animations. 717 */ 718 int getExitTransformsForFilterAnimation(ArrayList<Task> curTasks, 719 ArrayList<TaskViewTransform> curTaskTransforms, 720 ArrayList<Task> tasks, ArrayList<TaskViewTransform> taskTransforms, 721 HashMap<TaskView, Pair<Integer, TaskViewTransform>> childViewTransformsOut, 722 ArrayList<TaskView> childrenToRemoveOut, 723 RecentsConfiguration config) { 724 // Animate all of the existing views out of view (if they are not in the visible range in 725 // the new stack) or to their final positions in the new stack 726 int movement = 0; 727 int childCount = getChildCount(); 728 for (int i = 0; i < childCount; i++) { 729 TaskView tv = (TaskView) getChildAt(i); 730 Task task = tv.getTask(); 731 int taskIndex = tasks.indexOf(task); 732 TaskViewTransform toTransform; 733 734 // If the view is no longer visible, then we should just animate it out 735 boolean willBeInvisible = taskIndex < 0 || !taskTransforms.get(taskIndex).visible; 736 if (willBeInvisible) { 737 if (taskIndex < 0) { 738 toTransform = curTaskTransforms.get(curTasks.indexOf(task)); 739 } else { 740 toTransform = new TaskViewTransform(taskTransforms.get(taskIndex)); 741 } 742 tv.prepareTaskTransformForFilterTaskVisible(toTransform); 743 childrenToRemoveOut.add(tv); 744 } else { 745 toTransform = taskTransforms.get(taskIndex); 746 // Use the movement of the visible views to calculate the duration of the animation 747 movement = Math.max(movement, Math.abs(toTransform.translationY - 748 (int) tv.getTranslationY())); 749 } 750 childViewTransformsOut.put(tv, new Pair(0, toTransform)); 751 } 752 return Utilities.calculateTranslationAnimationDuration(movement, 753 config.filteringCurrentViewsMinAnimDuration); 754 } 755 756 /** 757 * Creates the animations for all the children views that need to be animated in when we are 758 * un/filtering a stack, and returns the duration for these animations. 759 */ 760 int getEnterTransformsForFilterAnimation(ArrayList<Task> tasks, 761 ArrayList<TaskViewTransform> taskTransforms, 762 HashMap<TaskView, Pair<Integer, TaskViewTransform>> childViewTransformsOut, 763 RecentsConfiguration config) { 764 int offset = 0; 765 int movement = 0; 766 int taskCount = tasks.size(); 767 for (int i = taskCount - 1; i >= 0; i--) { 768 Task task = tasks.get(i); 769 TaskViewTransform toTransform = taskTransforms.get(i); 770 if (toTransform.visible) { 771 TaskView tv = getChildViewForTask(task); 772 if (tv == null) { 773 // For views that are not already visible, animate them in 774 tv = mViewPool.pickUpViewFromPool(task, task); 775 776 // Compose a new transform to fade and slide the new task in 777 TaskViewTransform fromTransform = new TaskViewTransform(toTransform); 778 tv.prepareTaskTransformForFilterTaskHidden(fromTransform); 779 tv.updateViewPropertiesToTaskTransform(null, fromTransform, 0); 780 781 int startDelay = offset * 782 Constants.Values.TaskStackView.FilterStartDelay; 783 childViewTransformsOut.put(tv, new Pair(startDelay, toTransform)); 784 785 // Use the movement of the new views to calculate the duration of the animation 786 movement = Math.max(movement, 787 Math.abs(toTransform.translationY - fromTransform.translationY)); 788 offset++; 789 } 790 } 791 } 792 return Utilities.calculateTranslationAnimationDuration(movement, 793 config.filteringNewViewsMinAnimDuration); 794 } 795 796 /** Orchestrates the animations of the current child views and any new views. */ 797 void doFilteringAnimation(ArrayList<Task> curTasks, 798 ArrayList<TaskViewTransform> curTaskTransforms, 799 final ArrayList<Task> tasks, 800 final ArrayList<TaskViewTransform> taskTransforms) { 801 final RecentsConfiguration config = RecentsConfiguration.getInstance(); 802 803 // Calculate the transforms to animate out all the existing views if they are not in the 804 // new visible range (or to their final positions in the stack if they are) 805 final ArrayList<TaskView> childrenToRemove = new ArrayList<TaskView>(); 806 final HashMap<TaskView, Pair<Integer, TaskViewTransform>> childViewTransforms = 807 new HashMap<TaskView, Pair<Integer, TaskViewTransform>>(); 808 int duration = getExitTransformsForFilterAnimation(curTasks, curTaskTransforms, tasks, 809 taskTransforms, childViewTransforms, childrenToRemove, config); 810 811 // If all the current views are in the visible range of the new stack, then don't wait for 812 // views to animate out and animate all the new views into their place 813 final boolean unifyNewViewAnimation = childrenToRemove.isEmpty(); 814 if (unifyNewViewAnimation) { 815 int inDuration = getEnterTransformsForFilterAnimation(tasks, taskTransforms, 816 childViewTransforms, config); 817 duration = Math.max(duration, inDuration); 818 } 819 820 // Animate all the views to their final transforms 821 for (final TaskView tv : childViewTransforms.keySet()) { 822 Pair<Integer, TaskViewTransform> t = childViewTransforms.get(tv); 823 tv.animate().cancel(); 824 tv.animate() 825 .setStartDelay(t.first) 826 .withEndAction(new Runnable() { 827 @Override 828 public void run() { 829 childViewTransforms.remove(tv); 830 if (childViewTransforms.isEmpty()) { 831 // Return all the removed children to the view pool 832 for (TaskView tv : childrenToRemove) { 833 mViewPool.returnViewToPool(tv); 834 } 835 836 if (!unifyNewViewAnimation) { 837 // For views that are not already visible, animate them in 838 childViewTransforms.clear(); 839 int duration = getEnterTransformsForFilterAnimation(tasks, 840 taskTransforms, childViewTransforms, config); 841 for (final TaskView tv : childViewTransforms.keySet()) { 842 Pair<Integer, TaskViewTransform> t = childViewTransforms.get(tv); 843 tv.animate().setStartDelay(t.first); 844 tv.updateViewPropertiesToTaskTransform(null, t.second, duration); 845 } 846 } 847 } 848 } 849 }); 850 tv.updateViewPropertiesToTaskTransform(null, t.second, duration); 851 } 852 } 853 854 @Override 855 public void onStackFiltered(TaskStack newStack, final ArrayList<Task> curTasks, 856 Task filteredTask) { 857 // Close any open info panes 858 closeOpenInfoPanes(); 859 860 // Stash the scroll and filtered task for us to restore to when we unfilter 861 mStashedScroll = getStackScroll(); 862 863 // Calculate the current task transforms 864 ArrayList<TaskViewTransform> curTaskTransforms = 865 getStackTransforms(curTasks, getStackScroll(), null, true); 866 867 // Scroll the item to the top of the stack (sans-peek) rect so that we can see it better 868 updateMinMaxScroll(false); 869 float overlapHeight = Constants.Values.TaskStackView.StackOverlapPct * mTaskRect.height(); 870 setStackScrollRaw((int) (newStack.indexOfTask(filteredTask) * overlapHeight)); 871 boundScrollRaw(); 872 873 // Compute the transforms of the items in the new stack after setting the new scroll 874 final ArrayList<Task> tasks = mStack.getTasks(); 875 final ArrayList<TaskViewTransform> taskTransforms = 876 getStackTransforms(mStack.getTasks(), getStackScroll(), null, true); 877 878 // Animate 879 doFilteringAnimation(curTasks, curTaskTransforms, tasks, taskTransforms); 880 } 881 882 @Override 883 public void onStackUnfiltered(TaskStack newStack, final ArrayList<Task> curTasks) { 884 // Close any open info panes 885 closeOpenInfoPanes(); 886 887 // Calculate the current task transforms 888 final ArrayList<TaskViewTransform> curTaskTransforms = 889 getStackTransforms(curTasks, getStackScroll(), null, true); 890 891 // Restore the stashed scroll 892 updateMinMaxScroll(false); 893 setStackScrollRaw(mStashedScroll); 894 boundScrollRaw(); 895 896 // Compute the transforms of the items in the new stack after restoring the stashed scroll 897 final ArrayList<Task> tasks = mStack.getTasks(); 898 final ArrayList<TaskViewTransform> taskTransforms = 899 getStackTransforms(tasks, getStackScroll(), null, true); 900 901 // Animate 902 doFilteringAnimation(curTasks, curTaskTransforms, tasks, taskTransforms); 903 904 // Clear the saved vars 905 mStashedScroll = 0; 906 } 907 908 /**** ViewPoolConsumer Implementation ****/ 909 910 @Override 911 public TaskView createView(Context context) { 912 Console.log(Constants.Log.ViewPool.PoolCallbacks, "[TaskStackView|createPoolView]"); 913 return (TaskView) mInflater.inflate(R.layout.recents_task_view, this, false); 914 } 915 916 @Override 917 public void prepareViewToEnterPool(TaskView tv) { 918 Task task = tv.getTask(); 919 tv.resetViewProperties(); 920 Console.log(Constants.Log.ViewPool.PoolCallbacks, "[TaskStackView|returnToPool]", 921 tv.getTask() + " tv: " + tv); 922 923 // Report that this tasks's data is no longer being used 924 RecentsTaskLoader loader = RecentsTaskLoader.getInstance(); 925 loader.unloadTaskData(task); 926 927 // Detach the view from the hierarchy 928 detachViewFromParent(tv); 929 930 // Disable hw layers on this view 931 tv.disableHwLayers(); 932 } 933 934 @Override 935 public void prepareViewToLeavePool(TaskView tv, Task prepareData, boolean isNewView) { 936 Console.log(Constants.Log.ViewPool.PoolCallbacks, "[TaskStackView|leavePool]", 937 "isNewView: " + isNewView); 938 939 // Setup and attach the view to the window 940 Task task = prepareData; 941 // We try and rebind the task (this MUST be done before the task filled) 942 tv.onTaskBound(task); 943 // Request that this tasks's data be filled 944 RecentsTaskLoader loader = RecentsTaskLoader.getInstance(); 945 loader.loadTaskData(task); 946 947 // Find the index where this task should be placed in the children 948 int insertIndex = -1; 949 int childCount = getChildCount(); 950 for (int i = 0; i < childCount; i++) { 951 Task tvTask = ((TaskView) getChildAt(i)).getTask(); 952 if (mStack.containsTask(task) && (mStack.indexOfTask(task) < mStack.indexOfTask(tvTask))) { 953 insertIndex = i; 954 break; 955 } 956 } 957 958 // Add/attach the view to the hierarchy 959 Console.log(Constants.Log.ViewPool.PoolCallbacks, " [TaskStackView|insertIndex]", 960 "" + insertIndex); 961 if (isNewView) { 962 addView(tv, insertIndex); 963 964 // Set the callbacks and listeners for this new view 965 tv.setOnClickListener(this); 966 if (Constants.DebugFlags.App.EnableInfoPane) { 967 tv.setOnLongClickListener(this); 968 } 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.Log.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 } 999 } 1000 1001 @Override 1002 public void onTaskInfoPanelShown(TaskView tv) { 1003 // Do nothing 1004 } 1005 1006 @Override 1007 public void onTaskInfoPanelHidden(TaskView tv) { 1008 // Unset the saved scroll 1009 mLastInfoPaneStackScroll = -1; 1010 } 1011 1012 @Override 1013 public void onTaskAppInfoClicked(TaskView tv) { 1014 if (mCb != null) { 1015 mCb.onTaskAppInfoLaunched(tv.getTask()); 1016 } 1017 } 1018 1019 /**** View.OnClickListener Implementation ****/ 1020 1021 @Override 1022 public void onClick(View v) { 1023 TaskView tv = (TaskView) v; 1024 Task task = tv.getTask(); 1025 Console.log(Constants.Log.UI.ClickEvents, "[TaskStack|Clicked|Thumbnail]", 1026 task + " cb: " + mCb); 1027 1028 // Close any open info panes if the user taps on another task 1029 if (closeOpenInfoPanes()) { 1030 return; 1031 } 1032 1033 if (mCb != null) { 1034 mCb.onTaskLaunched(this, tv, mStack, task); 1035 } 1036 } 1037 1038 @Override 1039 public boolean onLongClick(View v) { 1040 if (!Constants.DebugFlags.App.EnableInfoPane) return false; 1041 1042 TaskView tv = (TaskView) v; 1043 1044 // Close any other task info panels if we launch another info pane 1045 closeOpenInfoPanes(); 1046 1047 // Scroll the task view so that it is maximally visible 1048 float overlapHeight = Constants.Values.TaskStackView.StackOverlapPct * mTaskRect.height(); 1049 int taskIndex = mStack.indexOfTask(tv.getTask()); 1050 int curScroll = getStackScroll(); 1051 int newScroll = (int) Math.max(mMinScroll, Math.min(mMaxScroll, taskIndex * overlapHeight)); 1052 TaskViewTransform transform = getStackTransform(taskIndex, curScroll); 1053 Rect nonOverlapRect = new Rect(transform.rect); 1054 if (taskIndex < (mStack.getTaskCount() - 1)) { 1055 nonOverlapRect.bottom = nonOverlapRect.top + (int) overlapHeight; 1056 } 1057 1058 // XXX: Use HW Layers 1059 if (transform.t < 0f) { 1060 animateScroll(curScroll, newScroll, null); 1061 } else if (nonOverlapRect.bottom > mStackRectSansPeek.bottom) { 1062 // Check if we are out of bounds, if so, just scroll it in such that the bottom of the 1063 // task view is visible 1064 newScroll = curScroll - (mStackRectSansPeek.bottom - nonOverlapRect.bottom); 1065 animateScroll(curScroll, newScroll, null); 1066 } 1067 mLastInfoPaneStackScroll = newScroll; 1068 1069 // Show the info pane for this task view 1070 tv.showInfoPane(new Rect(0, 0, 0, (int) overlapHeight)); 1071 return true; 1072 } 1073} 1074 1075/* Handles touch events */ 1076class TaskStackViewTouchHandler implements SwipeHelper.Callback { 1077 static int INACTIVE_POINTER_ID = -1; 1078 1079 TaskStackView mSv; 1080 VelocityTracker mVelocityTracker; 1081 1082 boolean mIsScrolling; 1083 1084 int mInitialMotionX, mInitialMotionY; 1085 int mLastMotionX, mLastMotionY; 1086 int mActivePointerId = INACTIVE_POINTER_ID; 1087 TaskView mActiveTaskView = null; 1088 1089 int mTotalScrollMotion; 1090 int mMinimumVelocity; 1091 int mMaximumVelocity; 1092 // The scroll touch slop is used to calculate when we start scrolling 1093 int mScrollTouchSlop; 1094 // The page touch slop is used to calculate when we start swiping 1095 float mPagingTouchSlop; 1096 1097 SwipeHelper mSwipeHelper; 1098 boolean mInterceptedBySwipeHelper; 1099 1100 public TaskStackViewTouchHandler(Context context, TaskStackView sv) { 1101 ViewConfiguration configuration = ViewConfiguration.get(context); 1102 mMinimumVelocity = configuration.getScaledMinimumFlingVelocity(); 1103 mMaximumVelocity = configuration.getScaledMaximumFlingVelocity(); 1104 mScrollTouchSlop = configuration.getScaledTouchSlop(); 1105 mPagingTouchSlop = configuration.getScaledPagingTouchSlop(); 1106 mSv = sv; 1107 1108 1109 float densityScale = context.getResources().getDisplayMetrics().density; 1110 mSwipeHelper = new SwipeHelper(SwipeHelper.X, this, densityScale, mPagingTouchSlop); 1111 mSwipeHelper.setMinAlpha(1f); 1112 } 1113 1114 /** Velocity tracker helpers */ 1115 void initOrResetVelocityTracker() { 1116 if (mVelocityTracker == null) { 1117 mVelocityTracker = VelocityTracker.obtain(); 1118 } else { 1119 mVelocityTracker.clear(); 1120 } 1121 } 1122 void initVelocityTrackerIfNotExists() { 1123 if (mVelocityTracker == null) { 1124 mVelocityTracker = VelocityTracker.obtain(); 1125 } 1126 } 1127 void recycleVelocityTracker() { 1128 if (mVelocityTracker != null) { 1129 mVelocityTracker.recycle(); 1130 mVelocityTracker = null; 1131 } 1132 } 1133 1134 /** Returns the view at the specified coordinates */ 1135 TaskView findViewAtPoint(int x, int y) { 1136 int childCount = mSv.getChildCount(); 1137 for (int i = childCount - 1; i >= 0; i--) { 1138 TaskView tv = (TaskView) mSv.getChildAt(i); 1139 if (tv.getVisibility() == View.VISIBLE) { 1140 if (mSv.isTransformedTouchPointInView(x, y, tv)) { 1141 return tv; 1142 } 1143 } 1144 } 1145 return null; 1146 } 1147 1148 /** Touch preprocessing for handling below */ 1149 public boolean onInterceptTouchEvent(MotionEvent ev) { 1150 Console.log(Constants.Log.UI.TouchEvents, 1151 "[TaskStackViewTouchHandler|interceptTouchEvent]", 1152 Console.motionEventActionToString(ev.getAction()), Console.AnsiBlue); 1153 1154 // Return early if we have no children 1155 boolean hasChildren = (mSv.getChildCount() > 0); 1156 if (!hasChildren) { 1157 return false; 1158 } 1159 1160 // Pass through to swipe helper if we are swiping 1161 mInterceptedBySwipeHelper = mSwipeHelper.onInterceptTouchEvent(ev); 1162 if (mInterceptedBySwipeHelper) { 1163 return true; 1164 } 1165 1166 boolean wasScrolling = !mSv.mScroller.isFinished() || 1167 (mSv.mScrollAnimator != null && mSv.mScrollAnimator.isRunning()); 1168 int action = ev.getAction(); 1169 switch (action & MotionEvent.ACTION_MASK) { 1170 case MotionEvent.ACTION_DOWN: { 1171 // Save the touch down info 1172 mInitialMotionX = mLastMotionX = (int) ev.getX(); 1173 mInitialMotionY = mLastMotionY = (int) ev.getY(); 1174 mActivePointerId = ev.getPointerId(0); 1175 mActiveTaskView = findViewAtPoint(mLastMotionX, mLastMotionY); 1176 // Stop the current scroll if it is still flinging 1177 mSv.abortScroller(); 1178 mSv.abortBoundScrollAnimation(); 1179 // Initialize the velocity tracker 1180 initOrResetVelocityTracker(); 1181 mVelocityTracker.addMovement(ev); 1182 // Check if the scroller is finished yet 1183 mIsScrolling = !mSv.mScroller.isFinished(); 1184 break; 1185 } 1186 case MotionEvent.ACTION_MOVE: { 1187 if (mActivePointerId == INACTIVE_POINTER_ID) break; 1188 1189 int activePointerIndex = ev.findPointerIndex(mActivePointerId); 1190 int y = (int) ev.getY(activePointerIndex); 1191 int x = (int) ev.getX(activePointerIndex); 1192 if (Math.abs(y - mInitialMotionY) > mScrollTouchSlop) { 1193 // Save the touch move info 1194 mIsScrolling = true; 1195 // Initialize the velocity tracker if necessary 1196 initVelocityTrackerIfNotExists(); 1197 mVelocityTracker.addMovement(ev); 1198 // Disallow parents from intercepting touch events 1199 final ViewParent parent = mSv.getParent(); 1200 if (parent != null) { 1201 parent.requestDisallowInterceptTouchEvent(true); 1202 } 1203 // Enable HW layers 1204 mSv.addHwLayersRefCount("stackScroll"); 1205 } 1206 1207 mLastMotionX = x; 1208 mLastMotionY = y; 1209 break; 1210 } 1211 case MotionEvent.ACTION_CANCEL: 1212 case MotionEvent.ACTION_UP: { 1213 // Animate the scroll back if we've cancelled 1214 mSv.animateBoundScroll(); 1215 // Disable HW layers 1216 if (mIsScrolling) { 1217 mSv.decHwLayersRefCount("stackScroll"); 1218 } 1219 // Reset the drag state and the velocity tracker 1220 mIsScrolling = false; 1221 mActivePointerId = INACTIVE_POINTER_ID; 1222 mActiveTaskView = null; 1223 mTotalScrollMotion = 0; 1224 recycleVelocityTracker(); 1225 break; 1226 } 1227 } 1228 1229 return wasScrolling || mIsScrolling; 1230 } 1231 1232 /** Handles touch events once we have intercepted them */ 1233 public boolean onTouchEvent(MotionEvent ev) { 1234 Console.log(Constants.Log.UI.TouchEvents, 1235 "[TaskStackViewTouchHandler|touchEvent]", 1236 Console.motionEventActionToString(ev.getAction()), Console.AnsiBlue); 1237 1238 // Short circuit if we have no children 1239 boolean hasChildren = (mSv.getChildCount() > 0); 1240 if (!hasChildren) { 1241 return false; 1242 } 1243 1244 // Pass through to swipe helper if we are swiping 1245 if (mInterceptedBySwipeHelper && mSwipeHelper.onTouchEvent(ev)) { 1246 return true; 1247 } 1248 1249 // Update the velocity tracker 1250 initVelocityTrackerIfNotExists(); 1251 mVelocityTracker.addMovement(ev); 1252 1253 int action = ev.getAction(); 1254 switch (action & MotionEvent.ACTION_MASK) { 1255 case MotionEvent.ACTION_DOWN: { 1256 // Save the touch down info 1257 mInitialMotionX = mLastMotionX = (int) ev.getX(); 1258 mInitialMotionY = mLastMotionY = (int) ev.getY(); 1259 mActivePointerId = ev.getPointerId(0); 1260 mActiveTaskView = findViewAtPoint(mLastMotionX, mLastMotionY); 1261 // Stop the current scroll if it is still flinging 1262 mSv.abortScroller(); 1263 mSv.abortBoundScrollAnimation(); 1264 // Initialize the velocity tracker 1265 initOrResetVelocityTracker(); 1266 mVelocityTracker.addMovement(ev); 1267 // Disallow parents from intercepting touch events 1268 final ViewParent parent = mSv.getParent(); 1269 if (parent != null) { 1270 parent.requestDisallowInterceptTouchEvent(true); 1271 } 1272 break; 1273 } 1274 case MotionEvent.ACTION_POINTER_DOWN: { 1275 final int index = ev.getActionIndex(); 1276 mActivePointerId = ev.getPointerId(index); 1277 mLastMotionX = (int) ev.getX(index); 1278 mLastMotionY = (int) ev.getY(index); 1279 break; 1280 } 1281 case MotionEvent.ACTION_MOVE: { 1282 if (mActivePointerId == INACTIVE_POINTER_ID) break; 1283 1284 int activePointerIndex = ev.findPointerIndex(mActivePointerId); 1285 int x = (int) ev.getX(activePointerIndex); 1286 int y = (int) ev.getY(activePointerIndex); 1287 int yTotal = Math.abs(y - mInitialMotionY); 1288 int deltaY = mLastMotionY - y; 1289 if (!mIsScrolling) { 1290 if (yTotal > mScrollTouchSlop) { 1291 mIsScrolling = true; 1292 // Initialize the velocity tracker 1293 initOrResetVelocityTracker(); 1294 mVelocityTracker.addMovement(ev); 1295 // Disallow parents from intercepting touch events 1296 final ViewParent parent = mSv.getParent(); 1297 if (parent != null) { 1298 parent.requestDisallowInterceptTouchEvent(true); 1299 } 1300 // Enable HW layers 1301 mSv.addHwLayersRefCount("stackScroll"); 1302 } 1303 } 1304 if (mIsScrolling) { 1305 int curStackScroll = mSv.getStackScroll(); 1306 int overScrollAmount = mSv.getScrollAmountOutOfBounds(curStackScroll + deltaY); 1307 if (overScrollAmount != 0) { 1308 // Bound the overscroll to a fixed amount, and inversely scale the y-movement 1309 // relative to how close we are to the max overscroll 1310 float maxOverScroll = mSv.mTaskRect.height() / 3f; 1311 deltaY = Math.round(deltaY * (1f - (Math.min(maxOverScroll, overScrollAmount) 1312 / maxOverScroll))); 1313 } 1314 mSv.setStackScroll(curStackScroll + deltaY); 1315 if (mSv.isScrollOutOfBounds()) { 1316 mVelocityTracker.clear(); 1317 } 1318 } 1319 mLastMotionX = x; 1320 mLastMotionY = y; 1321 mTotalScrollMotion += Math.abs(deltaY); 1322 break; 1323 } 1324 case MotionEvent.ACTION_UP: { 1325 final VelocityTracker velocityTracker = mVelocityTracker; 1326 velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); 1327 int velocity = (int) velocityTracker.getYVelocity(mActivePointerId); 1328 1329 if (mIsScrolling && (Math.abs(velocity) > mMinimumVelocity)) { 1330 // Enable HW layers on the stack 1331 mSv.addHwLayersRefCount("flingScroll"); 1332 // XXX: Make this animation a function of the velocity AND distance 1333 int overscrollRange = (int) (Math.min(1f, 1334 Math.abs((float) velocity / mMaximumVelocity)) * 1335 Constants.Values.TaskStackView.TaskStackOverscrollRange); 1336 1337 Console.log(Constants.Log.UI.TouchEvents, 1338 "[TaskStackViewTouchHandler|fling]", 1339 "scroll: " + mSv.getStackScroll() + " velocity: " + velocity + 1340 " maxVelocity: " + mMaximumVelocity + 1341 " overscrollRange: " + overscrollRange, 1342 Console.AnsiGreen); 1343 1344 // Fling scroll 1345 mSv.mScroller.fling(0, mSv.getStackScroll(), 1346 0, -velocity, 1347 0, 0, 1348 mSv.mMinScroll, mSv.mMaxScroll, 1349 0, overscrollRange); 1350 // Invalidate to kick off computeScroll 1351 mSv.invalidate(); 1352 } else if (mSv.isScrollOutOfBounds()) { 1353 // Animate the scroll back into bounds 1354 // XXX: Make this animation a function of the velocity OR distance 1355 mSv.animateBoundScroll(); 1356 } 1357 1358 if (mIsScrolling) { 1359 // Disable HW layers 1360 mSv.decHwLayersRefCount("stackScroll"); 1361 } 1362 mActivePointerId = INACTIVE_POINTER_ID; 1363 mIsScrolling = false; 1364 mTotalScrollMotion = 0; 1365 recycleVelocityTracker(); 1366 break; 1367 } 1368 case MotionEvent.ACTION_POINTER_UP: { 1369 int pointerIndex = ev.getActionIndex(); 1370 int pointerId = ev.getPointerId(pointerIndex); 1371 if (pointerId == mActivePointerId) { 1372 // Select a new active pointer id and reset the motion state 1373 final int newPointerIndex = (pointerIndex == 0) ? 1 : 0; 1374 mActivePointerId = ev.getPointerId(newPointerIndex); 1375 mLastMotionX = (int) ev.getX(newPointerIndex); 1376 mLastMotionY = (int) ev.getY(newPointerIndex); 1377 mVelocityTracker.clear(); 1378 } 1379 break; 1380 } 1381 case MotionEvent.ACTION_CANCEL: { 1382 if (mIsScrolling) { 1383 // Disable HW layers 1384 mSv.decHwLayersRefCount("stackScroll"); 1385 } 1386 if (mSv.isScrollOutOfBounds()) { 1387 // Animate the scroll back into bounds 1388 // XXX: Make this animation a function of the velocity OR distance 1389 mSv.animateBoundScroll(); 1390 } 1391 mActivePointerId = INACTIVE_POINTER_ID; 1392 mIsScrolling = false; 1393 mTotalScrollMotion = 0; 1394 recycleVelocityTracker(); 1395 break; 1396 } 1397 } 1398 return true; 1399 } 1400 1401 /**** SwipeHelper Implementation ****/ 1402 1403 @Override 1404 public View getChildAtPosition(MotionEvent ev) { 1405 return findViewAtPoint((int) ev.getX(), (int) ev.getY()); 1406 } 1407 1408 @Override 1409 public boolean canChildBeDismissed(View v) { 1410 return true; 1411 } 1412 1413 @Override 1414 public void onBeginDrag(View v) { 1415 // Enable HW layers 1416 mSv.addHwLayersRefCount("swipeBegin"); 1417 // Disallow parents from intercepting touch events 1418 final ViewParent parent = mSv.getParent(); 1419 if (parent != null) { 1420 parent.requestDisallowInterceptTouchEvent(true); 1421 } 1422 // If the info panel is currently showing on this view, then we need to dismiss it 1423 if (Constants.DebugFlags.App.EnableInfoPane) { 1424 TaskView tv = (TaskView) v; 1425 if (tv.isInfoPaneVisible()) { 1426 tv.hideInfoPane(); 1427 } 1428 } 1429 } 1430 1431 @Override 1432 public void onChildDismissed(View v) { 1433 TaskView tv = (TaskView) v; 1434 Task task = tv.getTask(); 1435 Activity activity = (Activity) mSv.getContext(); 1436 1437 // Remove the task from the view 1438 mSv.mStack.removeTask(task); 1439 1440 // Remove any stored data from the loader 1441 RecentsTaskLoader loader = RecentsTaskLoader.getInstance(); 1442 loader.deleteTaskData(task); 1443 1444 // Remove the task from activity manager 1445 RecentsTaskLoader.getInstance().getSystemServicesProxy().removeTask(tv.getTask().key.id); 1446 1447 // If there are no remaining tasks, then either unfilter the current stack, or just close 1448 // the activity if there are no filtered stacks 1449 if (mSv.mStack.getTaskCount() == 0) { 1450 boolean shouldFinishActivity = true; 1451 if (mSv.mStack.hasFilteredTasks()) { 1452 mSv.mStack.unfilterTasks(); 1453 shouldFinishActivity = (mSv.mStack.getTaskCount() == 0); 1454 } 1455 if (shouldFinishActivity) { 1456 activity.finish(); 1457 } 1458 } 1459 1460 // Disable HW layers 1461 mSv.decHwLayersRefCount("swipeComplete"); 1462 } 1463 1464 @Override 1465 public void onSnapBackCompleted(View v) { 1466 // Do Nothing 1467 } 1468 1469 @Override 1470 public void onDragCancelled(View v) { 1471 // Disable HW layers 1472 mSv.decHwLayersRefCount("swipeCancelled"); 1473 } 1474} 1475