TaskStackView.java revision 04dfe0d26b944324ee920001f40d74cff47281d6
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.app.ActivityManager; 25import android.content.Context; 26import android.graphics.Canvas; 27import android.graphics.Rect; 28import android.graphics.Region; 29import android.view.MotionEvent; 30import android.view.VelocityTracker; 31import android.view.View; 32import android.view.ViewConfiguration; 33import android.view.ViewParent; 34import android.widget.FrameLayout; 35import android.widget.OverScroller; 36import com.android.systemui.recents.Console; 37import com.android.systemui.recents.Constants; 38import com.android.systemui.recents.RecentsConfiguration; 39import com.android.systemui.recents.RecentsTaskLoader; 40import com.android.systemui.recents.Utilities; 41import com.android.systemui.recents.model.Task; 42import com.android.systemui.recents.model.TaskStack; 43 44import java.util.ArrayList; 45 46 47/* The visual representation of a task stack view */ 48public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCallbacks, 49 TaskView.TaskViewCallbacks, ViewPool.ViewPoolConsumer<TaskView, Task>, 50 View.OnClickListener { 51 52 /** The TaskView callbacks */ 53 interface TaskStackViewCallbacks { 54 public void onTaskLaunched(TaskStackView stackView, TaskView tv, TaskStack stack, Task t); 55 } 56 57 TaskStack mStack; 58 TaskStackViewTouchHandler mTouchHandler; 59 TaskStackViewCallbacks mCb; 60 ViewPool<TaskView, Task> mViewPool; 61 62 // The various rects that define the stack view 63 Rect mRect = new Rect(); 64 Rect mStackRect = new Rect(); 65 Rect mStackRectSansPeek = new Rect(); 66 Rect mTaskRect = new Rect(); 67 68 // The virtual stack scroll that we use for the card layout 69 int mStackScroll; 70 int mMinScroll; 71 int mMaxScroll; 72 OverScroller mScroller; 73 ObjectAnimator mScrollAnimator; 74 75 // Optimizations 76 int mHwLayersRefCount; 77 int mStackViewsAnimationDuration; 78 boolean mStackViewsDirty = true; 79 boolean mAwaitingFirstLayout = true; 80 81 public TaskStackView(Context context, TaskStack stack) { 82 super(context); 83 mStack = stack; 84 mStack.setCallbacks(this); 85 mScroller = new OverScroller(context); 86 mTouchHandler = new TaskStackViewTouchHandler(context, this); 87 mViewPool = new ViewPool<TaskView, Task>(context, this); 88 } 89 90 /** Sets the callbacks */ 91 void setCallbacks(TaskStackViewCallbacks cb) { 92 mCb = cb; 93 } 94 95 /** Requests that the views be synchronized with the model */ 96 void requestSynchronizeStackViewsWithModel() { 97 requestSynchronizeStackViewsWithModel(0); 98 } 99 void requestSynchronizeStackViewsWithModel(int duration) { 100 Console.log(Constants.DebugFlags.TaskStack.SynchronizeViewsWithModel, 101 "[TaskStackView|requestSynchronize]", "", Console.AnsiYellow); 102 if (!mStackViewsDirty) { 103 invalidate(); 104 } 105 if (mAwaitingFirstLayout) { 106 // Skip the animation if we are awaiting first layout 107 mStackViewsAnimationDuration = 0; 108 } else { 109 mStackViewsAnimationDuration = Math.max(mStackViewsAnimationDuration, duration); 110 } 111 mStackViewsDirty = true; 112 } 113 114 // XXX: Optimization: Use a mapping of Task -> View 115 private TaskView getChildViewForTask(Task t) { 116 int childCount = getChildCount(); 117 for (int i = 0; i < childCount; i++) { 118 TaskView tv = (TaskView) getChildAt(i); 119 if (tv.getTask() == t) { 120 return tv; 121 } 122 } 123 return null; 124 } 125 126 /** Update/get the transform */ 127 public TaskViewTransform getStackTransform(int indexInStack) { 128 TaskViewTransform transform = new TaskViewTransform(); 129 130 // Map the items to an continuous position relative to the current scroll 131 int numPeekCards = Constants.Values.TaskStackView.StackPeekNumCards; 132 float overlapHeight = Constants.Values.TaskStackView.StackOverlapPct * mTaskRect.height(); 133 float peekHeight = Constants.Values.TaskStackView.StackPeekHeightPct * mStackRect.height(); 134 float t = ((indexInStack * overlapHeight) - getStackScroll()) / overlapHeight; 135 float boundedT = Math.max(t, -(numPeekCards + 1)); 136 137 // Set the scale relative to its position 138 float minScale = Constants.Values.TaskStackView.StackPeekMinScale; 139 float scaleRange = 1f - minScale; 140 float scaleInc = scaleRange / numPeekCards; 141 float scale = Math.max(minScale, Math.min(1f, 1f + (boundedT * scaleInc))); 142 float scaleYOffset = ((1f - scale) * mTaskRect.height()) / 2; 143 transform.scale = scale; 144 145 // Set the translation 146 if (boundedT < 0f) { 147 transform.translationY = (int) ((Math.max(-numPeekCards, boundedT) / 148 numPeekCards) * peekHeight - scaleYOffset); 149 } else { 150 transform.translationY = (int) (boundedT * overlapHeight - scaleYOffset); 151 } 152 153 // Update the rect and visibility 154 transform.rect.set(mTaskRect); 155 if (t < -(numPeekCards + 1)) { 156 transform.visible = false; 157 } else { 158 transform.rect.offset(0, transform.translationY); 159 Utilities.scaleRectAboutCenter(transform.rect, scale); 160 transform.visible = Rect.intersects(mRect, transform.rect); 161 } 162 transform.t = t; 163 return transform; 164 } 165 166 /** Synchronizes the views with the model */ 167 void synchronizeStackViewsWithModel() { 168 Console.log(Constants.DebugFlags.TaskStack.SynchronizeViewsWithModel, 169 "[TaskStackView|synchronizeViewsWithModel]", 170 "mStackViewsDirty: " + mStackViewsDirty, Console.AnsiYellow); 171 if (mStackViewsDirty) { 172 173 // XXX: Optimization: Use binary search to find the visible range 174 // XXX: Optimize to not call getStackTransform() so many times 175 // XXX: Consider using TaskViewTransform pool to prevent allocations 176 // XXX: Iterate children views, update transforms and remove all that are not visible 177 // For all remaining tasks, update transforms and if visible add the view 178 179 // Update the visible state of all the tasks 180 ArrayList<Task> tasks = mStack.getTasks(); 181 int taskCount = tasks.size(); 182 for (int i = 0; i < taskCount; i++) { 183 Task task = tasks.get(i); 184 TaskViewTransform transform = getStackTransform(i); 185 TaskView tv = getChildViewForTask(task); 186 187 if (transform.visible) { 188 if (tv == null) { 189 tv = mViewPool.pickUpViewFromPool(task, task); 190 // When we are picking up a new view from the view pool, prepare it for any 191 // following animation by putting it in a reasonable place 192 if (mStackViewsAnimationDuration > 0 && i != 0) { 193 // XXX: We have to animate when filtering, etc. Maybe we should have a 194 // runnable that ensures that tasks are animated in a special way 195 // when they are entering the scene? 196 int fromIndex = (transform.t < 0) ? (i - 1) : (i + 1); 197 tv.updateViewPropertiesFromTask(null, getStackTransform(fromIndex), 0); 198 } 199 } 200 } else { 201 if (tv != null) { 202 mViewPool.returnViewToPool(tv); 203 } 204 } 205 } 206 207 // Update all the current view children 208 // NOTE: We have to iterate in reverse where because we are removing views directly 209 int childCount = getChildCount(); 210 for (int i = childCount - 1; i >= 0; i--) { 211 TaskView tv = (TaskView) getChildAt(i); 212 Task task = tv.getTask(); 213 TaskViewTransform transform = getStackTransform(mStack.indexOfTask(task)); 214 if (!transform.visible) { 215 mViewPool.returnViewToPool(tv); 216 } else { 217 tv.updateViewPropertiesFromTask(null, transform, mStackViewsAnimationDuration); 218 } 219 } 220 221 Console.log(Constants.DebugFlags.TaskStack.SynchronizeViewsWithModel, 222 " [TaskStackView|viewChildren]", "" + getChildCount()); 223 224 mStackViewsAnimationDuration = 0; 225 mStackViewsDirty = false; 226 } 227 } 228 229 /** Sets the current stack scroll */ 230 public void setStackScroll(int value) { 231 mStackScroll = value; 232 requestSynchronizeStackViewsWithModel(); 233 } 234 235 /** Gets the current stack scroll */ 236 public int getStackScroll() { 237 return mStackScroll; 238 } 239 240 /** Animates the stack scroll into bounds */ 241 ObjectAnimator animateBoundScroll(int duration) { 242 int curScroll = getStackScroll(); 243 int newScroll = Math.max(mMinScroll, Math.min(mMaxScroll, curScroll)); 244 if (newScroll != curScroll) { 245 // Enable hw layers on the stack 246 addHwLayersRefCount(); 247 248 // Abort any current animations 249 mScroller.abortAnimation(); 250 if (mScrollAnimator != null) { 251 mScrollAnimator.cancel(); 252 mScrollAnimator.removeAllListeners(); 253 } 254 255 // Start a new scroll animation 256 mScrollAnimator = ObjectAnimator.ofInt(this, "stackScroll", curScroll, newScroll); 257 mScrollAnimator.setDuration(duration); 258 mScrollAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 259 @Override 260 public void onAnimationUpdate(ValueAnimator animation) { 261 setStackScroll((Integer) animation.getAnimatedValue()); 262 } 263 }); 264 mScrollAnimator.addListener(new AnimatorListenerAdapter() { 265 @Override 266 public void onAnimationEnd(Animator animation) { 267 // Disable hw layers on the stack 268 decHwLayersRefCount(); 269 } 270 }); 271 mScrollAnimator.start(); 272 } 273 return mScrollAnimator; 274 } 275 276 /** Aborts any current stack scrolls */ 277 void abortBoundScrollAnimation() { 278 if (mScrollAnimator != null) { 279 mScrollAnimator.cancel(); 280 } 281 } 282 283 /** Bounds the current scroll if necessary */ 284 public boolean boundScroll() { 285 int curScroll = getStackScroll(); 286 int newScroll = Math.max(mMinScroll, Math.min(mMaxScroll, curScroll)); 287 if (newScroll != curScroll) { 288 setStackScroll(newScroll); 289 return true; 290 } 291 return false; 292 } 293 294 /** Returns whether the current scroll is out of bounds */ 295 boolean isScrollOutOfBounds() { 296 return (getStackScroll() < 0) || (getStackScroll() > mMaxScroll); 297 } 298 299 /** Updates the min and max virtual scroll bounds */ 300 void updateMinMaxScroll(boolean boundScrollToNewMinMax) { 301 // Compute the min and max scroll values 302 int numTasks = Math.max(1, mStack.getTaskCount()); 303 int taskHeight = mTaskRect.height(); 304 int stackHeight = mStackRectSansPeek.height(); 305 int maxScrollHeight = taskHeight + (int) ((numTasks - 1) * 306 Constants.Values.TaskStackView.StackOverlapPct * taskHeight); 307 mMinScroll = Math.min(stackHeight, maxScrollHeight) - stackHeight; 308 mMaxScroll = maxScrollHeight - stackHeight; 309 310 // Debug logging 311 if (Constants.DebugFlags.UI.MeasureAndLayout) { 312 Console.log(" [TaskStack|minScroll] " + mMinScroll); 313 Console.log(" [TaskStack|maxScroll] " + mMaxScroll); 314 } 315 316 if (boundScrollToNewMinMax) { 317 boundScroll(); 318 } 319 } 320 321 /** Enables the hw layers and increments the hw layer requirement ref count */ 322 void addHwLayersRefCount() { 323 Console.log(Constants.DebugFlags.UI.HwLayers, 324 "[TaskStackView|addHwLayersRefCount] refCount: " + 325 mHwLayersRefCount + "->" + (mHwLayersRefCount + 1)); 326 if (mHwLayersRefCount == 0) { 327 // Enable hw layers on each of the children 328 int childCount = getChildCount(); 329 for (int i = 0; i < childCount; i++) { 330 TaskView tv = (TaskView) getChildAt(i); 331 tv.enableHwLayers(); 332 } 333 } 334 mHwLayersRefCount++; 335 } 336 337 /** Decrements the hw layer requirement ref count and disables the hw layers when we don't 338 need them anymore. */ 339 void decHwLayersRefCount() { 340 Console.log(Constants.DebugFlags.UI.HwLayers, 341 "[TaskStackView|decHwLayersRefCount] refCount: " + 342 mHwLayersRefCount + "->" + (mHwLayersRefCount - 1)); 343 mHwLayersRefCount--; 344 if (mHwLayersRefCount == 0) { 345 // Disable hw layers on each of the children 346 int childCount = getChildCount(); 347 for (int i = 0; i < childCount; i++) { 348 TaskView tv = (TaskView) getChildAt(i); 349 tv.disableHwLayers(); 350 } 351 } else if (mHwLayersRefCount < 0) { 352 new Throwable("Invalid hw layers ref count").printStackTrace(); 353 Console.logError(getContext(), "Invalid HW layers ref count"); 354 } 355 } 356 357 @Override 358 public void computeScroll() { 359 if (mScroller.computeScrollOffset()) { 360 setStackScroll(mScroller.getCurrY()); 361 invalidate(); 362 363 // If we just finished scrolling, then disable the hw layers 364 if (mScroller.isFinished()) { 365 decHwLayersRefCount(); 366 } 367 } 368 } 369 370 @Override 371 public boolean onInterceptTouchEvent(MotionEvent ev) { 372 return mTouchHandler.onInterceptTouchEvent(ev); 373 } 374 375 @Override 376 public boolean onTouchEvent(MotionEvent ev) { 377 return mTouchHandler.onTouchEvent(ev); 378 } 379 380 @Override 381 public void dispatchDraw(Canvas canvas) { 382 Console.log(Constants.DebugFlags.UI.Draw, "[TaskStackView|dispatchDraw]", "", 383 Console.AnsiPurple); 384 synchronizeStackViewsWithModel(); 385 super.dispatchDraw(canvas); 386 } 387 388 @Override 389 protected boolean drawChild(Canvas canvas, View child, long drawingTime) { 390 if (Constants.DebugFlags.App.EnableTaskStackClipping) { 391 TaskView tv = (TaskView) child; 392 TaskView nextTv = null; 393 int curIndex = indexOfChild(tv); 394 if (curIndex < (getChildCount() - 1)) { 395 // Clip against the next view (if we aren't animating its alpha) 396 nextTv = (TaskView) getChildAt(curIndex + 1); 397 if (nextTv.getAlpha() == 1f) { 398 Rect curRect = tv.getClippingRect(Utilities.tmpRect, false); 399 Rect nextRect = nextTv.getClippingRect(Utilities.tmpRect2, true); 400 RecentsConfiguration config = RecentsConfiguration.getInstance(); 401 // The hit rects are relative to the task view, which needs to be offset by the 402 // system bar height 403 curRect.offset(0, config.systemInsets.top); 404 nextRect.offset(0, config.systemInsets.top); 405 // Compute the clip region 406 Region clipRegion = new Region(); 407 clipRegion.op(curRect, Region.Op.UNION); 408 clipRegion.op(nextRect, Region.Op.DIFFERENCE); 409 // Clip the canvas 410 int saveCount = canvas.save(Canvas.CLIP_SAVE_FLAG); 411 canvas.clipRegion(clipRegion); 412 boolean invalidate = super.drawChild(canvas, child, drawingTime); 413 canvas.restoreToCount(saveCount); 414 return invalidate; 415 } 416 } 417 } 418 return super.drawChild(canvas, child, drawingTime); 419 } 420 421 /** Computes the stack and task rects */ 422 public void computeRects(int width, int height) { 423 // Note: We let the stack view be the full height because we want the cards to go under the 424 // navigation bar if possible. However, the stack rects which we use to calculate 425 // max scroll, etc. need to take the nav bar into account 426 427 // Compute the stack rects 428 RecentsConfiguration config = RecentsConfiguration.getInstance(); 429 mRect.set(0, 0, width, height); 430 mStackRect.set(mRect); 431 mStackRect.bottom -= config.systemInsets.bottom; 432 433 int smallestDimension = Math.min(width, height); 434 int padding = (int) (Constants.Values.TaskStackView.StackPaddingPct * smallestDimension / 2f); 435 mStackRect.inset(padding, padding); 436 mStackRectSansPeek.set(mStackRect); 437 mStackRectSansPeek.top += Constants.Values.TaskStackView.StackPeekHeightPct * mStackRect.height(); 438 439 // Compute the task rect 440 int minHeight = (int) (mStackRect.height() - 441 (Constants.Values.TaskStackView.StackPeekHeightPct * mStackRect.height())); 442 int size = Math.min(minHeight, Math.min(mStackRect.width(), mStackRect.height())); 443 int centerX = mStackRect.centerX(); 444 mTaskRect.set(centerX - size / 2, mStackRectSansPeek.top, 445 centerX + size / 2, mStackRectSansPeek.top + size); 446 447 // Update the scroll bounds 448 updateMinMaxScroll(false); 449 } 450 451 @Override 452 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 453 int width = MeasureSpec.getSize(widthMeasureSpec); 454 int height = MeasureSpec.getSize(heightMeasureSpec); 455 Console.log(Constants.DebugFlags.UI.MeasureAndLayout, "[TaskStackView|measure]", 456 "width: " + width + " height: " + height + 457 " awaitingFirstLayout: " + mAwaitingFirstLayout, Console.AnsiGreen); 458 459 // Compute our stack/task rects 460 computeRects(width, height); 461 462 // Debug logging 463 if (Constants.DebugFlags.UI.MeasureAndLayout) { 464 Console.log(" [TaskStack|fullRect] " + mRect); 465 Console.log(" [TaskStack|stackRect] " + mStackRect); 466 Console.log(" [TaskStack|stackRectSansPeek] " + mStackRectSansPeek); 467 Console.log(" [TaskStack|taskRect] " + mTaskRect); 468 } 469 470 // If this is the first layout, then scroll to the front of the stack and synchronize the 471 // stack views immediately 472 if (mAwaitingFirstLayout) { 473 setStackScroll(mMaxScroll); 474 requestSynchronizeStackViewsWithModel(); 475 synchronizeStackViewsWithModel(); 476 477 // Animate the icon of the first task view 478 if (Constants.Values.TaskView.AnimateFrontTaskIconOnEnterRecents) { 479 TaskView tv = (TaskView) getChildAt(getChildCount() - 1); 480 if (tv != null) { 481 tv.animateOnEnterRecents(); 482 } 483 } 484 } 485 486 // Measure each of the children 487 int childCount = getChildCount(); 488 for (int i = 0; i < childCount; i++) { 489 TaskView t = (TaskView) getChildAt(i); 490 t.measure(MeasureSpec.makeMeasureSpec(mTaskRect.width(), MeasureSpec.EXACTLY), 491 MeasureSpec.makeMeasureSpec(mTaskRect.height(), MeasureSpec.EXACTLY)); 492 } 493 494 setMeasuredDimension(width, height); 495 } 496 497 @Override 498 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 499 Console.log(Constants.DebugFlags.UI.MeasureAndLayout, "[TaskStackView|layout]", 500 "" + new Rect(left, top, right, bottom), Console.AnsiGreen); 501 502 // Debug logging 503 if (Constants.DebugFlags.UI.MeasureAndLayout) { 504 Console.log(" [TaskStack|fullRect] " + mRect); 505 Console.log(" [TaskStack|stackRect] " + mStackRect); 506 Console.log(" [TaskStack|stackRectSansPeek] " + mStackRectSansPeek); 507 Console.log(" [TaskStack|taskRect] " + mTaskRect); 508 } 509 510 // Layout each of the children 511 int childCount = getChildCount(); 512 for (int i = 0; i < childCount; i++) { 513 TaskView t = (TaskView) getChildAt(i); 514 t.layout(mTaskRect.left, mStackRectSansPeek.top, 515 mTaskRect.right, mStackRectSansPeek.top + mTaskRect.height()); 516 } 517 518 if (!mAwaitingFirstLayout) { 519 requestSynchronizeStackViewsWithModel(); 520 } else { 521 mAwaitingFirstLayout = false; 522 } 523 } 524 525 @Override 526 protected void onScrollChanged(int l, int t, int oldl, int oldt) { 527 super.onScrollChanged(l, t, oldl, oldt); 528 requestSynchronizeStackViewsWithModel(); 529 } 530 531 public boolean isTransformedTouchPointInView(float x, float y, View child) { 532 return isTransformedTouchPointInView(x, y, child, null); 533 } 534 535 /**** TaskStackCallbacks Implementation ****/ 536 537 @Override 538 public void onStackTaskAdded(TaskStack stack, Task t) { 539 requestSynchronizeStackViewsWithModel(); 540 } 541 542 @Override 543 public void onStackTaskRemoved(TaskStack stack, Task t) { 544 // Remove the view associated with this task, we can't rely on updateTransforms 545 // to work here because the task is no longer in the list 546 int childCount = getChildCount(); 547 for (int i = childCount - 1; i >= 0; i--) { 548 TaskView tv = (TaskView) getChildAt(i); 549 if (tv.getTask() == t) { 550 mViewPool.returnViewToPool(tv); 551 break; 552 } 553 } 554 555 updateMinMaxScroll(true); 556 requestSynchronizeStackViewsWithModel(Constants.Values.TaskStackView.Animation.TaskRemovedReshuffleDuration); 557 } 558 559 @Override 560 public void onStackFiltered(TaskStack stack) { 561 requestSynchronizeStackViewsWithModel(); 562 } 563 564 @Override 565 public void onStackUnfiltered(TaskStack stack) { 566 requestSynchronizeStackViewsWithModel(); 567 } 568 569 /**** ViewPoolConsumer Implementation ****/ 570 571 @Override 572 public TaskView createView(Context context) { 573 Console.log(Constants.DebugFlags.ViewPool.PoolCallbacks, "[TaskStackView|createPoolView]"); 574 return new TaskView(context); 575 } 576 577 @Override 578 public void prepareViewToEnterPool(TaskView tv) { 579 Task task = tv.getTask(); 580 tv.resetViewProperties(); 581 Console.log(Constants.DebugFlags.ViewPool.PoolCallbacks, "[TaskStackView|returnToPool]", 582 tv.getTask() + " tv: " + tv); 583 584 // Report that this tasks's data is no longer being used 585 RecentsTaskLoader loader = RecentsTaskLoader.getInstance(); 586 loader.unloadTaskData(task); 587 588 // Detach the view from the hierarchy 589 detachViewFromParent(tv); 590 591 // Disable hw layers on this view 592 tv.disableHwLayers(); 593 } 594 595 @Override 596 public void prepareViewToLeavePool(TaskView tv, Task prepareData, boolean isNewView) { 597 Console.log(Constants.DebugFlags.ViewPool.PoolCallbacks, "[TaskStackView|leavePool]", 598 "isNewView: " + isNewView); 599 600 // Setup and attach the view to the window 601 Task task = prepareData; 602 // We try and rebind the task (this MUST be done before the task filled) 603 tv.onTaskBound(task); 604 // Request that this tasks's data be filled 605 RecentsTaskLoader loader = RecentsTaskLoader.getInstance(); 606 loader.loadTaskData(task); 607 608 // Find the index where this task should be placed in the children 609 int insertIndex = -1; 610 int childCount = getChildCount(); 611 for (int i = 0; i < childCount; i++) { 612 Task tvTask = ((TaskView) getChildAt(i)).getTask(); 613 if (mStack.containsTask(task) && (mStack.indexOfTask(task) < mStack.indexOfTask(tvTask))) { 614 insertIndex = i; 615 break; 616 } 617 } 618 619 // Add/attach the view to the hierarchy 620 Console.log(Constants.DebugFlags.ViewPool.PoolCallbacks, " [TaskStackView|insertIndex]", 621 "" + insertIndex); 622 if (isNewView) { 623 addView(tv, insertIndex); 624 625 // Set the callbacks and listeners for this new view 626 tv.setOnClickListener(this); 627 tv.setCallbacks(this); 628 } else { 629 attachViewToParent(tv, insertIndex, tv.getLayoutParams()); 630 } 631 632 // Enable hw layers on this view if hw layers are enabled on the stack 633 if (mHwLayersRefCount > 0) { 634 tv.enableHwLayers(); 635 } 636 } 637 638 @Override 639 public boolean hasPreferredData(TaskView tv, Task preferredData) { 640 return (tv.getTask() == preferredData); 641 } 642 643 /**** TaskViewCallbacks Implementation ****/ 644 645 @Override 646 public void onTaskIconClicked(TaskView tv) { 647 Console.log(Constants.DebugFlags.UI.ClickEvents, "[TaskStack|Clicked|Icon]", 648 tv.getTask() + " is currently filtered: " + mStack.hasFilteredTasks(), 649 Console.AnsiCyan); 650 if (Constants.DebugFlags.App.EnableTaskFiltering) { 651 if (mStack.hasFilteredTasks()) { 652 mStack.unfilterTasks(); 653 } else { 654 mStack.filterTasks(tv.getTask()); 655 } 656 } else { 657 Console.logError(getContext(), "Task Filtering TBD"); 658 } 659 } 660 661 /**** View.OnClickListener Implementation ****/ 662 663 @Override 664 public void onClick(View v) { 665 TaskView tv = (TaskView) v; 666 Task task = tv.getTask(); 667 Console.log(Constants.DebugFlags.UI.ClickEvents, "[TaskStack|Clicked|Thumbnail]", 668 task + " cb: " + mCb); 669 670 if (mCb != null) { 671 mCb.onTaskLaunched(this, tv, mStack, task); 672 } 673 } 674} 675 676/* Handles touch events */ 677class TaskStackViewTouchHandler implements SwipeHelper.Callback { 678 static int INACTIVE_POINTER_ID = -1; 679 680 TaskStackView mSv; 681 VelocityTracker mVelocityTracker; 682 683 boolean mIsScrolling; 684 685 int mInitialMotionX, mInitialMotionY; 686 int mLastMotionX, mLastMotionY; 687 int mActivePointerId = INACTIVE_POINTER_ID; 688 TaskView mActiveTaskView = null; 689 690 int mTotalScrollMotion; 691 int mMinimumVelocity; 692 int mMaximumVelocity; 693 // The scroll touch slop is used to calculate when we start scrolling 694 int mScrollTouchSlop; 695 // The page touch slop is used to calculate when we start swiping 696 float mPagingTouchSlop; 697 698 SwipeHelper mSwipeHelper; 699 boolean mInterceptedBySwipeHelper; 700 701 public TaskStackViewTouchHandler(Context context, TaskStackView sv) { 702 ViewConfiguration configuration = ViewConfiguration.get(context); 703 mMinimumVelocity = configuration.getScaledMinimumFlingVelocity(); 704 mMaximumVelocity = configuration.getScaledMaximumFlingVelocity(); 705 mScrollTouchSlop = configuration.getScaledTouchSlop(); 706 mPagingTouchSlop = configuration.getScaledPagingTouchSlop(); 707 mSv = sv; 708 709 710 float densityScale = context.getResources().getDisplayMetrics().density; 711 mSwipeHelper = new SwipeHelper(SwipeHelper.X, this, densityScale, mPagingTouchSlop); 712 mSwipeHelper.setMinAlpha(1f); 713 } 714 715 /** Velocity tracker helpers */ 716 void initOrResetVelocityTracker() { 717 if (mVelocityTracker == null) { 718 mVelocityTracker = VelocityTracker.obtain(); 719 } else { 720 mVelocityTracker.clear(); 721 } 722 } 723 void initVelocityTrackerIfNotExists() { 724 if (mVelocityTracker == null) { 725 mVelocityTracker = VelocityTracker.obtain(); 726 } 727 } 728 void recycleVelocityTracker() { 729 if (mVelocityTracker != null) { 730 mVelocityTracker.recycle(); 731 mVelocityTracker = null; 732 } 733 } 734 735 /** Returns the view at the specified coordinates */ 736 TaskView findViewAtPoint(int x, int y) { 737 int childCount = mSv.getChildCount(); 738 for (int i = childCount - 1; i >= 0; i--) { 739 TaskView tv = (TaskView) mSv.getChildAt(i); 740 if (tv.getVisibility() == View.VISIBLE) { 741 if (mSv.isTransformedTouchPointInView(x, y, tv)) { 742 return tv; 743 } 744 } 745 } 746 return null; 747 } 748 749 /** Touch preprocessing for handling below */ 750 public boolean onInterceptTouchEvent(MotionEvent ev) { 751 Console.log(Constants.DebugFlags.UI.TouchEvents, 752 "[TaskStackViewTouchHandler|interceptTouchEvent]", 753 Console.motionEventActionToString(ev.getAction()), Console.AnsiBlue); 754 755 // Return early if we have no children 756 boolean hasChildren = (mSv.getChildCount() > 0); 757 if (!hasChildren) { 758 return false; 759 } 760 761 // Pass through to swipe helper if we are swiping 762 mInterceptedBySwipeHelper = mSwipeHelper.onInterceptTouchEvent(ev); 763 if (mInterceptedBySwipeHelper) { 764 return true; 765 } 766 767 boolean wasScrolling = !mSv.mScroller.isFinished() || 768 (mSv.mScrollAnimator != null && mSv.mScrollAnimator.isRunning()); 769 int action = ev.getAction(); 770 switch (action & MotionEvent.ACTION_MASK) { 771 case MotionEvent.ACTION_DOWN: { 772 // Save the touch down info 773 mInitialMotionX = mLastMotionX = (int) ev.getX(); 774 mInitialMotionY = mLastMotionY = (int) ev.getY(); 775 mActivePointerId = ev.getPointerId(0); 776 mActiveTaskView = findViewAtPoint(mLastMotionX, mLastMotionY); 777 // Stop the current scroll if it is still flinging 778 mSv.mScroller.abortAnimation(); 779 mSv.abortBoundScrollAnimation(); 780 // Initialize the velocity tracker 781 initOrResetVelocityTracker(); 782 mVelocityTracker.addMovement(ev); 783 // Check if the scroller is finished yet 784 mIsScrolling = !mSv.mScroller.isFinished(); 785 // Enable HW layers 786 mSv.addHwLayersRefCount(); 787 break; 788 } 789 case MotionEvent.ACTION_MOVE: { 790 if (mActivePointerId == INACTIVE_POINTER_ID) break; 791 792 int activePointerIndex = ev.findPointerIndex(mActivePointerId); 793 int y = (int) ev.getY(activePointerIndex); 794 int x = (int) ev.getX(activePointerIndex); 795 if (Math.abs(y - mInitialMotionY) > mScrollTouchSlop) { 796 // Save the touch move info 797 mIsScrolling = true; 798 // Initialize the velocity tracker if necessary 799 initVelocityTrackerIfNotExists(); 800 mVelocityTracker.addMovement(ev); 801 // Disallow parents from intercepting touch events 802 final ViewParent parent = mSv.getParent(); 803 if (parent != null) { 804 parent.requestDisallowInterceptTouchEvent(true); 805 } 806 } 807 808 mLastMotionX = x; 809 mLastMotionY = y; 810 break; 811 } 812 case MotionEvent.ACTION_CANCEL: 813 case MotionEvent.ACTION_UP: { 814 // Animate the scroll back if we've cancelled 815 mSv.animateBoundScroll(Constants.Values.TaskStackView.Animation.SnapScrollBackDuration); 816 // Reset the drag state and the velocity tracker 817 mIsScrolling = false; 818 mActivePointerId = INACTIVE_POINTER_ID; 819 mActiveTaskView = null; 820 mTotalScrollMotion = 0; 821 recycleVelocityTracker(); 822 // Disable HW layers 823 mSv.decHwLayersRefCount(); 824 break; 825 } 826 } 827 828 return wasScrolling || mIsScrolling; 829 } 830 831 /** Handles touch events once we have intercepted them */ 832 public boolean onTouchEvent(MotionEvent ev) { 833 Console.log(Constants.DebugFlags.TaskStack.SynchronizeViewsWithModel, 834 "[TaskStackViewTouchHandler|touchEvent]", 835 Console.motionEventActionToString(ev.getAction()), Console.AnsiBlue); 836 837 // Short circuit if we have no children 838 boolean hasChildren = (mSv.getChildCount() > 0); 839 if (!hasChildren) { 840 return false; 841 } 842 843 // Pass through to swipe helper if we are swiping 844 if (mInterceptedBySwipeHelper && mSwipeHelper.onTouchEvent(ev)) { 845 return true; 846 } 847 848 // Update the velocity tracker 849 initVelocityTrackerIfNotExists(); 850 mVelocityTracker.addMovement(ev); 851 852 int action = ev.getAction(); 853 switch (action & MotionEvent.ACTION_MASK) { 854 case MotionEvent.ACTION_DOWN: { 855 // Save the touch down info 856 mInitialMotionX = mLastMotionX = (int) ev.getX(); 857 mInitialMotionY = mLastMotionY = (int) ev.getY(); 858 mActivePointerId = ev.getPointerId(0); 859 mActiveTaskView = findViewAtPoint(mLastMotionX, mLastMotionY); 860 // Stop the current scroll if it is still flinging 861 mSv.mScroller.abortAnimation(); 862 mSv.abortBoundScrollAnimation(); 863 // Initialize the velocity tracker 864 initOrResetVelocityTracker(); 865 mVelocityTracker.addMovement(ev); 866 // Disallow parents from intercepting touch events 867 final ViewParent parent = mSv.getParent(); 868 if (parent != null) { 869 parent.requestDisallowInterceptTouchEvent(true); 870 } 871 break; 872 } 873 case MotionEvent.ACTION_MOVE: { 874 if (mActivePointerId == INACTIVE_POINTER_ID) break; 875 876 int activePointerIndex = ev.findPointerIndex(mActivePointerId); 877 int x = (int) ev.getX(activePointerIndex); 878 int y = (int) ev.getY(activePointerIndex); 879 int deltaY = mLastMotionY - y; 880 if (!mIsScrolling) { 881 if (Math.abs(y - mInitialMotionY) > mScrollTouchSlop) { 882 mIsScrolling = true; 883 // Initialize the velocity tracker 884 initOrResetVelocityTracker(); 885 mVelocityTracker.addMovement(ev); 886 // Disallow parents from intercepting touch events 887 final ViewParent parent = mSv.getParent(); 888 if (parent != null) { 889 parent.requestDisallowInterceptTouchEvent(true); 890 } 891 // Enable HW layers 892 mSv.addHwLayersRefCount(); 893 } 894 } 895 if (mIsScrolling) { 896 mSv.setStackScroll(mSv.getStackScroll() + deltaY); 897 if (mSv.isScrollOutOfBounds()) { 898 mVelocityTracker.clear(); 899 } 900 } 901 mLastMotionX = x; 902 mLastMotionY = y; 903 mTotalScrollMotion += Math.abs(deltaY); 904 break; 905 } 906 case MotionEvent.ACTION_UP: { 907 final VelocityTracker velocityTracker = mVelocityTracker; 908 velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); 909 int velocity = (int) velocityTracker.getYVelocity(mActivePointerId); 910 911 if (mIsScrolling && (Math.abs(velocity) > mMinimumVelocity)) { 912 Console.log(Constants.DebugFlags.UI.TouchEvents, 913 "[TaskStackViewTouchHandler|fling]", 914 "scroll: " + mSv.getStackScroll() + " velocity: " + velocity, 915 Console.AnsiGreen); 916 // Enable HW layers on the stack 917 mSv.addHwLayersRefCount(); 918 // Fling scroll 919 mSv.mScroller.fling(0, mSv.getStackScroll(), 920 0, -velocity, 921 0, 0, 922 mSv.mMinScroll, mSv.mMaxScroll, 923 0, 0); 924 // Invalidate to kick off computeScroll 925 mSv.invalidate(); 926 } else if (mSv.isScrollOutOfBounds()) { 927 // Animate the scroll back into bounds 928 // XXX: Make this animation a function of the velocity OR distance 929 mSv.animateBoundScroll(Constants.Values.TaskStackView.Animation.SnapScrollBackDuration); 930 } 931 932 mActivePointerId = INACTIVE_POINTER_ID; 933 mIsScrolling = false; 934 mTotalScrollMotion = 0; 935 recycleVelocityTracker(); 936 // Disable HW layers 937 mSv.decHwLayersRefCount(); 938 break; 939 } 940 case MotionEvent.ACTION_CANCEL: { 941 if (mSv.isScrollOutOfBounds()) { 942 // Animate the scroll back into bounds 943 // XXX: Make this animation a function of the velocity OR distance 944 mSv.animateBoundScroll(Constants.Values.TaskStackView.Animation.SnapScrollBackDuration); 945 } 946 947 mActivePointerId = INACTIVE_POINTER_ID; 948 mIsScrolling = false; 949 mTotalScrollMotion = 0; 950 recycleVelocityTracker(); 951 // Disable HW layers 952 mSv.decHwLayersRefCount(); 953 break; 954 } 955 } 956 return true; 957 } 958 959 /**** SwipeHelper Implementation ****/ 960 961 @Override 962 public View getChildAtPosition(MotionEvent ev) { 963 return findViewAtPoint((int) ev.getX(), (int) ev.getY()); 964 } 965 966 @Override 967 public boolean canChildBeDismissed(View v) { 968 return true; 969 } 970 971 @Override 972 public void onBeginDrag(View v) { 973 // Enable HW layers 974 mSv.addHwLayersRefCount(); 975 // Disallow parents from intercepting touch events 976 final ViewParent parent = mSv.getParent(); 977 if (parent != null) { 978 parent.requestDisallowInterceptTouchEvent(true); 979 } 980 } 981 982 @Override 983 public void onChildDismissed(View v) { 984 TaskView tv = (TaskView) v; 985 Task task = tv.getTask(); 986 Activity activity = (Activity) mSv.getContext(); 987 988 // We have to disable the listener to ensure that we 989 // don't hit this again 990 tv.animate().setListener(null); 991 992 // Remove the task from the view 993 mSv.mStack.removeTask(task); 994 995 // Remove any stored data from the loader 996 RecentsTaskLoader loader = RecentsTaskLoader.getInstance(); 997 loader.deleteTaskData(task); 998 999 // Remove the task from activity manager 1000 final ActivityManager am = (ActivityManager) 1001 activity.getSystemService(Context.ACTIVITY_SERVICE); 1002 if (am != null) { 1003 am.removeTask(tv.getTask().key.id, 1004 ActivityManager.REMOVE_TASK_KILL_PROCESS); 1005 } 1006 1007 // If there are no remaining tasks, then just close the activity 1008 if (mSv.mStack.getTaskCount() == 0) { 1009 activity.finish(); 1010 } 1011 1012 // Disable HW layers 1013 mSv.decHwLayersRefCount(); 1014 } 1015 1016 @Override 1017 public void onSnapBackCompleted(View v) { 1018 // Do Nothing 1019 } 1020 1021 @Override 1022 public void onDragCancelled(View v) { 1023 // Disable HW layers 1024 mSv.decHwLayersRefCount(); 1025 } 1026} 1027