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