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