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