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