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