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