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