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