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