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