TaskStackView.java revision e0e45bc26d02e2c6ec505ea006e7487f3a5bddc5
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.ComponentName; 25import android.content.Context; 26import android.graphics.Canvas; 27import android.graphics.Rect; 28import android.graphics.Region; 29import android.util.Pair; 30import android.view.LayoutInflater; 31import android.view.MotionEvent; 32import android.view.VelocityTracker; 33import android.view.View; 34import android.view.ViewConfiguration; 35import android.view.ViewParent; 36import android.widget.FrameLayout; 37import android.widget.OverScroller; 38import com.android.systemui.R; 39import com.android.systemui.recents.Console; 40import com.android.systemui.recents.Constants; 41import com.android.systemui.recents.DozeTrigger; 42import com.android.systemui.recents.RecentsConfiguration; 43import com.android.systemui.recents.RecentsPackageMonitor; 44import com.android.systemui.recents.RecentsTaskLoader; 45import com.android.systemui.recents.ReferenceCountedTrigger; 46import com.android.systemui.recents.Utilities; 47import com.android.systemui.recents.model.Task; 48import com.android.systemui.recents.model.TaskStack; 49 50import java.util.ArrayList; 51import java.util.HashMap; 52import java.util.Set; 53 54 55/* The visual representation of a task stack view */ 56public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCallbacks, 57 TaskView.TaskViewCallbacks, ViewPool.ViewPoolConsumer<TaskView, Task>, 58 View.OnClickListener, RecentsPackageMonitor.PackageCallbacks { 59 60 /** The TaskView callbacks */ 61 interface TaskStackViewCallbacks { 62 public void onTaskLaunched(TaskStackView stackView, TaskView tv, TaskStack stack, Task t); 63 public void onTaskAppInfoLaunched(Task t); 64 public void onTaskRemoved(Task t); 65 } 66 67 RecentsConfiguration mConfig; 68 69 TaskStack mStack; 70 TaskStackViewTouchHandler mTouchHandler; 71 TaskStackViewCallbacks mCb; 72 ViewPool<TaskView, Task> mViewPool; 73 ArrayList<TaskViewTransform> mTaskTransforms = new ArrayList<TaskViewTransform>(); 74 DozeTrigger mDozeTrigger; 75 76 // The various rects that define the stack view 77 Rect mRect = new Rect(); 78 Rect mStackRect = new Rect(); 79 Rect mStackRectSansPeek = new Rect(); 80 Rect mTaskRect = new Rect(); 81 82 // The virtual stack scroll that we use for the card layout 83 int mStackScroll; 84 int mMinScroll; 85 int mMaxScroll; 86 int mStashedScroll; 87 int mFocusedTaskIndex = -1; 88 OverScroller mScroller; 89 ObjectAnimator mScrollAnimator; 90 91 // Optimizations 92 ReferenceCountedTrigger mHwLayersTrigger; 93 int mStackViewsAnimationDuration; 94 boolean mStackViewsDirty = true; 95 boolean mAwaitingFirstLayout = true; 96 boolean mStartEnterAnimationRequestedAfterLayout; 97 ViewAnimation.TaskViewEnterContext mStartEnterAnimationContext; 98 int[] mTmpVisibleRange = new int[2]; 99 Rect mTmpRect = new Rect(); 100 Rect mTmpRect2 = new Rect(); 101 LayoutInflater mInflater; 102 103 public TaskStackView(Context context, TaskStack stack) { 104 super(context); 105 mConfig = RecentsConfiguration.getInstance(); 106 mStack = stack; 107 mStack.setCallbacks(this); 108 mScroller = new OverScroller(context); 109 mTouchHandler = new TaskStackViewTouchHandler(context, this); 110 mViewPool = new ViewPool<TaskView, Task>(context, this); 111 mInflater = LayoutInflater.from(context); 112 mDozeTrigger = new DozeTrigger(mConfig.taskBarDismissDozeDelaySeconds, new Runnable() { 113 @Override 114 public void run() { 115 // Show the task bar dismiss buttons 116 int childCount = getChildCount(); 117 for (int i = 0; i < childCount; i++) { 118 TaskView tv = (TaskView) getChildAt(i); 119 tv.startNoUserInteractionAnimation(); 120 } 121 } 122 }); 123 mHwLayersTrigger = new ReferenceCountedTrigger(getContext(), new Runnable() { 124 @Override 125 public void run() { 126 // Enable hw layers on each of the children 127 int childCount = getChildCount(); 128 for (int i = 0; i < childCount; i++) { 129 TaskView tv = (TaskView) getChildAt(i); 130 tv.enableHwLayers(); 131 } 132 } 133 }, new Runnable() { 134 @Override 135 public void run() { 136 // Disable hw layers on each of the children 137 int childCount = getChildCount(); 138 for (int i = 0; i < childCount; i++) { 139 TaskView tv = (TaskView) getChildAt(i); 140 tv.disableHwLayers(); 141 } 142 } 143 }, new Runnable() { 144 @Override 145 public void run() { 146 new Throwable("Invalid hw layers ref count").printStackTrace(); 147 Console.logError(getContext(), "Invalid HW layers ref count"); 148 } 149 }); 150 } 151 152 /** Sets the callbacks */ 153 void setCallbacks(TaskStackViewCallbacks cb) { 154 mCb = cb; 155 } 156 157 /** Requests that the views be synchronized with the model */ 158 void requestSynchronizeStackViewsWithModel() { 159 requestSynchronizeStackViewsWithModel(0); 160 } 161 void requestSynchronizeStackViewsWithModel(int duration) { 162 if (Console.Enabled) { 163 Console.log(Constants.Log.TaskStack.SynchronizeViewsWithModel, 164 "[TaskStackView|requestSynchronize]", "" + duration + "ms", Console.AnsiYellow); 165 } 166 if (!mStackViewsDirty) { 167 invalidate(mStackRect); 168 } 169 if (mAwaitingFirstLayout) { 170 // Skip the animation if we are awaiting first layout 171 mStackViewsAnimationDuration = 0; 172 } else { 173 mStackViewsAnimationDuration = Math.max(mStackViewsAnimationDuration, duration); 174 } 175 mStackViewsDirty = true; 176 } 177 178 /** Finds the child view given a specific task */ 179 private TaskView getChildViewForTask(Task t) { 180 int childCount = getChildCount(); 181 for (int i = 0; i < childCount; i++) { 182 TaskView tv = (TaskView) getChildAt(i); 183 if (tv.getTask() == t) { 184 return tv; 185 } 186 } 187 return null; 188 } 189 190 /** Update/get the transform */ 191 public TaskViewTransform getStackTransform(int indexInStack, int stackScroll) { 192 TaskViewTransform transform = new TaskViewTransform(); 193 194 // Return early if we have an invalid index 195 if (indexInStack < 0) return transform; 196 197 // Map the items to an continuous position relative to the specified scroll 198 int numPeekCards = Constants.Values.TaskStackView.StackPeekNumCards; 199 float overlapHeight = Constants.Values.TaskStackView.StackOverlapPct * mTaskRect.height(); 200 float peekHeight = Constants.Values.TaskStackView.StackPeekHeightPct * mStackRect.height(); 201 float t = ((indexInStack * overlapHeight) - stackScroll) / overlapHeight; 202 float boundedT = Math.max(t, -(numPeekCards + 1)); 203 204 // Set the scale relative to its position 205 int numFrontScaledCards = 3; 206 float minScale = Constants.Values.TaskStackView.StackPeekMinScale; 207 float scaleRange = 1f - minScale; 208 float scaleInc = scaleRange / (numPeekCards + numFrontScaledCards); 209 float scale = Math.max(minScale, Math.min(1f, minScale + 210 ((boundedT + (numPeekCards + 1)) * scaleInc))); 211 float scaleYOffset = ((1f - scale) * mTaskRect.height()) / 2; 212 transform.scale = scale; 213 214 // Set the y translation 215 if (boundedT < 0f) { 216 transform.translationY = (int) ((Math.max(-numPeekCards, boundedT) / 217 numPeekCards) * peekHeight - scaleYOffset); 218 } else { 219 transform.translationY = (int) (boundedT * overlapHeight - scaleYOffset); 220 } 221 222 // Set the z translation 223 int minZ = mConfig.taskViewTranslationZMinPx; 224 int incZ = mConfig.taskViewTranslationZIncrementPx; 225 transform.translationZ = (int) Math.max(minZ, minZ + ((boundedT + numPeekCards) * incZ)); 226 227 // Set the alphas 228 transform.dismissAlpha = Math.max(-1f, Math.min(0f, t + 1)) + 1f; 229 230 // Update the rect and visibility 231 transform.rect.set(mTaskRect); 232 if (t < -(numPeekCards + 1)) { 233 transform.visible = false; 234 } else { 235 transform.rect.offset(0, transform.translationY); 236 Utilities.scaleRectAboutCenter(transform.rect, transform.scale); 237 transform.visible = Rect.intersects(mRect, transform.rect); 238 } 239 transform.t = t; 240 return transform; 241 } 242 243 /** 244 * Gets the stack transforms of a list of tasks, and returns the visible range of tasks. 245 */ 246 private void updateStackTransforms(ArrayList<TaskViewTransform> taskTransforms, 247 ArrayList<Task> tasks, 248 int stackScroll, 249 int[] visibleRangeOut, 250 boolean boundTranslationsToRect) { 251 // XXX: Optimization: Use binary search to find the visible range 252 253 int taskCount = tasks.size(); 254 int firstVisibleIndex = -1; 255 int lastVisibleIndex = -1; 256 taskTransforms.clear(); 257 taskTransforms.ensureCapacity(taskCount); 258 for (int i = 0; i < taskCount; i++) { 259 TaskViewTransform transform = getStackTransform(i, stackScroll); 260 taskTransforms.add(transform); 261 if (transform.visible) { 262 if (firstVisibleIndex < 0) { 263 firstVisibleIndex = i; 264 } 265 lastVisibleIndex = i; 266 } 267 268 if (boundTranslationsToRect) { 269 transform.translationY = Math.min(transform.translationY, mRect.bottom); 270 } 271 } 272 if (visibleRangeOut != null) { 273 visibleRangeOut[0] = firstVisibleIndex; 274 visibleRangeOut[1] = lastVisibleIndex; 275 } 276 } 277 278 /** 279 * Gets the stack transforms of a list of tasks, and returns the visible range of tasks. This 280 * call is less optimal than calling updateStackTransforms directly. 281 */ 282 private ArrayList<TaskViewTransform> getStackTransforms(ArrayList<Task> tasks, 283 int stackScroll, 284 int[] visibleRangeOut, 285 boolean boundTranslationsToRect) { 286 ArrayList<TaskViewTransform> taskTransforms = new ArrayList<TaskViewTransform>(); 287 updateStackTransforms(taskTransforms, tasks, stackScroll, visibleRangeOut, 288 boundTranslationsToRect); 289 return taskTransforms; 290 } 291 292 /** Synchronizes the views with the model */ 293 void synchronizeStackViewsWithModel() { 294 if (Console.Enabled) { 295 Console.log(Constants.Log.TaskStack.SynchronizeViewsWithModel, 296 "[TaskStackView|synchronizeViewsWithModel]", 297 "mStackViewsDirty: " + mStackViewsDirty, Console.AnsiYellow); 298 } 299 if (mStackViewsDirty) { 300 // XXX: Consider using TaskViewTransform pool to prevent allocations 301 // XXX: Iterate children views, update transforms and remove all that are not visible 302 // For all remaining tasks, update transforms and if visible add the view 303 304 // Get all the task transforms 305 int[] visibleRange = mTmpVisibleRange; 306 int stackScroll = getStackScroll(); 307 ArrayList<Task> tasks = mStack.getTasks(); 308 updateStackTransforms(mTaskTransforms, tasks, stackScroll, visibleRange, false); 309 310 // Update the visible state of all the tasks 311 int taskCount = tasks.size(); 312 for (int i = 0; i < taskCount; i++) { 313 Task task = tasks.get(i); 314 TaskViewTransform transform = mTaskTransforms.get(i); 315 TaskView tv = getChildViewForTask(task); 316 317 if (transform.visible) { 318 if (tv == null) { 319 tv = mViewPool.pickUpViewFromPool(task, task); 320 // When we are picking up a new view from the view pool, prepare it for any 321 // following animation by putting it in a reasonable place 322 if (mStackViewsAnimationDuration > 0 && i != 0) { 323 int fromIndex = (transform.t < 0) ? (visibleRange[0] - 1) : 324 (visibleRange[1] + 1); 325 tv.updateViewPropertiesToTaskTransform( 326 getStackTransform(fromIndex, stackScroll), 0); 327 } 328 } 329 } else { 330 if (tv != null) { 331 mViewPool.returnViewToPool(tv); 332 } 333 } 334 } 335 336 // Update all the remaining view children 337 // NOTE: We have to iterate in reverse where because we are removing views directly 338 int childCount = getChildCount(); 339 for (int i = childCount - 1; i >= 0; i--) { 340 TaskView tv = (TaskView) getChildAt(i); 341 Task task = tv.getTask(); 342 int taskIndex = mStack.indexOfTask(task); 343 if (taskIndex < 0 || !mTaskTransforms.get(taskIndex).visible) { 344 mViewPool.returnViewToPool(tv); 345 } else { 346 tv.updateViewPropertiesToTaskTransform(mTaskTransforms.get(taskIndex), 347 mStackViewsAnimationDuration); 348 } 349 } 350 351 if (Console.Enabled) { 352 Console.log(Constants.Log.TaskStack.SynchronizeViewsWithModel, 353 " [TaskStackView|viewChildren]", "" + getChildCount()); 354 } 355 356 mStackViewsAnimationDuration = 0; 357 mStackViewsDirty = false; 358 } 359 } 360 361 /** Sets the current stack scroll */ 362 public void setStackScroll(int value) { 363 mStackScroll = value; 364 requestSynchronizeStackViewsWithModel(); 365 } 366 /** Sets the current stack scroll without synchronizing the stack view with the model */ 367 public void setStackScrollRaw(int value) { 368 mStackScroll = value; 369 } 370 /** Sets the current stack scroll to the initial state when you first enter recents */ 371 public void setStackScrollToInitialState() { 372 if (mStack.getTaskCount() > 2) { 373 int initialScroll = mMaxScroll - mTaskRect.height() / 2; 374 setStackScroll(initialScroll); 375 } else { 376 setStackScroll(mMaxScroll); 377 } 378 } 379 380 /** 381 * Returns the scroll to such that the task transform at that index will have t=0. (If the scroll 382 * is not bounded) 383 */ 384 int getStackScrollForTaskIndex(int i) { 385 int taskHeight = mTaskRect.height(); 386 return (int) (i * Constants.Values.TaskStackView.StackOverlapPct * taskHeight); 387 } 388 389 /** Gets the current stack scroll */ 390 public int getStackScroll() { 391 return mStackScroll; 392 } 393 394 /** Animates the stack scroll into bounds */ 395 ObjectAnimator animateBoundScroll() { 396 int curScroll = getStackScroll(); 397 int newScroll = Math.max(mMinScroll, Math.min(mMaxScroll, curScroll)); 398 if (newScroll != curScroll) { 399 // Enable hw layers on the stack 400 addHwLayersRefCount("animateBoundScroll"); 401 402 // Start a new scroll animation 403 animateScroll(curScroll, newScroll, new Runnable() { 404 @Override 405 public void run() { 406 // Disable hw layers on the stack 407 decHwLayersRefCount("animateBoundScroll"); 408 } 409 }); 410 } 411 return mScrollAnimator; 412 } 413 414 /** Animates the stack scroll */ 415 void animateScroll(int curScroll, int newScroll, final Runnable postRunnable) { 416 // Abort any current animations 417 abortScroller(); 418 abortBoundScrollAnimation(); 419 420 mScrollAnimator = ObjectAnimator.ofInt(this, "stackScroll", curScroll, newScroll); 421 mScrollAnimator.setDuration(Utilities.calculateTranslationAnimationDuration(newScroll - 422 curScroll, 250)); 423 mScrollAnimator.setInterpolator(mConfig.fastOutSlowInInterpolator); 424 mScrollAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 425 @Override 426 public void onAnimationUpdate(ValueAnimator animation) { 427 setStackScroll((Integer) animation.getAnimatedValue()); 428 } 429 }); 430 mScrollAnimator.addListener(new AnimatorListenerAdapter() { 431 @Override 432 public void onAnimationEnd(Animator animation) { 433 if (postRunnable != null) { 434 postRunnable.run(); 435 } 436 mScrollAnimator.removeAllListeners(); 437 } 438 }); 439 mScrollAnimator.start(); 440 } 441 442 /** Aborts any current stack scrolls */ 443 void abortBoundScrollAnimation() { 444 if (mScrollAnimator != null) { 445 mScrollAnimator.cancel(); 446 } 447 } 448 449 /** Aborts the scroller and any current fling */ 450 void abortScroller() { 451 if (!mScroller.isFinished()) { 452 // Abort the scroller 453 mScroller.abortAnimation(); 454 // And disable hw layers on the stack 455 decHwLayersRefCount("flingScroll"); 456 } 457 } 458 459 /** Bounds the current scroll if necessary */ 460 public boolean boundScroll() { 461 int curScroll = getStackScroll(); 462 int newScroll = Math.max(mMinScroll, Math.min(mMaxScroll, curScroll)); 463 if (newScroll != curScroll) { 464 setStackScroll(newScroll); 465 return true; 466 } 467 return false; 468 } 469 470 /** 471 * Bounds the current scroll if necessary, but does not synchronize the stack view with the 472 * model. 473 */ 474 public boolean boundScrollRaw() { 475 int curScroll = getStackScroll(); 476 int newScroll = Math.max(mMinScroll, Math.min(mMaxScroll, curScroll)); 477 if (newScroll != curScroll) { 478 setStackScrollRaw(newScroll); 479 return true; 480 } 481 return false; 482 } 483 484 485 /** Returns the amount that the scroll is out of bounds */ 486 int getScrollAmountOutOfBounds(int scroll) { 487 if (scroll < mMinScroll) { 488 return mMinScroll - scroll; 489 } else if (scroll > mMaxScroll) { 490 return scroll - mMaxScroll; 491 } 492 return 0; 493 } 494 495 /** Returns whether the specified scroll is out of bounds */ 496 boolean isScrollOutOfBounds() { 497 return getScrollAmountOutOfBounds(getStackScroll()) != 0; 498 } 499 500 /** Returns whether the task view is in the stack bounds or not */ 501 boolean isTaskInStackBounds(TaskView tv) { 502 Rect r = new Rect(); 503 tv.getHitRect(r); 504 return r.bottom <= mRect.bottom; 505 } 506 507 /** Updates the min and max virtual scroll bounds */ 508 void updateMinMaxScroll(boolean boundScrollToNewMinMax) { 509 // Compute the min and max scroll values 510 int numTasks = Math.max(1, mStack.getTaskCount()); 511 int taskHeight = mTaskRect.height(); 512 int stackHeight = mStackRectSansPeek.height(); 513 int maxScrollHeight = taskHeight + (int) ((numTasks - 1) * 514 Constants.Values.TaskStackView.StackOverlapPct * taskHeight); 515 516 if (numTasks <= 1) { 517 // If there is only one task, then center the task in the stack rect (sans peek) 518 mMinScroll = mMaxScroll = -(stackHeight - taskHeight) / 2; 519 } else { 520 mMinScroll = Math.min(stackHeight, maxScrollHeight) - stackHeight; 521 mMaxScroll = maxScrollHeight - stackHeight; 522 } 523 524 // Debug logging 525 if (Constants.Log.UI.MeasureAndLayout) { 526 Console.log(" [TaskStack|minScroll] " + mMinScroll); 527 Console.log(" [TaskStack|maxScroll] " + mMaxScroll); 528 } 529 530 if (boundScrollToNewMinMax) { 531 boundScroll(); 532 } 533 } 534 535 /** Animates a task view in this stack as it launches. */ 536 public void animateOnLaunchingTask(TaskView tv, final Runnable r) { 537 // Hide each of the task bar dismiss buttons 538 int childCount = getChildCount(); 539 for (int i = 0; i < childCount; i++) { 540 TaskView t = (TaskView) getChildAt(i); 541 if (t == tv) { 542 t.startLaunchTaskAnimation(r, true); 543 } else { 544 t.startLaunchTaskAnimation(null, false); 545 } 546 } 547 } 548 549 /** Focuses the task at the specified index in the stack */ 550 void focusTask(int taskIndex, boolean scrollToNewPosition) { 551 if (Console.Enabled) { 552 Console.log(Constants.Log.UI.Focus, "[TaskStackView|focusTask]", "" + taskIndex); 553 } 554 if (0 <= taskIndex && taskIndex < mStack.getTaskCount()) { 555 mFocusedTaskIndex = taskIndex; 556 557 // Focus the view if possible, otherwise, focus the view after we scroll into position 558 Task t = mStack.getTasks().get(taskIndex); 559 TaskView tv = getChildViewForTask(t); 560 Runnable postScrollRunnable = null; 561 if (tv != null) { 562 tv.setFocusedTask(); 563 if (Console.Enabled) { 564 Console.log(Constants.Log.UI.Focus, "[TaskStackView|focusTask]", "Requesting focus"); 565 } 566 } else { 567 postScrollRunnable = new Runnable() { 568 @Override 569 public void run() { 570 Task t = mStack.getTasks().get(mFocusedTaskIndex); 571 TaskView tv = getChildViewForTask(t); 572 if (tv != null) { 573 tv.setFocusedTask(); 574 if (Console.Enabled) { 575 Console.log(Constants.Log.UI.Focus, "[TaskStackView|focusTask]", 576 "Requesting focus after scroll animation"); 577 } 578 } 579 } 580 }; 581 } 582 583 if (scrollToNewPosition) { 584 // Scroll the view into position 585 int newScroll = Math.max(mMinScroll, Math.min(mMaxScroll, 586 getStackScrollForTaskIndex(taskIndex))); 587 588 animateScroll(getStackScroll(), newScroll, postScrollRunnable); 589 } else { 590 if (postScrollRunnable != null) { 591 postScrollRunnable.run(); 592 } 593 } 594 } 595 } 596 597 /** Focuses the next task in the stack */ 598 void focusNextTask(boolean forward) { 599 if (Console.Enabled) { 600 Console.log(Constants.Log.UI.Focus, "[TaskStackView|focusNextTask]", "" + 601 mFocusedTaskIndex); 602 } 603 604 // Find the next index to focus 605 int numTasks = mStack.getTaskCount(); 606 if (mFocusedTaskIndex < 0) { 607 mFocusedTaskIndex = numTasks - 1; 608 } 609 if (0 <= mFocusedTaskIndex && mFocusedTaskIndex < numTasks) { 610 mFocusedTaskIndex = Math.max(0, Math.min(numTasks - 1, 611 mFocusedTaskIndex + (forward ? -1 : 1))); 612 } 613 focusTask(mFocusedTaskIndex, true); 614 } 615 616 /** Enables the hw layers and increments the hw layer requirement ref count */ 617 void addHwLayersRefCount(String reason) { 618 if (Console.Enabled) { 619 int refCount = mHwLayersTrigger.getCount(); 620 Console.log(Constants.Log.UI.HwLayers, 621 "[TaskStackView|addHwLayersRefCount] refCount: " + 622 refCount + "->" + (refCount + 1) + " " + reason); 623 } 624 mHwLayersTrigger.increment(); 625 } 626 627 /** Decrements the hw layer requirement ref count and disables the hw layers when we don't 628 need them anymore. */ 629 void decHwLayersRefCount(String reason) { 630 if (Console.Enabled) { 631 int refCount = mHwLayersTrigger.getCount(); 632 Console.log(Constants.Log.UI.HwLayers, 633 "[TaskStackView|decHwLayersRefCount] refCount: " + 634 refCount + "->" + (refCount - 1) + " " + reason); 635 } 636 mHwLayersTrigger.decrement(); 637 } 638 639 @Override 640 public void computeScroll() { 641 if (mScroller.computeScrollOffset()) { 642 setStackScroll(mScroller.getCurrY()); 643 invalidate(mStackRect); 644 645 // If we just finished scrolling, then disable the hw layers 646 if (mScroller.isFinished()) { 647 decHwLayersRefCount("finishedFlingScroll"); 648 } 649 } 650 } 651 652 @Override 653 public boolean onInterceptTouchEvent(MotionEvent ev) { 654 return mTouchHandler.onInterceptTouchEvent(ev); 655 } 656 657 @Override 658 public boolean onTouchEvent(MotionEvent ev) { 659 return mTouchHandler.onTouchEvent(ev); 660 } 661 662 @Override 663 public void dispatchDraw(Canvas canvas) { 664 if (Console.Enabled) { 665 Console.log(Constants.Log.UI.Draw, "[TaskStackView|dispatchDraw]", "", 666 Console.AnsiPurple); 667 } 668 synchronizeStackViewsWithModel(); 669 super.dispatchDraw(canvas); 670 } 671 672 @Override 673 protected boolean drawChild(Canvas canvas, View child, long drawingTime) { 674 if (Constants.DebugFlags.App.EnableTaskStackClipping) { 675 TaskView tv = (TaskView) child; 676 TaskView nextTv = null; 677 TaskView tmpTv = null; 678 if (tv.shouldClipViewInStack()) { 679 int curIndex = indexOfChild(tv); 680 681 // Find the next view to clip against 682 while (nextTv == null && curIndex < getChildCount()) { 683 tmpTv = (TaskView) getChildAt(++curIndex); 684 if (tmpTv != null && tmpTv.shouldClipViewInStack()) { 685 nextTv = tmpTv; 686 } 687 } 688 689 // Clip against the next view (if we aren't animating its alpha) 690 if (nextTv != null) { 691 Rect curRect = tv.getClippingRect(mTmpRect); 692 Rect nextRect = nextTv.getClippingRect(mTmpRect2); 693 // The hit rects are relative to the task view, which needs to be offset by 694 // the system bar height 695 curRect.offset(0, mConfig.systemInsets.top); 696 nextRect.offset(0, mConfig.systemInsets.top); 697 // Compute the clip region 698 Region clipRegion = new Region(); 699 clipRegion.op(curRect, Region.Op.UNION); 700 clipRegion.op(nextRect, Region.Op.DIFFERENCE); 701 // Clip the canvas 702 int saveCount = canvas.save(Canvas.CLIP_SAVE_FLAG); 703 canvas.clipRegion(clipRegion); 704 boolean invalidate = super.drawChild(canvas, child, drawingTime); 705 canvas.restoreToCount(saveCount); 706 return invalidate; 707 } 708 } 709 } 710 return super.drawChild(canvas, child, drawingTime); 711 } 712 713 /** Computes the stack and task rects */ 714 public void computeRects(int width, int height, int insetLeft, int insetBottom) { 715 // Note: We let the stack view be the full height because we want the cards to go under the 716 // navigation bar if possible. However, the stack rects which we use to calculate 717 // max scroll, etc. need to take the nav bar into account 718 719 // Compute the stack rects 720 mRect.set(0, 0, width, height); 721 mStackRect.set(mRect); 722 mStackRect.left += insetLeft; 723 mStackRect.bottom -= insetBottom; 724 725 int widthPadding = (int) (mConfig.taskStackWidthPaddingPct * mStackRect.width()); 726 int heightPadding = mConfig.taskStackTopPaddingPx; 727 if (Constants.DebugFlags.App.EnableSearchLayout) { 728 mStackRect.top += heightPadding; 729 mStackRect.left += widthPadding; 730 mStackRect.right -= widthPadding; 731 mStackRect.bottom -= heightPadding; 732 } else { 733 mStackRect.inset(widthPadding, heightPadding); 734 } 735 mStackRectSansPeek.set(mStackRect); 736 mStackRectSansPeek.top += Constants.Values.TaskStackView.StackPeekHeightPct * mStackRect.height(); 737 738 // Compute the task rect 739 int size = mStackRect.width(); 740 int left = mStackRect.left + (mStackRect.width() - size) / 2; 741 mTaskRect.set(left, mStackRectSansPeek.top, 742 left + size, mStackRectSansPeek.top + size); 743 744 // Update the scroll bounds 745 updateMinMaxScroll(false); 746 } 747 748 /** 749 * This is called with the size of the space not including the top or right insets, or the 750 * search bar height in portrait (but including the search bar width in landscape, since we want 751 * to draw under it. 752 */ 753 @Override 754 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 755 int width = MeasureSpec.getSize(widthMeasureSpec); 756 int height = MeasureSpec.getSize(heightMeasureSpec); 757 if (Console.Enabled) { 758 Console.log(Constants.Log.UI.MeasureAndLayout, "[TaskStackView|measure]", 759 "width: " + width + " height: " + height + 760 " awaitingFirstLayout: " + mAwaitingFirstLayout, Console.AnsiGreen); 761 } 762 763 // Compute our stack/task rects 764 Rect taskStackBounds = new Rect(); 765 mConfig.getTaskStackBounds(width, height, taskStackBounds); 766 computeRects(width, height, taskStackBounds.left, mConfig.systemInsets.bottom); 767 768 // Debug logging 769 if (Constants.Log.UI.MeasureAndLayout) { 770 Console.log(" [TaskStack|fullRect] " + mRect); 771 Console.log(" [TaskStack|stackRect] " + mStackRect); 772 Console.log(" [TaskStack|stackRectSansPeek] " + mStackRectSansPeek); 773 Console.log(" [TaskStack|taskRect] " + mTaskRect); 774 } 775 776 // If this is the first layout, then scroll to the front of the stack and synchronize the 777 // stack views immediately 778 if (mAwaitingFirstLayout) { 779 setStackScrollToInitialState(); 780 requestSynchronizeStackViewsWithModel(); 781 synchronizeStackViewsWithModel(); 782 } 783 784 // Measure each of the children 785 int childCount = getChildCount(); 786 for (int i = 0; i < childCount; i++) { 787 TaskView t = (TaskView) getChildAt(i); 788 t.measure(MeasureSpec.makeMeasureSpec(mTaskRect.width(), MeasureSpec.EXACTLY), 789 MeasureSpec.makeMeasureSpec(mTaskRect.height(), MeasureSpec.EXACTLY)); 790 } 791 792 setMeasuredDimension(width, height); 793 } 794 795 /** 796 * This is called with the size of the space not including the top or right insets, or the 797 * search bar height in portrait (but including the search bar width in landscape, since we want 798 * to draw under it. 799 */ 800 @Override 801 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 802 if (Console.Enabled) { 803 Console.log(Constants.Log.UI.MeasureAndLayout, "[TaskStackView|layout]", 804 "" + new Rect(left, top, right, bottom), Console.AnsiGreen); 805 } 806 807 // Debug logging 808 if (Constants.Log.UI.MeasureAndLayout) { 809 Console.log(" [TaskStack|fullRect] " + mRect); 810 Console.log(" [TaskStack|stackRect] " + mStackRect); 811 Console.log(" [TaskStack|stackRectSansPeek] " + mStackRectSansPeek); 812 Console.log(" [TaskStack|taskRect] " + mTaskRect); 813 } 814 815 // Layout each of the children 816 int childCount = getChildCount(); 817 for (int i = 0; i < childCount; i++) { 818 TaskView t = (TaskView) getChildAt(i); 819 t.layout(mTaskRect.left, mStackRectSansPeek.top, 820 mTaskRect.right, mStackRectSansPeek.top + mTaskRect.height()); 821 } 822 823 if (mAwaitingFirstLayout) { 824 // Mark that we have completely the first layout 825 mAwaitingFirstLayout = false; 826 827 // Start dozing 828 mDozeTrigger.startDozing(); 829 830 // Prepare the first view for its enter animation 831 int offsetTopAlign = -mTaskRect.top; 832 int offscreenY = mRect.bottom - (mTaskRect.top - mRect.top); 833 for (int i = childCount - 1; i >= 0; i--) { 834 TaskView tv = (TaskView) getChildAt(i); 835 tv.prepareEnterRecentsAnimation((i == (getChildCount() - 1)), offsetTopAlign, 836 offscreenY, mTaskRect); 837 } 838 839 // If the enter animation started already and we haven't completed a layout yet, do the 840 // enter animation now 841 if (mStartEnterAnimationRequestedAfterLayout) { 842 startEnterRecentsAnimation(mStartEnterAnimationContext); 843 mStartEnterAnimationRequestedAfterLayout = false; 844 mStartEnterAnimationContext = null; 845 } 846 847 // Update the focused task index to be the next item to the top task 848 if (mConfig.launchedWithAltTab) { 849 focusTask(Math.max(0, mStack.getTaskCount() - 2), false); 850 } 851 } 852 } 853 854 /** Requests this task stacks to start it's enter-recents animation */ 855 public void startEnterRecentsAnimation(ViewAnimation.TaskViewEnterContext ctx) { 856 // If we are still waiting to layout, then just defer until then 857 if (mAwaitingFirstLayout) { 858 mStartEnterAnimationRequestedAfterLayout = true; 859 mStartEnterAnimationContext = ctx; 860 return; 861 } 862 863 // Animate all the task views into view 864 ctx.taskRect = mTaskRect; 865 ctx.stackRectSansPeek = mStackRectSansPeek; 866 int childCount = getChildCount(); 867 for (int i = childCount - 1; i >= 0; i--) { 868 TaskView tv = (TaskView) getChildAt(i); 869 TaskViewTransform transform = getStackTransform(mStack.indexOfTask(tv.getTask()), 870 getStackScroll()); 871 ctx.stackViewIndex = i; 872 ctx.stackViewCount = childCount; 873 ctx.isFrontMost = (i == (getChildCount() - 1)); 874 ctx.transform = transform; 875 tv.startEnterRecentsAnimation(ctx); 876 } 877 } 878 879 /** Requests this task stacks to start it's exit-recents animation. */ 880 public void startExitToHomeAnimation(ViewAnimation.TaskViewExitContext ctx) { 881 // Animate all the task views into view 882 ctx.offscreenTranslationY = mRect.bottom - (mTaskRect.top - mRect.top); 883 int childCount = getChildCount(); 884 for (int i = 0; i < childCount; i++) { 885 TaskView tv = (TaskView) getChildAt(i); 886 tv.startExitToHomeAnimation(ctx); 887 } 888 } 889 890 @Override 891 protected void onScrollChanged(int l, int t, int oldl, int oldt) { 892 super.onScrollChanged(l, t, oldl, oldt); 893 requestSynchronizeStackViewsWithModel(); 894 } 895 896 public boolean isTransformedTouchPointInView(float x, float y, View child) { 897 return isTransformedTouchPointInView(x, y, child, null); 898 } 899 900 /** Pokes the dozer on user interaction. */ 901 void onUserInteraction() { 902 // If the dozer is not running, then either we have not yet laid out, or it has already 903 // fallen asleep, so just let it rest. 904 if (mDozeTrigger.isDozing()) { 905 mDozeTrigger.poke(); 906 } 907 } 908 909 /**** TaskStackCallbacks Implementation ****/ 910 911 @Override 912 public void onStackTaskAdded(TaskStack stack, Task t) { 913 requestSynchronizeStackViewsWithModel(); 914 } 915 916 @Override 917 public void onStackTaskRemoved(TaskStack stack, Task t) { 918 // Remove the view associated with this task, we can't rely on updateTransforms 919 // to work here because the task is no longer in the list 920 int childCount = getChildCount(); 921 for (int i = childCount - 1; i >= 0; i--) { 922 TaskView tv = (TaskView) getChildAt(i); 923 if (tv.getTask() == t) { 924 mViewPool.returnViewToPool(tv); 925 break; 926 } 927 } 928 929 // Update the min/max scroll and animate other task views into their new positions 930 updateMinMaxScroll(true); 931 int movement = (int) (Constants.Values.TaskStackView.StackOverlapPct * mTaskRect.height()); 932 requestSynchronizeStackViewsWithModel(Utilities.calculateTranslationAnimationDuration(movement)); 933 934 // If there are no remaining tasks, then either unfilter the current stack, or just close 935 // the activity if there are no filtered stacks 936 if (mStack.getTaskCount() == 0) { 937 boolean shouldFinishActivity = true; 938 if (mStack.hasFilteredTasks()) { 939 mStack.unfilterTasks(); 940 shouldFinishActivity = (mStack.getTaskCount() == 0); 941 } 942 if (shouldFinishActivity) { 943 Activity activity = (Activity) getContext(); 944 activity.finish(); 945 } 946 } 947 } 948 949 /** 950 * Creates the animations for all the children views that need to be removed or to move views 951 * to their un/filtered position when we are un/filtering a stack, and returns the duration 952 * for these animations. 953 */ 954 int getExitTransformsForFilterAnimation(ArrayList<Task> curTasks, 955 ArrayList<TaskViewTransform> curTaskTransforms, 956 ArrayList<Task> tasks, ArrayList<TaskViewTransform> taskTransforms, 957 HashMap<TaskView, Pair<Integer, TaskViewTransform>> childViewTransformsOut, 958 ArrayList<TaskView> childrenToRemoveOut) { 959 // Animate all of the existing views out of view (if they are not in the visible range in 960 // the new stack) or to their final positions in the new stack 961 int offset = 0; 962 int movement = 0; 963 int childCount = getChildCount(); 964 for (int i = 0; i < childCount; i++) { 965 TaskView tv = (TaskView) getChildAt(i); 966 Task task = tv.getTask(); 967 int taskIndex = tasks.indexOf(task); 968 TaskViewTransform toTransform; 969 970 // If the view is no longer visible, then we should just animate it out 971 boolean willBeInvisible = taskIndex < 0 || !taskTransforms.get(taskIndex).visible; 972 if (willBeInvisible) { 973 if (taskIndex < 0) { 974 toTransform = curTaskTransforms.get(curTasks.indexOf(task)); 975 } else { 976 toTransform = new TaskViewTransform(taskTransforms.get(taskIndex)); 977 } 978 tv.prepareTaskTransformForFilterTaskVisible(toTransform); 979 childrenToRemoveOut.add(tv); 980 } else { 981 toTransform = taskTransforms.get(taskIndex); 982 // Use the movement of the visible views to calculate the duration of the animation 983 movement = Math.max(movement, Math.abs(toTransform.translationY - 984 (int) tv.getTranslationY())); 985 } 986 987 int startDelay = offset * 988 Constants.Values.TaskStackView.FilterStartDelay; 989 childViewTransformsOut.put(tv, new Pair(startDelay, toTransform)); 990 offset++; 991 } 992 return mConfig.filteringCurrentViewsAnimDuration; 993 } 994 995 /** 996 * Creates the animations for all the children views that need to be animated in when we are 997 * un/filtering a stack, and returns the duration for these animations. 998 */ 999 int getEnterTransformsForFilterAnimation(ArrayList<Task> tasks, 1000 ArrayList<TaskViewTransform> taskTransforms, 1001 HashMap<TaskView, Pair<Integer, TaskViewTransform>> childViewTransformsOut) { 1002 int offset = 0; 1003 int movement = 0; 1004 int taskCount = tasks.size(); 1005 for (int i = taskCount - 1; i >= 0; i--) { 1006 Task task = tasks.get(i); 1007 TaskViewTransform toTransform = taskTransforms.get(i); 1008 if (toTransform.visible) { 1009 TaskView tv = getChildViewForTask(task); 1010 if (tv == null) { 1011 // For views that are not already visible, animate them in 1012 tv = mViewPool.pickUpViewFromPool(task, task); 1013 1014 // Compose a new transform to fade and slide the new task in 1015 TaskViewTransform fromTransform = new TaskViewTransform(toTransform); 1016 tv.prepareTaskTransformForFilterTaskHidden(fromTransform); 1017 tv.updateViewPropertiesToTaskTransform(fromTransform, 0); 1018 1019 int startDelay = offset * 1020 Constants.Values.TaskStackView.FilterStartDelay; 1021 childViewTransformsOut.put(tv, new Pair(startDelay, toTransform)); 1022 1023 // Use the movement of the new views to calculate the duration of the animation 1024 movement = Math.max(movement, 1025 Math.abs(toTransform.translationY - fromTransform.translationY)); 1026 offset++; 1027 } 1028 } 1029 } 1030 return mConfig.filteringNewViewsAnimDuration; 1031 } 1032 1033 /** Orchestrates the animations of the current child views and any new views. */ 1034 void doFilteringAnimation(ArrayList<Task> curTasks, 1035 ArrayList<TaskViewTransform> curTaskTransforms, 1036 final ArrayList<Task> tasks, 1037 final ArrayList<TaskViewTransform> taskTransforms) { 1038 // Calculate the transforms to animate out all the existing views if they are not in the 1039 // new visible range (or to their final positions in the stack if they are) 1040 final ArrayList<TaskView> childrenToRemove = new ArrayList<TaskView>(); 1041 final HashMap<TaskView, Pair<Integer, TaskViewTransform>> childViewTransforms = 1042 new HashMap<TaskView, Pair<Integer, TaskViewTransform>>(); 1043 int duration = getExitTransformsForFilterAnimation(curTasks, curTaskTransforms, tasks, 1044 taskTransforms, childViewTransforms, childrenToRemove); 1045 1046 // If all the current views are in the visible range of the new stack, then don't wait for 1047 // views to animate out and animate all the new views into their place 1048 final boolean unifyNewViewAnimation = childrenToRemove.isEmpty(); 1049 if (unifyNewViewAnimation) { 1050 int inDuration = getEnterTransformsForFilterAnimation(tasks, taskTransforms, 1051 childViewTransforms); 1052 duration = Math.max(duration, inDuration); 1053 } 1054 1055 // Animate all the views to their final transforms 1056 for (final TaskView tv : childViewTransforms.keySet()) { 1057 Pair<Integer, TaskViewTransform> t = childViewTransforms.get(tv); 1058 tv.animate().cancel(); 1059 tv.animate() 1060 .setStartDelay(t.first) 1061 .withEndAction(new Runnable() { 1062 @Override 1063 public void run() { 1064 childViewTransforms.remove(tv); 1065 if (childViewTransforms.isEmpty()) { 1066 // Return all the removed children to the view pool 1067 for (TaskView tv : childrenToRemove) { 1068 mViewPool.returnViewToPool(tv); 1069 } 1070 1071 if (!unifyNewViewAnimation) { 1072 // For views that are not already visible, animate them in 1073 childViewTransforms.clear(); 1074 int duration = getEnterTransformsForFilterAnimation(tasks, 1075 taskTransforms, childViewTransforms); 1076 for (final TaskView tv : childViewTransforms.keySet()) { 1077 Pair<Integer, TaskViewTransform> t = childViewTransforms.get(tv); 1078 tv.animate().setStartDelay(t.first); 1079 tv.updateViewPropertiesToTaskTransform(t.second, duration); 1080 } 1081 } 1082 } 1083 } 1084 }); 1085 tv.updateViewPropertiesToTaskTransform(t.second, duration); 1086 } 1087 } 1088 1089 @Override 1090 public void onStackFiltered(TaskStack newStack, final ArrayList<Task> curTasks, 1091 Task filteredTask) { 1092 // Stash the scroll and filtered task for us to restore to when we unfilter 1093 mStashedScroll = getStackScroll(); 1094 1095 // Calculate the current task transforms 1096 ArrayList<TaskViewTransform> curTaskTransforms = 1097 getStackTransforms(curTasks, getStackScroll(), null, true); 1098 1099 // Scroll the item to the top of the stack (sans-peek) rect so that we can see it better 1100 updateMinMaxScroll(false); 1101 float overlapHeight = Constants.Values.TaskStackView.StackOverlapPct * mTaskRect.height(); 1102 setStackScrollRaw((int) (newStack.indexOfTask(filteredTask) * overlapHeight)); 1103 boundScrollRaw(); 1104 1105 // Compute the transforms of the items in the new stack after setting the new scroll 1106 final ArrayList<Task> tasks = mStack.getTasks(); 1107 final ArrayList<TaskViewTransform> taskTransforms = 1108 getStackTransforms(mStack.getTasks(), getStackScroll(), null, true); 1109 1110 // Animate 1111 doFilteringAnimation(curTasks, curTaskTransforms, tasks, taskTransforms); 1112 } 1113 1114 @Override 1115 public void onStackUnfiltered(TaskStack newStack, final ArrayList<Task> curTasks) { 1116 // Calculate the current task transforms 1117 final ArrayList<TaskViewTransform> curTaskTransforms = 1118 getStackTransforms(curTasks, getStackScroll(), null, true); 1119 1120 // Restore the stashed scroll 1121 updateMinMaxScroll(false); 1122 setStackScrollRaw(mStashedScroll); 1123 boundScrollRaw(); 1124 1125 // Compute the transforms of the items in the new stack after restoring the stashed scroll 1126 final ArrayList<Task> tasks = mStack.getTasks(); 1127 final ArrayList<TaskViewTransform> taskTransforms = 1128 getStackTransforms(tasks, getStackScroll(), null, true); 1129 1130 // Animate 1131 doFilteringAnimation(curTasks, curTaskTransforms, tasks, taskTransforms); 1132 1133 // Clear the saved vars 1134 mStashedScroll = 0; 1135 } 1136 1137 /**** ViewPoolConsumer Implementation ****/ 1138 1139 @Override 1140 public TaskView createView(Context context) { 1141 if (Console.Enabled) { 1142 Console.log(Constants.Log.ViewPool.PoolCallbacks, "[TaskStackView|createPoolView]"); 1143 } 1144 return (TaskView) mInflater.inflate(R.layout.recents_task_view, this, false); 1145 } 1146 1147 @Override 1148 public void prepareViewToEnterPool(TaskView tv) { 1149 Task task = tv.getTask(); 1150 tv.resetViewProperties(); 1151 if (Console.Enabled) { 1152 Console.log(Constants.Log.ViewPool.PoolCallbacks, "[TaskStackView|returnToPool]", 1153 tv.getTask() + " tv: " + tv); 1154 } 1155 1156 // Report that this tasks's data is no longer being used 1157 RecentsTaskLoader loader = RecentsTaskLoader.getInstance(); 1158 loader.unloadTaskData(task); 1159 1160 // Detach the view from the hierarchy 1161 detachViewFromParent(tv); 1162 1163 // Disable hw layers on this view 1164 tv.disableHwLayers(); 1165 } 1166 1167 @Override 1168 public void prepareViewToLeavePool(TaskView tv, Task prepareData, boolean isNewView) { 1169 if (Console.Enabled) { 1170 Console.log(Constants.Log.ViewPool.PoolCallbacks, "[TaskStackView|leavePool]", 1171 "isNewView: " + isNewView); 1172 } 1173 1174 // Setup and attach the view to the window 1175 Task task = prepareData; 1176 // We try and rebind the task (this MUST be done before the task filled) 1177 tv.onTaskBound(task); 1178 // Request that this tasks's data be filled 1179 RecentsTaskLoader loader = RecentsTaskLoader.getInstance(); 1180 loader.loadTaskData(task); 1181 1182 // Find the index where this task should be placed in the children 1183 int insertIndex = -1; 1184 int childCount = getChildCount(); 1185 for (int i = 0; i < childCount; i++) { 1186 Task tvTask = ((TaskView) getChildAt(i)).getTask(); 1187 if (mStack.containsTask(task) && (mStack.indexOfTask(task) < mStack.indexOfTask(tvTask))) { 1188 insertIndex = i; 1189 break; 1190 } 1191 } 1192 1193 // Sanity check, the task view should always be clipping against the stack at this point, 1194 // but just in case, re-enable it here 1195 tv.setClipViewInStack(true); 1196 1197 // If the doze trigger has already fired, then update the state for this task view 1198 if (mDozeTrigger.hasTriggered()) { 1199 tv.setNoUserInteractionState(); 1200 } 1201 1202 // Add/attach the view to the hierarchy 1203 if (Console.Enabled) { 1204 Console.log(Constants.Log.ViewPool.PoolCallbacks, " [TaskStackView|insertIndex]", 1205 "" + insertIndex); 1206 } 1207 if (isNewView) { 1208 addView(tv, insertIndex); 1209 1210 // Set the callbacks and listeners for this new view 1211 tv.setOnClickListener(this); 1212 tv.setCallbacks(this); 1213 } else { 1214 attachViewToParent(tv, insertIndex, tv.getLayoutParams()); 1215 } 1216 1217 // Enable hw layers on this view if hw layers are enabled on the stack 1218 if (mHwLayersTrigger.getCount() > 0) { 1219 tv.enableHwLayers(); 1220 } 1221 } 1222 1223 @Override 1224 public boolean hasPreferredData(TaskView tv, Task preferredData) { 1225 return (tv.getTask() == preferredData); 1226 } 1227 1228 /**** TaskViewCallbacks Implementation ****/ 1229 1230 @Override 1231 public void onTaskIconClicked(TaskView tv) { 1232 if (Console.Enabled) { 1233 Console.log(Constants.Log.UI.ClickEvents, "[TaskStack|Clicked|Icon]", 1234 tv.getTask() + " is currently filtered: " + mStack.hasFilteredTasks(), 1235 Console.AnsiCyan); 1236 } 1237 if (Constants.DebugFlags.App.EnableTaskFiltering) { 1238 if (mStack.hasFilteredTasks()) { 1239 mStack.unfilterTasks(); 1240 } else { 1241 mStack.filterTasks(tv.getTask()); 1242 } 1243 } 1244 } 1245 1246 @Override 1247 public void onTaskAppInfoClicked(TaskView tv) { 1248 if (mCb != null) { 1249 mCb.onTaskAppInfoLaunched(tv.getTask()); 1250 } 1251 } 1252 1253 @Override 1254 public void onTaskFocused(TaskView tv) { 1255 // Do nothing 1256 } 1257 1258 @Override 1259 public void onTaskDismissed(TaskView tv) { 1260 Task task = tv.getTask(); 1261 // Remove the task from the view 1262 mStack.removeTask(task); 1263 // Notify the callback that we've removed the task and it can clean up after it 1264 mCb.onTaskRemoved(task); 1265 } 1266 1267 /**** View.OnClickListener Implementation ****/ 1268 1269 @Override 1270 public void onClick(View v) { 1271 TaskView tv = (TaskView) v; 1272 Task task = tv.getTask(); 1273 if (Console.Enabled) { 1274 Console.log(Constants.Log.UI.ClickEvents, "[TaskStack|Clicked|Thumbnail]", 1275 task + " cb: " + mCb); 1276 } 1277 1278 if (mCb != null) { 1279 mCb.onTaskLaunched(this, tv, mStack, task); 1280 } 1281 } 1282 1283 /**** RecentsPackageMonitor.PackageCallbacks Implementation ****/ 1284 1285 @Override 1286 public void onComponentRemoved(Set<ComponentName> cns) { 1287 // For other tasks, just remove them directly if they no longer exist 1288 ArrayList<Task> tasks = mStack.getTasks(); 1289 for (int i = tasks.size() - 1; i >= 0; i--) { 1290 final Task t = tasks.get(i); 1291 if (cns.contains(t.key.baseIntent.getComponent())) { 1292 TaskView tv = getChildViewForTask(t); 1293 if (tv != null) { 1294 // For visible children, defer removing the task until after the animation 1295 tv.startDeleteTaskAnimation(new Runnable() { 1296 @Override 1297 public void run() { 1298 mStack.removeTask(t); 1299 } 1300 }); 1301 } else { 1302 // Otherwise, remove the task from the stack immediately 1303 mStack.removeTask(t); 1304 } 1305 } 1306 } 1307 } 1308} 1309 1310/* Handles touch events */ 1311class TaskStackViewTouchHandler implements SwipeHelper.Callback { 1312 static int INACTIVE_POINTER_ID = -1; 1313 1314 TaskStackView mSv; 1315 VelocityTracker mVelocityTracker; 1316 1317 boolean mIsScrolling; 1318 1319 int mInitialMotionX, mInitialMotionY; 1320 int mLastMotionX, mLastMotionY; 1321 int mActivePointerId = INACTIVE_POINTER_ID; 1322 TaskView mActiveTaskView = null; 1323 1324 int mTotalScrollMotion; 1325 int mMinimumVelocity; 1326 int mMaximumVelocity; 1327 // The scroll touch slop is used to calculate when we start scrolling 1328 int mScrollTouchSlop; 1329 // The page touch slop is used to calculate when we start swiping 1330 float mPagingTouchSlop; 1331 1332 SwipeHelper mSwipeHelper; 1333 boolean mInterceptedBySwipeHelper; 1334 1335 public TaskStackViewTouchHandler(Context context, TaskStackView sv) { 1336 ViewConfiguration configuration = ViewConfiguration.get(context); 1337 mMinimumVelocity = configuration.getScaledMinimumFlingVelocity(); 1338 mMaximumVelocity = configuration.getScaledMaximumFlingVelocity(); 1339 mScrollTouchSlop = configuration.getScaledTouchSlop(); 1340 mPagingTouchSlop = configuration.getScaledPagingTouchSlop(); 1341 mSv = sv; 1342 1343 1344 float densityScale = context.getResources().getDisplayMetrics().density; 1345 mSwipeHelper = new SwipeHelper(SwipeHelper.X, this, densityScale, mPagingTouchSlop); 1346 mSwipeHelper.setMinAlpha(1f); 1347 } 1348 1349 /** Velocity tracker helpers */ 1350 void initOrResetVelocityTracker() { 1351 if (mVelocityTracker == null) { 1352 mVelocityTracker = VelocityTracker.obtain(); 1353 } else { 1354 mVelocityTracker.clear(); 1355 } 1356 } 1357 void initVelocityTrackerIfNotExists() { 1358 if (mVelocityTracker == null) { 1359 mVelocityTracker = VelocityTracker.obtain(); 1360 } 1361 } 1362 void recycleVelocityTracker() { 1363 if (mVelocityTracker != null) { 1364 mVelocityTracker.recycle(); 1365 mVelocityTracker = null; 1366 } 1367 } 1368 1369 /** Returns the view at the specified coordinates */ 1370 TaskView findViewAtPoint(int x, int y) { 1371 int childCount = mSv.getChildCount(); 1372 for (int i = childCount - 1; i >= 0; i--) { 1373 TaskView tv = (TaskView) mSv.getChildAt(i); 1374 if (tv.getVisibility() == View.VISIBLE) { 1375 if (mSv.isTransformedTouchPointInView(x, y, tv)) { 1376 return tv; 1377 } 1378 } 1379 } 1380 return null; 1381 } 1382 1383 /** Touch preprocessing for handling below */ 1384 public boolean onInterceptTouchEvent(MotionEvent ev) { 1385 if (Console.Enabled) { 1386 Console.log(Constants.Log.UI.TouchEvents, 1387 "[TaskStackViewTouchHandler|interceptTouchEvent]", 1388 Console.motionEventActionToString(ev.getAction()), Console.AnsiBlue); 1389 } 1390 1391 // Return early if we have no children 1392 boolean hasChildren = (mSv.getChildCount() > 0); 1393 if (!hasChildren) { 1394 return false; 1395 } 1396 1397 // Pass through to swipe helper if we are swiping 1398 mInterceptedBySwipeHelper = mSwipeHelper.onInterceptTouchEvent(ev); 1399 if (mInterceptedBySwipeHelper) { 1400 return true; 1401 } 1402 1403 boolean wasScrolling = !mSv.mScroller.isFinished() || 1404 (mSv.mScrollAnimator != null && mSv.mScrollAnimator.isRunning()); 1405 int action = ev.getAction(); 1406 switch (action & MotionEvent.ACTION_MASK) { 1407 case MotionEvent.ACTION_DOWN: { 1408 // Save the touch down info 1409 mInitialMotionX = mLastMotionX = (int) ev.getX(); 1410 mInitialMotionY = mLastMotionY = (int) ev.getY(); 1411 mActivePointerId = ev.getPointerId(0); 1412 mActiveTaskView = findViewAtPoint(mLastMotionX, mLastMotionY); 1413 // Stop the current scroll if it is still flinging 1414 mSv.abortScroller(); 1415 mSv.abortBoundScrollAnimation(); 1416 // Initialize the velocity tracker 1417 initOrResetVelocityTracker(); 1418 mVelocityTracker.addMovement(ev); 1419 // Check if the scroller is finished yet 1420 mIsScrolling = !mSv.mScroller.isFinished(); 1421 break; 1422 } 1423 case MotionEvent.ACTION_MOVE: { 1424 if (mActivePointerId == INACTIVE_POINTER_ID) break; 1425 1426 int activePointerIndex = ev.findPointerIndex(mActivePointerId); 1427 int y = (int) ev.getY(activePointerIndex); 1428 int x = (int) ev.getX(activePointerIndex); 1429 if (Math.abs(y - mInitialMotionY) > mScrollTouchSlop) { 1430 // Save the touch move info 1431 mIsScrolling = true; 1432 // Initialize the velocity tracker if necessary 1433 initVelocityTrackerIfNotExists(); 1434 mVelocityTracker.addMovement(ev); 1435 // Disallow parents from intercepting touch events 1436 final ViewParent parent = mSv.getParent(); 1437 if (parent != null) { 1438 parent.requestDisallowInterceptTouchEvent(true); 1439 } 1440 // Enable HW layers 1441 mSv.addHwLayersRefCount("stackScroll"); 1442 } 1443 1444 mLastMotionX = x; 1445 mLastMotionY = y; 1446 break; 1447 } 1448 case MotionEvent.ACTION_CANCEL: 1449 case MotionEvent.ACTION_UP: { 1450 // Animate the scroll back if we've cancelled 1451 mSv.animateBoundScroll(); 1452 // Disable HW layers 1453 if (mIsScrolling) { 1454 mSv.decHwLayersRefCount("stackScroll"); 1455 } 1456 // Reset the drag state and the velocity tracker 1457 mIsScrolling = false; 1458 mActivePointerId = INACTIVE_POINTER_ID; 1459 mActiveTaskView = null; 1460 mTotalScrollMotion = 0; 1461 recycleVelocityTracker(); 1462 break; 1463 } 1464 } 1465 1466 return wasScrolling || mIsScrolling; 1467 } 1468 1469 /** Handles touch events once we have intercepted them */ 1470 public boolean onTouchEvent(MotionEvent ev) { 1471 if (Console.Enabled) { 1472 Console.log(Constants.Log.UI.TouchEvents, 1473 "[TaskStackViewTouchHandler|touchEvent]", 1474 Console.motionEventActionToString(ev.getAction()), Console.AnsiBlue); 1475 } 1476 1477 // Short circuit if we have no children 1478 boolean hasChildren = (mSv.getChildCount() > 0); 1479 if (!hasChildren) { 1480 return false; 1481 } 1482 1483 // Pass through to swipe helper if we are swiping 1484 if (mInterceptedBySwipeHelper && mSwipeHelper.onTouchEvent(ev)) { 1485 return true; 1486 } 1487 1488 // Update the velocity tracker 1489 initVelocityTrackerIfNotExists(); 1490 mVelocityTracker.addMovement(ev); 1491 1492 int action = ev.getAction(); 1493 switch (action & MotionEvent.ACTION_MASK) { 1494 case MotionEvent.ACTION_DOWN: { 1495 // Save the touch down info 1496 mInitialMotionX = mLastMotionX = (int) ev.getX(); 1497 mInitialMotionY = mLastMotionY = (int) ev.getY(); 1498 mActivePointerId = ev.getPointerId(0); 1499 mActiveTaskView = findViewAtPoint(mLastMotionX, mLastMotionY); 1500 // Stop the current scroll if it is still flinging 1501 mSv.abortScroller(); 1502 mSv.abortBoundScrollAnimation(); 1503 // Initialize the velocity tracker 1504 initOrResetVelocityTracker(); 1505 mVelocityTracker.addMovement(ev); 1506 // Disallow parents from intercepting touch events 1507 final ViewParent parent = mSv.getParent(); 1508 if (parent != null) { 1509 parent.requestDisallowInterceptTouchEvent(true); 1510 } 1511 break; 1512 } 1513 case MotionEvent.ACTION_POINTER_DOWN: { 1514 final int index = ev.getActionIndex(); 1515 mActivePointerId = ev.getPointerId(index); 1516 mLastMotionX = (int) ev.getX(index); 1517 mLastMotionY = (int) ev.getY(index); 1518 break; 1519 } 1520 case MotionEvent.ACTION_MOVE: { 1521 if (mActivePointerId == INACTIVE_POINTER_ID) break; 1522 1523 int activePointerIndex = ev.findPointerIndex(mActivePointerId); 1524 int x = (int) ev.getX(activePointerIndex); 1525 int y = (int) ev.getY(activePointerIndex); 1526 int yTotal = Math.abs(y - mInitialMotionY); 1527 int deltaY = mLastMotionY - y; 1528 if (!mIsScrolling) { 1529 if (yTotal > mScrollTouchSlop) { 1530 mIsScrolling = true; 1531 // Initialize the velocity tracker 1532 initOrResetVelocityTracker(); 1533 mVelocityTracker.addMovement(ev); 1534 // Disallow parents from intercepting touch events 1535 final ViewParent parent = mSv.getParent(); 1536 if (parent != null) { 1537 parent.requestDisallowInterceptTouchEvent(true); 1538 } 1539 // Enable HW layers 1540 mSv.addHwLayersRefCount("stackScroll"); 1541 } 1542 } 1543 if (mIsScrolling) { 1544 int curStackScroll = mSv.getStackScroll(); 1545 int overScrollAmount = mSv.getScrollAmountOutOfBounds(curStackScroll + deltaY); 1546 if (overScrollAmount != 0) { 1547 // Bound the overscroll to a fixed amount, and inversely scale the y-movement 1548 // relative to how close we are to the max overscroll 1549 float maxOverScroll = mSv.mTaskRect.height() / 3f; 1550 deltaY = Math.round(deltaY * (1f - (Math.min(maxOverScroll, overScrollAmount) 1551 / maxOverScroll))); 1552 } 1553 mSv.setStackScroll(curStackScroll + deltaY); 1554 if (mSv.isScrollOutOfBounds()) { 1555 mVelocityTracker.clear(); 1556 } 1557 } 1558 mLastMotionX = x; 1559 mLastMotionY = y; 1560 mTotalScrollMotion += Math.abs(deltaY); 1561 break; 1562 } 1563 case MotionEvent.ACTION_UP: { 1564 final VelocityTracker velocityTracker = mVelocityTracker; 1565 velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); 1566 int velocity = (int) velocityTracker.getYVelocity(mActivePointerId); 1567 1568 if (mIsScrolling && (Math.abs(velocity) > mMinimumVelocity)) { 1569 // Enable HW layers on the stack 1570 mSv.addHwLayersRefCount("flingScroll"); 1571 // XXX: Make this animation a function of the velocity AND distance 1572 int overscrollRange = (int) (Math.min(1f, 1573 Math.abs((float) velocity / mMaximumVelocity)) * 1574 Constants.Values.TaskStackView.TaskStackOverscrollRange); 1575 1576 if (Console.Enabled) { 1577 Console.log(Constants.Log.UI.TouchEvents, 1578 "[TaskStackViewTouchHandler|fling]", 1579 "scroll: " + mSv.getStackScroll() + " velocity: " + velocity + 1580 " maxVelocity: " + mMaximumVelocity + 1581 " overscrollRange: " + overscrollRange, 1582 Console.AnsiGreen); 1583 } 1584 1585 // Fling scroll 1586 mSv.mScroller.fling(0, mSv.getStackScroll(), 1587 0, -velocity, 1588 0, 0, 1589 mSv.mMinScroll, mSv.mMaxScroll, 1590 0, overscrollRange); 1591 // Invalidate to kick off computeScroll 1592 mSv.invalidate(mSv.mStackRect); 1593 } else if (mSv.isScrollOutOfBounds()) { 1594 // Animate the scroll back into bounds 1595 // XXX: Make this animation a function of the velocity OR distance 1596 mSv.animateBoundScroll(); 1597 } 1598 1599 if (mIsScrolling) { 1600 // Disable HW layers 1601 mSv.decHwLayersRefCount("stackScroll"); 1602 } 1603 mActivePointerId = INACTIVE_POINTER_ID; 1604 mIsScrolling = false; 1605 mTotalScrollMotion = 0; 1606 recycleVelocityTracker(); 1607 break; 1608 } 1609 case MotionEvent.ACTION_POINTER_UP: { 1610 int pointerIndex = ev.getActionIndex(); 1611 int pointerId = ev.getPointerId(pointerIndex); 1612 if (pointerId == mActivePointerId) { 1613 // Select a new active pointer id and reset the motion state 1614 final int newPointerIndex = (pointerIndex == 0) ? 1 : 0; 1615 mActivePointerId = ev.getPointerId(newPointerIndex); 1616 mLastMotionX = (int) ev.getX(newPointerIndex); 1617 mLastMotionY = (int) ev.getY(newPointerIndex); 1618 mVelocityTracker.clear(); 1619 } 1620 break; 1621 } 1622 case MotionEvent.ACTION_CANCEL: { 1623 if (mIsScrolling) { 1624 // Disable HW layers 1625 mSv.decHwLayersRefCount("stackScroll"); 1626 } 1627 if (mSv.isScrollOutOfBounds()) { 1628 // Animate the scroll back into bounds 1629 // XXX: Make this animation a function of the velocity OR distance 1630 mSv.animateBoundScroll(); 1631 } 1632 mActivePointerId = INACTIVE_POINTER_ID; 1633 mIsScrolling = false; 1634 mTotalScrollMotion = 0; 1635 recycleVelocityTracker(); 1636 break; 1637 } 1638 } 1639 return true; 1640 } 1641 1642 /**** SwipeHelper Implementation ****/ 1643 1644 @Override 1645 public View getChildAtPosition(MotionEvent ev) { 1646 return findViewAtPoint((int) ev.getX(), (int) ev.getY()); 1647 } 1648 1649 @Override 1650 public boolean canChildBeDismissed(View v) { 1651 return true; 1652 } 1653 1654 @Override 1655 public void onBeginDrag(View v) { 1656 // Enable HW layers 1657 mSv.addHwLayersRefCount("swipeBegin"); 1658 // Disable clipping with the stack while we are swiping 1659 TaskView tv = (TaskView) v; 1660 tv.setClipViewInStack(false); 1661 // Disallow parents from intercepting touch events 1662 final ViewParent parent = mSv.getParent(); 1663 if (parent != null) { 1664 parent.requestDisallowInterceptTouchEvent(true); 1665 } 1666 } 1667 1668 @Override 1669 public void onSwipeChanged(View v, float delta) { 1670 // Do nothing 1671 } 1672 1673 @Override 1674 public void onChildDismissed(View v) { 1675 TaskView tv = (TaskView) v; 1676 mSv.onTaskDismissed(tv); 1677 1678 // Re-enable clipping with the stack (we will reuse this view) 1679 tv.setClipViewInStack(true); 1680 1681 // Disable HW layers 1682 mSv.decHwLayersRefCount("swipeComplete"); 1683 } 1684 1685 @Override 1686 public void onSnapBackCompleted(View v) { 1687 // Re-enable clipping with the stack 1688 TaskView tv = (TaskView) v; 1689 tv.setClipViewInStack(true); 1690 } 1691 1692 @Override 1693 public void onDragCancelled(View v) { 1694 // Disable HW layers 1695 mSv.decHwLayersRefCount("swipeCancelled"); 1696 } 1697} 1698