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