RecentsView.java revision b34b7deb1e5dacab2b3dd54dfd63918ce139083f
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.MultiWindowStateChangedEvent;
61import com.android.systemui.recents.events.activity.ShowStackActionButtonEvent;
62import com.android.systemui.recents.events.ui.AllTaskViewsDismissedEvent;
63import com.android.systemui.recents.events.ui.DismissAllTaskViewsEvent;
64import com.android.systemui.recents.events.ui.DraggingInRecentsEndedEvent;
65import com.android.systemui.recents.events.ui.DraggingInRecentsEvent;
66import com.android.systemui.recents.events.ui.dragndrop.DragDropTargetChangedEvent;
67import com.android.systemui.recents.events.ui.dragndrop.DragEndCancelledEvent;
68import com.android.systemui.recents.events.ui.dragndrop.DragEndEvent;
69import com.android.systemui.recents.events.ui.dragndrop.DragStartEvent;
70import com.android.systemui.recents.misc.ReferenceCountedTrigger;
71import com.android.systemui.recents.misc.SystemServicesProxy;
72import com.android.systemui.recents.misc.Utilities;
73import com.android.systemui.recents.model.Task;
74import com.android.systemui.recents.model.TaskStack;
75import com.android.systemui.recents.views.RecentsTransitionHelper.AnimationSpecComposer;
76import com.android.systemui.stackdivider.WindowManagerProxy;
77import com.android.systemui.statusbar.FlingAnimationUtils;
78
79import java.io.FileDescriptor;
80import java.io.PrintWriter;
81import java.util.ArrayList;
82import java.util.List;
83
84/**
85 * This view is the the top level layout that contains TaskStacks (which are laid out according
86 * to their SpaceNode bounds.
87 */
88public class RecentsView extends FrameLayout {
89
90    private static final String TAG = "RecentsView";
91
92    private static final int DEFAULT_UPDATE_SCRIM_DURATION = 200;
93    private static final float DEFAULT_SCRIM_ALPHA = 0.33f;
94
95    private static final int SHOW_STACK_ACTION_BUTTON_DURATION = 134;
96    private static final int HIDE_STACK_ACTION_BUTTON_DURATION = 100;
97
98    private TaskStack mStack;
99    private TaskStackView mTaskStackView;
100    private TextView mStackActionButton;
101    private TextView mEmptyView;
102
103    private boolean mAwaitingFirstLayout = true;
104    private boolean mLastTaskLaunchedWasFreeform;
105
106    @ViewDebug.ExportedProperty(category="recents")
107    private Rect mSystemInsets = new Rect();
108    private int mDividerSize;
109
110    private Drawable mBackgroundScrim = new ColorDrawable(
111            Color.argb((int) (DEFAULT_SCRIM_ALPHA * 255), 0, 0, 0)).mutate();
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        mTransitionHelper = new RecentsTransitionHelper(getContext());
137        mDividerSize = ssp.getDockedDividerSize(context);
138        mTouchHandler = new RecentsViewTouchHandler(this);
139        mFlingAnimationUtils = new FlingAnimationUtils(context, 0.3f);
140
141        LayoutInflater inflater = LayoutInflater.from(context);
142        if (RecentsDebugFlags.Static.EnableStackActionButton) {
143            mStackActionButton = (TextView) inflater.inflate(R.layout.recents_stack_action_button,
144                    this, false);
145            mStackActionButton.setOnClickListener(new View.OnClickListener() {
146                @Override
147                public void onClick(View v) {
148                    EventBus.getDefault().send(new DismissAllTaskViewsEvent());
149                }
150            });
151            addView(mStackActionButton);
152        }
153        mEmptyView = (TextView) inflater.inflate(R.layout.recents_empty, this, false);
154        addView(mEmptyView);
155    }
156
157    /**
158     * Called from RecentsActivity when it is relaunched.
159     */
160    public void onReload(boolean isResumingFromVisible, boolean isTaskStackEmpty) {
161        RecentsConfiguration config = Recents.getConfiguration();
162        RecentsActivityLaunchState launchState = config.getLaunchState();
163
164        if (mTaskStackView == null) {
165            isResumingFromVisible = false;
166            mTaskStackView = new TaskStackView(getContext());
167            mTaskStackView.setSystemInsets(mSystemInsets);
168            addView(mTaskStackView);
169        }
170
171        // Reset the state
172        mAwaitingFirstLayout = !isResumingFromVisible;
173        mLastTaskLaunchedWasFreeform = false;
174
175        // Update the stack
176        mTaskStackView.onReload(isResumingFromVisible);
177
178        if (isResumingFromVisible) {
179            // If we are already visible, then restore the background scrim
180            animateBackgroundScrim(1f, DEFAULT_UPDATE_SCRIM_DURATION);
181        } else {
182            // If we are already occluded by the app, then set the final background scrim alpha now.
183            // Otherwise, defer until the enter animation completes to animate the scrim alpha with
184            // the tasks for the home animation.
185            if (launchState.launchedViaDockGesture || launchState.launchedFromApp
186                    || isTaskStackEmpty) {
187                mBackgroundScrim.setAlpha(255);
188            } else {
189                mBackgroundScrim.setAlpha(0);
190            }
191        }
192    }
193
194    /**
195     * Called from RecentsActivity when the task stack is updated.
196     */
197    public void updateStack(TaskStack stack, boolean setStackViewTasks) {
198        mStack = stack;
199        if (setStackViewTasks) {
200            mTaskStackView.setTasks(stack, true /* allowNotifyStackChanges */);
201        }
202
203        // Update the top level view's visibilities
204        if (stack.getTaskCount() > 0) {
205            hideEmptyView();
206        } else {
207            showEmptyView(R.string.recents_empty_message);
208        }
209    }
210
211    /**
212     * Returns the current TaskStack.
213     */
214    public TaskStack getStack() {
215        return mStack;
216    }
217
218    /*
219     * Returns the window background scrim.
220     */
221    public Drawable getBackgroundScrim() {
222        return mBackgroundScrim;
223    }
224
225    /**
226     * Returns whether the last task launched was in the freeform stack or not.
227     */
228    public boolean isLastTaskLaunchedFreeform() {
229        return mLastTaskLaunchedWasFreeform;
230    }
231
232    /** Launches the focused task from the first stack if possible */
233    public boolean launchFocusedTask(int logEvent) {
234        if (mTaskStackView != null) {
235            Task task = mTaskStackView.getFocusedTask();
236            if (task != null) {
237                TaskView taskView = mTaskStackView.getChildViewForTask(task);
238                EventBus.getDefault().send(new LaunchTaskEvent(taskView, task, null,
239                        INVALID_STACK_ID, false));
240
241                if (logEvent != 0) {
242                    MetricsLogger.action(getContext(), logEvent,
243                            task.key.getComponent().toString());
244                }
245                return true;
246            }
247        }
248        return false;
249    }
250
251    /** Launches the task that recents was launched from if possible */
252    public boolean launchPreviousTask() {
253        if (mTaskStackView != null) {
254            TaskStack stack = mTaskStackView.getStack();
255            Task task = stack.getLaunchTarget();
256            if (task != null) {
257                TaskView taskView = mTaskStackView.getChildViewForTask(task);
258                EventBus.getDefault().send(new LaunchTaskEvent(taskView, task, null,
259                        INVALID_STACK_ID, false));
260                return true;
261            }
262        }
263        return false;
264    }
265
266    /** Launches a given task. */
267    public boolean launchTask(Task task, Rect taskBounds, int destinationStack) {
268        if (mTaskStackView != null) {
269            // Iterate the stack views and try and find the given task.
270            List<TaskView> taskViews = mTaskStackView.getTaskViews();
271            int taskViewCount = taskViews.size();
272            for (int j = 0; j < taskViewCount; j++) {
273                TaskView tv = taskViews.get(j);
274                if (tv.getTask() == task) {
275                    EventBus.getDefault().send(new LaunchTaskEvent(tv, task, taskBounds,
276                            destinationStack, false));
277                    return true;
278                }
279            }
280        }
281        return false;
282    }
283
284    /**
285     * Hides the task stack and shows the empty view.
286     */
287    public void showEmptyView(int msgResId) {
288        mTaskStackView.setVisibility(View.INVISIBLE);
289        mEmptyView.setText(msgResId);
290        mEmptyView.setVisibility(View.VISIBLE);
291        mEmptyView.bringToFront();
292        if (RecentsDebugFlags.Static.EnableStackActionButton) {
293            mStackActionButton.bringToFront();
294        }
295    }
296
297    /**
298     * Shows the task stack and hides the empty view.
299     */
300    public void hideEmptyView() {
301        mEmptyView.setVisibility(View.INVISIBLE);
302        mTaskStackView.setVisibility(View.VISIBLE);
303        mTaskStackView.bringToFront();
304        if (RecentsDebugFlags.Static.EnableStackActionButton) {
305            mStackActionButton.bringToFront();
306        }
307    }
308
309    @Override
310    protected void onAttachedToWindow() {
311        EventBus.getDefault().register(this, RecentsActivity.EVENT_BUS_PRIORITY + 1);
312        EventBus.getDefault().register(mTouchHandler, RecentsActivity.EVENT_BUS_PRIORITY + 2);
313        super.onAttachedToWindow();
314    }
315
316    @Override
317    protected void onDetachedFromWindow() {
318        super.onDetachedFromWindow();
319        EventBus.getDefault().unregister(this);
320        EventBus.getDefault().unregister(mTouchHandler);
321    }
322
323    /**
324     * This is called with the full size of the window since we are handling our own insets.
325     */
326    @Override
327    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
328        int width = MeasureSpec.getSize(widthMeasureSpec);
329        int height = MeasureSpec.getSize(heightMeasureSpec);
330
331        if (mTaskStackView.getVisibility() != GONE) {
332            mTaskStackView.measure(widthMeasureSpec, heightMeasureSpec);
333        }
334
335        // Measure the empty view to the full size of the screen
336        if (mEmptyView.getVisibility() != GONE) {
337            measureChild(mEmptyView, MeasureSpec.makeMeasureSpec(width, MeasureSpec.AT_MOST),
338                    MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST));
339        }
340
341        if (RecentsDebugFlags.Static.EnableStackActionButton) {
342            // Measure the stack action button within the constraints of the space above the stack
343            Rect buttonBounds = mTaskStackView.mLayoutAlgorithm.mStackActionButtonRect;
344            measureChild(mStackActionButton,
345                    MeasureSpec.makeMeasureSpec(buttonBounds.width(), MeasureSpec.AT_MOST),
346                    MeasureSpec.makeMeasureSpec(buttonBounds.height(), MeasureSpec.AT_MOST));
347        }
348
349        setMeasuredDimension(width, height);
350    }
351
352    /**
353     * This is called with the full size of the window since we are handling our own insets.
354     */
355    @Override
356    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
357        if (mTaskStackView.getVisibility() != GONE) {
358            mTaskStackView.layout(left, top, left + getMeasuredWidth(), top + getMeasuredHeight());
359        }
360
361        // Layout the empty view
362        if (mEmptyView.getVisibility() != GONE) {
363            int leftRightInsets = mSystemInsets.left + mSystemInsets.right;
364            int topBottomInsets = mSystemInsets.top + mSystemInsets.bottom;
365            int childWidth = mEmptyView.getMeasuredWidth();
366            int childHeight = mEmptyView.getMeasuredHeight();
367            int childLeft = left + mSystemInsets.left +
368                    Math.max(0, (right - left - leftRightInsets - childWidth)) / 2;
369            int childTop = top + mSystemInsets.top +
370                    Math.max(0, (bottom - top - topBottomInsets - childHeight)) / 2;
371            mEmptyView.layout(childLeft, childTop, childLeft + childWidth, childTop + childHeight);
372        }
373
374        if (RecentsDebugFlags.Static.EnableStackActionButton) {
375            // Layout the stack action button such that its drawable is start-aligned with the
376            // stack, vertically centered in the available space above the stack
377            Rect buttonBounds = getStackActionButtonBoundsFromStackLayout();
378            mStackActionButton.layout(buttonBounds.left, buttonBounds.top, buttonBounds.right,
379                    buttonBounds.bottom);
380        }
381
382        if (mAwaitingFirstLayout) {
383            mAwaitingFirstLayout = false;
384
385            // If launched via dragging from the nav bar, then we should translate the whole view
386            // down offscreen
387            RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState();
388            if (launchState.launchedViaDragGesture) {
389                setTranslationY(getMeasuredHeight());
390            } else {
391                setTranslationY(0f);
392            }
393        }
394    }
395
396    @Override
397    public WindowInsets onApplyWindowInsets(WindowInsets insets) {
398        mSystemInsets.set(insets.getSystemWindowInsets());
399        mTaskStackView.setSystemInsets(mSystemInsets);
400        requestLayout();
401        return insets;
402    }
403
404    @Override
405    public boolean onInterceptTouchEvent(MotionEvent ev) {
406        return mTouchHandler.onInterceptTouchEvent(ev);
407    }
408
409    @Override
410    public boolean onTouchEvent(MotionEvent ev) {
411        return mTouchHandler.onTouchEvent(ev);
412    }
413
414    @Override
415    public void onDrawForeground(Canvas canvas) {
416        super.onDrawForeground(canvas);
417
418        ArrayList<TaskStack.DockState> visDockStates = mTouchHandler.getVisibleDockStates();
419        for (int i = visDockStates.size() - 1; i >= 0; i--) {
420            visDockStates.get(i).viewState.draw(canvas);
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                TaskStack.DockState.NONE.viewState.hintTextAlpha,
457                true /* animateAlpha */, false /* animateBounds */);
458
459        // Temporarily hide the stack action button without changing visibility
460        if (mStackActionButton != null) {
461            mStackActionButton.animate()
462                    .alpha(0f)
463                    .setDuration(HIDE_STACK_ACTION_BUTTON_DURATION)
464                    .setInterpolator(Interpolators.ALPHA_OUT)
465                    .start();
466        }
467    }
468
469    public final void onBusEvent(DragDropTargetChangedEvent event) {
470        if (event.dropTarget == null || !(event.dropTarget instanceof TaskStack.DockState)) {
471            updateVisibleDockRegions(mTouchHandler.getDockStatesForCurrentOrientation(),
472                    true /* isDefaultDockState */, TaskStack.DockState.NONE.viewState.dockAreaAlpha,
473                    TaskStack.DockState.NONE.viewState.hintTextAlpha,
474                    true /* animateAlpha */, true /* animateBounds */);
475        } else {
476            final TaskStack.DockState dockState = (TaskStack.DockState) event.dropTarget;
477            updateVisibleDockRegions(new TaskStack.DockState[] {dockState},
478                    false /* isDefaultDockState */, -1, -1, true /* animateAlpha */,
479                    true /* animateBounds */);
480        }
481        if (mStackActionButton != null) {
482            event.addPostAnimationCallback(new Runnable() {
483                @Override
484                public void run() {
485                    // Move the clear all button to its new position
486                    Rect buttonBounds = getStackActionButtonBoundsFromStackLayout();
487                    mStackActionButton.setLeftTopRightBottom(buttonBounds.left, buttonBounds.top,
488                            buttonBounds.right, buttonBounds.bottom);
489                }
490            });
491        }
492    }
493
494    public final void onBusEvent(final DragEndEvent event) {
495        // Handle the case where we drop onto a dock region
496        if (event.dropTarget instanceof TaskStack.DockState) {
497            final TaskStack.DockState dockState = (TaskStack.DockState) event.dropTarget;
498
499            // Hide the dock region
500            updateVisibleDockRegions(null, false /* isDefaultDockState */, -1, -1,
501                    false /* animateAlpha */, false /* animateBounds */);
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            Utilities.setViewFrameFromTranslation(event.taskView);
506
507            // Dock the task and launch it
508            SystemServicesProxy ssp = Recents.getSystemServices();
509            if (ssp.startTaskInDockedMode(event.task.key.id, dockState.createMode)) {
510                final OnAnimationStartedListener startedListener =
511                        new OnAnimationStartedListener() {
512                    @Override
513                    public void onAnimationStarted() {
514                        EventBus.getDefault().send(new DockedFirstAnimationFrameEvent());
515                        // Remove the task and don't bother relaying out, as all the tasks will be
516                        // relaid out when the stack changes on the multiwindow change event
517                        mTaskStackView.getStack().removeTask(event.task, null,
518                                true /* fromDockGesture */);
519                    }
520                };
521
522                final Rect taskRect = getTaskRect(event.taskView);
523                IAppTransitionAnimationSpecsFuture future =
524                        mTransitionHelper.getAppTransitionFuture(
525                                new AnimationSpecComposer() {
526                                    @Override
527                                    public List<AppTransitionAnimationSpec> composeSpecs() {
528                                        return mTransitionHelper.composeDockAnimationSpec(
529                                                event.taskView, taskRect);
530                                    }
531                                });
532                ssp.overridePendingAppTransitionMultiThumbFuture(future,
533                        mTransitionHelper.wrapStartedListener(startedListener),
534                        true /* scaleUp */);
535
536                MetricsLogger.action(mContext, MetricsEvent.ACTION_WINDOW_DOCK_DRAG_DROP,
537                        event.task.getTopComponent().flattenToShortString());
538            } else {
539                EventBus.getDefault().send(new DragEndCancelledEvent(mStack, event.task,
540                        event.taskView));
541            }
542        } else {
543            // Animate the overlay alpha back to 0
544            updateVisibleDockRegions(null, true /* isDefaultDockState */, -1, -1,
545                    true /* animateAlpha */, false /* animateBounds */);
546        }
547
548        // Show the stack action button again without changing visibility
549        if (mStackActionButton != null) {
550            mStackActionButton.animate()
551                    .alpha(1f)
552                    .setDuration(SHOW_STACK_ACTION_BUTTON_DURATION)
553                    .setInterpolator(Interpolators.ALPHA_IN)
554                    .start();
555        }
556    }
557
558    public final void onBusEvent(final DragEndCancelledEvent event) {
559        // Animate the overlay alpha back to 0
560        updateVisibleDockRegions(null, true /* isDefaultDockState */, -1, -1,
561                true /* animateAlpha */, false /* animateBounds */);
562    }
563
564    private Rect getTaskRect(TaskView taskView) {
565        int[] location = taskView.getLocationOnScreen();
566        int viewX = location[0];
567        int viewY = location[1];
568        return new Rect(viewX, viewY,
569                (int) (viewX + taskView.getWidth() * taskView.getScaleX()),
570                (int) (viewY + taskView.getHeight() * taskView.getScaleY()));
571    }
572
573    public final void onBusEvent(DraggingInRecentsEvent event) {
574        if (mTaskStackView.getTaskViews().size() > 0) {
575            setTranslationY(event.distanceFromTop - mTaskStackView.getTaskViews().get(0).getY());
576        }
577    }
578
579    public final void onBusEvent(DraggingInRecentsEndedEvent event) {
580        ViewPropertyAnimator animator = animate();
581        if (event.velocity > mFlingAnimationUtils.getMinVelocityPxPerSecond()) {
582            animator.translationY(getHeight());
583            animator.withEndAction(new Runnable() {
584                @Override
585                public void run() {
586                    WindowManagerProxy.getInstance().maximizeDockedStack();
587                }
588            });
589            mFlingAnimationUtils.apply(animator, getTranslationY(), getHeight(), event.velocity);
590        } else {
591            animator.translationY(0f);
592            animator.setListener(null);
593            mFlingAnimationUtils.apply(animator, getTranslationY(), 0, event.velocity);
594        }
595        animator.start();
596    }
597
598    public final void onBusEvent(EnterRecentsWindowAnimationCompletedEvent event) {
599        RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState();
600        if (!launchState.launchedViaDockGesture && !launchState.launchedFromApp
601                && mStack.getTaskCount() > 0) {
602            animateBackgroundScrim(1f,
603                    TaskStackAnimationHelper.ENTER_FROM_HOME_TRANSLATION_DURATION);
604        }
605    }
606
607    public final void onBusEvent(AllTaskViewsDismissedEvent event) {
608        hideStackActionButton(HIDE_STACK_ACTION_BUTTON_DURATION, true /* translate */);
609    }
610
611    public final void onBusEvent(DismissAllTaskViewsEvent event) {
612        SystemServicesProxy ssp = Recents.getSystemServices();
613        if (!ssp.hasDockedTask()) {
614            // Animate the background away only if we are dismissing Recents to home
615            animateBackgroundScrim(0f, DEFAULT_UPDATE_SCRIM_DURATION);
616        }
617    }
618
619    public final void onBusEvent(ShowStackActionButtonEvent event) {
620        if (!RecentsDebugFlags.Static.EnableStackActionButton) {
621            return;
622        }
623
624        showStackActionButton(SHOW_STACK_ACTION_BUTTON_DURATION, event.translate);
625    }
626
627    public final void onBusEvent(HideStackActionButtonEvent event) {
628        if (!RecentsDebugFlags.Static.EnableStackActionButton) {
629            return;
630        }
631
632        hideStackActionButton(HIDE_STACK_ACTION_BUTTON_DURATION, true /* translate */);
633    }
634
635    public final void onBusEvent(MultiWindowStateChangedEvent event) {
636        updateStack(event.stack, false /* setStackViewTasks */);
637    }
638
639    /**
640     * Shows the stack action button.
641     */
642    private void showStackActionButton(final int duration, final boolean translate) {
643        if (!RecentsDebugFlags.Static.EnableStackActionButton) {
644            return;
645        }
646
647        final ReferenceCountedTrigger postAnimationTrigger = new ReferenceCountedTrigger();
648        if (mStackActionButton.getVisibility() == View.INVISIBLE) {
649            mStackActionButton.setVisibility(View.VISIBLE);
650            mStackActionButton.setAlpha(0f);
651            if (translate) {
652                mStackActionButton.setTranslationY(-mStackActionButton.getMeasuredHeight() * 0.25f);
653            } else {
654                mStackActionButton.setTranslationY(0f);
655            }
656            postAnimationTrigger.addLastDecrementRunnable(new Runnable() {
657                @Override
658                public void run() {
659                    if (translate) {
660                        mStackActionButton.animate()
661                            .translationY(0f);
662                    }
663                    mStackActionButton.animate()
664                            .alpha(1f)
665                            .setDuration(duration)
666                            .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
667                            .start();
668                }
669            });
670        }
671        postAnimationTrigger.flushLastDecrementRunnables();
672    }
673
674    /**
675     * Hides the stack action button.
676     */
677    private void hideStackActionButton(int duration, boolean translate) {
678        if (!RecentsDebugFlags.Static.EnableStackActionButton) {
679            return;
680        }
681
682        final ReferenceCountedTrigger postAnimationTrigger = new ReferenceCountedTrigger();
683        hideStackActionButton(duration, translate, postAnimationTrigger);
684        postAnimationTrigger.flushLastDecrementRunnables();
685    }
686
687    /**
688     * Hides the stack action button.
689     */
690    private void hideStackActionButton(int duration, boolean translate,
691                                       final ReferenceCountedTrigger postAnimationTrigger) {
692        if (!RecentsDebugFlags.Static.EnableStackActionButton) {
693            return;
694        }
695
696        if (mStackActionButton.getVisibility() == View.VISIBLE) {
697            if (translate) {
698                mStackActionButton.animate()
699                    .translationY(-mStackActionButton.getMeasuredHeight() * 0.25f);
700            }
701            mStackActionButton.animate()
702                    .alpha(0f)
703                    .setDuration(duration)
704                    .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
705                    .withEndAction(new Runnable() {
706                        @Override
707                        public void run() {
708                            mStackActionButton.setVisibility(View.INVISIBLE);
709                            postAnimationTrigger.decrement();
710                        }
711                    })
712                    .start();
713            postAnimationTrigger.increment();
714        }
715    }
716
717    /**
718     * Updates the dock region to match the specified dock state.
719     */
720    private void updateVisibleDockRegions(TaskStack.DockState[] newDockStates,
721            boolean isDefaultDockState, int overrideAreaAlpha, int overrideHintAlpha,
722            boolean animateAlpha, boolean animateBounds) {
723        ArraySet<TaskStack.DockState> newDockStatesSet = Utilities.arrayToSet(newDockStates,
724                new ArraySet<TaskStack.DockState>());
725        ArrayList<TaskStack.DockState> visDockStates = mTouchHandler.getVisibleDockStates();
726        for (int i = visDockStates.size() - 1; i >= 0; i--) {
727            TaskStack.DockState dockState = visDockStates.get(i);
728            TaskStack.DockState.ViewState viewState = dockState.viewState;
729            if (newDockStates == null || !newDockStatesSet.contains(dockState)) {
730                // This is no longer visible, so hide it
731                viewState.startAnimation(null, 0, 0, TaskStackView.SLOW_SYNC_STACK_DURATION,
732                        Interpolators.FAST_OUT_SLOW_IN, animateAlpha, animateBounds);
733            } else {
734                // This state is now visible, update the bounds and show it
735                int areaAlpha = overrideAreaAlpha != -1
736                        ? overrideAreaAlpha
737                        : viewState.dockAreaAlpha;
738                int hintAlpha = overrideHintAlpha != -1
739                        ? overrideHintAlpha
740                        : viewState.hintTextAlpha;
741                Rect bounds = isDefaultDockState
742                        ? dockState.getPreDockedBounds(getMeasuredWidth(), getMeasuredHeight())
743                        : dockState.getDockedBounds(getMeasuredWidth(), getMeasuredHeight(),
744                        mDividerSize, mSystemInsets, getResources());
745                if (viewState.dockAreaOverlay.getCallback() != this) {
746                    viewState.dockAreaOverlay.setCallback(this);
747                    viewState.dockAreaOverlay.setBounds(bounds);
748                }
749                viewState.startAnimation(bounds, areaAlpha, hintAlpha,
750                        TaskStackView.SLOW_SYNC_STACK_DURATION, Interpolators.FAST_OUT_SLOW_IN,
751                        animateAlpha, animateBounds);
752            }
753        }
754    }
755
756    /**
757     * Animates the background scrim to the given {@param alpha}.
758     */
759    private void animateBackgroundScrim(float alpha, int duration) {
760        Utilities.cancelAnimationWithoutCallbacks(mBackgroundScrimAnimator);
761        // Calculate the absolute alpha to animate from
762        int fromAlpha = (int) ((mBackgroundScrim.getAlpha() / (DEFAULT_SCRIM_ALPHA * 255)) * 255);
763        int toAlpha = (int) (alpha * 255);
764        mBackgroundScrimAnimator = ObjectAnimator.ofInt(mBackgroundScrim, Utilities.DRAWABLE_ALPHA,
765                fromAlpha, toAlpha);
766        mBackgroundScrimAnimator.setDuration(duration);
767        mBackgroundScrimAnimator.setInterpolator(toAlpha > fromAlpha
768                ? Interpolators.ALPHA_IN
769                : Interpolators.ALPHA_OUT);
770        mBackgroundScrimAnimator.start();
771    }
772
773    /**
774     * @return the bounds of the stack action button.
775     */
776    private Rect getStackActionButtonBoundsFromStackLayout() {
777        Rect actionButtonRect = new Rect(mTaskStackView.mLayoutAlgorithm.mStackActionButtonRect);
778        int left = isLayoutRtl()
779                ? actionButtonRect.left - mStackActionButton.getPaddingLeft()
780                : actionButtonRect.right + mStackActionButton.getPaddingRight()
781                        - mStackActionButton.getMeasuredWidth();
782        int top = actionButtonRect.top +
783                (actionButtonRect.height() - mStackActionButton.getMeasuredHeight()) / 2;
784        actionButtonRect.set(left, top, left + mStackActionButton.getMeasuredWidth(),
785                top + mStackActionButton.getMeasuredHeight());
786        return actionButtonRect;
787    }
788
789    public void dump(String prefix, PrintWriter writer) {
790        String innerPrefix = prefix + "  ";
791        String id = Integer.toHexString(System.identityHashCode(this));
792
793        writer.print(prefix); writer.print(TAG);
794        writer.print(" awaitingFirstLayout="); writer.print(mAwaitingFirstLayout ? "Y" : "N");
795        writer.print(" insets="); writer.print(Utilities.dumpRect(mSystemInsets));
796        writer.print(" [0x"); writer.print(id); writer.print("]");
797        writer.println();
798
799        if (mStack != null) {
800            mStack.dump(innerPrefix, writer);
801        }
802        if (mTaskStackView != null) {
803            mTaskStackView.dump(innerPrefix, writer);
804        }
805    }
806}
807