TaskStackView.java revision ebfc6981828b0699eef85c58b23a61f2cac41af3
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.content.ComponentName; 20import android.content.Context; 21import android.graphics.Rect; 22import android.view.LayoutInflater; 23import android.view.MotionEvent; 24import android.view.View; 25import android.widget.FrameLayout; 26import com.android.systemui.R; 27import com.android.systemui.recents.Constants; 28import com.android.systemui.recents.RecentsConfiguration; 29import com.android.systemui.recents.misc.DozeTrigger; 30import com.android.systemui.recents.model.RecentsPackageMonitor; 31import com.android.systemui.recents.model.RecentsTaskLoader; 32import com.android.systemui.recents.model.Task; 33import com.android.systemui.recents.model.TaskStack; 34 35import java.util.ArrayList; 36import java.util.HashMap; 37import java.util.HashSet; 38 39 40/* The visual representation of a task stack view */ 41public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCallbacks, 42 TaskView.TaskViewCallbacks, TaskStackViewScroller.TaskStackViewScrollerCallbacks, 43 ViewPool.ViewPoolConsumer<TaskView, Task>, RecentsPackageMonitor.PackageCallbacks { 44 45 /** The TaskView callbacks */ 46 interface TaskStackViewCallbacks { 47 public void onTaskViewClicked(TaskStackView stackView, TaskView tv, TaskStack stack, Task t, 48 boolean lockToTask); 49 public void onTaskViewAppInfoClicked(Task t); 50 public void onTaskViewDismissed(Task t); 51 public void onAllTaskViewsDismissed(); 52 public void onTaskStackFilterTriggered(); 53 public void onTaskStackUnfilterTriggered(); 54 } 55 56 RecentsConfiguration mConfig; 57 58 TaskStack mStack; 59 TaskStackViewLayoutAlgorithm mLayoutAlgorithm; 60 TaskStackViewFilterAlgorithm mFilterAlgorithm; 61 TaskStackViewScroller mStackScroller; 62 TaskStackViewTouchHandler mTouchHandler; 63 TaskStackViewCallbacks mCb; 64 ViewPool<TaskView, Task> mViewPool; 65 ArrayList<TaskViewTransform> mCurrentTaskTransforms = new ArrayList<TaskViewTransform>(); 66 DozeTrigger mUIDozeTrigger; 67 DebugOverlayView mDebugOverlay; 68 Rect mTaskStackBounds = new Rect(); 69 int mFocusedTaskIndex = -1; 70 71 // Optimizations 72 int mStackViewsAnimationDuration; 73 boolean mStackViewsDirty = true; 74 boolean mAwaitingFirstLayout = true; 75 boolean mStartEnterAnimationRequestedAfterLayout; 76 boolean mStartEnterAnimationCompleted; 77 ViewAnimation.TaskViewEnterContext mStartEnterAnimationContext; 78 int[] mTmpVisibleRange = new int[2]; 79 TaskViewTransform mTmpTransform = new TaskViewTransform(); 80 HashMap<Task, TaskView> mTmpTaskViewMap = new HashMap<Task, TaskView>(); 81 LayoutInflater mInflater; 82 83 // A convenience runnable to return all views to the pool 84 Runnable mReturnAllViewsToPoolRunnable = new Runnable() { 85 @Override 86 public void run() { 87 int childCount = getChildCount(); 88 for (int i = childCount - 1; i >= 0; i--) { 89 TaskView tv = (TaskView) getChildAt(i); 90 mViewPool.returnViewToPool(tv); 91 // Also hide the view since we don't need it anymore 92 tv.setVisibility(View.INVISIBLE); 93 } 94 } 95 }; 96 97 public TaskStackView(Context context, TaskStack stack) { 98 super(context); 99 mConfig = RecentsConfiguration.getInstance(); 100 mStack = stack; 101 mStack.setCallbacks(this); 102 mViewPool = new ViewPool<TaskView, Task>(context, this); 103 mInflater = LayoutInflater.from(context); 104 mLayoutAlgorithm = new TaskStackViewLayoutAlgorithm(mConfig); 105 mFilterAlgorithm = new TaskStackViewFilterAlgorithm(mConfig, this, mViewPool); 106 mStackScroller = new TaskStackViewScroller(context, mConfig, mLayoutAlgorithm); 107 mStackScroller.setCallbacks(this); 108 mTouchHandler = new TaskStackViewTouchHandler(context, this, mConfig, mStackScroller); 109 mUIDozeTrigger = new DozeTrigger(mConfig.taskBarDismissDozeDelaySeconds, new Runnable() { 110 @Override 111 public void run() { 112 // Show the task bar dismiss buttons 113 int childCount = getChildCount(); 114 for (int i = 0; i < childCount; i++) { 115 TaskView tv = (TaskView) getChildAt(i); 116 tv.startNoUserInteractionAnimation(); 117 } 118 } 119 }); 120 } 121 122 /** Sets the callbacks */ 123 void setCallbacks(TaskStackViewCallbacks cb) { 124 mCb = cb; 125 } 126 127 /** Sets the debug overlay */ 128 public void setDebugOverlay(DebugOverlayView overlay) { 129 mDebugOverlay = overlay; 130 } 131 132 /** Requests that the views be synchronized with the model */ 133 void requestSynchronizeStackViewsWithModel() { 134 requestSynchronizeStackViewsWithModel(0); 135 } 136 void requestSynchronizeStackViewsWithModel(int duration) { 137 if (!mStackViewsDirty) { 138 invalidate(); 139 mStackViewsDirty = true; 140 } 141 if (mAwaitingFirstLayout) { 142 // Skip the animation if we are awaiting first layout 143 mStackViewsAnimationDuration = 0; 144 } else { 145 mStackViewsAnimationDuration = Math.max(mStackViewsAnimationDuration, duration); 146 } 147 } 148 149 /** Finds the child view given a specific task. */ 150 public TaskView getChildViewForTask(Task t) { 151 int childCount = getChildCount(); 152 for (int i = 0; i < childCount; i++) { 153 TaskView tv = (TaskView) getChildAt(i); 154 if (tv.getTask() == t) { 155 return tv; 156 } 157 } 158 return null; 159 } 160 161 /** Returns the stack algorithm for this task stack. */ 162 public TaskStackViewLayoutAlgorithm getStackAlgorithm() { 163 return mLayoutAlgorithm; 164 } 165 166 /** 167 * Gets the stack transforms of a list of tasks, and returns the visible range of tasks. 168 */ 169 private boolean updateStackTransforms(ArrayList<TaskViewTransform> taskTransforms, 170 ArrayList<Task> tasks, 171 float stackScroll, 172 int[] visibleRangeOut, 173 boolean boundTranslationsToRect) { 174 // XXX: We should be intelligent about where to look for the visible stack range using the 175 // current stack scroll. 176 // XXX: We should log extra cases like the ones below where we don't expect to hit very often 177 // XXX: Print out approximately how many indices we have to go through to find the first visible transform 178 179 int taskTransformCount = taskTransforms.size(); 180 int taskCount = tasks.size(); 181 int frontMostVisibleIndex = -1; 182 int backMostVisibleIndex = -1; 183 184 // We can reuse the task transforms where possible to reduce object allocation 185 if (taskTransformCount < taskCount) { 186 // If there are less transforms than tasks, then add as many transforms as necessary 187 for (int i = taskTransformCount; i < taskCount; i++) { 188 taskTransforms.add(new TaskViewTransform()); 189 } 190 } else if (taskTransformCount > taskCount) { 191 // If there are more transforms than tasks, then just subset the transform list 192 taskTransforms.subList(0, taskCount); 193 } 194 195 // Update the stack transforms 196 TaskViewTransform prevTransform = null; 197 for (int i = taskCount - 1; i >= 0; i--) { 198 TaskViewTransform transform = mLayoutAlgorithm.getStackTransform(tasks.get(i), 199 stackScroll, taskTransforms.get(i), prevTransform); 200 if (transform.visible) { 201 if (frontMostVisibleIndex < 0) { 202 frontMostVisibleIndex = i; 203 } 204 backMostVisibleIndex = i; 205 } else { 206 if (backMostVisibleIndex != -1) { 207 // We've reached the end of the visible range, so going down the rest of the 208 // stack, we can just reset the transforms accordingly 209 while (i >= 0) { 210 taskTransforms.get(i).reset(); 211 i--; 212 } 213 break; 214 } 215 } 216 217 if (boundTranslationsToRect) { 218 transform.translationY = Math.min(transform.translationY, 219 mLayoutAlgorithm.mViewRect.bottom); 220 } 221 prevTransform = transform; 222 } 223 if (visibleRangeOut != null) { 224 visibleRangeOut[0] = frontMostVisibleIndex; 225 visibleRangeOut[1] = backMostVisibleIndex; 226 } 227 return frontMostVisibleIndex != -1 && backMostVisibleIndex != -1; 228 } 229 230 /** 231 * Gets the stack transforms of a list of tasks, and returns the visible range of tasks. This 232 * call is less optimal than calling updateStackTransforms directly. 233 */ 234 private ArrayList<TaskViewTransform> getStackTransforms(ArrayList<Task> tasks, 235 float stackScroll, 236 int[] visibleRangeOut, 237 boolean boundTranslationsToRect) { 238 ArrayList<TaskViewTransform> taskTransforms = new ArrayList<TaskViewTransform>(); 239 updateStackTransforms(taskTransforms, tasks, stackScroll, visibleRangeOut, 240 boundTranslationsToRect); 241 return taskTransforms; 242 } 243 244 /** Synchronizes the views with the model */ 245 boolean synchronizeStackViewsWithModel() { 246 if (mStackViewsDirty) { 247 // Get all the task transforms 248 ArrayList<Task> tasks = mStack.getTasks(); 249 float stackScroll = mStackScroller.getStackScroll(); 250 int[] visibleRange = mTmpVisibleRange; 251 boolean isValidVisibleRange = updateStackTransforms(mCurrentTaskTransforms, tasks, 252 stackScroll, visibleRange, false); 253 if (mDebugOverlay != null) { 254 mDebugOverlay.setText("vis[" + visibleRange[1] + "-" + visibleRange[0] + "]"); 255 } 256 257 // Return all the invisible children to the pool 258 mTmpTaskViewMap.clear(); 259 int childCount = getChildCount(); 260 for (int i = childCount - 1; i >= 0; i--) { 261 TaskView tv = (TaskView) getChildAt(i); 262 Task task = tv.getTask(); 263 int taskIndex = mStack.indexOfTask(task); 264 if (visibleRange[1] <= taskIndex && taskIndex <= visibleRange[0]) { 265 mTmpTaskViewMap.put(task, tv); 266 } else { 267 mViewPool.returnViewToPool(tv); 268 } 269 } 270 271 // Pick up all the newly visible children and update all the existing children 272 for (int i = visibleRange[0]; isValidVisibleRange && i >= visibleRange[1]; i--) { 273 Task task = tasks.get(i); 274 TaskViewTransform transform = mCurrentTaskTransforms.get(i); 275 TaskView tv = mTmpTaskViewMap.get(task); 276 int taskIndex = mStack.indexOfTask(task); 277 278 if (tv == null) { 279 tv = mViewPool.pickUpViewFromPool(task, task); 280 281 if (mStackViewsAnimationDuration > 0) { 282 // For items in the list, put them in start animating them from the 283 // approriate ends of the list where they are expected to appear 284 if (Float.compare(transform.p, 0f) <= 0) { 285 mLayoutAlgorithm.getStackTransform(0f, 0f, mTmpTransform, null); 286 } else { 287 mLayoutAlgorithm.getStackTransform(1f, 0f, mTmpTransform, null); 288 } 289 tv.updateViewPropertiesToTaskTransform(mTmpTransform, 0); 290 } 291 } 292 293 // Animate the task into place 294 tv.updateViewPropertiesToTaskTransform(mCurrentTaskTransforms.get(taskIndex), 295 mStackViewsAnimationDuration); 296 } 297 298 // Reset the request-synchronize params 299 mStackViewsAnimationDuration = 0; 300 mStackViewsDirty = false; 301 return true; 302 } 303 return false; 304 } 305 306 /** Updates the clip for each of the task views. */ 307 void clipTaskViews() { 308 // Update the clip on each task child 309 if (Constants.DebugFlags.App.EnableTaskStackClipping) { 310 int childCount = getChildCount(); 311 for (int i = 0; i < childCount - 1; i++) { 312 TaskView tv = (TaskView) getChildAt(i); 313 TaskView nextTv = null; 314 TaskView tmpTv = null; 315 int clipBottom = 0; 316 if (tv.shouldClipViewInStack()) { 317 // Find the next view to clip against 318 int nextIndex = i; 319 while (nextIndex < getChildCount()) { 320 tmpTv = (TaskView) getChildAt(++nextIndex); 321 if (tmpTv != null && tmpTv.shouldClipViewInStack()) { 322 nextTv = tmpTv; 323 break; 324 } 325 } 326 327 // Clip against the next view, this is just an approximation since we are 328 // stacked and we can make assumptions about the visibility of the this 329 // task relative to the ones in front of it. 330 if (nextTv != null) { 331 // We can reuse the current task transforms to find the task rects 332 TaskViewTransform transform = mCurrentTaskTransforms.get(mStack.indexOfTask(tv.getTask())); 333 TaskViewTransform nextTransform = mCurrentTaskTransforms.get(mStack.indexOfTask(nextTv.getTask())); 334 clipBottom = transform.rect.bottom - nextTransform.rect.top; 335 } 336 } 337 tv.getViewBounds().setClipBottom(clipBottom); 338 } 339 if (getChildCount() > 0) { 340 // The front most task should never be clipped 341 TaskView tv = (TaskView) getChildAt(getChildCount() - 1); 342 tv.getViewBounds().setClipBottom(0); 343 } 344 } 345 } 346 347 /** The stack insets to apply to the stack contents */ 348 public void setStackInsetRect(Rect r) { 349 mTaskStackBounds.set(r); 350 } 351 352 /** Updates the min and max virtual scroll bounds */ 353 void updateMinMaxScroll(boolean boundScrollToNewMinMax, boolean launchedWithAltTab, 354 boolean launchedFromHome) { 355 // Compute the min and max scroll values 356 mLayoutAlgorithm.computeMinMaxScroll(mStack.getTasks(), launchedWithAltTab, launchedFromHome); 357 358 // Debug logging 359 if (boundScrollToNewMinMax) { 360 mStackScroller.boundScroll(); 361 } 362 } 363 364 /** Returns the scroller. */ 365 public TaskStackViewScroller getScroller() { 366 return mStackScroller; 367 } 368 369 /** Focuses the task at the specified index in the stack */ 370 void focusTask(int taskIndex, boolean scrollToNewPosition) { 371 // Return early if the task is already focused 372 if (taskIndex == mFocusedTaskIndex) return; 373 374 if (0 <= taskIndex && taskIndex < mStack.getTaskCount()) { 375 mFocusedTaskIndex = taskIndex; 376 377 // Focus the view if possible, otherwise, focus the view after we scroll into position 378 Task t = mStack.getTasks().get(taskIndex); 379 TaskView tv = getChildViewForTask(t); 380 Runnable postScrollRunnable = null; 381 if (tv != null) { 382 tv.setFocusedTask(); 383 } else { 384 postScrollRunnable = new Runnable() { 385 @Override 386 public void run() { 387 Task t = mStack.getTasks().get(mFocusedTaskIndex); 388 TaskView tv = getChildViewForTask(t); 389 if (tv != null) { 390 tv.setFocusedTask(); 391 } 392 } 393 }; 394 } 395 396 // Scroll the view into position (just center it in the curve) 397 if (scrollToNewPosition) { 398 float newScroll = mLayoutAlgorithm.getStackScrollForTask(t) - 0.5f; 399 newScroll = mStackScroller.getBoundedStackScroll(newScroll); 400 mStackScroller.animateScroll(mStackScroller.getStackScroll(), newScroll, postScrollRunnable); 401 } else { 402 if (postScrollRunnable != null) { 403 postScrollRunnable.run(); 404 } 405 } 406 407 } 408 } 409 410 /** Focuses the next task in the stack */ 411 void focusNextTask(boolean forward) { 412 // Find the next index to focus 413 int numTasks = mStack.getTaskCount(); 414 if (numTasks == 0) return; 415 416 int nextFocusIndex = numTasks - 1; 417 if (0 <= mFocusedTaskIndex && mFocusedTaskIndex < numTasks) { 418 nextFocusIndex = Math.max(0, Math.min(numTasks - 1, 419 mFocusedTaskIndex + (forward ? -1 : 1))); 420 } 421 focusTask(nextFocusIndex, true); 422 } 423 424 /** Dismisses the focused task. */ 425 public void dismissFocusedTask() { 426 // Return early if there is no focused task index 427 if (mFocusedTaskIndex < 0) return; 428 429 Task t = mStack.getTasks().get(mFocusedTaskIndex); 430 TaskView tv = getChildViewForTask(t); 431 tv.dismissTask(); 432 } 433 434 @Override 435 public boolean onInterceptTouchEvent(MotionEvent ev) { 436 return mTouchHandler.onInterceptTouchEvent(ev); 437 } 438 439 @Override 440 public boolean onTouchEvent(MotionEvent ev) { 441 return mTouchHandler.onTouchEvent(ev); 442 } 443 444 @Override 445 public void computeScroll() { 446 mStackScroller.computeScroll(); 447 // Synchronize the views 448 synchronizeStackViewsWithModel(); 449 clipTaskViews(); 450 } 451 452 /** Computes the stack and task rects */ 453 public void computeRects(int windowWidth, int windowHeight, Rect taskStackBounds, 454 boolean launchedWithAltTab, boolean launchedFromHome) { 455 // Compute the rects in the stack algorithm 456 mLayoutAlgorithm.computeRects(windowWidth, windowHeight, taskStackBounds); 457 458 // Update the scroll bounds 459 updateMinMaxScroll(false, launchedWithAltTab, launchedFromHome); 460 } 461 462 /** 463 * This is ONLY used from AlternateRecentsComponent to update the dummy stack view for purposes 464 * of getting the task rect to animate to. 465 */ 466 public void updateMinMaxScrollForStack(TaskStack stack, boolean launchedWithAltTab, 467 boolean launchedFromHome) { 468 mStack = stack; 469 updateMinMaxScroll(false, launchedWithAltTab, launchedFromHome); 470 } 471 472 /** 473 * This is called with the full window width and height to allow stack view children to 474 * perform the full screen transition down. 475 */ 476 @Override 477 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 478 int width = MeasureSpec.getSize(widthMeasureSpec); 479 int height = MeasureSpec.getSize(heightMeasureSpec); 480 481 // Compute our stack/task rects 482 Rect taskStackBounds = new Rect(mTaskStackBounds); 483 taskStackBounds.bottom -= mConfig.systemInsets.bottom; 484 computeRects(width, height, taskStackBounds, mConfig.launchedWithAltTab, 485 mConfig.launchedFromHome); 486 487 // If this is the first layout, then scroll to the front of the stack and synchronize the 488 // stack views immediately to load all the views 489 if (mAwaitingFirstLayout) { 490 mStackScroller.setStackScrollToInitialState(); 491 requestSynchronizeStackViewsWithModel(); 492 synchronizeStackViewsWithModel(); 493 } 494 495 // Measure each of the TaskViews 496 int childCount = getChildCount(); 497 for (int i = 0; i < childCount; i++) { 498 TaskView tv = (TaskView) getChildAt(i); 499 if (tv.isFullScreenView()) { 500 tv.measure(widthMeasureSpec, heightMeasureSpec); 501 } else { 502 tv.measure( 503 MeasureSpec.makeMeasureSpec(mLayoutAlgorithm.mTaskRect.width(), 504 MeasureSpec.EXACTLY), 505 MeasureSpec.makeMeasureSpec(mLayoutAlgorithm.mTaskRect.height() + 506 tv.getMaxFooterHeight(), MeasureSpec.EXACTLY)); 507 } 508 } 509 510 setMeasuredDimension(width, height); 511 } 512 513 /** 514 * This is called with the size of the space not including the top or right insets, or the 515 * search bar height in portrait (but including the search bar width in landscape, since we want 516 * to draw under it. 517 */ 518 @Override 519 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 520 // Layout each of the children 521 int childCount = getChildCount(); 522 for (int i = 0; i < childCount; i++) { 523 TaskView tv = (TaskView) getChildAt(i); 524 if (tv.isFullScreenView()) { 525 tv.layout(left, top, left + tv.getMeasuredWidth(), top + tv.getMeasuredHeight()); 526 } else { 527 tv.layout(mLayoutAlgorithm.mTaskRect.left, mLayoutAlgorithm.mTaskRect.top, 528 mLayoutAlgorithm.mTaskRect.right, mLayoutAlgorithm.mTaskRect.bottom + 529 tv.getMaxFooterHeight()); 530 } 531 } 532 533 if (mAwaitingFirstLayout) { 534 mAwaitingFirstLayout = false; 535 onFirstLayout(); 536 } 537 } 538 539 /** Handler for the first layout. */ 540 void onFirstLayout() { 541 int offscreenY = mLayoutAlgorithm.mViewRect.bottom - 542 (mLayoutAlgorithm.mTaskRect.top - mLayoutAlgorithm.mViewRect.top); 543 544 // Find the launch target task 545 Task launchTargetTask = null; 546 int childCount = getChildCount(); 547 for (int i = childCount - 1; i >= 0; i--) { 548 TaskView tv = (TaskView) getChildAt(i); 549 Task task = tv.getTask(); 550 if (task.isLaunchTarget) { 551 launchTargetTask = task; 552 break; 553 } 554 } 555 556 // Prepare the first view for its enter animation 557 for (int i = childCount - 1; i >= 0; i--) { 558 TaskView tv = (TaskView) getChildAt(i); 559 Task task = tv.getTask(); 560 boolean occludesLaunchTarget = (launchTargetTask != null) && 561 launchTargetTask.group.isTaskAboveTask(task, launchTargetTask); 562 tv.prepareEnterRecentsAnimation(task.isLaunchTarget, occludesLaunchTarget, offscreenY); 563 } 564 565 // If the enter animation started already and we haven't completed a layout yet, do the 566 // enter animation now 567 if (mStartEnterAnimationRequestedAfterLayout) { 568 startEnterRecentsAnimation(mStartEnterAnimationContext); 569 mStartEnterAnimationRequestedAfterLayout = false; 570 mStartEnterAnimationContext = null; 571 } 572 573 // When Alt-Tabbing, we scroll to and focus the previous task 574 if (mConfig.launchedWithAltTab) { 575 if (mConfig.launchedFromHome) { 576 focusTask(Math.max(0, mStack.getTaskCount() - 1), false); 577 } else { 578 focusTask(Math.max(0, mStack.getTaskCount() - 2), false); 579 } 580 } 581 } 582 583 /** Requests this task stacks to start it's enter-recents animation */ 584 public void startEnterRecentsAnimation(ViewAnimation.TaskViewEnterContext ctx) { 585 // If we are still waiting to layout, then just defer until then 586 if (mAwaitingFirstLayout) { 587 mStartEnterAnimationRequestedAfterLayout = true; 588 mStartEnterAnimationContext = ctx; 589 return; 590 } 591 592 if (mStack.getTaskCount() > 0) { 593 // Find the launch target task 594 Task launchTargetTask = null; 595 int childCount = getChildCount(); 596 for (int i = childCount - 1; i >= 0; i--) { 597 TaskView tv = (TaskView) getChildAt(i); 598 Task task = tv.getTask(); 599 if (task.isLaunchTarget) { 600 launchTargetTask = task; 601 break; 602 } 603 } 604 605 // Animate all the task views into view 606 for (int i = childCount - 1; i >= 0; i--) { 607 TaskView tv = (TaskView) getChildAt(i); 608 Task task = tv.getTask(); 609 ctx.currentTaskTransform = new TaskViewTransform(); 610 ctx.currentStackViewIndex = i; 611 ctx.currentStackViewCount = childCount; 612 ctx.currentTaskRect = mLayoutAlgorithm.mTaskRect; 613 ctx.currentTaskOccludesLaunchTarget = (launchTargetTask != null) && 614 launchTargetTask.group.isTaskAboveTask(task, launchTargetTask); 615 mLayoutAlgorithm.getStackTransform(task, mStackScroller.getStackScroll(), ctx.currentTaskTransform, null); 616 tv.startEnterRecentsAnimation(ctx); 617 } 618 619 // Add a runnable to the post animation ref counter to clear all the views 620 ctx.postAnimationTrigger.addLastDecrementRunnable(new Runnable() { 621 @Override 622 public void run() { 623 mStartEnterAnimationCompleted = true; 624 // Start dozing 625 mUIDozeTrigger.startDozing(); 626 } 627 }); 628 } 629 } 630 631 /** Requests this task stacks to start it's exit-recents animation. */ 632 public void startExitToHomeAnimation(ViewAnimation.TaskViewExitContext ctx) { 633 // Stop any scrolling 634 mStackScroller.stopScroller(); 635 mStackScroller.stopBoundScrollAnimation(); 636 // Animate all the task views out of view 637 ctx.offscreenTranslationY = mLayoutAlgorithm.mViewRect.bottom - 638 (mLayoutAlgorithm.mTaskRect.top - mLayoutAlgorithm.mViewRect.top); 639 int childCount = getChildCount(); 640 for (int i = 0; i < childCount; i++) { 641 TaskView tv = (TaskView) getChildAt(i); 642 tv.startExitToHomeAnimation(ctx); 643 } 644 645 // Add a runnable to the post animation ref counter to clear all the views 646 ctx.postAnimationTrigger.addLastDecrementRunnable(mReturnAllViewsToPoolRunnable); 647 } 648 649 /** Animates a task view in this stack as it launches. */ 650 public void startLaunchTaskAnimation(TaskView tv, Runnable r, boolean lockToTask) { 651 Task launchTargetTask = tv.getTask(); 652 int childCount = getChildCount(); 653 for (int i = 0; i < childCount; i++) { 654 TaskView t = (TaskView) getChildAt(i); 655 if (t == tv) { 656 t.startLaunchTaskAnimation(r, true, true, lockToTask); 657 } else { 658 boolean occludesLaunchTarget = launchTargetTask.group.isTaskAboveTask(t.getTask(), 659 launchTargetTask); 660 t.startLaunchTaskAnimation(null, false, occludesLaunchTarget, lockToTask); 661 } 662 } 663 } 664 665 public boolean isTransformedTouchPointInView(float x, float y, View child) { 666 return isTransformedTouchPointInView(x, y, child, null); 667 } 668 669 /** Pokes the dozer on user interaction. */ 670 void onUserInteraction() { 671 // Poke the doze trigger if it is dozing 672 mUIDozeTrigger.poke(); 673 } 674 675 /**** TaskStackCallbacks Implementation ****/ 676 677 @Override 678 public void onStackTaskAdded(TaskStack stack, Task t) { 679 requestSynchronizeStackViewsWithModel(); 680 } 681 682 @Override 683 public void onStackTaskRemoved(TaskStack stack, Task removedTask, Task newFrontMostTask) { 684 // Remove the view associated with this task, we can't rely on updateTransforms 685 // to work here because the task is no longer in the list 686 TaskView tv = getChildViewForTask(removedTask); 687 if (tv != null) { 688 mViewPool.returnViewToPool(tv); 689 } 690 691 // Notify the callback that we've removed the task and it can clean up after it 692 mCb.onTaskViewDismissed(removedTask); 693 694 // Get the stack scroll of the task to anchor to (since we are removing something, the front 695 // most task will be our anchor task) 696 Task anchorTask = null; 697 float prevAnchorTaskScroll = 0; 698 boolean pullStackForward = stack.getTaskCount() > 0; 699 if (pullStackForward) { 700 anchorTask = mStack.getFrontMostTask(); 701 prevAnchorTaskScroll = mLayoutAlgorithm.getStackScrollForTask(anchorTask); 702 } 703 704 // Update the min/max scroll and animate other task views into their new positions 705 updateMinMaxScroll(true, mConfig.launchedWithAltTab, mConfig.launchedFromHome); 706 707 // Offset the stack by as much as the anchor task would otherwise move back 708 if (pullStackForward) { 709 float anchorTaskScroll = mLayoutAlgorithm.getStackScrollForTask(anchorTask); 710 mStackScroller.setStackScroll(mStackScroller.getStackScroll() + (anchorTaskScroll 711 - prevAnchorTaskScroll)); 712 mStackScroller.boundScroll(); 713 } 714 715 // Animate all the tasks into place 716 requestSynchronizeStackViewsWithModel(200); 717 718 // Update the new front most task 719 if (newFrontMostTask != null) { 720 TaskView frontTv = getChildViewForTask(newFrontMostTask); 721 if (frontTv != null) { 722 frontTv.onTaskBound(newFrontMostTask); 723 } 724 } 725 726 // If there are no remaining tasks, then either unfilter the current stack, or just close 727 // the activity if there are no filtered stacks 728 if (mStack.getTaskCount() == 0) { 729 boolean shouldFinishActivity = true; 730 if (mStack.hasFilteredTasks()) { 731 mStack.unfilterTasks(); 732 shouldFinishActivity = (mStack.getTaskCount() == 0); 733 } 734 if (shouldFinishActivity) { 735 mCb.onAllTaskViewsDismissed(); 736 } 737 } 738 } 739 740 @Override 741 public void onStackFiltered(TaskStack newStack, final ArrayList<Task> curTasks, 742 Task filteredTask) { 743 /* 744 // Stash the scroll and filtered task for us to restore to when we unfilter 745 mStashedScroll = getStackScroll(); 746 747 // Calculate the current task transforms 748 ArrayList<TaskViewTransform> curTaskTransforms = 749 getStackTransforms(curTasks, getStackScroll(), null, true); 750 751 // Update the task offsets 752 mLayoutAlgorithm.updateTaskOffsets(mStack.getTasks()); 753 754 // Scroll the item to the top of the stack (sans-peek) rect so that we can see it better 755 updateMinMaxScroll(false); 756 float overlapHeight = mLayoutAlgorithm.getTaskOverlapHeight(); 757 setStackScrollRaw((int) (newStack.indexOfTask(filteredTask) * overlapHeight)); 758 boundScrollRaw(); 759 760 // Compute the transforms of the items in the new stack after setting the new scroll 761 final ArrayList<Task> tasks = mStack.getTasks(); 762 final ArrayList<TaskViewTransform> taskTransforms = 763 getStackTransforms(mStack.getTasks(), getStackScroll(), null, true); 764 765 // Animate 766 mFilterAlgorithm.startFilteringAnimation(curTasks, curTaskTransforms, tasks, taskTransforms); 767 768 // Notify any callbacks 769 mCb.onTaskStackFilterTriggered(); 770 */ 771 } 772 773 @Override 774 public void onStackUnfiltered(TaskStack newStack, final ArrayList<Task> curTasks) { 775 /* 776 // Calculate the current task transforms 777 final ArrayList<TaskViewTransform> curTaskTransforms = 778 getStackTransforms(curTasks, getStackScroll(), null, true); 779 780 // Update the task offsets 781 mLayoutAlgorithm.updateTaskOffsets(mStack.getTasks()); 782 783 // Restore the stashed scroll 784 updateMinMaxScroll(false); 785 setStackScrollRaw(mStashedScroll); 786 boundScrollRaw(); 787 788 // Compute the transforms of the items in the new stack after restoring the stashed scroll 789 final ArrayList<Task> tasks = mStack.getTasks(); 790 final ArrayList<TaskViewTransform> taskTransforms = 791 getStackTransforms(tasks, getStackScroll(), null, true); 792 793 // Animate 794 mFilterAlgorithm.startFilteringAnimation(curTasks, curTaskTransforms, tasks, taskTransforms); 795 796 // Clear the saved vars 797 mStashedScroll = 0; 798 799 // Notify any callbacks 800 mCb.onTaskStackUnfilterTriggered(); 801 */ 802 } 803 804 /**** ViewPoolConsumer Implementation ****/ 805 806 @Override 807 public TaskView createView(Context context) { 808 return (TaskView) mInflater.inflate(R.layout.recents_task_view, this, false); 809 } 810 811 @Override 812 public void prepareViewToEnterPool(TaskView tv) { 813 Task task = tv.getTask(); 814 815 // Report that this tasks's data is no longer being used 816 RecentsTaskLoader.getInstance().unloadTaskData(task); 817 818 // Detach the view from the hierarchy 819 detachViewFromParent(tv); 820 821 // Reset the view properties 822 tv.resetViewProperties(); 823 } 824 825 @Override 826 public void prepareViewToLeavePool(TaskView tv, Task task, boolean isNewView) { 827 // Rebind the task and request that this task's data be filled into the TaskView 828 tv.onTaskBound(task); 829 830 // Mark the launch task as fullscreen 831 if (Constants.DebugFlags.App.EnableScreenshotAppTransition && mAwaitingFirstLayout) { 832 if (task.isLaunchTarget) { 833 tv.setIsFullScreen(true); 834 } 835 } 836 837 // Load the task data 838 RecentsTaskLoader.getInstance().loadTaskData(task); 839 840 // Sanity check, the task view should always be clipping against the stack at this point, 841 // but just in case, re-enable it here 842 tv.setClipViewInStack(true); 843 844 // If the doze trigger has already fired, then update the state for this task view 845 if (mUIDozeTrigger.hasTriggered()) { 846 tv.setNoUserInteractionState(); 847 } 848 849 // If we've finished the start animation, then ensure we always enable the focus animations 850 if (mStartEnterAnimationCompleted) { 851 tv.enableFocusAnimations(); 852 } 853 854 // Find the index where this task should be placed in the stack 855 int insertIndex = -1; 856 int taskIndex = mStack.indexOfTask(task); 857 if (taskIndex != -1) { 858 int childCount = getChildCount(); 859 for (int i = 0; i < childCount; i++) { 860 Task tvTask = ((TaskView) getChildAt(i)).getTask(); 861 if (taskIndex < mStack.indexOfTask(tvTask)) { 862 insertIndex = i; 863 break; 864 } 865 } 866 } 867 868 // Add/attach the view to the hierarchy 869 if (isNewView) { 870 addView(tv, insertIndex); 871 872 // Set the callbacks and listeners for this new view 873 tv.setTouchEnabled(true); 874 tv.setCallbacks(this); 875 } else { 876 attachViewToParent(tv, insertIndex, tv.getLayoutParams()); 877 } 878 } 879 880 @Override 881 public boolean hasPreferredData(TaskView tv, Task preferredData) { 882 return (tv.getTask() == preferredData); 883 } 884 885 /**** TaskViewCallbacks Implementation ****/ 886 887 @Override 888 public void onTaskViewAppIconClicked(TaskView tv) { 889 if (Constants.DebugFlags.App.EnableTaskFiltering) { 890 if (mStack.hasFilteredTasks()) { 891 mStack.unfilterTasks(); 892 } else { 893 mStack.filterTasks(tv.getTask()); 894 } 895 } 896 } 897 898 @Override 899 public void onTaskViewAppInfoClicked(TaskView tv) { 900 if (mCb != null) { 901 mCb.onTaskViewAppInfoClicked(tv.getTask()); 902 } 903 } 904 905 @Override 906 public void onTaskViewClicked(TaskView tv, Task task, boolean lockToTask) { 907 // Cancel any doze triggers 908 mUIDozeTrigger.stopDozing(); 909 910 if (mCb != null) { 911 mCb.onTaskViewClicked(this, tv, mStack, task, lockToTask); 912 } 913 } 914 915 @Override 916 public void onTaskViewDismissed(TaskView tv) { 917 Task task = tv.getTask(); 918 int taskIndex = mStack.indexOfTask(task); 919 boolean taskWasFocused = tv.isFocusedTask(); 920 // Announce for accessibility 921 tv.announceForAccessibility(getContext().getString(R.string.accessibility_recents_item_dismissed, 922 tv.getTask().activityLabel)); 923 // Remove the task from the view 924 mStack.removeTask(task); 925 // If the dismissed task was focused, then we should focus the next task in front 926 if (taskWasFocused) { 927 ArrayList<Task> tasks = mStack.getTasks(); 928 int nextTaskIndex = Math.min(tasks.size() - 1, taskIndex); 929 if (nextTaskIndex >= 0) { 930 Task nextTask = tasks.get(nextTaskIndex); 931 TaskView nextTv = getChildViewForTask(nextTask); 932 nextTv.setFocusedTask(); 933 } 934 } 935 } 936 937 @Override 938 public void onTaskViewClipStateChanged(TaskView tv) { 939 invalidate(); 940 } 941 942 @Override 943 public void onTaskViewFullScreenTransitionCompleted() { 944 requestSynchronizeStackViewsWithModel(); 945 } 946 947 @Override 948 public void onTaskViewFocusChanged(TaskView tv, boolean focused) { 949 if (focused) { 950 mFocusedTaskIndex = mStack.indexOfTask(tv.getTask()); 951 } 952 } 953 954 /**** TaskStackViewScroller.TaskStackViewScrollerCallbacks ****/ 955 956 @Override 957 public void onScrollChanged(float p) { 958 mUIDozeTrigger.poke(); 959 requestSynchronizeStackViewsWithModel(); 960 postInvalidateOnAnimation(); 961 } 962 963 /**** RecentsPackageMonitor.PackageCallbacks Implementation ****/ 964 965 @Override 966 public void onComponentRemoved(HashSet<ComponentName> cns) { 967 // For other tasks, just remove them directly if they no longer exist 968 ArrayList<Task> tasks = mStack.getTasks(); 969 for (int i = tasks.size() - 1; i >= 0; i--) { 970 final Task t = tasks.get(i); 971 if (cns.contains(t.key.baseIntent.getComponent())) { 972 TaskView tv = getChildViewForTask(t); 973 if (tv != null) { 974 // For visible children, defer removing the task until after the animation 975 tv.startDeleteTaskAnimation(new Runnable() { 976 @Override 977 public void run() { 978 mStack.removeTask(t); 979 } 980 }); 981 } else { 982 // Otherwise, remove the task from the stack immediately 983 mStack.removeTask(t); 984 } 985 } 986 } 987 } 988} 989