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