Home | History | Annotate | Download | only in views
      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 
    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
    328     protected void onAttachedToWindow() {
    329         EventBus.getDefault().register(this, RecentsActivity.EVENT_BUS_PRIORITY + 1);
    330         super.onAttachedToWindow();
    331         readSystemFlags();
    332     }
    333 
    334     @Override
    335     protected void onDetachedFromWindow() {
    336         super.onDetachedFromWindow();
    337         EventBus.getDefault().unregister(this);
    338     }
    339 
    340     /**
    341      * Called from RecentsActivity when it is relaunched.
    342      */
    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      */
    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. */
    393     public TaskStack getStack() {
    394         return mStack;
    395     }
    396 
    397     /**
    398      * Updates this TaskStackView to the initial state.
    399      */
    400     public void updateToInitialState() {
    401         mStackScroller.setStackScrollToInitialState();
    402         mLayoutAlgorithm.setTaskOverridesForInitialState(mStack, false /* ignoreScrollToFront */);
    403     }
    404 
    405     /** Updates the list of task views */
    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 */
    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      */
    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      */
    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. */
    459     public TaskStackLayoutAlgorithm getStackAlgorithm() {
    460         return mLayoutAlgorithm;
    461     }
    462 
    463     /** Returns the grid algorithm for this task stack. */
    464     public TaskGridLayoutAlgorithm getGridAlgorithm() {
    465         return mLayoutAlgorithm.mTaskGridLayoutAlgorithm;
    466     }
    467 
    468     /**
    469      * Returns the touch handler for this task stack.
    470      */
    471     public TaskStackViewTouchHandler getTouchHandler() {
    472         return mTouchHandler;
    473     }
    474 
    475     /**
    476      * Adds a task to the ignored set.
    477      */
    478     void addIgnoreTask(Task task) {
    479         mIgnoreTasks.add(task.key);
    480     }
    481 
    482     /**
    483      * Removes a task from the ignored set.
    484      */
    485     void removeIgnoreTask(Task task) {
    486         mIgnoreTasks.remove(task.key);
    487     }
    488 
    489     /**
    490      * Returns whether the specified {@param task} is ignored.
    491      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    802     void cancelDeferredTaskViewLayoutAnimation() {
    803         mDeferredTaskViewLayoutAnimation = null;
    804     }
    805 
    806     /**
    807      * Cancels all {@link TaskView} animations.
    808      */
    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      */
    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 
    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      */
    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      */
    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. */
    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      */
    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      */
    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      */
   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      */
   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      */
   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      */
   1110     public Task getFocusedTask() {
   1111         return mFocusedTask;
   1112     }
   1113 
   1114     /**
   1115      * Returns the accessibility focused task.
   1116      */
   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
   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
   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
   1172     public CharSequence getAccessibilityClassName() {
   1173         return ScrollView.class.getName();
   1174     }
   1175 
   1176     @Override
   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
   1201     public boolean onInterceptTouchEvent(MotionEvent ev) {
   1202         return mTouchHandler.onInterceptTouchEvent(ev);
   1203     }
   1204 
   1205     @Override
   1206     public boolean onTouchEvent(MotionEvent ev) {
   1207         return mTouchHandler.onTouchEvent(ev);
   1208     }
   1209 
   1210     @Override
   1211     public boolean onGenericMotionEvent(MotionEvent ev) {
   1212         return mTouchHandler.onGenericMotionEvent(ev);
   1213     }
   1214 
   1215     @Override
   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      */
   1237     public TaskStackLayoutAlgorithm.VisibilityReport computeStackVisibilityReport() {
   1238         return mLayoutAlgorithm.computeStackVisibilityReport(mStack.getStackTasks());
   1239     }
   1240 
   1241     /**
   1242      * Updates the system insets.
   1243      */
   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
   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      */
   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
   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      */
   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. */
   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 
   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      */
   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
   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
   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      */
   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
   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
   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
   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
   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
   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
   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
   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
   1677     public boolean hasPreferredData(TaskView tv, Task preferredData) {
   1678         return (tv.getTask() == preferredData);
   1679     }
   1680 
   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 
   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 
   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 
   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
   1739     public void onTaskViewClipStateChanged(TaskView tv) {
   1740         if (!mTaskViewsClipDirty) {
   1741             mTaskViewsClipDirty = true;
   1742             invalidate();
   1743         }
   1744     }
   1745 
   1746     /**** TaskStackLayoutAlgorithm.TaskStackLayoutAlgorithmCallbacks ****/
   1747 
   1748     @Override
   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
   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 
   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 
   1802     public final void onBusEvent(LaunchTaskEvent event) {
   1803         // Cancel any doze triggers once a task is launched
   1804         mUIDozeTrigger.stopDozing();
   1805     }
   1806 
   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 
   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 
   1853     public final void onBusEvent(LaunchTaskStartedEvent event) {
   1854         mAnimationHelper.startLaunchTaskAnimation(event.taskView, event.screenPinningRequested,
   1855                 event.getAnimationTrigger());
   1856     }
   1857 
   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 
   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 
   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 
   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 
   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 
   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 
   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 
   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 
   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 
   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 
   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 
   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 
   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 
   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 
   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 
   2133     public final void onBusEvent(EnterRecentsWindowAnimationCompletedEvent event) {
   2134         mEnterAnimationComplete = true;
   2135         tryStartEnterAnimation();
   2136     }
   2137 
   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 
   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 
   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 
   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 
   2246     public final void onBusEvent(RecentsGrowingEvent event) {
   2247         mResetToInitialStateWhenResized = true;
   2248     }
   2249 
   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 
   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 
   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      */
   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      */
   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 
   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      */
   2366     public boolean useGridLayout() {
   2367         return mLayoutAlgorithm.useGridLayout();
   2368     }
   2369 
   2370     /**
   2371      * Reads current system flags related to accessibility and screen pinning.
   2372      */
   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 
   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 
   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