RecentsView.java revision 931845fcb3823ce175fcfc7c6ce2d9751102c331
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.AnimatorListenerAdapter;
23import android.animation.ObjectAnimator;
24import android.content.Context;
25import android.graphics.Canvas;
26import android.graphics.Color;
27import android.graphics.Outline;
28import android.graphics.Rect;
29import android.graphics.drawable.ColorDrawable;
30import android.graphics.drawable.Drawable;
31import android.os.Handler;
32import android.util.ArraySet;
33import android.util.AttributeSet;
34import android.view.LayoutInflater;
35import android.view.MotionEvent;
36import android.view.View;
37import android.view.ViewDebug;
38import android.view.ViewOutlineProvider;
39import android.view.ViewPropertyAnimator;
40import android.view.WindowInsets;
41import android.widget.FrameLayout;
42import android.widget.TextView;
43
44import com.android.internal.logging.MetricsLogger;
45import com.android.internal.logging.MetricsProto.MetricsEvent;
46import com.android.systemui.Interpolators;
47import com.android.systemui.R;
48import com.android.systemui.recents.Recents;
49import com.android.systemui.recents.RecentsActivity;
50import com.android.systemui.recents.RecentsActivityLaunchState;
51import com.android.systemui.recents.RecentsAppWidgetHostView;
52import com.android.systemui.recents.RecentsConfiguration;
53import com.android.systemui.recents.RecentsDebugFlags;
54import com.android.systemui.recents.events.EventBus;
55import com.android.systemui.recents.events.activity.ClearHistoryEvent;
56import com.android.systemui.recents.events.activity.DismissRecentsToHomeAnimationStarted;
57import com.android.systemui.recents.events.activity.EnterRecentsWindowAnimationCompletedEvent;
58import com.android.systemui.recents.events.activity.HideHistoryButtonEvent;
59import com.android.systemui.recents.events.activity.HideHistoryEvent;
60import com.android.systemui.recents.events.activity.LaunchTaskEvent;
61import com.android.systemui.recents.events.activity.ShowHistoryButtonEvent;
62import com.android.systemui.recents.events.activity.ShowHistoryEvent;
63import com.android.systemui.recents.events.activity.TaskStackUpdatedEvent;
64import com.android.systemui.recents.events.activity.ToggleHistoryEvent;
65import com.android.systemui.recents.events.component.RecentsVisibilityChangedEvent;
66import com.android.systemui.recents.events.ui.DraggingInRecentsEndedEvent;
67import com.android.systemui.recents.events.ui.DraggingInRecentsEvent;
68import com.android.systemui.recents.events.ui.ResetBackgroundScrimEvent;
69import com.android.systemui.recents.events.ui.UpdateBackgroundScrimEvent;
70import com.android.systemui.recents.events.ui.dragndrop.DragDropTargetChangedEvent;
71import com.android.systemui.recents.events.ui.dragndrop.DragEndEvent;
72import com.android.systemui.recents.events.ui.dragndrop.DragStartEvent;
73import com.android.systemui.recents.history.RecentsHistoryView;
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.stackdivider.WindowManagerProxy;
80import com.android.systemui.statusbar.FlingAnimationUtils;
81
82import java.util.ArrayList;
83import java.util.List;
84
85/**
86 * This view is the the top level layout that contains TaskStacks (which are laid out according
87 * to their SpaceNode bounds.
88 */
89public class RecentsView extends FrameLayout {
90
91    private static final int DOCK_AREA_OVERLAY_TRANSITION_DURATION = 135;
92    private static final int DEFAULT_UPDATE_SCRIM_DURATION = 200;
93    private static final float DEFAULT_SCRIM_ALPHA = 0.33f;
94
95    private final Handler mHandler;
96
97    private TaskStack mStack;
98    private TaskStackView mTaskStackView;
99    private RecentsAppWidgetHostView mSearchBar;
100    private TextView mHistoryButton;
101    private TextView mHistoryClearAllButton;
102    private View mEmptyView;
103    private RecentsHistoryView mHistoryView;
104
105    private boolean mAwaitingFirstLayout = true;
106    private boolean mLastTaskLaunchedWasFreeform;
107
108    @ViewDebug.ExportedProperty(category="recents")
109    private Rect mSystemInsets = new Rect();
110    private int mDividerSize;
111
112    private ColorDrawable mBackgroundScrim = new ColorDrawable(Color.BLACK);
113    private Animator mBackgroundScrimAnimator;
114
115    private RecentsTransitionHelper mTransitionHelper;
116    @ViewDebug.ExportedProperty(deepExport=true, prefix="touch_")
117    private RecentsViewTouchHandler mTouchHandler;
118    private final FlingAnimationUtils mFlingAnimationUtils;
119
120    public RecentsView(Context context) {
121        this(context, null);
122    }
123
124    public RecentsView(Context context, AttributeSet attrs) {
125        this(context, attrs, 0);
126    }
127
128    public RecentsView(Context context, AttributeSet attrs, int defStyleAttr) {
129        this(context, attrs, defStyleAttr, 0);
130    }
131
132    public RecentsView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
133        super(context, attrs, defStyleAttr, defStyleRes);
134        setWillNotDraw(false);
135
136        SystemServicesProxy ssp = Recents.getSystemServices();
137        mHandler = new Handler();
138        mTransitionHelper = new RecentsTransitionHelper(getContext(), mHandler);
139        mDividerSize = ssp.getDockedDividerSize(context);
140        mTouchHandler = new RecentsViewTouchHandler(this);
141        mFlingAnimationUtils = new FlingAnimationUtils(context, 0.3f);
142
143        final float cornerRadius = context.getResources().getDimensionPixelSize(
144                R.dimen.recents_task_view_rounded_corners_radius);
145        LayoutInflater inflater = LayoutInflater.from(context);
146        if (RecentsDebugFlags.Static.EnableHistory) {
147            mHistoryButton = (TextView) inflater.inflate(R.layout.recents_history_button, this,
148                    false);
149            mHistoryButton.setOnClickListener(new View.OnClickListener() {
150                @Override
151                public void onClick(View v) {
152                    EventBus.getDefault().send(new ToggleHistoryEvent());
153                }
154            });
155            addView(mHistoryButton);
156            mHistoryButton.setClipToOutline(true);
157            mHistoryButton.setOutlineProvider(new ViewOutlineProvider() {
158                @Override
159                public void getOutline(View view, Outline outline) {
160                    outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(), cornerRadius);
161                }
162            });
163        }
164        mEmptyView = inflater.inflate(R.layout.recents_empty, this, false);
165        addView(mEmptyView);
166
167        setBackground(mBackgroundScrim);
168    }
169
170    /** Set/get the bsp root node */
171    public void setTaskStack(TaskStack stack) {
172        RecentsConfiguration config = Recents.getConfiguration();
173        RecentsActivityLaunchState launchState = config.getLaunchState();
174        mStack = stack;
175        if (launchState.launchedReuseTaskStackViews) {
176            if (mTaskStackView != null) {
177                // If onRecentsHidden is not triggered, we need to the stack view again here
178                mTaskStackView.reset();
179                mTaskStackView.setStack(stack);
180            } else {
181                mTaskStackView = new TaskStackView(getContext(), stack);
182                addView(mTaskStackView);
183            }
184        } else {
185            if (mTaskStackView != null) {
186                removeView(mTaskStackView);
187            }
188            mTaskStackView = new TaskStackView(getContext(), stack);
189            addView(mTaskStackView);
190        }
191
192        // If we are already occluded by the app, then just set the default background scrim now.
193        // Otherwise, defer until the enter animation completes to animate the scrim with the
194        // tasks for the home animation.
195        if (launchState.launchedWhileDocking || launchState.launchedFromAppWithThumbnail
196                || mStack.getTaskCount() == 0) {
197            mBackgroundScrim.setAlpha((int) (DEFAULT_SCRIM_ALPHA * 255));
198        } else {
199            mBackgroundScrim.setAlpha(0);
200        }
201
202        // Update the top level view's visibilities
203        if (stack.getTaskCount() > 0) {
204            hideEmptyView();
205        } else {
206            showEmptyView();
207        }
208
209        // Trigger a new layout
210        requestLayout();
211    }
212
213    /**
214     * Returns whether the last task launched was in the freeform stack or not.
215     */
216    public boolean isLastTaskLaunchedFreeform() {
217        return mLastTaskLaunchedWasFreeform;
218    }
219
220    /**
221     * Returns whether the history is visible or not.
222     */
223    public boolean isHistoryVisible() {
224        return mHistoryView != null && mHistoryView.isVisible();
225    }
226
227    /**
228     * Returns the currently set task stack.
229     */
230    public TaskStack getTaskStack() {
231        return mStack;
232    }
233
234    /** Gets the next task in the stack - or if the last - the top task */
235    public Task getNextTaskOrTopTask(Task taskToSearch) {
236        Task returnTask = null;
237        boolean found = false;
238        if (mTaskStackView != null) {
239            TaskStack stack = mTaskStackView.getStack();
240            ArrayList<Task> taskList = stack.getStackTasks();
241            // Iterate the stack views and try and find the focused task
242            for (int j = taskList.size() - 1; j >= 0; --j) {
243                Task task = taskList.get(j);
244                // Return the next task in the line.
245                if (found)
246                    return task;
247                // Remember the first possible task as the top task.
248                if (returnTask == null)
249                    returnTask = task;
250                if (task == taskToSearch)
251                    found = true;
252            }
253        }
254        return returnTask;
255    }
256
257    /** Launches the focused task from the first stack if possible */
258    public boolean launchFocusedTask(int logEvent) {
259        if (mTaskStackView != null) {
260            Task task = mTaskStackView.getFocusedTask();
261            if (task != null) {
262                TaskView taskView = mTaskStackView.getChildViewForTask(task);
263                EventBus.getDefault().send(new LaunchTaskEvent(taskView, task, null,
264                        INVALID_STACK_ID, false));
265
266                if (logEvent != 0) {
267                    MetricsLogger.action(getContext(), logEvent,
268                            task.key.getComponent().toString());
269                }
270                return true;
271            }
272        }
273        return false;
274    }
275
276    /** Launches the task that recents was launched from if possible */
277    public boolean launchPreviousTask() {
278        if (mTaskStackView != null) {
279            TaskStack stack = mTaskStackView.getStack();
280            Task task = stack.getLaunchTarget();
281            if (task != null) {
282                TaskView taskView = mTaskStackView.getChildViewForTask(task);
283                EventBus.getDefault().send(new LaunchTaskEvent(taskView, task, null,
284                        INVALID_STACK_ID, false));
285                return true;
286            }
287        }
288        return false;
289    }
290
291    /** Launches a given task. */
292    public boolean launchTask(Task task, Rect taskBounds, int destinationStack) {
293        if (mTaskStackView != null) {
294            // Iterate the stack views and try and find the given task.
295            List<TaskView> taskViews = mTaskStackView.getTaskViews();
296            int taskViewCount = taskViews.size();
297            for (int j = 0; j < taskViewCount; j++) {
298                TaskView tv = taskViews.get(j);
299                if (tv.getTask() == task) {
300                    EventBus.getDefault().send(new LaunchTaskEvent(tv, task, taskBounds,
301                            destinationStack, false));
302                    return true;
303                }
304            }
305        }
306        return false;
307    }
308
309    /** Adds the search bar */
310    public void setSearchBar(RecentsAppWidgetHostView searchBar) {
311        // Remove the previous search bar if one exists
312        if (mSearchBar != null && indexOfChild(mSearchBar) > -1) {
313            removeView(mSearchBar);
314        }
315        // Add the new search bar
316        if (searchBar != null) {
317            mSearchBar = searchBar;
318            addView(mSearchBar);
319        }
320    }
321
322    /** Returns whether there is currently a search bar */
323    public boolean hasValidSearchBar() {
324        return mSearchBar != null && !mSearchBar.isReinflateRequired();
325    }
326
327    /**
328     * Hides the task stack and shows the empty view.
329     */
330    public void showEmptyView() {
331        if (RecentsDebugFlags.Static.EnableSearchBar && (mSearchBar != null)) {
332            mSearchBar.setVisibility(View.INVISIBLE);
333        }
334        mTaskStackView.setVisibility(View.INVISIBLE);
335        mEmptyView.setVisibility(View.VISIBLE);
336        mEmptyView.bringToFront();
337        if (RecentsDebugFlags.Static.EnableHistory) {
338            mHistoryButton.bringToFront();
339        }
340    }
341
342    /**
343     * Shows the task stack and hides the empty view.
344     */
345    public void hideEmptyView() {
346        mEmptyView.setVisibility(View.INVISIBLE);
347        mTaskStackView.setVisibility(View.VISIBLE);
348        if (RecentsDebugFlags.Static.EnableSearchBar && (mSearchBar != null)) {
349            mSearchBar.setVisibility(View.VISIBLE);
350        }
351        mTaskStackView.bringToFront();
352        if (mSearchBar != null) {
353            mSearchBar.bringToFront();
354        }
355        if (RecentsDebugFlags.Static.EnableHistory) {
356            mHistoryButton.bringToFront();
357        }
358    }
359
360    @Override
361    protected void onAttachedToWindow() {
362        EventBus.getDefault().register(this, RecentsActivity.EVENT_BUS_PRIORITY + 1);
363        EventBus.getDefault().register(mTouchHandler, RecentsActivity.EVENT_BUS_PRIORITY + 2);
364        super.onAttachedToWindow();
365    }
366
367    @Override
368    protected void onDetachedFromWindow() {
369        super.onDetachedFromWindow();
370        EventBus.getDefault().unregister(this);
371        EventBus.getDefault().unregister(mTouchHandler);
372    }
373
374    /**
375     * This is called with the full size of the window since we are handling our own insets.
376     */
377    @Override
378    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
379        RecentsConfiguration config = Recents.getConfiguration();
380        int width = MeasureSpec.getSize(widthMeasureSpec);
381        int height = MeasureSpec.getSize(heightMeasureSpec);
382
383        // Get the search bar bounds and measure the search bar layout
384        Rect searchBarSpaceBounds = new Rect();
385        if (mSearchBar != null) {
386            config.getSearchBarBounds(new Rect(0, 0, width, height), mSystemInsets.top,
387                    searchBarSpaceBounds);
388            mSearchBar.measure(
389                    MeasureSpec.makeMeasureSpec(searchBarSpaceBounds.width(), MeasureSpec.EXACTLY),
390                    MeasureSpec.makeMeasureSpec(searchBarSpaceBounds.height(), MeasureSpec.EXACTLY));
391        }
392
393        Rect taskStackBounds = new Rect();
394        config.getTaskStackBounds(new Rect(0, 0, width, height), mSystemInsets.top,
395                mSystemInsets.right, searchBarSpaceBounds, taskStackBounds);
396        if (mTaskStackView != null && mTaskStackView.getVisibility() != GONE) {
397            mTaskStackView.setTaskStackBounds(taskStackBounds, mSystemInsets);
398            mTaskStackView.measure(widthMeasureSpec, heightMeasureSpec);
399        }
400
401        // Measure the empty view to the full size of the screen
402        if (mEmptyView.getVisibility() != GONE) {
403            measureChild(mEmptyView, MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
404                    MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
405        }
406
407        if (RecentsDebugFlags.Static.EnableHistory) {
408            // Measure the history view
409            if (mHistoryView != null && mHistoryView.getVisibility() != GONE) {
410                measureChild(mHistoryView, MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
411                        MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
412            }
413
414            // Measure the history button within the constraints of the space above the stack
415            Rect historyButtonRect = mTaskStackView.mLayoutAlgorithm.mHistoryButtonRect;
416            measureChild(mHistoryButton,
417                    MeasureSpec.makeMeasureSpec(historyButtonRect.width(), MeasureSpec.AT_MOST),
418                    MeasureSpec.makeMeasureSpec(historyButtonRect.height(), MeasureSpec.AT_MOST));
419            if (mHistoryClearAllButton != null && mHistoryClearAllButton.getVisibility() != GONE) {
420                measureChild(mHistoryClearAllButton,
421                    MeasureSpec.makeMeasureSpec(historyButtonRect.width(), MeasureSpec.AT_MOST),
422                    MeasureSpec.makeMeasureSpec(historyButtonRect.height(), MeasureSpec.AT_MOST));
423            }
424        }
425
426        setMeasuredDimension(width, height);
427    }
428
429    /**
430     * This is called with the full size of the window since we are handling our own insets.
431     */
432    @Override
433    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
434        RecentsConfiguration config = Recents.getConfiguration();
435
436        // Get the search bar bounds so that we lay it out
437        Rect measuredRect = new Rect(0, 0, getMeasuredWidth(), getMeasuredHeight());
438        Rect searchBarSpaceBounds = new Rect();
439        if (mSearchBar != null) {
440            config.getSearchBarBounds(measuredRect,
441                    mSystemInsets.top, searchBarSpaceBounds);
442            mSearchBar.layout(searchBarSpaceBounds.left, searchBarSpaceBounds.top,
443                    searchBarSpaceBounds.right, searchBarSpaceBounds.bottom);
444        }
445
446        if (mTaskStackView != null && mTaskStackView.getVisibility() != GONE) {
447            mTaskStackView.layout(left, top, left + getMeasuredWidth(), top + getMeasuredHeight());
448        }
449
450        // Layout the empty view
451        if (mEmptyView.getVisibility() != GONE) {
452            mEmptyView.layout(left, top, right, bottom);
453        }
454
455        if (RecentsDebugFlags.Static.EnableHistory) {
456            // Layout the history view
457            if (mHistoryView != null && mHistoryView.getVisibility() != GONE) {
458                mHistoryView.layout(left, top, right, bottom);
459            }
460
461            // Layout the history button such that its drawable is start-aligned with the stack,
462            // vertically centered in the available space above the stack
463            Rect historyButtonRect = mTaskStackView.mLayoutAlgorithm.mHistoryButtonRect;
464            int historyLeft = isLayoutRtl()
465                    ? historyButtonRect.right + mHistoryButton.getPaddingStart()
466                    - mHistoryButton.getMeasuredWidth()
467                    : historyButtonRect.left - mHistoryButton.getPaddingStart();
468            int historyTop = historyButtonRect.top +
469                    (historyButtonRect.height() - mHistoryButton.getMeasuredHeight()) / 2;
470            mHistoryButton.layout(historyLeft, historyTop,
471                    historyLeft + mHistoryButton.getMeasuredWidth(),
472                    historyTop + mHistoryButton.getMeasuredHeight());
473
474            // Layout the history clear all button such that it is end-aligned with the stack,
475            // vertically centered in the available space above the stack
476            if (mHistoryClearAllButton != null && mHistoryClearAllButton.getVisibility() != GONE) {
477                int clearAllLeft = isLayoutRtl()
478                        ? historyButtonRect.left - mHistoryClearAllButton.getPaddingStart()
479                        : historyButtonRect.right + mHistoryClearAllButton.getPaddingStart()
480                        - mHistoryClearAllButton.getMeasuredWidth();
481                int clearAllTop = historyButtonRect.top +
482                        (historyButtonRect.height() - mHistoryClearAllButton.getMeasuredHeight()) /
483                                2;
484                mHistoryClearAllButton.layout(clearAllLeft, clearAllTop,
485                        clearAllLeft + mHistoryClearAllButton.getMeasuredWidth(),
486                        clearAllTop + mHistoryClearAllButton.getMeasuredHeight());
487            }
488        }
489
490        if (mAwaitingFirstLayout) {
491            mAwaitingFirstLayout = false;
492
493            // If launched via dragging from the nav bar, then we should translate the whole view
494            // down offscreen
495            RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState();
496            if (launchState.launchedViaDragGesture) {
497                setTranslationY(getMeasuredHeight());
498            } else {
499                setTranslationY(0f);
500            }
501        }
502    }
503
504    @Override
505    public WindowInsets onApplyWindowInsets(WindowInsets insets) {
506        mSystemInsets.set(insets.getSystemWindowInsets());
507        requestLayout();
508        return insets;
509    }
510
511    @Override
512    public boolean onInterceptTouchEvent(MotionEvent ev) {
513        return mTouchHandler.onInterceptTouchEvent(ev);
514    }
515
516    @Override
517    public boolean onTouchEvent(MotionEvent ev) {
518        return mTouchHandler.onTouchEvent(ev);
519    }
520
521    @Override
522    public void onDrawForeground(Canvas canvas) {
523        super.onDrawForeground(canvas);
524
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.getAlpha() > 0) {
529                d.draw(canvas);
530            }
531        }
532    }
533
534    @Override
535    protected boolean verifyDrawable(Drawable who) {
536        ArrayList<TaskStack.DockState> visDockStates = mTouchHandler.getVisibleDockStates();
537        for (int i = visDockStates.size() - 1; i >= 0; i--) {
538            Drawable d = visDockStates.get(i).viewState.dockAreaOverlay;
539            if (d == who) {
540                return true;
541            }
542        }
543        return super.verifyDrawable(who);
544    }
545
546    /**** EventBus Events ****/
547
548    public final void onBusEvent(LaunchTaskEvent event) {
549        mLastTaskLaunchedWasFreeform = event.task.isFreeformTask();
550        mTransitionHelper.launchTaskFromRecents(mStack, event.task, mTaskStackView, event.taskView,
551                event.screenPinningRequested, event.targetTaskBounds, event.targetTaskStack);
552    }
553
554    public final void onBusEvent(DismissRecentsToHomeAnimationStarted event) {
555        int taskViewExitToHomeDuration = TaskStackAnimationHelper.EXIT_TO_HOME_TRANSLATION_DURATION;
556        if (RecentsDebugFlags.Static.EnableHistory) {
557            // Hide the history button
558            hideHistoryButton(taskViewExitToHomeDuration, false /* translate */);
559        }
560        animateBackgroundScrim(0f, taskViewExitToHomeDuration);
561    }
562
563    public final void onBusEvent(DragStartEvent event) {
564        updateVisibleDockRegions(mTouchHandler.getDockStatesForCurrentOrientation(),
565                true /* isDefaultDockState */, TaskStack.DockState.NONE.viewState.dockAreaAlpha,
566                true /* animateAlpha */, false /* animateBounds */);
567    }
568
569    public final void onBusEvent(DragDropTargetChangedEvent event) {
570        if (event.dropTarget == null || !(event.dropTarget instanceof TaskStack.DockState)) {
571            updateVisibleDockRegions(mTouchHandler.getDockStatesForCurrentOrientation(),
572                    true /* isDefaultDockState */, TaskStack.DockState.NONE.viewState.dockAreaAlpha,
573                    true /* animateAlpha */, true /* animateBounds */);
574        } else {
575            final TaskStack.DockState dockState = (TaskStack.DockState) event.dropTarget;
576            updateVisibleDockRegions(new TaskStack.DockState[] {dockState},
577                    false /* isDefaultDockState */, -1, true /* animateAlpha */,
578                    true /* animateBounds */);
579        }
580    }
581
582    public final void onBusEvent(final DragEndEvent event) {
583        // Handle the case where we drop onto a dock region
584        if (event.dropTarget instanceof TaskStack.DockState) {
585            final TaskStack.DockState dockState = (TaskStack.DockState) event.dropTarget;
586
587            // Hide the dock region
588            updateVisibleDockRegions(null, false /* isDefaultDockState */, -1,
589                    false /* animateAlpha */, false /* animateBounds */);
590
591            TaskStackLayoutAlgorithm stackLayout = mTaskStackView.getStackAlgorithm();
592            TaskStackViewScroller stackScroller = mTaskStackView.getScroller();
593            TaskViewTransform tmpTransform = new TaskViewTransform();
594
595            // We translated the view but we need to animate it back from the current layout-space
596            // rect to its final layout-space rect
597            int x = (int) event.taskView.getTranslationX();
598            int y = (int) event.taskView.getTranslationY();
599            Rect taskViewRect = new Rect(event.taskView.getLeft(), event.taskView.getTop(),
600                    event.taskView.getRight(), event.taskView.getBottom());
601            taskViewRect.offset(x, y);
602            event.taskView.setTranslationX(0);
603            event.taskView.setTranslationY(0);
604            event.taskView.setLeftTopRightBottom(taskViewRect.left, taskViewRect.top,
605                    taskViewRect.right, taskViewRect.bottom);
606
607            // Remove the task view after it is docked
608            mTaskStackView.updateLayoutAlgorithm(false /* boundScroll */);
609            stackLayout.getStackTransform(event.task, stackScroller.getStackScroll(), tmpTransform,
610                    null);
611            tmpTransform.alpha = 0;
612            tmpTransform.scale = 1f;
613            tmpTransform.rect.set(taskViewRect);
614            mTaskStackView.updateTaskViewToTransform(event.taskView, tmpTransform,
615                    new AnimationProps(125, Interpolators.ALPHA_OUT,
616                            new AnimatorListenerAdapter() {
617                                @Override
618                                public void onAnimationEnd(Animator animation) {
619                                    // Dock the task and launch it
620                                    SystemServicesProxy ssp = Recents.getSystemServices();
621                                    ssp.startTaskInDockedMode(getContext(), event.taskView,
622                                            event.task.key.id, dockState.createMode);
623
624                                    // Animate the stack accordingly
625                                    AnimationProps stackAnim = new AnimationProps(
626                                            TaskStackView.DEFAULT_SYNC_STACK_DURATION,
627                                            Interpolators.FAST_OUT_SLOW_IN);
628                                    mTaskStackView.getStack().removeTask(event.task, stackAnim);
629                                }
630                            }));
631
632            MetricsLogger.action(mContext, MetricsEvent.ACTION_WINDOW_DOCK_DRAG_DROP);
633        } else {
634            // Animate the overlay alpha back to 0
635            updateVisibleDockRegions(null, true /* isDefaultDockState */, -1,
636                    true /* animateAlpha */, false /* animateBounds */);
637        }
638    }
639
640    public final void onBusEvent(DraggingInRecentsEvent event) {
641        if (mTaskStackView.getTaskViews().size() > 0) {
642            setTranslationY(event.distanceFromTop - mTaskStackView.getTaskViews().get(0).getY());
643        }
644    }
645
646    public final void onBusEvent(DraggingInRecentsEndedEvent event) {
647        ViewPropertyAnimator animator = animate();
648        if (event.velocity > mFlingAnimationUtils.getMinVelocityPxPerSecond()) {
649            animator.translationY(getHeight());
650            animator.withEndAction(new Runnable() {
651                @Override
652                public void run() {
653                    WindowManagerProxy.getInstance().maximizeDockedStack();
654                }
655            });
656            mFlingAnimationUtils.apply(animator, getTranslationY(), getHeight(), event.velocity);
657        } else {
658            animator.translationY(0f);
659            animator.setListener(null);
660            mFlingAnimationUtils.apply(animator, getTranslationY(), 0, event.velocity);
661        }
662        animator.start();
663    }
664
665    public final void onBusEvent(TaskStackUpdatedEvent event) {
666        if (!event.inMultiWindow) {
667            mStack.setTasks(event.stack.computeAllTasksList(), true /* notifyStackChanges */);
668            mStack.createAffiliatedGroupings(getContext());
669        }
670    }
671
672    public final void onBusEvent(EnterRecentsWindowAnimationCompletedEvent event) {
673        RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState();
674        if (!launchState.launchedWhileDocking && !launchState.launchedFromAppWithThumbnail
675                && mStack.getTaskCount() > 0) {
676            animateBackgroundScrim(DEFAULT_SCRIM_ALPHA,
677                    TaskStackAnimationHelper.ENTER_FROM_HOME_TRANSLATION_DURATION);
678        }
679    }
680
681    public final void onBusEvent(UpdateBackgroundScrimEvent event) {
682        animateBackgroundScrim(event.alpha, DEFAULT_UPDATE_SCRIM_DURATION);
683    }
684
685    public final void onBusEvent(ResetBackgroundScrimEvent event) {
686        animateBackgroundScrim(DEFAULT_SCRIM_ALPHA, DEFAULT_UPDATE_SCRIM_DURATION);
687    }
688
689    public final void onBusEvent(RecentsVisibilityChangedEvent event) {
690        if (!event.visible) {
691            // Reset the view state
692            mAwaitingFirstLayout = true;
693            mLastTaskLaunchedWasFreeform = false;
694            if (RecentsDebugFlags.Static.EnableHistory) {
695                hideHistoryButton(0, false /* translate */);
696            }
697        }
698    }
699
700    public final void onBusEvent(ToggleHistoryEvent event) {
701        if (!RecentsDebugFlags.Static.EnableHistory) {
702            return;
703        }
704
705        if (mHistoryView != null && mHistoryView.isVisible()) {
706            EventBus.getDefault().send(new HideHistoryEvent(true /* animate */));
707        } else {
708            EventBus.getDefault().send(new ShowHistoryEvent());
709        }
710    }
711
712    public final void onBusEvent(ShowHistoryEvent event) {
713        if (!RecentsDebugFlags.Static.EnableHistory) {
714            return;
715        }
716
717        if (mHistoryView == null) {
718            LayoutInflater inflater = LayoutInflater.from(getContext());
719            mHistoryView = (RecentsHistoryView) inflater.inflate(R.layout.recents_history, this,
720                    false);
721            addView(mHistoryView);
722
723            final float cornerRadius = getResources().getDimensionPixelSize(
724                    R.dimen.recents_task_view_rounded_corners_radius);
725            mHistoryClearAllButton = (TextView) inflater.inflate(
726                    R.layout.recents_history_clear_all_button, this, false);
727            mHistoryClearAllButton.setOnClickListener(new View.OnClickListener() {
728                @Override
729                public void onClick(View v) {
730                    EventBus.getDefault().send(new ClearHistoryEvent());
731                }
732            });
733            mHistoryClearAllButton.setClipToOutline(true);
734            mHistoryClearAllButton.setOutlineProvider(new ViewOutlineProvider() {
735                @Override
736                public void getOutline(View view, Outline outline) {
737                    outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(), cornerRadius);
738                }
739            });
740            addView(mHistoryClearAllButton);
741
742            // Since this history view is inflated by a view stub after the insets have already
743            // been applied, we have to set them ourselves initial from the insets that were last
744            // provided.
745            mHistoryView.setSystemInsets(mSystemInsets);
746            mHistoryView.setHeaderHeight(mHistoryButton.getMeasuredHeight());
747            mHistoryButton.bringToFront();
748            mHistoryClearAllButton.bringToFront();
749        }
750
751        // Animate the empty view in parallel with the history view (the task view animations are
752        // handled in TaskStackView)
753        Rect stackRect = mTaskStackView.mLayoutAlgorithm.mStackRect;
754        if (mEmptyView.getVisibility() == View.VISIBLE) {
755            int historyTransitionDuration = getResources().getInteger(
756                    R.integer.recents_history_transition_duration);
757            mEmptyView.animate()
758                    .alpha(0f)
759                    .translationY(stackRect.height() / 2)
760                    .setDuration(historyTransitionDuration)
761                    .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
762                    .withEndAction(new Runnable() {
763                        @Override
764                        public void run() {
765                            mEmptyView.setVisibility(View.INVISIBLE);
766                        }
767                    })
768                    .start();
769        }
770
771        mHistoryView.show(mStack, stackRect.height(), mHistoryClearAllButton);
772    }
773
774    public final void onBusEvent(HideHistoryEvent event) {
775        if (!RecentsDebugFlags.Static.EnableHistory) {
776            return;
777        }
778
779        // Animate the empty view in parallel with the history view (the task view animations are
780        // handled in TaskStackView)
781        Rect stackRect = mTaskStackView.mLayoutAlgorithm.mStackRect;
782        if (mStack.getTaskCount() == 0) {
783            int historyTransitionDuration = getResources().getInteger(
784                    R.integer.recents_history_transition_duration);
785            mEmptyView.setVisibility(View.VISIBLE);
786            mEmptyView.animate()
787                    .alpha(1f)
788                    .translationY(0)
789                    .setDuration(historyTransitionDuration)
790                    .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
791                    .start();
792        }
793
794        mHistoryView.hide(event.animate, stackRect.height(), mHistoryClearAllButton);
795    }
796
797    public final void onBusEvent(ShowHistoryButtonEvent event) {
798        if (!RecentsDebugFlags.Static.EnableHistory) {
799            return;
800        }
801
802        showHistoryButton(150, event.translate);
803    }
804
805    public final void onBusEvent(HideHistoryButtonEvent event) {
806        if (!RecentsDebugFlags.Static.EnableHistory) {
807            return;
808        }
809
810        hideHistoryButton(100, true /* translate */);
811    }
812
813    /**
814     * Shows the history button.
815     */
816    private void showHistoryButton(final int duration, final boolean translate) {
817        if (!RecentsDebugFlags.Static.EnableHistory) {
818            return;
819        }
820
821        final ReferenceCountedTrigger postAnimationTrigger = new ReferenceCountedTrigger();
822        if (mHistoryButton.getVisibility() == View.INVISIBLE) {
823            mHistoryButton.setVisibility(View.VISIBLE);
824            mHistoryButton.setAlpha(0f);
825            if (translate) {
826                mHistoryButton.setTranslationY(-mHistoryButton.getMeasuredHeight() * 0.25f);
827            } else {
828                mHistoryButton.setTranslationY(0f);
829            }
830            postAnimationTrigger.addLastDecrementRunnable(new Runnable() {
831                @Override
832                public void run() {
833                    if (translate) {
834                        mHistoryButton.animate()
835                            .translationY(0f);
836                    }
837                    mHistoryButton.animate()
838                            .alpha(1f)
839                            .setDuration(duration)
840                            .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
841                            .withLayer()
842                            .start();
843                }
844            });
845        }
846        postAnimationTrigger.flushLastDecrementRunnables();
847    }
848
849    /**
850     * Hides the history button.
851     */
852    private void hideHistoryButton(int duration, boolean translate) {
853        if (!RecentsDebugFlags.Static.EnableHistory) {
854            return;
855        }
856
857        final ReferenceCountedTrigger postAnimationTrigger = new ReferenceCountedTrigger();
858        hideHistoryButton(duration, translate, postAnimationTrigger);
859        postAnimationTrigger.flushLastDecrementRunnables();
860    }
861
862    /**
863     * Hides the history button.
864     */
865    private void hideHistoryButton(int duration, boolean translate,
866            final ReferenceCountedTrigger postAnimationTrigger) {
867        if (!RecentsDebugFlags.Static.EnableHistory) {
868            return;
869        }
870
871        if (mHistoryButton.getVisibility() == View.VISIBLE) {
872            if (translate) {
873                mHistoryButton.animate()
874                    .translationY(-mHistoryButton.getMeasuredHeight() * 0.25f);
875            }
876            mHistoryButton.animate()
877                    .alpha(0f)
878                    .setDuration(duration)
879                    .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
880                    .withEndAction(new Runnable() {
881                        @Override
882                        public void run() {
883                            mHistoryButton.setVisibility(View.INVISIBLE);
884                            postAnimationTrigger.decrement();
885                        }
886                    })
887                    .withLayer()
888                    .start();
889            postAnimationTrigger.increment();
890        }
891    }
892
893    /**
894     * Updates the dock region to match the specified dock state.
895     */
896    private void updateVisibleDockRegions(TaskStack.DockState[] newDockStates,
897            boolean isDefaultDockState, int overrideAlpha, boolean animateAlpha,
898            boolean animateBounds) {
899        ArraySet<TaskStack.DockState> newDockStatesSet = Utilities.arrayToSet(newDockStates,
900                new ArraySet<TaskStack.DockState>());
901        ArrayList<TaskStack.DockState> visDockStates = mTouchHandler.getVisibleDockStates();
902        for (int i = visDockStates.size() - 1; i >= 0; i--) {
903            TaskStack.DockState dockState = visDockStates.get(i);
904            TaskStack.DockState.ViewState viewState = dockState.viewState;
905            if (newDockStates == null || !newDockStatesSet.contains(dockState)) {
906                // This is no longer visible, so hide it
907                viewState.startAnimation(null, 0, DOCK_AREA_OVERLAY_TRANSITION_DURATION,
908                        Interpolators.ALPHA_OUT, animateAlpha, animateBounds);
909            } else {
910                // This state is now visible, update the bounds and show it
911                int alpha = (overrideAlpha != -1 ? overrideAlpha : viewState.dockAreaAlpha);
912                Rect bounds = isDefaultDockState
913                        ? dockState.getPreDockedBounds(getMeasuredWidth(), getMeasuredHeight())
914                        : dockState.getDockedBounds(getMeasuredWidth(), getMeasuredHeight(),
915                        mDividerSize, mSystemInsets, getResources());
916                if (viewState.dockAreaOverlay.getCallback() != this) {
917                    viewState.dockAreaOverlay.setCallback(this);
918                    viewState.dockAreaOverlay.setBounds(bounds);
919                }
920                viewState.startAnimation(bounds, alpha, DOCK_AREA_OVERLAY_TRANSITION_DURATION,
921                        Interpolators.ALPHA_IN, animateAlpha, animateBounds);
922            }
923        }
924    }
925
926    /**
927     * Animates the background scrim to the given {@param alpha}.
928     */
929    private void animateBackgroundScrim(float alpha, int duration) {
930        Utilities.cancelAnimationWithoutCallbacks(mBackgroundScrimAnimator);
931        int alphaInt = (int) (alpha * 255);
932        mBackgroundScrimAnimator = ObjectAnimator.ofInt(mBackgroundScrim, Utilities.DRAWABLE_ALPHA,
933                mBackgroundScrim.getAlpha(), alphaInt);
934        mBackgroundScrimAnimator.setDuration(duration);
935        mBackgroundScrimAnimator.setInterpolator(alphaInt > mBackgroundScrim.getAlpha()
936                ? Interpolators.ALPHA_OUT
937                : Interpolators.ALPHA_IN);
938        mBackgroundScrimAnimator.start();
939    }
940}
941