TaskStackView.java revision 9b5c847a19bf7dbfe3fe00f11cbbdf581794fccc
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 android.animation.ObjectAnimator;
20import android.animation.ValueAnimator;
21import android.content.ComponentName;
22import android.content.Context;
23import android.content.res.Resources;
24import android.graphics.Canvas;
25import android.graphics.Rect;
26import android.graphics.drawable.Drawable;
27import android.graphics.drawable.GradientDrawable;
28import android.os.Bundle;
29import android.os.Parcelable;
30import android.provider.Settings;
31import android.util.ArrayMap;
32import android.util.ArraySet;
33import android.util.MutableBoolean;
34import android.view.LayoutInflater;
35import android.view.MotionEvent;
36import android.view.View;
37import android.view.accessibility.AccessibilityEvent;
38import android.view.accessibility.AccessibilityNodeInfo;
39import android.view.animation.Interpolator;
40import android.widget.FrameLayout;
41
42import com.android.systemui.Interpolators;
43import com.android.systemui.R;
44import com.android.systemui.recents.Recents;
45import com.android.systemui.recents.RecentsActivity;
46import com.android.systemui.recents.RecentsActivityLaunchState;
47import com.android.systemui.recents.RecentsConfiguration;
48import com.android.systemui.recents.RecentsDebugFlags;
49import com.android.systemui.recents.events.EventBus;
50import com.android.systemui.recents.events.activity.CancelEnterRecentsWindowAnimationEvent;
51import com.android.systemui.recents.events.activity.DismissRecentsToHomeAnimationStarted;
52import com.android.systemui.recents.events.activity.EnterRecentsTaskStackAnimationCompletedEvent;
53import com.android.systemui.recents.events.activity.EnterRecentsWindowAnimationCompletedEvent;
54import com.android.systemui.recents.events.activity.HideHistoryButtonEvent;
55import com.android.systemui.recents.events.activity.HideHistoryEvent;
56import com.android.systemui.recents.events.activity.IterateRecentsEvent;
57import com.android.systemui.recents.events.activity.LaunchTaskEvent;
58import com.android.systemui.recents.events.activity.LaunchTaskStartedEvent;
59import com.android.systemui.recents.events.activity.PackagesChangedEvent;
60import com.android.systemui.recents.events.activity.ShowHistoryButtonEvent;
61import com.android.systemui.recents.events.activity.ShowHistoryEvent;
62import com.android.systemui.recents.events.component.RecentsVisibilityChangedEvent;
63import com.android.systemui.recents.events.ui.AllTaskViewsDismissedEvent;
64import com.android.systemui.recents.events.ui.DeleteTaskDataEvent;
65import com.android.systemui.recents.events.ui.DismissTaskViewEvent;
66import com.android.systemui.recents.events.ui.StackViewScrolledEvent;
67import com.android.systemui.recents.events.ui.TaskViewDismissedEvent;
68import com.android.systemui.recents.events.ui.UpdateFreeformTaskViewVisibilityEvent;
69import com.android.systemui.recents.events.ui.UserInteractionEvent;
70import com.android.systemui.recents.events.ui.dragndrop.DragDropTargetChangedEvent;
71import com.android.systemui.recents.events.ui.dragndrop.DragEndEvent;
72import com.android.systemui.recents.events.ui.dragndrop.DragStartEvent;
73import com.android.systemui.recents.events.ui.dragndrop.DragStartInitializeDropTargetsEvent;
74import com.android.systemui.recents.events.ui.focus.DismissFocusedTaskViewEvent;
75import com.android.systemui.recents.events.ui.focus.FocusNextTaskViewEvent;
76import com.android.systemui.recents.events.ui.focus.FocusPreviousTaskViewEvent;
77import com.android.systemui.recents.misc.DozeTrigger;
78import com.android.systemui.recents.misc.ReferenceCountedTrigger;
79import com.android.systemui.recents.misc.SystemServicesProxy;
80import com.android.systemui.recents.misc.Utilities;
81import com.android.systemui.recents.model.Task;
82import com.android.systemui.recents.model.TaskStack;
83
84import java.util.ArrayList;
85import java.util.List;
86
87import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
88import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
89import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
90
91
92/* The visual representation of a task stack view */
93public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCallbacks,
94        TaskView.TaskViewCallbacks, TaskStackViewScroller.TaskStackViewScrollerCallbacks,
95        TaskStackLayoutAlgorithm.TaskStackLayoutAlgorithmCallbacks,
96        ViewPool.ViewPoolConsumer<TaskView, Task> {
97
98    private final static String KEY_SAVED_STATE_SUPER = "saved_instance_state_super";
99    private final static String KEY_SAVED_STATE_LAYOUT_FOCUSED_STATE =
100            "saved_instance_state_layout_focused_state";
101    private final static String KEY_SAVED_STATE_LAYOUT_STACK_SCROLL =
102            "saved_instance_state_layout_stack_scroll";
103
104    // The thresholds at which to show/hide the history button.
105    private static final float SHOW_HISTORY_BUTTON_SCROLL_THRESHOLD = 0.3f;
106    private static final float HIDE_HISTORY_BUTTON_SCROLL_THRESHOLD = 0.3f;
107
108    public static final int DEFAULT_SYNC_STACK_DURATION = 200;
109    private static final int DRAG_SCALE_DURATION = 175;
110    private static final float DRAG_SCALE_FACTOR = 1.05f;
111
112    private static final ArraySet<Task.TaskKey> EMPTY_TASK_SET = new ArraySet<>();
113
114    TaskStack mStack;
115    TaskStackLayoutAlgorithm mLayoutAlgorithm;
116    TaskStackViewScroller mStackScroller;
117    TaskStackViewTouchHandler mTouchHandler;
118    TaskStackAnimationHelper mAnimationHelper;
119    GradientDrawable mFreeformWorkspaceBackground;
120    ObjectAnimator mFreeformWorkspaceBackgroundAnimator;
121    ViewPool<TaskView, Task> mViewPool;
122
123    ArrayList<TaskView> mTaskViews = new ArrayList<>();
124    ArrayList<TaskViewTransform> mCurrentTaskTransforms = new ArrayList<>();
125    ArraySet<Task.TaskKey> mIgnoreTasks = new ArraySet<>();
126    TaskViewAnimation mDeferredTaskViewLayoutAnimation = null;
127
128    DozeTrigger mUIDozeTrigger;
129    Task mFocusedTask;
130
131    int mTaskCornerRadiusPx;
132    private int mDividerSize;
133    private int mStartTimerIndicatorDuration;
134
135    boolean mTaskViewsClipDirty = true;
136    boolean mAwaitingFirstLayout = true;
137    boolean mEnterAnimationComplete = false;
138    boolean mTouchExplorationEnabled;
139    boolean mScreenPinningEnabled;
140
141    // The stable stack bounds are the full bounds that we were measured with from RecentsView
142    Rect mStableStackBounds = new Rect();
143    // The current stack bounds are dynamic and may change as the user drags and drops
144    Rect mStackBounds = new Rect();
145    int[] mTmpVisibleRange = new int[2];
146    Rect mTmpRect = new Rect();
147    ArrayMap<Task.TaskKey, TaskView> mTmpTaskViewMap = new ArrayMap<>();
148    List<TaskView> mTmpTaskViews = new ArrayList<>();
149    TaskViewTransform mTmpTransform = new TaskViewTransform();
150    LayoutInflater mInflater;
151
152    // A convenience update listener to request updating clipping of tasks
153    private ValueAnimator.AnimatorUpdateListener mRequestUpdateClippingListener =
154            new ValueAnimator.AnimatorUpdateListener() {
155                @Override
156                public void onAnimationUpdate(ValueAnimator animation) {
157                    if (!mTaskViewsClipDirty) {
158                        mTaskViewsClipDirty = true;
159                        invalidate();
160                    }
161                }
162            };
163
164    // The drop targets for a task drag
165    private DropTarget mFreeformWorkspaceDropTarget = new DropTarget() {
166        @Override
167        public boolean acceptsDrop(int x, int y, int width, int height, boolean isCurrentTarget) {
168            // This drop target has a fixed bounds and should be checked last, so just fall through
169            // if it is the current target
170            if (!isCurrentTarget) {
171                return mLayoutAlgorithm.mFreeformRect.contains(x, y);
172            }
173            return false;
174        }
175    };
176
177    private DropTarget mStackDropTarget = new DropTarget() {
178        @Override
179        public boolean acceptsDrop(int x, int y, int width, int height, boolean isCurrentTarget) {
180            // This drop target has a fixed bounds and should be checked last, so just fall through
181            // if it is the current target
182            if (!isCurrentTarget) {
183                return mLayoutAlgorithm.mStackRect.contains(x, y);
184            }
185            return false;
186        }
187    };
188
189    public TaskStackView(Context context, TaskStack stack) {
190        super(context);
191        SystemServicesProxy ssp = Recents.getSystemServices();
192        Resources res = context.getResources();
193
194        // Set the stack first
195        setStack(stack);
196        mViewPool = new ViewPool<>(context, this);
197        mInflater = LayoutInflater.from(context);
198        mLayoutAlgorithm = new TaskStackLayoutAlgorithm(context, this);
199        mStackScroller = new TaskStackViewScroller(context, this, mLayoutAlgorithm);
200        mTouchHandler = new TaskStackViewTouchHandler(context, this, mStackScroller);
201        mAnimationHelper = new TaskStackAnimationHelper(context, this);
202        mTaskCornerRadiusPx = res.getDimensionPixelSize(
203                R.dimen.recents_task_view_rounded_corners_radius);
204        mDividerSize = ssp.getDockedDividerSize(context);
205
206        int taskBarDismissDozeDelaySeconds = getResources().getInteger(
207                R.integer.recents_task_bar_dismiss_delay_seconds);
208        mUIDozeTrigger = new DozeTrigger(taskBarDismissDozeDelaySeconds, new Runnable() {
209            @Override
210            public void run() {
211                // Show the task bar dismiss buttons
212                List<TaskView> taskViews = getTaskViews();
213                int taskViewCount = taskViews.size();
214                for (int i = 0; i < taskViewCount; i++) {
215                    TaskView tv = taskViews.get(i);
216                    tv.startNoUserInteractionAnimation();
217                }
218            }
219        });
220        setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
221
222        mFreeformWorkspaceBackground = (GradientDrawable) getContext().getDrawable(
223                R.drawable.recents_freeform_workspace_bg);
224        mFreeformWorkspaceBackground.setCallback(this);
225        if (ssp.hasFreeformWorkspaceSupport()) {
226            mFreeformWorkspaceBackground.setColor(
227                    getContext().getColor(R.color.recents_freeform_workspace_bg_color));
228        }
229    }
230
231    @Override
232    protected void onAttachedToWindow() {
233        EventBus.getDefault().register(this, RecentsActivity.EVENT_BUS_PRIORITY + 1);
234        super.onAttachedToWindow();
235        readSystemFlags();
236    }
237
238    @Override
239    protected void onDetachedFromWindow() {
240        super.onDetachedFromWindow();
241        EventBus.getDefault().unregister(this);
242    }
243
244    /** Sets the task stack */
245    void setStack(TaskStack stack) {
246        // Set the new stack
247        mStack = stack;
248        if (mStack != null) {
249            mStack.setCallbacks(this);
250        }
251        // Layout again with the new stack
252        requestLayout();
253    }
254
255    /** Returns the task stack. */
256    TaskStack getStack() {
257        return mStack;
258    }
259
260    /** Updates the list of task views */
261    void updateTaskViewsList() {
262        mTaskViews.clear();
263        int childCount = getChildCount();
264        for (int i = 0; i < childCount; i++) {
265            View v = getChildAt(i);
266            if (v instanceof TaskView) {
267                mTaskViews.add((TaskView) v);
268            }
269        }
270    }
271
272    /** Gets the list of task views */
273    List<TaskView> getTaskViews() {
274        return mTaskViews;
275    }
276
277    /**
278     * Returns the front most task view.
279     *
280     * @param stackTasksOnly if set, will return the front most task view in the stack (by default
281     *                       the front most task view will be freeform since they are placed above
282     *                       stack tasks)
283     */
284    private TaskView getFrontMostTaskView(boolean stackTasksOnly) {
285        List<TaskView> taskViews = getTaskViews();
286        int taskViewCount = taskViews.size();
287        for (int i = taskViewCount - 1; i >= 0; i--) {
288            TaskView tv = taskViews.get(i);
289            Task task = tv.getTask();
290            if (stackTasksOnly && task.isFreeformTask()) {
291                continue;
292            }
293            return tv;
294        }
295        return null;
296    }
297
298    /**
299     * Finds the child view given a specific {@param task}.
300     */
301    public TaskView getChildViewForTask(Task t) {
302        List<TaskView> taskViews = getTaskViews();
303        int taskViewCount = taskViews.size();
304        for (int i = 0; i < taskViewCount; i++) {
305            TaskView tv = taskViews.get(i);
306            if (tv.getTask() == t) {
307                return tv;
308            }
309        }
310        return null;
311    }
312
313    /** Resets this TaskStackView for reuse. */
314    void reset() {
315        // Reset the focused task
316        resetFocusedTask(getFocusedTask());
317
318        // Return all the views to the pool
319        List<TaskView> taskViews = getTaskViews();
320        int taskViewCount = taskViews.size();
321        for (int i = taskViewCount - 1; i >= 0; i--) {
322            mViewPool.returnViewToPool(taskViews.get(i));
323        }
324
325        // Mark each task view for relayout
326        List<TaskView> poolViews = mViewPool.getViews();
327        for (TaskView tv : poolViews) {
328            tv.reset();
329        }
330
331        // Reset the stack state
332        mStack.reset();
333        mTaskViewsClipDirty = true;
334        mAwaitingFirstLayout = true;
335        mEnterAnimationComplete = false;
336        mUIDozeTrigger.stopDozing();
337        mUIDozeTrigger.resetTrigger();
338        mStackScroller.reset();
339        mLayoutAlgorithm.reset();
340        readSystemFlags();
341        requestLayout();
342    }
343
344    /** Returns the stack algorithm for this task stack. */
345    public TaskStackLayoutAlgorithm getStackAlgorithm() {
346        return mLayoutAlgorithm;
347    }
348
349    /**
350     * Adds a task to the ignored set.
351     */
352    void addIgnoreTask(Task task) {
353        mIgnoreTasks.add(task.key);
354    }
355
356    /**
357     * Removes a task from the ignored set.
358     */
359    void removeIgnoreTask(Task task) {
360        mIgnoreTasks.remove(task.key);
361    }
362
363    /**
364     * Returns whether the specified {@param task} is ignored.
365     */
366    boolean isIgnoredTask(Task task) {
367        return mIgnoreTasks.contains(task.key);
368    }
369
370    /**
371     * Computes the task transforms at the current stack scroll for all visible tasks. If a valid
372     * target stack scroll is provided (ie. is different than {@param curStackScroll}), then the
373     * visible range includes all tasks at the target stack scroll. This is useful for ensure that
374     * all views necessary for a transition or animation will be visible at the start.
375     *
376     * This call ignores freeform tasks.
377     *
378     * @param taskTransforms The set of task view transforms to reuse, this list will be sized to
379     *                       match the size of {@param tasks}
380     * @param tasks The set of tasks for which to generate transforms
381     * @param curStackScroll The current stack scroll
382     * @param targetStackScroll The stack scroll that we anticipate we are going to be scrolling to.
383     *                          The range of the union of the visible views at the current and
384     *                          target stack scrolls will be returned.
385     * @param ignoreTasksSet The set of tasks to skip for purposes of calculaing the visible range.
386     *                       Transforms will still be calculated for the ignore tasks.
387     */
388    boolean computeVisibleTaskTransforms(ArrayList<TaskViewTransform> taskTransforms,
389            ArrayList<Task> tasks, float curStackScroll, float targetStackScroll,
390            int[] visibleRangeOut, ArraySet<Task.TaskKey> ignoreTasksSet) {
391        int taskCount = tasks.size();
392        int frontMostVisibleIndex = -1;
393        int backMostVisibleIndex = -1;
394        boolean useTargetStackScroll = Float.compare(curStackScroll, targetStackScroll) != 0;
395
396        // We can reuse the task transforms where possible to reduce object allocation
397        Utilities.matchTaskListSize(tasks, taskTransforms);
398
399        // Update the stack transforms
400        TaskViewTransform frontTransform = null;
401        TaskViewTransform frontTransformAtTarget = null;
402        TaskViewTransform transform = null;
403        TaskViewTransform transformAtTarget = null;
404        for (int i = taskCount - 1; i >= 0; i--) {
405            Task task = tasks.get(i);
406
407            // Calculate the current and (if necessary) the target transform for the task
408            transform = mLayoutAlgorithm.getStackTransform(task, curStackScroll,
409                    taskTransforms.get(i), frontTransform);
410            if (useTargetStackScroll && !transform.visible) {
411                // If we have a target stack scroll and the task is not currently visible, then we
412                // just update the transform at the new scroll
413                // TODO: Optimize this
414                transformAtTarget = mLayoutAlgorithm.getStackTransform(task,
415                        targetStackScroll, new TaskViewTransform(), frontTransformAtTarget);
416                if (transformAtTarget.visible) {
417                    transform.copyFrom(transformAtTarget);
418                }
419            }
420
421            // For ignore tasks, only calculate the stack transform and skip the calculation of the
422            // visible stack indices
423            if (ignoreTasksSet.contains(task.key)) {
424                continue;
425            }
426
427            // For freeform tasks, only calculate the stack transform and skip the calculation of
428            // the visible stack indices
429            if (task.isFreeformTask()) {
430                continue;
431            }
432
433            if (transform.visible) {
434                if (frontMostVisibleIndex < 0) {
435                    frontMostVisibleIndex = i;
436                }
437                backMostVisibleIndex = i;
438            } else {
439                if (backMostVisibleIndex != -1) {
440                    // We've reached the end of the visible range, so going down the rest of the
441                    // stack, we can just reset the transforms accordingly
442                    while (i >= 0) {
443                        taskTransforms.get(i).reset();
444                        i--;
445                    }
446                    break;
447                }
448            }
449
450            frontTransform = transform;
451            frontTransformAtTarget = transformAtTarget;
452        }
453        if (visibleRangeOut != null) {
454            visibleRangeOut[0] = frontMostVisibleIndex;
455            visibleRangeOut[1] = backMostVisibleIndex;
456        }
457        return frontMostVisibleIndex != -1 && backMostVisibleIndex != -1;
458    }
459
460    /**
461     * Binds the visible {@link TaskView}s at the given target scroll.
462     *
463     * @see #bindVisibleTaskViews(float, ArraySet<Task.TaskKey>)
464     */
465    void bindVisibleTaskViews(float targetStackScroll) {
466        bindVisibleTaskViews(targetStackScroll, mIgnoreTasks);
467    }
468
469    /**
470     * Synchronizes the set of children {@link TaskView}s to match the visible set of tasks in the
471     * current {@link TaskStack}. This call does not continue on to update their position to the
472     * computed {@link TaskViewTransform}s of the visible range, but only ensures that they will
473     * be added/removed from the view hierarchy and placed in the correct Z order and initial
474     * position (if not currently on screen).
475     *
476     * @param targetStackScroll If provided, will ensure that the set of visible {@link TaskView}s
477     *                          includes those visible at the current stack scroll, and all at the
478     *                          target stack scroll.
479     * @param ignoreTasksSet The set of tasks to ignore in this rebinding of the visible
480     *                       {@link TaskView}s
481     */
482    void bindVisibleTaskViews(float targetStackScroll, ArraySet<Task.TaskKey> ignoreTasksSet) {
483        final int[] visibleStackRange = mTmpVisibleRange;
484
485        // Get all the task transforms
486        final ArrayList<Task> tasks = mStack.getStackTasks();
487        final boolean isValidVisibleRange = computeVisibleTaskTransforms(mCurrentTaskTransforms,
488                tasks, mStackScroller.getStackScroll(), targetStackScroll, visibleStackRange,
489                ignoreTasksSet);
490
491        // Return all the invisible children to the pool
492        mTmpTaskViewMap.clear();
493        List<TaskView> taskViews = getTaskViews();
494        int lastFocusedTaskIndex = -1;
495        int taskViewCount = taskViews.size();
496        for (int i = taskViewCount - 1; i >= 0; i--) {
497            TaskView tv = taskViews.get(i);
498            Task task = tv.getTask();
499            int taskIndex = mStack.indexOfStackTask(task);
500
501            // Skip ignored tasks
502            if (ignoreTasksSet.contains(task.key)) {
503                continue;
504            }
505
506            if (task.isFreeformTask() ||
507                    visibleStackRange[1] <= taskIndex && taskIndex <= visibleStackRange[0]) {
508                mTmpTaskViewMap.put(task.key, tv);
509            } else {
510                if (mTouchExplorationEnabled) {
511                    lastFocusedTaskIndex = taskIndex;
512                    resetFocusedTask(task);
513                }
514                mViewPool.returnViewToPool(tv);
515            }
516        }
517
518        // Pick up all the newly visible children
519        int lastVisStackIndex = isValidVisibleRange ? visibleStackRange[1] : 0;
520        for (int i = tasks.size() - 1; i >= lastVisStackIndex; i--) {
521            Task task = tasks.get(i);
522            TaskViewTransform transform = mCurrentTaskTransforms.get(i);
523
524            // Skip ignored tasks
525            if (ignoreTasksSet.contains(task.key)) {
526                continue;
527            }
528
529            // Skip the invisible non-freeform stack tasks
530            if (i > visibleStackRange[0] && !task.isFreeformTask()) {
531                continue;
532            }
533
534            TaskView tv = mTmpTaskViewMap.get(task.key);
535            if (tv == null) {
536                tv = mViewPool.pickUpViewFromPool(task, task);
537                if (task.isFreeformTask()) {
538                    tv.updateViewPropertiesToTaskTransform(transform, TaskViewAnimation.IMMEDIATE,
539                            mRequestUpdateClippingListener);
540                } else {
541                    if (Float.compare(transform.p, 0f) <= 0) {
542                        tv.updateViewPropertiesToTaskTransform(
543                                mLayoutAlgorithm.getBackOfStackTransform(),
544                                TaskViewAnimation.IMMEDIATE, mRequestUpdateClippingListener);
545                    } else {
546                        tv.updateViewPropertiesToTaskTransform(
547                                mLayoutAlgorithm.getFrontOfStackTransform(),
548                                TaskViewAnimation.IMMEDIATE, mRequestUpdateClippingListener);
549                    }
550                }
551            } else {
552                // Reattach it in the right z order
553                final int taskIndex = mStack.indexOfStackTask(task);
554                final int insertIndex = findTaskViewInsertIndex(task, taskIndex);
555                if (insertIndex != getTaskViews().indexOf(tv)){
556                    detachViewFromParent(tv);
557                    attachViewToParent(tv, insertIndex, tv.getLayoutParams());
558                    updateTaskViewsList();
559                }
560            }
561        }
562
563        // Update the focus if the previous focused task was returned to the view pool
564        if (lastFocusedTaskIndex != -1) {
565            if (lastFocusedTaskIndex < visibleStackRange[1]) {
566                setFocusedTask(visibleStackRange[1], false /* scrollToTask */,
567                        true /* requestViewFocus */);
568            } else {
569                setFocusedTask(visibleStackRange[0], false /* scrollToTask */,
570                        true /* requestViewFocus */);
571            }
572        }
573    }
574
575    /**
576     * Relayout the the visible {@link TaskView}s to their current transforms as specified by the
577     * {@link TaskStackLayoutAlgorithm} with the given {@param animation}. This call cancels any
578     * animations that are current running on those task views, and will ensure that the children
579     * {@link TaskView}s will match the set of visible tasks in the stack.
580     *
581     * @see #relayoutTaskViews(TaskViewAnimation, ArraySet<Task.TaskKey>)
582     */
583    void relayoutTaskViews(TaskViewAnimation animation) {
584        relayoutTaskViews(animation, mIgnoreTasks);
585    }
586
587    /**
588     * Relayout the the visible {@link TaskView}s to their current transforms as specified by the
589     * {@link TaskStackLayoutAlgorithm} with the given {@param animation}. This call cancels any
590     * animations that are current running on those task views, and will ensure that the children
591     * {@link TaskView}s will match the set of visible tasks in the stack.
592     *
593     * @param ignoreTasksSet the set of tasks to ignore in the relayout
594     */
595    void relayoutTaskViews(TaskViewAnimation animation, ArraySet<Task.TaskKey> ignoreTasksSet) {
596        // If we had a deferred animation, cancel that
597        mDeferredTaskViewLayoutAnimation = null;
598
599        // Cancel all task view animations
600        cancelAllTaskViewAnimations();
601
602        // Synchronize the current set of TaskViews
603        bindVisibleTaskViews(mStackScroller.getStackScroll(), ignoreTasksSet);
604
605        // Animate them to their final transforms with the given animation
606        List<TaskView> taskViews = getTaskViews();
607        int taskViewCount = taskViews.size();
608        for (int i = 0; i < taskViewCount; i++) {
609            final TaskView tv = taskViews.get(i);
610            final int taskIndex = mStack.indexOfStackTask(tv.getTask());
611            final TaskViewTransform transform = mCurrentTaskTransforms.get(taskIndex);
612
613            if (ignoreTasksSet.contains(tv.getTask().key)) {
614                continue;
615            }
616
617            updateTaskViewToTransform(tv, transform, animation);
618        }
619    }
620
621    /**
622     * Posts an update to synchronize the {@link TaskView}s with the stack on the next frame.
623     */
624    void relayoutTaskViewsOnNextFrame(TaskViewAnimation animation) {
625        mDeferredTaskViewLayoutAnimation = animation;
626        invalidate();
627    }
628
629    /**
630     * Called to update a specific {@link TaskView} to a given {@link TaskViewTransform} with a
631     * given set of {@link TaskViewAnimation} properties.
632     */
633    public void updateTaskViewToTransform(TaskView taskView, TaskViewTransform transform,
634            TaskViewAnimation animation) {
635        taskView.updateViewPropertiesToTaskTransform(transform, animation,
636                mRequestUpdateClippingListener);
637    }
638
639    /**
640     * Returns the current task transforms of all tasks, falling back to the stack layout if there
641     * is no {@link TaskView} for the task.
642     */
643    public void getCurrentTaskTransforms(ArrayList<Task> tasks,
644            ArrayList<TaskViewTransform> transformsOut) {
645        Utilities.matchTaskListSize(tasks, transformsOut);
646        for (int i = tasks.size() - 1; i >= 0; i--) {
647            Task task = tasks.get(i);
648            TaskViewTransform transform = transformsOut.get(i);
649            TaskView tv = getChildViewForTask(task);
650            if (tv != null) {
651                transform.fillIn(tv);
652            } else {
653                mLayoutAlgorithm.getStackTransform(task, mStackScroller.getStackScroll(),
654                        transform, null);
655            }
656            transform.visible = true;
657        }
658    }
659
660    /**
661     * Returns the task transforms for all the tasks in the stack if the stack was at the given
662     * {@param stackScroll}.
663     */
664    public void getLayoutTaskTransforms(float stackScroll, ArrayList<Task> tasks,
665            ArrayList<TaskViewTransform> transformsOut) {
666        Utilities.matchTaskListSize(tasks, transformsOut);
667        for (int i = tasks.size() - 1; i >= 0; i--) {
668            Task task = tasks.get(i);
669            TaskViewTransform transform = transformsOut.get(i);
670            mLayoutAlgorithm.getStackTransform(task, stackScroll, transform, null);
671            transform.visible = true;
672        }
673    }
674
675    /**
676     * Cancels all {@link TaskView} animations.
677     *
678     * @see #cancelAllTaskViewAnimations(ArraySet<Task.TaskKey>)
679     */
680    void cancelAllTaskViewAnimations() {
681        cancelAllTaskViewAnimations(mIgnoreTasks);
682    }
683
684    /**
685     * Cancels all {@link TaskView} animations.
686     *
687     * @param ignoreTasksSet The set of tasks to continue running their animations.
688     */
689    void cancelAllTaskViewAnimations(ArraySet<Task.TaskKey> ignoreTasksSet) {
690        List<TaskView> taskViews = getTaskViews();
691        for (int i = taskViews.size() - 1; i >= 0; i--) {
692            final TaskView tv = taskViews.get(i);
693            if (!ignoreTasksSet.contains(tv.getTask().key)) {
694                tv.cancelTransformAnimation();
695            }
696        }
697    }
698
699    /**
700     * Updates the clip for each of the task views from back to front.
701     */
702    private void clipTaskViews() {
703        RecentsConfiguration config = Recents.getConfiguration();
704
705        // Update the clip on each task child
706        List<TaskView> taskViews = getTaskViews();
707        TaskView tmpTv = null;
708        TaskView prevVisibleTv = null;
709        int taskViewCount = taskViews.size();
710        for (int i = 0; i < taskViewCount; i++) {
711            TaskView tv = taskViews.get(i);
712            TaskView frontTv = null;
713            int clipBottom = 0;
714
715            if (mIgnoreTasks.contains(tv.getTask().key)) {
716                // For each of the ignore tasks, update the translationZ of its TaskView to be
717                // between the translationZ of the tasks immediately underneath it
718                if (prevVisibleTv != null) {
719                    tv.setTranslationZ(Math.max(tv.getTranslationZ(),
720                            prevVisibleTv.getTranslationZ() + 0.1f));
721                }
722            }
723
724            if (i < (taskViewCount - 1) && tv.shouldClipViewInStack()) {
725                // Find the next view to clip against
726                for (int j = i + 1; j < taskViewCount; j++) {
727                    tmpTv = taskViews.get(j);
728
729                    if (tmpTv.shouldClipViewInStack()) {
730                        frontTv = tmpTv;
731                        break;
732                    }
733                }
734
735                // Clip against the next view, this is just an approximation since we are
736                // stacked and we can make assumptions about the visibility of the this
737                // task relative to the ones in front of it.
738                if (frontTv != null) {
739                    float taskBottom = tv.getBottom();
740                    float frontTaskTop = frontTv.getTop();
741                    if (frontTaskTop < taskBottom) {
742                        // Map the stack view space coordinate (the rects) to view space
743                        clipBottom = (int) (taskBottom - frontTaskTop) - mTaskCornerRadiusPx;
744                    }
745                }
746            }
747            tv.getViewBounds().setClipBottom(clipBottom);
748            if (!config.useHardwareLayers) {
749                tv.mThumbnailView.updateThumbnailVisibility(clipBottom - tv.getPaddingBottom());
750            }
751            prevVisibleTv = tv;
752        }
753        mTaskViewsClipDirty = false;
754    }
755
756    /**
757     * Updates the layout algorithm min and max virtual scroll bounds.
758     *
759     * @see #updateLayoutAlgorithm(boolean, ArraySet<Task.TaskKey>)
760     */
761    void updateLayoutAlgorithm(boolean boundScrollToNewMinMax) {
762        updateLayoutAlgorithm(boundScrollToNewMinMax, mIgnoreTasks);
763    }
764
765    /**
766     * Updates the min and max virtual scroll bounds.
767     *
768     * @param ignoreTasksSet the set of tasks to ignore in the relayout
769     */
770    void updateLayoutAlgorithm(boolean boundScrollToNewMinMax,
771            ArraySet<Task.TaskKey> ignoreTasksSet) {
772        // Compute the min and max scroll values
773        mLayoutAlgorithm.update(mStack, ignoreTasksSet);
774
775        // Update the freeform workspace background
776        SystemServicesProxy ssp = Recents.getSystemServices();
777        if (ssp.hasFreeformWorkspaceSupport()) {
778            mTmpRect.set(mLayoutAlgorithm.mFreeformRect);
779            mFreeformWorkspaceBackground.setBounds(mTmpRect);
780        }
781
782        if (boundScrollToNewMinMax) {
783            mStackScroller.boundScroll();
784        }
785    }
786
787    /** Returns the scroller. */
788    public TaskStackViewScroller getScroller() {
789        return mStackScroller;
790    }
791
792    /**
793     * Sets the focused task to the provided (bounded taskIndex).
794     *
795     * @return whether or not the stack will scroll as a part of this focus change
796     */
797    private boolean setFocusedTask(int taskIndex, boolean scrollToTask,
798            final boolean requestViewFocus) {
799        return setFocusedTask(taskIndex, scrollToTask, requestViewFocus, 0);
800    }
801
802    /**
803     * Sets the focused task to the provided (bounded taskIndex).
804     *
805     * @return whether or not the stack will scroll as a part of this focus change
806     */
807    private boolean setFocusedTask(int taskIndex, boolean scrollToTask,
808            final boolean requestViewFocus, final int timerIndicatorDuration) {
809        // Find the next task to focus
810        int newFocusedTaskIndex = mStack.getTaskCount() > 0 ?
811                Math.max(0, Math.min(mStack.getTaskCount() - 1, taskIndex)) : -1;
812        final Task newFocusedTask = (newFocusedTaskIndex != -1) ?
813                mStack.getStackTasks().get(newFocusedTaskIndex) : null;
814
815        // Reset the last focused task state if changed
816        if (mFocusedTask != null) {
817            // Cancel the timer indicator, if applicable
818            if (timerIndicatorDuration > 0) {
819                final TaskView tv = getChildViewForTask(mFocusedTask);
820                if (tv != null) {
821                    tv.getHeaderView().cancelFocusTimerIndicator();
822                }
823            }
824
825            resetFocusedTask(mFocusedTask);
826        }
827
828        boolean willScroll = false;
829
830        mFocusedTask = newFocusedTask;
831
832        if (newFocusedTask != null) {
833            // Start the timer indicator, if applicable
834            if (timerIndicatorDuration > 0) {
835                final TaskView tv = getChildViewForTask(mFocusedTask);
836                if (tv != null) {
837                    tv.getHeaderView().startFocusTimerIndicator(timerIndicatorDuration);
838                } else {
839                    // The view is null; set a flag for later
840                    mStartTimerIndicatorDuration = timerIndicatorDuration;
841                }
842            }
843
844            Runnable focusTaskRunnable = new Runnable() {
845                @Override
846                public void run() {
847                    final TaskView tv = getChildViewForTask(newFocusedTask);
848                    if (tv != null) {
849                        tv.setFocusedState(true, requestViewFocus);
850                    }
851                }
852            };
853
854            if (scrollToTask) {
855                // Cancel any running enter animations at this point when we scroll or change focus
856                if (!mEnterAnimationComplete) {
857                    cancelAllTaskViewAnimations();
858                }
859
860                // TODO: Center the newly focused task view, only if not freeform
861                float newScroll = mLayoutAlgorithm.getStackScrollForTask(newFocusedTask);
862                if (Float.compare(newScroll, mStackScroller.getStackScroll()) != 0) {
863                    mStackScroller.animateScroll(mStackScroller.getStackScroll(), newScroll,
864                            focusTaskRunnable);
865                    willScroll = true;
866                } else {
867                    focusTaskRunnable.run();
868                }
869                mLayoutAlgorithm.animateFocusState(TaskStackLayoutAlgorithm.STATE_FOCUSED);
870            } else {
871                focusTaskRunnable.run();
872            }
873        }
874        return willScroll;
875    }
876
877    /**
878     * Sets the focused task relative to the currently focused task.
879     *
880     * @param forward whether to go to the next task in the stack (along the curve) or the previous
881     * @param stackTasksOnly if set, will ensure that the traversal only goes along stack tasks, and
882     *                       if the currently focused task is not a stack task, will set the focus
883     *                       to the first visible stack task
884     * @param animated determines whether to actually draw the highlight along with the change in
885     *                            focus.
886     */
887    public void setRelativeFocusedTask(boolean forward, boolean stackTasksOnly, boolean animated) {
888        setRelativeFocusedTask(forward, stackTasksOnly, animated, false);
889    }
890
891    /**
892     * Sets the focused task relative to the currently focused task.
893     *
894     * @param forward whether to go to the next task in the stack (along the curve) or the previous
895     * @param stackTasksOnly if set, will ensure that the traversal only goes along stack tasks, and
896     *                       if the currently focused task is not a stack task, will set the focus
897     *                       to the first visible stack task
898     * @param animated determines whether to actually draw the highlight along with the change in
899     *                            focus.
900     * @param cancelWindowAnimations if set, will attempt to cancel window animations if a scroll
901     *                               happens.
902     */
903    public void setRelativeFocusedTask(boolean forward, boolean stackTasksOnly, boolean animated,
904                                       boolean cancelWindowAnimations) {
905        setRelativeFocusedTask(forward, stackTasksOnly, animated, cancelWindowAnimations, 0);
906    }
907
908    /**
909     * Sets the focused task relative to the currently focused task.
910     *
911     * @param forward whether to go to the next task in the stack (along the curve) or the previous
912     * @param stackTasksOnly if set, will ensure that the traversal only goes along stack tasks, and
913     *                       if the currently focused task is not a stack task, will set the focus
914     *                       to the first visible stack task
915     * @param animated determines whether to actually draw the highlight along with the change in
916     *                            focus.
917     * @param cancelWindowAnimations if set, will attempt to cancel window animations if a scroll
918     *                               happens.
919     * @param timerIndicatorDuration the duration to initialize the auto-advance timer indicator
920     */
921    public void setRelativeFocusedTask(boolean forward, boolean stackTasksOnly, boolean animated,
922                                       boolean cancelWindowAnimations,
923                                       int timerIndicatorDuration) {
924        int newIndex = mStack.indexOfStackTask(mFocusedTask);
925        if (mFocusedTask != null) {
926            if (stackTasksOnly) {
927                List<Task> tasks =  mStack.getStackTasks();
928                if (mFocusedTask.isFreeformTask()) {
929                    // Try and focus the front most stack task
930                    TaskView tv = getFrontMostTaskView(stackTasksOnly);
931                    if (tv != null) {
932                        newIndex = mStack.indexOfStackTask(tv.getTask());
933                    }
934                } else {
935                    // Try the next task if it is a stack task
936                    int tmpNewIndex = newIndex + (forward ? -1 : 1);
937                    if (0 <= tmpNewIndex && tmpNewIndex < tasks.size()) {
938                        Task t = tasks.get(tmpNewIndex);
939                        if (!t.isFreeformTask()) {
940                            newIndex = tmpNewIndex;
941                        }
942                    }
943                }
944            } else {
945                // No restrictions, lets just move to the new task (looping forward/backwards if
946                // necessary)
947                int taskCount = mStack.getTaskCount();
948                newIndex = (newIndex + (forward ? -1 : 1) + taskCount) % taskCount;
949            }
950        } else {
951            // We don't have a focused task, so focus the first visible task view
952            TaskView tv = getFrontMostTaskView(stackTasksOnly);
953            if (tv != null) {
954                newIndex = mStack.indexOfStackTask(tv.getTask());
955            }
956        }
957        if (newIndex != -1) {
958            boolean willScroll = setFocusedTask(newIndex, true /* scrollToTask */,
959                    true /* requestViewFocus */, timerIndicatorDuration);
960            if (willScroll && cancelWindowAnimations) {
961                // As we iterate to the next/previous task, cancel any current/lagging window
962                // transition animations
963                EventBus.getDefault().send(new CancelEnterRecentsWindowAnimationEvent(null));
964            }
965        }
966    }
967
968    /**
969     * Resets the focused task.
970     */
971    void resetFocusedTask(Task task) {
972        if (task != null) {
973            TaskView tv = getChildViewForTask(task);
974            if (tv != null) {
975                tv.setFocusedState(false, false /* requestViewFocus */);
976            }
977        }
978        mFocusedTask = null;
979    }
980
981    /**
982     * Returns the focused task.
983     */
984    Task getFocusedTask() {
985        return mFocusedTask;
986    }
987
988    @Override
989    public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
990        super.onInitializeAccessibilityEvent(event);
991        List<TaskView> taskViews = getTaskViews();
992        int taskViewCount = taskViews.size();
993        if (taskViewCount > 0) {
994            TaskView backMostTask = taskViews.get(0);
995            TaskView frontMostTask = taskViews.get(taskViewCount - 1);
996            event.setFromIndex(mStack.indexOfStackTask(backMostTask.getTask()));
997            event.setToIndex(mStack.indexOfStackTask(frontMostTask.getTask()));
998            event.setContentDescription(frontMostTask.getTask().title);
999        }
1000        event.setItemCount(mStack.getTaskCount());
1001        event.setScrollY(mStackScroller.mScroller.getCurrY());
1002        event.setMaxScrollY(mStackScroller.progressToScrollRange(mLayoutAlgorithm.mMaxScrollP));
1003    }
1004
1005    @Override
1006    public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
1007        super.onInitializeAccessibilityNodeInfo(info);
1008        List<TaskView> taskViews = getTaskViews();
1009        int taskViewCount = taskViews.size();
1010        if (taskViewCount > 1 && mFocusedTask != null) {
1011            info.setScrollable(true);
1012            int focusedTaskIndex = mStack.indexOfStackTask(mFocusedTask);
1013            if (focusedTaskIndex > 0) {
1014                info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
1015            }
1016            if (focusedTaskIndex < mStack.getTaskCount() - 1) {
1017                info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD);
1018            }
1019        }
1020    }
1021
1022    @Override
1023    protected Parcelable onSaveInstanceState() {
1024        Bundle savedState = new Bundle();
1025        savedState.putParcelable(KEY_SAVED_STATE_SUPER, super.onSaveInstanceState());
1026        savedState.putFloat(KEY_SAVED_STATE_LAYOUT_FOCUSED_STATE, mLayoutAlgorithm.getFocusState());
1027        savedState.putFloat(KEY_SAVED_STATE_LAYOUT_STACK_SCROLL, mStackScroller.getStackScroll());
1028        return super.onSaveInstanceState();
1029    }
1030
1031    @Override
1032    protected void onRestoreInstanceState(Parcelable state) {
1033        Bundle savedState = (Bundle) state;
1034        super.onRestoreInstanceState(savedState.getParcelable(KEY_SAVED_STATE_SUPER));
1035
1036        mLayoutAlgorithm.setFocusState(savedState.getFloat(KEY_SAVED_STATE_LAYOUT_FOCUSED_STATE));
1037        mStackScroller.setStackScroll(savedState.getFloat(KEY_SAVED_STATE_LAYOUT_STACK_SCROLL));
1038    }
1039
1040    @Override
1041    public CharSequence getAccessibilityClassName() {
1042        return TaskStackView.class.getName();
1043    }
1044
1045    @Override
1046    public boolean performAccessibilityAction(int action, Bundle arguments) {
1047        if (super.performAccessibilityAction(action, arguments)) {
1048            return true;
1049        }
1050        switch (action) {
1051            case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD: {
1052                setRelativeFocusedTask(true, false /* stackTasksOnly */, false /* animated */);
1053                return true;
1054            }
1055            case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD: {
1056                setRelativeFocusedTask(false, false /* stackTasksOnly */, false /* animated */);
1057                return true;
1058            }
1059        }
1060        return false;
1061    }
1062
1063    @Override
1064    public boolean onInterceptTouchEvent(MotionEvent ev) {
1065        return mTouchHandler.onInterceptTouchEvent(ev);
1066    }
1067
1068    @Override
1069    public boolean onTouchEvent(MotionEvent ev) {
1070        return mTouchHandler.onTouchEvent(ev);
1071    }
1072
1073    @Override
1074    public boolean onGenericMotionEvent(MotionEvent ev) {
1075        return mTouchHandler.onGenericMotionEvent(ev);
1076    }
1077
1078    @Override
1079    public void computeScroll() {
1080        if (mStackScroller.computeScroll()) {
1081            // Notify accessibility
1082            sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SCROLLED);
1083        }
1084        if (mDeferredTaskViewLayoutAnimation != null) {
1085            relayoutTaskViews(mDeferredTaskViewLayoutAnimation);
1086            mTaskViewsClipDirty = true;
1087            mDeferredTaskViewLayoutAnimation = null;
1088        }
1089        if (mTaskViewsClipDirty) {
1090            clipTaskViews();
1091        }
1092    }
1093
1094    /**
1095     * This is ONLY used from the Recents component to update the dummy stack view for purposes
1096     * of getting the task rect to animate to.
1097     */
1098    public void updateLayoutForStack(TaskStack stack) {
1099        mStack = stack;
1100        updateLayoutAlgorithm(false /* boundScroll */, EMPTY_TASK_SET);
1101    }
1102
1103    /**
1104     * Computes the maximum number of visible tasks and thumbnails. Requires that
1105     * updateLayoutForStack() is called first.
1106     */
1107    public TaskStackLayoutAlgorithm.VisibilityReport computeStackVisibilityReport() {
1108        return mLayoutAlgorithm.computeStackVisibilityReport(mStack.getStackTasks());
1109    }
1110
1111    /**
1112     * Updates the expected task stack bounds for this stack view.
1113     */
1114    public void setTaskStackBounds(Rect taskStackBounds, Rect systemInsets) {
1115        // We can get spurious measure passes with the old bounds when docking, and since we are
1116        // using the current stack bounds during drag and drop, don't overwrite them until we
1117        // actually get new bounds
1118        boolean requiresLayout = false;
1119        if (!taskStackBounds.equals(mStableStackBounds)) {
1120            mStableStackBounds.set(taskStackBounds);
1121            mStackBounds.set(taskStackBounds);
1122            requiresLayout = true;
1123        }
1124        if (!systemInsets.equals(mLayoutAlgorithm.mSystemInsets)) {
1125            mLayoutAlgorithm.setSystemInsets(systemInsets);
1126            requiresLayout = true;
1127        }
1128        if (requiresLayout) {
1129            requestLayout();
1130        }
1131    }
1132
1133    /**
1134     * This is called with the full window width and height to allow stack view children to
1135     * perform the full screen transition down.
1136     */
1137    @Override
1138    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
1139        int width = MeasureSpec.getSize(widthMeasureSpec);
1140        int height = MeasureSpec.getSize(heightMeasureSpec);
1141
1142        // Compute the rects in the stack algorithm
1143        mLayoutAlgorithm.initialize(mStackBounds,
1144                TaskStackLayoutAlgorithm.StackState.getStackStateForStack(mStack));
1145        updateLayoutAlgorithm(false /* boundScroll */, EMPTY_TASK_SET);
1146
1147        // If this is the first layout, then scroll to the front of the stack, then update the
1148        // TaskViews with the stack so that we can lay them out
1149        if (mAwaitingFirstLayout) {
1150            mStackScroller.setStackScrollToInitialState();
1151        }
1152        // Rebind all the views, including the ignore ones
1153        bindVisibleTaskViews(mStackScroller.getStackScroll(), EMPTY_TASK_SET);
1154
1155        // Measure each of the TaskViews
1156        mTmpTaskViews.clear();
1157        mTmpTaskViews.addAll(getTaskViews());
1158        mTmpTaskViews.addAll(mViewPool.getViews());
1159        int taskViewCount = mTmpTaskViews.size();
1160        for (int i = 0; i < taskViewCount; i++) {
1161            TaskView tv = mTmpTaskViews.get(i);
1162            if (tv.getBackground() != null) {
1163                tv.getBackground().getPadding(mTmpRect);
1164            } else {
1165                mTmpRect.setEmpty();
1166            }
1167            tv.measure(
1168                    MeasureSpec.makeMeasureSpec(
1169                            mLayoutAlgorithm.mTaskRect.width() + mTmpRect.left + mTmpRect.right,
1170                            MeasureSpec.EXACTLY),
1171                    MeasureSpec.makeMeasureSpec(
1172                            mLayoutAlgorithm.mTaskRect.height() + mTmpRect.top + mTmpRect.bottom,
1173                            MeasureSpec.EXACTLY));
1174        }
1175
1176        setMeasuredDimension(width, height);
1177    }
1178
1179    /**
1180     * This is called with the size of the space not including the top or right insets, or the
1181     * search bar height in portrait (but including the search bar width in landscape, since we want
1182     * to draw under it.
1183     */
1184    @Override
1185    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
1186        // Layout each of the TaskViews
1187        mTmpTaskViews.clear();
1188        mTmpTaskViews.addAll(getTaskViews());
1189        mTmpTaskViews.addAll(mViewPool.getViews());
1190        int taskViewCount = mTmpTaskViews.size();
1191        for (int i = 0; i < taskViewCount; i++) {
1192            TaskView tv = mTmpTaskViews.get(i);
1193            if (tv.getBackground() != null) {
1194                tv.getBackground().getPadding(mTmpRect);
1195            } else {
1196                mTmpRect.setEmpty();
1197            }
1198            Rect taskRect = mLayoutAlgorithm.mTaskRect;
1199            tv.layout(taskRect.left - mTmpRect.left, taskRect.top - mTmpRect.top,
1200                    taskRect.right + mTmpRect.right, taskRect.bottom + mTmpRect.bottom);
1201        }
1202
1203        if (changed) {
1204            if (mStackScroller.isScrollOutOfBounds()) {
1205                mStackScroller.boundScroll();
1206            }
1207        }
1208        // Relayout all of the task views including the ignored ones
1209        relayoutTaskViews(TaskViewAnimation.IMMEDIATE, EMPTY_TASK_SET);
1210        clipTaskViews();
1211
1212        if (mAwaitingFirstLayout || !mEnterAnimationComplete) {
1213            mAwaitingFirstLayout = false;
1214            onFirstLayout();
1215            return;
1216        }
1217    }
1218
1219    /** Handler for the first layout. */
1220    void onFirstLayout() {
1221        // Setup the view for the enter animation
1222        mAnimationHelper.prepareForEnterAnimation();
1223
1224        // Animate in the freeform workspace
1225        animateFreeformWorkspaceBackgroundAlpha(
1226                mLayoutAlgorithm.getStackState().freeformBackgroundAlpha, 150,
1227                Interpolators.FAST_OUT_SLOW_IN);
1228
1229        // Set the task focused state without requesting view focus, and leave the focus animations
1230        // until after the enter-animation
1231        Task launchTask = mStack.getLaunchTarget();
1232        RecentsConfiguration config = Recents.getConfiguration();
1233        RecentsActivityLaunchState launchState = config.getLaunchState();
1234        int focusedTaskIndex = launchState.getInitialFocusTaskIndex(mStack.getTaskCount());
1235        if (focusedTaskIndex != -1) {
1236            setFocusedTask(focusedTaskIndex, false /* scrollToTask */,
1237                    false /* requestViewFocus */);
1238        }
1239
1240        // Update the history button visibility
1241        if (shouldShowHistoryButton() &&
1242                mStackScroller.getStackScroll() < SHOW_HISTORY_BUTTON_SCROLL_THRESHOLD) {
1243            EventBus.getDefault().send(new ShowHistoryButtonEvent(false /* translate */));
1244        } else {
1245            EventBus.getDefault().send(new HideHistoryButtonEvent());
1246        }
1247    }
1248
1249    public boolean isTouchPointInView(float x, float y, TaskView tv) {
1250        mTmpRect.set(tv.getLeft(), tv.getTop(), tv.getRight(), tv.getBottom());
1251        mTmpRect.offset((int) tv.getTranslationX(), (int) tv.getTranslationY());
1252        return mTmpRect.contains((int) x, (int) y);
1253    }
1254
1255    /**
1256     * Returns a non-ignored task in the {@param tasks} list that can be used as an achor when
1257     * calculating the scroll position before and after a layout change.
1258     */
1259    public Task findAnchorTask(List<Task> tasks, MutableBoolean isFrontMostTask) {
1260        for (int i = tasks.size() - 1; i >= 0; i--) {
1261            Task task = tasks.get(i);
1262
1263            // Ignore deleting tasks
1264            if (mIgnoreTasks.contains(task.key)) {
1265                if (i == tasks.size() - 1) {
1266                    isFrontMostTask.value = true;
1267                }
1268                continue;
1269            }
1270            return task;
1271        }
1272        return null;
1273    }
1274
1275    @Override
1276    protected void dispatchDraw(Canvas canvas) {
1277        // Draw the freeform workspace background
1278        SystemServicesProxy ssp = Recents.getSystemServices();
1279        if (ssp.hasFreeformWorkspaceSupport()) {
1280            if (mFreeformWorkspaceBackground.getAlpha() > 0) {
1281                mFreeformWorkspaceBackground.draw(canvas);
1282            }
1283        }
1284
1285        super.dispatchDraw(canvas);
1286    }
1287
1288    @Override
1289    protected boolean verifyDrawable(Drawable who) {
1290        if (who == mFreeformWorkspaceBackground) {
1291            return true;
1292        }
1293        return super.verifyDrawable(who);
1294    }
1295
1296    /**
1297     * Launches the freeform tasks.
1298     */
1299    public boolean launchFreeformTasks() {
1300        ArrayList<Task> tasks = mStack.getFreeformTasks();
1301        if (!tasks.isEmpty()) {
1302            Task frontTask = tasks.get(tasks.size() - 1);
1303            if (frontTask != null && frontTask.isFreeformTask()) {
1304                EventBus.getDefault().send(new LaunchTaskEvent(getChildViewForTask(frontTask),
1305                        frontTask, null, INVALID_STACK_ID, false));
1306                return true;
1307            }
1308        }
1309        return false;
1310    }
1311
1312    /**** TaskStackCallbacks Implementation ****/
1313
1314    @Override
1315    public void onStackTaskAdded(TaskStack stack, Task newTask) {
1316        // Update the min/max scroll and animate other task views into their new positions
1317        updateLayoutAlgorithm(true /* boundScroll */);
1318
1319        // Animate all the tasks into place
1320        relayoutTaskViews(new TaskViewAnimation(DEFAULT_SYNC_STACK_DURATION,
1321                Interpolators.FAST_OUT_SLOW_IN));
1322    }
1323
1324    /**
1325     * We expect that the {@link TaskView} associated with the removed task is already hidden.
1326     */
1327    @Override
1328    public void onStackTaskRemoved(TaskStack stack, Task removedTask, boolean wasFrontMostTask,
1329            Task newFrontMostTask, TaskViewAnimation animation) {
1330        if (mFocusedTask == removedTask) {
1331            resetFocusedTask(removedTask);
1332        }
1333
1334        // Remove the view associated with this task, we can't rely on updateTransforms
1335        // to work here because the task is no longer in the list
1336        TaskView tv = getChildViewForTask(removedTask);
1337        if (tv != null) {
1338            mViewPool.returnViewToPool(tv);
1339        }
1340
1341        // Remove the task from the ignored set
1342        removeIgnoreTask(removedTask);
1343
1344        // If requested, relayout with the given animation
1345        if (animation != null) {
1346            updateLayoutAlgorithm(true /* boundScroll */);
1347            relayoutTaskViews(animation);
1348        }
1349
1350        // Update the new front most task's action button
1351        if (mScreenPinningEnabled && newFrontMostTask != null) {
1352            TaskView frontTv = getChildViewForTask(newFrontMostTask);
1353            if (frontTv != null) {
1354                frontTv.showActionButton(true /* fadeIn */, DEFAULT_SYNC_STACK_DURATION);
1355            }
1356        }
1357
1358        // If there are no remaining tasks, then just close recents
1359        if (mStack.getTaskCount() == 0) {
1360            EventBus.getDefault().send(new AllTaskViewsDismissedEvent());
1361        }
1362    }
1363
1364    @Override
1365    public void onHistoryTaskRemoved(TaskStack stack, Task removedTask,
1366            TaskViewAnimation animation) {
1367        // To be implemented
1368    }
1369
1370    /**** ViewPoolConsumer Implementation ****/
1371
1372    @Override
1373    public TaskView createView(Context context) {
1374        return (TaskView) mInflater.inflate(R.layout.recents_task_view, this, false);
1375    }
1376
1377    @Override
1378    public void prepareViewToEnterPool(TaskView tv) {
1379        final Task task = tv.getTask();
1380
1381        // Report that this tasks's data is no longer being used
1382        Recents.getTaskLoader().unloadTaskData(task);
1383
1384        // Reset the view properties and view state
1385        tv.resetViewProperties();
1386        tv.setFocusedState(false, false /* requestViewFocus */);
1387        tv.setClipViewInStack(false);
1388        if (mScreenPinningEnabled) {
1389            tv.hideActionButton(false /* fadeOut */, 0 /* duration */, false /* scaleDown */, null);
1390        }
1391
1392        // Detach the view from the hierarchy
1393        detachViewFromParent(tv);
1394        // Update the task views list after removing the task view
1395        updateTaskViewsList();
1396    }
1397
1398    @Override
1399    public void prepareViewToLeavePool(TaskView tv, Task task, boolean isNewView) {
1400        // Find the index where this task should be placed in the stack
1401        int taskIndex = mStack.indexOfStackTask(task);
1402        int insertIndex = findTaskViewInsertIndex(task, taskIndex);
1403
1404        // Add/attach the view to the hierarchy
1405        if (isNewView) {
1406            addView(tv, insertIndex);
1407        } else {
1408            attachViewToParent(tv, insertIndex, tv.getLayoutParams());
1409        }
1410        // Update the task views list after adding the new task view
1411        updateTaskViewsList();
1412
1413        // Rebind the task and request that this task's data be filled into the TaskView
1414        tv.onTaskBound(task);
1415
1416        // Load the task data
1417        Recents.getTaskLoader().loadTaskData(task, true /* fetchAndInvalidateThumbnails */);
1418
1419        // If the doze trigger has already fired, then update the state for this task view
1420        if (mUIDozeTrigger.hasTriggered()) {
1421            tv.setNoUserInteractionState();
1422        }
1423
1424        // Set the new state for this view, including the callbacks and view clipping
1425        tv.setCallbacks(this);
1426        tv.setTouchEnabled(true);
1427        tv.setClipViewInStack(true);
1428        if (mFocusedTask == task) {
1429            tv.setFocusedState(true, false /* requestViewFocus */);
1430            if (mStartTimerIndicatorDuration > 0) {
1431                // The timer indicator couldn't be started before, so start it now
1432                tv.getHeaderView().startFocusTimerIndicator(mStartTimerIndicatorDuration);
1433                mStartTimerIndicatorDuration = 0;
1434            }
1435        }
1436
1437        // Restore the action button visibility if it is the front most task view
1438        if (mScreenPinningEnabled && tv.getTask() ==
1439                mStack.getStackFrontMostTask(false /* includeFreeform */)) {
1440            tv.showActionButton(false /* fadeIn */, 0 /* fadeInDuration */);
1441        }
1442    }
1443
1444    @Override
1445    public boolean hasPreferredData(TaskView tv, Task preferredData) {
1446        return (tv.getTask() == preferredData);
1447    }
1448
1449    /**** TaskViewCallbacks Implementation ****/
1450
1451    @Override
1452    public void onTaskViewClipStateChanged(TaskView tv) {
1453        if (!mTaskViewsClipDirty) {
1454            mTaskViewsClipDirty = true;
1455            invalidate();
1456        }
1457    }
1458
1459    /**** TaskStackLayoutAlgorithm.TaskStackLayoutAlgorithmCallbacks ****/
1460
1461    @Override
1462    public void onFocusStateChanged(float prevFocusState, float curFocusState) {
1463        if (mDeferredTaskViewLayoutAnimation == null) {
1464            mUIDozeTrigger.poke();
1465            relayoutTaskViewsOnNextFrame(TaskViewAnimation.IMMEDIATE);
1466        }
1467    }
1468
1469    /**** TaskStackViewScroller.TaskStackViewScrollerCallbacks ****/
1470
1471    @Override
1472    public void onScrollChanged(float prevScroll, float curScroll, TaskViewAnimation animation) {
1473        mUIDozeTrigger.poke();
1474        if (animation != null) {
1475            relayoutTaskViewsOnNextFrame(animation);
1476        }
1477
1478        if (mEnterAnimationComplete) {
1479            if (shouldShowHistoryButton() &&
1480                    prevScroll > SHOW_HISTORY_BUTTON_SCROLL_THRESHOLD &&
1481                    curScroll <= SHOW_HISTORY_BUTTON_SCROLL_THRESHOLD) {
1482                EventBus.getDefault().send(new ShowHistoryButtonEvent(true /* translate */));
1483            } else if (prevScroll < HIDE_HISTORY_BUTTON_SCROLL_THRESHOLD &&
1484                    curScroll >= HIDE_HISTORY_BUTTON_SCROLL_THRESHOLD) {
1485                EventBus.getDefault().send(new HideHistoryButtonEvent());
1486            }
1487        }
1488    }
1489
1490    /**** EventBus Events ****/
1491
1492    public final void onBusEvent(PackagesChangedEvent event) {
1493        // Compute which components need to be removed
1494        ArraySet<ComponentName> removedComponents = mStack.computeComponentsRemoved(
1495                event.packageName, event.userId);
1496
1497        // For other tasks, just remove them directly if they no longer exist
1498        ArrayList<Task> tasks = mStack.getStackTasks();
1499        for (int i = tasks.size() - 1; i >= 0; i--) {
1500            final Task t = tasks.get(i);
1501            if (removedComponents.contains(t.key.getComponent())) {
1502                final TaskView tv = getChildViewForTask(t);
1503                if (tv != null) {
1504                    // For visible children, defer removing the task until after the animation
1505                    tv.dismissTask();
1506                } else {
1507                    // Otherwise, remove the task from the stack immediately
1508                    mStack.removeTask(t, TaskViewAnimation.IMMEDIATE);
1509                }
1510            }
1511        }
1512    }
1513
1514    public final void onBusEvent(LaunchTaskEvent event) {
1515        // Cancel any doze triggers once a task is launched
1516        mUIDozeTrigger.stopDozing();
1517    }
1518
1519    public final void onBusEvent(LaunchTaskStartedEvent event) {
1520        mAnimationHelper.startLaunchTaskAnimation(event.taskView, event.screenPinningRequested,
1521                event.getAnimationTrigger());
1522    }
1523
1524    public final void onBusEvent(DismissRecentsToHomeAnimationStarted event) {
1525        // Stop any scrolling
1526        mStackScroller.stopScroller();
1527        mStackScroller.stopBoundScrollAnimation();
1528
1529        // Start the task animations
1530        mAnimationHelper.startExitToHomeAnimation(event.animated, event.getAnimationTrigger());
1531
1532        // Dismiss the freeform workspace background
1533        int taskViewExitToHomeDuration = getResources().getInteger(
1534                R.integer.recents_task_exit_to_home_duration);
1535        animateFreeformWorkspaceBackgroundAlpha(0, taskViewExitToHomeDuration,
1536                Interpolators.FAST_OUT_SLOW_IN);
1537    }
1538
1539    public final void onBusEvent(DismissFocusedTaskViewEvent event) {
1540        if (mFocusedTask != null) {
1541            TaskView tv = getChildViewForTask(mFocusedTask);
1542            if (tv != null) {
1543                tv.dismissTask();
1544            }
1545            resetFocusedTask(mFocusedTask);
1546        }
1547    }
1548
1549    public final void onBusEvent(final DismissTaskViewEvent event) {
1550        // For visible children, defer removing the task until after the animation
1551        mAnimationHelper.startDeleteTaskAnimation(event.task, event.taskView,
1552                event.getAnimationTrigger());
1553    }
1554
1555    public final void onBusEvent(TaskViewDismissedEvent event) {
1556        removeTaskViewFromStack(event.taskView, event.task);
1557        EventBus.getDefault().send(new DeleteTaskDataEvent(event.task));
1558    }
1559
1560    public final void onBusEvent(FocusNextTaskViewEvent event) {
1561        setRelativeFocusedTask(true, false /* stackTasksOnly */, true /* animated */, false,
1562                event.timerIndicatorDuration);
1563    }
1564
1565    public final void onBusEvent(FocusPreviousTaskViewEvent event) {
1566        setRelativeFocusedTask(false, false /* stackTasksOnly */, true /* animated */);
1567    }
1568
1569    public final void onBusEvent(UserInteractionEvent event) {
1570        // Poke the doze trigger on user interaction
1571        mUIDozeTrigger.poke();
1572
1573        RecentsDebugFlags debugFlags = Recents.getDebugFlags();
1574        if (debugFlags.isFastToggleRecentsEnabled() && mFocusedTask != null) {
1575            TaskView tv = getChildViewForTask(mFocusedTask);
1576            if (tv != null) {
1577                tv.getHeaderView().cancelFocusTimerIndicator();
1578            }
1579        }
1580    }
1581
1582    public final void onBusEvent(RecentsVisibilityChangedEvent event) {
1583        if (!event.visible) {
1584            reset();
1585        }
1586    }
1587
1588    public final void onBusEvent(DragStartEvent event) {
1589        if (event.task.isFreeformTask()) {
1590            // Animate to the front of the stack
1591            mStackScroller.animateScroll(mStackScroller.getStackScroll(),
1592                    mLayoutAlgorithm.mInitialScrollP, null);
1593        }
1594
1595        // Enlarge the dragged view slightly
1596        float finalScale = event.taskView.getScaleX() * DRAG_SCALE_FACTOR;
1597        mLayoutAlgorithm.getStackTransform(event.task, getScroller().getStackScroll(),
1598                mTmpTransform, null);
1599        mTmpTransform.scale = finalScale;
1600        mTmpTransform.translationZ = mLayoutAlgorithm.mMaxTranslationZ + 1;
1601        updateTaskViewToTransform(event.taskView, mTmpTransform,
1602                new TaskViewAnimation(DRAG_SCALE_DURATION, Interpolators.FAST_OUT_SLOW_IN));
1603    }
1604
1605    public final void onBusEvent(DragStartInitializeDropTargetsEvent event) {
1606        SystemServicesProxy ssp = Recents.getSystemServices();
1607        if (ssp.hasFreeformWorkspaceSupport()) {
1608            event.handler.registerDropTargetForCurrentDrag(mStackDropTarget);
1609            event.handler.registerDropTargetForCurrentDrag(mFreeformWorkspaceDropTarget);
1610        }
1611    }
1612
1613    public final void onBusEvent(DragDropTargetChangedEvent event) {
1614        TaskViewAnimation animation = new TaskViewAnimation(250, Interpolators.FAST_OUT_SLOW_IN);
1615        if (event.dropTarget instanceof TaskStack.DockState) {
1616            // Calculate the new task stack bounds that matches the window size that Recents will
1617            // have after the drop
1618            addIgnoreTask(event.task);
1619            final TaskStack.DockState dockState = (TaskStack.DockState) event.dropTarget;
1620            mStackBounds.set(dockState.getDockedTaskStackBounds(getMeasuredWidth(),
1621                    getMeasuredHeight(), mDividerSize, mLayoutAlgorithm.mSystemInsets,
1622                    getResources()));
1623            mLayoutAlgorithm.initialize(mStackBounds,
1624                    TaskStackLayoutAlgorithm.StackState.getStackStateForStack(mStack));
1625            updateLayoutAlgorithm(true /* boundScroll */);
1626        } else {
1627            // Restore the pre-drag task stack bounds, but ensure that we don't layout the dragging
1628            // task view, so add it back to the ignore set after updating the layout
1629            mStackBounds.set(mStableStackBounds);
1630            removeIgnoreTask(event.task);
1631            mLayoutAlgorithm.initialize(mStackBounds,
1632                    TaskStackLayoutAlgorithm.StackState.getStackStateForStack(mStack));
1633            updateLayoutAlgorithm(true /* boundScroll */);
1634            addIgnoreTask(event.task);
1635        }
1636        relayoutTaskViews(animation);
1637    }
1638
1639    public final void onBusEvent(final DragEndEvent event) {
1640        // We don't handle drops on the dock regions
1641        if (event.dropTarget instanceof TaskStack.DockState) {
1642            return;
1643        }
1644
1645        boolean isFreeformTask = event.task.isFreeformTask();
1646        boolean hasChangedStacks =
1647                (!isFreeformTask && event.dropTarget == mFreeformWorkspaceDropTarget) ||
1648                        (isFreeformTask && event.dropTarget == mStackDropTarget);
1649
1650        if (hasChangedStacks) {
1651            // Move the task to the right position in the stack (ie. the front of the stack if
1652            // freeform or the front of the stack if fullscreen). Note, we MUST move the tasks
1653            // before we update their stack ids, otherwise, the keys will have changed.
1654            if (event.dropTarget == mFreeformWorkspaceDropTarget) {
1655                mStack.moveTaskToStack(event.task, FREEFORM_WORKSPACE_STACK_ID);
1656            } else if (event.dropTarget == mStackDropTarget) {
1657                mStack.moveTaskToStack(event.task, FULLSCREEN_WORKSPACE_STACK_ID);
1658            }
1659            updateLayoutAlgorithm(true /* boundScroll */);
1660
1661            // Move the task to the new stack in the system after the animation completes
1662            event.addPostAnimationCallback(new Runnable() {
1663                @Override
1664                public void run() {
1665                    SystemServicesProxy ssp = Recents.getSystemServices();
1666                    ssp.moveTaskToStack(event.task.key.id, event.task.key.stackId);
1667                }
1668            });
1669        }
1670
1671        // We translated the view but we need to animate it back from the current layout-space rect
1672        // to its final layout-space rect
1673        int x = (int) event.taskView.getTranslationX();
1674        int y = (int) event.taskView.getTranslationY();
1675        Rect taskViewRect = new Rect(event.taskView.getLeft(), event.taskView.getTop(),
1676                event.taskView.getRight(), event.taskView.getBottom());
1677        taskViewRect.offset(x, y);
1678        event.taskView.setTranslationX(0);
1679        event.taskView.setTranslationY(0);
1680        event.taskView.setLeftTopRightBottom(taskViewRect.left, taskViewRect.top,
1681                taskViewRect.right, taskViewRect.bottom);
1682
1683        // Animate all the TaskViews back into position
1684        mLayoutAlgorithm.getStackTransform(event.task, getScroller().getStackScroll(),
1685                mTmpTransform, null);
1686        event.getAnimationTrigger().increment();
1687        relayoutTaskViews(new TaskViewAnimation(DEFAULT_SYNC_STACK_DURATION,
1688                Interpolators.FAST_OUT_SLOW_IN));
1689        updateTaskViewToTransform(event.taskView, mTmpTransform,
1690                new TaskViewAnimation(DEFAULT_SYNC_STACK_DURATION, Interpolators.FAST_OUT_SLOW_IN,
1691                        event.getAnimationTrigger().decrementOnAnimationEnd()));
1692        removeIgnoreTask(event.task);
1693    }
1694
1695    public final void onBusEvent(StackViewScrolledEvent event) {
1696        mLayoutAlgorithm.updateFocusStateOnScroll(event.yMovement.value);
1697    }
1698
1699    public final void onBusEvent(IterateRecentsEvent event) {
1700        if (!mEnterAnimationComplete) {
1701            // Cancel the previous task's window transition before animating the focused state
1702            EventBus.getDefault().send(new CancelEnterRecentsWindowAnimationEvent(null));
1703        }
1704    }
1705
1706    public final void onBusEvent(EnterRecentsWindowAnimationCompletedEvent event) {
1707        mEnterAnimationComplete = true;
1708
1709        if (mStack.getTaskCount() > 0) {
1710            // Start the task enter animations
1711            mAnimationHelper.startEnterAnimation(event.getAnimationTrigger());
1712
1713            // Add a runnable to the post animation ref counter to clear all the views
1714            event.addPostAnimationCallback(new Runnable() {
1715                @Override
1716                public void run() {
1717                    // Start the dozer to trigger to trigger any UI that shows after a timeout
1718                    mUIDozeTrigger.startDozing();
1719
1720                    // Update the focused state here -- since we only set the focused task without
1721                    // requesting view focus in onFirstLayout(), actually request view focus and
1722                    // animate the focused state if we are alt-tabbing now, after the window enter
1723                    // animation is completed
1724                    if (mFocusedTask != null) {
1725                        RecentsConfiguration config = Recents.getConfiguration();
1726                        RecentsActivityLaunchState launchState = config.getLaunchState();
1727                        setFocusedTask(mStack.indexOfStackTask(mFocusedTask),
1728                                false /* scrollToTask */, launchState.launchedWithAltTab);
1729                    }
1730
1731                    EventBus.getDefault().send(new EnterRecentsTaskStackAnimationCompletedEvent());
1732                }
1733            });
1734        }
1735    }
1736
1737    public final void onBusEvent(EnterRecentsTaskStackAnimationCompletedEvent event) {
1738        RecentsDebugFlags debugFlags = Recents.getDebugFlags();
1739        RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState();
1740        if (!launchState.launchedWithAltTab && debugFlags.isFastToggleRecentsEnabled() &&
1741                RecentsDebugFlags.Static.EnableFastToggleTimeoutOnEnter) {
1742            if (mFocusedTask != null) {
1743                int timerIndicatorDuration = getResources().getInteger(
1744                        R.integer.recents_auto_advance_duration);
1745                int focusedTaskIndex = mStack.indexOfStackTask(mFocusedTask);
1746                setFocusedTask(focusedTaskIndex, false /* scrollToTask */,
1747                        false /* requestViewFocus */, timerIndicatorDuration);
1748            }
1749        }
1750    }
1751
1752    public final void onBusEvent(UpdateFreeformTaskViewVisibilityEvent event) {
1753        List<TaskView> taskViews = getTaskViews();
1754        int taskViewCount = taskViews.size();
1755        for (int i = 0; i < taskViewCount; i++) {
1756            TaskView tv = taskViews.get(i);
1757            Task task = tv.getTask();
1758            if (task.isFreeformTask()) {
1759                tv.setVisibility(event.visible ? View.VISIBLE : View.INVISIBLE);
1760            }
1761        }
1762    }
1763
1764    public final void onBusEvent(ShowHistoryEvent event) {
1765        ReferenceCountedTrigger postAnimTrigger = new ReferenceCountedTrigger();
1766        postAnimTrigger.addLastDecrementRunnable(new Runnable() {
1767            @Override
1768            public void run() {
1769                setVisibility(View.INVISIBLE);
1770            }
1771        });
1772        mAnimationHelper.startShowHistoryAnimation(postAnimTrigger);
1773    }
1774
1775    public final void onBusEvent(HideHistoryEvent event) {
1776        setVisibility(View.VISIBLE);
1777        mAnimationHelper.startHideHistoryAnimation();
1778    }
1779
1780    /**
1781     * Removes the task from the stack, and updates the focus to the next task in the stack if the
1782     * removed TaskView was focused.
1783     */
1784    private void removeTaskViewFromStack(TaskView tv, Task task) {
1785        // Announce for accessibility
1786        tv.announceForAccessibility(getContext().getString(
1787                R.string.accessibility_recents_item_dismissed, task.title));
1788
1789        // Remove the task from the stack
1790        mStack.removeTask(task, new TaskViewAnimation(DEFAULT_SYNC_STACK_DURATION,
1791                Interpolators.FAST_OUT_SLOW_IN));
1792    }
1793
1794    /**
1795     * Starts an alpha animation on the freeform workspace background.
1796     */
1797    private void animateFreeformWorkspaceBackgroundAlpha(int targetAlpha, int duration,
1798            Interpolator interpolator) {
1799        if (mFreeformWorkspaceBackground.getAlpha() == targetAlpha) {
1800            return;
1801        }
1802
1803        Utilities.cancelAnimationWithoutCallbacks(mFreeformWorkspaceBackgroundAnimator);
1804        mFreeformWorkspaceBackgroundAnimator = ObjectAnimator.ofInt(mFreeformWorkspaceBackground,
1805                Utilities.DRAWABLE_ALPHA, mFreeformWorkspaceBackground.getAlpha(), targetAlpha);
1806        mFreeformWorkspaceBackgroundAnimator.setDuration(duration);
1807        mFreeformWorkspaceBackgroundAnimator.setInterpolator(interpolator);
1808        mFreeformWorkspaceBackgroundAnimator.start();
1809    }
1810
1811    /**
1812     * Returns the insert index for the task in the current set of task views. If the given task
1813     * is already in the task view list, then this method returns the insert index assuming it
1814     * is first removed at the previous index.
1815     *
1816     * @param task the task we are finding the index for
1817     * @param taskIndex the index of the task in the stack
1818     */
1819    private int findTaskViewInsertIndex(Task task, int taskIndex) {
1820        if (taskIndex != -1) {
1821            List<TaskView> taskViews = getTaskViews();
1822            boolean foundTaskView = false;
1823            int taskViewCount = taskViews.size();
1824            for (int i = 0; i < taskViewCount; i++) {
1825                Task tvTask = taskViews.get(i).getTask();
1826                if (tvTask == task) {
1827                    foundTaskView = true;
1828                } else if (taskIndex < mStack.indexOfStackTask(tvTask)) {
1829                    if (foundTaskView) {
1830                        return i - 1;
1831                    } else {
1832                        return i;
1833                    }
1834                }
1835            }
1836        }
1837        return -1;
1838    }
1839
1840    /**
1841     * @return whether the history button should be visible
1842     */
1843    private boolean shouldShowHistoryButton() {
1844        return !mStack.getHistoricalTasks().isEmpty();
1845    }
1846
1847    /**
1848     * Reads current system flags related to accessibility and screen pinning.
1849     */
1850    private void readSystemFlags() {
1851        SystemServicesProxy ssp = Recents.getSystemServices();
1852        mTouchExplorationEnabled = ssp.isTouchExplorationEnabled();
1853        mScreenPinningEnabled = ssp.getSystemSetting(getContext(),
1854                Settings.System.LOCK_TO_APP_ENABLED) != 0;
1855    }
1856}
1857