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