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