[go: nahoru, domu]

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