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