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