TaskStackView.java revision 133ad44269e4b45e056793b579a7628aa4d91ccb
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.ObjectAnimator; 20import android.animation.ValueAnimator; 21import android.content.ComponentName; 22import android.content.Context; 23import android.content.res.Resources; 24import android.graphics.Canvas; 25import android.graphics.Rect; 26import android.graphics.drawable.Drawable; 27import android.graphics.drawable.GradientDrawable; 28import android.os.Bundle; 29import android.os.Parcelable; 30import android.provider.Settings; 31import android.util.ArrayMap; 32import android.util.ArraySet; 33import android.util.MutableBoolean; 34import android.view.LayoutInflater; 35import android.view.MotionEvent; 36import android.view.View; 37import android.view.accessibility.AccessibilityEvent; 38import android.view.accessibility.AccessibilityNodeInfo; 39import android.view.animation.Interpolator; 40import android.widget.FrameLayout; 41 42import com.android.systemui.Interpolators; 43import com.android.systemui.R; 44import com.android.systemui.recents.Recents; 45import com.android.systemui.recents.RecentsActivity; 46import com.android.systemui.recents.RecentsActivityLaunchState; 47import com.android.systemui.recents.RecentsConfiguration; 48import com.android.systemui.recents.RecentsDebugFlags; 49import com.android.systemui.recents.events.EventBus; 50import com.android.systemui.recents.events.activity.CancelEnterRecentsWindowAnimationEvent; 51import com.android.systemui.recents.events.activity.DismissRecentsToHomeAnimationStarted; 52import com.android.systemui.recents.events.activity.EnterRecentsTaskStackAnimationCompletedEvent; 53import com.android.systemui.recents.events.activity.EnterRecentsWindowAnimationCompletedEvent; 54import com.android.systemui.recents.events.activity.HideHistoryButtonEvent; 55import com.android.systemui.recents.events.activity.HideHistoryEvent; 56import com.android.systemui.recents.events.activity.IterateRecentsEvent; 57import com.android.systemui.recents.events.activity.LaunchTaskEvent; 58import com.android.systemui.recents.events.activity.LaunchTaskStartedEvent; 59import com.android.systemui.recents.events.activity.PackagesChangedEvent; 60import com.android.systemui.recents.events.activity.ShowHistoryButtonEvent; 61import com.android.systemui.recents.events.activity.ShowHistoryEvent; 62import com.android.systemui.recents.events.component.RecentsVisibilityChangedEvent; 63import com.android.systemui.recents.events.ui.AllTaskViewsDismissedEvent; 64import com.android.systemui.recents.events.ui.DeleteTaskDataEvent; 65import com.android.systemui.recents.events.ui.DismissTaskViewEvent; 66import com.android.systemui.recents.events.ui.StackViewScrolledEvent; 67import com.android.systemui.recents.events.ui.TaskViewDismissedEvent; 68import com.android.systemui.recents.events.ui.UpdateFreeformTaskViewVisibilityEvent; 69import com.android.systemui.recents.events.ui.UserInteractionEvent; 70import com.android.systemui.recents.events.ui.dragndrop.DragDropTargetChangedEvent; 71import com.android.systemui.recents.events.ui.dragndrop.DragEndEvent; 72import com.android.systemui.recents.events.ui.dragndrop.DragStartEvent; 73import com.android.systemui.recents.events.ui.dragndrop.DragStartInitializeDropTargetsEvent; 74import com.android.systemui.recents.events.ui.focus.DismissFocusedTaskViewEvent; 75import com.android.systemui.recents.events.ui.focus.FocusNextTaskViewEvent; 76import com.android.systemui.recents.events.ui.focus.FocusPreviousTaskViewEvent; 77import com.android.systemui.recents.misc.DozeTrigger; 78import com.android.systemui.recents.misc.ReferenceCountedTrigger; 79import com.android.systemui.recents.misc.SystemServicesProxy; 80import com.android.systemui.recents.misc.Utilities; 81import com.android.systemui.recents.model.Task; 82import com.android.systemui.recents.model.TaskStack; 83 84import java.util.ArrayList; 85import java.util.List; 86 87import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID; 88import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID; 89import static android.app.ActivityManager.StackId.INVALID_STACK_ID; 90 91 92/* The visual representation of a task stack view */ 93public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCallbacks, 94 TaskView.TaskViewCallbacks, TaskStackViewScroller.TaskStackViewScrollerCallbacks, 95 TaskStackLayoutAlgorithm.TaskStackLayoutAlgorithmCallbacks, 96 ViewPool.ViewPoolConsumer<TaskView, Task> { 97 98 private final static String KEY_SAVED_STATE_SUPER = "saved_instance_state_super"; 99 private final static String KEY_SAVED_STATE_LAYOUT_FOCUSED_STATE = 100 "saved_instance_state_layout_focused_state"; 101 private final static String KEY_SAVED_STATE_LAYOUT_STACK_SCROLL = 102 "saved_instance_state_layout_stack_scroll"; 103 104 // The thresholds at which to show/hide the history button. 105 private static final float SHOW_HISTORY_BUTTON_SCROLL_THRESHOLD = 0.3f; 106 private static final float HIDE_HISTORY_BUTTON_SCROLL_THRESHOLD = 0.3f; 107 108 public static final int DEFAULT_SYNC_STACK_DURATION = 200; 109 private static final int DRAG_SCALE_DURATION = 175; 110 private static final float DRAG_SCALE_FACTOR = 1.05f; 111 112 private static final ArraySet<Task.TaskKey> EMPTY_TASK_SET = new ArraySet<>(); 113 114 TaskStack mStack; 115 TaskStackLayoutAlgorithm mLayoutAlgorithm; 116 TaskStackViewScroller mStackScroller; 117 TaskStackViewTouchHandler mTouchHandler; 118 TaskStackAnimationHelper mAnimationHelper; 119 GradientDrawable mFreeformWorkspaceBackground; 120 ObjectAnimator mFreeformWorkspaceBackgroundAnimator; 121 ViewPool<TaskView, Task> mViewPool; 122 123 ArrayList<TaskView> mTaskViews = new ArrayList<>(); 124 ArrayList<TaskViewTransform> mCurrentTaskTransforms = new ArrayList<>(); 125 ArraySet<Task.TaskKey> mIgnoreTasks = new ArraySet<>(); 126 TaskViewAnimation mDeferredTaskViewLayoutAnimation = null; 127 128 DozeTrigger mUIDozeTrigger; 129 Task mFocusedTask; 130 131 int mTaskCornerRadiusPx; 132 private int mDividerSize; 133 private int mStartTimerIndicatorDuration; 134 135 boolean mTaskViewsClipDirty = true; 136 boolean mAwaitingFirstLayout = true; 137 boolean mEnterAnimationComplete = false; 138 boolean mTouchExplorationEnabled; 139 boolean mScreenPinningEnabled; 140 141 // The stable stack bounds are the full bounds that we were measured with from RecentsView 142 Rect mStableStackBounds = new Rect(); 143 // The current stack bounds are dynamic and may change as the user drags and drops 144 Rect mStackBounds = new Rect(); 145 int[] mTmpVisibleRange = new int[2]; 146 Rect mTmpRect = new Rect(); 147 ArrayMap<Task.TaskKey, TaskView> mTmpTaskViewMap = new ArrayMap<>(); 148 List<TaskView> mTmpTaskViews = new ArrayList<>(); 149 TaskViewTransform mTmpTransform = new TaskViewTransform(); 150 LayoutInflater mInflater; 151 152 // A convenience update listener to request updating clipping of tasks 153 private ValueAnimator.AnimatorUpdateListener mRequestUpdateClippingListener = 154 new ValueAnimator.AnimatorUpdateListener() { 155 @Override 156 public void onAnimationUpdate(ValueAnimator animation) { 157 if (!mTaskViewsClipDirty) { 158 mTaskViewsClipDirty = true; 159 invalidate(); 160 } 161 } 162 }; 163 164 // The drop targets for a task drag 165 private DropTarget mFreeformWorkspaceDropTarget = new DropTarget() { 166 @Override 167 public boolean acceptsDrop(int x, int y, int width, int height, boolean isCurrentTarget) { 168 // This drop target has a fixed bounds and should be checked last, so just fall through 169 // if it is the current target 170 if (!isCurrentTarget) { 171 return mLayoutAlgorithm.mFreeformRect.contains(x, y); 172 } 173 return false; 174 } 175 }; 176 177 private DropTarget mStackDropTarget = new DropTarget() { 178 @Override 179 public boolean acceptsDrop(int x, int y, int width, int height, boolean isCurrentTarget) { 180 // This drop target has a fixed bounds and should be checked last, so just fall through 181 // if it is the current target 182 if (!isCurrentTarget) { 183 return mLayoutAlgorithm.mStackRect.contains(x, y); 184 } 185 return false; 186 } 187 }; 188 189 public TaskStackView(Context context, TaskStack stack) { 190 super(context); 191 SystemServicesProxy ssp = Recents.getSystemServices(); 192 Resources res = context.getResources(); 193 194 // Set the stack first 195 setStack(stack); 196 mViewPool = new ViewPool<>(context, this); 197 mInflater = LayoutInflater.from(context); 198 mLayoutAlgorithm = new TaskStackLayoutAlgorithm(context, this); 199 mStackScroller = new TaskStackViewScroller(context, this, mLayoutAlgorithm); 200 mTouchHandler = new TaskStackViewTouchHandler(context, this, mStackScroller); 201 mAnimationHelper = new TaskStackAnimationHelper(context, this); 202 mTaskCornerRadiusPx = res.getDimensionPixelSize( 203 R.dimen.recents_task_view_rounded_corners_radius); 204 mDividerSize = ssp.getDockedDividerSize(context); 205 206 int taskBarDismissDozeDelaySeconds = getResources().getInteger( 207 R.integer.recents_task_bar_dismiss_delay_seconds); 208 mUIDozeTrigger = new DozeTrigger(taskBarDismissDozeDelaySeconds, new Runnable() { 209 @Override 210 public void run() { 211 // Show the task bar dismiss buttons 212 List<TaskView> taskViews = getTaskViews(); 213 int taskViewCount = taskViews.size(); 214 for (int i = 0; i < taskViewCount; i++) { 215 TaskView tv = taskViews.get(i); 216 tv.startNoUserInteractionAnimation(); 217 } 218 } 219 }); 220 setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES); 221 222 mFreeformWorkspaceBackground = (GradientDrawable) getContext().getDrawable( 223 R.drawable.recents_freeform_workspace_bg); 224 mFreeformWorkspaceBackground.setCallback(this); 225 if (ssp.hasFreeformWorkspaceSupport()) { 226 mFreeformWorkspaceBackground.setColor( 227 getContext().getColor(R.color.recents_freeform_workspace_bg_color)); 228 } 229 } 230 231 @Override 232 protected void onAttachedToWindow() { 233 EventBus.getDefault().register(this, RecentsActivity.EVENT_BUS_PRIORITY + 1); 234 readSystemFlags(); 235 super.onAttachedToWindow(); 236 } 237 238 @Override 239 protected void onDetachedFromWindow() { 240 super.onDetachedFromWindow(); 241 EventBus.getDefault().unregister(this); 242 } 243 244 /** Sets the task stack */ 245 void setStack(TaskStack stack) { 246 // Set the new stack 247 mStack = stack; 248 if (mStack != null) { 249 mStack.setCallbacks(this); 250 } 251 // Layout again with the new stack 252 requestLayout(); 253 } 254 255 /** Returns the task stack. */ 256 TaskStack getStack() { 257 return mStack; 258 } 259 260 /** Updates the list of task views */ 261 void updateTaskViewsList() { 262 mTaskViews.clear(); 263 int childCount = getChildCount(); 264 for (int i = 0; i < childCount; i++) { 265 View v = getChildAt(i); 266 if (v instanceof TaskView) { 267 mTaskViews.add((TaskView) v); 268 } 269 } 270 } 271 272 /** Gets the list of task views */ 273 List<TaskView> getTaskViews() { 274 return mTaskViews; 275 } 276 277 /** 278 * Returns the front most task view. 279 * 280 * @param stackTasksOnly if set, will return the front most task view in the stack (by default 281 * the front most task view will be freeform since they are placed above 282 * stack tasks) 283 */ 284 private TaskView getFrontMostTaskView(boolean stackTasksOnly) { 285 List<TaskView> taskViews = getTaskViews(); 286 int taskViewCount = taskViews.size(); 287 for (int i = taskViewCount - 1; i >= 0; i--) { 288 TaskView tv = taskViews.get(i); 289 Task task = tv.getTask(); 290 if (stackTasksOnly && task.isFreeformTask()) { 291 continue; 292 } 293 return tv; 294 } 295 return null; 296 } 297 298 /** 299 * Finds the child view given a specific {@param task}. 300 */ 301 public TaskView getChildViewForTask(Task t) { 302 List<TaskView> taskViews = getTaskViews(); 303 int taskViewCount = taskViews.size(); 304 for (int i = 0; i < taskViewCount; i++) { 305 TaskView tv = taskViews.get(i); 306 if (tv.getTask() == t) { 307 return tv; 308 } 309 } 310 return null; 311 } 312 313 /** Resets this TaskStackView for reuse. */ 314 void reset() { 315 // Reset the focused task 316 resetFocusedTask(getFocusedTask()); 317 318 // Return all the views to the pool 319 List<TaskView> taskViews = getTaskViews(); 320 int taskViewCount = taskViews.size(); 321 for (int i = taskViewCount - 1; i >= 0; i--) { 322 mViewPool.returnViewToPool(taskViews.get(i)); 323 } 324 325 // Mark each task view for relayout 326 List<TaskView> poolViews = mViewPool.getViews(); 327 for (TaskView tv : poolViews) { 328 tv.reset(); 329 } 330 331 // Reset the stack state 332 mStack.reset(); 333 mTaskViewsClipDirty = true; 334 mAwaitingFirstLayout = true; 335 mEnterAnimationComplete = false; 336 mUIDozeTrigger.stopDozing(); 337 mUIDozeTrigger.resetTrigger(); 338 mStackScroller.reset(); 339 mLayoutAlgorithm.reset(); 340 readSystemFlags(); 341 requestLayout(); 342 } 343 344 /** Returns the stack algorithm for this task stack. */ 345 public TaskStackLayoutAlgorithm getStackAlgorithm() { 346 return mLayoutAlgorithm; 347 } 348 349 /** 350 * Adds a task to the ignored set. 351 */ 352 void addIgnoreTask(Task task) { 353 mIgnoreTasks.add(task.key); 354 } 355 356 /** 357 * Removes a task from the ignored set. 358 */ 359 void removeIgnoreTask(Task task) { 360 mIgnoreTasks.remove(task.key); 361 } 362 363 /** 364 * Returns whether the specified {@param task} is ignored. 365 */ 366 boolean isIgnoredTask(Task task) { 367 return mIgnoreTasks.contains(task.key); 368 } 369 370 /** 371 * Computes the task transforms at the current stack scroll for all visible tasks. If a valid 372 * target stack scroll is provided (ie. is different than {@param curStackScroll}), then the 373 * visible range includes all tasks at the target stack scroll. This is useful for ensure that 374 * all views necessary for a transition or animation will be visible at the start. 375 * 376 * This call ignores freeform tasks. 377 * 378 * @param taskTransforms The set of task view transforms to reuse, this list will be sized to 379 * match the size of {@param tasks} 380 * @param tasks The set of tasks for which to generate transforms 381 * @param curStackScroll The current stack scroll 382 * @param targetStackScroll The stack scroll that we anticipate we are going to be scrolling to. 383 * The range of the union of the visible views at the current and 384 * target stack scrolls will be returned. 385 * @param ignoreTasksSet The set of tasks to skip for purposes of calculaing the visible range. 386 * Transforms will still be calculated for the ignore tasks. 387 */ 388 boolean computeVisibleTaskTransforms(ArrayList<TaskViewTransform> taskTransforms, 389 ArrayList<Task> tasks, float curStackScroll, float targetStackScroll, 390 int[] visibleRangeOut, ArraySet<Task.TaskKey> ignoreTasksSet) { 391 int taskCount = tasks.size(); 392 int frontMostVisibleIndex = -1; 393 int backMostVisibleIndex = -1; 394 boolean useTargetStackScroll = Float.compare(curStackScroll, targetStackScroll) != 0; 395 396 // We can reuse the task transforms where possible to reduce object allocation 397 Utilities.matchTaskListSize(tasks, taskTransforms); 398 399 // Update the stack transforms 400 TaskViewTransform frontTransform = null; 401 TaskViewTransform frontTransformAtTarget = null; 402 TaskViewTransform transform = null; 403 TaskViewTransform transformAtTarget = null; 404 for (int i = taskCount - 1; i >= 0; i--) { 405 Task task = tasks.get(i); 406 407 // Calculate the current and (if necessary) the target transform for the task 408 transform = mLayoutAlgorithm.getStackTransform(task, curStackScroll, 409 taskTransforms.get(i), frontTransform); 410 if (useTargetStackScroll && !transform.visible) { 411 // If we have a target stack scroll and the task is not currently visible, then we 412 // just update the transform at the new scroll 413 // TODO: Optimize this 414 transformAtTarget = mLayoutAlgorithm.getStackTransform(task, 415 targetStackScroll, new TaskViewTransform(), frontTransformAtTarget); 416 if (transformAtTarget.visible) { 417 transform.copyFrom(transformAtTarget); 418 } 419 } 420 421 // For ignore tasks, only calculate the stack transform and skip the calculation of the 422 // visible stack indices 423 if (ignoreTasksSet.contains(task.key)) { 424 continue; 425 } 426 427 // For freeform tasks, only calculate the stack transform and skip the calculation of 428 // the visible stack indices 429 if (task.isFreeformTask()) { 430 continue; 431 } 432 433 if (transform.visible) { 434 if (frontMostVisibleIndex < 0) { 435 frontMostVisibleIndex = i; 436 } 437 backMostVisibleIndex = i; 438 } else { 439 if (backMostVisibleIndex != -1) { 440 // We've reached the end of the visible range, so going down the rest of the 441 // stack, we can just reset the transforms accordingly 442 while (i >= 0) { 443 taskTransforms.get(i).reset(); 444 i--; 445 } 446 break; 447 } 448 } 449 450 frontTransform = transform; 451 frontTransformAtTarget = transformAtTarget; 452 } 453 if (visibleRangeOut != null) { 454 visibleRangeOut[0] = frontMostVisibleIndex; 455 visibleRangeOut[1] = backMostVisibleIndex; 456 } 457 return frontMostVisibleIndex != -1 && backMostVisibleIndex != -1; 458 } 459 460 /** 461 * Binds the visible {@link TaskView}s at the given target scroll. 462 * 463 * @see #bindVisibleTaskViews(float, ArraySet<Task.TaskKey>) 464 */ 465 void bindVisibleTaskViews(float targetStackScroll) { 466 bindVisibleTaskViews(targetStackScroll, mIgnoreTasks); 467 } 468 469 /** 470 * Synchronizes the set of children {@link TaskView}s to match the visible set of tasks in the 471 * current {@link TaskStack}. This call does not continue on to update their position to the 472 * computed {@link TaskViewTransform}s of the visible range, but only ensures that they will 473 * be added/removed from the view hierarchy and placed in the correct Z order and initial 474 * position (if not currently on screen). 475 * 476 * @param targetStackScroll If provided, will ensure that the set of visible {@link TaskView}s 477 * includes those visible at the current stack scroll, and all at the 478 * target stack scroll. 479 * @param ignoreTasksSet The set of tasks to ignore in this rebinding of the visible 480 * {@link TaskView}s 481 */ 482 void bindVisibleTaskViews(float targetStackScroll, ArraySet<Task.TaskKey> ignoreTasksSet) { 483 final int[] visibleStackRange = mTmpVisibleRange; 484 485 // Get all the task transforms 486 final ArrayList<Task> tasks = mStack.getStackTasks(); 487 final boolean isValidVisibleRange = computeVisibleTaskTransforms(mCurrentTaskTransforms, 488 tasks, mStackScroller.getStackScroll(), targetStackScroll, visibleStackRange, 489 ignoreTasksSet); 490 491 // Return all the invisible children to the pool 492 mTmpTaskViewMap.clear(); 493 List<TaskView> taskViews = getTaskViews(); 494 int lastFocusedTaskIndex = -1; 495 int taskViewCount = taskViews.size(); 496 for (int i = taskViewCount - 1; i >= 0; i--) { 497 TaskView tv = taskViews.get(i); 498 Task task = tv.getTask(); 499 int taskIndex = mStack.indexOfStackTask(task); 500 501 // Skip ignored tasks 502 if (ignoreTasksSet.contains(task.key)) { 503 continue; 504 } 505 506 if (task.isFreeformTask() || 507 visibleStackRange[1] <= taskIndex && taskIndex <= visibleStackRange[0]) { 508 mTmpTaskViewMap.put(task.key, tv); 509 } else { 510 if (mTouchExplorationEnabled) { 511 lastFocusedTaskIndex = taskIndex; 512 resetFocusedTask(task); 513 } 514 mViewPool.returnViewToPool(tv); 515 } 516 } 517 518 // Pick up all the newly visible children 519 int lastVisStackIndex = isValidVisibleRange ? visibleStackRange[1] : 0; 520 for (int i = tasks.size() - 1; i >= lastVisStackIndex; i--) { 521 Task task = tasks.get(i); 522 TaskViewTransform transform = mCurrentTaskTransforms.get(i); 523 524 // Skip ignored tasks 525 if (ignoreTasksSet.contains(task.key)) { 526 continue; 527 } 528 529 // Skip the invisible non-freeform stack tasks 530 if (i > visibleStackRange[0] && !task.isFreeformTask()) { 531 continue; 532 } 533 534 TaskView tv = mTmpTaskViewMap.get(task.key); 535 if (tv == null) { 536 tv = mViewPool.pickUpViewFromPool(task, task); 537 if (task.isFreeformTask()) { 538 tv.updateViewPropertiesToTaskTransform(transform, TaskViewAnimation.IMMEDIATE, 539 mRequestUpdateClippingListener); 540 } else { 541 if (Float.compare(transform.p, 0f) <= 0) { 542 tv.updateViewPropertiesToTaskTransform( 543 mLayoutAlgorithm.getBackOfStackTransform(), 544 TaskViewAnimation.IMMEDIATE, mRequestUpdateClippingListener); 545 } else { 546 tv.updateViewPropertiesToTaskTransform( 547 mLayoutAlgorithm.getFrontOfStackTransform(), 548 TaskViewAnimation.IMMEDIATE, mRequestUpdateClippingListener); 549 } 550 } 551 } else { 552 // Reattach it in the right z order 553 final int taskIndex = mStack.indexOfStackTask(task); 554 final int insertIndex = findTaskViewInsertIndex(task, taskIndex); 555 if (insertIndex != getTaskViews().indexOf(tv)){ 556 detachViewFromParent(tv); 557 attachViewToParent(tv, insertIndex, tv.getLayoutParams()); 558 updateTaskViewsList(); 559 } 560 } 561 } 562 563 // Update the focus if the previous focused task was returned to the view pool 564 if (lastFocusedTaskIndex != -1) { 565 if (lastFocusedTaskIndex < visibleStackRange[1]) { 566 setFocusedTask(visibleStackRange[1], false /* scrollToTask */, 567 true /* requestViewFocus */); 568 } else { 569 setFocusedTask(visibleStackRange[0], false /* scrollToTask */, 570 true /* requestViewFocus */); 571 } 572 } 573 } 574 575 /** 576 * Relayout the the visible {@link TaskView}s to their current transforms as specified by the 577 * {@link TaskStackLayoutAlgorithm} with the given {@param animation}. This call cancels any 578 * animations that are current running on those task views, and will ensure that the children 579 * {@link TaskView}s will match the set of visible tasks in the stack. 580 * 581 * @see #relayoutTaskViews(TaskViewAnimation, ArraySet<Task.TaskKey>) 582 */ 583 void relayoutTaskViews(TaskViewAnimation animation) { 584 relayoutTaskViews(animation, mIgnoreTasks); 585 } 586 587 /** 588 * Relayout the the visible {@link TaskView}s to their current transforms as specified by the 589 * {@link TaskStackLayoutAlgorithm} with the given {@param animation}. This call cancels any 590 * animations that are current running on those task views, and will ensure that the children 591 * {@link TaskView}s will match the set of visible tasks in the stack. 592 * 593 * @param ignoreTasksSet the set of tasks to ignore in the relayout 594 */ 595 void relayoutTaskViews(TaskViewAnimation animation, ArraySet<Task.TaskKey> ignoreTasksSet) { 596 // If we had a deferred animation, cancel that 597 mDeferredTaskViewLayoutAnimation = null; 598 599 // Cancel all task view animations 600 cancelAllTaskViewAnimations(); 601 602 // Synchronize the current set of TaskViews 603 bindVisibleTaskViews(mStackScroller.getStackScroll(), ignoreTasksSet); 604 605 // Animate them to their final transforms with the given animation 606 List<TaskView> taskViews = getTaskViews(); 607 int taskViewCount = taskViews.size(); 608 for (int i = 0; i < taskViewCount; i++) { 609 final TaskView tv = taskViews.get(i); 610 final int taskIndex = mStack.indexOfStackTask(tv.getTask()); 611 final TaskViewTransform transform = mCurrentTaskTransforms.get(taskIndex); 612 613 if (ignoreTasksSet.contains(tv.getTask().key)) { 614 continue; 615 } 616 617 updateTaskViewToTransform(tv, transform, animation); 618 } 619 } 620 621 /** 622 * Posts an update to synchronize the {@link TaskView}s with the stack on the next frame. 623 */ 624 void relayoutTaskViewsOnNextFrame(TaskViewAnimation animation) { 625 mDeferredTaskViewLayoutAnimation = animation; 626 invalidate(); 627 } 628 629 /** 630 * Called to update a specific {@link TaskView} to a given {@link TaskViewTransform} with a 631 * given set of {@link TaskViewAnimation} properties. 632 */ 633 public void updateTaskViewToTransform(TaskView taskView, TaskViewTransform transform, 634 TaskViewAnimation animation) { 635 taskView.updateViewPropertiesToTaskTransform(transform, animation, 636 mRequestUpdateClippingListener); 637 } 638 639 /** 640 * Returns the current task transforms of all tasks, falling back to the stack layout if there 641 * is no {@link TaskView} for the task. 642 */ 643 public void getCurrentTaskTransforms(ArrayList<Task> tasks, 644 ArrayList<TaskViewTransform> transformsOut) { 645 Utilities.matchTaskListSize(tasks, transformsOut); 646 for (int i = tasks.size() - 1; i >= 0; i--) { 647 Task task = tasks.get(i); 648 TaskViewTransform transform = transformsOut.get(i); 649 TaskView tv = getChildViewForTask(task); 650 if (tv != null) { 651 transform.fillIn(tv); 652 } else { 653 mLayoutAlgorithm.getStackTransform(task, mStackScroller.getStackScroll(), 654 transform, null); 655 } 656 transform.visible = true; 657 } 658 } 659 660 /** 661 * Returns the task transforms for all the tasks in the stack if the stack was at the given 662 * {@param stackScroll}. 663 */ 664 public void getLayoutTaskTransforms(float stackScroll, ArrayList<Task> tasks, 665 ArrayList<TaskViewTransform> transformsOut) { 666 Utilities.matchTaskListSize(tasks, transformsOut); 667 for (int i = tasks.size() - 1; i >= 0; i--) { 668 Task task = tasks.get(i); 669 TaskViewTransform transform = transformsOut.get(i); 670 mLayoutAlgorithm.getStackTransform(task, stackScroll, transform, null); 671 transform.visible = true; 672 } 673 } 674 675 /** 676 * Cancels all {@link TaskView} animations. 677 * 678 * @see #cancelAllTaskViewAnimations(ArraySet<Task.TaskKey>) 679 */ 680 void cancelAllTaskViewAnimations() { 681 cancelAllTaskViewAnimations(mIgnoreTasks); 682 } 683 684 /** 685 * Cancels all {@link TaskView} animations. 686 * 687 * @param ignoreTasksSet The set of tasks to continue running their animations. 688 */ 689 void cancelAllTaskViewAnimations(ArraySet<Task.TaskKey> ignoreTasksSet) { 690 List<TaskView> taskViews = getTaskViews(); 691 for (int i = taskViews.size() - 1; i >= 0; i--) { 692 final TaskView tv = taskViews.get(i); 693 if (!ignoreTasksSet.contains(tv.getTask().key)) { 694 tv.cancelTransformAnimation(); 695 } 696 } 697 } 698 699 /** 700 * Updates the clip for each of the task views from back to front. 701 */ 702 private void clipTaskViews() { 703 RecentsConfiguration config = Recents.getConfiguration(); 704 705 // Update the clip on each task child 706 List<TaskView> taskViews = getTaskViews(); 707 TaskView tmpTv = null; 708 TaskView prevVisibleTv = null; 709 int taskViewCount = taskViews.size(); 710 for (int i = 0; i < taskViewCount; i++) { 711 TaskView tv = taskViews.get(i); 712 TaskView frontTv = null; 713 int clipBottom = 0; 714 715 if (mIgnoreTasks.contains(tv.getTask().key)) { 716 // For each of the ignore tasks, update the translationZ of its TaskView to be 717 // between the translationZ of the tasks immediately underneath it 718 if (prevVisibleTv != null) { 719 tv.setTranslationZ(Math.max(tv.getTranslationZ(), 720 prevVisibleTv.getTranslationZ() + 0.1f)); 721 } 722 } 723 724 if (i < (taskViewCount - 1) && tv.shouldClipViewInStack()) { 725 // Find the next view to clip against 726 for (int j = i + 1; j < taskViewCount; j++) { 727 tmpTv = taskViews.get(j); 728 729 if (tmpTv.shouldClipViewInStack()) { 730 frontTv = tmpTv; 731 break; 732 } 733 } 734 735 // Clip against the next view, this is just an approximation since we are 736 // stacked and we can make assumptions about the visibility of the this 737 // task relative to the ones in front of it. 738 if (frontTv != null) { 739 float taskBottom = tv.getBottom(); 740 float frontTaskTop = frontTv.getTop(); 741 if (frontTaskTop < taskBottom) { 742 // Map the stack view space coordinate (the rects) to view space 743 clipBottom = (int) (taskBottom - frontTaskTop) - mTaskCornerRadiusPx; 744 } 745 } 746 } 747 tv.getViewBounds().setClipBottom(clipBottom); 748 if (!config.useHardwareLayers) { 749 tv.mThumbnailView.updateThumbnailVisibility(clipBottom - tv.getPaddingBottom()); 750 } 751 prevVisibleTv = tv; 752 } 753 mTaskViewsClipDirty = false; 754 } 755 756 /** 757 * Updates the layout algorithm min and max virtual scroll bounds. 758 * 759 * @see #updateLayoutAlgorithm(boolean, ArraySet<Task.TaskKey>) 760 */ 761 void updateLayoutAlgorithm(boolean boundScrollToNewMinMax) { 762 updateLayoutAlgorithm(boundScrollToNewMinMax, mIgnoreTasks); 763 } 764 765 /** 766 * Updates the min and max virtual scroll bounds. 767 * 768 * @param ignoreTasksSet the set of tasks to ignore in the relayout 769 */ 770 void updateLayoutAlgorithm(boolean boundScrollToNewMinMax, 771 ArraySet<Task.TaskKey> ignoreTasksSet) { 772 // Compute the min and max scroll values 773 mLayoutAlgorithm.update(mStack, ignoreTasksSet); 774 775 // Update the freeform workspace background 776 SystemServicesProxy ssp = Recents.getSystemServices(); 777 if (ssp.hasFreeformWorkspaceSupport()) { 778 mTmpRect.set(mLayoutAlgorithm.mFreeformRect); 779 mFreeformWorkspaceBackground.setBounds(mTmpRect); 780 } 781 782 if (boundScrollToNewMinMax) { 783 mStackScroller.boundScroll(); 784 } 785 } 786 787 /** Returns the scroller. */ 788 public TaskStackViewScroller getScroller() { 789 return mStackScroller; 790 } 791 792 /** 793 * Sets the focused task to the provided (bounded taskIndex). 794 * 795 * @return whether or not the stack will scroll as a part of this focus change 796 */ 797 private boolean setFocusedTask(int taskIndex, boolean scrollToTask, 798 final boolean requestViewFocus) { 799 return setFocusedTask(taskIndex, scrollToTask, requestViewFocus, 0); 800 } 801 802 /** 803 * Sets the focused task to the provided (bounded taskIndex). 804 * 805 * @return whether or not the stack will scroll as a part of this focus change 806 */ 807 private boolean setFocusedTask(int taskIndex, boolean scrollToTask, 808 final boolean requestViewFocus, final int timerIndicatorDuration) { 809 // Find the next task to focus 810 int newFocusedTaskIndex = mStack.getTaskCount() > 0 ? 811 Math.max(0, Math.min(mStack.getTaskCount() - 1, taskIndex)) : -1; 812 final Task newFocusedTask = (newFocusedTaskIndex != -1) ? 813 mStack.getStackTasks().get(newFocusedTaskIndex) : null; 814 815 // Reset the last focused task state if changed 816 if (mFocusedTask != null) { 817 // Cancel the timer indicator, if applicable 818 if (timerIndicatorDuration > 0) { 819 final TaskView tv = getChildViewForTask(mFocusedTask); 820 if (tv != null) { 821 tv.getHeaderView().cancelFocusTimerIndicator(); 822 } 823 } 824 825 resetFocusedTask(mFocusedTask); 826 } 827 828 boolean willScroll = false; 829 830 mFocusedTask = newFocusedTask; 831 832 if (newFocusedTask != null) { 833 // Start the timer indicator, if applicable 834 if (timerIndicatorDuration > 0) { 835 final TaskView tv = getChildViewForTask(mFocusedTask); 836 if (tv != null) { 837 tv.getHeaderView().startFocusTimerIndicator(timerIndicatorDuration); 838 } else { 839 // The view is null; set a flag for later 840 mStartTimerIndicatorDuration = timerIndicatorDuration; 841 } 842 } 843 844 Runnable focusTaskRunnable = new Runnable() { 845 @Override 846 public void run() { 847 final TaskView tv = getChildViewForTask(newFocusedTask); 848 if (tv != null) { 849 tv.setFocusedState(true, requestViewFocus); 850 } 851 } 852 }; 853 854 if (scrollToTask) { 855 // Cancel any running enter animations at this point when we scroll or change focus 856 if (!mEnterAnimationComplete) { 857 cancelAllTaskViewAnimations(); 858 } 859 860 // TODO: Center the newly focused task view, only if not freeform 861 float newScroll = mLayoutAlgorithm.getStackScrollForTask(newFocusedTask); 862 if (Float.compare(newScroll, mStackScroller.getStackScroll()) != 0) { 863 mStackScroller.animateScroll(mStackScroller.getStackScroll(), newScroll, 864 focusTaskRunnable); 865 willScroll = true; 866 } else { 867 focusTaskRunnable.run(); 868 } 869 mLayoutAlgorithm.animateFocusState(TaskStackLayoutAlgorithm.STATE_FOCUSED); 870 } else { 871 focusTaskRunnable.run(); 872 } 873 } 874 return willScroll; 875 } 876 877 /** 878 * Sets the focused task relative to the currently focused task. 879 * 880 * @param forward whether to go to the next task in the stack (along the curve) or the previous 881 * @param stackTasksOnly if set, will ensure that the traversal only goes along stack tasks, and 882 * if the currently focused task is not a stack task, will set the focus 883 * to the first visible stack task 884 * @param animated determines whether to actually draw the highlight along with the change in 885 * focus. 886 */ 887 public void setRelativeFocusedTask(boolean forward, boolean stackTasksOnly, boolean animated) { 888 setRelativeFocusedTask(forward, stackTasksOnly, animated, false); 889 } 890 891 /** 892 * Sets the focused task relative to the currently focused task. 893 * 894 * @param forward whether to go to the next task in the stack (along the curve) or the previous 895 * @param stackTasksOnly if set, will ensure that the traversal only goes along stack tasks, and 896 * if the currently focused task is not a stack task, will set the focus 897 * to the first visible stack task 898 * @param animated determines whether to actually draw the highlight along with the change in 899 * focus. 900 * @param cancelWindowAnimations if set, will attempt to cancel window animations if a scroll 901 * happens. 902 */ 903 public void setRelativeFocusedTask(boolean forward, boolean stackTasksOnly, boolean animated, 904 boolean cancelWindowAnimations) { 905 setRelativeFocusedTask(forward, stackTasksOnly, animated, cancelWindowAnimations, 0); 906 } 907 908 /** 909 * Sets the focused task relative to the currently focused task. 910 * 911 * @param forward whether to go to the next task in the stack (along the curve) or the previous 912 * @param stackTasksOnly if set, will ensure that the traversal only goes along stack tasks, and 913 * if the currently focused task is not a stack task, will set the focus 914 * to the first visible stack task 915 * @param animated determines whether to actually draw the highlight along with the change in 916 * focus. 917 * @param cancelWindowAnimations if set, will attempt to cancel window animations if a scroll 918 * happens. 919 * @param timerIndicatorDuration the duration to initialize the auto-advance timer indicator 920 */ 921 public void setRelativeFocusedTask(boolean forward, boolean stackTasksOnly, boolean animated, 922 boolean cancelWindowAnimations, 923 int timerIndicatorDuration) { 924 int newIndex = mStack.indexOfStackTask(mFocusedTask); 925 if (mFocusedTask != null) { 926 if (stackTasksOnly) { 927 List<Task> tasks = mStack.getStackTasks(); 928 if (mFocusedTask.isFreeformTask()) { 929 // Try and focus the front most stack task 930 TaskView tv = getFrontMostTaskView(stackTasksOnly); 931 if (tv != null) { 932 newIndex = mStack.indexOfStackTask(tv.getTask()); 933 } 934 } else { 935 // Try the next task if it is a stack task 936 int tmpNewIndex = newIndex + (forward ? -1 : 1); 937 if (0 <= tmpNewIndex && tmpNewIndex < tasks.size()) { 938 Task t = tasks.get(tmpNewIndex); 939 if (!t.isFreeformTask()) { 940 newIndex = tmpNewIndex; 941 } 942 } 943 } 944 } else { 945 // No restrictions, lets just move to the new task (looping forward/backwards if 946 // necessary) 947 int taskCount = mStack.getTaskCount(); 948 newIndex = (newIndex + (forward ? -1 : 1) + taskCount) % taskCount; 949 } 950 } else { 951 // We don't have a focused task, so focus the first visible task view 952 TaskView tv = getFrontMostTaskView(stackTasksOnly); 953 if (tv != null) { 954 newIndex = mStack.indexOfStackTask(tv.getTask()); 955 } 956 } 957 if (newIndex != -1) { 958 boolean willScroll = setFocusedTask(newIndex, true /* scrollToTask */, 959 true /* requestViewFocus */, timerIndicatorDuration); 960 if (willScroll && cancelWindowAnimations) { 961 // As we iterate to the next/previous task, cancel any current/lagging window 962 // transition animations 963 EventBus.getDefault().send(new CancelEnterRecentsWindowAnimationEvent(null)); 964 } 965 } 966 } 967 968 /** 969 * Resets the focused task. 970 */ 971 void resetFocusedTask(Task task) { 972 if (task != null) { 973 TaskView tv = getChildViewForTask(task); 974 if (tv != null) { 975 tv.setFocusedState(false, false /* requestViewFocus */); 976 } 977 } 978 mFocusedTask = null; 979 } 980 981 /** 982 * Returns the focused task. 983 */ 984 Task getFocusedTask() { 985 return mFocusedTask; 986 } 987 988 @Override 989 public void onInitializeAccessibilityEvent(AccessibilityEvent event) { 990 super.onInitializeAccessibilityEvent(event); 991 List<TaskView> taskViews = getTaskViews(); 992 int taskViewCount = taskViews.size(); 993 if (taskViewCount > 0) { 994 TaskView backMostTask = taskViews.get(0); 995 TaskView frontMostTask = taskViews.get(taskViewCount - 1); 996 event.setFromIndex(mStack.indexOfStackTask(backMostTask.getTask())); 997 event.setToIndex(mStack.indexOfStackTask(frontMostTask.getTask())); 998 event.setContentDescription(frontMostTask.getTask().title); 999 } 1000 event.setItemCount(mStack.getTaskCount()); 1001 event.setScrollY(mStackScroller.mScroller.getCurrY()); 1002 event.setMaxScrollY(mStackScroller.progressToScrollRange(mLayoutAlgorithm.mMaxScrollP)); 1003 } 1004 1005 @Override 1006 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { 1007 super.onInitializeAccessibilityNodeInfo(info); 1008 List<TaskView> taskViews = getTaskViews(); 1009 int taskViewCount = taskViews.size(); 1010 if (taskViewCount > 1 && mFocusedTask != null) { 1011 info.setScrollable(true); 1012 int focusedTaskIndex = mStack.indexOfStackTask(mFocusedTask); 1013 if (focusedTaskIndex > 0) { 1014 info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD); 1015 } 1016 if (focusedTaskIndex < mStack.getTaskCount() - 1) { 1017 info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD); 1018 } 1019 } 1020 } 1021 1022 @Override 1023 protected Parcelable onSaveInstanceState() { 1024 Bundle savedState = new Bundle(); 1025 savedState.putParcelable(KEY_SAVED_STATE_SUPER, super.onSaveInstanceState()); 1026 savedState.putFloat(KEY_SAVED_STATE_LAYOUT_FOCUSED_STATE, mLayoutAlgorithm.getFocusState()); 1027 savedState.putFloat(KEY_SAVED_STATE_LAYOUT_STACK_SCROLL, mStackScroller.getStackScroll()); 1028 return super.onSaveInstanceState(); 1029 } 1030 1031 @Override 1032 protected void onRestoreInstanceState(Parcelable state) { 1033 Bundle savedState = (Bundle) state; 1034 super.onRestoreInstanceState(savedState.getParcelable(KEY_SAVED_STATE_SUPER)); 1035 1036 mLayoutAlgorithm.setFocusState(savedState.getFloat(KEY_SAVED_STATE_LAYOUT_FOCUSED_STATE)); 1037 mStackScroller.setStackScroll(savedState.getFloat(KEY_SAVED_STATE_LAYOUT_STACK_SCROLL)); 1038 } 1039 1040 @Override 1041 public CharSequence getAccessibilityClassName() { 1042 return TaskStackView.class.getName(); 1043 } 1044 1045 @Override 1046 public boolean performAccessibilityAction(int action, Bundle arguments) { 1047 if (super.performAccessibilityAction(action, arguments)) { 1048 return true; 1049 } 1050 switch (action) { 1051 case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD: { 1052 setRelativeFocusedTask(true, false /* stackTasksOnly */, false /* animated */); 1053 return true; 1054 } 1055 case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD: { 1056 setRelativeFocusedTask(false, false /* stackTasksOnly */, false /* animated */); 1057 return true; 1058 } 1059 } 1060 return false; 1061 } 1062 1063 @Override 1064 public boolean onInterceptTouchEvent(MotionEvent ev) { 1065 return mTouchHandler.onInterceptTouchEvent(ev); 1066 } 1067 1068 @Override 1069 public boolean onTouchEvent(MotionEvent ev) { 1070 return mTouchHandler.onTouchEvent(ev); 1071 } 1072 1073 @Override 1074 public boolean onGenericMotionEvent(MotionEvent ev) { 1075 return mTouchHandler.onGenericMotionEvent(ev); 1076 } 1077 1078 @Override 1079 public void computeScroll() { 1080 if (mStackScroller.computeScroll()) { 1081 // Notify accessibility 1082 sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SCROLLED); 1083 } 1084 if (mDeferredTaskViewLayoutAnimation != null) { 1085 relayoutTaskViews(mDeferredTaskViewLayoutAnimation); 1086 mTaskViewsClipDirty = true; 1087 mDeferredTaskViewLayoutAnimation = null; 1088 } 1089 if (mTaskViewsClipDirty) { 1090 clipTaskViews(); 1091 } 1092 } 1093 1094 /** 1095 * This is ONLY used from the Recents component to update the dummy stack view for purposes 1096 * of getting the task rect to animate to. 1097 */ 1098 public void updateLayoutForStack(TaskStack stack) { 1099 mStack = stack; 1100 updateLayoutAlgorithm(false /* boundScroll */, EMPTY_TASK_SET); 1101 } 1102 1103 /** 1104 * Computes the maximum number of visible tasks and thumbnails. Requires that 1105 * updateLayoutForStack() is called first. 1106 */ 1107 public TaskStackLayoutAlgorithm.VisibilityReport computeStackVisibilityReport() { 1108 return mLayoutAlgorithm.computeStackVisibilityReport(mStack.getStackTasks()); 1109 } 1110 1111 /** 1112 * Updates the expected task stack bounds for this stack view. 1113 */ 1114 public void setTaskStackBounds(Rect taskStackBounds, Rect systemInsets) { 1115 // We can get spurious measure passes with the old bounds when docking, and since we are 1116 // using the current stack bounds during drag and drop, don't overwrite them until we 1117 // actually get new bounds 1118 boolean requiresLayout = false; 1119 if (!taskStackBounds.equals(mStableStackBounds)) { 1120 mStableStackBounds.set(taskStackBounds); 1121 mStackBounds.set(taskStackBounds); 1122 requiresLayout = true; 1123 } 1124 if (!systemInsets.equals(mLayoutAlgorithm.mSystemInsets)) { 1125 mLayoutAlgorithm.setSystemInsets(systemInsets); 1126 requiresLayout = true; 1127 } 1128 if (requiresLayout) { 1129 requestLayout(); 1130 } 1131 } 1132 1133 /** 1134 * This is called with the full window width and height to allow stack view children to 1135 * perform the full screen transition down. 1136 */ 1137 @Override 1138 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 1139 int width = MeasureSpec.getSize(widthMeasureSpec); 1140 int height = MeasureSpec.getSize(heightMeasureSpec); 1141 1142 // Compute the rects in the stack algorithm 1143 mLayoutAlgorithm.initialize(mStackBounds, 1144 TaskStackLayoutAlgorithm.StackState.getStackStateForStack(mStack)); 1145 updateLayoutAlgorithm(false /* boundScroll */, EMPTY_TASK_SET); 1146 1147 // If this is the first layout, then scroll to the front of the stack, then update the 1148 // TaskViews with the stack so that we can lay them out 1149 if (mAwaitingFirstLayout) { 1150 mStackScroller.setStackScrollToInitialState(); 1151 } 1152 // Rebind all the views, including the ignore ones 1153 bindVisibleTaskViews(mStackScroller.getStackScroll(), EMPTY_TASK_SET); 1154 1155 // Measure each of the TaskViews 1156 mTmpTaskViews.clear(); 1157 mTmpTaskViews.addAll(getTaskViews()); 1158 mTmpTaskViews.addAll(mViewPool.getViews()); 1159 int taskViewCount = mTmpTaskViews.size(); 1160 for (int i = 0; i < taskViewCount; i++) { 1161 TaskView tv = mTmpTaskViews.get(i); 1162 if (tv.getBackground() != null) { 1163 tv.getBackground().getPadding(mTmpRect); 1164 } else { 1165 mTmpRect.setEmpty(); 1166 } 1167 tv.measure( 1168 MeasureSpec.makeMeasureSpec( 1169 mLayoutAlgorithm.mTaskRect.width() + mTmpRect.left + mTmpRect.right, 1170 MeasureSpec.EXACTLY), 1171 MeasureSpec.makeMeasureSpec( 1172 mLayoutAlgorithm.mTaskRect.height() + mTmpRect.top + mTmpRect.bottom, 1173 MeasureSpec.EXACTLY)); 1174 } 1175 1176 setMeasuredDimension(width, height); 1177 } 1178 1179 /** 1180 * This is called with the size of the space not including the top or right insets, or the 1181 * search bar height in portrait (but including the search bar width in landscape, since we want 1182 * to draw under it. 1183 */ 1184 @Override 1185 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 1186 // Layout each of the TaskViews 1187 mTmpTaskViews.clear(); 1188 mTmpTaskViews.addAll(getTaskViews()); 1189 mTmpTaskViews.addAll(mViewPool.getViews()); 1190 int taskViewCount = mTmpTaskViews.size(); 1191 for (int i = 0; i < taskViewCount; i++) { 1192 TaskView tv = mTmpTaskViews.get(i); 1193 if (tv.getBackground() != null) { 1194 tv.getBackground().getPadding(mTmpRect); 1195 } else { 1196 mTmpRect.setEmpty(); 1197 } 1198 Rect taskRect = mLayoutAlgorithm.mTaskRect; 1199 tv.layout(taskRect.left - mTmpRect.left, taskRect.top - mTmpRect.top, 1200 taskRect.right + mTmpRect.right, taskRect.bottom + mTmpRect.bottom); 1201 } 1202 1203 if (changed) { 1204 if (mStackScroller.isScrollOutOfBounds()) { 1205 mStackScroller.boundScroll(); 1206 } 1207 } 1208 // Relayout all of the task views including the ignored ones 1209 relayoutTaskViews(TaskViewAnimation.IMMEDIATE, EMPTY_TASK_SET); 1210 clipTaskViews(); 1211 1212 if (mAwaitingFirstLayout || !mEnterAnimationComplete) { 1213 mAwaitingFirstLayout = false; 1214 onFirstLayout(); 1215 return; 1216 } 1217 } 1218 1219 /** Handler for the first layout. */ 1220 void onFirstLayout() { 1221 // Setup the view for the enter animation 1222 mAnimationHelper.prepareForEnterAnimation(); 1223 1224 // Animate in the freeform workspace 1225 animateFreeformWorkspaceBackgroundAlpha( 1226 mLayoutAlgorithm.getStackState().freeformBackgroundAlpha, 150, 1227 Interpolators.FAST_OUT_SLOW_IN); 1228 1229 // Set the task focused state without requesting view focus, and leave the focus animations 1230 // until after the enter-animation 1231 Task launchTask = mStack.getLaunchTarget(); 1232 RecentsConfiguration config = Recents.getConfiguration(); 1233 RecentsActivityLaunchState launchState = config.getLaunchState(); 1234 int focusedTaskIndex = launchState.getInitialFocusTaskIndex(mStack.getTaskCount()); 1235 if (focusedTaskIndex != -1) { 1236 setFocusedTask(focusedTaskIndex, false /* scrollToTask */, 1237 false /* requestViewFocus */); 1238 } 1239 1240 // Update the history button visibility 1241 if (shouldShowHistoryButton() && 1242 mStackScroller.getStackScroll() < SHOW_HISTORY_BUTTON_SCROLL_THRESHOLD) { 1243 EventBus.getDefault().send(new ShowHistoryButtonEvent(false /* translate */)); 1244 } else { 1245 EventBus.getDefault().send(new HideHistoryButtonEvent()); 1246 } 1247 } 1248 1249 public boolean isTouchPointInView(float x, float y, TaskView tv) { 1250 mTmpRect.set(tv.getLeft(), tv.getTop(), tv.getRight(), tv.getBottom()); 1251 mTmpRect.offset((int) tv.getTranslationX(), (int) tv.getTranslationY()); 1252 return mTmpRect.contains((int) x, (int) y); 1253 } 1254 1255 /** 1256 * Returns a non-ignored task in the {@param tasks} list that can be used as an achor when 1257 * calculating the scroll position before and after a layout change. 1258 */ 1259 public Task findAnchorTask(List<Task> tasks, MutableBoolean isFrontMostTask) { 1260 for (int i = tasks.size() - 1; i >= 0; i--) { 1261 Task task = tasks.get(i); 1262 1263 // Ignore deleting tasks 1264 if (mIgnoreTasks.contains(task.key)) { 1265 if (i == tasks.size() - 1) { 1266 isFrontMostTask.value = true; 1267 } 1268 continue; 1269 } 1270 return task; 1271 } 1272 return null; 1273 } 1274 1275 @Override 1276 protected void dispatchDraw(Canvas canvas) { 1277 // Draw the freeform workspace background 1278 SystemServicesProxy ssp = Recents.getSystemServices(); 1279 if (ssp.hasFreeformWorkspaceSupport()) { 1280 if (mFreeformWorkspaceBackground.getAlpha() > 0) { 1281 mFreeformWorkspaceBackground.draw(canvas); 1282 } 1283 } 1284 1285 super.dispatchDraw(canvas); 1286 } 1287 1288 @Override 1289 protected boolean verifyDrawable(Drawable who) { 1290 if (who == mFreeformWorkspaceBackground) { 1291 return true; 1292 } 1293 return super.verifyDrawable(who); 1294 } 1295 1296 /** 1297 * Launches the freeform tasks. 1298 */ 1299 public boolean launchFreeformTasks() { 1300 ArrayList<Task> tasks = mStack.getFreeformTasks(); 1301 if (!tasks.isEmpty()) { 1302 Task frontTask = tasks.get(tasks.size() - 1); 1303 if (frontTask != null && frontTask.isFreeformTask()) { 1304 EventBus.getDefault().send(new LaunchTaskEvent(getChildViewForTask(frontTask), 1305 frontTask, null, INVALID_STACK_ID, false)); 1306 return true; 1307 } 1308 } 1309 return false; 1310 } 1311 1312 /**** TaskStackCallbacks Implementation ****/ 1313 1314 @Override 1315 public void onStackTaskAdded(TaskStack stack, Task newTask) { 1316 // Update the min/max scroll and animate other task views into their new positions 1317 updateLayoutAlgorithm(true /* boundScroll */); 1318 1319 // Animate all the tasks into place 1320 relayoutTaskViews(new TaskViewAnimation(DEFAULT_SYNC_STACK_DURATION, 1321 Interpolators.FAST_OUT_SLOW_IN)); 1322 } 1323 1324 /** 1325 * We expect that the {@link TaskView} associated with the removed task is already hidden. 1326 */ 1327 @Override 1328 public void onStackTaskRemoved(TaskStack stack, Task removedTask, boolean wasFrontMostTask, 1329 Task newFrontMostTask, TaskViewAnimation animation) { 1330 if (mFocusedTask == removedTask) { 1331 resetFocusedTask(removedTask); 1332 } 1333 1334 // Remove the view associated with this task, we can't rely on updateTransforms 1335 // to work here because the task is no longer in the list 1336 TaskView tv = getChildViewForTask(removedTask); 1337 if (tv != null) { 1338 mViewPool.returnViewToPool(tv); 1339 } 1340 1341 // Remove the task from the ignored set 1342 removeIgnoreTask(removedTask); 1343 1344 // If requested, relayout with the given animation 1345 if (animation != null) { 1346 updateLayoutAlgorithm(true /* boundScroll */); 1347 relayoutTaskViews(animation); 1348 } 1349 1350 // Update the new front most task's action button 1351 if (mScreenPinningEnabled && newFrontMostTask != null) { 1352 TaskView frontTv = getChildViewForTask(newFrontMostTask); 1353 if (frontTv != null) { 1354 frontTv.showActionButton(true /* fadeIn */, DEFAULT_SYNC_STACK_DURATION); 1355 } 1356 } 1357 1358 // If there are no remaining tasks, then just close recents 1359 if (mStack.getTaskCount() == 0) { 1360 EventBus.getDefault().send(new AllTaskViewsDismissedEvent()); 1361 } 1362 } 1363 1364 @Override 1365 public void onHistoryTaskRemoved(TaskStack stack, Task removedTask, 1366 TaskViewAnimation animation) { 1367 // To be implemented 1368 } 1369 1370 /**** ViewPoolConsumer Implementation ****/ 1371 1372 @Override 1373 public TaskView createView(Context context) { 1374 return (TaskView) mInflater.inflate(R.layout.recents_task_view, this, false); 1375 } 1376 1377 @Override 1378 public void prepareViewToEnterPool(TaskView tv) { 1379 final Task task = tv.getTask(); 1380 1381 // Report that this tasks's data is no longer being used 1382 Recents.getTaskLoader().unloadTaskData(task); 1383 1384 // Reset the view properties and view state 1385 tv.resetViewProperties(); 1386 tv.setFocusedState(false, false /* requestViewFocus */); 1387 tv.setClipViewInStack(false); 1388 if (mScreenPinningEnabled) { 1389 tv.hideActionButton(false /* fadeOut */, 0 /* duration */, false /* scaleDown */, null); 1390 } 1391 1392 // Detach the view from the hierarchy 1393 detachViewFromParent(tv); 1394 // Update the task views list after removing the task view 1395 updateTaskViewsList(); 1396 } 1397 1398 @Override 1399 public void prepareViewToLeavePool(TaskView tv, Task task, boolean isNewView) { 1400 // Find the index where this task should be placed in the stack 1401 int taskIndex = mStack.indexOfStackTask(task); 1402 int insertIndex = findTaskViewInsertIndex(task, taskIndex); 1403 1404 // Add/attach the view to the hierarchy 1405 if (isNewView) { 1406 addView(tv, insertIndex); 1407 } else { 1408 attachViewToParent(tv, insertIndex, tv.getLayoutParams()); 1409 } 1410 // Update the task views list after adding the new task view 1411 updateTaskViewsList(); 1412 1413 // Rebind the task and request that this task's data be filled into the TaskView 1414 tv.onTaskBound(task); 1415 1416 // Load the task data 1417 Recents.getTaskLoader().loadTaskData(task, true /* fetchAndInvalidateThumbnails */); 1418 1419 // If the doze trigger has already fired, then update the state for this task view 1420 tv.setNoUserInteractionState(); 1421 1422 // Set the new state for this view, including the callbacks and view clipping 1423 tv.setCallbacks(this); 1424 tv.setTouchEnabled(true); 1425 tv.setClipViewInStack(true); 1426 if (mFocusedTask == task) { 1427 tv.setFocusedState(true, false /* requestViewFocus */); 1428 if (mStartTimerIndicatorDuration > 0) { 1429 // The timer indicator couldn't be started before, so start it now 1430 tv.getHeaderView().startFocusTimerIndicator(mStartTimerIndicatorDuration); 1431 mStartTimerIndicatorDuration = 0; 1432 } 1433 } 1434 1435 // Restore the action button visibility if it is the front most task view 1436 if (mScreenPinningEnabled && tv.getTask() == 1437 mStack.getStackFrontMostTask(false /* includeFreeform */)) { 1438 tv.showActionButton(false /* fadeIn */, 0 /* fadeInDuration */); 1439 } 1440 } 1441 1442 @Override 1443 public boolean hasPreferredData(TaskView tv, Task preferredData) { 1444 return (tv.getTask() == preferredData); 1445 } 1446 1447 /**** TaskViewCallbacks Implementation ****/ 1448 1449 @Override 1450 public void onTaskViewClipStateChanged(TaskView tv) { 1451 if (!mTaskViewsClipDirty) { 1452 mTaskViewsClipDirty = true; 1453 invalidate(); 1454 } 1455 } 1456 1457 /**** TaskStackLayoutAlgorithm.TaskStackLayoutAlgorithmCallbacks ****/ 1458 1459 @Override 1460 public void onFocusStateChanged(float prevFocusState, float curFocusState) { 1461 if (mDeferredTaskViewLayoutAnimation == null) { 1462 mUIDozeTrigger.poke(); 1463 relayoutTaskViewsOnNextFrame(TaskViewAnimation.IMMEDIATE); 1464 } 1465 } 1466 1467 /**** TaskStackViewScroller.TaskStackViewScrollerCallbacks ****/ 1468 1469 @Override 1470 public void onScrollChanged(float prevScroll, float curScroll, TaskViewAnimation animation) { 1471 mUIDozeTrigger.poke(); 1472 if (animation != null) { 1473 relayoutTaskViewsOnNextFrame(animation); 1474 } 1475 1476 if (mEnterAnimationComplete) { 1477 if (shouldShowHistoryButton() && 1478 prevScroll > SHOW_HISTORY_BUTTON_SCROLL_THRESHOLD && 1479 curScroll <= SHOW_HISTORY_BUTTON_SCROLL_THRESHOLD) { 1480 EventBus.getDefault().send(new ShowHistoryButtonEvent(true /* translate */)); 1481 } else if (prevScroll < HIDE_HISTORY_BUTTON_SCROLL_THRESHOLD && 1482 curScroll >= HIDE_HISTORY_BUTTON_SCROLL_THRESHOLD) { 1483 EventBus.getDefault().send(new HideHistoryButtonEvent()); 1484 } 1485 } 1486 } 1487 1488 /**** EventBus Events ****/ 1489 1490 public final void onBusEvent(PackagesChangedEvent event) { 1491 // Compute which components need to be removed 1492 ArraySet<ComponentName> removedComponents = mStack.computeComponentsRemoved( 1493 event.packageName, event.userId); 1494 1495 // For other tasks, just remove them directly if they no longer exist 1496 ArrayList<Task> tasks = mStack.getStackTasks(); 1497 for (int i = tasks.size() - 1; i >= 0; i--) { 1498 final Task t = tasks.get(i); 1499 if (removedComponents.contains(t.key.getComponent())) { 1500 final TaskView tv = getChildViewForTask(t); 1501 if (tv != null) { 1502 // For visible children, defer removing the task until after the animation 1503 tv.dismissTask(); 1504 } else { 1505 // Otherwise, remove the task from the stack immediately 1506 mStack.removeTask(t, TaskViewAnimation.IMMEDIATE); 1507 } 1508 } 1509 } 1510 } 1511 1512 public final void onBusEvent(LaunchTaskEvent event) { 1513 // Cancel any doze triggers once a task is launched 1514 mUIDozeTrigger.stopDozing(); 1515 } 1516 1517 public final void onBusEvent(LaunchTaskStartedEvent event) { 1518 mAnimationHelper.startLaunchTaskAnimation(event.taskView, event.screenPinningRequested, 1519 event.getAnimationTrigger()); 1520 } 1521 1522 public final void onBusEvent(DismissRecentsToHomeAnimationStarted event) { 1523 // Stop any scrolling 1524 mStackScroller.stopScroller(); 1525 mStackScroller.stopBoundScrollAnimation(); 1526 1527 // Start the task animations 1528 mAnimationHelper.startExitToHomeAnimation(event.animated, event.getAnimationTrigger()); 1529 1530 // Dismiss the freeform workspace background 1531 int taskViewExitToHomeDuration = getResources().getInteger( 1532 R.integer.recents_task_exit_to_home_duration); 1533 animateFreeformWorkspaceBackgroundAlpha(0, taskViewExitToHomeDuration, 1534 Interpolators.FAST_OUT_SLOW_IN); 1535 } 1536 1537 public final void onBusEvent(DismissFocusedTaskViewEvent event) { 1538 if (mFocusedTask != null) { 1539 TaskView tv = getChildViewForTask(mFocusedTask); 1540 if (tv != null) { 1541 tv.dismissTask(); 1542 } 1543 resetFocusedTask(mFocusedTask); 1544 } 1545 } 1546 1547 public final void onBusEvent(final DismissTaskViewEvent event) { 1548 // For visible children, defer removing the task until after the animation 1549 mAnimationHelper.startDeleteTaskAnimation(event.task, event.taskView, 1550 event.getAnimationTrigger()); 1551 } 1552 1553 public final void onBusEvent(TaskViewDismissedEvent event) { 1554 removeTaskViewFromStack(event.taskView, event.task); 1555 EventBus.getDefault().send(new DeleteTaskDataEvent(event.task)); 1556 } 1557 1558 public final void onBusEvent(FocusNextTaskViewEvent event) { 1559 setRelativeFocusedTask(true, false /* stackTasksOnly */, true /* animated */, false, 1560 event.timerIndicatorDuration); 1561 } 1562 1563 public final void onBusEvent(FocusPreviousTaskViewEvent event) { 1564 setRelativeFocusedTask(false, false /* stackTasksOnly */, true /* animated */); 1565 } 1566 1567 public final void onBusEvent(UserInteractionEvent event) { 1568 // Poke the doze trigger on user interaction 1569 mUIDozeTrigger.poke(); 1570 1571 RecentsDebugFlags debugFlags = Recents.getDebugFlags(); 1572 if (debugFlags.isFastToggleRecentsEnabled() && mFocusedTask != null) { 1573 TaskView tv = getChildViewForTask(mFocusedTask); 1574 if (tv != null) { 1575 tv.getHeaderView().cancelFocusTimerIndicator(); 1576 } 1577 } 1578 } 1579 1580 public final void onBusEvent(RecentsVisibilityChangedEvent event) { 1581 if (!event.visible) { 1582 reset(); 1583 } 1584 } 1585 1586 public final void onBusEvent(DragStartEvent event) { 1587 if (event.task.isFreeformTask()) { 1588 // Animate to the front of the stack 1589 mStackScroller.animateScroll(mStackScroller.getStackScroll(), 1590 mLayoutAlgorithm.mInitialScrollP, null); 1591 } 1592 1593 // Enlarge the dragged view slightly 1594 float finalScale = event.taskView.getScaleX() * DRAG_SCALE_FACTOR; 1595 mLayoutAlgorithm.getStackTransform(event.task, getScroller().getStackScroll(), 1596 mTmpTransform, null); 1597 mTmpTransform.scale = finalScale; 1598 mTmpTransform.translationZ = mLayoutAlgorithm.mMaxTranslationZ + 1; 1599 updateTaskViewToTransform(event.taskView, mTmpTransform, 1600 new TaskViewAnimation(DRAG_SCALE_DURATION, Interpolators.FAST_OUT_SLOW_IN)); 1601 } 1602 1603 public final void onBusEvent(DragStartInitializeDropTargetsEvent event) { 1604 SystemServicesProxy ssp = Recents.getSystemServices(); 1605 if (ssp.hasFreeformWorkspaceSupport()) { 1606 event.handler.registerDropTargetForCurrentDrag(mStackDropTarget); 1607 event.handler.registerDropTargetForCurrentDrag(mFreeformWorkspaceDropTarget); 1608 } 1609 } 1610 1611 public final void onBusEvent(DragDropTargetChangedEvent event) { 1612 TaskViewAnimation animation = new TaskViewAnimation(250, Interpolators.FAST_OUT_SLOW_IN); 1613 if (event.dropTarget instanceof TaskStack.DockState) { 1614 // Calculate the new task stack bounds that matches the window size that Recents will 1615 // have after the drop 1616 addIgnoreTask(event.task); 1617 final TaskStack.DockState dockState = (TaskStack.DockState) event.dropTarget; 1618 mStackBounds.set(dockState.getDockedTaskStackBounds(getMeasuredWidth(), 1619 getMeasuredHeight(), mDividerSize, mLayoutAlgorithm.mSystemInsets, 1620 getResources())); 1621 mLayoutAlgorithm.initialize(mStackBounds, 1622 TaskStackLayoutAlgorithm.StackState.getStackStateForStack(mStack)); 1623 updateLayoutAlgorithm(true /* boundScroll */); 1624 } else { 1625 // Restore the pre-drag task stack bounds, but ensure that we don't layout the dragging 1626 // task view, so add it back to the ignore set after updating the layout 1627 mStackBounds.set(mStableStackBounds); 1628 removeIgnoreTask(event.task); 1629 mLayoutAlgorithm.initialize(mStackBounds, 1630 TaskStackLayoutAlgorithm.StackState.getStackStateForStack(mStack)); 1631 updateLayoutAlgorithm(true /* boundScroll */); 1632 addIgnoreTask(event.task); 1633 } 1634 relayoutTaskViews(animation); 1635 } 1636 1637 public final void onBusEvent(final DragEndEvent event) { 1638 // We don't handle drops on the dock regions 1639 if (event.dropTarget instanceof TaskStack.DockState) { 1640 return; 1641 } 1642 1643 boolean isFreeformTask = event.task.isFreeformTask(); 1644 boolean hasChangedStacks = 1645 (!isFreeformTask && event.dropTarget == mFreeformWorkspaceDropTarget) || 1646 (isFreeformTask && event.dropTarget == mStackDropTarget); 1647 1648 if (hasChangedStacks) { 1649 // Move the task to the right position in the stack (ie. the front of the stack if 1650 // freeform or the front of the stack if fullscreen). Note, we MUST move the tasks 1651 // before we update their stack ids, otherwise, the keys will have changed. 1652 if (event.dropTarget == mFreeformWorkspaceDropTarget) { 1653 mStack.moveTaskToStack(event.task, FREEFORM_WORKSPACE_STACK_ID); 1654 } else if (event.dropTarget == mStackDropTarget) { 1655 mStack.moveTaskToStack(event.task, FULLSCREEN_WORKSPACE_STACK_ID); 1656 } 1657 updateLayoutAlgorithm(true /* boundScroll */); 1658 1659 // Move the task to the new stack in the system after the animation completes 1660 event.addPostAnimationCallback(new Runnable() { 1661 @Override 1662 public void run() { 1663 SystemServicesProxy ssp = Recents.getSystemServices(); 1664 ssp.moveTaskToStack(event.task.key.id, event.task.key.stackId); 1665 } 1666 }); 1667 } 1668 1669 // We translated the view but we need to animate it back from the current layout-space rect 1670 // to its final layout-space rect 1671 int x = (int) event.taskView.getTranslationX(); 1672 int y = (int) event.taskView.getTranslationY(); 1673 Rect taskViewRect = new Rect(event.taskView.getLeft(), event.taskView.getTop(), 1674 event.taskView.getRight(), event.taskView.getBottom()); 1675 taskViewRect.offset(x, y); 1676 event.taskView.setTranslationX(0); 1677 event.taskView.setTranslationY(0); 1678 event.taskView.setLeftTopRightBottom(taskViewRect.left, taskViewRect.top, 1679 taskViewRect.right, taskViewRect.bottom); 1680 1681 // Animate all the TaskViews back into position 1682 mLayoutAlgorithm.getStackTransform(event.task, getScroller().getStackScroll(), 1683 mTmpTransform, null); 1684 event.getAnimationTrigger().increment(); 1685 relayoutTaskViews(new TaskViewAnimation(DEFAULT_SYNC_STACK_DURATION, 1686 Interpolators.FAST_OUT_SLOW_IN)); 1687 updateTaskViewToTransform(event.taskView, mTmpTransform, 1688 new TaskViewAnimation(DEFAULT_SYNC_STACK_DURATION, Interpolators.FAST_OUT_SLOW_IN, 1689 event.getAnimationTrigger().decrementOnAnimationEnd())); 1690 removeIgnoreTask(event.task); 1691 } 1692 1693 public final void onBusEvent(StackViewScrolledEvent event) { 1694 mLayoutAlgorithm.updateFocusStateOnScroll(event.yMovement.value); 1695 } 1696 1697 public final void onBusEvent(IterateRecentsEvent event) { 1698 if (!mEnterAnimationComplete) { 1699 // Cancel the previous task's window transition before animating the focused state 1700 EventBus.getDefault().send(new CancelEnterRecentsWindowAnimationEvent(null)); 1701 } 1702 } 1703 1704 public final void onBusEvent(EnterRecentsWindowAnimationCompletedEvent event) { 1705 mEnterAnimationComplete = true; 1706 1707 if (mStack.getTaskCount() > 0) { 1708 // Start the task enter animations 1709 mAnimationHelper.startEnterAnimation(event.getAnimationTrigger()); 1710 1711 // Add a runnable to the post animation ref counter to clear all the views 1712 event.addPostAnimationCallback(new Runnable() { 1713 @Override 1714 public void run() { 1715 // Start the dozer to trigger to trigger any UI that shows after a timeout 1716 mUIDozeTrigger.startDozing(); 1717 1718 // Update the focused state here -- since we only set the focused task without 1719 // requesting view focus in onFirstLayout(), actually request view focus and 1720 // animate the focused state if we are alt-tabbing now, after the window enter 1721 // animation is completed 1722 if (mFocusedTask != null) { 1723 RecentsConfiguration config = Recents.getConfiguration(); 1724 RecentsActivityLaunchState launchState = config.getLaunchState(); 1725 setFocusedTask(mStack.indexOfStackTask(mFocusedTask), 1726 false /* scrollToTask */, launchState.launchedWithAltTab); 1727 } 1728 1729 EventBus.getDefault().send(new EnterRecentsTaskStackAnimationCompletedEvent()); 1730 } 1731 }); 1732 } 1733 } 1734 1735 public final void onBusEvent(EnterRecentsTaskStackAnimationCompletedEvent event) { 1736 RecentsDebugFlags debugFlags = Recents.getDebugFlags(); 1737 RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState(); 1738 if (!launchState.launchedWithAltTab && debugFlags.isFastToggleRecentsEnabled() && 1739 RecentsDebugFlags.Static.EnableFastToggleTimeoutOnEnter) { 1740 if (mFocusedTask != null) { 1741 int timerIndicatorDuration = getResources().getInteger( 1742 R.integer.recents_auto_advance_duration); 1743 int focusedTaskIndex = mStack.indexOfStackTask(mFocusedTask); 1744 setFocusedTask(focusedTaskIndex, false /* scrollToTask */, 1745 false /* requestViewFocus */, timerIndicatorDuration); 1746 } 1747 } 1748 } 1749 1750 public final void onBusEvent(UpdateFreeformTaskViewVisibilityEvent event) { 1751 List<TaskView> taskViews = getTaskViews(); 1752 int taskViewCount = taskViews.size(); 1753 for (int i = 0; i < taskViewCount; i++) { 1754 TaskView tv = taskViews.get(i); 1755 Task task = tv.getTask(); 1756 if (task.isFreeformTask()) { 1757 tv.setVisibility(event.visible ? View.VISIBLE : View.INVISIBLE); 1758 } 1759 } 1760 } 1761 1762 public final void onBusEvent(ShowHistoryEvent event) { 1763 ReferenceCountedTrigger postAnimTrigger = new ReferenceCountedTrigger(); 1764 postAnimTrigger.addLastDecrementRunnable(new Runnable() { 1765 @Override 1766 public void run() { 1767 setVisibility(View.INVISIBLE); 1768 } 1769 }); 1770 mAnimationHelper.startShowHistoryAnimation(postAnimTrigger); 1771 } 1772 1773 public final void onBusEvent(HideHistoryEvent event) { 1774 setVisibility(View.VISIBLE); 1775 mAnimationHelper.startHideHistoryAnimation(); 1776 } 1777 1778 /** 1779 * Removes the task from the stack, and updates the focus to the next task in the stack if the 1780 * removed TaskView was focused. 1781 */ 1782 private void removeTaskViewFromStack(TaskView tv, Task task) { 1783 // Announce for accessibility 1784 tv.announceForAccessibility(getContext().getString( 1785 R.string.accessibility_recents_item_dismissed, task.title)); 1786 1787 // Remove the task from the stack 1788 mStack.removeTask(task, new TaskViewAnimation(DEFAULT_SYNC_STACK_DURATION, 1789 Interpolators.FAST_OUT_SLOW_IN)); 1790 } 1791 1792 /** 1793 * Starts an alpha animation on the freeform workspace background. 1794 */ 1795 private void animateFreeformWorkspaceBackgroundAlpha(int targetAlpha, int duration, 1796 Interpolator interpolator) { 1797 if (mFreeformWorkspaceBackground.getAlpha() == targetAlpha) { 1798 return; 1799 } 1800 1801 Utilities.cancelAnimationWithoutCallbacks(mFreeformWorkspaceBackgroundAnimator); 1802 mFreeformWorkspaceBackgroundAnimator = ObjectAnimator.ofInt(mFreeformWorkspaceBackground, 1803 Utilities.DRAWABLE_ALPHA, mFreeformWorkspaceBackground.getAlpha(), targetAlpha); 1804 mFreeformWorkspaceBackgroundAnimator.setDuration(duration); 1805 mFreeformWorkspaceBackgroundAnimator.setInterpolator(interpolator); 1806 mFreeformWorkspaceBackgroundAnimator.start(); 1807 } 1808 1809 /** 1810 * Returns the insert index for the task in the current set of task views. If the given task 1811 * is already in the task view list, then this method returns the insert index assuming it 1812 * is first removed at the previous index. 1813 * 1814 * @param task the task we are finding the index for 1815 * @param taskIndex the index of the task in the stack 1816 */ 1817 private int findTaskViewInsertIndex(Task task, int taskIndex) { 1818 if (taskIndex != -1) { 1819 List<TaskView> taskViews = getTaskViews(); 1820 boolean foundTaskView = false; 1821 int taskViewCount = taskViews.size(); 1822 for (int i = 0; i < taskViewCount; i++) { 1823 Task tvTask = taskViews.get(i).getTask(); 1824 if (tvTask == task) { 1825 foundTaskView = true; 1826 } else if (taskIndex < mStack.indexOfStackTask(tvTask)) { 1827 if (foundTaskView) { 1828 return i - 1; 1829 } else { 1830 return i; 1831 } 1832 } 1833 } 1834 } 1835 return -1; 1836 } 1837 1838 /** 1839 * @return whether the history button should be visible 1840 */ 1841 private boolean shouldShowHistoryButton() { 1842 return !mStack.getHistoricalTasks().isEmpty(); 1843 } 1844 1845 /** 1846 * Reads current system flags related to accessibility and screen pinning. 1847 */ 1848 private void readSystemFlags() { 1849 SystemServicesProxy ssp = Recents.getSystemServices(); 1850 mTouchExplorationEnabled = ssp.isTouchExplorationEnabled(); 1851 mScreenPinningEnabled = ssp.getSystemSetting(getContext(), 1852 Settings.System.LOCK_TO_APP_ENABLED) != 0; 1853 } 1854} 1855