RecentsView.java revision d2eb55d0982c7408153b890d69e267a930b30562
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.animation.ValueAnimator;
24import android.animation.ValueAnimator.AnimatorUpdateListener;
25import android.app.ActivityOptions.OnAnimationStartedListener;
26import android.content.Context;
27import android.content.res.ColorStateList;
28import android.graphics.Canvas;
29import android.graphics.Color;
30import android.graphics.Point;
31import android.graphics.PointF;
32import android.graphics.Rect;
33import android.graphics.drawable.ColorDrawable;
34import android.graphics.drawable.Drawable;
35import android.util.ArraySet;
36import android.util.AttributeSet;
37import android.util.MathUtils;
38import android.view.AppTransitionAnimationSpec;
39import android.view.LayoutInflater;
40import android.view.MotionEvent;
41import android.view.View;
42import android.view.ViewDebug;
43import android.view.ViewPropertyAnimator;
44import android.view.Window;
45import android.view.WindowInsets;
46import android.widget.FrameLayout;
47import android.widget.TextView;
48
49import com.android.internal.colorextraction.ColorExtractor;
50import com.android.internal.colorextraction.drawable.GradientDrawable;
51import com.android.internal.logging.MetricsLogger;
52import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
53import com.android.settingslib.Utils;
54import com.android.systemui.Interpolators;
55import com.android.systemui.R;
56import com.android.systemui.recents.Recents;
57import com.android.systemui.recents.RecentsActivity;
58import com.android.systemui.recents.RecentsActivityLaunchState;
59import com.android.systemui.recents.RecentsConfiguration;
60import com.android.systemui.recents.RecentsDebugFlags;
61import com.android.systemui.recents.events.EventBus;
62import com.android.systemui.recents.events.activity.DismissRecentsToHomeAnimationStarted;
63import com.android.systemui.recents.events.activity.DockedFirstAnimationFrameEvent;
64import com.android.systemui.recents.events.activity.EnterRecentsWindowAnimationCompletedEvent;
65import com.android.systemui.recents.events.activity.HideStackActionButtonEvent;
66import com.android.systemui.recents.events.activity.LaunchTaskEvent;
67import com.android.systemui.recents.events.activity.MultiWindowStateChangedEvent;
68import com.android.systemui.recents.events.activity.ShowEmptyViewEvent;
69import com.android.systemui.recents.events.activity.ShowStackActionButtonEvent;
70import com.android.systemui.recents.events.component.ExpandPipEvent;
71import com.android.systemui.recents.events.ui.AllTaskViewsDismissedEvent;
72import com.android.systemui.recents.events.ui.DismissAllTaskViewsEvent;
73import com.android.systemui.recents.events.ui.DraggingInRecentsEndedEvent;
74import com.android.systemui.recents.events.ui.DraggingInRecentsEvent;
75import com.android.systemui.recents.events.ui.dragndrop.DragDropTargetChangedEvent;
76import com.android.systemui.recents.events.ui.dragndrop.DragEndCancelledEvent;
77import com.android.systemui.recents.events.ui.dragndrop.DragEndEvent;
78import com.android.systemui.recents.events.ui.dragndrop.DragStartEvent;
79import com.android.systemui.recents.misc.ReferenceCountedTrigger;
80import com.android.systemui.recents.misc.SystemServicesProxy;
81import com.android.systemui.recents.misc.Utilities;
82import com.android.systemui.recents.model.Task;
83import com.android.systemui.recents.model.TaskStack;
84import com.android.systemui.recents.views.RecentsTransitionHelper.AnimationSpecComposer;
85import com.android.systemui.recents.views.RecentsTransitionHelper.AppTransitionAnimationSpecsFuture;
86import com.android.systemui.stackdivider.WindowManagerProxy;
87import com.android.systemui.statusbar.FlingAnimationUtils;
88import com.android.systemui.statusbar.phone.ScrimController;
89
90import java.io.PrintWriter;
91import java.util.ArrayList;
92import java.util.List;
93
94/**
95 * This view is the the top level layout that contains TaskStacks (which are laid out according
96 * to their SpaceNode bounds.
97 */
98public class RecentsView extends FrameLayout {
99
100    private static final String TAG = "RecentsView";
101
102    private static final int DEFAULT_UPDATE_SCRIM_DURATION = 200;
103
104    private static final int SHOW_STACK_ACTION_BUTTON_DURATION = 134;
105    private static final int HIDE_STACK_ACTION_BUTTON_DURATION = 100;
106
107    private static final int BUSY_RECENTS_TASK_COUNT = 3;
108
109    private TaskStackView mTaskStackView;
110    private TextView mStackActionButton;
111    private TextView mEmptyView;
112    private final float mStackButtonShadowRadius;
113    private final PointF mStackButtonShadowDistance;
114    private final int mStackButtonShadowColor;
115
116    private boolean mAwaitingFirstLayout = true;
117    private boolean mLastTaskLaunchedWasFreeform;
118
119    @ViewDebug.ExportedProperty(category="recents")
120    Rect mSystemInsets = new Rect();
121    private int mDividerSize;
122
123    private float mBusynessFactor;
124    private GradientDrawable mBackgroundScrim;
125    private ColorDrawable mMultiWindowBackgroundScrim;
126    private ValueAnimator mBackgroundScrimAnimator;
127    private Point mTmpDisplaySize = new Point();
128
129    private final AnimatorUpdateListener mUpdateBackgroundScrimAlpha = (animation) -> {
130        int alpha = (Integer) animation.getAnimatedValue();
131        mBackgroundScrim.setAlpha(alpha);
132        mMultiWindowBackgroundScrim.setAlpha(alpha);
133    };
134
135    private RecentsTransitionHelper mTransitionHelper;
136    @ViewDebug.ExportedProperty(deepExport=true, prefix="touch_")
137    private RecentsViewTouchHandler mTouchHandler;
138    private final FlingAnimationUtils mFlingAnimationUtils;
139
140    public RecentsView(Context context) {
141        this(context, null);
142    }
143
144    public RecentsView(Context context, AttributeSet attrs) {
145        this(context, attrs, 0);
146    }
147
148    public RecentsView(Context context, AttributeSet attrs, int defStyleAttr) {
149        this(context, attrs, defStyleAttr, 0);
150    }
151
152    public RecentsView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
153        super(context, attrs, defStyleAttr, defStyleRes);
154        setWillNotDraw(false);
155
156        SystemServicesProxy ssp = Recents.getSystemServices();
157        mTransitionHelper = new RecentsTransitionHelper(getContext());
158        mDividerSize = ssp.getDockedDividerSize(context);
159        mTouchHandler = new RecentsViewTouchHandler(this);
160        mFlingAnimationUtils = new FlingAnimationUtils(context, 0.3f);
161        mBackgroundScrim = new GradientDrawable(context);
162        mMultiWindowBackgroundScrim = new ColorDrawable();
163
164        LayoutInflater inflater = LayoutInflater.from(context);
165        mEmptyView = (TextView) inflater.inflate(R.layout.recents_empty, this, false);
166        addView(mEmptyView);
167
168        if (RecentsDebugFlags.Static.EnableStackActionButton) {
169            if (mStackActionButton != null) {
170                removeView(mStackActionButton);
171            }
172            mStackActionButton = (TextView) inflater.inflate(Recents.getConfiguration()
173                            .isLowRamDevice
174                        ? R.layout.recents_low_ram_stack_action_button
175                        : R.layout.recents_stack_action_button,
176                    this, false);
177
178            mStackButtonShadowRadius = mStackActionButton.getShadowRadius();
179            mStackButtonShadowDistance = new PointF(mStackActionButton.getShadowDx(),
180                    mStackActionButton.getShadowDy());
181            mStackButtonShadowColor = mStackActionButton.getShadowColor();
182            addView(mStackActionButton);
183        }
184
185        reevaluateStyles();
186    }
187
188    public void reevaluateStyles() {
189        int textColor = Utils.getColorAttr(mContext, R.attr.wallpaperTextColor);
190        boolean usingDarkText = Color.luminance(textColor) < 0.5f;
191
192        mEmptyView.setTextColor(textColor);
193        mEmptyView.setCompoundDrawableTintList(new ColorStateList(new int[][]{
194                {android.R.attr.state_enabled}}, new int[]{textColor}));
195
196        if (mStackActionButton != null) {
197            mStackActionButton.setTextColor(textColor);
198            // Enable/disable shadow if text color is already dark.
199            if (usingDarkText) {
200                mStackActionButton.setShadowLayer(0, 0, 0, 0);
201            } else {
202                mStackActionButton.setShadowLayer(mStackButtonShadowRadius,
203                        mStackButtonShadowDistance.x, mStackButtonShadowDistance.y,
204                        mStackButtonShadowColor);
205            }
206        }
207
208        // Let's also require dark status and nav bars if the text is dark
209        int systemBarsStyle = usingDarkText ? View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR |
210                View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR : 0;
211
212        setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE |
213                View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN |
214                View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
215                systemBarsStyle);
216    }
217
218    /**
219     * Called from RecentsActivity when it is relaunched.
220     */
221    public void onReload(TaskStack stack, boolean isResumingFromVisible) {
222        final RecentsConfiguration config = Recents.getConfiguration();
223        final RecentsActivityLaunchState launchState = config.getLaunchState();
224        final boolean isTaskStackEmpty = stack.getTaskCount() == 0;
225
226        if (mTaskStackView == null) {
227            isResumingFromVisible = false;
228            mTaskStackView = new TaskStackView(getContext());
229            mTaskStackView.setSystemInsets(mSystemInsets);
230            addView(mTaskStackView);
231        }
232
233        // Reset the state
234        mAwaitingFirstLayout = !isResumingFromVisible;
235        mLastTaskLaunchedWasFreeform = false;
236
237        // Update the stack
238        mTaskStackView.onReload(isResumingFromVisible);
239        updateStack(stack, true /* setStackViewTasks */);
240        updateBusyness();
241
242        if (isResumingFromVisible) {
243            // If we are already visible, then restore the background scrim
244            animateBackgroundScrim(getOpaqueScrimAlpha(), DEFAULT_UPDATE_SCRIM_DURATION);
245        } else {
246            // If we are already occluded by the app, then set the final background scrim alpha now.
247            // Otherwise, defer until the enter animation completes to animate the scrim alpha with
248            // the tasks for the home animation.
249            if (launchState.launchedViaDockGesture || launchState.launchedFromApp
250                    || isTaskStackEmpty) {
251                mBackgroundScrim.setAlpha((int) (getOpaqueScrimAlpha() * 255));
252            } else {
253                mBackgroundScrim.setAlpha(0);
254            }
255            mMultiWindowBackgroundScrim.setAlpha(mBackgroundScrim.getAlpha());
256        }
257    }
258
259    /**
260     * Called from RecentsActivity when the task stack is updated.
261     */
262    public void updateStack(TaskStack stack, boolean setStackViewTasks) {
263        if (setStackViewTasks) {
264            mTaskStackView.setTasks(stack, true /* allowNotifyStackChanges */);
265        }
266
267        // Update the top level view's visibilities
268        if (stack.getTaskCount() > 0) {
269            hideEmptyView();
270        } else {
271            showEmptyView(R.string.recents_empty_message);
272        }
273    }
274
275    /**
276     * Animates the scrim opacity based on how many tasks are visible.
277     * Called from {@link RecentsActivity} when tasks are dismissed.
278     */
279    public void updateScrimOpacity() {
280        if (updateBusyness()) {
281            animateBackgroundScrim(getOpaqueScrimAlpha(), DEFAULT_UPDATE_SCRIM_DURATION);
282        }
283    }
284
285    /**
286     * Updates the busyness factor.
287     *
288     * @return True if it changed.
289     */
290    private boolean updateBusyness() {
291        final int taskCount = mTaskStackView.getStack().getStackTaskCount();
292        final float busyness = Math.min(taskCount, BUSY_RECENTS_TASK_COUNT)
293                / (float) BUSY_RECENTS_TASK_COUNT;
294        if (mBusynessFactor == busyness) {
295            return false;
296        } else {
297            mBusynessFactor = busyness;
298            return true;
299        }
300    }
301
302    /**
303     * Returns the current TaskStack.
304     */
305    public TaskStack getStack() {
306        return mTaskStackView.getStack();
307    }
308
309    /**
310     * Returns the window background scrim.
311     */
312    public void updateBackgroundScrim(Window window, boolean isInMultiWindow) {
313        if (isInMultiWindow) {
314            mBackgroundScrim.setCallback(null);
315            window.setBackgroundDrawable(mMultiWindowBackgroundScrim);
316        } else {
317            mMultiWindowBackgroundScrim.setCallback(null);
318            window.setBackgroundDrawable(mBackgroundScrim);
319        }
320    }
321
322    /**
323     * Returns whether the last task launched was in the freeform stack or not.
324     */
325    public boolean isLastTaskLaunchedFreeform() {
326        return mLastTaskLaunchedWasFreeform;
327    }
328
329    /** Launches the focused task from the first stack if possible */
330    public boolean launchFocusedTask(int logEvent) {
331        if (mTaskStackView != null) {
332            Task task = mTaskStackView.getFocusedTask();
333            if (task != null) {
334                TaskView taskView = mTaskStackView.getChildViewForTask(task);
335                EventBus.getDefault().send(new LaunchTaskEvent(taskView, task, null,
336                        INVALID_STACK_ID, false));
337
338                if (logEvent != 0) {
339                    MetricsLogger.action(getContext(), logEvent,
340                            task.key.getComponent().toString());
341                }
342                return true;
343            }
344        }
345        return false;
346    }
347
348    /** Launches the task that recents was launched from if possible */
349    public boolean launchPreviousTask() {
350        if (Recents.getConfiguration().getLaunchState().launchedFromPipApp) {
351            // If the app auto-entered PiP on the way to Recents, then just re-expand it
352            EventBus.getDefault().send(new ExpandPipEvent());
353            return true;
354        }
355
356        if (mTaskStackView != null) {
357            Task task = getStack().getLaunchTarget();
358            if (task != null) {
359                TaskView taskView = mTaskStackView.getChildViewForTask(task);
360                EventBus.getDefault().send(new LaunchTaskEvent(taskView, task, null,
361                        INVALID_STACK_ID, false));
362                return true;
363            }
364        }
365        return false;
366    }
367
368    /** Launches a given task. */
369    public boolean launchTask(Task task, Rect taskBounds, int destinationStack) {
370        if (mTaskStackView != null) {
371            // Iterate the stack views and try and find the given task.
372            List<TaskView> taskViews = mTaskStackView.getTaskViews();
373            int taskViewCount = taskViews.size();
374            for (int j = 0; j < taskViewCount; j++) {
375                TaskView tv = taskViews.get(j);
376                if (tv.getTask() == task) {
377                    EventBus.getDefault().send(new LaunchTaskEvent(tv, task, taskBounds,
378                            destinationStack, false));
379                    return true;
380                }
381            }
382        }
383        return false;
384    }
385
386    /**
387     * Hides the task stack and shows the empty view.
388     */
389    public void showEmptyView(int msgResId) {
390        mTaskStackView.setVisibility(View.INVISIBLE);
391        mEmptyView.setText(msgResId);
392        mEmptyView.setVisibility(View.VISIBLE);
393        mEmptyView.bringToFront();
394        if (RecentsDebugFlags.Static.EnableStackActionButton) {
395            mStackActionButton.bringToFront();
396        }
397    }
398
399    /**
400     * Shows the task stack and hides the empty view.
401     */
402    public void hideEmptyView() {
403        mEmptyView.setVisibility(View.INVISIBLE);
404        mTaskStackView.setVisibility(View.VISIBLE);
405        mTaskStackView.bringToFront();
406        if (RecentsDebugFlags.Static.EnableStackActionButton) {
407            mStackActionButton.bringToFront();
408        }
409    }
410
411    /**
412     * Set the color of the scrim.
413     *
414     * @param scrimColors Colors to use.
415     * @param animated Interpolate colors if true.
416     */
417    public void setScrimColors(ColorExtractor.GradientColors scrimColors, boolean animated) {
418        mBackgroundScrim.setColors(scrimColors, animated);
419        int alpha = mMultiWindowBackgroundScrim.getAlpha();
420        mMultiWindowBackgroundScrim.setColor(scrimColors.getMainColor());
421        mMultiWindowBackgroundScrim.setAlpha(alpha);
422    }
423
424    @Override
425    protected void onAttachedToWindow() {
426        EventBus.getDefault().register(this, RecentsActivity.EVENT_BUS_PRIORITY + 1);
427        EventBus.getDefault().register(mTouchHandler, RecentsActivity.EVENT_BUS_PRIORITY + 2);
428        super.onAttachedToWindow();
429    }
430
431    @Override
432    protected void onDetachedFromWindow() {
433        super.onDetachedFromWindow();
434        EventBus.getDefault().unregister(this);
435        EventBus.getDefault().unregister(mTouchHandler);
436    }
437
438    /**
439     * This is called with the full size of the window since we are handling our own insets.
440     */
441    @Override
442    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
443        int width = MeasureSpec.getSize(widthMeasureSpec);
444        int height = MeasureSpec.getSize(heightMeasureSpec);
445
446        if (mTaskStackView.getVisibility() != GONE) {
447            mTaskStackView.measure(widthMeasureSpec, heightMeasureSpec);
448        }
449
450        // Measure the empty view to the full size of the screen
451        if (mEmptyView.getVisibility() != GONE) {
452            measureChild(mEmptyView, MeasureSpec.makeMeasureSpec(width, MeasureSpec.AT_MOST),
453                    MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST));
454        }
455
456        if (RecentsDebugFlags.Static.EnableStackActionButton) {
457            // Measure the stack action button within the constraints of the space above the stack
458            Rect buttonBounds = mTaskStackView.mLayoutAlgorithm.getStackActionButtonRect();
459            measureChild(mStackActionButton,
460                    MeasureSpec.makeMeasureSpec(buttonBounds.width(), MeasureSpec.AT_MOST),
461                    MeasureSpec.makeMeasureSpec(buttonBounds.height(), MeasureSpec.AT_MOST));
462        }
463
464        setMeasuredDimension(width, height);
465    }
466
467    /**
468     * This is called with the full size of the window since we are handling our own insets.
469     */
470    @Override
471    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
472        if (mTaskStackView.getVisibility() != GONE) {
473            mTaskStackView.layout(left, top, left + getMeasuredWidth(), top + getMeasuredHeight());
474        }
475
476        // Layout the empty view
477        if (mEmptyView.getVisibility() != GONE) {
478            int leftRightInsets = mSystemInsets.left + mSystemInsets.right;
479            int topBottomInsets = mSystemInsets.top + mSystemInsets.bottom;
480            int childWidth = mEmptyView.getMeasuredWidth();
481            int childHeight = mEmptyView.getMeasuredHeight();
482            int childLeft = left + mSystemInsets.left +
483                    Math.max(0, (right - left - leftRightInsets - childWidth)) / 2;
484            int childTop = top + mSystemInsets.top +
485                    Math.max(0, (bottom - top - topBottomInsets - childHeight)) / 2;
486            mEmptyView.layout(childLeft, childTop, childLeft + childWidth, childTop + childHeight);
487        }
488
489        // Needs to know the screen size since the gradient never scales up or down
490        // even when bounds change.
491        mContext.getDisplay().getRealSize(mTmpDisplaySize);
492        mBackgroundScrim.setScreenSize(mTmpDisplaySize.x, mTmpDisplaySize.y);
493        mBackgroundScrim.setBounds(left, top, right, bottom);
494        mMultiWindowBackgroundScrim.setBounds(0, 0, mTmpDisplaySize.x, mTmpDisplaySize.y);
495
496        if (RecentsDebugFlags.Static.EnableStackActionButton) {
497            // Layout the stack action button such that its drawable is start-aligned with the
498            // stack, vertically centered in the available space above the stack
499            Rect buttonBounds = getStackActionButtonBoundsFromStackLayout();
500            mStackActionButton.layout(buttonBounds.left, buttonBounds.top, buttonBounds.right,
501                    buttonBounds.bottom);
502        }
503
504        if (mAwaitingFirstLayout) {
505            mAwaitingFirstLayout = false;
506            // If launched via dragging from the nav bar, then we should translate the whole view
507            // down offscreen
508            RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState();
509            if (launchState.launchedViaDragGesture) {
510                setTranslationY(getMeasuredHeight());
511            } else {
512                setTranslationY(0f);
513            }
514
515            if (Recents.getConfiguration().isLowRamDevice
516                    && mEmptyView.getVisibility() == View.VISIBLE) {
517                animateEmptyView(true /* show */, null /* postAnimationTrigger */);
518            }
519        }
520    }
521
522    @Override
523    public WindowInsets onApplyWindowInsets(WindowInsets insets) {
524        mSystemInsets.set(insets.getSystemWindowInsets());
525        mTaskStackView.setSystemInsets(mSystemInsets);
526        requestLayout();
527        return insets;
528    }
529
530    @Override
531    public boolean onInterceptTouchEvent(MotionEvent ev) {
532        return mTouchHandler.onInterceptTouchEvent(ev);
533    }
534
535    @Override
536    public boolean onTouchEvent(MotionEvent ev) {
537        return mTouchHandler.onTouchEvent(ev);
538    }
539
540    @Override
541    public void onDrawForeground(Canvas canvas) {
542        super.onDrawForeground(canvas);
543
544        ArrayList<TaskStack.DockState> visDockStates = mTouchHandler.getVisibleDockStates();
545        for (int i = visDockStates.size() - 1; i >= 0; i--) {
546            visDockStates.get(i).viewState.draw(canvas);
547        }
548    }
549
550    @Override
551    protected boolean verifyDrawable(Drawable who) {
552        ArrayList<TaskStack.DockState> visDockStates = mTouchHandler.getVisibleDockStates();
553        for (int i = visDockStates.size() - 1; i >= 0; i--) {
554            Drawable d = visDockStates.get(i).viewState.dockAreaOverlay;
555            if (d == who) {
556                return true;
557            }
558        }
559        return super.verifyDrawable(who);
560    }
561
562    /**** EventBus Events ****/
563
564    public final void onBusEvent(LaunchTaskEvent event) {
565        mLastTaskLaunchedWasFreeform = event.task.isFreeformTask();
566        mTransitionHelper.launchTaskFromRecents(getStack(), event.task, mTaskStackView,
567                event.taskView, event.screenPinningRequested, event.targetTaskStack);
568        if (Recents.getConfiguration().isLowRamDevice) {
569            EventBus.getDefault().send(new HideStackActionButtonEvent(false /* translate */));
570        }
571    }
572
573    public final void onBusEvent(DismissRecentsToHomeAnimationStarted event) {
574        int taskViewExitToHomeDuration = TaskStackAnimationHelper.EXIT_TO_HOME_TRANSLATION_DURATION;
575        if (RecentsDebugFlags.Static.EnableStackActionButton) {
576            // Hide the stack action button
577            EventBus.getDefault().send(new HideStackActionButtonEvent());
578        }
579        animateBackgroundScrim(0f, taskViewExitToHomeDuration);
580
581        if (Recents.getConfiguration().isLowRamDevice) {
582            animateEmptyView(false /* show */, event.getAnimationTrigger());
583        }
584    }
585
586    public final void onBusEvent(DragStartEvent event) {
587        updateVisibleDockRegions(Recents.getConfiguration().getDockStatesForCurrentOrientation(),
588                true /* isDefaultDockState */, TaskStack.DockState.NONE.viewState.dockAreaAlpha,
589                TaskStack.DockState.NONE.viewState.hintTextAlpha,
590                true /* animateAlpha */, false /* animateBounds */);
591
592        // Temporarily hide the stack action button without changing visibility
593        if (mStackActionButton != null) {
594            mStackActionButton.animate()
595                    .alpha(0f)
596                    .setDuration(HIDE_STACK_ACTION_BUTTON_DURATION)
597                    .setInterpolator(Interpolators.ALPHA_OUT)
598                    .start();
599        }
600    }
601
602    public final void onBusEvent(DragDropTargetChangedEvent event) {
603        if (event.dropTarget == null || !(event.dropTarget instanceof TaskStack.DockState)) {
604            updateVisibleDockRegions(
605                    Recents.getConfiguration().getDockStatesForCurrentOrientation(),
606                    true /* isDefaultDockState */, TaskStack.DockState.NONE.viewState.dockAreaAlpha,
607                    TaskStack.DockState.NONE.viewState.hintTextAlpha,
608                    true /* animateAlpha */, true /* animateBounds */);
609        } else {
610            final TaskStack.DockState dockState = (TaskStack.DockState) event.dropTarget;
611            updateVisibleDockRegions(new TaskStack.DockState[] {dockState},
612                    false /* isDefaultDockState */, -1, -1, true /* animateAlpha */,
613                    true /* animateBounds */);
614        }
615        if (mStackActionButton != null) {
616            event.addPostAnimationCallback(new Runnable() {
617                @Override
618                public void run() {
619                    // Move the clear all button to its new position
620                    Rect buttonBounds = getStackActionButtonBoundsFromStackLayout();
621                    mStackActionButton.setLeftTopRightBottom(buttonBounds.left, buttonBounds.top,
622                            buttonBounds.right, buttonBounds.bottom);
623                }
624            });
625        }
626    }
627
628    public final void onBusEvent(final DragEndEvent event) {
629        // Handle the case where we drop onto a dock region
630        if (event.dropTarget instanceof TaskStack.DockState) {
631            final TaskStack.DockState dockState = (TaskStack.DockState) event.dropTarget;
632
633            // Hide the dock region
634            updateVisibleDockRegions(null, false /* isDefaultDockState */, -1, -1,
635                    false /* animateAlpha */, false /* animateBounds */);
636
637            // We translated the view but we need to animate it back from the current layout-space
638            // rect to its final layout-space rect
639            Utilities.setViewFrameFromTranslation(event.taskView);
640
641            // Dock the task and launch it
642            SystemServicesProxy ssp = Recents.getSystemServices();
643            if (ssp.startTaskInDockedMode(event.task.key.id, dockState.createMode)) {
644                final OnAnimationStartedListener startedListener =
645                        new OnAnimationStartedListener() {
646                    @Override
647                    public void onAnimationStarted() {
648                        EventBus.getDefault().send(new DockedFirstAnimationFrameEvent());
649                        // Remove the task and don't bother relaying out, as all the tasks will be
650                        // relaid out when the stack changes on the multiwindow change event
651                        getStack().removeTask(event.task, null, true /* fromDockGesture */);
652                    }
653                };
654
655                final Rect taskRect = getTaskRect(event.taskView);
656                AppTransitionAnimationSpecsFuture future =
657                        mTransitionHelper.getAppTransitionFuture(
658                                new AnimationSpecComposer() {
659                                    @Override
660                                    public List<AppTransitionAnimationSpec> composeSpecs() {
661                                        return mTransitionHelper.composeDockAnimationSpec(
662                                                event.taskView, taskRect);
663                                    }
664                                });
665                ssp.overridePendingAppTransitionMultiThumbFuture(future.getFuture(),
666                        mTransitionHelper.wrapStartedListener(startedListener),
667                        true /* scaleUp */);
668
669                MetricsLogger.action(mContext, MetricsEvent.ACTION_WINDOW_DOCK_DRAG_DROP,
670                        event.task.getTopComponent().flattenToShortString());
671            } else {
672                EventBus.getDefault().send(new DragEndCancelledEvent(getStack(), event.task,
673                        event.taskView));
674            }
675        } else {
676            // Animate the overlay alpha back to 0
677            updateVisibleDockRegions(null, true /* isDefaultDockState */, -1, -1,
678                    true /* animateAlpha */, false /* animateBounds */);
679        }
680
681        // Show the stack action button again without changing visibility
682        if (mStackActionButton != null) {
683            mStackActionButton.animate()
684                    .alpha(1f)
685                    .setDuration(SHOW_STACK_ACTION_BUTTON_DURATION)
686                    .setInterpolator(Interpolators.ALPHA_IN)
687                    .start();
688        }
689    }
690
691    public final void onBusEvent(final DragEndCancelledEvent event) {
692        // Animate the overlay alpha back to 0
693        updateVisibleDockRegions(null, true /* isDefaultDockState */, -1, -1,
694                true /* animateAlpha */, false /* animateBounds */);
695    }
696
697    private Rect getTaskRect(TaskView taskView) {
698        int[] location = taskView.getLocationOnScreen();
699        int viewX = location[0];
700        int viewY = location[1];
701        return new Rect(viewX, viewY,
702                (int) (viewX + taskView.getWidth() * taskView.getScaleX()),
703                (int) (viewY + taskView.getHeight() * taskView.getScaleY()));
704    }
705
706    public final void onBusEvent(DraggingInRecentsEvent event) {
707        if (mTaskStackView.getTaskViews().size() > 0) {
708            setTranslationY(event.distanceFromTop - mTaskStackView.getTaskViews().get(0).getY());
709        }
710    }
711
712    public final void onBusEvent(DraggingInRecentsEndedEvent event) {
713        ViewPropertyAnimator animator = animate();
714        if (event.velocity > mFlingAnimationUtils.getMinVelocityPxPerSecond()) {
715            animator.translationY(getHeight());
716            animator.withEndAction(new Runnable() {
717                @Override
718                public void run() {
719                    WindowManagerProxy.getInstance().maximizeDockedStack();
720                }
721            });
722            mFlingAnimationUtils.apply(animator, getTranslationY(), getHeight(), event.velocity);
723        } else {
724            animator.translationY(0f);
725            animator.setListener(null);
726            mFlingAnimationUtils.apply(animator, getTranslationY(), 0, event.velocity);
727        }
728        animator.start();
729    }
730
731    public final void onBusEvent(EnterRecentsWindowAnimationCompletedEvent event) {
732        RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState();
733        if (!launchState.launchedViaDockGesture && !launchState.launchedFromApp
734                && getStack().getTaskCount() > 0) {
735            animateBackgroundScrim(getOpaqueScrimAlpha(),
736                    TaskStackAnimationHelper.ENTER_FROM_HOME_TRANSLATION_DURATION);
737        }
738    }
739
740    public final void onBusEvent(AllTaskViewsDismissedEvent event) {
741        EventBus.getDefault().send(new HideStackActionButtonEvent());
742    }
743
744    public final void onBusEvent(DismissAllTaskViewsEvent event) {
745        SystemServicesProxy ssp = Recents.getSystemServices();
746        if (!ssp.hasDockedTask()) {
747            // Animate the background away only if we are dismissing Recents to home
748            animateBackgroundScrim(0f, DEFAULT_UPDATE_SCRIM_DURATION);
749        }
750    }
751
752    public final void onBusEvent(ShowStackActionButtonEvent event) {
753        if (!RecentsDebugFlags.Static.EnableStackActionButton) {
754            return;
755        }
756
757        showStackActionButton(SHOW_STACK_ACTION_BUTTON_DURATION, event.translate);
758    }
759
760    public final void onBusEvent(HideStackActionButtonEvent event) {
761        if (!RecentsDebugFlags.Static.EnableStackActionButton) {
762            return;
763        }
764
765        hideStackActionButton(HIDE_STACK_ACTION_BUTTON_DURATION, true /* translate */);
766    }
767
768    public final void onBusEvent(MultiWindowStateChangedEvent event) {
769        updateStack(event.stack, false /* setStackViewTasks */);
770    }
771
772    public final void onBusEvent(ShowEmptyViewEvent event) {
773        showEmptyView(R.string.recents_empty_message);
774    }
775
776    /**
777     * Shows the stack action button.
778     */
779    private void showStackActionButton(final int duration, final boolean translate) {
780        if (!RecentsDebugFlags.Static.EnableStackActionButton) {
781            return;
782        }
783
784        final ReferenceCountedTrigger postAnimationTrigger = new ReferenceCountedTrigger();
785        if (mStackActionButton.getVisibility() == View.INVISIBLE) {
786            mStackActionButton.setVisibility(View.VISIBLE);
787            mStackActionButton.setAlpha(0f);
788            if (translate) {
789                mStackActionButton.setTranslationY(mStackActionButton.getMeasuredHeight() *
790                        (Recents.getConfiguration().isLowRamDevice ? 1 : -0.25f));
791            } else {
792                mStackActionButton.setTranslationY(0f);
793            }
794            postAnimationTrigger.addLastDecrementRunnable(new Runnable() {
795                @Override
796                public void run() {
797                    if (translate) {
798                        mStackActionButton.animate()
799                            .translationY(0f);
800                    }
801                    mStackActionButton.animate()
802                            .alpha(1f)
803                            .setDuration(duration)
804                            .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
805                            .start();
806                }
807            });
808        }
809        postAnimationTrigger.flushLastDecrementRunnables();
810    }
811
812    /**
813     * Hides the stack action button.
814     */
815    private void hideStackActionButton(int duration, boolean translate) {
816        if (!RecentsDebugFlags.Static.EnableStackActionButton) {
817            return;
818        }
819
820        final ReferenceCountedTrigger postAnimationTrigger = new ReferenceCountedTrigger();
821        hideStackActionButton(duration, translate, postAnimationTrigger);
822        postAnimationTrigger.flushLastDecrementRunnables();
823    }
824
825    /**
826     * Hides the stack action button.
827     */
828    private void hideStackActionButton(int duration, boolean translate,
829                                       final ReferenceCountedTrigger postAnimationTrigger) {
830        if (!RecentsDebugFlags.Static.EnableStackActionButton) {
831            return;
832        }
833
834        if (mStackActionButton.getVisibility() == View.VISIBLE) {
835            if (translate) {
836                mStackActionButton.animate().translationY(mStackActionButton.getMeasuredHeight()
837                        * (Recents.getConfiguration().isLowRamDevice ? 1 : -0.25f));
838            }
839            mStackActionButton.animate()
840                    .alpha(0f)
841                    .setDuration(duration)
842                    .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
843                    .withEndAction(new Runnable() {
844                        @Override
845                        public void run() {
846                            mStackActionButton.setVisibility(View.INVISIBLE);
847                            postAnimationTrigger.decrement();
848                        }
849                    })
850                    .start();
851            postAnimationTrigger.increment();
852        }
853    }
854
855    /**
856     * Animates a translation in the Y direction and fades in/out for empty view to show or hide it.
857     * @param show whether to translate up and fade in the empty view to the center of the screen
858     * @param postAnimationTrigger to keep track of the animation
859     */
860    private void animateEmptyView(boolean show, ReferenceCountedTrigger postAnimationTrigger) {
861        float start = mTaskStackView.getStackAlgorithm().getTaskRect().height() / 4;
862        mEmptyView.setTranslationY(show ? start : 0);
863        mEmptyView.setAlpha(show ? 0f : 1f);
864        ViewPropertyAnimator animator = mEmptyView.animate()
865                .setDuration(150)
866                .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
867                .translationY(show ? 0 : start)
868                .alpha(show ? 1f : 0f);
869
870        if (postAnimationTrigger != null) {
871            animator.setListener(postAnimationTrigger.decrementOnAnimationEnd());
872            postAnimationTrigger.increment();
873        }
874        animator.start();
875    }
876
877    /**
878     * Updates the dock region to match the specified dock state.
879     */
880    private void updateVisibleDockRegions(TaskStack.DockState[] newDockStates,
881            boolean isDefaultDockState, int overrideAreaAlpha, int overrideHintAlpha,
882            boolean animateAlpha, boolean animateBounds) {
883        ArraySet<TaskStack.DockState> newDockStatesSet = Utilities.arrayToSet(newDockStates,
884                new ArraySet<TaskStack.DockState>());
885        ArrayList<TaskStack.DockState> visDockStates = mTouchHandler.getVisibleDockStates();
886        for (int i = visDockStates.size() - 1; i >= 0; i--) {
887            TaskStack.DockState dockState = visDockStates.get(i);
888            TaskStack.DockState.ViewState viewState = dockState.viewState;
889            if (newDockStates == null || !newDockStatesSet.contains(dockState)) {
890                // This is no longer visible, so hide it
891                viewState.startAnimation(null, 0, 0, TaskStackView.SLOW_SYNC_STACK_DURATION,
892                        Interpolators.FAST_OUT_SLOW_IN, animateAlpha, animateBounds);
893            } else {
894                // This state is now visible, update the bounds and show it
895                int areaAlpha = overrideAreaAlpha != -1
896                        ? overrideAreaAlpha
897                        : viewState.dockAreaAlpha;
898                int hintAlpha = overrideHintAlpha != -1
899                        ? overrideHintAlpha
900                        : viewState.hintTextAlpha;
901                Rect bounds = isDefaultDockState
902                        ? dockState.getPreDockedBounds(getMeasuredWidth(), getMeasuredHeight(),
903                                mSystemInsets)
904                        : dockState.getDockedBounds(getMeasuredWidth(), getMeasuredHeight(),
905                                mDividerSize, mSystemInsets, getResources());
906                if (viewState.dockAreaOverlay.getCallback() != this) {
907                    viewState.dockAreaOverlay.setCallback(this);
908                    viewState.dockAreaOverlay.setBounds(bounds);
909                }
910                viewState.startAnimation(bounds, areaAlpha, hintAlpha,
911                        TaskStackView.SLOW_SYNC_STACK_DURATION, Interpolators.FAST_OUT_SLOW_IN,
912                        animateAlpha, animateBounds);
913            }
914        }
915    }
916
917    /**
918     * Scrim alpha based on how busy recents is:
919     * Scrim will be {@link ScrimController#GRADIENT_SCRIM_ALPHA} when the stack is empty,
920     * and {@link ScrimController#GRADIENT_SCRIM_ALPHA_BUSY} when it's full.
921     *
922     * @return Alpha from 0 to 1.
923     */
924    private float getOpaqueScrimAlpha() {
925        return MathUtils.map(0, 1, ScrimController.GRADIENT_SCRIM_ALPHA,
926                ScrimController.GRADIENT_SCRIM_ALPHA_BUSY, mBusynessFactor);
927    }
928
929    /**
930     * Animates the background scrim to the given {@param alpha}.
931     */
932    private void animateBackgroundScrim(float alpha, int duration) {
933        Utilities.cancelAnimationWithoutCallbacks(mBackgroundScrimAnimator);
934        // Calculate the absolute alpha to animate from
935        final int fromAlpha = mBackgroundScrim.getAlpha();
936        final int toAlpha = (int) (alpha * 255);
937        mBackgroundScrimAnimator = ValueAnimator.ofInt(fromAlpha, toAlpha);
938        mBackgroundScrimAnimator.setDuration(duration);
939        mBackgroundScrimAnimator.setInterpolator(toAlpha > fromAlpha
940                ? Interpolators.ALPHA_IN
941                : Interpolators.ALPHA_OUT);
942        mBackgroundScrimAnimator.addUpdateListener(mUpdateBackgroundScrimAlpha);
943        mBackgroundScrimAnimator.start();
944    }
945
946    /**
947     * @return the bounds of the stack action button.
948     */
949    Rect getStackActionButtonBoundsFromStackLayout() {
950        Rect actionButtonRect = new Rect(mTaskStackView.mLayoutAlgorithm.getStackActionButtonRect());
951        int left, top;
952        if (Recents.getConfiguration().isLowRamDevice) {
953            Rect windowRect = Recents.getSystemServices().getWindowRect();
954            int spaceLeft = windowRect.width() - mSystemInsets.left - mSystemInsets.right;
955            left = (spaceLeft - mStackActionButton.getMeasuredWidth()) / 2 + mSystemInsets.left;
956            top = windowRect.height() - (mStackActionButton.getMeasuredHeight()
957                    + mSystemInsets.bottom + mStackActionButton.getPaddingBottom() / 2);
958        } else {
959            left = isLayoutRtl()
960                ? actionButtonRect.left - mStackActionButton.getPaddingLeft()
961                : actionButtonRect.right + mStackActionButton.getPaddingRight()
962                        - mStackActionButton.getMeasuredWidth();
963            top = actionButtonRect.top +
964                (actionButtonRect.height() - mStackActionButton.getMeasuredHeight()) / 2;
965        }
966        actionButtonRect.set(left, top, left + mStackActionButton.getMeasuredWidth(),
967                top + mStackActionButton.getMeasuredHeight());
968        return actionButtonRect;
969    }
970
971    View getStackActionButton() {
972        return mStackActionButton;
973    }
974
975    @Override
976    public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
977        super.requestDisallowInterceptTouchEvent(disallowIntercept);
978        mTouchHandler.cancelStackActionButtonClick();
979    }
980
981    public void dump(String prefix, PrintWriter writer) {
982        String innerPrefix = prefix + "  ";
983        String id = Integer.toHexString(System.identityHashCode(this));
984
985        writer.print(prefix); writer.print(TAG);
986        writer.print(" awaitingFirstLayout="); writer.print(mAwaitingFirstLayout ? "Y" : "N");
987        writer.print(" insets="); writer.print(Utilities.dumpRect(mSystemInsets));
988        writer.print(" [0x"); writer.print(id); writer.print("]");
989        writer.println();
990
991        if (getStack() != null) {
992            getStack().dump(innerPrefix, writer);
993        }
994        if (mTaskStackView != null) {
995            mTaskStackView.dump(innerPrefix, writer);
996        }
997    }
998}
999