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