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.Canvas; 23 import android.graphics.Matrix; 24 import android.graphics.Rect; 25 import android.os.Bundle; 26 import android.view.LayoutInflater; 27 import android.view.MotionEvent; 28 import android.view.View; 29 import android.view.accessibility.AccessibilityEvent; 30 import android.view.accessibility.AccessibilityNodeInfo; 31 import android.widget.FrameLayout; 32 33 import com.android.internal.logging.MetricsLogger; 34 import com.android.systemui.R; 35 import com.android.systemui.recents.Constants; 36 import com.android.systemui.recents.RecentsConfiguration; 37 import com.android.systemui.recents.misc.DozeTrigger; 38 import com.android.systemui.recents.misc.SystemServicesProxy; 39 import com.android.systemui.recents.misc.Utilities; 40 import com.android.systemui.recents.model.RecentsPackageMonitor; 41 import com.android.systemui.recents.model.RecentsTaskLoader; 42 import com.android.systemui.recents.model.Task; 43 import com.android.systemui.recents.model.TaskStack; 44 import com.android.systemui.statusbar.DismissView; 45 46 import java.util.ArrayList; 47 import java.util.Collections; 48 import java.util.HashMap; 49 import java.util.HashSet; 50 import java.util.Iterator; 51 import java.util.List; 52 53 54 /* The visual representation of a task stack view */ 55 public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCallbacks, 56 TaskView.TaskViewCallbacks, TaskStackViewScroller.TaskStackViewScrollerCallbacks, 57 ViewPool.ViewPoolConsumer<TaskView, Task>, RecentsPackageMonitor.PackageCallbacks { 58 59 /** The TaskView callbacks */ 60 interface TaskStackViewCallbacks { 61 public void onTaskViewClicked(TaskStackView stackView, TaskView tv, TaskStack stack, Task t, 62 boolean lockToTask); 63 public void onTaskViewAppInfoClicked(Task t); 64 public void onTaskViewDismissed(Task t); 65 public void onAllTaskViewsDismissed(ArrayList<Task> removedTasks); 66 public void onTaskStackFilterTriggered(); 67 public void onTaskStackUnfilterTriggered(); 68 69 public void onTaskResize(Task t); 70 } 71 RecentsConfiguration mConfig; 72 73 TaskStack mStack; 74 TaskStackViewLayoutAlgorithm mLayoutAlgorithm; 75 TaskStackViewFilterAlgorithm mFilterAlgorithm; 76 TaskStackViewScroller mStackScroller; 77 TaskStackViewTouchHandler mTouchHandler; 78 TaskStackViewCallbacks mCb; 79 ViewPool<TaskView, Task> mViewPool; 80 ArrayList<TaskViewTransform> mCurrentTaskTransforms = new ArrayList<TaskViewTransform>(); 81 DozeTrigger mUIDozeTrigger; 82 DebugOverlayView mDebugOverlay; 83 Rect mTaskStackBounds = new Rect(); 84 DismissView mDismissAllButton; 85 boolean mDismissAllButtonAnimating; 86 int mFocusedTaskIndex = -1; 87 int mPrevAccessibilityFocusedIndex = -1; 88 // Optimizations 89 int mStackViewsAnimationDuration; 90 boolean mStackViewsDirty = true; 91 boolean mStackViewsClipDirty = true; 92 boolean mAwaitingFirstLayout = true; 93 boolean mStartEnterAnimationRequestedAfterLayout; 94 boolean mStartEnterAnimationCompleted; 95 ViewAnimation.TaskViewEnterContext mStartEnterAnimationContext; 96 int[] mTmpVisibleRange = new int[2]; 97 float[] mTmpCoord = new float[2]; 98 Matrix mTmpMatrix = new Matrix(); 99 Rect mTmpRect = new Rect(); 100 TaskViewTransform mTmpTransform = new TaskViewTransform(); 101 HashMap<Task, TaskView> mTmpTaskViewMap = new HashMap<Task, TaskView>(); 102 ArrayList<TaskView> mTaskViews = new ArrayList<TaskView>(); 103 List<TaskView> mImmutableTaskViews = new ArrayList<TaskView>(); 104 LayoutInflater mInflater; 105 boolean mLayersDisabled; 106 107 // A convenience update listener to request updating clipping of tasks 108 ValueAnimator.AnimatorUpdateListener mRequestUpdateClippingListener = 109 new ValueAnimator.AnimatorUpdateListener() { 110 @Override 111 public void onAnimationUpdate(ValueAnimator animation) { 112 requestUpdateStackViewsClip(); 113 } 114 }; 115 116 public TaskStackView(Context context, TaskStack stack) { 117 super(context); 118 // Set the stack first 119 setStack(stack); 120 mConfig = RecentsConfiguration.getInstance(); 121 mViewPool = new ViewPool<TaskView, Task>(context, this); 122 mInflater = LayoutInflater.from(context); 123 mLayoutAlgorithm = new TaskStackViewLayoutAlgorithm(mConfig); 124 mFilterAlgorithm = new TaskStackViewFilterAlgorithm(mConfig, this, mViewPool); 125 mStackScroller = new TaskStackViewScroller(context, mConfig, mLayoutAlgorithm); 126 mStackScroller.setCallbacks(this); 127 mTouchHandler = new TaskStackViewTouchHandler(context, this, mConfig, mStackScroller); 128 mUIDozeTrigger = new DozeTrigger(mConfig.taskBarDismissDozeDelaySeconds, new Runnable() { 129 @Override 130 public void run() { 131 // Show the task bar dismiss buttons 132 List<TaskView> taskViews = getTaskViews(); 133 int taskViewCount = taskViews.size(); 134 for (int i = 0; i < taskViewCount; i++) { 135 TaskView tv = taskViews.get(i); 136 tv.startNoUserInteractionAnimation(); 137 } 138 } 139 }); 140 setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES); 141 } 142 143 /** Sets the callbacks */ 144 void setCallbacks(TaskStackViewCallbacks cb) { 145 mCb = cb; 146 } 147 148 /** Sets the task stack */ 149 void setStack(TaskStack stack) { 150 // Set the new stack 151 mStack = stack; 152 if (mStack != null) { 153 mStack.setCallbacks(this); 154 } 155 // Layout again with the new stack 156 requestLayout(); 157 } 158 159 /** Returns the task stack. */ 160 TaskStack getStack() { 161 return mStack; 162 } 163 164 /** Sets the debug overlay */ 165 public void setDebugOverlay(DebugOverlayView overlay) { 166 mDebugOverlay = overlay; 167 } 168 169 /** Updates the list of task views */ 170 void updateTaskViewsList() { 171 mTaskViews.clear(); 172 int childCount = getChildCount(); 173 for (int i = 0; i < childCount; i++) { 174 View v = getChildAt(i); 175 if (v instanceof TaskView) { 176 mTaskViews.add((TaskView) v); 177 } 178 } 179 mImmutableTaskViews = Collections.unmodifiableList(mTaskViews); 180 } 181 182 /** Gets the list of task views */ 183 List<TaskView> getTaskViews() { 184 return mImmutableTaskViews; 185 } 186 187 /** Resets this TaskStackView for reuse. */ 188 void reset() { 189 // Reset the focused task 190 resetFocusedTask(); 191 192 // Return all the views to the pool 193 List<TaskView> taskViews = getTaskViews(); 194 int taskViewCount = taskViews.size(); 195 for (int i = taskViewCount - 1; i >= 0; i--) { 196 mViewPool.returnViewToPool(taskViews.get(i)); 197 } 198 199 // Mark each task view for relayout 200 if (mViewPool != null) { 201 Iterator<TaskView> iter = mViewPool.poolViewIterator(); 202 if (iter != null) { 203 while (iter.hasNext()) { 204 TaskView tv = iter.next(); 205 tv.reset(); 206 } 207 } 208 } 209 210 // Reset the stack state 211 mStack.reset(); 212 mStackViewsDirty = true; 213 mStackViewsClipDirty = true; 214 mAwaitingFirstLayout = true; 215 mPrevAccessibilityFocusedIndex = -1; 216 if (mUIDozeTrigger != null) { 217 mUIDozeTrigger.stopDozing(); 218 mUIDozeTrigger.resetTrigger(); 219 } 220 mStackScroller.reset(); 221 } 222 223 /** Requests that the views be synchronized with the model */ 224 void requestSynchronizeStackViewsWithModel() { 225 requestSynchronizeStackViewsWithModel(0); 226 } 227 void requestSynchronizeStackViewsWithModel(int duration) { 228 if (!mStackViewsDirty) { 229 invalidate(); 230 mStackViewsDirty = true; 231 } 232 if (mAwaitingFirstLayout) { 233 // Skip the animation if we are awaiting first layout 234 mStackViewsAnimationDuration = 0; 235 } else { 236 mStackViewsAnimationDuration = Math.max(mStackViewsAnimationDuration, duration); 237 } 238 } 239 240 /** Requests that the views clipping be updated. */ 241 void requestUpdateStackViewsClip() { 242 if (!mStackViewsClipDirty) { 243 invalidate(); 244 mStackViewsClipDirty = true; 245 } 246 } 247 248 /** Finds the child view given a specific task. */ 249 public TaskView getChildViewForTask(Task t) { 250 List<TaskView> taskViews = getTaskViews(); 251 int taskViewCount = taskViews.size(); 252 for (int i = 0; i < taskViewCount; i++) { 253 TaskView tv = taskViews.get(i); 254 if (tv.getTask() == t) { 255 return tv; 256 } 257 } 258 return null; 259 } 260 261 /** Returns the stack algorithm for this task stack. */ 262 public TaskStackViewLayoutAlgorithm getStackAlgorithm() { 263 return mLayoutAlgorithm; 264 } 265 266 /** 267 * Gets the stack transforms of a list of tasks, and returns the visible range of tasks. 268 */ 269 private boolean updateStackTransforms(ArrayList<TaskViewTransform> taskTransforms, 270 ArrayList<Task> tasks, 271 float stackScroll, 272 int[] visibleRangeOut, 273 boolean boundTranslationsToRect) { 274 int taskTransformCount = taskTransforms.size(); 275 int taskCount = tasks.size(); 276 int frontMostVisibleIndex = -1; 277 int backMostVisibleIndex = -1; 278 279 // We can reuse the task transforms where possible to reduce object allocation 280 if (taskTransformCount < taskCount) { 281 // If there are less transforms than tasks, then add as many transforms as necessary 282 for (int i = taskTransformCount; i < taskCount; i++) { 283 taskTransforms.add(new TaskViewTransform()); 284 } 285 } else if (taskTransformCount > taskCount) { 286 // If there are more transforms than tasks, then just subset the transform list 287 taskTransforms.subList(0, taskCount); 288 } 289 290 // Update the stack transforms 291 TaskViewTransform prevTransform = null; 292 for (int i = taskCount - 1; i >= 0; i--) { 293 TaskViewTransform transform = mLayoutAlgorithm.getStackTransform(tasks.get(i), 294 stackScroll, taskTransforms.get(i), prevTransform); 295 if (transform.visible) { 296 if (frontMostVisibleIndex < 0) { 297 frontMostVisibleIndex = i; 298 } 299 backMostVisibleIndex = i; 300 } else { 301 if (backMostVisibleIndex != -1) { 302 // We've reached the end of the visible range, so going down the rest of the 303 // stack, we can just reset the transforms accordingly 304 while (i >= 0) { 305 taskTransforms.get(i).reset(); 306 i--; 307 } 308 break; 309 } 310 } 311 312 if (boundTranslationsToRect) { 313 transform.translationY = Math.min(transform.translationY, 314 mLayoutAlgorithm.mViewRect.bottom); 315 } 316 prevTransform = transform; 317 } 318 if (visibleRangeOut != null) { 319 visibleRangeOut[0] = frontMostVisibleIndex; 320 visibleRangeOut[1] = backMostVisibleIndex; 321 } 322 return frontMostVisibleIndex != -1 && backMostVisibleIndex != -1; 323 } 324 325 /** Synchronizes the views with the model */ 326 boolean synchronizeStackViewsWithModel() { 327 if (mStackViewsDirty) { 328 RecentsTaskLoader loader = RecentsTaskLoader.getInstance(); 329 SystemServicesProxy ssp = loader.getSystemServicesProxy(); 330 331 // Get all the task transforms 332 ArrayList<Task> tasks = mStack.getTasks(); 333 float stackScroll = mStackScroller.getStackScroll(); 334 int[] visibleRange = mTmpVisibleRange; 335 boolean isValidVisibleRange = updateStackTransforms(mCurrentTaskTransforms, tasks, 336 stackScroll, visibleRange, false); 337 if (mDebugOverlay != null) { 338 mDebugOverlay.setText("vis[" + visibleRange[1] + "-" + visibleRange[0] + "]"); 339 } 340 341 // Inflate and add the dismiss button if necessary 342 if (Constants.DebugFlags.App.EnableDismissAll && mDismissAllButton == null) { 343 mDismissAllButton = (DismissView) 344 mInflater.inflate(R.layout.recents_dismiss_button, this, false); 345 mDismissAllButton.setOnButtonClickListener(new View.OnClickListener() { 346 @Override 347 public void onClick(View v) { 348 mStack.removeAllTasks(); 349 } 350 }); 351 addView(mDismissAllButton, 0); 352 } 353 354 // Return all the invisible children to the pool 355 mTmpTaskViewMap.clear(); 356 List<TaskView> taskViews = getTaskViews(); 357 int taskViewCount = taskViews.size(); 358 boolean reaquireAccessibilityFocus = false; 359 for (int i = taskViewCount - 1; i >= 0; i--) { 360 TaskView tv = taskViews.get(i); 361 Task task = tv.getTask(); 362 int taskIndex = mStack.indexOfTask(task); 363 if (visibleRange[1] <= taskIndex && taskIndex <= visibleRange[0]) { 364 mTmpTaskViewMap.put(task, tv); 365 } else { 366 mViewPool.returnViewToPool(tv); 367 reaquireAccessibilityFocus |= (i == mPrevAccessibilityFocusedIndex); 368 369 // Hide the dismiss button if the front most task is invisible 370 if (task == mStack.getFrontMostTask()) { 371 hideDismissAllButton(null); 372 } 373 } 374 } 375 376 // Pick up all the newly visible children and update all the existing children 377 for (int i = visibleRange[0]; isValidVisibleRange && i >= visibleRange[1]; i--) { 378 Task task = tasks.get(i); 379 TaskViewTransform transform = mCurrentTaskTransforms.get(i); 380 TaskView tv = mTmpTaskViewMap.get(task); 381 int taskIndex = mStack.indexOfTask(task); 382 383 if (tv == null) { 384 tv = mViewPool.pickUpViewFromPool(task, task); 385 if (mLayersDisabled) { 386 tv.disableLayersForOneFrame(); 387 } 388 if (mStackViewsAnimationDuration > 0) { 389 // For items in the list, put them in start animating them from the 390 // approriate ends of the list where they are expected to appear 391 if (Float.compare(transform.p, 0f) <= 0) { 392 mLayoutAlgorithm.getStackTransform(0f, 0f, mTmpTransform, null); 393 } else { 394 mLayoutAlgorithm.getStackTransform(1f, 0f, mTmpTransform, null); 395 } 396 tv.updateViewPropertiesToTaskTransform(mTmpTransform, 0); 397 } 398 399 // If we show the front most task view then ensure that the dismiss button 400 // is visible too. 401 if (!mAwaitingFirstLayout && (task == mStack.getFrontMostTask())) { 402 showDismissAllButton(); 403 } 404 } 405 406 // Animate the task into place 407 tv.updateViewPropertiesToTaskTransform(mCurrentTaskTransforms.get(taskIndex), 408 mStackViewsAnimationDuration, mRequestUpdateClippingListener); 409 410 // Request accessibility focus on the next view if we removed the task 411 // that previously held accessibility focus 412 if (reaquireAccessibilityFocus) { 413 taskViews = getTaskViews(); 414 taskViewCount = taskViews.size(); 415 if (taskViewCount > 0 && ssp.isTouchExplorationEnabled() && 416 mPrevAccessibilityFocusedIndex != -1) { 417 TaskView atv = taskViews.get(taskViewCount - 1); 418 int indexOfTask = mStack.indexOfTask(atv.getTask()); 419 if (mPrevAccessibilityFocusedIndex != indexOfTask) { 420 tv.requestAccessibilityFocus(); 421 mPrevAccessibilityFocusedIndex = indexOfTask; 422 } 423 } 424 } 425 } 426 427 // Reset the request-synchronize params 428 mStackViewsAnimationDuration = 0; 429 mStackViewsDirty = false; 430 mStackViewsClipDirty = true; 431 return true; 432 } 433 return false; 434 } 435 436 /** Updates the clip for each of the task views. */ 437 void clipTaskViews() { 438 // Update the clip on each task child 439 List<TaskView> taskViews = getTaskViews(); 440 int taskViewCount = taskViews.size(); 441 for (int i = 0; i < taskViewCount - 1; i++) { 442 TaskView tv = taskViews.get(i); 443 TaskView nextTv = null; 444 TaskView tmpTv = null; 445 int clipBottom = 0; 446 if (tv.shouldClipViewInStack()) { 447 // Find the next view to clip against 448 int nextIndex = i; 449 while (nextIndex < (taskViewCount - 1)) { 450 tmpTv = taskViews.get(++nextIndex); 451 if (tmpTv != null && tmpTv.shouldClipViewInStack()) { 452 nextTv = tmpTv; 453 break; 454 } 455 } 456 457 // Clip against the next view, this is just an approximation since we are 458 // stacked and we can make assumptions about the visibility of the this 459 // task relative to the ones in front of it. 460 if (nextTv != null) { 461 // Map the top edge of next task view into the local space of the current 462 // task view to find the clip amount in local space 463 mTmpCoord[0] = mTmpCoord[1] = 0; 464 Utilities.mapCoordInDescendentToSelf(nextTv, this, mTmpCoord, false); 465 Utilities.mapCoordInSelfToDescendent(tv, this, mTmpCoord, mTmpMatrix); 466 clipBottom = (int) Math.floor(tv.getMeasuredHeight() - mTmpCoord[1] 467 - nextTv.getPaddingTop() - 1); 468 } 469 } 470 tv.getViewBounds().setClipBottom(clipBottom); 471 } 472 if (taskViewCount > 0) { 473 // The front most task should never be clipped 474 TaskView tv = taskViews.get(taskViewCount - 1); 475 tv.getViewBounds().setClipBottom(0); 476 } 477 mStackViewsClipDirty = false; 478 } 479 480 /** The stack insets to apply to the stack contents */ 481 public void setStackInsetRect(Rect r) { 482 mTaskStackBounds.set(r); 483 } 484 485 /** Updates the min and max virtual scroll bounds */ 486 void updateMinMaxScroll(boolean boundScrollToNewMinMax, boolean launchedWithAltTab, 487 boolean launchedFromHome) { 488 // Compute the min and max scroll values 489 mLayoutAlgorithm.computeMinMaxScroll(mStack.getTasks(), launchedWithAltTab, launchedFromHome); 490 491 // Debug logging 492 if (boundScrollToNewMinMax) { 493 mStackScroller.boundScroll(); 494 } 495 } 496 497 /** Returns the scroller. */ 498 public TaskStackViewScroller getScroller() { 499 return mStackScroller; 500 } 501 502 /** Focuses the task at the specified index in the stack */ 503 void focusTask(int taskIndex, boolean scrollToNewPosition, final boolean animateFocusedState) { 504 // Return early if the task is already focused 505 if (taskIndex == mFocusedTaskIndex) return; 506 507 if (0 <= taskIndex && taskIndex < mStack.getTaskCount()) { 508 mFocusedTaskIndex = taskIndex; 509 mPrevAccessibilityFocusedIndex = taskIndex; 510 511 // Focus the view if possible, otherwise, focus the view after we scroll into position 512 final Task t = mStack.getTasks().get(mFocusedTaskIndex); 513 Runnable postScrollRunnable = new Runnable() { 514 @Override 515 public void run() { 516 TaskView tv = getChildViewForTask(t); 517 if (tv != null) { 518 tv.setFocusedTask(animateFocusedState); 519 tv.requestAccessibilityFocus(); 520 } 521 } 522 }; 523 524 // Scroll the view into position (just center it in the curve) 525 if (scrollToNewPosition) { 526 float newScroll = mLayoutAlgorithm.getStackScrollForTask(t) - 0.5f; 527 newScroll = mStackScroller.getBoundedStackScroll(newScroll); 528 mStackScroller.animateScroll(mStackScroller.getStackScroll(), newScroll, postScrollRunnable); 529 } else { 530 if (postScrollRunnable != null) { 531 postScrollRunnable.run(); 532 } 533 } 534 535 } 536 } 537 538 /** 539 * Ensures that there is a task focused, if nothing is focused, then we will use the task 540 * at the center of the visible stack. 541 */ 542 public boolean ensureFocusedTask(boolean findClosestToCenter) { 543 if (mFocusedTaskIndex < 0) { 544 List<TaskView> taskViews = getTaskViews(); 545 int taskViewCount = taskViews.size(); 546 if (findClosestToCenter) { 547 // If there is no task focused, then find the task that is closes to the center 548 // of the screen and use that as the currently focused task 549 int x = mLayoutAlgorithm.mStackVisibleRect.centerX(); 550 int y = mLayoutAlgorithm.mStackVisibleRect.centerY(); 551 for (int i = taskViewCount - 1; i >= 0; i--) { 552 TaskView tv = taskViews.get(i); 553 tv.getHitRect(mTmpRect); 554 if (mTmpRect.contains(x, y)) { 555 mFocusedTaskIndex = mStack.indexOfTask(tv.getTask()); 556 mPrevAccessibilityFocusedIndex = mFocusedTaskIndex; 557 break; 558 } 559 } 560 } 561 // If we can't find the center task, then use the front most index 562 if (mFocusedTaskIndex < 0 && taskViewCount > 0) { 563 TaskView tv = taskViews.get(taskViewCount - 1); 564 mFocusedTaskIndex = mStack.indexOfTask(tv.getTask()); 565 mPrevAccessibilityFocusedIndex = mFocusedTaskIndex; 566 } 567 } 568 return mFocusedTaskIndex >= 0; 569 } 570 571 /** 572 * Focuses the next task in the stack. 573 * @param animateFocusedState determines whether to actually draw the highlight along with 574 * the change in focus, as well as whether to scroll to fit the 575 * task into view. 576 */ 577 public void focusNextTask(boolean forward, boolean animateFocusedState) { 578 // Find the next index to focus 579 int numTasks = mStack.getTaskCount(); 580 if (numTasks == 0) return; 581 582 int direction = (forward ? -1 : 1); 583 int newIndex = mFocusedTaskIndex + direction; 584 if (newIndex >= 0 && newIndex <= (numTasks - 1)) { 585 newIndex = Math.max(0, Math.min(numTasks - 1, newIndex)); 586 focusTask(newIndex, true, animateFocusedState); 587 } 588 } 589 590 /** Dismisses the focused task. */ 591 public void dismissFocusedTask() { 592 // Return early if the focused task index is invalid 593 if (mFocusedTaskIndex < 0 || mFocusedTaskIndex >= mStack.getTaskCount()) { 594 mFocusedTaskIndex = -1; 595 return; 596 } 597 598 Task t = mStack.getTasks().get(mFocusedTaskIndex); 599 TaskView tv = getChildViewForTask(t); 600 tv.dismissTask(); 601 } 602 603 /** Resets the focused task. */ 604 void resetFocusedTask() { 605 if ((0 <= mFocusedTaskIndex) && (mFocusedTaskIndex < mStack.getTaskCount())) { 606 Task t = mStack.getTasks().get(mFocusedTaskIndex); 607 TaskView tv = getChildViewForTask(t); 608 if (tv != null) { 609 tv.unsetFocusedTask(); 610 } 611 } 612 mFocusedTaskIndex = -1; 613 mPrevAccessibilityFocusedIndex = -1; 614 } 615 616 @Override 617 public void onInitializeAccessibilityEvent(AccessibilityEvent event) { 618 super.onInitializeAccessibilityEvent(event); 619 List<TaskView> taskViews = getTaskViews(); 620 int taskViewCount = taskViews.size(); 621 if (taskViewCount > 0) { 622 TaskView backMostTask = taskViews.get(0); 623 TaskView frontMostTask = taskViews.get(taskViewCount - 1); 624 event.setFromIndex(mStack.indexOfTask(backMostTask.getTask())); 625 event.setToIndex(mStack.indexOfTask(frontMostTask.getTask())); 626 event.setContentDescription(frontMostTask.getTask().activityLabel); 627 } 628 event.setItemCount(mStack.getTaskCount()); 629 event.setScrollY(mStackScroller.mScroller.getCurrY()); 630 event.setMaxScrollY(mStackScroller.progressToScrollRange(mLayoutAlgorithm.mMaxScrollP)); 631 } 632 633 @Override 634 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { 635 super.onInitializeAccessibilityNodeInfo(info); 636 List<TaskView> taskViews = getTaskViews(); 637 int taskViewCount = taskViews.size(); 638 if (taskViewCount > 1 && mPrevAccessibilityFocusedIndex != -1) { 639 info.setScrollable(true); 640 if (mPrevAccessibilityFocusedIndex > 0) { 641 info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD); 642 } 643 if (mPrevAccessibilityFocusedIndex < mStack.getTaskCount() - 1) { 644 info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD); 645 } 646 } 647 } 648 649 @Override 650 public CharSequence getAccessibilityClassName() { 651 return TaskStackView.class.getName(); 652 } 653 654 @Override 655 public boolean performAccessibilityAction(int action, Bundle arguments) { 656 if (super.performAccessibilityAction(action, arguments)) { 657 return true; 658 } 659 if (ensureFocusedTask(false)) { 660 switch (action) { 661 case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD: { 662 if (mPrevAccessibilityFocusedIndex > 0) { 663 focusNextTask(true, false); 664 return true; 665 } 666 } 667 break; 668 case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD: { 669 if (mPrevAccessibilityFocusedIndex < mStack.getTaskCount() - 1) { 670 focusNextTask(false, false); 671 return true; 672 } 673 } 674 break; 675 } 676 } 677 return false; 678 } 679 680 @Override 681 public boolean onInterceptTouchEvent(MotionEvent ev) { 682 return mTouchHandler.onInterceptTouchEvent(ev); 683 } 684 685 @Override 686 public boolean onTouchEvent(MotionEvent ev) { 687 return mTouchHandler.onTouchEvent(ev); 688 } 689 690 @Override 691 public boolean onGenericMotionEvent(MotionEvent ev) { 692 return mTouchHandler.onGenericMotionEvent(ev); 693 } 694 695 /** Returns the region that touch gestures can be started in. */ 696 Rect getTouchableRegion() { 697 return mTaskStackBounds; 698 } 699 700 @Override 701 public void computeScroll() { 702 mStackScroller.computeScroll(); 703 // Synchronize the views 704 synchronizeStackViewsWithModel(); 705 clipTaskViews(); 706 updateDismissButtonPosition(); 707 // Notify accessibility 708 sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SCROLLED); 709 } 710 711 /** Computes the stack and task rects */ 712 public void computeRects(int windowWidth, int windowHeight, Rect taskStackBounds, 713 boolean launchedWithAltTab, boolean launchedFromHome) { 714 // Compute the rects in the stack algorithm 715 mLayoutAlgorithm.computeRects(windowWidth, windowHeight, taskStackBounds); 716 717 // Update the scroll bounds 718 updateMinMaxScroll(false, launchedWithAltTab, launchedFromHome); 719 } 720 721 /** 722 * This is ONLY used from AlternateRecentsComponent to update the dummy stack view for purposes 723 * of getting the task rect to animate to. 724 */ 725 public void updateMinMaxScrollForStack(TaskStack stack, boolean launchedWithAltTab, 726 boolean launchedFromHome) { 727 mStack = stack; 728 updateMinMaxScroll(false, launchedWithAltTab, launchedFromHome); 729 } 730 731 /** 732 * Computes the maximum number of visible tasks and thumbnails. Requires that 733 * updateMinMaxScrollForStack() is called first. 734 */ 735 public TaskStackViewLayoutAlgorithm.VisibilityReport computeStackVisibilityReport() { 736 return mLayoutAlgorithm.computeStackVisibilityReport(mStack.getTasks()); 737 } 738 739 /** 740 * This is called with the full window width and height to allow stack view children to 741 * perform the full screen transition down. 742 */ 743 @Override 744 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 745 int width = MeasureSpec.getSize(widthMeasureSpec); 746 int height = MeasureSpec.getSize(heightMeasureSpec); 747 748 // Compute our stack/task rects 749 Rect taskStackBounds = new Rect(mTaskStackBounds); 750 taskStackBounds.bottom -= mConfig.systemInsets.bottom; 751 computeRects(width, height, taskStackBounds, mConfig.launchedWithAltTab, 752 mConfig.launchedFromHome); 753 754 // If this is the first layout, then scroll to the front of the stack and synchronize the 755 // stack views immediately to load all the views 756 if (mAwaitingFirstLayout) { 757 mStackScroller.setStackScrollToInitialState(); 758 requestSynchronizeStackViewsWithModel(); 759 synchronizeStackViewsWithModel(); 760 } 761 762 // Measure each of the TaskViews 763 List<TaskView> taskViews = getTaskViews(); 764 int taskViewCount = taskViews.size(); 765 for (int i = 0; i < taskViewCount; i++) { 766 TaskView tv = taskViews.get(i); 767 if (tv.getBackground() != null) { 768 tv.getBackground().getPadding(mTmpRect); 769 } else { 770 mTmpRect.setEmpty(); 771 } 772 tv.measure( 773 MeasureSpec.makeMeasureSpec( 774 mLayoutAlgorithm.mTaskRect.width() + mTmpRect.left + mTmpRect.right, 775 MeasureSpec.EXACTLY), 776 MeasureSpec.makeMeasureSpec( 777 mLayoutAlgorithm.mTaskRect.height() + mTmpRect.top + mTmpRect.bottom, 778 MeasureSpec.EXACTLY)); 779 } 780 781 // Measure the dismiss button 782 if (mDismissAllButton != null) { 783 int taskRectWidth = mLayoutAlgorithm.mTaskRect.width(); 784 mDismissAllButton.measure( 785 MeasureSpec.makeMeasureSpec(taskRectWidth, MeasureSpec.EXACTLY), 786 MeasureSpec.makeMeasureSpec(mConfig.dismissAllButtonSizePx, MeasureSpec.EXACTLY)); 787 } 788 789 setMeasuredDimension(width, height); 790 } 791 792 /** 793 * This is called with the size of the space not including the top or right insets, or the 794 * search bar height in portrait (but including the search bar width in landscape, since we want 795 * to draw under it. 796 */ 797 @Override 798 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 799 // Layout each of the children 800 List<TaskView> taskViews = getTaskViews(); 801 int taskViewCount = taskViews.size(); 802 for (int i = 0; i < taskViewCount; i++) { 803 TaskView tv = taskViews.get(i); 804 if (tv.getBackground() != null) { 805 tv.getBackground().getPadding(mTmpRect); 806 } else { 807 mTmpRect.setEmpty(); 808 } 809 tv.layout(mLayoutAlgorithm.mTaskRect.left - mTmpRect.left, 810 mLayoutAlgorithm.mTaskRect.top - mTmpRect.top, 811 mLayoutAlgorithm.mTaskRect.right + mTmpRect.right, 812 mLayoutAlgorithm.mTaskRect.bottom + mTmpRect.bottom); 813 } 814 815 // Layout the dismiss button at the top of the screen, and just translate it accordingly 816 // when synchronizing the views with the model to attach it to the bottom of the front-most 817 // task view 818 if (mDismissAllButton != null) { 819 mDismissAllButton.layout(mLayoutAlgorithm.mTaskRect.left, 0, 820 mLayoutAlgorithm.mTaskRect.left + mDismissAllButton.getMeasuredWidth(), 821 mDismissAllButton.getMeasuredHeight()); 822 } 823 824 if (mAwaitingFirstLayout) { 825 mAwaitingFirstLayout = false; 826 onFirstLayout(); 827 } 828 } 829 830 /** Handler for the first layout. */ 831 void onFirstLayout() { 832 int offscreenY = mLayoutAlgorithm.mViewRect.bottom - 833 (mLayoutAlgorithm.mTaskRect.top - mLayoutAlgorithm.mViewRect.top); 834 835 // Find the launch target task 836 Task launchTargetTask = null; 837 List<TaskView> taskViews = getTaskViews(); 838 int taskViewCount = taskViews.size(); 839 for (int i = taskViewCount - 1; i >= 0; i--) { 840 TaskView tv = taskViews.get(i); 841 Task task = tv.getTask(); 842 if (task.isLaunchTarget) { 843 launchTargetTask = task; 844 break; 845 } 846 } 847 848 // Prepare the first view for its enter animation 849 for (int i = taskViewCount - 1; i >= 0; i--) { 850 TaskView tv = taskViews.get(i); 851 Task task = tv.getTask(); 852 boolean occludesLaunchTarget = (launchTargetTask != null) && 853 launchTargetTask.group.isTaskAboveTask(task, launchTargetTask); 854 tv.prepareEnterRecentsAnimation(task.isLaunchTarget, occludesLaunchTarget, offscreenY); 855 } 856 857 // If the enter animation started already and we haven't completed a layout yet, do the 858 // enter animation now 859 if (mStartEnterAnimationRequestedAfterLayout) { 860 startEnterRecentsAnimation(mStartEnterAnimationContext); 861 mStartEnterAnimationRequestedAfterLayout = false; 862 mStartEnterAnimationContext = null; 863 } 864 865 // When Alt-Tabbing, focus the previous task (but leave the animation until we finish the 866 // enter animation). 867 if (mConfig.launchedWithAltTab) { 868 if (mConfig.launchedFromAppWithThumbnail) { 869 focusTask(Math.max(0, mStack.getTaskCount() - 2), false, 870 mConfig.launchedHasConfigurationChanged); 871 } else { 872 focusTask(Math.max(0, mStack.getTaskCount() - 1), false, 873 mConfig.launchedHasConfigurationChanged); 874 } 875 } 876 877 // Start dozing 878 if (!mConfig.multiStackEnabled) { 879 mUIDozeTrigger.startDozing(); 880 } 881 } 882 883 /** Requests this task stacks to start it's enter-recents animation */ 884 public void startEnterRecentsAnimation(ViewAnimation.TaskViewEnterContext ctx) { 885 // If we are still waiting to layout, then just defer until then 886 if (mAwaitingFirstLayout) { 887 mStartEnterAnimationRequestedAfterLayout = true; 888 mStartEnterAnimationContext = ctx; 889 return; 890 } 891 892 if (mStack.getTaskCount() > 0) { 893 // Find the launch target task 894 Task launchTargetTask = null; 895 List<TaskView> taskViews = getTaskViews(); 896 int taskViewCount = taskViews.size(); 897 for (int i = taskViewCount - 1; i >= 0; i--) { 898 TaskView tv = taskViews.get(i); 899 Task task = tv.getTask(); 900 if (task.isLaunchTarget) { 901 launchTargetTask = task; 902 break; 903 } 904 } 905 906 // Animate all the task views into view 907 for (int i = taskViewCount - 1; i >= 0; i--) { 908 TaskView tv = taskViews.get(i); 909 Task task = tv.getTask(); 910 ctx.currentTaskTransform = new TaskViewTransform(); 911 ctx.currentStackViewIndex = i; 912 ctx.currentStackViewCount = taskViewCount; 913 ctx.currentTaskRect = mLayoutAlgorithm.mTaskRect; 914 ctx.currentTaskOccludesLaunchTarget = (launchTargetTask != null) && 915 launchTargetTask.group.isTaskAboveTask(task, launchTargetTask); 916 ctx.updateListener = mRequestUpdateClippingListener; 917 mLayoutAlgorithm.getStackTransform(task, mStackScroller.getStackScroll(), ctx.currentTaskTransform, null); 918 tv.startEnterRecentsAnimation(ctx); 919 } 920 921 // Add a runnable to the post animation ref counter to clear all the views 922 ctx.postAnimationTrigger.addLastDecrementRunnable(new Runnable() { 923 @Override 924 public void run() { 925 mStartEnterAnimationCompleted = true; 926 // Poke the dozer to restart the trigger after the animation completes 927 mUIDozeTrigger.poke(); 928 929 RecentsTaskLoader loader = RecentsTaskLoader.getInstance(); 930 SystemServicesProxy ssp = loader.getSystemServicesProxy(); 931 List<TaskView> taskViews = getTaskViews(); 932 int taskViewCount = taskViews.size(); 933 if (taskViewCount > 0) { 934 // Focus the first view if accessibility is enabled 935 if (ssp.isTouchExplorationEnabled()) { 936 TaskView tv = taskViews.get(taskViewCount - 1); 937 tv.requestAccessibilityFocus(); 938 mPrevAccessibilityFocusedIndex = mStack.indexOfTask(tv.getTask()); 939 } 940 } 941 942 // Start the focus animation when alt-tabbing 943 ArrayList<Task> tasks = mStack.getTasks(); 944 if (mConfig.launchedWithAltTab && !mConfig.launchedHasConfigurationChanged && 945 0 <= mFocusedTaskIndex && mFocusedTaskIndex < tasks.size()) { 946 TaskView tv = getChildViewForTask(tasks.get(mFocusedTaskIndex)); 947 if (tv != null) { 948 tv.setFocusedTask(true); 949 } 950 } 951 952 // Show the dismiss button 953 showDismissAllButton(); 954 } 955 }); 956 } 957 } 958 959 /** Requests this task stack to start it's exit-recents animation. */ 960 public void startExitToHomeAnimation(ViewAnimation.TaskViewExitContext ctx) { 961 // Stop any scrolling 962 mStackScroller.stopScroller(); 963 mStackScroller.stopBoundScrollAnimation(); 964 // Animate all the task views out of view 965 ctx.offscreenTranslationY = mLayoutAlgorithm.mViewRect.bottom - 966 (mLayoutAlgorithm.mTaskRect.top - mLayoutAlgorithm.mViewRect.top); 967 // Animate the dismiss-all button 968 hideDismissAllButton(null); 969 970 List<TaskView> taskViews = getTaskViews(); 971 int taskViewCount = taskViews.size(); 972 for (int i = 0; i < taskViewCount; i++) { 973 TaskView tv = taskViews.get(i); 974 tv.startExitToHomeAnimation(ctx); 975 } 976 } 977 978 /** Requests this task stack to start it's dismiss-all animation. */ 979 public void startDismissAllAnimation(final Runnable postAnimationRunnable) { 980 // Clear the focused task 981 resetFocusedTask(); 982 // Animate the dismiss-all button 983 hideDismissAllButton(new Runnable() { 984 @Override 985 public void run() { 986 List<TaskView> taskViews = getTaskViews(); 987 int taskViewCount = taskViews.size(); 988 int count = 0; 989 for (int i = taskViewCount - 1; i >= 0; i--) { 990 TaskView tv = taskViews.get(i); 991 tv.startDeleteTaskAnimation(i > 0 ? null : postAnimationRunnable, count * 50); 992 count++; 993 } 994 } 995 }); 996 } 997 998 /** Animates a task view in this stack as it launches. */ 999 public void startLaunchTaskAnimation(TaskView tv, Runnable r, boolean lockToTask) { 1000 Task launchTargetTask = tv.getTask(); 1001 List<TaskView> taskViews = getTaskViews(); 1002 int taskViewCount = taskViews.size(); 1003 for (int i = 0; i < taskViewCount; i++) { 1004 TaskView t = taskViews.get(i); 1005 if (t == tv) { 1006 t.setClipViewInStack(false); 1007 t.startLaunchTaskAnimation(r, true, true, lockToTask); 1008 } else { 1009 boolean occludesLaunchTarget = launchTargetTask.group.isTaskAboveTask(t.getTask(), 1010 launchTargetTask); 1011 t.startLaunchTaskAnimation(null, false, occludesLaunchTarget, lockToTask); 1012 } 1013 } 1014 } 1015 1016 /** Shows the dismiss button */ 1017 void showDismissAllButton() { 1018 if (mDismissAllButton == null) return; 1019 1020 if (mDismissAllButtonAnimating || mDismissAllButton.getVisibility() != View.VISIBLE || 1021 Float.compare(mDismissAllButton.getAlpha(), 0f) == 0) { 1022 mDismissAllButtonAnimating = true; 1023 mDismissAllButton.setVisibility(View.VISIBLE); 1024 mDismissAllButton.showClearButton(); 1025 mDismissAllButton.findViewById(R.id.dismiss_text).setAlpha(1f); 1026 mDismissAllButton.setAlpha(0f); 1027 mDismissAllButton.animate() 1028 .alpha(1f) 1029 .setDuration(250) 1030 .withEndAction(new Runnable() { 1031 @Override 1032 public void run() { 1033 mDismissAllButtonAnimating = false; 1034 } 1035 }) 1036 .start(); 1037 } 1038 } 1039 1040 /** Hides the dismiss button */ 1041 void hideDismissAllButton(final Runnable postAnimRunnable) { 1042 if (mDismissAllButton == null) return; 1043 1044 mDismissAllButtonAnimating = true; 1045 mDismissAllButton.animate() 1046 .alpha(0f) 1047 .setDuration(200) 1048 .withEndAction(new Runnable() { 1049 @Override 1050 public void run() { 1051 mDismissAllButtonAnimating = false; 1052 mDismissAllButton.setVisibility(View.GONE); 1053 if (postAnimRunnable != null) { 1054 postAnimRunnable.run(); 1055 } 1056 } 1057 }) 1058 .start(); 1059 } 1060 1061 /** Updates the dismiss button position */ 1062 void updateDismissButtonPosition() { 1063 if (mDismissAllButton == null) return; 1064 1065 // Update the position of the clear-all button to hang it off the first task view 1066 if (mStack.getTaskCount() > 0) { 1067 mTmpCoord[0] = mTmpCoord[1] = 0; 1068 TaskView tv = getChildViewForTask(mStack.getFrontMostTask()); 1069 TaskViewTransform transform = mCurrentTaskTransforms.get(mStack.getTaskCount() - 1); 1070 if (tv != null && transform.visible) { 1071 Utilities.mapCoordInDescendentToSelf(tv, this, mTmpCoord, false); 1072 mDismissAllButton.setTranslationY(mTmpCoord[1] + (tv.getScaleY() * tv.getHeight())); 1073 mDismissAllButton.setTranslationX(-(mLayoutAlgorithm.mStackRect.width() - 1074 transform.rect.width()) / 2f); 1075 } 1076 } 1077 } 1078 1079 /** Final callback after Recents is finally hidden. */ 1080 void onRecentsHidden() { 1081 reset(); 1082 } 1083 1084 public boolean isTransformedTouchPointInView(float x, float y, View child) { 1085 return isTransformedTouchPointInView(x, y, child, null); 1086 } 1087 1088 /** Pokes the dozer on user interaction. */ 1089 void onUserInteraction() { 1090 // Poke the doze trigger if it is dozing 1091 mUIDozeTrigger.poke(); 1092 } 1093 1094 @Override 1095 protected void dispatchDraw(Canvas canvas) { 1096 mLayersDisabled = false; 1097 super.dispatchDraw(canvas); 1098 } 1099 1100 public void disableLayersForOneFrame() { 1101 mLayersDisabled = true; 1102 List<TaskView> taskViews = getTaskViews(); 1103 for (int i = 0; i < taskViews.size(); i++) { 1104 taskViews.get(i).disableLayersForOneFrame(); 1105 } 1106 } 1107 1108 /**** TaskStackCallbacks Implementation ****/ 1109 1110 @Override 1111 public void onStackTaskAdded(TaskStack stack, Task t) { 1112 requestSynchronizeStackViewsWithModel(); 1113 } 1114 1115 @Override 1116 public void onStackTaskRemoved(TaskStack stack, Task removedTask, Task newFrontMostTask) { 1117 // Remove the view associated with this task, we can't rely on updateTransforms 1118 // to work here because the task is no longer in the list 1119 TaskView tv = getChildViewForTask(removedTask); 1120 if (tv != null) { 1121 mViewPool.returnViewToPool(tv); 1122 } 1123 1124 // Get the stack scroll of the task to anchor to (since we are removing something, the front 1125 // most task will be our anchor task) 1126 Task anchorTask = null; 1127 float prevAnchorTaskScroll = 0; 1128 boolean pullStackForward = stack.getTaskCount() > 0; 1129 if (pullStackForward) { 1130 anchorTask = mStack.getFrontMostTask(); 1131 prevAnchorTaskScroll = mLayoutAlgorithm.getStackScrollForTask(anchorTask); 1132 } 1133 1134 // Update the min/max scroll and animate other task views into their new positions 1135 updateMinMaxScroll(true, mConfig.launchedWithAltTab, mConfig.launchedFromHome); 1136 1137 // Offset the stack by as much as the anchor task would otherwise move back 1138 if (pullStackForward) { 1139 float anchorTaskScroll = mLayoutAlgorithm.getStackScrollForTask(anchorTask); 1140 mStackScroller.setStackScroll(mStackScroller.getStackScroll() + (anchorTaskScroll 1141 - prevAnchorTaskScroll)); 1142 mStackScroller.boundScroll(); 1143 } 1144 1145 // Animate all the tasks into place 1146 requestSynchronizeStackViewsWithModel(200); 1147 1148 // Update the new front most task 1149 if (newFrontMostTask != null) { 1150 TaskView frontTv = getChildViewForTask(newFrontMostTask); 1151 if (frontTv != null) { 1152 frontTv.onTaskBound(newFrontMostTask); 1153 frontTv.fadeInActionButton(0, mConfig.taskViewEnterFromAppDuration); 1154 } 1155 } 1156 1157 // If there are no remaining tasks, then either unfilter the current stack, or just close 1158 // the activity if there are no filtered stacks 1159 if (mStack.getTaskCount() == 0) { 1160 boolean shouldFinishActivity = true; 1161 if (mStack.hasFilteredTasks()) { 1162 mStack.unfilterTasks(); 1163 shouldFinishActivity = (mStack.getTaskCount() == 0); 1164 } 1165 if (shouldFinishActivity) { 1166 mCb.onAllTaskViewsDismissed(null); 1167 } 1168 } else { 1169 // Fade the dismiss button back in 1170 showDismissAllButton(); 1171 } 1172 1173 // Notify the callback that we've removed the task and it can clean up after it. Note, we 1174 // do this after onAllTaskViewsDismissed() is called, to allow the home activity to be 1175 // started before the call to remove the task. 1176 mCb.onTaskViewDismissed(removedTask); 1177 } 1178 1179 @Override 1180 public void onStackAllTasksRemoved(TaskStack stack, final ArrayList<Task> removedTasks) { 1181 // Announce for accessibility 1182 String msg = getContext().getString(R.string.accessibility_recents_all_items_dismissed); 1183 announceForAccessibility(msg); 1184 1185 startDismissAllAnimation(new Runnable() { 1186 @Override 1187 public void run() { 1188 // Notify that all tasks have been removed 1189 mCb.onAllTaskViewsDismissed(removedTasks); 1190 } 1191 }); 1192 } 1193 1194 @Override 1195 public void onStackFiltered(TaskStack newStack, final ArrayList<Task> curTasks, 1196 Task filteredTask) { 1197 /* 1198 // Stash the scroll and filtered task for us to restore to when we unfilter 1199 mStashedScroll = getStackScroll(); 1200 1201 // Calculate the current task transforms 1202 ArrayList<TaskViewTransform> curTaskTransforms = 1203 getStackTransforms(curTasks, getStackScroll(), null, true); 1204 1205 // Update the task offsets 1206 mLayoutAlgorithm.updateTaskOffsets(mStack.getTasks()); 1207 1208 // Scroll the item to the top of the stack (sans-peek) rect so that we can see it better 1209 updateMinMaxScroll(false); 1210 float overlapHeight = mLayoutAlgorithm.getTaskOverlapHeight(); 1211 setStackScrollRaw((int) (newStack.indexOfTask(filteredTask) * overlapHeight)); 1212 boundScrollRaw(); 1213 1214 // Compute the transforms of the items in the new stack after setting the new scroll 1215 final ArrayList<Task> tasks = mStack.getTasks(); 1216 final ArrayList<TaskViewTransform> taskTransforms = 1217 getStackTransforms(mStack.getTasks(), getStackScroll(), null, true); 1218 1219 // Animate 1220 mFilterAlgorithm.startFilteringAnimation(curTasks, curTaskTransforms, tasks, taskTransforms); 1221 1222 // Notify any callbacks 1223 mCb.onTaskStackFilterTriggered(); 1224 */ 1225 } 1226 1227 @Override 1228 public void onStackUnfiltered(TaskStack newStack, final ArrayList<Task> curTasks) { 1229 /* 1230 // Calculate the current task transforms 1231 final ArrayList<TaskViewTransform> curTaskTransforms = 1232 getStackTransforms(curTasks, getStackScroll(), null, true); 1233 1234 // Update the task offsets 1235 mLayoutAlgorithm.updateTaskOffsets(mStack.getTasks()); 1236 1237 // Restore the stashed scroll 1238 updateMinMaxScroll(false); 1239 setStackScrollRaw(mStashedScroll); 1240 boundScrollRaw(); 1241 1242 // Compute the transforms of the items in the new stack after restoring the stashed scroll 1243 final ArrayList<Task> tasks = mStack.getTasks(); 1244 final ArrayList<TaskViewTransform> taskTransforms = 1245 getStackTransforms(tasks, getStackScroll(), null, true); 1246 1247 // Animate 1248 mFilterAlgorithm.startFilteringAnimation(curTasks, curTaskTransforms, tasks, taskTransforms); 1249 1250 // Clear the saved vars 1251 mStashedScroll = 0; 1252 1253 // Notify any callbacks 1254 mCb.onTaskStackUnfilterTriggered(); 1255 */ 1256 } 1257 1258 /**** ViewPoolConsumer Implementation ****/ 1259 1260 @Override 1261 public TaskView createView(Context context) { 1262 return (TaskView) mInflater.inflate(R.layout.recents_task_view, this, false); 1263 } 1264 1265 @Override 1266 public void prepareViewToEnterPool(TaskView tv) { 1267 Task task = tv.getTask(); 1268 1269 // Clear the accessibility focus for that view 1270 if (tv.isAccessibilityFocused()) { 1271 tv.clearAccessibilityFocus(); 1272 } 1273 1274 // Report that this tasks's data is no longer being used 1275 RecentsTaskLoader.getInstance().unloadTaskData(task); 1276 1277 // Detach the view from the hierarchy 1278 detachViewFromParent(tv); 1279 // Update the task views list after removing the task view 1280 updateTaskViewsList(); 1281 1282 // Reset the view properties 1283 tv.resetViewProperties(); 1284 1285 // Reset the clip state of the task view 1286 tv.setClipViewInStack(false); 1287 } 1288 1289 @Override 1290 public void prepareViewToLeavePool(TaskView tv, Task task, boolean isNewView) { 1291 // It is possible for a view to be returned to the view pool before it is laid out, 1292 // which means that we will need to relayout the view when it is first used next. 1293 boolean requiresRelayout = tv.getWidth() <= 0 && !isNewView; 1294 1295 // Rebind the task and request that this task's data be filled into the TaskView 1296 tv.onTaskBound(task); 1297 1298 // Load the task data 1299 RecentsTaskLoader.getInstance().loadTaskData(task); 1300 1301 // If the doze trigger has already fired, then update the state for this task view 1302 if (mConfig.multiStackEnabled || mUIDozeTrigger.hasTriggered()) { 1303 tv.setNoUserInteractionState(); 1304 } 1305 1306 // If we've finished the start animation, then ensure we always enable the focus animations 1307 if (mStartEnterAnimationCompleted) { 1308 tv.enableFocusAnimations(); 1309 } 1310 1311 // Find the index where this task should be placed in the stack 1312 int insertIndex = -1; 1313 int taskIndex = mStack.indexOfTask(task); 1314 if (taskIndex != -1) { 1315 1316 List<TaskView> taskViews = getTaskViews(); 1317 int taskViewCount = taskViews.size(); 1318 for (int i = 0; i < taskViewCount; i++) { 1319 Task tvTask = taskViews.get(i).getTask(); 1320 if (taskIndex < mStack.indexOfTask(tvTask)) { 1321 // Offset by 1 if we have a dismiss-all button 1322 insertIndex = i + (Constants.DebugFlags.App.EnableDismissAll ? 1 : 0); 1323 break; 1324 } 1325 } 1326 } 1327 1328 // Add/attach the view to the hierarchy 1329 if (isNewView) { 1330 addView(tv, insertIndex); 1331 } else { 1332 attachViewToParent(tv, insertIndex, tv.getLayoutParams()); 1333 if (requiresRelayout) { 1334 tv.requestLayout(); 1335 } 1336 } 1337 // Update the task views list after adding the new task view 1338 updateTaskViewsList(); 1339 1340 // Set the new state for this view, including the callbacks and view clipping 1341 tv.setCallbacks(this); 1342 tv.setTouchEnabled(true); 1343 tv.setClipViewInStack(true); 1344 } 1345 1346 @Override 1347 public boolean hasPreferredData(TaskView tv, Task preferredData) { 1348 return (tv.getTask() == preferredData); 1349 } 1350 1351 /**** TaskViewCallbacks Implementation ****/ 1352 1353 @Override 1354 public void onTaskViewAppIconClicked(TaskView tv) { 1355 if (Constants.DebugFlags.App.EnableTaskFiltering) { 1356 if (mStack.hasFilteredTasks()) { 1357 mStack.unfilterTasks(); 1358 } else { 1359 mStack.filterTasks(tv.getTask()); 1360 } 1361 } 1362 } 1363 1364 @Override 1365 public void onTaskViewAppInfoClicked(TaskView tv) { 1366 if (mCb != null) { 1367 mCb.onTaskViewAppInfoClicked(tv.getTask()); 1368 1369 // Keep track of app-info invocations 1370 MetricsLogger.count(getContext(), "overview_app_info", 1); 1371 } 1372 } 1373 1374 @Override 1375 public void onTaskViewClicked(TaskView tv, Task task, boolean lockToTask) { 1376 // Cancel any doze triggers 1377 mUIDozeTrigger.stopDozing(); 1378 1379 if (mCb != null) { 1380 mCb.onTaskViewClicked(this, tv, mStack, task, lockToTask); 1381 } 1382 } 1383 1384 @Override 1385 public void onTaskViewDismissed(TaskView tv) { 1386 Task task = tv.getTask(); 1387 int taskIndex = mStack.indexOfTask(task); 1388 boolean taskWasFocused = tv.isFocusedTask(); 1389 // Announce for accessibility 1390 tv.announceForAccessibility(getContext().getString(R.string.accessibility_recents_item_dismissed, 1391 tv.getTask().activityLabel)); 1392 // Remove the task from the view 1393 mStack.removeTask(task); 1394 // If the dismissed task was focused, then we should focus the new task in the same index 1395 if (taskWasFocused) { 1396 ArrayList<Task> tasks = mStack.getTasks(); 1397 int nextTaskIndex = Math.min(tasks.size() - 1, taskIndex - 1); 1398 if (nextTaskIndex >= 0) { 1399 Task nextTask = tasks.get(nextTaskIndex); 1400 TaskView nextTv = getChildViewForTask(nextTask); 1401 if (nextTv != null) { 1402 // Focus the next task, and only animate the visible state if we are launched 1403 // from Alt-Tab 1404 nextTv.setFocusedTask(mConfig.launchedWithAltTab); 1405 } 1406 } 1407 } 1408 } 1409 1410 @Override 1411 public void onTaskViewClipStateChanged(TaskView tv) { 1412 if (!mStackViewsDirty) { 1413 invalidate(); 1414 } 1415 } 1416 1417 @Override 1418 public void onTaskViewFocusChanged(TaskView tv, boolean focused) { 1419 if (focused) { 1420 mFocusedTaskIndex = mStack.indexOfTask(tv.getTask()); 1421 } 1422 } 1423 1424 @Override 1425 public void onTaskResize(TaskView tv) { 1426 if (mCb != null) { 1427 mCb.onTaskResize(tv.getTask()); 1428 } 1429 } 1430 1431 /**** TaskStackViewScroller.TaskStackViewScrollerCallbacks ****/ 1432 1433 @Override 1434 public void onScrollChanged(float p) { 1435 mUIDozeTrigger.poke(); 1436 requestSynchronizeStackViewsWithModel(); 1437 postInvalidateOnAnimation(); 1438 } 1439 1440 /**** RecentsPackageMonitor.PackageCallbacks Implementation ****/ 1441 1442 @Override 1443 public void onPackagesChanged(RecentsPackageMonitor monitor, String packageName, int userId) { 1444 // Compute which components need to be removed 1445 HashSet<ComponentName> removedComponents = monitor.computeComponentsRemoved( 1446 mStack.getTaskKeys(), packageName, userId); 1447 1448 // For other tasks, just remove them directly if they no longer exist 1449 ArrayList<Task> tasks = mStack.getTasks(); 1450 for (int i = tasks.size() - 1; i >= 0; i--) { 1451 final Task t = tasks.get(i); 1452 if (removedComponents.contains(t.key.baseIntent.getComponent())) { 1453 TaskView tv = getChildViewForTask(t); 1454 if (tv != null) { 1455 // For visible children, defer removing the task until after the animation 1456 tv.startDeleteTaskAnimation(new Runnable() { 1457 @Override 1458 public void run() { 1459 mStack.removeTask(t); 1460 } 1461 }, 0); 1462 } else { 1463 // Otherwise, remove the task from the stack immediately 1464 mStack.removeTask(t); 1465 } 1466 } 1467 } 1468 } 1469 } 1470