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