RecentsView.java revision 9a74290a95e27fc6478758f32a681ccdf495f1e4
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.content.Context;
20import android.content.res.Resources;
21import android.graphics.Canvas;
22import android.graphics.Rect;
23import android.graphics.drawable.Drawable;
24import android.os.Handler;
25import android.util.ArraySet;
26import android.util.AttributeSet;
27import android.view.LayoutInflater;
28import android.view.MotionEvent;
29import android.view.View;
30import android.view.ViewPropertyAnimator;
31import android.view.WindowInsets;
32import android.view.animation.AnimationUtils;
33import android.view.animation.Interpolator;
34import android.widget.FrameLayout;
35
36import com.android.internal.logging.MetricsLogger;
37import com.android.systemui.R;
38import com.android.systemui.recents.Recents;
39import com.android.systemui.recents.RecentsActivity;
40import com.android.systemui.recents.RecentsActivityLaunchState;
41import com.android.systemui.recents.RecentsAppWidgetHostView;
42import com.android.systemui.recents.RecentsConfiguration;
43import com.android.systemui.recents.RecentsDebugFlags;
44import com.android.systemui.recents.events.EventBus;
45import com.android.systemui.recents.events.activity.CancelEnterRecentsWindowAnimationEvent;
46import com.android.systemui.recents.events.activity.DebugFlagsChangedEvent;
47import com.android.systemui.recents.events.activity.DismissRecentsToHomeAnimationStarted;
48import com.android.systemui.recents.events.activity.HideHistoryButtonEvent;
49import com.android.systemui.recents.events.activity.HideHistoryEvent;
50import com.android.systemui.recents.events.activity.ShowHistoryButtonEvent;
51import com.android.systemui.recents.events.activity.ShowHistoryEvent;
52import com.android.systemui.recents.events.component.RecentsVisibilityChangedEvent;
53import com.android.systemui.recents.events.ui.DraggingInRecentsEndedEvent;
54import com.android.systemui.recents.events.ui.DraggingInRecentsEvent;
55import com.android.systemui.recents.events.ui.dragndrop.DragDropTargetChangedEvent;
56import com.android.systemui.recents.events.ui.dragndrop.DragEndEvent;
57import com.android.systemui.recents.events.ui.dragndrop.DragStartEvent;
58import com.android.systemui.recents.misc.ReferenceCountedTrigger;
59import com.android.systemui.recents.misc.SystemServicesProxy;
60import com.android.systemui.recents.model.Task;
61import com.android.systemui.recents.model.TaskStack;
62import com.android.systemui.stackdivider.WindowManagerProxy;
63import com.android.systemui.statusbar.FlingAnimationUtils;
64
65import java.util.ArrayList;
66import java.util.List;
67
68import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
69
70/**
71 * This view is the the top level layout that contains TaskStacks (which are laid out according
72 * to their SpaceNode bounds.
73 */
74public class RecentsView extends FrameLayout implements TaskStackView.TaskStackViewCallbacks {
75
76    private static final String TAG = "RecentsView";
77    private static final boolean DEBUG = false;
78
79    private final Handler mHandler;
80
81    private TaskStack mStack;
82    private TaskStackView mTaskStackView;
83    private RecentsAppWidgetHostView mSearchBar;
84    private View mHistoryButton;
85    private View mEmptyView;
86    private boolean mAwaitingFirstLayout = true;
87    private boolean mLastTaskLaunchedWasFreeform;
88    private Rect mSystemInsets = new Rect();
89
90    private RecentsTransitionHelper mTransitionHelper;
91    private RecentsViewTouchHandler mTouchHandler;
92    private TaskStack.DockState[] mVisibleDockStates = {
93            TaskStack.DockState.LEFT,
94            TaskStack.DockState.TOP,
95            TaskStack.DockState.RIGHT,
96            TaskStack.DockState.BOTTOM,
97    };
98
99    private final Interpolator mFastOutSlowInInterpolator;
100    private final Interpolator mFastOutLinearInInterpolator;
101    private final FlingAnimationUtils mFlingAnimationUtils;
102
103    public RecentsView(Context context) {
104        this(context, null);
105    }
106
107    public RecentsView(Context context, AttributeSet attrs) {
108        this(context, attrs, 0);
109    }
110
111    public RecentsView(Context context, AttributeSet attrs, int defStyleAttr) {
112        this(context, attrs, defStyleAttr, 0);
113    }
114
115    public RecentsView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
116        super(context, attrs, defStyleAttr, defStyleRes);
117        Resources res = context.getResources();
118        setWillNotDraw(false);
119        mHandler = new Handler();
120        mTransitionHelper = new RecentsTransitionHelper(getContext(), mHandler);
121        mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(context,
122                com.android.internal.R.interpolator.fast_out_slow_in);
123        mFastOutLinearInInterpolator = AnimationUtils.loadInterpolator(context,
124                com.android.internal.R.interpolator.fast_out_linear_in);
125        mTouchHandler = new RecentsViewTouchHandler(this);
126        mFlingAnimationUtils = new FlingAnimationUtils(context, 0.3f);
127
128        LayoutInflater inflater = LayoutInflater.from(context);
129        mHistoryButton = inflater.inflate(R.layout.recents_history_button, this, false);
130        mHistoryButton.setOnClickListener(new View.OnClickListener() {
131            @Override
132            public void onClick(View v) {
133                ReferenceCountedTrigger postHideStackAnimationTrigger = new ReferenceCountedTrigger(v.getContext());
134                postHideStackAnimationTrigger.increment();
135                EventBus.getDefault().send(new ShowHistoryEvent(postHideStackAnimationTrigger));
136                postHideStackAnimationTrigger.decrement();
137            }
138        });
139        addView(mHistoryButton);
140        mEmptyView = inflater.inflate(R.layout.recents_empty, this, false);
141        addView(mEmptyView);
142    }
143
144    /** Set/get the bsp root node */
145    public void setTaskStack(TaskStack stack) {
146        RecentsConfiguration config = Recents.getConfiguration();
147        RecentsActivityLaunchState launchState = config.getLaunchState();
148        mStack = stack;
149        // Disable reusing task stack views until the visibility bug is fixed. b/25998134
150        if (false && launchState.launchedReuseTaskStackViews) {
151            if (mTaskStackView != null) {
152                // If onRecentsHidden is not triggered, we need to the stack view again here
153                mTaskStackView.reset();
154                mTaskStackView.setStack(stack);
155                removeView(mTaskStackView);
156                addView(mTaskStackView);
157            } else {
158                mTaskStackView = new TaskStackView(getContext(), stack);
159                mTaskStackView.setCallbacks(this);
160                addView(mTaskStackView);
161            }
162        } else {
163            if (mTaskStackView != null) {
164                removeView(mTaskStackView);
165            }
166            mTaskStackView = new TaskStackView(getContext(), stack);
167            mTaskStackView.setCallbacks(this);
168            addView(mTaskStackView);
169        }
170
171        // Update the top level view's visibilities
172        if (stack.getStackTaskCount() > 0) {
173            hideEmptyView();
174        } else {
175            showEmptyView();
176        }
177
178        // Trigger a new layout
179        requestLayout();
180    }
181
182    /**
183     * Returns whether the last task launched was in the freeform stack or not.
184     */
185    public boolean isLastTaskLaunchedFreeform() {
186        return mLastTaskLaunchedWasFreeform;
187    }
188
189    /**
190     * Returns the currently set task stack.
191     */
192    public TaskStack getTaskStack() {
193        return mStack;
194    }
195
196    /** Gets the next task in the stack - or if the last - the top task */
197    public Task getNextTaskOrTopTask(Task taskToSearch) {
198        Task returnTask = null;
199        boolean found = false;
200        if (mTaskStackView != null) {
201            TaskStack stack = mTaskStackView.getStack();
202            ArrayList<Task> taskList = stack.getStackTasks();
203            // Iterate the stack views and try and find the focused task
204            for (int j = taskList.size() - 1; j >= 0; --j) {
205                Task task = taskList.get(j);
206                // Return the next task in the line.
207                if (found)
208                    return task;
209                // Remember the first possible task as the top task.
210                if (returnTask == null)
211                    returnTask = task;
212                if (task == taskToSearch)
213                    found = true;
214            }
215        }
216        return returnTask;
217    }
218
219    /** Launches the focused task from the first stack if possible */
220    public boolean launchFocusedTask() {
221        if (mTaskStackView != null) {
222            TaskStack stack = mTaskStackView.getStack();
223            Task task = mTaskStackView.getFocusedTask();
224            if (task != null) {
225                TaskView taskView = mTaskStackView.getChildViewForTask(task);
226                onTaskViewClicked(mTaskStackView, taskView, stack, task, false, null,
227                        INVALID_STACK_ID);
228                return true;
229            }
230        }
231        return false;
232    }
233
234    /** Launches the task that recents was launched from if possible */
235    public boolean launchPreviousTask() {
236        if (mTaskStackView != null) {
237            TaskStack stack = mTaskStackView.getStack();
238            Task task = stack.getLaunchTarget();
239            if (task != null) {
240                TaskView taskView = mTaskStackView.getChildViewForTask(task);
241                onTaskViewClicked(mTaskStackView, taskView, stack, task, false, null,
242                        INVALID_STACK_ID);
243                return true;
244            }
245        }
246        return false;
247    }
248
249    /** Launches a given task. */
250    public boolean launchTask(Task task, Rect taskBounds, int destinationStack) {
251        if (mTaskStackView != null) {
252            TaskStack stack = mTaskStackView.getStack();
253            // Iterate the stack views and try and find the given task.
254            List<TaskView> taskViews = mTaskStackView.getTaskViews();
255            int taskViewCount = taskViews.size();
256            for (int j = 0; j < taskViewCount; j++) {
257                TaskView tv = taskViews.get(j);
258                if (tv.getTask() == task) {
259                    onTaskViewClicked(mTaskStackView, tv, stack, task, false,
260                            taskBounds, destinationStack);
261                    return true;
262                }
263            }
264        }
265        return false;
266    }
267
268    /** Requests all task stacks to start their enter-recents animation */
269    public void startEnterRecentsAnimation(ViewAnimation.TaskViewEnterContext ctx) {
270        // We have to increment/decrement the post animation trigger in case there are no children
271        // to ensure that it runs
272        ctx.postAnimationTrigger.increment();
273        if (mTaskStackView != null) {
274            mTaskStackView.startEnterRecentsAnimation(ctx);
275        }
276        ctx.postAnimationTrigger.decrement();
277    }
278
279    /** Requests all task stacks to start their exit-recents animation */
280    public void startExitToHomeAnimation(ViewAnimation.TaskViewExitContext ctx) {
281        // We have to increment/decrement the post animation trigger in case there are no children
282        // to ensure that it runs
283        ctx.postAnimationTrigger.increment();
284        if (mTaskStackView != null) {
285            mTaskStackView.startExitToHomeAnimation(ctx);
286        }
287        ctx.postAnimationTrigger.decrement();
288
289        // Hide the history button
290        int taskViewExitToHomeDuration = getResources().getInteger(
291                R.integer.recents_task_exit_to_home_duration);
292        hideHistoryButton(taskViewExitToHomeDuration);
293
294        // If we are going home, cancel the previous task's window transition
295        EventBus.getDefault().send(new CancelEnterRecentsWindowAnimationEvent(null));
296
297        // Notify sof the exit animation
298        EventBus.getDefault().send(new DismissRecentsToHomeAnimationStarted());
299    }
300
301    /** Adds the search bar */
302    public void setSearchBar(RecentsAppWidgetHostView searchBar) {
303        // Remove the previous search bar if one exists
304        if (mSearchBar != null && indexOfChild(mSearchBar) > -1) {
305            removeView(mSearchBar);
306        }
307        // Add the new search bar
308        if (searchBar != null) {
309            mSearchBar = searchBar;
310            addView(mSearchBar);
311        }
312    }
313
314    /** Returns whether there is currently a search bar */
315    public boolean hasValidSearchBar() {
316        return mSearchBar != null && !mSearchBar.isReinflateRequired();
317    }
318
319    /**
320     * Hides the task stack and shows the empty view.
321     */
322    public void showEmptyView() {
323        if (!RecentsDebugFlags.Static.DisableSearchBar && (mSearchBar != null)) {
324            mSearchBar.setVisibility(View.INVISIBLE);
325        }
326        mTaskStackView.setVisibility(View.INVISIBLE);
327        mEmptyView.setVisibility(View.VISIBLE);
328        mEmptyView.bringToFront();
329        mHistoryButton.bringToFront();
330    }
331
332    /**
333     * Shows the task stack and hides the empty view.
334     */
335    public void hideEmptyView() {
336        mEmptyView.setVisibility(View.INVISIBLE);
337        mTaskStackView.setVisibility(View.VISIBLE);
338        if (!RecentsDebugFlags.Static.DisableSearchBar && (mSearchBar != null)) {
339            mSearchBar.setVisibility(View.VISIBLE);
340        }
341        mTaskStackView.bringToFront();
342        if (mSearchBar != null) {
343            mSearchBar.bringToFront();
344        }
345        mHistoryButton.bringToFront();
346    }
347
348    /**
349     * Returns the last known system insets.
350     */
351    public Rect getSystemInsets() {
352        return mSystemInsets;
353    }
354
355    @Override
356    protected void onAttachedToWindow() {
357        EventBus.getDefault().register(this, RecentsActivity.EVENT_BUS_PRIORITY + 1);
358        EventBus.getDefault().register(mTouchHandler, RecentsActivity.EVENT_BUS_PRIORITY + 1);
359        super.onAttachedToWindow();
360    }
361
362    @Override
363    protected void onDetachedFromWindow() {
364        super.onDetachedFromWindow();
365        EventBus.getDefault().unregister(this);
366        EventBus.getDefault().unregister(mTouchHandler);
367    }
368
369    /**
370     * This is called with the full size of the window since we are handling our own insets.
371     */
372    @Override
373    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
374        RecentsConfiguration config = Recents.getConfiguration();
375        int width = MeasureSpec.getSize(widthMeasureSpec);
376        int height = MeasureSpec.getSize(heightMeasureSpec);
377
378        // Get the search bar bounds and measure the search bar layout
379        Rect searchBarSpaceBounds = new Rect();
380        if (mSearchBar != null) {
381            config.getSearchBarBounds(new Rect(0, 0, width, height), mSystemInsets.top,
382                    searchBarSpaceBounds);
383            mSearchBar.measure(
384                    MeasureSpec.makeMeasureSpec(searchBarSpaceBounds.width(), MeasureSpec.EXACTLY),
385                    MeasureSpec.makeMeasureSpec(searchBarSpaceBounds.height(), MeasureSpec.EXACTLY));
386        }
387
388        Rect taskStackBounds = new Rect();
389        config.getTaskStackBounds(new Rect(0, 0, width, height), mSystemInsets.top,
390                mSystemInsets.right, searchBarSpaceBounds, taskStackBounds);
391        if (mTaskStackView != null && mTaskStackView.getVisibility() != GONE) {
392            mTaskStackView.setTaskStackBounds(taskStackBounds, mSystemInsets);
393            mTaskStackView.measure(widthMeasureSpec, heightMeasureSpec);
394        }
395
396        // Measure the empty view
397        measureChild(mEmptyView, MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
398                MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
399
400        // Measure the history button with the full space above the stack, but width-constrained
401        // to the stack
402        Rect historyButtonRect = mTaskStackView.mLayoutAlgorithm.mHistoryButtonRect;
403        measureChild(mHistoryButton,
404                MeasureSpec.makeMeasureSpec(historyButtonRect.width(), MeasureSpec.EXACTLY),
405                MeasureSpec.makeMeasureSpec(historyButtonRect.height(),
406                        MeasureSpec.EXACTLY));
407
408        setMeasuredDimension(width, height);
409    }
410
411    /**
412     * This is called with the full size of the window since we are handling our own insets.
413     */
414    @Override
415    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
416        RecentsConfiguration config = Recents.getConfiguration();
417
418        // Get the search bar bounds so that we lay it out
419        Rect measuredRect = new Rect(0, 0, getMeasuredWidth(), getMeasuredHeight());
420        Rect searchBarSpaceBounds = new Rect();
421        if (mSearchBar != null) {
422            config.getSearchBarBounds(measuredRect,
423                    mSystemInsets.top, searchBarSpaceBounds);
424            mSearchBar.layout(searchBarSpaceBounds.left, searchBarSpaceBounds.top,
425                    searchBarSpaceBounds.right, searchBarSpaceBounds.bottom);
426        }
427
428        if (mTaskStackView != null && mTaskStackView.getVisibility() != GONE) {
429            mTaskStackView.layout(left, top, left + getMeasuredWidth(), top + getMeasuredHeight());
430        }
431
432        // Layout the empty view
433        mEmptyView.layout(left, top, right, bottom);
434
435        // Layout the history button left-aligned with the stack, but offset from the top of the
436        // view
437        Rect historyButtonRect = mTaskStackView.mLayoutAlgorithm.mHistoryButtonRect;
438        mHistoryButton.layout(historyButtonRect.left, historyButtonRect.top,
439                historyButtonRect.right, historyButtonRect.bottom);
440
441        if (mAwaitingFirstLayout) {
442            mAwaitingFirstLayout = false;
443
444            // If launched via dragging from the nav bar, then we should translate the whole view
445            // down offscreen
446            RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState();
447            if (launchState.launchedViaDragGesture) {
448                setTranslationY(getMeasuredHeight());
449            } else {
450                setTranslationY(0f);
451            }
452        }
453    }
454
455    @Override
456    public WindowInsets onApplyWindowInsets(WindowInsets insets) {
457        mSystemInsets.set(insets.getSystemWindowInsets());
458        requestLayout();
459        return insets;
460    }
461
462    @Override
463    public boolean onInterceptTouchEvent(MotionEvent ev) {
464        return mTouchHandler.onInterceptTouchEvent(ev);
465    }
466
467    @Override
468    public boolean onTouchEvent(MotionEvent ev) {
469        return mTouchHandler.onTouchEvent(ev);
470    }
471
472    @Override
473    protected void dispatchDraw(Canvas canvas) {
474        super.dispatchDraw(canvas);
475        for (int i = mVisibleDockStates.length - 1; i >= 0; i--) {
476            Drawable d = mVisibleDockStates[i].viewState.dockAreaOverlay;
477            if (d.getAlpha() > 0) {
478                d.draw(canvas);
479            }
480        }
481    }
482
483    @Override
484    protected boolean verifyDrawable(Drawable who) {
485        for (int i = mVisibleDockStates.length - 1; i >= 0; i--) {
486            Drawable d = mVisibleDockStates[i].viewState.dockAreaOverlay;
487            if (d == who) {
488                return true;
489            }
490        }
491        return super.verifyDrawable(who);
492    }
493
494    /**** TaskStackView.TaskStackCallbacks Implementation ****/
495
496    @Override
497    public void onTaskViewClicked(final TaskStackView stackView, final TaskView tv,
498            final TaskStack stack, final Task task, final boolean lockToTask,
499            final Rect bounds, int destinationStack) {
500        mLastTaskLaunchedWasFreeform = task.isFreeformTask();
501        mTransitionHelper.launchTaskFromRecents(stack, task, stackView, tv, lockToTask, bounds,
502                destinationStack);
503    }
504
505    /**** EventBus Events ****/
506
507    public final void onBusEvent(DragStartEvent event) {
508        updateVisibleDockRegions(mTouchHandler.getDockStatesForCurrentOrientation(),
509                TaskStack.DockState.NONE.viewState.dockAreaAlpha);
510    }
511
512    public final void onBusEvent(DragDropTargetChangedEvent event) {
513        if (event.dropTarget == null || !(event.dropTarget instanceof TaskStack.DockState)) {
514            updateVisibleDockRegions(mTouchHandler.getDockStatesForCurrentOrientation(),
515                    TaskStack.DockState.NONE.viewState.dockAreaAlpha);
516        } else {
517            final TaskStack.DockState dockState = (TaskStack.DockState) event.dropTarget;
518            updateVisibleDockRegions(new TaskStack.DockState[] {dockState}, -1);
519        }
520    }
521
522    public final void onBusEvent(final DragEndEvent event) {
523        // Animate the overlay alpha back to 0
524        updateVisibleDockRegions(null, -1);
525
526        // Handle the case where we drop onto a dock region
527        if (event.dropTarget instanceof TaskStack.DockState) {
528            final TaskStack.DockState dockState = (TaskStack.DockState) event.dropTarget;
529
530            // Remove the task after it is docked
531            event.taskView.animate()
532                    .alpha(0f)
533                    .setDuration(150)
534                    .setInterpolator(mFastOutLinearInInterpolator)
535                    .setUpdateListener(null)
536                    .setListener(null)
537                    .withEndAction(new Runnable() {
538                        @Override
539                        public void run() {
540                            mTaskStackView.getStack().removeTask(event.task);
541                        }
542                    })
543                    .withLayer()
544                    .start();
545
546            // Dock the task and launch it
547            SystemServicesProxy ssp = Recents.getSystemServices();
548            ssp.startTaskInDockedMode(event.task.key.id, dockState.createMode);
549            launchTask(event.task, null, INVALID_STACK_ID);
550
551            MetricsLogger.action(mContext,
552                    MetricsLogger.ACTION_WINDOW_DOCK_DRAG_DROP);
553        }
554    }
555
556    public final void onBusEvent(DraggingInRecentsEvent event) {
557        if (mTaskStackView.getTaskViews().size() > 0) {
558            setTranslationY(event.distanceFromTop - mTaskStackView.getTaskViews().get(0).getY());
559        }
560    }
561
562    public final void onBusEvent(DraggingInRecentsEndedEvent event) {
563        ViewPropertyAnimator animator = animate();
564        if (event.velocity > mFlingAnimationUtils.getMinVelocityPxPerSecond()) {
565            animator.translationY(getHeight());
566            animator.withEndAction(new Runnable() {
567                @Override
568                public void run() {
569                    WindowManagerProxy.getInstance().maximizeDockedStack();
570                }
571            });
572            mFlingAnimationUtils.apply(animator, getTranslationY(), getHeight(), event.velocity);
573        } else {
574            animator.translationY(0f);
575            animator.setListener(null);
576            mFlingAnimationUtils.apply(animator, getTranslationY(), 0, event.velocity);
577        }
578        animator.start();
579    }
580
581    public final void onBusEvent(ShowHistoryEvent event) {
582        // Hide the history button when the history view is shown
583        hideHistoryButton(getResources().getInteger(R.integer.recents_history_transition_duration),
584                event.postHideStackAnimationTrigger);
585        event.postHideStackAnimationTrigger.addLastDecrementRunnable(new Runnable() {
586            @Override
587            public void run() {
588                setAlpha(0f);
589            }
590        });
591    }
592
593    public final void onBusEvent(HideHistoryEvent event) {
594        // Show the history button when the history view is hidden
595        setAlpha(1f);
596        showHistoryButton(getResources().getInteger(R.integer.recents_history_transition_duration),
597                event.postHideHistoryAnimationTrigger);
598    }
599
600    public final void onBusEvent(ShowHistoryButtonEvent event) {
601        showHistoryButton(150);
602    }
603
604    public final void onBusEvent(HideHistoryButtonEvent event) {
605        hideHistoryButton(100);
606    }
607
608    public final void onBusEvent(DebugFlagsChangedEvent event) {
609        RecentsDebugFlags debugFlags = Recents.getDebugFlags();
610        if (!debugFlags.isHistoryEnabled()) {
611            hideHistoryButton(100);
612        }
613    }
614
615    /**
616     * Shows the history button.
617     */
618    private void showHistoryButton(final int duration) {
619        ReferenceCountedTrigger postAnimationTrigger = new ReferenceCountedTrigger(getContext());
620        postAnimationTrigger.increment();
621        showHistoryButton(duration, postAnimationTrigger);
622        postAnimationTrigger.decrement();
623    }
624
625    private void showHistoryButton(final int duration,
626            final ReferenceCountedTrigger postHideHistoryAnimationTrigger) {
627        RecentsDebugFlags debugFlags = Recents.getDebugFlags();
628        if (!debugFlags.isHistoryEnabled()) {
629            return;
630        }
631
632        mHistoryButton.setVisibility(View.VISIBLE);
633        mHistoryButton.setAlpha(0f);
634        postHideHistoryAnimationTrigger.addLastDecrementRunnable(new Runnable() {
635            @Override
636            public void run() {
637                mHistoryButton.animate()
638                        .alpha(1f)
639                        .setDuration(duration)
640                        .setInterpolator(mFastOutSlowInInterpolator)
641                        .withLayer()
642                        .start();
643            }
644        });
645    }
646
647    /**
648     * Hides the history button.
649     */
650    private void hideHistoryButton(int duration) {
651        ReferenceCountedTrigger postAnimationTrigger = new ReferenceCountedTrigger(getContext());
652        postAnimationTrigger.increment();
653        hideHistoryButton(duration, postAnimationTrigger);
654        postAnimationTrigger.decrement();
655    }
656
657    private void hideHistoryButton(int duration,
658            final ReferenceCountedTrigger postHideStackAnimationTrigger) {
659        mHistoryButton.animate()
660                .alpha(0f)
661                .setDuration(duration)
662                .setInterpolator(mFastOutLinearInInterpolator)
663                .withEndAction(new Runnable() {
664                    @Override
665                    public void run() {
666                        mHistoryButton.setVisibility(View.INVISIBLE);
667                        postHideStackAnimationTrigger.decrement();
668                    }
669                })
670                .withLayer()
671                .start();
672        postHideStackAnimationTrigger.increment();
673    }
674
675    /**
676     * Updates the dock region to match the specified dock state.
677     */
678    private void updateVisibleDockRegions(TaskStack.DockState[] newDockStates, int overrideAlpha) {
679        ArraySet<TaskStack.DockState> newDockStatesSet = new ArraySet<>();
680        if (newDockStates != null) {
681            for (TaskStack.DockState dockState : newDockStates) {
682                newDockStatesSet.add(dockState);
683            }
684        }
685        for (TaskStack.DockState dockState : mVisibleDockStates) {
686            TaskStack.DockState.ViewState viewState = dockState.viewState;
687            if (newDockStates == null || !newDockStatesSet.contains(dockState)) {
688                // This is no longer visible, so hide it
689                viewState.startAlphaAnimation(0, 150);
690            } else {
691                // This state is now visible, update the bounds and show it
692                int alpha = (overrideAlpha != -1 ? overrideAlpha : viewState.dockAreaAlpha);
693                viewState.dockAreaOverlay.setBounds(
694                        dockState.getDockedBounds(getMeasuredWidth(), getMeasuredHeight()));
695                viewState.dockAreaOverlay.setCallback(this);
696                viewState.startAlphaAnimation(alpha, 150);
697            }
698        }
699    }
700
701    public final void onBusEvent(RecentsVisibilityChangedEvent event) {
702        if (!event.visible) {
703            // Reset the view state
704            mAwaitingFirstLayout = true;
705            mLastTaskLaunchedWasFreeform = false;
706        }
707    }
708}
709