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