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