RecentsView.java revision 47c78f93c2cf9fd62b1c32ceb9717285f3f26b3b
1/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.systemui.recents.views;
18
19import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
20
21import android.animation.Animator;
22import android.animation.ObjectAnimator;
23import android.app.ActivityOptions.OnAnimationStartedListener;
24import android.content.Context;
25import android.graphics.Canvas;
26import android.graphics.Color;
27import android.graphics.Outline;
28import android.graphics.Rect;
29import android.graphics.drawable.ColorDrawable;
30import android.graphics.drawable.Drawable;
31import android.util.ArraySet;
32import android.util.AttributeSet;
33import android.view.AppTransitionAnimationSpec;
34import android.view.IAppTransitionAnimationSpecsFuture;
35import android.view.LayoutInflater;
36import android.view.MotionEvent;
37import android.view.View;
38import android.view.ViewDebug;
39import android.view.ViewOutlineProvider;
40import android.view.ViewPropertyAnimator;
41import android.view.WindowInsets;
42import android.widget.FrameLayout;
43import android.widget.TextView;
44
45import com.android.internal.logging.MetricsLogger;
46import com.android.internal.logging.MetricsProto.MetricsEvent;
47import com.android.systemui.Interpolators;
48import com.android.systemui.R;
49import com.android.systemui.recents.Recents;
50import com.android.systemui.recents.RecentsActivity;
51import com.android.systemui.recents.RecentsActivityLaunchState;
52import com.android.systemui.recents.RecentsConfiguration;
53import com.android.systemui.recents.RecentsDebugFlags;
54import com.android.systemui.recents.events.EventBus;
55import com.android.systemui.recents.events.activity.DismissRecentsToHomeAnimationStarted;
56import com.android.systemui.recents.events.activity.DockedFirstAnimationFrameEvent;
57import com.android.systemui.recents.events.activity.EnterRecentsWindowAnimationCompletedEvent;
58import com.android.systemui.recents.events.activity.HideStackActionButtonEvent;
59import com.android.systemui.recents.events.activity.LaunchTaskEvent;
60import com.android.systemui.recents.events.activity.ShowStackActionButtonEvent;
61import com.android.systemui.recents.events.ui.AllTaskViewsDismissedEvent;
62import com.android.systemui.recents.events.ui.DismissAllTaskViewsEvent;
63import com.android.systemui.recents.events.ui.DraggingInRecentsEndedEvent;
64import com.android.systemui.recents.events.ui.DraggingInRecentsEvent;
65import com.android.systemui.recents.events.ui.dragndrop.DragDropTargetChangedEvent;
66import com.android.systemui.recents.events.ui.dragndrop.DragEndEvent;
67import com.android.systemui.recents.events.ui.dragndrop.DragStartEvent;
68import com.android.systemui.recents.misc.ReferenceCountedTrigger;
69import com.android.systemui.recents.misc.SystemServicesProxy;
70import com.android.systemui.recents.misc.Utilities;
71import com.android.systemui.recents.model.Task;
72import com.android.systemui.recents.model.TaskStack;
73import com.android.systemui.recents.views.RecentsTransitionHelper.AnimationSpecComposer;
74import com.android.systemui.stackdivider.WindowManagerProxy;
75import com.android.systemui.statusbar.FlingAnimationUtils;
76
77import java.util.ArrayList;
78import java.util.List;
79
80/**
81 * This view is the the top level layout that contains TaskStacks (which are laid out according
82 * to their SpaceNode bounds.
83 */
84public class RecentsView extends FrameLayout {
85
86    private static final int DOCK_AREA_OVERLAY_TRANSITION_DURATION = 135;
87    private static final int DEFAULT_UPDATE_SCRIM_DURATION = 200;
88    private static final float DEFAULT_SCRIM_ALPHA = 0.33f;
89
90    private static final int SHOW_STACK_ACTION_BUTTON_DURATION = 150;
91    private static final int HIDE_STACK_ACTION_BUTTON_DURATION = 100;
92
93    private TaskStack mStack;
94    private TaskStackView mTaskStackView;
95    private TextView mStackActionButton;
96    private TextView mEmptyView;
97
98    private boolean mAwaitingFirstLayout = true;
99    private boolean mLastTaskLaunchedWasFreeform;
100
101    @ViewDebug.ExportedProperty(category="recents")
102    private Rect mSystemInsets = new Rect();
103    private int mDividerSize;
104
105    private Drawable mBackgroundScrim = new ColorDrawable(
106            Color.argb((int) (DEFAULT_SCRIM_ALPHA * 255), 0, 0, 0)).mutate();
107    private Animator mBackgroundScrimAnimator;
108
109    private RecentsTransitionHelper mTransitionHelper;
110    @ViewDebug.ExportedProperty(deepExport=true, prefix="touch_")
111    private RecentsViewTouchHandler mTouchHandler;
112    private final FlingAnimationUtils mFlingAnimationUtils;
113
114    public RecentsView(Context context) {
115        this(context, null);
116    }
117
118    public RecentsView(Context context, AttributeSet attrs) {
119        this(context, attrs, 0);
120    }
121
122    public RecentsView(Context context, AttributeSet attrs, int defStyleAttr) {
123        this(context, attrs, defStyleAttr, 0);
124    }
125
126    public RecentsView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
127        super(context, attrs, defStyleAttr, defStyleRes);
128        setWillNotDraw(false);
129
130        SystemServicesProxy ssp = Recents.getSystemServices();
131        mTransitionHelper = new RecentsTransitionHelper(getContext());
132        mDividerSize = ssp.getDockedDividerSize(context);
133        mTouchHandler = new RecentsViewTouchHandler(this);
134        mFlingAnimationUtils = new FlingAnimationUtils(context, 0.3f);
135
136        LayoutInflater inflater = LayoutInflater.from(context);
137        if (RecentsDebugFlags.Static.EnableStackActionButton) {
138            float cornerRadius = context.getResources().getDimensionPixelSize(
139                    R.dimen.recents_task_view_rounded_corners_radius);
140            mStackActionButton = (TextView) inflater.inflate(R.layout.recents_stack_action_button,
141                    this, false);
142            mStackActionButton.forceHasOverlappingRendering(false);
143            mStackActionButton.setOnClickListener(new View.OnClickListener() {
144                @Override
145                public void onClick(View v) {
146                    EventBus.getDefault().send(new DismissAllTaskViewsEvent());
147                }
148            });
149            addView(mStackActionButton);
150            mStackActionButton.setClipToOutline(true);
151            mStackActionButton.setOutlineProvider(new ViewOutlineProvider() {
152                @Override
153                public void getOutline(View view, Outline outline) {
154                    outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(), cornerRadius);
155                }
156            });
157        }
158        mEmptyView = (TextView) inflater.inflate(R.layout.recents_empty, this, false);
159        addView(mEmptyView);
160    }
161
162    /**
163     * Called from RecentsActivity when it is relaunched.
164     */
165    public void onReload(boolean isResumingFromVisible, boolean isTaskStackEmpty) {
166        RecentsConfiguration config = Recents.getConfiguration();
167        RecentsActivityLaunchState launchState = config.getLaunchState();
168
169        if (mTaskStackView == null) {
170            isResumingFromVisible = false;
171            mTaskStackView = new TaskStackView(getContext());
172            mTaskStackView.setSystemInsets(mSystemInsets);
173            addView(mTaskStackView);
174        }
175
176        // Reset the state
177        mAwaitingFirstLayout = !isResumingFromVisible;
178        mLastTaskLaunchedWasFreeform = false;
179
180        // Update the stack
181        mTaskStackView.onReload(isResumingFromVisible);
182
183        if (isResumingFromVisible) {
184            // If we are already visible, then restore the background scrim
185            animateBackgroundScrim(1f, DEFAULT_UPDATE_SCRIM_DURATION);
186        } else {
187            // If we are already occluded by the app, then set the final background scrim alpha now.
188            // Otherwise, defer until the enter animation completes to animate the scrim alpha with
189            // the tasks for the home animation.
190            if (launchState.launchedViaDockGesture || launchState.launchedFromApp
191                    || isTaskStackEmpty) {
192                mBackgroundScrim.setAlpha(255);
193            } else {
194                mBackgroundScrim.setAlpha(0);
195            }
196        }
197    }
198
199    /**
200     * Called from RecentsActivity when the task stack is updated.
201     */
202    public void updateStack(TaskStack stack) {
203        mStack = stack;
204        mTaskStackView.setTasks(stack, true /* allowNotifyStackChanges */);
205
206        // Update the top level view's visibilities
207        if (stack.getTaskCount() > 0) {
208            hideEmptyView();
209        } else {
210            showEmptyView(R.string.recents_empty_message);
211        }
212    }
213
214    /**
215     * Returns the current TaskStack.
216     */
217    public TaskStack getStack() {
218        return mStack;
219    }
220
221    /*
222     * Returns the window background scrim.
223     */
224    public Drawable getBackgroundScrim() {
225        return mBackgroundScrim;
226    }
227
228    /**
229     * Returns whether the last task launched was in the freeform stack or not.
230     */
231    public boolean isLastTaskLaunchedFreeform() {
232        return mLastTaskLaunchedWasFreeform;
233    }
234
235    /** Launches the focused task from the first stack if possible */
236    public boolean launchFocusedTask(int logEvent) {
237        if (mTaskStackView != null) {
238            Task task = mTaskStackView.getFocusedTask();
239            if (task != null) {
240                TaskView taskView = mTaskStackView.getChildViewForTask(task);
241                EventBus.getDefault().send(new LaunchTaskEvent(taskView, task, null,
242                        INVALID_STACK_ID, false));
243
244                if (logEvent != 0) {
245                    MetricsLogger.action(getContext(), logEvent,
246                            task.key.getComponent().toString());
247                }
248                return true;
249            }
250        }
251        return false;
252    }
253
254    /** Launches the task that recents was launched from if possible */
255    public boolean launchPreviousTask() {
256        if (mTaskStackView != null) {
257            TaskStack stack = mTaskStackView.getStack();
258            Task task = stack.getLaunchTarget();
259            if (task != null) {
260                TaskView taskView = mTaskStackView.getChildViewForTask(task);
261                EventBus.getDefault().send(new LaunchTaskEvent(taskView, task, null,
262                        INVALID_STACK_ID, false));
263                return true;
264            }
265        }
266        return false;
267    }
268
269    /** Launches a given task. */
270    public boolean launchTask(Task task, Rect taskBounds, int destinationStack) {
271        if (mTaskStackView != null) {
272            // Iterate the stack views and try and find the given task.
273            List<TaskView> taskViews = mTaskStackView.getTaskViews();
274            int taskViewCount = taskViews.size();
275            for (int j = 0; j < taskViewCount; j++) {
276                TaskView tv = taskViews.get(j);
277                if (tv.getTask() == task) {
278                    EventBus.getDefault().send(new LaunchTaskEvent(tv, task, taskBounds,
279                            destinationStack, false));
280                    return true;
281                }
282            }
283        }
284        return false;
285    }
286
287    /**
288     * Hides the task stack and shows the empty view.
289     */
290    public void showEmptyView(int msgResId) {
291        mTaskStackView.setVisibility(View.INVISIBLE);
292        mEmptyView.setText(msgResId);
293        mEmptyView.setVisibility(View.VISIBLE);
294        mEmptyView.bringToFront();
295        if (RecentsDebugFlags.Static.EnableStackActionButton) {
296            mStackActionButton.bringToFront();
297        }
298    }
299
300    /**
301     * Shows the task stack and hides the empty view.
302     */
303    public void hideEmptyView() {
304        mEmptyView.setVisibility(View.INVISIBLE);
305        mTaskStackView.setVisibility(View.VISIBLE);
306        mTaskStackView.bringToFront();
307        if (RecentsDebugFlags.Static.EnableStackActionButton) {
308            mStackActionButton.bringToFront();
309        }
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 + 2);
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        int width = MeasureSpec.getSize(widthMeasureSpec);
332        int height = MeasureSpec.getSize(heightMeasureSpec);
333
334        if (mTaskStackView.getVisibility() != GONE) {
335            mTaskStackView.measure(widthMeasureSpec, heightMeasureSpec);
336        }
337
338        // Measure the empty view to the full size of the screen
339        if (mEmptyView.getVisibility() != GONE) {
340            measureChild(mEmptyView, MeasureSpec.makeMeasureSpec(width, MeasureSpec.AT_MOST),
341                    MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST));
342        }
343
344        if (RecentsDebugFlags.Static.EnableStackActionButton) {
345            // Measure the stack action button within the constraints of the space above the stack
346            Rect buttonBounds = mTaskStackView.mLayoutAlgorithm.mStackActionButtonRect;
347            measureChild(mStackActionButton,
348                    MeasureSpec.makeMeasureSpec(buttonBounds.width(), MeasureSpec.AT_MOST),
349                    MeasureSpec.makeMeasureSpec(buttonBounds.height(), MeasureSpec.AT_MOST));
350        }
351
352        setMeasuredDimension(width, height);
353    }
354
355    /**
356     * This is called with the full size of the window since we are handling our own insets.
357     */
358    @Override
359    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
360        if (mTaskStackView.getVisibility() != GONE) {
361            mTaskStackView.layout(left, top, left + getMeasuredWidth(), top + getMeasuredHeight());
362        }
363
364        // Layout the empty view
365        if (mEmptyView.getVisibility() != GONE) {
366            int leftRightInsets = mSystemInsets.left + mSystemInsets.right;
367            int topBottomInsets = mSystemInsets.top + mSystemInsets.bottom;
368            int childWidth = mEmptyView.getMeasuredWidth();
369            int childHeight = mEmptyView.getMeasuredHeight();
370            int childLeft = left + Math.max(0, (right - left - leftRightInsets - childWidth)) / 2;
371            int childTop = top + Math.max(0, (bottom - top - topBottomInsets - childHeight)) / 2;
372            mEmptyView.layout(childLeft, childTop, childLeft + childWidth, childTop + childHeight);
373        }
374
375        if (RecentsDebugFlags.Static.EnableStackActionButton) {
376            // Layout the stack action button such that its drawable is start-aligned with the
377            // stack, vertically centered in the available space above the stack
378            Rect buttonBounds = getStackActionButtonBoundsFromStackLayout();
379            mStackActionButton.layout(buttonBounds.left, buttonBounds.top, buttonBounds.right,
380                    buttonBounds.bottom);
381        }
382
383        if (mAwaitingFirstLayout) {
384            mAwaitingFirstLayout = false;
385
386            // If launched via dragging from the nav bar, then we should translate the whole view
387            // down offscreen
388            RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState();
389            if (launchState.launchedViaDragGesture) {
390                setTranslationY(getMeasuredHeight());
391            } else {
392                setTranslationY(0f);
393            }
394        }
395    }
396
397    @Override
398    public WindowInsets onApplyWindowInsets(WindowInsets insets) {
399        mSystemInsets.set(insets.getSystemWindowInsets());
400        mTaskStackView.setSystemInsets(mSystemInsets);
401        requestLayout();
402        return insets;
403    }
404
405    @Override
406    public boolean onInterceptTouchEvent(MotionEvent ev) {
407        return mTouchHandler.onInterceptTouchEvent(ev);
408    }
409
410    @Override
411    public boolean onTouchEvent(MotionEvent ev) {
412        return mTouchHandler.onTouchEvent(ev);
413    }
414
415    @Override
416    public void onDrawForeground(Canvas canvas) {
417        super.onDrawForeground(canvas);
418
419        ArrayList<TaskStack.DockState> visDockStates = mTouchHandler.getVisibleDockStates();
420        for (int i = visDockStates.size() - 1; i >= 0; i--) {
421            Drawable d = visDockStates.get(i).viewState.dockAreaOverlay;
422            if (d.getAlpha() > 0) {
423                d.draw(canvas);
424            }
425        }
426    }
427
428    @Override
429    protected boolean verifyDrawable(Drawable who) {
430        ArrayList<TaskStack.DockState> visDockStates = mTouchHandler.getVisibleDockStates();
431        for (int i = visDockStates.size() - 1; i >= 0; i--) {
432            Drawable d = visDockStates.get(i).viewState.dockAreaOverlay;
433            if (d == who) {
434                return true;
435            }
436        }
437        return super.verifyDrawable(who);
438    }
439
440    /**** EventBus Events ****/
441
442    public final void onBusEvent(LaunchTaskEvent event) {
443        mLastTaskLaunchedWasFreeform = event.task.isFreeformTask();
444        mTransitionHelper.launchTaskFromRecents(mStack, event.task, mTaskStackView, event.taskView,
445                event.screenPinningRequested, event.targetTaskBounds, event.targetTaskStack);
446    }
447
448    public final void onBusEvent(DismissRecentsToHomeAnimationStarted event) {
449        int taskViewExitToHomeDuration = TaskStackAnimationHelper.EXIT_TO_HOME_TRANSLATION_DURATION;
450        if (RecentsDebugFlags.Static.EnableStackActionButton) {
451            // Hide the stack action button
452            hideStackActionButton(taskViewExitToHomeDuration, false /* translate */);
453        }
454        animateBackgroundScrim(0f, taskViewExitToHomeDuration);
455    }
456
457    public final void onBusEvent(DragStartEvent event) {
458        updateVisibleDockRegions(mTouchHandler.getDockStatesForCurrentOrientation(),
459                true /* isDefaultDockState */, TaskStack.DockState.NONE.viewState.dockAreaAlpha,
460                true /* animateAlpha */, false /* animateBounds */);
461    }
462
463    public final void onBusEvent(DragDropTargetChangedEvent event) {
464        if (event.dropTarget == null || !(event.dropTarget instanceof TaskStack.DockState)) {
465            updateVisibleDockRegions(mTouchHandler.getDockStatesForCurrentOrientation(),
466                    true /* isDefaultDockState */, TaskStack.DockState.NONE.viewState.dockAreaAlpha,
467                    true /* animateAlpha */, true /* animateBounds */);
468        } else {
469            final TaskStack.DockState dockState = (TaskStack.DockState) event.dropTarget;
470            updateVisibleDockRegions(new TaskStack.DockState[] {dockState},
471                    false /* isDefaultDockState */, -1, true /* animateAlpha */,
472                    true /* animateBounds */);
473        }
474        if (mStackActionButton != null) {
475            event.addPostAnimationCallback(new Runnable() {
476                @Override
477                public void run() {
478                    // Move the clear all button to its new position
479                    Rect buttonBounds = getStackActionButtonBoundsFromStackLayout();
480                    mStackActionButton.setLeftTopRightBottom(buttonBounds.left, buttonBounds.top,
481                            buttonBounds.right, buttonBounds.bottom);
482                }
483            });
484        }
485    }
486
487    public final void onBusEvent(final DragEndEvent event) {
488        // Handle the case where we drop onto a dock region
489        if (event.dropTarget instanceof TaskStack.DockState) {
490            final TaskStack.DockState dockState = (TaskStack.DockState) event.dropTarget;
491
492            // Hide the dock region
493            updateVisibleDockRegions(null, false /* isDefaultDockState */, -1,
494                    false /* animateAlpha */, false /* animateBounds */);
495
496            TaskStackLayoutAlgorithm stackLayout = mTaskStackView.getStackAlgorithm();
497            TaskStackViewScroller stackScroller = mTaskStackView.getScroller();
498            TaskViewTransform tmpTransform = new TaskViewTransform();
499
500            // We translated the view but we need to animate it back from the current layout-space
501            // rect to its final layout-space rect
502            int x = (int) event.taskView.getTranslationX();
503            int y = (int) event.taskView.getTranslationY();
504            Rect taskViewRect = new Rect(event.taskView.getLeft(), event.taskView.getTop(),
505                    event.taskView.getRight(), event.taskView.getBottom());
506            taskViewRect.offset(x, y);
507            event.taskView.setTranslationX(0);
508            event.taskView.setTranslationY(0);
509            event.taskView.setLeftTopRightBottom(taskViewRect.left, taskViewRect.top,
510                    taskViewRect.right, taskViewRect.bottom);
511
512            final OnAnimationStartedListener startedListener = new OnAnimationStartedListener() {
513                @Override
514                public void onAnimationStarted() {
515                    EventBus.getDefault().send(new DockedFirstAnimationFrameEvent());
516                    mTaskStackView.getStack().removeTask(event.task, AnimationProps.IMMEDIATE,
517                            true /* fromDockGesture */);
518                }
519            };
520
521            // Dock the task and launch it
522            SystemServicesProxy ssp = Recents.getSystemServices();
523            ssp.startTaskInDockedMode(event.task.key.id, dockState.createMode);
524            final Rect taskRect = getTaskRect(event.taskView);
525            IAppTransitionAnimationSpecsFuture future = mTransitionHelper.getAppTransitionFuture(
526                    new AnimationSpecComposer() {
527                        @Override
528                        public List<AppTransitionAnimationSpec> composeSpecs() {
529                            return mTransitionHelper.composeDockAnimationSpec(
530                                    event.taskView, taskRect);
531                        }
532                    });
533            ssp.overridePendingAppTransitionMultiThumbFuture(future,
534                    mTransitionHelper.wrapStartedListener(startedListener),
535                    true /* scaleUp */);
536
537            MetricsLogger.action(mContext, MetricsEvent.ACTION_WINDOW_DOCK_DRAG_DROP);
538        } else {
539            // Animate the overlay alpha back to 0
540            updateVisibleDockRegions(null, true /* isDefaultDockState */, -1,
541                    true /* animateAlpha */, false /* animateBounds */);
542        }
543    }
544
545    private Rect getTaskRect(TaskView taskView) {
546        int[] location = taskView.getLocationOnScreen();
547        int viewX = location[0];
548        int viewY = location[1];
549        return new Rect(viewX, viewY,
550                (int) (viewX + taskView.getWidth() * taskView.getScaleX()),
551                (int) (viewY + taskView.getHeight() * taskView.getScaleY()));
552    }
553
554    public final void onBusEvent(DraggingInRecentsEvent event) {
555        if (mTaskStackView.getTaskViews().size() > 0) {
556            setTranslationY(event.distanceFromTop - mTaskStackView.getTaskViews().get(0).getY());
557        }
558    }
559
560    public final void onBusEvent(DraggingInRecentsEndedEvent event) {
561        ViewPropertyAnimator animator = animate();
562        if (event.velocity > mFlingAnimationUtils.getMinVelocityPxPerSecond()) {
563            animator.translationY(getHeight());
564            animator.withEndAction(new Runnable() {
565                @Override
566                public void run() {
567                    WindowManagerProxy.getInstance().maximizeDockedStack();
568                }
569            });
570            mFlingAnimationUtils.apply(animator, getTranslationY(), getHeight(), event.velocity);
571        } else {
572            animator.translationY(0f);
573            animator.setListener(null);
574            mFlingAnimationUtils.apply(animator, getTranslationY(), 0, event.velocity);
575        }
576        animator.start();
577    }
578
579    public final void onBusEvent(EnterRecentsWindowAnimationCompletedEvent event) {
580        RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState();
581        if (!launchState.launchedViaDockGesture && !launchState.launchedFromApp
582                && mStack.getTaskCount() > 0) {
583            animateBackgroundScrim(1f,
584                    TaskStackAnimationHelper.ENTER_FROM_HOME_TRANSLATION_DURATION);
585        }
586    }
587
588    public final void onBusEvent(AllTaskViewsDismissedEvent event) {
589        hideStackActionButton(HIDE_STACK_ACTION_BUTTON_DURATION, true /* translate */);
590    }
591
592    public final void onBusEvent(DismissAllTaskViewsEvent event) {
593        SystemServicesProxy ssp = Recents.getSystemServices();
594        if (!ssp.hasDockedTask()) {
595            // Animate the background away only if we are dismissing Recents to home
596            animateBackgroundScrim(0f, DEFAULT_UPDATE_SCRIM_DURATION);
597        }
598    }
599
600    public final void onBusEvent(ShowStackActionButtonEvent event) {
601        if (!RecentsDebugFlags.Static.EnableStackActionButton) {
602            return;
603        }
604
605        showStackActionButton(SHOW_STACK_ACTION_BUTTON_DURATION, event.translate);
606    }
607
608    public final void onBusEvent(HideStackActionButtonEvent event) {
609        if (!RecentsDebugFlags.Static.EnableStackActionButton) {
610            return;
611        }
612
613        hideStackActionButton(HIDE_STACK_ACTION_BUTTON_DURATION, true /* translate */);
614    }
615
616    /**
617     * Shows the stack action button.
618     */
619    private void showStackActionButton(final int duration, final boolean translate) {
620        if (!RecentsDebugFlags.Static.EnableStackActionButton) {
621            return;
622        }
623
624        final ReferenceCountedTrigger postAnimationTrigger = new ReferenceCountedTrigger();
625        if (mStackActionButton.getVisibility() == View.INVISIBLE) {
626            mStackActionButton.setVisibility(View.VISIBLE);
627            mStackActionButton.setAlpha(0f);
628            if (translate) {
629                mStackActionButton.setTranslationY(-mStackActionButton.getMeasuredHeight() * 0.25f);
630            } else {
631                mStackActionButton.setTranslationY(0f);
632            }
633            postAnimationTrigger.addLastDecrementRunnable(new Runnable() {
634                @Override
635                public void run() {
636                    if (translate) {
637                        mStackActionButton.animate()
638                            .translationY(0f);
639                    }
640                    mStackActionButton.animate()
641                            .alpha(1f)
642                            .setDuration(duration)
643                            .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
644                            .start();
645                }
646            });
647        }
648        postAnimationTrigger.flushLastDecrementRunnables();
649    }
650
651    /**
652     * Hides the stack action button.
653     */
654    private void hideStackActionButton(int duration, boolean translate) {
655        if (!RecentsDebugFlags.Static.EnableStackActionButton) {
656            return;
657        }
658
659        final ReferenceCountedTrigger postAnimationTrigger = new ReferenceCountedTrigger();
660        hideStackActionButton(duration, translate, postAnimationTrigger);
661        postAnimationTrigger.flushLastDecrementRunnables();
662    }
663
664    /**
665     * Hides the stack action button.
666     */
667    private void hideStackActionButton(int duration, boolean translate,
668                                       final ReferenceCountedTrigger postAnimationTrigger) {
669        if (!RecentsDebugFlags.Static.EnableStackActionButton) {
670            return;
671        }
672
673        if (mStackActionButton.getVisibility() == View.VISIBLE) {
674            if (translate) {
675                mStackActionButton.animate()
676                    .translationY(-mStackActionButton.getMeasuredHeight() * 0.25f);
677            }
678            mStackActionButton.animate()
679                    .alpha(0f)
680                    .setDuration(duration)
681                    .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
682                    .withEndAction(new Runnable() {
683                        @Override
684                        public void run() {
685                            mStackActionButton.setVisibility(View.INVISIBLE);
686                            postAnimationTrigger.decrement();
687                        }
688                    })
689                    .start();
690            postAnimationTrigger.increment();
691        }
692    }
693
694    /**
695     * Updates the dock region to match the specified dock state.
696     */
697    private void updateVisibleDockRegions(TaskStack.DockState[] newDockStates,
698            boolean isDefaultDockState, int overrideAlpha, boolean animateAlpha,
699            boolean animateBounds) {
700        ArraySet<TaskStack.DockState> newDockStatesSet = Utilities.arrayToSet(newDockStates,
701                new ArraySet<TaskStack.DockState>());
702        ArrayList<TaskStack.DockState> visDockStates = mTouchHandler.getVisibleDockStates();
703        for (int i = visDockStates.size() - 1; i >= 0; i--) {
704            TaskStack.DockState dockState = visDockStates.get(i);
705            TaskStack.DockState.ViewState viewState = dockState.viewState;
706            if (newDockStates == null || !newDockStatesSet.contains(dockState)) {
707                // This is no longer visible, so hide it
708                viewState.startAnimation(null, 0, DOCK_AREA_OVERLAY_TRANSITION_DURATION,
709                        Interpolators.ALPHA_OUT, animateAlpha, animateBounds);
710            } else {
711                // This state is now visible, update the bounds and show it
712                int alpha = (overrideAlpha != -1 ? overrideAlpha : viewState.dockAreaAlpha);
713                Rect bounds = isDefaultDockState
714                        ? dockState.getPreDockedBounds(getMeasuredWidth(), getMeasuredHeight())
715                        : dockState.getDockedBounds(getMeasuredWidth(), getMeasuredHeight(),
716                        mDividerSize, mSystemInsets, getResources());
717                if (viewState.dockAreaOverlay.getCallback() != this) {
718                    viewState.dockAreaOverlay.setCallback(this);
719                    viewState.dockAreaOverlay.setBounds(bounds);
720                }
721                viewState.startAnimation(bounds, alpha, DOCK_AREA_OVERLAY_TRANSITION_DURATION,
722                        Interpolators.ALPHA_IN, animateAlpha, animateBounds);
723            }
724        }
725    }
726
727    /**
728     * Animates the background scrim to the given {@param alpha}.
729     */
730    private void animateBackgroundScrim(float alpha, int duration) {
731        Utilities.cancelAnimationWithoutCallbacks(mBackgroundScrimAnimator);
732        // Calculate the absolute alpha to animate from
733        int fromAlpha = (int) ((mBackgroundScrim.getAlpha() / (DEFAULT_SCRIM_ALPHA * 255)) * 255);
734        int toAlpha = (int) (alpha * 255);
735        mBackgroundScrimAnimator = ObjectAnimator.ofInt(mBackgroundScrim, Utilities.DRAWABLE_ALPHA,
736                fromAlpha, toAlpha);
737        mBackgroundScrimAnimator.setDuration(duration);
738        mBackgroundScrimAnimator.setInterpolator(toAlpha > fromAlpha
739                ? Interpolators.ALPHA_IN
740                : Interpolators.ALPHA_OUT);
741        mBackgroundScrimAnimator.start();
742    }
743
744    /**
745     * @return the bounds of the stack action button.
746     */
747    private Rect getStackActionButtonBoundsFromStackLayout() {
748        Rect actionButtonRect = new Rect(mTaskStackView.mLayoutAlgorithm.mStackActionButtonRect);
749        int left = isLayoutRtl()
750                ? actionButtonRect.left - mStackActionButton.getPaddingLeft()
751                : actionButtonRect.right + mStackActionButton.getPaddingRight()
752                        - mStackActionButton.getMeasuredWidth();
753        int top = actionButtonRect.top +
754                (actionButtonRect.height() - mStackActionButton.getMeasuredHeight()) / 2;
755        actionButtonRect.set(left, top, left + mStackActionButton.getMeasuredWidth(),
756                top + mStackActionButton.getMeasuredHeight());
757        return actionButtonRect;
758    }
759}
760