RecentsView.java revision 7aaa353cbb872c308b9be292e11e88d4e497645c
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.WallpaperColors;
25import android.app.WallpaperManager;
26import android.content.Context;
27import android.graphics.Canvas;
28import android.graphics.Color;
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.LayoutInflater;
36import android.view.MotionEvent;
37import android.view.View;
38import android.view.ViewDebug;
39import android.view.ViewPropertyAnimator;
40import android.view.WindowInsets;
41import android.widget.FrameLayout;
42import android.widget.TextView;
43
44import com.android.internal.logging.MetricsLogger;
45import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
46import com.android.systemui.Dependency;
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.ShowEmptyViewEvent;
62import com.android.systemui.recents.events.activity.ShowStackActionButtonEvent;
63import com.android.systemui.recents.events.component.ExpandPipEvent;
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.recents.views.RecentsTransitionHelper.AppTransitionAnimationSpecsFuture;
79import com.android.systemui.stackdivider.WindowManagerProxy;
80import com.android.systemui.statusbar.FlingAnimationUtils;
81
82import com.google.android.colorextraction.ColorExtractor;
83import com.google.android.colorextraction.drawable.GradientDrawable;
84
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 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 (Recents.getConfiguration().getLaunchState().launchedFromPipApp) {
263            // If the app auto-entered PiP on the way to Recents, then just re-expand it
264            EventBus.getDefault().send(new ExpandPipEvent());
265            return true;
266        }
267
268        if (mTaskStackView != null) {
269            Task task = getStack().getLaunchTarget();
270            if (task != null) {
271                TaskView taskView = mTaskStackView.getChildViewForTask(task);
272                EventBus.getDefault().send(new LaunchTaskEvent(taskView, task, null,
273                        INVALID_STACK_ID, false));
274                return true;
275            }
276        }
277        return false;
278    }
279
280    /** Launches a given task. */
281    public boolean launchTask(Task task, Rect taskBounds, int destinationStack) {
282        if (mTaskStackView != null) {
283            // Iterate the stack views and try and find the given task.
284            List<TaskView> taskViews = mTaskStackView.getTaskViews();
285            int taskViewCount = taskViews.size();
286            for (int j = 0; j < taskViewCount; j++) {
287                TaskView tv = taskViews.get(j);
288                if (tv.getTask() == task) {
289                    EventBus.getDefault().send(new LaunchTaskEvent(tv, task, taskBounds,
290                            destinationStack, false));
291                    return true;
292                }
293            }
294        }
295        return false;
296    }
297
298    /**
299     * Hides the task stack and shows the empty view.
300     */
301    public void showEmptyView(int msgResId) {
302        mTaskStackView.setVisibility(View.INVISIBLE);
303        mEmptyView.setText(msgResId);
304        mEmptyView.setVisibility(View.VISIBLE);
305        mEmptyView.bringToFront();
306        if (RecentsDebugFlags.Static.EnableStackActionButton) {
307            mStackActionButton.bringToFront();
308        }
309    }
310
311    /**
312     * Shows the task stack and hides the empty view.
313     */
314    public void hideEmptyView() {
315        mEmptyView.setVisibility(View.INVISIBLE);
316        mTaskStackView.setVisibility(View.VISIBLE);
317        mTaskStackView.bringToFront();
318        if (RecentsDebugFlags.Static.EnableStackActionButton) {
319            mStackActionButton.bringToFront();
320        }
321    }
322
323    @Override
324    protected void onAttachedToWindow() {
325        EventBus.getDefault().register(this, RecentsActivity.EVENT_BUS_PRIORITY + 1);
326        EventBus.getDefault().register(mTouchHandler, RecentsActivity.EVENT_BUS_PRIORITY + 2);
327        super.onAttachedToWindow();
328    }
329
330    @Override
331    protected void onDetachedFromWindow() {
332        super.onDetachedFromWindow();
333        EventBus.getDefault().unregister(this);
334        EventBus.getDefault().unregister(mTouchHandler);
335    }
336
337    /**
338     * This is called with the full size of the window since we are handling our own insets.
339     */
340    @Override
341    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
342        int width = MeasureSpec.getSize(widthMeasureSpec);
343        int height = MeasureSpec.getSize(heightMeasureSpec);
344
345        if (mTaskStackView.getVisibility() != GONE) {
346            mTaskStackView.measure(widthMeasureSpec, heightMeasureSpec);
347        }
348
349        // Measure the empty view to the full size of the screen
350        if (mEmptyView.getVisibility() != GONE) {
351            measureChild(mEmptyView, MeasureSpec.makeMeasureSpec(width, MeasureSpec.AT_MOST),
352                    MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST));
353        }
354
355        if (RecentsDebugFlags.Static.EnableStackActionButton) {
356            // Measure the stack action button within the constraints of the space above the stack
357            Rect buttonBounds = mTaskStackView.mLayoutAlgorithm.getStackActionButtonRect();
358            measureChild(mStackActionButton,
359                    MeasureSpec.makeMeasureSpec(buttonBounds.width(), MeasureSpec.AT_MOST),
360                    MeasureSpec.makeMeasureSpec(buttonBounds.height(), MeasureSpec.AT_MOST));
361        }
362
363        setMeasuredDimension(width, height);
364    }
365
366    /**
367     * This is called with the full size of the window since we are handling our own insets.
368     */
369    @Override
370    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
371        if (mTaskStackView.getVisibility() != GONE) {
372            mTaskStackView.layout(left, top, left + getMeasuredWidth(), top + getMeasuredHeight());
373        }
374
375        // Layout the empty view
376        if (mEmptyView.getVisibility() != GONE) {
377            int leftRightInsets = mSystemInsets.left + mSystemInsets.right;
378            int topBottomInsets = mSystemInsets.top + mSystemInsets.bottom;
379            int childWidth = mEmptyView.getMeasuredWidth();
380            int childHeight = mEmptyView.getMeasuredHeight();
381            int childLeft = left + mSystemInsets.left +
382                    Math.max(0, (right - left - leftRightInsets - childWidth)) / 2;
383            int childTop = top + mSystemInsets.top +
384                    Math.max(0, (bottom - top - topBottomInsets - childHeight)) / 2;
385            mEmptyView.layout(childLeft, childTop, childLeft + childWidth, childTop + childHeight);
386        }
387
388        // Needs to know the screen size since the gradient never scales up or down
389        // even when bounds change.
390        mBackgroundScrim.setScreenSize(right - left, bottom - top);
391        mBackgroundScrim.setBounds(left, top, right, bottom);
392
393        if (RecentsDebugFlags.Static.EnableStackActionButton) {
394            // Layout the stack action button such that its drawable is start-aligned with the
395            // stack, vertically centered in the available space above the stack
396            Rect buttonBounds = getStackActionButtonBoundsFromStackLayout();
397            mStackActionButton.layout(buttonBounds.left, buttonBounds.top, buttonBounds.right,
398                    buttonBounds.bottom);
399        }
400
401        if (mAwaitingFirstLayout) {
402            mAwaitingFirstLayout = false;
403
404            // If launched via dragging from the nav bar, then we should translate the whole view
405            // down offscreen
406            RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState();
407            if (launchState.launchedViaDragGesture) {
408                setTranslationY(getMeasuredHeight());
409            } else {
410                setTranslationY(0f);
411            }
412        }
413    }
414
415    @Override
416    public WindowInsets onApplyWindowInsets(WindowInsets insets) {
417        mSystemInsets.set(insets.getSystemWindowInsets());
418        mTaskStackView.setSystemInsets(mSystemInsets);
419        requestLayout();
420        return insets;
421    }
422
423    @Override
424    public boolean onInterceptTouchEvent(MotionEvent ev) {
425        return mTouchHandler.onInterceptTouchEvent(ev);
426    }
427
428    @Override
429    public boolean onTouchEvent(MotionEvent ev) {
430        return mTouchHandler.onTouchEvent(ev);
431    }
432
433    @Override
434    public void onDrawForeground(Canvas canvas) {
435        super.onDrawForeground(canvas);
436
437        ArrayList<TaskStack.DockState> visDockStates = mTouchHandler.getVisibleDockStates();
438        for (int i = visDockStates.size() - 1; i >= 0; i--) {
439            visDockStates.get(i).viewState.draw(canvas);
440        }
441    }
442
443    @Override
444    protected boolean verifyDrawable(Drawable who) {
445        ArrayList<TaskStack.DockState> visDockStates = mTouchHandler.getVisibleDockStates();
446        for (int i = visDockStates.size() - 1; i >= 0; i--) {
447            Drawable d = visDockStates.get(i).viewState.dockAreaOverlay;
448            if (d == who) {
449                return true;
450            }
451        }
452        return super.verifyDrawable(who);
453    }
454
455    /**** EventBus Events ****/
456
457    public final void onBusEvent(LaunchTaskEvent event) {
458        mLastTaskLaunchedWasFreeform = event.task.isFreeformTask();
459        mTransitionHelper.launchTaskFromRecents(getStack(), event.task, mTaskStackView,
460                event.taskView, event.screenPinningRequested, event.targetTaskStack);
461    }
462
463    public final void onBusEvent(DismissRecentsToHomeAnimationStarted event) {
464        int taskViewExitToHomeDuration = TaskStackAnimationHelper.EXIT_TO_HOME_TRANSLATION_DURATION;
465        if (RecentsDebugFlags.Static.EnableStackActionButton) {
466            // Hide the stack action button
467            hideStackActionButton(taskViewExitToHomeDuration, false /* translate */);
468        }
469        animateBackgroundScrim(0f, taskViewExitToHomeDuration);
470    }
471
472    public final void onBusEvent(DragStartEvent event) {
473        updateVisibleDockRegions(Recents.getConfiguration().getDockStatesForCurrentOrientation(),
474                true /* isDefaultDockState */, TaskStack.DockState.NONE.viewState.dockAreaAlpha,
475                TaskStack.DockState.NONE.viewState.hintTextAlpha,
476                true /* animateAlpha */, false /* animateBounds */);
477
478        // Temporarily hide the stack action button without changing visibility
479        if (mStackActionButton != null) {
480            mStackActionButton.animate()
481                    .alpha(0f)
482                    .setDuration(HIDE_STACK_ACTION_BUTTON_DURATION)
483                    .setInterpolator(Interpolators.ALPHA_OUT)
484                    .start();
485        }
486    }
487
488    public final void onBusEvent(DragDropTargetChangedEvent event) {
489        if (event.dropTarget == null || !(event.dropTarget instanceof TaskStack.DockState)) {
490            updateVisibleDockRegions(
491                    Recents.getConfiguration().getDockStatesForCurrentOrientation(),
492                    true /* isDefaultDockState */, TaskStack.DockState.NONE.viewState.dockAreaAlpha,
493                    TaskStack.DockState.NONE.viewState.hintTextAlpha,
494                    true /* animateAlpha */, true /* animateBounds */);
495        } else {
496            final TaskStack.DockState dockState = (TaskStack.DockState) event.dropTarget;
497            updateVisibleDockRegions(new TaskStack.DockState[] {dockState},
498                    false /* isDefaultDockState */, -1, -1, true /* animateAlpha */,
499                    true /* animateBounds */);
500        }
501        if (mStackActionButton != null) {
502            event.addPostAnimationCallback(new Runnable() {
503                @Override
504                public void run() {
505                    // Move the clear all button to its new position
506                    Rect buttonBounds = getStackActionButtonBoundsFromStackLayout();
507                    mStackActionButton.setLeftTopRightBottom(buttonBounds.left, buttonBounds.top,
508                            buttonBounds.right, buttonBounds.bottom);
509                }
510            });
511        }
512    }
513
514    public final void onBusEvent(final DragEndEvent event) {
515        // Handle the case where we drop onto a dock region
516        if (event.dropTarget instanceof TaskStack.DockState) {
517            final TaskStack.DockState dockState = (TaskStack.DockState) event.dropTarget;
518
519            // Hide the dock region
520            updateVisibleDockRegions(null, false /* isDefaultDockState */, -1, -1,
521                    false /* animateAlpha */, false /* animateBounds */);
522
523            // We translated the view but we need to animate it back from the current layout-space
524            // rect to its final layout-space rect
525            Utilities.setViewFrameFromTranslation(event.taskView);
526
527            // Dock the task and launch it
528            SystemServicesProxy ssp = Recents.getSystemServices();
529            if (ssp.startTaskInDockedMode(event.task.key.id, dockState.createMode)) {
530                final OnAnimationStartedListener startedListener =
531                        new OnAnimationStartedListener() {
532                    @Override
533                    public void onAnimationStarted() {
534                        EventBus.getDefault().send(new DockedFirstAnimationFrameEvent());
535                        // Remove the task and don't bother relaying out, as all the tasks will be
536                        // relaid out when the stack changes on the multiwindow change event
537                        getStack().removeTask(event.task, null, true /* fromDockGesture */);
538                    }
539                };
540
541                final Rect taskRect = getTaskRect(event.taskView);
542                AppTransitionAnimationSpecsFuture future =
543                        mTransitionHelper.getAppTransitionFuture(
544                                new AnimationSpecComposer() {
545                                    @Override
546                                    public List<AppTransitionAnimationSpec> composeSpecs() {
547                                        return mTransitionHelper.composeDockAnimationSpec(
548                                                event.taskView, taskRect);
549                                    }
550                                });
551                ssp.overridePendingAppTransitionMultiThumbFuture(future.getFuture(),
552                        mTransitionHelper.wrapStartedListener(startedListener),
553                        true /* scaleUp */);
554
555                MetricsLogger.action(mContext, MetricsEvent.ACTION_WINDOW_DOCK_DRAG_DROP,
556                        event.task.getTopComponent().flattenToShortString());
557            } else {
558                EventBus.getDefault().send(new DragEndCancelledEvent(getStack(), event.task,
559                        event.taskView));
560            }
561        } else {
562            // Animate the overlay alpha back to 0
563            updateVisibleDockRegions(null, true /* isDefaultDockState */, -1, -1,
564                    true /* animateAlpha */, false /* animateBounds */);
565        }
566
567        // Show the stack action button again without changing visibility
568        if (mStackActionButton != null) {
569            mStackActionButton.animate()
570                    .alpha(1f)
571                    .setDuration(SHOW_STACK_ACTION_BUTTON_DURATION)
572                    .setInterpolator(Interpolators.ALPHA_IN)
573                    .start();
574        }
575    }
576
577    public final void onBusEvent(final DragEndCancelledEvent event) {
578        // Animate the overlay alpha back to 0
579        updateVisibleDockRegions(null, true /* isDefaultDockState */, -1, -1,
580                true /* animateAlpha */, false /* animateBounds */);
581    }
582
583    private Rect getTaskRect(TaskView taskView) {
584        int[] location = taskView.getLocationOnScreen();
585        int viewX = location[0];
586        int viewY = location[1];
587        return new Rect(viewX, viewY,
588                (int) (viewX + taskView.getWidth() * taskView.getScaleX()),
589                (int) (viewY + taskView.getHeight() * taskView.getScaleY()));
590    }
591
592    public final void onBusEvent(DraggingInRecentsEvent event) {
593        if (mTaskStackView.getTaskViews().size() > 0) {
594            setTranslationY(event.distanceFromTop - mTaskStackView.getTaskViews().get(0).getY());
595        }
596    }
597
598    public final void onBusEvent(DraggingInRecentsEndedEvent event) {
599        ViewPropertyAnimator animator = animate();
600        if (event.velocity > mFlingAnimationUtils.getMinVelocityPxPerSecond()) {
601            animator.translationY(getHeight());
602            animator.withEndAction(new Runnable() {
603                @Override
604                public void run() {
605                    WindowManagerProxy.getInstance().maximizeDockedStack();
606                }
607            });
608            mFlingAnimationUtils.apply(animator, getTranslationY(), getHeight(), event.velocity);
609        } else {
610            animator.translationY(0f);
611            animator.setListener(null);
612            mFlingAnimationUtils.apply(animator, getTranslationY(), 0, event.velocity);
613        }
614        animator.start();
615    }
616
617    public final void onBusEvent(EnterRecentsWindowAnimationCompletedEvent event) {
618        RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState();
619        if (!launchState.launchedViaDockGesture && !launchState.launchedFromApp
620                && getStack().getTaskCount() > 0) {
621            animateBackgroundScrim(1f,
622                    TaskStackAnimationHelper.ENTER_FROM_HOME_TRANSLATION_DURATION);
623        }
624    }
625
626    public final void onBusEvent(AllTaskViewsDismissedEvent event) {
627        hideStackActionButton(HIDE_STACK_ACTION_BUTTON_DURATION, true /* translate */);
628    }
629
630    public final void onBusEvent(DismissAllTaskViewsEvent event) {
631        SystemServicesProxy ssp = Recents.getSystemServices();
632        if (!ssp.hasDockedTask()) {
633            // Animate the background away only if we are dismissing Recents to home
634            animateBackgroundScrim(0f, DEFAULT_UPDATE_SCRIM_DURATION);
635        }
636    }
637
638    public final void onBusEvent(ShowStackActionButtonEvent event) {
639        if (!RecentsDebugFlags.Static.EnableStackActionButton) {
640            return;
641        }
642
643        showStackActionButton(SHOW_STACK_ACTION_BUTTON_DURATION, event.translate);
644    }
645
646    public final void onBusEvent(HideStackActionButtonEvent event) {
647        if (!RecentsDebugFlags.Static.EnableStackActionButton) {
648            return;
649        }
650
651        hideStackActionButton(HIDE_STACK_ACTION_BUTTON_DURATION, true /* translate */);
652    }
653
654    public final void onBusEvent(MultiWindowStateChangedEvent event) {
655        updateStack(event.stack, false /* setStackViewTasks */);
656    }
657
658    public final void onBusEvent(ShowEmptyViewEvent event) {
659        showEmptyView(R.string.recents_empty_message);
660    }
661
662    /**
663     * Shows the stack action button.
664     */
665    private void showStackActionButton(final int duration, final boolean translate) {
666        if (!RecentsDebugFlags.Static.EnableStackActionButton) {
667            return;
668        }
669
670        final ReferenceCountedTrigger postAnimationTrigger = new ReferenceCountedTrigger();
671        if (mStackActionButton.getVisibility() == View.INVISIBLE) {
672            mStackActionButton.setVisibility(View.VISIBLE);
673            mStackActionButton.setAlpha(0f);
674            if (translate) {
675                mStackActionButton.setTranslationY(-mStackActionButton.getMeasuredHeight() * 0.25f);
676            } else {
677                mStackActionButton.setTranslationY(0f);
678            }
679            postAnimationTrigger.addLastDecrementRunnable(new Runnable() {
680                @Override
681                public void run() {
682                    if (translate) {
683                        mStackActionButton.animate()
684                            .translationY(0f);
685                    }
686                    mStackActionButton.animate()
687                            .alpha(1f)
688                            .setDuration(duration)
689                            .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
690                            .start();
691                }
692            });
693        }
694        postAnimationTrigger.flushLastDecrementRunnables();
695    }
696
697    /**
698     * Hides the stack action button.
699     */
700    private void hideStackActionButton(int duration, boolean translate) {
701        if (!RecentsDebugFlags.Static.EnableStackActionButton) {
702            return;
703        }
704
705        final ReferenceCountedTrigger postAnimationTrigger = new ReferenceCountedTrigger();
706        hideStackActionButton(duration, translate, postAnimationTrigger);
707        postAnimationTrigger.flushLastDecrementRunnables();
708    }
709
710    /**
711     * Hides the stack action button.
712     */
713    private void hideStackActionButton(int duration, boolean translate,
714                                       final ReferenceCountedTrigger postAnimationTrigger) {
715        if (!RecentsDebugFlags.Static.EnableStackActionButton) {
716            return;
717        }
718
719        if (mStackActionButton.getVisibility() == View.VISIBLE) {
720            if (translate) {
721                mStackActionButton.animate()
722                    .translationY(-mStackActionButton.getMeasuredHeight() * 0.25f);
723            }
724            mStackActionButton.animate()
725                    .alpha(0f)
726                    .setDuration(duration)
727                    .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
728                    .withEndAction(new Runnable() {
729                        @Override
730                        public void run() {
731                            mStackActionButton.setVisibility(View.INVISIBLE);
732                            postAnimationTrigger.decrement();
733                        }
734                    })
735                    .start();
736            postAnimationTrigger.increment();
737        }
738    }
739
740    /**
741     * Updates the dock region to match the specified dock state.
742     */
743    private void updateVisibleDockRegions(TaskStack.DockState[] newDockStates,
744            boolean isDefaultDockState, int overrideAreaAlpha, int overrideHintAlpha,
745            boolean animateAlpha, boolean animateBounds) {
746        ArraySet<TaskStack.DockState> newDockStatesSet = Utilities.arrayToSet(newDockStates,
747                new ArraySet<TaskStack.DockState>());
748        ArrayList<TaskStack.DockState> visDockStates = mTouchHandler.getVisibleDockStates();
749        for (int i = visDockStates.size() - 1; i >= 0; i--) {
750            TaskStack.DockState dockState = visDockStates.get(i);
751            TaskStack.DockState.ViewState viewState = dockState.viewState;
752            if (newDockStates == null || !newDockStatesSet.contains(dockState)) {
753                // This is no longer visible, so hide it
754                viewState.startAnimation(null, 0, 0, TaskStackView.SLOW_SYNC_STACK_DURATION,
755                        Interpolators.FAST_OUT_SLOW_IN, animateAlpha, animateBounds);
756            } else {
757                // This state is now visible, update the bounds and show it
758                int areaAlpha = overrideAreaAlpha != -1
759                        ? overrideAreaAlpha
760                        : viewState.dockAreaAlpha;
761                int hintAlpha = overrideHintAlpha != -1
762                        ? overrideHintAlpha
763                        : viewState.hintTextAlpha;
764                Rect bounds = isDefaultDockState
765                        ? dockState.getPreDockedBounds(getMeasuredWidth(), getMeasuredHeight(),
766                                mSystemInsets)
767                        : dockState.getDockedBounds(getMeasuredWidth(), getMeasuredHeight(),
768                                mDividerSize, mSystemInsets, getResources());
769                if (viewState.dockAreaOverlay.getCallback() != this) {
770                    viewState.dockAreaOverlay.setCallback(this);
771                    viewState.dockAreaOverlay.setBounds(bounds);
772                }
773                viewState.startAnimation(bounds, areaAlpha, hintAlpha,
774                        TaskStackView.SLOW_SYNC_STACK_DURATION, Interpolators.FAST_OUT_SLOW_IN,
775                        animateAlpha, animateBounds);
776            }
777        }
778    }
779
780    /**
781     * Animates the background scrim to the given {@param alpha}.
782     */
783    private void animateBackgroundScrim(float alpha, int duration) {
784        Utilities.cancelAnimationWithoutCallbacks(mBackgroundScrimAnimator);
785        // Calculate the absolute alpha to animate from
786        int fromAlpha = mBackgroundScrim.getAlpha();
787        int toAlpha = (int) (alpha * mScrimAlpha * 255);
788        mBackgroundScrimAnimator = ObjectAnimator.ofInt(mBackgroundScrim, Utilities.DRAWABLE_ALPHA,
789                fromAlpha, toAlpha);
790        mBackgroundScrimAnimator.setDuration(duration);
791        mBackgroundScrimAnimator.setInterpolator(toAlpha > fromAlpha
792                ? Interpolators.ALPHA_IN
793                : Interpolators.ALPHA_OUT);
794        mBackgroundScrimAnimator.start();
795    }
796
797    /**
798     * @return the bounds of the stack action button.
799     */
800    private Rect getStackActionButtonBoundsFromStackLayout() {
801        Rect actionButtonRect = new Rect(mTaskStackView.mLayoutAlgorithm.getStackActionButtonRect());
802        int left = isLayoutRtl()
803                ? actionButtonRect.left - mStackActionButton.getPaddingLeft()
804                : actionButtonRect.right + mStackActionButton.getPaddingRight()
805                        - mStackActionButton.getMeasuredWidth();
806        int top = actionButtonRect.top +
807                (actionButtonRect.height() - mStackActionButton.getMeasuredHeight()) / 2;
808        actionButtonRect.set(left, top, left + mStackActionButton.getMeasuredWidth(),
809                top + mStackActionButton.getMeasuredHeight());
810        return actionButtonRect;
811    }
812
813    public void dump(String prefix, PrintWriter writer) {
814        String innerPrefix = prefix + "  ";
815        String id = Integer.toHexString(System.identityHashCode(this));
816
817        writer.print(prefix); writer.print(TAG);
818        writer.print(" awaitingFirstLayout="); writer.print(mAwaitingFirstLayout ? "Y" : "N");
819        writer.print(" insets="); writer.print(Utilities.dumpRect(mSystemInsets));
820        writer.print(" [0x"); writer.print(id); writer.print("]");
821        writer.println();
822
823        if (getStack() != null) {
824            getStack().dump(innerPrefix, writer);
825        }
826        if (mTaskStackView != null) {
827            mTaskStackView.dump(innerPrefix, writer);
828        }
829    }
830
831    @Override
832    public void onColorsChanged(ColorExtractor extractor, int which) {
833        if ((which & WallpaperManager.FLAG_SYSTEM) != 0) {
834            mBackgroundScrim.setColors(extractor.getColors(WallpaperManager.FLAG_SYSTEM));
835        }
836    }
837
838    public void onStart() {
839        mColorExtractor.addOnColorsChangedListener(this);
840        // We don't want to interpolate colors because we're defining the initial state.
841        // Gradient should be set/ready when you open "Recents".
842        mBackgroundScrim.setColors(mColorExtractor.getColors(WallpaperManager.FLAG_SYSTEM), false);
843    }
844
845    public void onStop() {
846        mColorExtractor.removeOnColorsChangedListener(this);
847    }
848}
849