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