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