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