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