1 /* 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.systemui.recents.views; 18 19 import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID; 20 import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID; 21 import static android.app.ActivityManager.StackId.INVALID_STACK_ID; 22 23 import android.animation.ObjectAnimator; 24 import android.animation.ValueAnimator; 25 import android.annotation.IntDef; 26 import android.content.ComponentName; 27 import android.content.Context; 28 import android.content.res.Configuration; 29 import android.content.res.Resources; 30 import android.graphics.Canvas; 31 import android.graphics.Rect; 32 import android.graphics.drawable.Drawable; 33 import android.graphics.drawable.GradientDrawable; 34 import android.os.Bundle; 35 import android.provider.Settings; 36 import android.util.ArrayMap; 37 import android.util.ArraySet; 38 import android.util.MutableBoolean; 39 import android.view.LayoutInflater; 40 import android.view.MotionEvent; 41 import android.view.View; 42 import android.view.ViewDebug; 43 import android.view.ViewGroup; 44 import android.view.accessibility.AccessibilityEvent; 45 import android.view.accessibility.AccessibilityNodeInfo; 46 import android.widget.FrameLayout; 47 import android.widget.ScrollView; 48 49 import com.android.internal.logging.MetricsLogger; 50 import com.android.internal.logging.MetricsProto.MetricsEvent; 51 import com.android.systemui.Interpolators; 52 import com.android.systemui.R; 53 import com.android.systemui.recents.Recents; 54 import com.android.systemui.recents.RecentsActivity; 55 import com.android.systemui.recents.RecentsActivityLaunchState; 56 import com.android.systemui.recents.RecentsConfiguration; 57 import com.android.systemui.recents.RecentsDebugFlags; 58 import com.android.systemui.recents.events.EventBus; 59 import com.android.systemui.recents.events.activity.CancelEnterRecentsWindowAnimationEvent; 60 import com.android.systemui.recents.events.activity.ConfigurationChangedEvent; 61 import com.android.systemui.recents.events.activity.DismissRecentsToHomeAnimationStarted; 62 import com.android.systemui.recents.events.activity.EnterRecentsTaskStackAnimationCompletedEvent; 63 import com.android.systemui.recents.events.activity.EnterRecentsWindowAnimationCompletedEvent; 64 import com.android.systemui.recents.events.activity.HideRecentsEvent; 65 import com.android.systemui.recents.events.activity.HideStackActionButtonEvent; 66 import com.android.systemui.recents.events.activity.IterateRecentsEvent; 67 import com.android.systemui.recents.events.activity.LaunchNextTaskRequestEvent; 68 import com.android.systemui.recents.events.activity.LaunchTaskEvent; 69 import com.android.systemui.recents.events.activity.LaunchTaskStartedEvent; 70 import com.android.systemui.recents.events.activity.MultiWindowStateChangedEvent; 71 import com.android.systemui.recents.events.activity.PackagesChangedEvent; 72 import com.android.systemui.recents.events.activity.ShowStackActionButtonEvent; 73 import com.android.systemui.recents.events.ui.AllTaskViewsDismissedEvent; 74 import com.android.systemui.recents.events.ui.DeleteTaskDataEvent; 75 import com.android.systemui.recents.events.ui.DismissAllTaskViewsEvent; 76 import com.android.systemui.recents.events.ui.DismissTaskViewEvent; 77 import com.android.systemui.recents.events.ui.RecentsGrowingEvent; 78 import com.android.systemui.recents.events.ui.TaskViewDismissedEvent; 79 import com.android.systemui.recents.events.ui.UpdateFreeformTaskViewVisibilityEvent; 80 import com.android.systemui.recents.events.ui.UserInteractionEvent; 81 import com.android.systemui.recents.events.ui.dragndrop.DragDropTargetChangedEvent; 82 import com.android.systemui.recents.events.ui.dragndrop.DragEndEvent; 83 import com.android.systemui.recents.events.ui.dragndrop.DragEndCancelledEvent; 84 import com.android.systemui.recents.events.ui.dragndrop.DragStartEvent; 85 import com.android.systemui.recents.events.ui.dragndrop.DragStartInitializeDropTargetsEvent; 86 import com.android.systemui.recents.events.ui.focus.DismissFocusedTaskViewEvent; 87 import com.android.systemui.recents.events.ui.focus.FocusNextTaskViewEvent; 88 import com.android.systemui.recents.events.ui.focus.FocusPreviousTaskViewEvent; 89 import com.android.systemui.recents.misc.DozeTrigger; 90 import com.android.systemui.recents.misc.SystemServicesProxy; 91 import com.android.systemui.recents.misc.Utilities; 92 import com.android.systemui.recents.model.Task; 93 import com.android.systemui.recents.model.TaskStack; 94 95 import java.io.PrintWriter; 96 import java.lang.annotation.Retention; 97 import java.lang.annotation.RetentionPolicy; 98 import java.util.ArrayList; 99 import java.util.List; 100 101 102 /* The visual representation of a task stack view */ 103 public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCallbacks, 104 TaskView.TaskViewCallbacks, TaskStackViewScroller.TaskStackViewScrollerCallbacks, 105 TaskStackLayoutAlgorithm.TaskStackLayoutAlgorithmCallbacks, 106 ViewPool.ViewPoolConsumer<TaskView, Task> { 107 108 private static final String TAG = "TaskStackView"; 109 110 // The thresholds at which to show/hide the stack action button. 111 private static final float SHOW_STACK_ACTION_BUTTON_SCROLL_THRESHOLD = 0.3f; 112 private static final float HIDE_STACK_ACTION_BUTTON_SCROLL_THRESHOLD = 0.3f; 113 114 public static final int DEFAULT_SYNC_STACK_DURATION = 200; 115 public static final int SLOW_SYNC_STACK_DURATION = 250; 116 private static final int DRAG_SCALE_DURATION = 175; 117 static final float DRAG_SCALE_FACTOR = 1.05f; 118 119 private static final int LAUNCH_NEXT_SCROLL_BASE_DURATION = 216; 120 private static final int LAUNCH_NEXT_SCROLL_INCR_DURATION = 32; 121 122 // The actions to perform when resetting to initial state, 123 @Retention(RetentionPolicy.SOURCE) 124 @IntDef({INITIAL_STATE_UPDATE_NONE, INITIAL_STATE_UPDATE_ALL, INITIAL_STATE_UPDATE_LAYOUT_ONLY}) 125 public @interface InitialStateAction {} 126 /** Do not update the stack and layout to the initial state. */ 127 private static final int INITIAL_STATE_UPDATE_NONE = 0; 128 /** Update both the stack and layout to the initial state. */ 129 private static final int INITIAL_STATE_UPDATE_ALL = 1; 130 /** Update only the layout to the initial state. */ 131 private static final int INITIAL_STATE_UPDATE_LAYOUT_ONLY = 2; 132 133 private LayoutInflater mInflater; 134 private TaskStack mStack = new TaskStack(); 135 @ViewDebug.ExportedProperty(deepExport=true, prefix="layout_") 136 TaskStackLayoutAlgorithm mLayoutAlgorithm; 137 // The stable layout algorithm is only used to calculate the task rect with the stable bounds 138 private TaskStackLayoutAlgorithm mStableLayoutAlgorithm; 139 @ViewDebug.ExportedProperty(deepExport=true, prefix="scroller_") 140 private TaskStackViewScroller mStackScroller; 141 @ViewDebug.ExportedProperty(deepExport=true, prefix="touch_") 142 private TaskStackViewTouchHandler mTouchHandler; 143 private TaskStackAnimationHelper mAnimationHelper; 144 private GradientDrawable mFreeformWorkspaceBackground; 145 private ObjectAnimator mFreeformWorkspaceBackgroundAnimator; 146 private ViewPool<TaskView, Task> mViewPool; 147 148 private ArrayList<TaskView> mTaskViews = new ArrayList<>(); 149 private ArrayList<TaskViewTransform> mCurrentTaskTransforms = new ArrayList<>(); 150 private ArraySet<Task.TaskKey> mIgnoreTasks = new ArraySet<>(); 151 private AnimationProps mDeferredTaskViewLayoutAnimation = null; 152 153 @ViewDebug.ExportedProperty(deepExport=true, prefix="doze_") 154 private DozeTrigger mUIDozeTrigger; 155 @ViewDebug.ExportedProperty(deepExport=true, prefix="focused_task_") 156 private Task mFocusedTask; 157 158 private int mTaskCornerRadiusPx; 159 private int mDividerSize; 160 private int mStartTimerIndicatorDuration; 161 162 @ViewDebug.ExportedProperty(category="recents") 163 private boolean mTaskViewsClipDirty = true; 164 @ViewDebug.ExportedProperty(category="recents") 165 private boolean mAwaitingFirstLayout = true; 166 @ViewDebug.ExportedProperty(category="recents") 167 @InitialStateAction 168 private int mInitialState = INITIAL_STATE_UPDATE_ALL; 169 @ViewDebug.ExportedProperty(category="recents") 170 private boolean mInMeasureLayout = false; 171 @ViewDebug.ExportedProperty(category="recents") 172 private boolean mEnterAnimationComplete = false; 173 @ViewDebug.ExportedProperty(category="recents") 174 boolean mTouchExplorationEnabled; 175 @ViewDebug.ExportedProperty(category="recents") 176 boolean mScreenPinningEnabled; 177 178 // The stable stack bounds are the full bounds that we were measured with from RecentsView 179 @ViewDebug.ExportedProperty(category="recents") 180 private Rect mStableStackBounds = new Rect(); 181 // The current stack bounds are dynamic and may change as the user drags and drops 182 @ViewDebug.ExportedProperty(category="recents") 183 private Rect mStackBounds = new Rect(); 184 // The current window bounds at the point we were measured 185 @ViewDebug.ExportedProperty(category="recents") 186 private Rect mStableWindowRect = new Rect(); 187 // The current window bounds are dynamic and may change as the user drags and drops 188 @ViewDebug.ExportedProperty(category="recents") 189 private Rect mWindowRect = new Rect(); 190 // The current display bounds 191 @ViewDebug.ExportedProperty(category="recents") 192 private Rect mDisplayRect = new Rect(); 193 // The current display orientation 194 @ViewDebug.ExportedProperty(category="recents") 195 private int mDisplayOrientation = Configuration.ORIENTATION_UNDEFINED; 196 197 private Rect mTmpRect = new Rect(); 198 private ArrayMap<Task.TaskKey, TaskView> mTmpTaskViewMap = new ArrayMap<>(); 199 private List<TaskView> mTmpTaskViews = new ArrayList<>(); 200 private TaskViewTransform mTmpTransform = new TaskViewTransform(); 201 private int[] mTmpIntPair = new int[2]; 202 private boolean mResetToInitialStateWhenResized; 203 private int mLastWidth; 204 private int mLastHeight; 205 206 // A convenience update listener to request updating clipping of tasks 207 private ValueAnimator.AnimatorUpdateListener mRequestUpdateClippingListener = 208 new ValueAnimator.AnimatorUpdateListener() { 209 @Override 210 public void onAnimationUpdate(ValueAnimator animation) { 211 if (!mTaskViewsClipDirty) { 212 mTaskViewsClipDirty = true; 213 invalidate(); 214 } 215 } 216 }; 217 218 // The drop targets for a task drag 219 private DropTarget mFreeformWorkspaceDropTarget = new DropTarget() { 220 @Override 221 public boolean acceptsDrop(int x, int y, int width, int height, Rect insets, 222 boolean isCurrentTarget) { 223 // This drop target has a fixed bounds and should be checked last, so just fall through 224 // if it is the current target 225 if (!isCurrentTarget) { 226 return mLayoutAlgorithm.mFreeformRect.contains(x, y); 227 } 228 return false; 229 } 230 }; 231 232 private DropTarget mStackDropTarget = new DropTarget() { 233 @Override 234 public boolean acceptsDrop(int x, int y, int width, int height, Rect insets, 235 boolean isCurrentTarget) { 236 // This drop target has a fixed bounds and should be checked last, so just fall through 237 // if it is the current target 238 if (!isCurrentTarget) { 239 return mLayoutAlgorithm.mStackRect.contains(x, y); 240 } 241 return false; 242 } 243 }; 244 TaskStackView(Context context)245 public TaskStackView(Context context) { 246 super(context); 247 SystemServicesProxy ssp = Recents.getSystemServices(); 248 Resources res = context.getResources(); 249 250 // Set the stack first 251 mStack.setCallbacks(this); 252 mViewPool = new ViewPool<>(context, this); 253 mInflater = LayoutInflater.from(context); 254 mLayoutAlgorithm = new TaskStackLayoutAlgorithm(context, this); 255 mStableLayoutAlgorithm = new TaskStackLayoutAlgorithm(context, null); 256 mStackScroller = new TaskStackViewScroller(context, this, mLayoutAlgorithm); 257 mTouchHandler = new TaskStackViewTouchHandler(context, this, mStackScroller); 258 mAnimationHelper = new TaskStackAnimationHelper(context, this); 259 mTaskCornerRadiusPx = res.getDimensionPixelSize( 260 R.dimen.recents_task_view_rounded_corners_radius); 261 mDividerSize = ssp.getDockedDividerSize(context); 262 mDisplayOrientation = Utilities.getAppConfiguration(mContext).orientation; 263 mDisplayRect = ssp.getDisplayRect(); 264 265 int taskBarDismissDozeDelaySeconds = getResources().getInteger( 266 R.integer.recents_task_bar_dismiss_delay_seconds); 267 mUIDozeTrigger = new DozeTrigger(taskBarDismissDozeDelaySeconds, new Runnable() { 268 @Override 269 public void run() { 270 // Show the task bar dismiss buttons 271 List<TaskView> taskViews = getTaskViews(); 272 int taskViewCount = taskViews.size(); 273 for (int i = 0; i < taskViewCount; i++) { 274 TaskView tv = taskViews.get(i); 275 tv.startNoUserInteractionAnimation(); 276 } 277 } 278 }); 279 setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES); 280 281 mFreeformWorkspaceBackground = (GradientDrawable) getContext().getDrawable( 282 R.drawable.recents_freeform_workspace_bg); 283 mFreeformWorkspaceBackground.setCallback(this); 284 if (ssp.hasFreeformWorkspaceSupport()) { 285 mFreeformWorkspaceBackground.setColor( 286 getContext().getColor(R.color.recents_freeform_workspace_bg_color)); 287 } 288 } 289 290 @Override onAttachedToWindow()291 protected void onAttachedToWindow() { 292 EventBus.getDefault().register(this, RecentsActivity.EVENT_BUS_PRIORITY + 1); 293 super.onAttachedToWindow(); 294 readSystemFlags(); 295 } 296 297 @Override onDetachedFromWindow()298 protected void onDetachedFromWindow() { 299 super.onDetachedFromWindow(); 300 EventBus.getDefault().unregister(this); 301 } 302 303 /** 304 * Called from RecentsActivity when it is relaunched. 305 */ onReload(boolean isResumingFromVisible)306 void onReload(boolean isResumingFromVisible) { 307 if (!isResumingFromVisible) { 308 // Reset the focused task 309 resetFocusedTask(getFocusedTask()); 310 } 311 312 // Reset the state of each of the task views 313 List<TaskView> taskViews = new ArrayList<>(); 314 taskViews.addAll(getTaskViews()); 315 taskViews.addAll(mViewPool.getViews()); 316 for (int i = taskViews.size() - 1; i >= 0; i--) { 317 taskViews.get(i).onReload(isResumingFromVisible); 318 } 319 320 // Reset the stack state 321 readSystemFlags(); 322 mTaskViewsClipDirty = true; 323 mEnterAnimationComplete = false; 324 mUIDozeTrigger.stopDozing(); 325 if (isResumingFromVisible) { 326 // Animate in the freeform workspace 327 int ffBgAlpha = mLayoutAlgorithm.getStackState().freeformBackgroundAlpha; 328 animateFreeformWorkspaceBackgroundAlpha(ffBgAlpha, new AnimationProps(150, 329 Interpolators.FAST_OUT_SLOW_IN)); 330 } else { 331 mStackScroller.reset(); 332 mStableLayoutAlgorithm.reset(); 333 mLayoutAlgorithm.reset(); 334 } 335 336 // Since we always animate to the same place in (the initial state), always reset the stack 337 // to the initial state when resuming 338 mAwaitingFirstLayout = true; 339 mInitialState = INITIAL_STATE_UPDATE_ALL; 340 requestLayout(); 341 } 342 343 /** 344 * Sets the stack tasks of this TaskStackView from the given TaskStack. 345 */ setTasks(TaskStack stack, boolean allowNotifyStackChanges)346 public void setTasks(TaskStack stack, boolean allowNotifyStackChanges) { 347 boolean isInitialized = mLayoutAlgorithm.isInitialized(); 348 349 // Only notify if we are already initialized, otherwise, everything will pick up all the 350 // new and old tasks when we next layout 351 mStack.setTasks(getContext(), stack.computeAllTasksList(), 352 allowNotifyStackChanges && isInitialized); 353 } 354 355 /** Returns the task stack. */ getStack()356 public TaskStack getStack() { 357 return mStack; 358 } 359 360 /** 361 * Updates this TaskStackView to the initial state. 362 */ updateToInitialState()363 public void updateToInitialState() { 364 mStackScroller.setStackScrollToInitialState(); 365 mLayoutAlgorithm.setTaskOverridesForInitialState(mStack, false /* ignoreScrollToFront */); 366 } 367 368 /** Updates the list of task views */ updateTaskViewsList()369 void updateTaskViewsList() { 370 mTaskViews.clear(); 371 int childCount = getChildCount(); 372 for (int i = 0; i < childCount; i++) { 373 View v = getChildAt(i); 374 if (v instanceof TaskView) { 375 mTaskViews.add((TaskView) v); 376 } 377 } 378 } 379 380 /** Gets the list of task views */ getTaskViews()381 List<TaskView> getTaskViews() { 382 return mTaskViews; 383 } 384 385 /** 386 * Returns the front most task view. 387 * 388 * @param stackTasksOnly if set, will return the front most task view in the stack (by default 389 * the front most task view will be freeform since they are placed above 390 * stack tasks) 391 */ getFrontMostTaskView(boolean stackTasksOnly)392 private TaskView getFrontMostTaskView(boolean stackTasksOnly) { 393 List<TaskView> taskViews = getTaskViews(); 394 int taskViewCount = taskViews.size(); 395 for (int i = taskViewCount - 1; i >= 0; i--) { 396 TaskView tv = taskViews.get(i); 397 Task task = tv.getTask(); 398 if (stackTasksOnly && task.isFreeformTask()) { 399 continue; 400 } 401 return tv; 402 } 403 return null; 404 } 405 406 /** 407 * Finds the child view given a specific {@param task}. 408 */ getChildViewForTask(Task t)409 public TaskView getChildViewForTask(Task t) { 410 List<TaskView> taskViews = getTaskViews(); 411 int taskViewCount = taskViews.size(); 412 for (int i = 0; i < taskViewCount; i++) { 413 TaskView tv = taskViews.get(i); 414 if (tv.getTask() == t) { 415 return tv; 416 } 417 } 418 return null; 419 } 420 421 /** Returns the stack algorithm for this task stack. */ getStackAlgorithm()422 public TaskStackLayoutAlgorithm getStackAlgorithm() { 423 return mLayoutAlgorithm; 424 } 425 426 /** 427 * Returns the touch handler for this task stack. 428 */ getTouchHandler()429 public TaskStackViewTouchHandler getTouchHandler() { 430 return mTouchHandler; 431 } 432 433 /** 434 * Adds a task to the ignored set. 435 */ addIgnoreTask(Task task)436 void addIgnoreTask(Task task) { 437 mIgnoreTasks.add(task.key); 438 } 439 440 /** 441 * Removes a task from the ignored set. 442 */ removeIgnoreTask(Task task)443 void removeIgnoreTask(Task task) { 444 mIgnoreTasks.remove(task.key); 445 } 446 447 /** 448 * Returns whether the specified {@param task} is ignored. 449 */ isIgnoredTask(Task task)450 boolean isIgnoredTask(Task task) { 451 return mIgnoreTasks.contains(task.key); 452 } 453 454 /** 455 * Computes the task transforms at the current stack scroll for all visible tasks. If a valid 456 * target stack scroll is provided (ie. is different than {@param curStackScroll}), then the 457 * visible range includes all tasks at the target stack scroll. This is useful for ensure that 458 * all views necessary for a transition or animation will be visible at the start. 459 * 460 * This call ignores freeform tasks. 461 * 462 * @param taskTransforms The set of task view transforms to reuse, this list will be sized to 463 * match the size of {@param tasks} 464 * @param tasks The set of tasks for which to generate transforms 465 * @param curStackScroll The current stack scroll 466 * @param targetStackScroll The stack scroll that we anticipate we are going to be scrolling to. 467 * The range of the union of the visible views at the current and 468 * target stack scrolls will be returned. 469 * @param ignoreTasksSet The set of tasks to skip for purposes of calculaing the visible range. 470 * Transforms will still be calculated for the ignore tasks. 471 * @return the front and back most visible task indices (there may be non visible tasks in 472 * between this range) 473 */ computeVisibleTaskTransforms(ArrayList<TaskViewTransform> taskTransforms, ArrayList<Task> tasks, float curStackScroll, float targetStackScroll, ArraySet<Task.TaskKey> ignoreTasksSet, boolean ignoreTaskOverrides)474 int[] computeVisibleTaskTransforms(ArrayList<TaskViewTransform> taskTransforms, 475 ArrayList<Task> tasks, float curStackScroll, float targetStackScroll, 476 ArraySet<Task.TaskKey> ignoreTasksSet, boolean ignoreTaskOverrides) { 477 int taskCount = tasks.size(); 478 int[] visibleTaskRange = mTmpIntPair; 479 visibleTaskRange[0] = -1; 480 visibleTaskRange[1] = -1; 481 boolean useTargetStackScroll = Float.compare(curStackScroll, targetStackScroll) != 0; 482 483 // We can reuse the task transforms where possible to reduce object allocation 484 Utilities.matchTaskListSize(tasks, taskTransforms); 485 486 // Update the stack transforms 487 TaskViewTransform frontTransform = null; 488 TaskViewTransform frontTransformAtTarget = null; 489 TaskViewTransform transform = null; 490 TaskViewTransform transformAtTarget = null; 491 for (int i = taskCount - 1; i >= 0; i--) { 492 Task task = tasks.get(i); 493 494 // Calculate the current and (if necessary) the target transform for the task 495 transform = mLayoutAlgorithm.getStackTransform(task, curStackScroll, 496 taskTransforms.get(i), frontTransform, ignoreTaskOverrides); 497 if (useTargetStackScroll && !transform.visible) { 498 // If we have a target stack scroll and the task is not currently visible, then we 499 // just update the transform at the new scroll 500 // TODO: Optimize this 501 transformAtTarget = mLayoutAlgorithm.getStackTransform(task, 502 targetStackScroll, new TaskViewTransform(), frontTransformAtTarget); 503 if (transformAtTarget.visible) { 504 transform.copyFrom(transformAtTarget); 505 } 506 } 507 508 // For ignore tasks, only calculate the stack transform and skip the calculation of the 509 // visible stack indices 510 if (ignoreTasksSet.contains(task.key)) { 511 continue; 512 } 513 514 // For freeform tasks, only calculate the stack transform and skip the calculation of 515 // the visible stack indices 516 if (task.isFreeformTask()) { 517 continue; 518 } 519 520 frontTransform = transform; 521 frontTransformAtTarget = transformAtTarget; 522 if (transform.visible) { 523 if (visibleTaskRange[0] < 0) { 524 visibleTaskRange[0] = i; 525 } 526 visibleTaskRange[1] = i; 527 } 528 } 529 return visibleTaskRange; 530 } 531 532 /** 533 * Binds the visible {@link TaskView}s at the given target scroll. 534 */ bindVisibleTaskViews(float targetStackScroll)535 void bindVisibleTaskViews(float targetStackScroll) { 536 bindVisibleTaskViews(targetStackScroll, false /* ignoreTaskOverrides */); 537 } 538 539 /** 540 * Synchronizes the set of children {@link TaskView}s to match the visible set of tasks in the 541 * current {@link TaskStack}. This call does not continue on to update their position to the 542 * computed {@link TaskViewTransform}s of the visible range, but only ensures that they will 543 * be added/removed from the view hierarchy and placed in the correct Z order and initial 544 * position (if not currently on screen). 545 * 546 * @param targetStackScroll If provided, will ensure that the set of visible {@link TaskView}s 547 * includes those visible at the current stack scroll, and all at the 548 * target stack scroll. 549 * @param ignoreTaskOverrides If set, the visible task computation will get the transforms for 550 * tasks at their non-overridden task progress 551 */ bindVisibleTaskViews(float targetStackScroll, boolean ignoreTaskOverrides)552 void bindVisibleTaskViews(float targetStackScroll, boolean ignoreTaskOverrides) { 553 // Get all the task transforms 554 ArrayList<Task> tasks = mStack.getStackTasks(); 555 int[] visibleTaskRange = computeVisibleTaskTransforms(mCurrentTaskTransforms, tasks, 556 mStackScroller.getStackScroll(), targetStackScroll, mIgnoreTasks, 557 ignoreTaskOverrides); 558 559 // Return all the invisible children to the pool 560 mTmpTaskViewMap.clear(); 561 List<TaskView> taskViews = getTaskViews(); 562 int lastFocusedTaskIndex = -1; 563 int taskViewCount = taskViews.size(); 564 for (int i = taskViewCount - 1; i >= 0; i--) { 565 TaskView tv = taskViews.get(i); 566 Task task = tv.getTask(); 567 568 // Skip ignored tasks 569 if (mIgnoreTasks.contains(task.key)) { 570 continue; 571 } 572 573 // It is possible for the set of lingering TaskViews to differ from the stack if the 574 // stack was updated before the relayout. If the task view is no longer in the stack, 575 // then just return it back to the view pool. 576 int taskIndex = mStack.indexOfStackTask(task); 577 TaskViewTransform transform = null; 578 if (taskIndex != -1) { 579 transform = mCurrentTaskTransforms.get(taskIndex); 580 } 581 582 if (task.isFreeformTask() || (transform != null && transform.visible)) { 583 mTmpTaskViewMap.put(task.key, tv); 584 } else { 585 if (mTouchExplorationEnabled && Utilities.isDescendentAccessibilityFocused(tv)) { 586 lastFocusedTaskIndex = taskIndex; 587 resetFocusedTask(task); 588 } 589 mViewPool.returnViewToPool(tv); 590 } 591 } 592 593 // Pick up all the newly visible children 594 for (int i = tasks.size() - 1; i >= 0; i--) { 595 Task task = tasks.get(i); 596 TaskViewTransform transform = mCurrentTaskTransforms.get(i); 597 598 // Skip ignored tasks 599 if (mIgnoreTasks.contains(task.key)) { 600 continue; 601 } 602 603 // Skip the invisible non-freeform stack tasks 604 if (!task.isFreeformTask() && !transform.visible) { 605 continue; 606 } 607 608 TaskView tv = mTmpTaskViewMap.get(task.key); 609 if (tv == null) { 610 tv = mViewPool.pickUpViewFromPool(task, task); 611 if (task.isFreeformTask()) { 612 updateTaskViewToTransform(tv, transform, AnimationProps.IMMEDIATE); 613 } else { 614 if (transform.rect.top <= mLayoutAlgorithm.mStackRect.top) { 615 updateTaskViewToTransform(tv, mLayoutAlgorithm.getBackOfStackTransform(), 616 AnimationProps.IMMEDIATE); 617 } else { 618 updateTaskViewToTransform(tv, mLayoutAlgorithm.getFrontOfStackTransform(), 619 AnimationProps.IMMEDIATE); 620 } 621 } 622 } else { 623 // Reattach it in the right z order 624 final int taskIndex = mStack.indexOfStackTask(task); 625 final int insertIndex = findTaskViewInsertIndex(task, taskIndex); 626 if (insertIndex != getTaskViews().indexOf(tv)){ 627 detachViewFromParent(tv); 628 attachViewToParent(tv, insertIndex, tv.getLayoutParams()); 629 updateTaskViewsList(); 630 } 631 } 632 } 633 634 // Update the focus if the previous focused task was returned to the view pool 635 if (lastFocusedTaskIndex != -1) { 636 int newFocusedTaskIndex = (lastFocusedTaskIndex < visibleTaskRange[1]) 637 ? visibleTaskRange[1] 638 : visibleTaskRange[0]; 639 setFocusedTask(newFocusedTaskIndex, false /* scrollToTask */, 640 true /* requestViewFocus */); 641 TaskView focusedTaskView = getChildViewForTask(mFocusedTask); 642 if (focusedTaskView != null) { 643 focusedTaskView.requestAccessibilityFocus(); 644 } 645 } 646 } 647 648 /** 649 * @see #relayoutTaskViews(AnimationProps, ArrayMap<Task, AnimationProps>, boolean) 650 */ relayoutTaskViews(AnimationProps animation)651 public void relayoutTaskViews(AnimationProps animation) { 652 relayoutTaskViews(animation, null /* animationOverrides */, 653 false /* ignoreTaskOverrides */); 654 } 655 656 /** 657 * Relayout the the visible {@link TaskView}s to their current transforms as specified by the 658 * {@link TaskStackLayoutAlgorithm} with the given {@param animation}. This call cancels any 659 * animations that are current running on those task views, and will ensure that the children 660 * {@link TaskView}s will match the set of visible tasks in the stack. If a {@link Task} has 661 * an animation provided in {@param animationOverrides}, that will be used instead. 662 */ relayoutTaskViews(AnimationProps animation, ArrayMap<Task, AnimationProps> animationOverrides, boolean ignoreTaskOverrides)663 private void relayoutTaskViews(AnimationProps animation, 664 ArrayMap<Task, AnimationProps> animationOverrides, 665 boolean ignoreTaskOverrides) { 666 // If we had a deferred animation, cancel that 667 cancelDeferredTaskViewLayoutAnimation(); 668 669 // Synchronize the current set of TaskViews 670 bindVisibleTaskViews(mStackScroller.getStackScroll(), 671 ignoreTaskOverrides /* ignoreTaskOverrides */); 672 673 // Animate them to their final transforms with the given animation 674 List<TaskView> taskViews = getTaskViews(); 675 int taskViewCount = taskViews.size(); 676 for (int i = 0; i < taskViewCount; i++) { 677 TaskView tv = taskViews.get(i); 678 Task task = tv.getTask(); 679 int taskIndex = mStack.indexOfStackTask(task); 680 TaskViewTransform transform = mCurrentTaskTransforms.get(taskIndex); 681 682 if (mIgnoreTasks.contains(task.key)) { 683 continue; 684 } 685 686 if (animationOverrides != null && animationOverrides.containsKey(task)) { 687 animation = animationOverrides.get(task); 688 } 689 690 updateTaskViewToTransform(tv, transform, animation); 691 } 692 } 693 694 /** 695 * Posts an update to synchronize the {@link TaskView}s with the stack on the next frame. 696 */ relayoutTaskViewsOnNextFrame(AnimationProps animation)697 void relayoutTaskViewsOnNextFrame(AnimationProps animation) { 698 mDeferredTaskViewLayoutAnimation = animation; 699 invalidate(); 700 } 701 702 /** 703 * Called to update a specific {@link TaskView} to a given {@link TaskViewTransform} with a 704 * given set of {@link AnimationProps} properties. 705 */ updateTaskViewToTransform(TaskView taskView, TaskViewTransform transform, AnimationProps animation)706 public void updateTaskViewToTransform(TaskView taskView, TaskViewTransform transform, 707 AnimationProps animation) { 708 if (taskView.isAnimatingTo(transform)) { 709 return; 710 } 711 taskView.cancelTransformAnimation(); 712 taskView.updateViewPropertiesToTaskTransform(transform, animation, 713 mRequestUpdateClippingListener); 714 } 715 716 /** 717 * Returns the current task transforms of all tasks, falling back to the stack layout if there 718 * is no {@link TaskView} for the task. 719 */ getCurrentTaskTransforms(ArrayList<Task> tasks, ArrayList<TaskViewTransform> transformsOut)720 public void getCurrentTaskTransforms(ArrayList<Task> tasks, 721 ArrayList<TaskViewTransform> transformsOut) { 722 Utilities.matchTaskListSize(tasks, transformsOut); 723 int focusState = mLayoutAlgorithm.getFocusState(); 724 for (int i = tasks.size() - 1; i >= 0; i--) { 725 Task task = tasks.get(i); 726 TaskViewTransform transform = transformsOut.get(i); 727 TaskView tv = getChildViewForTask(task); 728 if (tv != null) { 729 transform.fillIn(tv); 730 } else { 731 mLayoutAlgorithm.getStackTransform(task, mStackScroller.getStackScroll(), 732 focusState, transform, null, true /* forceUpdate */, 733 false /* ignoreTaskOverrides */); 734 } 735 transform.visible = true; 736 } 737 } 738 739 /** 740 * Returns the task transforms for all the tasks in the stack if the stack was at the given 741 * {@param stackScroll} and {@param focusState}. 742 */ getLayoutTaskTransforms(float stackScroll, int focusState, ArrayList<Task> tasks, boolean ignoreTaskOverrides, ArrayList<TaskViewTransform> transformsOut)743 public void getLayoutTaskTransforms(float stackScroll, int focusState, ArrayList<Task> tasks, 744 boolean ignoreTaskOverrides, ArrayList<TaskViewTransform> transformsOut) { 745 Utilities.matchTaskListSize(tasks, transformsOut); 746 for (int i = tasks.size() - 1; i >= 0; i--) { 747 Task task = tasks.get(i); 748 TaskViewTransform transform = transformsOut.get(i); 749 mLayoutAlgorithm.getStackTransform(task, stackScroll, focusState, transform, null, 750 true /* forceUpdate */, ignoreTaskOverrides); 751 transform.visible = true; 752 } 753 } 754 755 /** 756 * Cancels the next deferred task view layout. 757 */ cancelDeferredTaskViewLayoutAnimation()758 void cancelDeferredTaskViewLayoutAnimation() { 759 mDeferredTaskViewLayoutAnimation = null; 760 } 761 762 /** 763 * Cancels all {@link TaskView} animations. 764 */ cancelAllTaskViewAnimations()765 void cancelAllTaskViewAnimations() { 766 List<TaskView> taskViews = getTaskViews(); 767 for (int i = taskViews.size() - 1; i >= 0; i--) { 768 final TaskView tv = taskViews.get(i); 769 if (!mIgnoreTasks.contains(tv.getTask().key)) { 770 tv.cancelTransformAnimation(); 771 } 772 } 773 } 774 775 /** 776 * Updates the clip for each of the task views from back to front. 777 */ clipTaskViews()778 private void clipTaskViews() { 779 // Update the clip on each task child 780 List<TaskView> taskViews = getTaskViews(); 781 TaskView tmpTv = null; 782 TaskView prevVisibleTv = null; 783 int taskViewCount = taskViews.size(); 784 for (int i = 0; i < taskViewCount; i++) { 785 TaskView tv = taskViews.get(i); 786 TaskView frontTv = null; 787 int clipBottom = 0; 788 789 if (isIgnoredTask(tv.getTask())) { 790 // For each of the ignore tasks, update the translationZ of its TaskView to be 791 // between the translationZ of the tasks immediately underneath it 792 if (prevVisibleTv != null) { 793 tv.setTranslationZ(Math.max(tv.getTranslationZ(), 794 prevVisibleTv.getTranslationZ() + 0.1f)); 795 } 796 } 797 798 if (i < (taskViewCount - 1) && tv.shouldClipViewInStack()) { 799 // Find the next view to clip against 800 for (int j = i + 1; j < taskViewCount; j++) { 801 tmpTv = taskViews.get(j); 802 803 if (tmpTv.shouldClipViewInStack()) { 804 frontTv = tmpTv; 805 break; 806 } 807 } 808 809 // Clip against the next view, this is just an approximation since we are 810 // stacked and we can make assumptions about the visibility of the this 811 // task relative to the ones in front of it. 812 if (frontTv != null) { 813 float taskBottom = tv.getBottom(); 814 float frontTaskTop = frontTv.getTop(); 815 if (frontTaskTop < taskBottom) { 816 // Map the stack view space coordinate (the rects) to view space 817 clipBottom = (int) (taskBottom - frontTaskTop) - mTaskCornerRadiusPx; 818 } 819 } 820 } 821 tv.getViewBounds().setClipBottom(clipBottom); 822 tv.mThumbnailView.updateThumbnailVisibility(clipBottom - tv.getPaddingBottom()); 823 prevVisibleTv = tv; 824 } 825 mTaskViewsClipDirty = false; 826 } 827 828 /** 829 * Updates the layout algorithm min and max virtual scroll bounds. 830 */ updateLayoutAlgorithm(boolean boundScrollToNewMinMax)831 public void updateLayoutAlgorithm(boolean boundScrollToNewMinMax) { 832 // Compute the min and max scroll values 833 mLayoutAlgorithm.update(mStack, mIgnoreTasks); 834 835 // Update the freeform workspace background 836 SystemServicesProxy ssp = Recents.getSystemServices(); 837 if (ssp.hasFreeformWorkspaceSupport()) { 838 mTmpRect.set(mLayoutAlgorithm.mFreeformRect); 839 mFreeformWorkspaceBackground.setBounds(mTmpRect); 840 } 841 842 if (boundScrollToNewMinMax) { 843 mStackScroller.boundScroll(); 844 } 845 } 846 847 /** 848 * Updates the stack layout to its stable places. 849 */ updateLayoutToStableBounds()850 private void updateLayoutToStableBounds() { 851 mWindowRect.set(mStableWindowRect); 852 mStackBounds.set(mStableStackBounds); 853 mLayoutAlgorithm.setSystemInsets(mStableLayoutAlgorithm.mSystemInsets); 854 mLayoutAlgorithm.initialize(mDisplayRect, mWindowRect, mStackBounds, 855 TaskStackLayoutAlgorithm.StackState.getStackStateForStack(mStack)); 856 updateLayoutAlgorithm(true /* boundScroll */); 857 } 858 859 /** Returns the scroller. */ getScroller()860 public TaskStackViewScroller getScroller() { 861 return mStackScroller; 862 } 863 864 /** 865 * Sets the focused task to the provided (bounded taskIndex). 866 * 867 * @return whether or not the stack will scroll as a part of this focus change 868 */ setFocusedTask(int taskIndex, boolean scrollToTask, final boolean requestViewFocus)869 private boolean setFocusedTask(int taskIndex, boolean scrollToTask, 870 final boolean requestViewFocus) { 871 return setFocusedTask(taskIndex, scrollToTask, requestViewFocus, 0); 872 } 873 874 /** 875 * Sets the focused task to the provided (bounded focusTaskIndex). 876 * 877 * @return whether or not the stack will scroll as a part of this focus change 878 */ setFocusedTask(int focusTaskIndex, boolean scrollToTask, boolean requestViewFocus, int timerIndicatorDuration)879 private boolean setFocusedTask(int focusTaskIndex, boolean scrollToTask, 880 boolean requestViewFocus, int timerIndicatorDuration) { 881 // Find the next task to focus 882 int newFocusedTaskIndex = mStack.getTaskCount() > 0 ? 883 Utilities.clamp(focusTaskIndex, 0, mStack.getTaskCount() - 1) : -1; 884 final Task newFocusedTask = (newFocusedTaskIndex != -1) ? 885 mStack.getStackTasks().get(newFocusedTaskIndex) : null; 886 887 // Reset the last focused task state if changed 888 if (mFocusedTask != null) { 889 // Cancel the timer indicator, if applicable 890 if (timerIndicatorDuration > 0) { 891 final TaskView tv = getChildViewForTask(mFocusedTask); 892 if (tv != null) { 893 tv.getHeaderView().cancelFocusTimerIndicator(); 894 } 895 } 896 897 resetFocusedTask(mFocusedTask); 898 } 899 900 boolean willScroll = false; 901 mFocusedTask = newFocusedTask; 902 903 if (newFocusedTask != null) { 904 // Start the timer indicator, if applicable 905 if (timerIndicatorDuration > 0) { 906 final TaskView tv = getChildViewForTask(mFocusedTask); 907 if (tv != null) { 908 tv.getHeaderView().startFocusTimerIndicator(timerIndicatorDuration); 909 } else { 910 // The view is null; set a flag for later 911 mStartTimerIndicatorDuration = timerIndicatorDuration; 912 } 913 } 914 915 if (scrollToTask) { 916 // Cancel any running enter animations at this point when we scroll or change focus 917 if (!mEnterAnimationComplete) { 918 cancelAllTaskViewAnimations(); 919 } 920 921 mLayoutAlgorithm.clearUnfocusedTaskOverrides(); 922 willScroll = mAnimationHelper.startScrollToFocusedTaskAnimation(newFocusedTask, 923 requestViewFocus); 924 } else { 925 // Focus the task view 926 TaskView newFocusedTaskView = getChildViewForTask(newFocusedTask); 927 if (newFocusedTaskView != null) { 928 newFocusedTaskView.setFocusedState(true, requestViewFocus); 929 } 930 } 931 } 932 return willScroll; 933 } 934 935 /** 936 * Sets the focused task relative to the currently focused task. 937 * 938 * @param forward whether to go to the next task in the stack (along the curve) or the previous 939 * @param stackTasksOnly if set, will ensure that the traversal only goes along stack tasks, and 940 * if the currently focused task is not a stack task, will set the focus 941 * to the first visible stack task 942 * @param animated determines whether to actually draw the highlight along with the change in 943 * focus. 944 */ setRelativeFocusedTask(boolean forward, boolean stackTasksOnly, boolean animated)945 public void setRelativeFocusedTask(boolean forward, boolean stackTasksOnly, boolean animated) { 946 setRelativeFocusedTask(forward, stackTasksOnly, animated, false, 0); 947 } 948 949 /** 950 * Sets the focused task relative to the currently focused task. 951 * 952 * @param forward whether to go to the next task in the stack (along the curve) or the previous 953 * @param stackTasksOnly if set, will ensure that the traversal only goes along stack tasks, and 954 * if the currently focused task is not a stack task, will set the focus 955 * to the first visible stack task 956 * @param animated determines whether to actually draw the highlight along with the change in 957 * focus. 958 * @param cancelWindowAnimations if set, will attempt to cancel window animations if a scroll 959 * happens. 960 * @param timerIndicatorDuration the duration to initialize the auto-advance timer indicator 961 */ setRelativeFocusedTask(boolean forward, boolean stackTasksOnly, boolean animated, boolean cancelWindowAnimations, int timerIndicatorDuration)962 public void setRelativeFocusedTask(boolean forward, boolean stackTasksOnly, boolean animated, 963 boolean cancelWindowAnimations, int timerIndicatorDuration) { 964 Task focusedTask = getFocusedTask(); 965 int newIndex = mStack.indexOfStackTask(focusedTask); 966 if (focusedTask != null) { 967 if (stackTasksOnly) { 968 List<Task> tasks = mStack.getStackTasks(); 969 if (focusedTask.isFreeformTask()) { 970 // Try and focus the front most stack task 971 TaskView tv = getFrontMostTaskView(stackTasksOnly); 972 if (tv != null) { 973 newIndex = mStack.indexOfStackTask(tv.getTask()); 974 } 975 } else { 976 // Try the next task if it is a stack task 977 int tmpNewIndex = newIndex + (forward ? -1 : 1); 978 if (0 <= tmpNewIndex && tmpNewIndex < tasks.size()) { 979 Task t = tasks.get(tmpNewIndex); 980 if (!t.isFreeformTask()) { 981 newIndex = tmpNewIndex; 982 } 983 } 984 } 985 } else { 986 // No restrictions, lets just move to the new task (looping forward/backwards if 987 // necessary) 988 int taskCount = mStack.getTaskCount(); 989 newIndex = (newIndex + (forward ? -1 : 1) + taskCount) % taskCount; 990 } 991 } else { 992 // We don't have a focused task 993 float stackScroll = mStackScroller.getStackScroll(); 994 ArrayList<Task> tasks = mStack.getStackTasks(); 995 int taskCount = tasks.size(); 996 if (forward) { 997 // Walk backwards and focus the next task smaller than the current stack scroll 998 for (newIndex = taskCount - 1; newIndex >= 0; newIndex--) { 999 float taskP = mLayoutAlgorithm.getStackScrollForTask(tasks.get(newIndex)); 1000 if (Float.compare(taskP, stackScroll) <= 0) { 1001 break; 1002 } 1003 } 1004 } else { 1005 // Walk forwards and focus the next task larger than the current stack scroll 1006 for (newIndex = 0; newIndex < taskCount; newIndex++) { 1007 float taskP = mLayoutAlgorithm.getStackScrollForTask(tasks.get(newIndex)); 1008 if (Float.compare(taskP, stackScroll) >= 0) { 1009 break; 1010 } 1011 } 1012 } 1013 } 1014 if (newIndex != -1) { 1015 boolean willScroll = setFocusedTask(newIndex, true /* scrollToTask */, 1016 true /* requestViewFocus */, timerIndicatorDuration); 1017 if (willScroll && cancelWindowAnimations) { 1018 // As we iterate to the next/previous task, cancel any current/lagging window 1019 // transition animations 1020 EventBus.getDefault().send(new CancelEnterRecentsWindowAnimationEvent(null)); 1021 } 1022 } 1023 } 1024 1025 /** 1026 * Resets the focused task. 1027 */ resetFocusedTask(Task task)1028 void resetFocusedTask(Task task) { 1029 if (task != null) { 1030 TaskView tv = getChildViewForTask(task); 1031 if (tv != null) { 1032 tv.setFocusedState(false, false /* requestViewFocus */); 1033 } 1034 } 1035 mFocusedTask = null; 1036 } 1037 1038 /** 1039 * Returns the focused task. 1040 */ getFocusedTask()1041 Task getFocusedTask() { 1042 return mFocusedTask; 1043 } 1044 1045 /** 1046 * Returns the accessibility focused task. 1047 */ getAccessibilityFocusedTask()1048 Task getAccessibilityFocusedTask() { 1049 List<TaskView> taskViews = getTaskViews(); 1050 int taskViewCount = taskViews.size(); 1051 for (int i = 0; i < taskViewCount; i++) { 1052 TaskView tv = taskViews.get(i); 1053 if (Utilities.isDescendentAccessibilityFocused(tv)) { 1054 return tv.getTask(); 1055 } 1056 } 1057 TaskView frontTv = getFrontMostTaskView(true /* stackTasksOnly */); 1058 if (frontTv != null) { 1059 return frontTv.getTask(); 1060 } 1061 return null; 1062 } 1063 1064 @Override onInitializeAccessibilityEvent(AccessibilityEvent event)1065 public void onInitializeAccessibilityEvent(AccessibilityEvent event) { 1066 super.onInitializeAccessibilityEvent(event); 1067 List<TaskView> taskViews = getTaskViews(); 1068 int taskViewCount = taskViews.size(); 1069 if (taskViewCount > 0) { 1070 TaskView backMostTask = taskViews.get(0); 1071 TaskView frontMostTask = taskViews.get(taskViewCount - 1); 1072 event.setFromIndex(mStack.indexOfStackTask(backMostTask.getTask())); 1073 event.setToIndex(mStack.indexOfStackTask(frontMostTask.getTask())); 1074 event.setContentDescription(frontMostTask.getTask().title); 1075 } 1076 event.setItemCount(mStack.getTaskCount()); 1077 1078 int stackHeight = mLayoutAlgorithm.mStackRect.height(); 1079 event.setScrollY((int) (mStackScroller.getStackScroll() * stackHeight)); 1080 event.setMaxScrollY((int) (mLayoutAlgorithm.mMaxScrollP * stackHeight)); 1081 } 1082 1083 @Override onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info)1084 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { 1085 super.onInitializeAccessibilityNodeInfo(info); 1086 List<TaskView> taskViews = getTaskViews(); 1087 int taskViewCount = taskViews.size(); 1088 if (taskViewCount > 1) { 1089 // Find the accessibility focused task 1090 Task focusedTask = getAccessibilityFocusedTask(); 1091 info.setScrollable(true); 1092 int focusedTaskIndex = mStack.indexOfStackTask(focusedTask); 1093 if (focusedTaskIndex > 0) { 1094 info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD); 1095 } 1096 if (0 <= focusedTaskIndex && focusedTaskIndex < mStack.getTaskCount() - 1) { 1097 info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD); 1098 } 1099 } 1100 } 1101 1102 @Override getAccessibilityClassName()1103 public CharSequence getAccessibilityClassName() { 1104 return ScrollView.class.getName(); 1105 } 1106 1107 @Override performAccessibilityAction(int action, Bundle arguments)1108 public boolean performAccessibilityAction(int action, Bundle arguments) { 1109 if (super.performAccessibilityAction(action, arguments)) { 1110 return true; 1111 } 1112 Task focusedTask = getAccessibilityFocusedTask(); 1113 int taskIndex = mStack.indexOfStackTask(focusedTask); 1114 if (0 <= taskIndex && taskIndex < mStack.getTaskCount()) { 1115 switch (action) { 1116 case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD: { 1117 setFocusedTask(taskIndex + 1, true /* scrollToTask */, true /* requestViewFocus */, 1118 0); 1119 return true; 1120 } 1121 case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD: { 1122 setFocusedTask(taskIndex - 1, true /* scrollToTask */, true /* requestViewFocus */, 1123 0); 1124 return true; 1125 } 1126 } 1127 } 1128 return false; 1129 } 1130 1131 @Override onInterceptTouchEvent(MotionEvent ev)1132 public boolean onInterceptTouchEvent(MotionEvent ev) { 1133 return mTouchHandler.onInterceptTouchEvent(ev); 1134 } 1135 1136 @Override onTouchEvent(MotionEvent ev)1137 public boolean onTouchEvent(MotionEvent ev) { 1138 return mTouchHandler.onTouchEvent(ev); 1139 } 1140 1141 @Override onGenericMotionEvent(MotionEvent ev)1142 public boolean onGenericMotionEvent(MotionEvent ev) { 1143 return mTouchHandler.onGenericMotionEvent(ev); 1144 } 1145 1146 @Override computeScroll()1147 public void computeScroll() { 1148 if (mStackScroller.computeScroll()) { 1149 // Notify accessibility 1150 sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SCROLLED); 1151 } 1152 if (mDeferredTaskViewLayoutAnimation != null) { 1153 relayoutTaskViews(mDeferredTaskViewLayoutAnimation); 1154 mTaskViewsClipDirty = true; 1155 mDeferredTaskViewLayoutAnimation = null; 1156 } 1157 if (mTaskViewsClipDirty) { 1158 clipTaskViews(); 1159 } 1160 } 1161 1162 /** 1163 * Computes the maximum number of visible tasks and thumbnails. Requires that 1164 * updateLayoutForStack() is called first. 1165 */ computeStackVisibilityReport()1166 public TaskStackLayoutAlgorithm.VisibilityReport computeStackVisibilityReport() { 1167 return mLayoutAlgorithm.computeStackVisibilityReport(mStack.getStackTasks()); 1168 } 1169 1170 /** 1171 * Updates the system insets. 1172 */ setSystemInsets(Rect systemInsets)1173 public void setSystemInsets(Rect systemInsets) { 1174 boolean changed = false; 1175 changed |= mStableLayoutAlgorithm.setSystemInsets(systemInsets); 1176 changed |= mLayoutAlgorithm.setSystemInsets(systemInsets); 1177 if (changed) { 1178 requestLayout(); 1179 } 1180 } 1181 1182 /** 1183 * This is called with the full window width and height to allow stack view children to 1184 * perform the full screen transition down. 1185 */ 1186 @Override onMeasure(int widthMeasureSpec, int heightMeasureSpec)1187 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 1188 mInMeasureLayout = true; 1189 int width = MeasureSpec.getSize(widthMeasureSpec); 1190 int height = MeasureSpec.getSize(heightMeasureSpec); 1191 1192 // Update the stable stack bounds, but only update the current stack bounds if the stable 1193 // bounds have changed. This is because we may get spurious measures while dragging where 1194 // our current stack bounds reflect the target drop region. 1195 mLayoutAlgorithm.getTaskStackBounds(mDisplayRect, new Rect(0, 0, width, height), 1196 mLayoutAlgorithm.mSystemInsets.top, mLayoutAlgorithm.mSystemInsets.left, 1197 mLayoutAlgorithm.mSystemInsets.right, mTmpRect); 1198 if (!mTmpRect.equals(mStableStackBounds)) { 1199 mStableStackBounds.set(mTmpRect); 1200 mStackBounds.set(mTmpRect); 1201 mStableWindowRect.set(0, 0, width, height); 1202 mWindowRect.set(0, 0, width, height); 1203 } 1204 1205 // Compute the rects in the stack algorithm 1206 mStableLayoutAlgorithm.initialize(mDisplayRect, mStableWindowRect, mStableStackBounds, 1207 TaskStackLayoutAlgorithm.StackState.getStackStateForStack(mStack)); 1208 mLayoutAlgorithm.initialize(mDisplayRect, mWindowRect, mStackBounds, 1209 TaskStackLayoutAlgorithm.StackState.getStackStateForStack(mStack)); 1210 updateLayoutAlgorithm(false /* boundScroll */); 1211 1212 // If this is the first layout, then scroll to the front of the stack, then update the 1213 // TaskViews with the stack so that we can lay them out 1214 boolean resetToInitialState = (width != mLastWidth || height != mLastHeight) 1215 && mResetToInitialStateWhenResized; 1216 if (mAwaitingFirstLayout || mInitialState != INITIAL_STATE_UPDATE_NONE 1217 || resetToInitialState) { 1218 if (mInitialState != INITIAL_STATE_UPDATE_LAYOUT_ONLY || resetToInitialState) { 1219 updateToInitialState(); 1220 mResetToInitialStateWhenResized = false; 1221 } 1222 if (!mAwaitingFirstLayout) { 1223 mInitialState = INITIAL_STATE_UPDATE_NONE; 1224 } 1225 } 1226 1227 // Rebind all the views, including the ignore ones 1228 bindVisibleTaskViews(mStackScroller.getStackScroll(), false /* ignoreTaskOverrides */); 1229 1230 // Measure each of the TaskViews 1231 mTmpTaskViews.clear(); 1232 mTmpTaskViews.addAll(getTaskViews()); 1233 mTmpTaskViews.addAll(mViewPool.getViews()); 1234 int taskViewCount = mTmpTaskViews.size(); 1235 for (int i = 0; i < taskViewCount; i++) { 1236 measureTaskView(mTmpTaskViews.get(i)); 1237 } 1238 1239 setMeasuredDimension(width, height); 1240 mLastWidth = width; 1241 mLastHeight = height; 1242 mInMeasureLayout = false; 1243 } 1244 1245 /** 1246 * Measures a TaskView. 1247 */ measureTaskView(TaskView tv)1248 private void measureTaskView(TaskView tv) { 1249 Rect padding = new Rect(); 1250 if (tv.getBackground() != null) { 1251 tv.getBackground().getPadding(padding); 1252 } 1253 mTmpRect.set(mStableLayoutAlgorithm.mTaskRect); 1254 mTmpRect.union(mLayoutAlgorithm.mTaskRect); 1255 tv.measure( 1256 MeasureSpec.makeMeasureSpec(mTmpRect.width() + padding.left + padding.right, 1257 MeasureSpec.EXACTLY), 1258 MeasureSpec.makeMeasureSpec(mTmpRect.height() + padding.top + padding.bottom, 1259 MeasureSpec.EXACTLY)); 1260 } 1261 1262 @Override onLayout(boolean changed, int left, int top, int right, int bottom)1263 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 1264 // Layout each of the TaskViews 1265 mTmpTaskViews.clear(); 1266 mTmpTaskViews.addAll(getTaskViews()); 1267 mTmpTaskViews.addAll(mViewPool.getViews()); 1268 int taskViewCount = mTmpTaskViews.size(); 1269 for (int i = 0; i < taskViewCount; i++) { 1270 layoutTaskView(changed, mTmpTaskViews.get(i)); 1271 } 1272 1273 if (changed) { 1274 if (mStackScroller.isScrollOutOfBounds()) { 1275 mStackScroller.boundScroll(); 1276 } 1277 } 1278 1279 // Relayout all of the task views including the ignored ones 1280 relayoutTaskViews(AnimationProps.IMMEDIATE); 1281 clipTaskViews(); 1282 1283 if (mAwaitingFirstLayout || !mEnterAnimationComplete) { 1284 mAwaitingFirstLayout = false; 1285 mInitialState = INITIAL_STATE_UPDATE_NONE; 1286 onFirstLayout(); 1287 } 1288 } 1289 1290 /** 1291 * Lays out a TaskView. 1292 */ layoutTaskView(boolean changed, TaskView tv)1293 private void layoutTaskView(boolean changed, TaskView tv) { 1294 if (changed) { 1295 Rect padding = new Rect(); 1296 if (tv.getBackground() != null) { 1297 tv.getBackground().getPadding(padding); 1298 } 1299 mTmpRect.set(mStableLayoutAlgorithm.mTaskRect); 1300 mTmpRect.union(mLayoutAlgorithm.mTaskRect); 1301 tv.cancelTransformAnimation(); 1302 tv.layout(mTmpRect.left - padding.left, mTmpRect.top - padding.top, 1303 mTmpRect.right + padding.right, mTmpRect.bottom + padding.bottom); 1304 } else { 1305 // If the layout has not changed, then just lay it out again in-place 1306 tv.layout(tv.getLeft(), tv.getTop(), tv.getRight(), tv.getBottom()); 1307 } 1308 } 1309 1310 /** Handler for the first layout. */ onFirstLayout()1311 void onFirstLayout() { 1312 // Setup the view for the enter animation 1313 mAnimationHelper.prepareForEnterAnimation(); 1314 1315 // Animate in the freeform workspace 1316 int ffBgAlpha = mLayoutAlgorithm.getStackState().freeformBackgroundAlpha; 1317 animateFreeformWorkspaceBackgroundAlpha(ffBgAlpha, new AnimationProps(150, 1318 Interpolators.FAST_OUT_SLOW_IN)); 1319 1320 // Set the task focused state without requesting view focus, and leave the focus animations 1321 // until after the enter-animation 1322 RecentsConfiguration config = Recents.getConfiguration(); 1323 RecentsActivityLaunchState launchState = config.getLaunchState(); 1324 int focusedTaskIndex = launchState.getInitialFocusTaskIndex(mStack.getTaskCount()); 1325 if (focusedTaskIndex != -1) { 1326 setFocusedTask(focusedTaskIndex, false /* scrollToTask */, 1327 false /* requestViewFocus */); 1328 } 1329 1330 // Update the stack action button visibility 1331 if (mStackScroller.getStackScroll() < SHOW_STACK_ACTION_BUTTON_SCROLL_THRESHOLD && 1332 mStack.getTaskCount() > 0) { 1333 EventBus.getDefault().send(new ShowStackActionButtonEvent(false /* translate */)); 1334 } else { 1335 EventBus.getDefault().send(new HideStackActionButtonEvent()); 1336 } 1337 } 1338 isTouchPointInView(float x, float y, TaskView tv)1339 public boolean isTouchPointInView(float x, float y, TaskView tv) { 1340 mTmpRect.set(tv.getLeft(), tv.getTop(), tv.getRight(), tv.getBottom()); 1341 mTmpRect.offset((int) tv.getTranslationX(), (int) tv.getTranslationY()); 1342 return mTmpRect.contains((int) x, (int) y); 1343 } 1344 1345 /** 1346 * Returns a non-ignored task in the {@param tasks} list that can be used as an achor when 1347 * calculating the scroll position before and after a layout change. 1348 */ findAnchorTask(List<Task> tasks, MutableBoolean isFrontMostTask)1349 public Task findAnchorTask(List<Task> tasks, MutableBoolean isFrontMostTask) { 1350 for (int i = tasks.size() - 1; i >= 0; i--) { 1351 Task task = tasks.get(i); 1352 1353 // Ignore deleting tasks 1354 if (isIgnoredTask(task)) { 1355 if (i == tasks.size() - 1) { 1356 isFrontMostTask.value = true; 1357 } 1358 continue; 1359 } 1360 return task; 1361 } 1362 return null; 1363 } 1364 1365 @Override onDraw(Canvas canvas)1366 protected void onDraw(Canvas canvas) { 1367 super.onDraw(canvas); 1368 1369 // Draw the freeform workspace background 1370 SystemServicesProxy ssp = Recents.getSystemServices(); 1371 if (ssp.hasFreeformWorkspaceSupport()) { 1372 if (mFreeformWorkspaceBackground.getAlpha() > 0) { 1373 mFreeformWorkspaceBackground.draw(canvas); 1374 } 1375 } 1376 } 1377 1378 @Override verifyDrawable(Drawable who)1379 protected boolean verifyDrawable(Drawable who) { 1380 if (who == mFreeformWorkspaceBackground) { 1381 return true; 1382 } 1383 return super.verifyDrawable(who); 1384 } 1385 1386 /** 1387 * Launches the freeform tasks. 1388 */ launchFreeformTasks()1389 public boolean launchFreeformTasks() { 1390 ArrayList<Task> tasks = mStack.getFreeformTasks(); 1391 if (!tasks.isEmpty()) { 1392 Task frontTask = tasks.get(tasks.size() - 1); 1393 if (frontTask != null && frontTask.isFreeformTask()) { 1394 EventBus.getDefault().send(new LaunchTaskEvent(getChildViewForTask(frontTask), 1395 frontTask, null, INVALID_STACK_ID, false)); 1396 return true; 1397 } 1398 } 1399 return false; 1400 } 1401 1402 /**** TaskStackCallbacks Implementation ****/ 1403 1404 @Override onStackTaskAdded(TaskStack stack, Task newTask)1405 public void onStackTaskAdded(TaskStack stack, Task newTask) { 1406 // Update the min/max scroll and animate other task views into their new positions 1407 updateLayoutAlgorithm(true /* boundScroll */); 1408 1409 // Animate all the tasks into place 1410 relayoutTaskViews(mAwaitingFirstLayout 1411 ? AnimationProps.IMMEDIATE 1412 : new AnimationProps(DEFAULT_SYNC_STACK_DURATION, Interpolators.FAST_OUT_SLOW_IN)); 1413 } 1414 1415 /** 1416 * We expect that the {@link TaskView} associated with the removed task is already hidden. 1417 */ 1418 @Override onStackTaskRemoved(TaskStack stack, Task removedTask, Task newFrontMostTask, AnimationProps animation, boolean fromDockGesture)1419 public void onStackTaskRemoved(TaskStack stack, Task removedTask, Task newFrontMostTask, 1420 AnimationProps animation, boolean fromDockGesture) { 1421 if (mFocusedTask == removedTask) { 1422 resetFocusedTask(removedTask); 1423 } 1424 1425 // Remove the view associated with this task, we can't rely on updateTransforms 1426 // to work here because the task is no longer in the list 1427 TaskView tv = getChildViewForTask(removedTask); 1428 if (tv != null) { 1429 mViewPool.returnViewToPool(tv); 1430 } 1431 1432 // Remove the task from the ignored set 1433 removeIgnoreTask(removedTask); 1434 1435 // If requested, relayout with the given animation 1436 if (animation != null) { 1437 updateLayoutAlgorithm(true /* boundScroll */); 1438 relayoutTaskViews(animation); 1439 } 1440 1441 // Update the new front most task's action button 1442 if (mScreenPinningEnabled && newFrontMostTask != null) { 1443 TaskView frontTv = getChildViewForTask(newFrontMostTask); 1444 if (frontTv != null) { 1445 frontTv.showActionButton(true /* fadeIn */, DEFAULT_SYNC_STACK_DURATION); 1446 } 1447 } 1448 1449 // If there are no remaining tasks, then just close recents 1450 if (mStack.getTaskCount() == 0) { 1451 EventBus.getDefault().send(new AllTaskViewsDismissedEvent(fromDockGesture 1452 ? R.string.recents_empty_message 1453 : R.string.recents_empty_message_dismissed_all)); 1454 } 1455 } 1456 1457 @Override onStackTasksRemoved(TaskStack stack)1458 public void onStackTasksRemoved(TaskStack stack) { 1459 // Reset the focused task 1460 resetFocusedTask(getFocusedTask()); 1461 1462 // Return all the views to the pool 1463 List<TaskView> taskViews = new ArrayList<>(); 1464 taskViews.addAll(getTaskViews()); 1465 for (int i = taskViews.size() - 1; i >= 0; i--) { 1466 mViewPool.returnViewToPool(taskViews.get(i)); 1467 } 1468 1469 // Remove all the ignore tasks 1470 mIgnoreTasks.clear(); 1471 1472 // If there are no remaining tasks, then just close recents 1473 EventBus.getDefault().send(new AllTaskViewsDismissedEvent( 1474 R.string.recents_empty_message_dismissed_all)); 1475 } 1476 1477 @Override onStackTasksUpdated(TaskStack stack)1478 public void onStackTasksUpdated(TaskStack stack) { 1479 // Update the layout and immediately layout 1480 updateLayoutAlgorithm(false /* boundScroll */); 1481 relayoutTaskViews(AnimationProps.IMMEDIATE); 1482 1483 // Rebind all the task views. This will not trigger new resources to be loaded 1484 // unless they have actually changed 1485 List<TaskView> taskViews = getTaskViews(); 1486 int taskViewCount = taskViews.size(); 1487 for (int i = 0; i < taskViewCount; i++) { 1488 TaskView tv = taskViews.get(i); 1489 bindTaskView(tv, tv.getTask()); 1490 } 1491 } 1492 1493 /**** ViewPoolConsumer Implementation ****/ 1494 1495 @Override createView(Context context)1496 public TaskView createView(Context context) { 1497 return (TaskView) mInflater.inflate(R.layout.recents_task_view, this, false); 1498 } 1499 1500 @Override onReturnViewToPool(TaskView tv)1501 public void onReturnViewToPool(TaskView tv) { 1502 final Task task = tv.getTask(); 1503 1504 // Unbind the task from the task view 1505 unbindTaskView(tv, task); 1506 1507 // Reset the view properties and view state 1508 tv.clearAccessibilityFocus(); 1509 tv.resetViewProperties(); 1510 tv.setFocusedState(false, false /* requestViewFocus */); 1511 tv.setClipViewInStack(false); 1512 if (mScreenPinningEnabled) { 1513 tv.hideActionButton(false /* fadeOut */, 0 /* duration */, false /* scaleDown */, null); 1514 } 1515 1516 // Detach the view from the hierarchy 1517 detachViewFromParent(tv); 1518 // Update the task views list after removing the task view 1519 updateTaskViewsList(); 1520 } 1521 1522 @Override onPickUpViewFromPool(TaskView tv, Task task, boolean isNewView)1523 public void onPickUpViewFromPool(TaskView tv, Task task, boolean isNewView) { 1524 // Find the index where this task should be placed in the stack 1525 int taskIndex = mStack.indexOfStackTask(task); 1526 int insertIndex = findTaskViewInsertIndex(task, taskIndex); 1527 1528 // Add/attach the view to the hierarchy 1529 if (isNewView) { 1530 if (mInMeasureLayout) { 1531 // If we are measuring the layout, then just add the view normally as it will be 1532 // laid out during the layout pass 1533 addView(tv, insertIndex); 1534 } else { 1535 // Otherwise, this is from a bindVisibleTaskViews() call outside the measure/layout 1536 // pass, and we should layout the new child ourselves 1537 ViewGroup.LayoutParams params = tv.getLayoutParams(); 1538 if (params == null) { 1539 params = generateDefaultLayoutParams(); 1540 } 1541 addViewInLayout(tv, insertIndex, params, true /* preventRequestLayout */); 1542 measureTaskView(tv); 1543 layoutTaskView(true /* changed */, tv); 1544 } 1545 } else { 1546 attachViewToParent(tv, insertIndex, tv.getLayoutParams()); 1547 } 1548 // Update the task views list after adding the new task view 1549 updateTaskViewsList(); 1550 1551 // Bind the task view to the new task 1552 bindTaskView(tv, task); 1553 1554 // If the doze trigger has already fired, then update the state for this task view 1555 if (mUIDozeTrigger.isAsleep()) { 1556 tv.setNoUserInteractionState(); 1557 } 1558 1559 // Set the new state for this view, including the callbacks and view clipping 1560 tv.setCallbacks(this); 1561 tv.setTouchEnabled(true); 1562 tv.setClipViewInStack(true); 1563 if (mFocusedTask == task) { 1564 tv.setFocusedState(true, false /* requestViewFocus */); 1565 if (mStartTimerIndicatorDuration > 0) { 1566 // The timer indicator couldn't be started before, so start it now 1567 tv.getHeaderView().startFocusTimerIndicator(mStartTimerIndicatorDuration); 1568 mStartTimerIndicatorDuration = 0; 1569 } 1570 } 1571 1572 // Restore the action button visibility if it is the front most task view 1573 if (mScreenPinningEnabled && tv.getTask() == 1574 mStack.getStackFrontMostTask(false /* includeFreeform */)) { 1575 tv.showActionButton(false /* fadeIn */, 0 /* fadeInDuration */); 1576 } 1577 } 1578 1579 @Override hasPreferredData(TaskView tv, Task preferredData)1580 public boolean hasPreferredData(TaskView tv, Task preferredData) { 1581 return (tv.getTask() == preferredData); 1582 } 1583 bindTaskView(TaskView tv, Task task)1584 private void bindTaskView(TaskView tv, Task task) { 1585 // Rebind the task and request that this task's data be filled into the TaskView 1586 tv.onTaskBound(task, mTouchExplorationEnabled, mDisplayOrientation, mDisplayRect); 1587 1588 // Load the task data 1589 Recents.getTaskLoader().loadTaskData(task); 1590 } 1591 unbindTaskView(TaskView tv, Task task)1592 private void unbindTaskView(TaskView tv, Task task) { 1593 // Report that this task's data is no longer being used 1594 Recents.getTaskLoader().unloadTaskData(task); 1595 } 1596 1597 /**** TaskViewCallbacks Implementation ****/ 1598 1599 @Override onTaskViewClipStateChanged(TaskView tv)1600 public void onTaskViewClipStateChanged(TaskView tv) { 1601 if (!mTaskViewsClipDirty) { 1602 mTaskViewsClipDirty = true; 1603 invalidate(); 1604 } 1605 } 1606 1607 /**** TaskStackLayoutAlgorithm.TaskStackLayoutAlgorithmCallbacks ****/ 1608 1609 @Override onFocusStateChanged(int prevFocusState, int curFocusState)1610 public void onFocusStateChanged(int prevFocusState, int curFocusState) { 1611 if (mDeferredTaskViewLayoutAnimation == null) { 1612 mUIDozeTrigger.poke(); 1613 relayoutTaskViewsOnNextFrame(AnimationProps.IMMEDIATE); 1614 } 1615 } 1616 1617 /**** TaskStackViewScroller.TaskStackViewScrollerCallbacks ****/ 1618 1619 @Override onStackScrollChanged(float prevScroll, float curScroll, AnimationProps animation)1620 public void onStackScrollChanged(float prevScroll, float curScroll, AnimationProps animation) { 1621 mUIDozeTrigger.poke(); 1622 if (animation != null) { 1623 relayoutTaskViewsOnNextFrame(animation); 1624 } 1625 1626 if (mEnterAnimationComplete) { 1627 if (prevScroll > SHOW_STACK_ACTION_BUTTON_SCROLL_THRESHOLD && 1628 curScroll <= SHOW_STACK_ACTION_BUTTON_SCROLL_THRESHOLD && 1629 mStack.getTaskCount() > 0) { 1630 EventBus.getDefault().send(new ShowStackActionButtonEvent(true /* translate */)); 1631 } else if (prevScroll < HIDE_STACK_ACTION_BUTTON_SCROLL_THRESHOLD && 1632 curScroll >= HIDE_STACK_ACTION_BUTTON_SCROLL_THRESHOLD) { 1633 EventBus.getDefault().send(new HideStackActionButtonEvent()); 1634 } 1635 } 1636 } 1637 1638 /**** EventBus Events ****/ 1639 onBusEvent(PackagesChangedEvent event)1640 public final void onBusEvent(PackagesChangedEvent event) { 1641 // Compute which components need to be removed 1642 ArraySet<ComponentName> removedComponents = mStack.computeComponentsRemoved( 1643 event.packageName, event.userId); 1644 1645 // For other tasks, just remove them directly if they no longer exist 1646 ArrayList<Task> tasks = mStack.getStackTasks(); 1647 for (int i = tasks.size() - 1; i >= 0; i--) { 1648 final Task t = tasks.get(i); 1649 if (removedComponents.contains(t.key.getComponent())) { 1650 final TaskView tv = getChildViewForTask(t); 1651 if (tv != null) { 1652 // For visible children, defer removing the task until after the animation 1653 tv.dismissTask(); 1654 } else { 1655 // Otherwise, remove the task from the stack immediately 1656 mStack.removeTask(t, AnimationProps.IMMEDIATE, false /* fromDockGesture */); 1657 } 1658 } 1659 } 1660 } 1661 onBusEvent(LaunchTaskEvent event)1662 public final void onBusEvent(LaunchTaskEvent event) { 1663 // Cancel any doze triggers once a task is launched 1664 mUIDozeTrigger.stopDozing(); 1665 } 1666 onBusEvent(LaunchNextTaskRequestEvent event)1667 public final void onBusEvent(LaunchNextTaskRequestEvent event) { 1668 int launchTaskIndex = mStack.indexOfStackTask(mStack.getLaunchTarget()); 1669 if (launchTaskIndex != -1) { 1670 launchTaskIndex = Math.max(0, launchTaskIndex - 1); 1671 } else { 1672 launchTaskIndex = mStack.getTaskCount() - 1; 1673 } 1674 if (launchTaskIndex != -1) { 1675 // Stop all animations 1676 cancelAllTaskViewAnimations(); 1677 1678 final Task launchTask = mStack.getStackTasks().get(launchTaskIndex); 1679 float curScroll = mStackScroller.getStackScroll(); 1680 float targetScroll = mLayoutAlgorithm.getStackScrollForTaskAtInitialOffset(launchTask); 1681 float absScrollDiff = Math.abs(targetScroll - curScroll); 1682 if (getChildViewForTask(launchTask) == null || absScrollDiff > 0.35f) { 1683 int duration = (int) (LAUNCH_NEXT_SCROLL_BASE_DURATION + 1684 absScrollDiff * LAUNCH_NEXT_SCROLL_INCR_DURATION); 1685 mStackScroller.animateScroll(targetScroll, 1686 duration, new Runnable() { 1687 @Override 1688 public void run() { 1689 EventBus.getDefault().send(new LaunchTaskEvent( 1690 getChildViewForTask(launchTask), launchTask, null, 1691 INVALID_STACK_ID, false /* screenPinningRequested */)); 1692 } 1693 }); 1694 } else { 1695 EventBus.getDefault().send(new LaunchTaskEvent(getChildViewForTask(launchTask), 1696 launchTask, null, INVALID_STACK_ID, false /* screenPinningRequested */)); 1697 } 1698 1699 MetricsLogger.action(getContext(), MetricsEvent.OVERVIEW_LAUNCH_PREVIOUS_TASK, 1700 launchTask.key.getComponent().toString()); 1701 } else if (mStack.getTaskCount() == 0) { 1702 // If there are no tasks, then just hide recents back to home. 1703 EventBus.getDefault().send(new HideRecentsEvent(false, true)); 1704 } 1705 } 1706 onBusEvent(LaunchTaskStartedEvent event)1707 public final void onBusEvent(LaunchTaskStartedEvent event) { 1708 mAnimationHelper.startLaunchTaskAnimation(event.taskView, event.screenPinningRequested, 1709 event.getAnimationTrigger()); 1710 } 1711 onBusEvent(DismissRecentsToHomeAnimationStarted event)1712 public final void onBusEvent(DismissRecentsToHomeAnimationStarted event) { 1713 // Stop any scrolling 1714 mTouchHandler.cancelNonDismissTaskAnimations(); 1715 mStackScroller.stopScroller(); 1716 mStackScroller.stopBoundScrollAnimation(); 1717 cancelDeferredTaskViewLayoutAnimation(); 1718 1719 // Start the task animations 1720 mAnimationHelper.startExitToHomeAnimation(event.animated, event.getAnimationTrigger()); 1721 1722 // Dismiss the freeform workspace background 1723 int taskViewExitToHomeDuration = TaskStackAnimationHelper.EXIT_TO_HOME_TRANSLATION_DURATION; 1724 animateFreeformWorkspaceBackgroundAlpha(0, new AnimationProps(taskViewExitToHomeDuration, 1725 Interpolators.FAST_OUT_SLOW_IN)); 1726 } 1727 onBusEvent(DismissFocusedTaskViewEvent event)1728 public final void onBusEvent(DismissFocusedTaskViewEvent event) { 1729 if (mFocusedTask != null) { 1730 TaskView tv = getChildViewForTask(mFocusedTask); 1731 if (tv != null) { 1732 tv.dismissTask(); 1733 } 1734 resetFocusedTask(mFocusedTask); 1735 } 1736 } 1737 onBusEvent(DismissTaskViewEvent event)1738 public final void onBusEvent(DismissTaskViewEvent event) { 1739 // For visible children, defer removing the task until after the animation 1740 mAnimationHelper.startDeleteTaskAnimation(event.taskView, event.getAnimationTrigger()); 1741 } 1742 onBusEvent(final DismissAllTaskViewsEvent event)1743 public final void onBusEvent(final DismissAllTaskViewsEvent event) { 1744 // Keep track of the tasks which will have their data removed 1745 ArrayList<Task> tasks = new ArrayList<>(mStack.getStackTasks()); 1746 mAnimationHelper.startDeleteAllTasksAnimation(getTaskViews(), event.getAnimationTrigger()); 1747 event.addPostAnimationCallback(new Runnable() { 1748 @Override 1749 public void run() { 1750 // Announce for accessibility 1751 announceForAccessibility(getContext().getString( 1752 R.string.accessibility_recents_all_items_dismissed)); 1753 1754 // Remove all tasks and delete the task data for all tasks 1755 mStack.removeAllTasks(); 1756 for (int i = tasks.size() - 1; i >= 0; i--) { 1757 EventBus.getDefault().send(new DeleteTaskDataEvent(tasks.get(i))); 1758 } 1759 1760 MetricsLogger.action(getContext(), MetricsEvent.OVERVIEW_DISMISS_ALL); 1761 } 1762 }); 1763 1764 } 1765 onBusEvent(TaskViewDismissedEvent event)1766 public final void onBusEvent(TaskViewDismissedEvent event) { 1767 // Announce for accessibility 1768 announceForAccessibility(getContext().getString( 1769 R.string.accessibility_recents_item_dismissed, event.task.title)); 1770 1771 // Remove the task from the stack 1772 mStack.removeTask(event.task, event.animation, false /* fromDockGesture */); 1773 EventBus.getDefault().send(new DeleteTaskDataEvent(event.task)); 1774 1775 MetricsLogger.action(getContext(), MetricsEvent.OVERVIEW_DISMISS, 1776 event.task.key.getComponent().toString()); 1777 } 1778 onBusEvent(FocusNextTaskViewEvent event)1779 public final void onBusEvent(FocusNextTaskViewEvent event) { 1780 // Stop any scrolling 1781 mStackScroller.stopScroller(); 1782 mStackScroller.stopBoundScrollAnimation(); 1783 1784 setRelativeFocusedTask(true, false /* stackTasksOnly */, true /* animated */, false, 1785 event.timerIndicatorDuration); 1786 } 1787 onBusEvent(FocusPreviousTaskViewEvent event)1788 public final void onBusEvent(FocusPreviousTaskViewEvent event) { 1789 // Stop any scrolling 1790 mStackScroller.stopScroller(); 1791 mStackScroller.stopBoundScrollAnimation(); 1792 1793 setRelativeFocusedTask(false, false /* stackTasksOnly */, true /* animated */); 1794 } 1795 onBusEvent(UserInteractionEvent event)1796 public final void onBusEvent(UserInteractionEvent event) { 1797 // Poke the doze trigger on user interaction 1798 mUIDozeTrigger.poke(); 1799 1800 RecentsDebugFlags debugFlags = Recents.getDebugFlags(); 1801 if (debugFlags.isFastToggleRecentsEnabled() && mFocusedTask != null) { 1802 TaskView tv = getChildViewForTask(mFocusedTask); 1803 if (tv != null) { 1804 tv.getHeaderView().cancelFocusTimerIndicator(); 1805 } 1806 } 1807 } 1808 onBusEvent(DragStartEvent event)1809 public final void onBusEvent(DragStartEvent event) { 1810 // Ensure that the drag task is not animated 1811 addIgnoreTask(event.task); 1812 1813 if (event.task.isFreeformTask()) { 1814 // Animate to the front of the stack 1815 mStackScroller.animateScroll(mLayoutAlgorithm.mInitialScrollP, null); 1816 } 1817 1818 // Enlarge the dragged view slightly 1819 float finalScale = event.taskView.getScaleX() * DRAG_SCALE_FACTOR; 1820 mLayoutAlgorithm.getStackTransform(event.task, getScroller().getStackScroll(), 1821 mTmpTransform, null); 1822 mTmpTransform.scale = finalScale; 1823 mTmpTransform.translationZ = mLayoutAlgorithm.mMaxTranslationZ + 1; 1824 mTmpTransform.dimAlpha = 0f; 1825 updateTaskViewToTransform(event.taskView, mTmpTransform, 1826 new AnimationProps(DRAG_SCALE_DURATION, Interpolators.FAST_OUT_SLOW_IN)); 1827 } 1828 onBusEvent(DragStartInitializeDropTargetsEvent event)1829 public final void onBusEvent(DragStartInitializeDropTargetsEvent event) { 1830 SystemServicesProxy ssp = Recents.getSystemServices(); 1831 if (ssp.hasFreeformWorkspaceSupport()) { 1832 event.handler.registerDropTargetForCurrentDrag(mStackDropTarget); 1833 event.handler.registerDropTargetForCurrentDrag(mFreeformWorkspaceDropTarget); 1834 } 1835 } 1836 onBusEvent(DragDropTargetChangedEvent event)1837 public final void onBusEvent(DragDropTargetChangedEvent event) { 1838 AnimationProps animation = new AnimationProps(SLOW_SYNC_STACK_DURATION, 1839 Interpolators.FAST_OUT_SLOW_IN); 1840 boolean ignoreTaskOverrides = false; 1841 if (event.dropTarget instanceof TaskStack.DockState) { 1842 // Calculate the new task stack bounds that matches the window size that Recents will 1843 // have after the drop 1844 final TaskStack.DockState dockState = (TaskStack.DockState) event.dropTarget; 1845 Rect systemInsets = new Rect(mStableLayoutAlgorithm.mSystemInsets); 1846 // When docked, the nav bar insets are consumed and the activity is measured without 1847 // insets. However, the window bounds include the insets, so we need to subtract them 1848 // here to make them identical. 1849 int height = getMeasuredHeight(); 1850 height -= systemInsets.bottom; 1851 systemInsets.bottom = 0; 1852 mStackBounds.set(dockState.getDockedTaskStackBounds(mDisplayRect, getMeasuredWidth(), 1853 height, mDividerSize, systemInsets, 1854 mLayoutAlgorithm, getResources(), mWindowRect)); 1855 mLayoutAlgorithm.setSystemInsets(systemInsets); 1856 mLayoutAlgorithm.initialize(mDisplayRect, mWindowRect, mStackBounds, 1857 TaskStackLayoutAlgorithm.StackState.getStackStateForStack(mStack)); 1858 updateLayoutAlgorithm(true /* boundScroll */); 1859 ignoreTaskOverrides = true; 1860 } else { 1861 // Restore the pre-drag task stack bounds, but ensure that we don't layout the dragging 1862 // task view, so add it back to the ignore set after updating the layout 1863 removeIgnoreTask(event.task); 1864 updateLayoutToStableBounds(); 1865 addIgnoreTask(event.task); 1866 } 1867 relayoutTaskViews(animation, null /* animationOverrides */, ignoreTaskOverrides); 1868 } 1869 onBusEvent(final DragEndEvent event)1870 public final void onBusEvent(final DragEndEvent event) { 1871 // We don't handle drops on the dock regions 1872 if (event.dropTarget instanceof TaskStack.DockState) { 1873 // However, we do need to reset the overrides, since the last state of this task stack 1874 // view layout was ignoring task overrides (see DragDropTargetChangedEvent handler) 1875 mLayoutAlgorithm.clearUnfocusedTaskOverrides(); 1876 return; 1877 } 1878 1879 boolean isFreeformTask = event.task.isFreeformTask(); 1880 boolean hasChangedStacks = 1881 (!isFreeformTask && event.dropTarget == mFreeformWorkspaceDropTarget) || 1882 (isFreeformTask && event.dropTarget == mStackDropTarget); 1883 1884 if (hasChangedStacks) { 1885 // Move the task to the right position in the stack (ie. the front of the stack if 1886 // freeform or the front of the stack if fullscreen). Note, we MUST move the tasks 1887 // before we update their stack ids, otherwise, the keys will have changed. 1888 if (event.dropTarget == mFreeformWorkspaceDropTarget) { 1889 mStack.moveTaskToStack(event.task, FREEFORM_WORKSPACE_STACK_ID); 1890 } else if (event.dropTarget == mStackDropTarget) { 1891 mStack.moveTaskToStack(event.task, FULLSCREEN_WORKSPACE_STACK_ID); 1892 } 1893 updateLayoutAlgorithm(true /* boundScroll */); 1894 1895 // Move the task to the new stack in the system after the animation completes 1896 event.addPostAnimationCallback(new Runnable() { 1897 @Override 1898 public void run() { 1899 SystemServicesProxy ssp = Recents.getSystemServices(); 1900 ssp.moveTaskToStack(event.task.key.id, event.task.key.stackId); 1901 } 1902 }); 1903 } 1904 1905 // Restore the task, so that relayout will apply to it below 1906 removeIgnoreTask(event.task); 1907 1908 // Convert the dragging task view back to its final layout-space rect 1909 Utilities.setViewFrameFromTranslation(event.taskView); 1910 1911 // Animate all the tasks into place 1912 ArrayMap<Task, AnimationProps> animationOverrides = new ArrayMap<>(); 1913 animationOverrides.put(event.task, new AnimationProps(SLOW_SYNC_STACK_DURATION, 1914 Interpolators.FAST_OUT_SLOW_IN, 1915 event.getAnimationTrigger().decrementOnAnimationEnd())); 1916 relayoutTaskViews(new AnimationProps(SLOW_SYNC_STACK_DURATION, 1917 Interpolators.FAST_OUT_SLOW_IN)); 1918 event.getAnimationTrigger().increment(); 1919 } 1920 onBusEvent(final DragEndCancelledEvent event)1921 public final void onBusEvent(final DragEndCancelledEvent event) { 1922 // Restore the pre-drag task stack bounds, including the dragging task view 1923 removeIgnoreTask(event.task); 1924 updateLayoutToStableBounds(); 1925 1926 // Convert the dragging task view back to its final layout-space rect 1927 Utilities.setViewFrameFromTranslation(event.taskView); 1928 1929 // Animate all the tasks into place 1930 ArrayMap<Task, AnimationProps> animationOverrides = new ArrayMap<>(); 1931 animationOverrides.put(event.task, new AnimationProps(SLOW_SYNC_STACK_DURATION, 1932 Interpolators.FAST_OUT_SLOW_IN, 1933 event.getAnimationTrigger().decrementOnAnimationEnd())); 1934 relayoutTaskViews(new AnimationProps(SLOW_SYNC_STACK_DURATION, 1935 Interpolators.FAST_OUT_SLOW_IN)); 1936 event.getAnimationTrigger().increment(); 1937 } 1938 onBusEvent(IterateRecentsEvent event)1939 public final void onBusEvent(IterateRecentsEvent event) { 1940 if (!mEnterAnimationComplete) { 1941 // Cancel the previous task's window transition before animating the focused state 1942 EventBus.getDefault().send(new CancelEnterRecentsWindowAnimationEvent(null)); 1943 } 1944 } 1945 onBusEvent(EnterRecentsWindowAnimationCompletedEvent event)1946 public final void onBusEvent(EnterRecentsWindowAnimationCompletedEvent event) { 1947 mEnterAnimationComplete = true; 1948 1949 if (mStack.getTaskCount() > 0) { 1950 // Start the task enter animations 1951 mAnimationHelper.startEnterAnimation(event.getAnimationTrigger()); 1952 1953 // Add a runnable to the post animation ref counter to clear all the views 1954 event.addPostAnimationCallback(new Runnable() { 1955 @Override 1956 public void run() { 1957 // Start the dozer to trigger to trigger any UI that shows after a timeout 1958 mUIDozeTrigger.startDozing(); 1959 1960 // Update the focused state here -- since we only set the focused task without 1961 // requesting view focus in onFirstLayout(), actually request view focus and 1962 // animate the focused state if we are alt-tabbing now, after the window enter 1963 // animation is completed 1964 if (mFocusedTask != null) { 1965 RecentsConfiguration config = Recents.getConfiguration(); 1966 RecentsActivityLaunchState launchState = config.getLaunchState(); 1967 setFocusedTask(mStack.indexOfStackTask(mFocusedTask), 1968 false /* scrollToTask */, launchState.launchedWithAltTab); 1969 TaskView focusedTaskView = getChildViewForTask(mFocusedTask); 1970 if (mTouchExplorationEnabled && focusedTaskView != null) { 1971 focusedTaskView.requestAccessibilityFocus(); 1972 } 1973 } 1974 1975 EventBus.getDefault().send(new EnterRecentsTaskStackAnimationCompletedEvent()); 1976 } 1977 }); 1978 } 1979 } 1980 onBusEvent(UpdateFreeformTaskViewVisibilityEvent event)1981 public final void onBusEvent(UpdateFreeformTaskViewVisibilityEvent event) { 1982 List<TaskView> taskViews = getTaskViews(); 1983 int taskViewCount = taskViews.size(); 1984 for (int i = 0; i < taskViewCount; i++) { 1985 TaskView tv = taskViews.get(i); 1986 Task task = tv.getTask(); 1987 if (task.isFreeformTask()) { 1988 tv.setVisibility(event.visible ? View.VISIBLE : View.INVISIBLE); 1989 } 1990 } 1991 } 1992 onBusEvent(final MultiWindowStateChangedEvent event)1993 public final void onBusEvent(final MultiWindowStateChangedEvent event) { 1994 if (event.inMultiWindow || !event.showDeferredAnimation) { 1995 setTasks(event.stack, true /* allowNotifyStackChanges */); 1996 } else { 1997 // Reset the launch state before handling the multiwindow change 1998 RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState(); 1999 launchState.reset(); 2000 2001 // Defer until the next frame to ensure that we have received all the system insets, and 2002 // initial layout updates 2003 event.getAnimationTrigger().increment(); 2004 post(new Runnable() { 2005 @Override 2006 public void run() { 2007 // Scroll the stack to the front to see the undocked task 2008 mAnimationHelper.startNewStackScrollAnimation(event.stack, 2009 event.getAnimationTrigger()); 2010 event.getAnimationTrigger().decrement(); 2011 } 2012 }); 2013 } 2014 } 2015 onBusEvent(ConfigurationChangedEvent event)2016 public final void onBusEvent(ConfigurationChangedEvent event) { 2017 if (event.fromDeviceOrientationChange) { 2018 mDisplayOrientation = Utilities.getAppConfiguration(mContext).orientation; 2019 mDisplayRect = Recents.getSystemServices().getDisplayRect(); 2020 2021 // Always stop the scroller, otherwise, we may continue setting the stack scroll to the 2022 // wrong bounds in the new layout 2023 mStackScroller.stopScroller(); 2024 } 2025 reloadOnConfigurationChange(); 2026 2027 // Notify the task views of the configuration change so they can reload their resources 2028 if (!event.fromMultiWindow) { 2029 mTmpTaskViews.clear(); 2030 mTmpTaskViews.addAll(getTaskViews()); 2031 mTmpTaskViews.addAll(mViewPool.getViews()); 2032 int taskViewCount = mTmpTaskViews.size(); 2033 for (int i = 0; i < taskViewCount; i++) { 2034 mTmpTaskViews.get(i).onConfigurationChanged(); 2035 } 2036 } 2037 2038 // Trigger a new layout and update to the initial state if necessary 2039 if (event.fromMultiWindow) { 2040 mInitialState = INITIAL_STATE_UPDATE_LAYOUT_ONLY; 2041 requestLayout(); 2042 } else if (event.fromDeviceOrientationChange) { 2043 mInitialState = INITIAL_STATE_UPDATE_ALL; 2044 requestLayout(); 2045 } 2046 } 2047 onBusEvent(RecentsGrowingEvent event)2048 public final void onBusEvent(RecentsGrowingEvent event) { 2049 mResetToInitialStateWhenResized = true; 2050 } 2051 reloadOnConfigurationChange()2052 public void reloadOnConfigurationChange() { 2053 mStableLayoutAlgorithm.reloadOnConfigurationChange(getContext()); 2054 mLayoutAlgorithm.reloadOnConfigurationChange(getContext()); 2055 } 2056 2057 /** 2058 * Starts an alpha animation on the freeform workspace background. 2059 */ animateFreeformWorkspaceBackgroundAlpha(int targetAlpha, AnimationProps animation)2060 private void animateFreeformWorkspaceBackgroundAlpha(int targetAlpha, 2061 AnimationProps animation) { 2062 if (mFreeformWorkspaceBackground.getAlpha() == targetAlpha) { 2063 return; 2064 } 2065 2066 Utilities.cancelAnimationWithoutCallbacks(mFreeformWorkspaceBackgroundAnimator); 2067 mFreeformWorkspaceBackgroundAnimator = ObjectAnimator.ofInt(mFreeformWorkspaceBackground, 2068 Utilities.DRAWABLE_ALPHA, mFreeformWorkspaceBackground.getAlpha(), targetAlpha); 2069 mFreeformWorkspaceBackgroundAnimator.setStartDelay( 2070 animation.getDuration(AnimationProps.ALPHA)); 2071 mFreeformWorkspaceBackgroundAnimator.setDuration( 2072 animation.getDuration(AnimationProps.ALPHA)); 2073 mFreeformWorkspaceBackgroundAnimator.setInterpolator( 2074 animation.getInterpolator(AnimationProps.ALPHA)); 2075 mFreeformWorkspaceBackgroundAnimator.start(); 2076 } 2077 2078 /** 2079 * Returns the insert index for the task in the current set of task views. If the given task 2080 * is already in the task view list, then this method returns the insert index assuming it 2081 * is first removed at the previous index. 2082 * 2083 * @param task the task we are finding the index for 2084 * @param taskIndex the index of the task in the stack 2085 */ findTaskViewInsertIndex(Task task, int taskIndex)2086 private int findTaskViewInsertIndex(Task task, int taskIndex) { 2087 if (taskIndex != -1) { 2088 List<TaskView> taskViews = getTaskViews(); 2089 boolean foundTaskView = false; 2090 int taskViewCount = taskViews.size(); 2091 for (int i = 0; i < taskViewCount; i++) { 2092 Task tvTask = taskViews.get(i).getTask(); 2093 if (tvTask == task) { 2094 foundTaskView = true; 2095 } else if (taskIndex < mStack.indexOfStackTask(tvTask)) { 2096 if (foundTaskView) { 2097 return i - 1; 2098 } else { 2099 return i; 2100 } 2101 } 2102 } 2103 } 2104 return -1; 2105 } 2106 2107 /** 2108 * Reads current system flags related to accessibility and screen pinning. 2109 */ readSystemFlags()2110 private void readSystemFlags() { 2111 SystemServicesProxy ssp = Recents.getSystemServices(); 2112 mTouchExplorationEnabled = ssp.isTouchExplorationEnabled(); 2113 mScreenPinningEnabled = ssp.getSystemSetting(getContext(), 2114 Settings.System.LOCK_TO_APP_ENABLED) != 0; 2115 } 2116 dump(String prefix, PrintWriter writer)2117 public void dump(String prefix, PrintWriter writer) { 2118 String innerPrefix = prefix + " "; 2119 String id = Integer.toHexString(System.identityHashCode(this)); 2120 2121 writer.print(prefix); writer.print(TAG); 2122 writer.print(" hasDefRelayout="); 2123 writer.print(mDeferredTaskViewLayoutAnimation != null ? "Y" : "N"); 2124 writer.print(" clipDirty="); writer.print(mTaskViewsClipDirty ? "Y" : "N"); 2125 writer.print(" awaitingFirstLayout="); writer.print(mAwaitingFirstLayout ? "Y" : "N"); 2126 writer.print(" initialState="); writer.print(mInitialState); 2127 writer.print(" inMeasureLayout="); writer.print(mInMeasureLayout ? "Y" : "N"); 2128 writer.print(" enterAnimCompleted="); writer.print(mEnterAnimationComplete ? "Y" : "N"); 2129 writer.print(" touchExplorationOn="); writer.print(mTouchExplorationEnabled ? "Y" : "N"); 2130 writer.print(" screenPinningOn="); writer.print(mScreenPinningEnabled ? "Y" : "N"); 2131 writer.print(" numIgnoreTasks="); writer.print(mIgnoreTasks.size()); 2132 writer.print(" numViewPool="); writer.print(mViewPool.getViews().size()); 2133 writer.print(" stableStackBounds="); writer.print(Utilities.dumpRect(mStableStackBounds)); 2134 writer.print(" stackBounds="); writer.print(Utilities.dumpRect(mStackBounds)); 2135 writer.print(" stableWindow="); writer.print(Utilities.dumpRect(mStableWindowRect)); 2136 writer.print(" window="); writer.print(Utilities.dumpRect(mWindowRect)); 2137 writer.print(" display="); writer.print(Utilities.dumpRect(mDisplayRect)); 2138 writer.print(" orientation="); writer.print(mDisplayOrientation); 2139 writer.print(" [0x"); writer.print(id); writer.print("]"); 2140 writer.println(); 2141 2142 if (mFocusedTask != null) { 2143 writer.print(innerPrefix); 2144 writer.print("Focused task: "); 2145 mFocusedTask.dump("", writer); 2146 } 2147 2148 mLayoutAlgorithm.dump(innerPrefix, writer); 2149 mStackScroller.dump(innerPrefix, writer); 2150 } 2151 } 2152