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