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