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