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