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 
17 package com.android.systemui.recents.views;
18 
19 import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
20 
21 import android.animation.Animator;
22 import android.animation.ObjectAnimator;
23 import android.app.ActivityOptions.OnAnimationStartedListener;
24 import android.content.Context;
25 import android.graphics.Canvas;
26 import android.graphics.Color;
27 import android.graphics.Outline;
28 import android.graphics.Rect;
29 import android.graphics.drawable.ColorDrawable;
30 import android.graphics.drawable.Drawable;
31 import android.util.ArraySet;
32 import android.util.AttributeSet;
33 import android.view.AppTransitionAnimationSpec;
34 import android.view.IAppTransitionAnimationSpecsFuture;
35 import android.view.LayoutInflater;
36 import android.view.MotionEvent;
37 import android.view.View;
38 import android.view.ViewDebug;
39 import android.view.ViewOutlineProvider;
40 import android.view.ViewPropertyAnimator;
41 import android.view.WindowInsets;
42 import android.widget.FrameLayout;
43 import android.widget.TextView;
44 
45 import com.android.internal.logging.MetricsLogger;
46 import com.android.internal.logging.MetricsProto.MetricsEvent;
47 import com.android.systemui.Interpolators;
48 import com.android.systemui.R;
49 import com.android.systemui.recents.Recents;
50 import com.android.systemui.recents.RecentsActivity;
51 import com.android.systemui.recents.RecentsActivityLaunchState;
52 import com.android.systemui.recents.RecentsConfiguration;
53 import com.android.systemui.recents.RecentsDebugFlags;
54 import com.android.systemui.recents.events.EventBus;
55 import com.android.systemui.recents.events.activity.DismissRecentsToHomeAnimationStarted;
56 import com.android.systemui.recents.events.activity.DockedFirstAnimationFrameEvent;
57 import com.android.systemui.recents.events.activity.EnterRecentsWindowAnimationCompletedEvent;
58 import com.android.systemui.recents.events.activity.HideStackActionButtonEvent;
59 import com.android.systemui.recents.events.activity.LaunchTaskEvent;
60 import com.android.systemui.recents.events.activity.MultiWindowStateChangedEvent;
61 import com.android.systemui.recents.events.activity.ShowStackActionButtonEvent;
62 import com.android.systemui.recents.events.ui.AllTaskViewsDismissedEvent;
63 import com.android.systemui.recents.events.ui.DismissAllTaskViewsEvent;
64 import com.android.systemui.recents.events.ui.DraggingInRecentsEndedEvent;
65 import com.android.systemui.recents.events.ui.DraggingInRecentsEvent;
66 import com.android.systemui.recents.events.ui.dragndrop.DragDropTargetChangedEvent;
67 import com.android.systemui.recents.events.ui.dragndrop.DragEndCancelledEvent;
68 import com.android.systemui.recents.events.ui.dragndrop.DragEndEvent;
69 import com.android.systemui.recents.events.ui.dragndrop.DragStartEvent;
70 import com.android.systemui.recents.misc.ReferenceCountedTrigger;
71 import com.android.systemui.recents.misc.SystemServicesProxy;
72 import com.android.systemui.recents.misc.Utilities;
73 import com.android.systemui.recents.model.Task;
74 import com.android.systemui.recents.model.TaskStack;
75 import com.android.systemui.recents.views.RecentsTransitionHelper.AnimationSpecComposer;
76 import com.android.systemui.stackdivider.WindowManagerProxy;
77 import com.android.systemui.statusbar.FlingAnimationUtils;
78 
79 import java.io.FileDescriptor;
80 import java.io.PrintWriter;
81 import java.util.ArrayList;
82 import java.util.List;
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  */
88 public class RecentsView extends FrameLayout {
89 
90     private static final String TAG = "RecentsView";
91 
92     private static final int DEFAULT_UPDATE_SCRIM_DURATION = 200;
93     private static final float DEFAULT_SCRIM_ALPHA = 0.33f;
94 
95     private static final int SHOW_STACK_ACTION_BUTTON_DURATION = 134;
96     private static final int HIDE_STACK_ACTION_BUTTON_DURATION = 100;
97 
98     private TaskStack mStack;
99     private TaskStackView mTaskStackView;
100     private TextView mStackActionButton;
101     private TextView mEmptyView;
102 
103     private boolean mAwaitingFirstLayout = true;
104     private boolean mLastTaskLaunchedWasFreeform;
105 
106     @ViewDebug.ExportedProperty(category="recents")
107     private Rect mSystemInsets = new Rect();
108     private int mDividerSize;
109 
110     private Drawable mBackgroundScrim = new ColorDrawable(
111             Color.argb((int) (DEFAULT_SCRIM_ALPHA * 255), 0, 0, 0)).mutate();
112     private Animator mBackgroundScrimAnimator;
113 
114     private RecentsTransitionHelper mTransitionHelper;
115     @ViewDebug.ExportedProperty(deepExport=true, prefix="touch_")
116     private RecentsViewTouchHandler mTouchHandler;
117     private final FlingAnimationUtils mFlingAnimationUtils;
118 
RecentsView(Context context)119     public RecentsView(Context context) {
120         this(context, null);
121     }
122 
RecentsView(Context context, AttributeSet attrs)123     public RecentsView(Context context, AttributeSet attrs) {
124         this(context, attrs, 0);
125     }
126 
RecentsView(Context context, AttributeSet attrs, int defStyleAttr)127     public RecentsView(Context context, AttributeSet attrs, int defStyleAttr) {
128         this(context, attrs, defStyleAttr, 0);
129     }
130 
RecentsView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)131     public RecentsView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
132         super(context, attrs, defStyleAttr, defStyleRes);
133         setWillNotDraw(false);
134 
135         SystemServicesProxy ssp = Recents.getSystemServices();
136         mTransitionHelper = new RecentsTransitionHelper(getContext());
137         mDividerSize = ssp.getDockedDividerSize(context);
138         mTouchHandler = new RecentsViewTouchHandler(this);
139         mFlingAnimationUtils = new FlingAnimationUtils(context, 0.3f);
140 
141         LayoutInflater inflater = LayoutInflater.from(context);
142         if (RecentsDebugFlags.Static.EnableStackActionButton) {
143             float cornerRadius = context.getResources().getDimensionPixelSize(
144                     R.dimen.recents_task_view_rounded_corners_radius);
145             mStackActionButton = (TextView) inflater.inflate(R.layout.recents_stack_action_button,
146                     this, false);
147             mStackActionButton.setOnClickListener(new View.OnClickListener() {
148                 @Override
149                 public void onClick(View v) {
150                     EventBus.getDefault().send(new DismissAllTaskViewsEvent());
151                 }
152             });
153             addView(mStackActionButton);
154             mStackActionButton.setClipToOutline(true);
155             mStackActionButton.setOutlineProvider(new ViewOutlineProvider() {
156                 @Override
157                 public void getOutline(View view, Outline outline) {
158                     outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(), cornerRadius);
159                 }
160             });
161         }
162         mEmptyView = (TextView) inflater.inflate(R.layout.recents_empty, this, false);
163         addView(mEmptyView);
164     }
165 
166     /**
167      * Called from RecentsActivity when it is relaunched.
168      */
onReload(boolean isResumingFromVisible, boolean isTaskStackEmpty)169     public void onReload(boolean isResumingFromVisible, boolean isTaskStackEmpty) {
170         RecentsConfiguration config = Recents.getConfiguration();
171         RecentsActivityLaunchState launchState = config.getLaunchState();
172 
173         if (mTaskStackView == null) {
174             isResumingFromVisible = false;
175             mTaskStackView = new TaskStackView(getContext());
176             mTaskStackView.setSystemInsets(mSystemInsets);
177             addView(mTaskStackView);
178         }
179 
180         // Reset the state
181         mAwaitingFirstLayout = !isResumingFromVisible;
182         mLastTaskLaunchedWasFreeform = false;
183 
184         // Update the stack
185         mTaskStackView.onReload(isResumingFromVisible);
186 
187         if (isResumingFromVisible) {
188             // If we are already visible, then restore the background scrim
189             animateBackgroundScrim(1f, DEFAULT_UPDATE_SCRIM_DURATION);
190         } else {
191             // If we are already occluded by the app, then set the final background scrim alpha now.
192             // Otherwise, defer until the enter animation completes to animate the scrim alpha with
193             // the tasks for the home animation.
194             if (launchState.launchedViaDockGesture || launchState.launchedFromApp
195                     || isTaskStackEmpty) {
196                 mBackgroundScrim.setAlpha(255);
197             } else {
198                 mBackgroundScrim.setAlpha(0);
199             }
200         }
201     }
202 
203     /**
204      * Called from RecentsActivity when the task stack is updated.
205      */
updateStack(TaskStack stack, boolean setStackViewTasks)206     public void updateStack(TaskStack stack, boolean setStackViewTasks) {
207         mStack = stack;
208         if (setStackViewTasks) {
209             mTaskStackView.setTasks(stack, true /* allowNotifyStackChanges */);
210         }
211 
212         // Update the top level view's visibilities
213         if (stack.getTaskCount() > 0) {
214             hideEmptyView();
215         } else {
216             showEmptyView(R.string.recents_empty_message);
217         }
218     }
219 
220     /**
221      * Returns the current TaskStack.
222      */
getStack()223     public TaskStack getStack() {
224         return mStack;
225     }
226 
227     /*
228      * Returns the window background scrim.
229      */
getBackgroundScrim()230     public Drawable getBackgroundScrim() {
231         return mBackgroundScrim;
232     }
233 
234     /**
235      * Returns whether the last task launched was in the freeform stack or not.
236      */
isLastTaskLaunchedFreeform()237     public boolean isLastTaskLaunchedFreeform() {
238         return mLastTaskLaunchedWasFreeform;
239     }
240 
241     /** Launches the focused task from the first stack if possible */
launchFocusedTask(int logEvent)242     public boolean launchFocusedTask(int logEvent) {
243         if (mTaskStackView != null) {
244             Task task = mTaskStackView.getFocusedTask();
245             if (task != null) {
246                 TaskView taskView = mTaskStackView.getChildViewForTask(task);
247                 EventBus.getDefault().send(new LaunchTaskEvent(taskView, task, null,
248                         INVALID_STACK_ID, false));
249 
250                 if (logEvent != 0) {
251                     MetricsLogger.action(getContext(), logEvent,
252                             task.key.getComponent().toString());
253                 }
254                 return true;
255             }
256         }
257         return false;
258     }
259 
260     /** Launches the task that recents was launched from if possible */
launchPreviousTask()261     public boolean launchPreviousTask() {
262         if (mTaskStackView != null) {
263             TaskStack stack = mTaskStackView.getStack();
264             Task task = stack.getLaunchTarget();
265             if (task != null) {
266                 TaskView taskView = mTaskStackView.getChildViewForTask(task);
267                 EventBus.getDefault().send(new LaunchTaskEvent(taskView, task, null,
268                         INVALID_STACK_ID, false));
269                 return true;
270             }
271         }
272         return false;
273     }
274 
275     /** Launches a given task. */
launchTask(Task task, Rect taskBounds, int destinationStack)276     public boolean launchTask(Task task, Rect taskBounds, int destinationStack) {
277         if (mTaskStackView != null) {
278             // Iterate the stack views and try and find the given task.
279             List<TaskView> taskViews = mTaskStackView.getTaskViews();
280             int taskViewCount = taskViews.size();
281             for (int j = 0; j < taskViewCount; j++) {
282                 TaskView tv = taskViews.get(j);
283                 if (tv.getTask() == task) {
284                     EventBus.getDefault().send(new LaunchTaskEvent(tv, task, taskBounds,
285                             destinationStack, false));
286                     return true;
287                 }
288             }
289         }
290         return false;
291     }
292 
293     /**
294      * Hides the task stack and shows the empty view.
295      */
showEmptyView(int msgResId)296     public void showEmptyView(int msgResId) {
297         mTaskStackView.setVisibility(View.INVISIBLE);
298         mEmptyView.setText(msgResId);
299         mEmptyView.setVisibility(View.VISIBLE);
300         mEmptyView.bringToFront();
301         if (RecentsDebugFlags.Static.EnableStackActionButton) {
302             mStackActionButton.bringToFront();
303         }
304     }
305 
306     /**
307      * Shows the task stack and hides the empty view.
308      */
hideEmptyView()309     public void hideEmptyView() {
310         mEmptyView.setVisibility(View.INVISIBLE);
311         mTaskStackView.setVisibility(View.VISIBLE);
312         mTaskStackView.bringToFront();
313         if (RecentsDebugFlags.Static.EnableStackActionButton) {
314             mStackActionButton.bringToFront();
315         }
316     }
317 
318     @Override
onAttachedToWindow()319     protected void onAttachedToWindow() {
320         EventBus.getDefault().register(this, RecentsActivity.EVENT_BUS_PRIORITY + 1);
321         EventBus.getDefault().register(mTouchHandler, RecentsActivity.EVENT_BUS_PRIORITY + 2);
322         super.onAttachedToWindow();
323     }
324 
325     @Override
onDetachedFromWindow()326     protected void onDetachedFromWindow() {
327         super.onDetachedFromWindow();
328         EventBus.getDefault().unregister(this);
329         EventBus.getDefault().unregister(mTouchHandler);
330     }
331 
332     /**
333      * This is called with the full size of the window since we are handling our own insets.
334      */
335     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)336     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
337         int width = MeasureSpec.getSize(widthMeasureSpec);
338         int height = MeasureSpec.getSize(heightMeasureSpec);
339 
340         if (mTaskStackView.getVisibility() != GONE) {
341             mTaskStackView.measure(widthMeasureSpec, heightMeasureSpec);
342         }
343 
344         // Measure the empty view to the full size of the screen
345         if (mEmptyView.getVisibility() != GONE) {
346             measureChild(mEmptyView, MeasureSpec.makeMeasureSpec(width, MeasureSpec.AT_MOST),
347                     MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST));
348         }
349 
350         if (RecentsDebugFlags.Static.EnableStackActionButton) {
351             // Measure the stack action button within the constraints of the space above the stack
352             Rect buttonBounds = mTaskStackView.mLayoutAlgorithm.mStackActionButtonRect;
353             measureChild(mStackActionButton,
354                     MeasureSpec.makeMeasureSpec(buttonBounds.width(), MeasureSpec.AT_MOST),
355                     MeasureSpec.makeMeasureSpec(buttonBounds.height(), MeasureSpec.AT_MOST));
356         }
357 
358         setMeasuredDimension(width, height);
359     }
360 
361     /**
362      * This is called with the full size of the window since we are handling our own insets.
363      */
364     @Override
onLayout(boolean changed, int left, int top, int right, int bottom)365     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
366         if (mTaskStackView.getVisibility() != GONE) {
367             mTaskStackView.layout(left, top, left + getMeasuredWidth(), top + getMeasuredHeight());
368         }
369 
370         // Layout the empty view
371         if (mEmptyView.getVisibility() != GONE) {
372             int leftRightInsets = mSystemInsets.left + mSystemInsets.right;
373             int topBottomInsets = mSystemInsets.top + mSystemInsets.bottom;
374             int childWidth = mEmptyView.getMeasuredWidth();
375             int childHeight = mEmptyView.getMeasuredHeight();
376             int childLeft = left + mSystemInsets.left +
377                     Math.max(0, (right - left - leftRightInsets - childWidth)) / 2;
378             int childTop = top + mSystemInsets.top +
379                     Math.max(0, (bottom - top - topBottomInsets - childHeight)) / 2;
380             mEmptyView.layout(childLeft, childTop, childLeft + childWidth, childTop + childHeight);
381         }
382 
383         if (RecentsDebugFlags.Static.EnableStackActionButton) {
384             // Layout the stack action button such that its drawable is start-aligned with the
385             // stack, vertically centered in the available space above the stack
386             Rect buttonBounds = getStackActionButtonBoundsFromStackLayout();
387             mStackActionButton.layout(buttonBounds.left, buttonBounds.top, buttonBounds.right,
388                     buttonBounds.bottom);
389         }
390 
391         if (mAwaitingFirstLayout) {
392             mAwaitingFirstLayout = false;
393 
394             // If launched via dragging from the nav bar, then we should translate the whole view
395             // down offscreen
396             RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState();
397             if (launchState.launchedViaDragGesture) {
398                 setTranslationY(getMeasuredHeight());
399             } else {
400                 setTranslationY(0f);
401             }
402         }
403     }
404 
405     @Override
onApplyWindowInsets(WindowInsets insets)406     public WindowInsets onApplyWindowInsets(WindowInsets insets) {
407         mSystemInsets.set(insets.getSystemWindowInsets());
408         mTaskStackView.setSystemInsets(mSystemInsets);
409         requestLayout();
410         return insets;
411     }
412 
413     @Override
onInterceptTouchEvent(MotionEvent ev)414     public boolean onInterceptTouchEvent(MotionEvent ev) {
415         return mTouchHandler.onInterceptTouchEvent(ev);
416     }
417 
418     @Override
onTouchEvent(MotionEvent ev)419     public boolean onTouchEvent(MotionEvent ev) {
420         return mTouchHandler.onTouchEvent(ev);
421     }
422 
423     @Override
onDrawForeground(Canvas canvas)424     public void onDrawForeground(Canvas canvas) {
425         super.onDrawForeground(canvas);
426 
427         ArrayList<TaskStack.DockState> visDockStates = mTouchHandler.getVisibleDockStates();
428         for (int i = visDockStates.size() - 1; i >= 0; i--) {
429             visDockStates.get(i).viewState.draw(canvas);
430         }
431     }
432 
433     @Override
verifyDrawable(Drawable who)434     protected boolean verifyDrawable(Drawable who) {
435         ArrayList<TaskStack.DockState> visDockStates = mTouchHandler.getVisibleDockStates();
436         for (int i = visDockStates.size() - 1; i >= 0; i--) {
437             Drawable d = visDockStates.get(i).viewState.dockAreaOverlay;
438             if (d == who) {
439                 return true;
440             }
441         }
442         return super.verifyDrawable(who);
443     }
444 
445     /**** EventBus Events ****/
446 
onBusEvent(LaunchTaskEvent event)447     public final void onBusEvent(LaunchTaskEvent event) {
448         mLastTaskLaunchedWasFreeform = event.task.isFreeformTask();
449         mTransitionHelper.launchTaskFromRecents(mStack, event.task, mTaskStackView, event.taskView,
450                 event.screenPinningRequested, event.targetTaskBounds, event.targetTaskStack);
451     }
452 
onBusEvent(DismissRecentsToHomeAnimationStarted event)453     public final void onBusEvent(DismissRecentsToHomeAnimationStarted event) {
454         int taskViewExitToHomeDuration = TaskStackAnimationHelper.EXIT_TO_HOME_TRANSLATION_DURATION;
455         if (RecentsDebugFlags.Static.EnableStackActionButton) {
456             // Hide the stack action button
457             hideStackActionButton(taskViewExitToHomeDuration, false /* translate */);
458         }
459         animateBackgroundScrim(0f, taskViewExitToHomeDuration);
460     }
461 
onBusEvent(DragStartEvent event)462     public final void onBusEvent(DragStartEvent event) {
463         updateVisibleDockRegions(mTouchHandler.getDockStatesForCurrentOrientation(),
464                 true /* isDefaultDockState */, TaskStack.DockState.NONE.viewState.dockAreaAlpha,
465                 TaskStack.DockState.NONE.viewState.hintTextAlpha,
466                 true /* animateAlpha */, false /* animateBounds */);
467 
468         // Temporarily hide the stack action button without changing visibility
469         if (mStackActionButton != null) {
470             mStackActionButton.animate()
471                     .alpha(0f)
472                     .setDuration(HIDE_STACK_ACTION_BUTTON_DURATION)
473                     .setInterpolator(Interpolators.ALPHA_OUT)
474                     .start();
475         }
476     }
477 
onBusEvent(DragDropTargetChangedEvent event)478     public final void onBusEvent(DragDropTargetChangedEvent event) {
479         if (event.dropTarget == null || !(event.dropTarget instanceof TaskStack.DockState)) {
480             updateVisibleDockRegions(mTouchHandler.getDockStatesForCurrentOrientation(),
481                     true /* isDefaultDockState */, TaskStack.DockState.NONE.viewState.dockAreaAlpha,
482                     TaskStack.DockState.NONE.viewState.hintTextAlpha,
483                     true /* animateAlpha */, true /* animateBounds */);
484         } else {
485             final TaskStack.DockState dockState = (TaskStack.DockState) event.dropTarget;
486             updateVisibleDockRegions(new TaskStack.DockState[] {dockState},
487                     false /* isDefaultDockState */, -1, -1, true /* animateAlpha */,
488                     true /* animateBounds */);
489         }
490         if (mStackActionButton != null) {
491             event.addPostAnimationCallback(new Runnable() {
492                 @Override
493                 public void run() {
494                     // Move the clear all button to its new position
495                     Rect buttonBounds = getStackActionButtonBoundsFromStackLayout();
496                     mStackActionButton.setLeftTopRightBottom(buttonBounds.left, buttonBounds.top,
497                             buttonBounds.right, buttonBounds.bottom);
498                 }
499             });
500         }
501     }
502 
onBusEvent(final DragEndEvent event)503     public final void onBusEvent(final DragEndEvent event) {
504         // Handle the case where we drop onto a dock region
505         if (event.dropTarget instanceof TaskStack.DockState) {
506             final TaskStack.DockState dockState = (TaskStack.DockState) event.dropTarget;
507 
508             // Hide the dock region
509             updateVisibleDockRegions(null, false /* isDefaultDockState */, -1, -1,
510                     false /* animateAlpha */, false /* animateBounds */);
511 
512             // We translated the view but we need to animate it back from the current layout-space
513             // rect to its final layout-space rect
514             Utilities.setViewFrameFromTranslation(event.taskView);
515 
516             // Dock the task and launch it
517             SystemServicesProxy ssp = Recents.getSystemServices();
518             if (ssp.startTaskInDockedMode(event.task.key.id, dockState.createMode)) {
519                 final OnAnimationStartedListener startedListener =
520                         new OnAnimationStartedListener() {
521                     @Override
522                     public void onAnimationStarted() {
523                         EventBus.getDefault().send(new DockedFirstAnimationFrameEvent());
524                         // Remove the task and don't bother relaying out, as all the tasks will be
525                         // relaid out when the stack changes on the multiwindow change event
526                         mTaskStackView.getStack().removeTask(event.task, null,
527                                 true /* fromDockGesture */);
528                     }
529                 };
530 
531                 final Rect taskRect = getTaskRect(event.taskView);
532                 IAppTransitionAnimationSpecsFuture future =
533                         mTransitionHelper.getAppTransitionFuture(
534                                 new AnimationSpecComposer() {
535                                     @Override
536                                     public List<AppTransitionAnimationSpec> composeSpecs() {
537                                         return mTransitionHelper.composeDockAnimationSpec(
538                                                 event.taskView, taskRect);
539                                     }
540                                 });
541                 ssp.overridePendingAppTransitionMultiThumbFuture(future,
542                         mTransitionHelper.wrapStartedListener(startedListener),
543                         true /* scaleUp */);
544 
545                 MetricsLogger.action(mContext, MetricsEvent.ACTION_WINDOW_DOCK_DRAG_DROP,
546                         event.task.getTopComponent().flattenToShortString());
547             } else {
548                 EventBus.getDefault().send(new DragEndCancelledEvent(mStack, event.task,
549                         event.taskView));
550             }
551         } else {
552             // Animate the overlay alpha back to 0
553             updateVisibleDockRegions(null, true /* isDefaultDockState */, -1, -1,
554                     true /* animateAlpha */, false /* animateBounds */);
555         }
556 
557         // Show the stack action button again without changing visibility
558         if (mStackActionButton != null) {
559             mStackActionButton.animate()
560                     .alpha(1f)
561                     .setDuration(SHOW_STACK_ACTION_BUTTON_DURATION)
562                     .setInterpolator(Interpolators.ALPHA_IN)
563                     .start();
564         }
565     }
566 
onBusEvent(final DragEndCancelledEvent event)567     public final void onBusEvent(final DragEndCancelledEvent event) {
568         // Animate the overlay alpha back to 0
569         updateVisibleDockRegions(null, true /* isDefaultDockState */, -1, -1,
570                 true /* animateAlpha */, false /* animateBounds */);
571     }
572 
getTaskRect(TaskView taskView)573     private Rect getTaskRect(TaskView taskView) {
574         int[] location = taskView.getLocationOnScreen();
575         int viewX = location[0];
576         int viewY = location[1];
577         return new Rect(viewX, viewY,
578                 (int) (viewX + taskView.getWidth() * taskView.getScaleX()),
579                 (int) (viewY + taskView.getHeight() * taskView.getScaleY()));
580     }
581 
onBusEvent(DraggingInRecentsEvent event)582     public final void onBusEvent(DraggingInRecentsEvent event) {
583         if (mTaskStackView.getTaskViews().size() > 0) {
584             setTranslationY(event.distanceFromTop - mTaskStackView.getTaskViews().get(0).getY());
585         }
586     }
587 
onBusEvent(DraggingInRecentsEndedEvent event)588     public final void onBusEvent(DraggingInRecentsEndedEvent event) {
589         ViewPropertyAnimator animator = animate();
590         if (event.velocity > mFlingAnimationUtils.getMinVelocityPxPerSecond()) {
591             animator.translationY(getHeight());
592             animator.withEndAction(new Runnable() {
593                 @Override
594                 public void run() {
595                     WindowManagerProxy.getInstance().maximizeDockedStack();
596                 }
597             });
598             mFlingAnimationUtils.apply(animator, getTranslationY(), getHeight(), event.velocity);
599         } else {
600             animator.translationY(0f);
601             animator.setListener(null);
602             mFlingAnimationUtils.apply(animator, getTranslationY(), 0, event.velocity);
603         }
604         animator.start();
605     }
606 
onBusEvent(EnterRecentsWindowAnimationCompletedEvent event)607     public final void onBusEvent(EnterRecentsWindowAnimationCompletedEvent event) {
608         RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState();
609         if (!launchState.launchedViaDockGesture && !launchState.launchedFromApp
610                 && mStack.getTaskCount() > 0) {
611             animateBackgroundScrim(1f,
612                     TaskStackAnimationHelper.ENTER_FROM_HOME_TRANSLATION_DURATION);
613         }
614     }
615 
onBusEvent(AllTaskViewsDismissedEvent event)616     public final void onBusEvent(AllTaskViewsDismissedEvent event) {
617         hideStackActionButton(HIDE_STACK_ACTION_BUTTON_DURATION, true /* translate */);
618     }
619 
onBusEvent(DismissAllTaskViewsEvent event)620     public final void onBusEvent(DismissAllTaskViewsEvent event) {
621         SystemServicesProxy ssp = Recents.getSystemServices();
622         if (!ssp.hasDockedTask()) {
623             // Animate the background away only if we are dismissing Recents to home
624             animateBackgroundScrim(0f, DEFAULT_UPDATE_SCRIM_DURATION);
625         }
626     }
627 
onBusEvent(ShowStackActionButtonEvent event)628     public final void onBusEvent(ShowStackActionButtonEvent event) {
629         if (!RecentsDebugFlags.Static.EnableStackActionButton) {
630             return;
631         }
632 
633         showStackActionButton(SHOW_STACK_ACTION_BUTTON_DURATION, event.translate);
634     }
635 
onBusEvent(HideStackActionButtonEvent event)636     public final void onBusEvent(HideStackActionButtonEvent event) {
637         if (!RecentsDebugFlags.Static.EnableStackActionButton) {
638             return;
639         }
640 
641         hideStackActionButton(HIDE_STACK_ACTION_BUTTON_DURATION, true /* translate */);
642     }
643 
onBusEvent(MultiWindowStateChangedEvent event)644     public final void onBusEvent(MultiWindowStateChangedEvent event) {
645         updateStack(event.stack, false /* setStackViewTasks */);
646     }
647 
648     /**
649      * Shows the stack action button.
650      */
showStackActionButton(final int duration, final boolean translate)651     private void showStackActionButton(final int duration, final boolean translate) {
652         if (!RecentsDebugFlags.Static.EnableStackActionButton) {
653             return;
654         }
655 
656         final ReferenceCountedTrigger postAnimationTrigger = new ReferenceCountedTrigger();
657         if (mStackActionButton.getVisibility() == View.INVISIBLE) {
658             mStackActionButton.setVisibility(View.VISIBLE);
659             mStackActionButton.setAlpha(0f);
660             if (translate) {
661                 mStackActionButton.setTranslationY(-mStackActionButton.getMeasuredHeight() * 0.25f);
662             } else {
663                 mStackActionButton.setTranslationY(0f);
664             }
665             postAnimationTrigger.addLastDecrementRunnable(new Runnable() {
666                 @Override
667                 public void run() {
668                     if (translate) {
669                         mStackActionButton.animate()
670                             .translationY(0f);
671                     }
672                     mStackActionButton.animate()
673                             .alpha(1f)
674                             .setDuration(duration)
675                             .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
676                             .start();
677                 }
678             });
679         }
680         postAnimationTrigger.flushLastDecrementRunnables();
681     }
682 
683     /**
684      * Hides the stack action button.
685      */
hideStackActionButton(int duration, boolean translate)686     private void hideStackActionButton(int duration, boolean translate) {
687         if (!RecentsDebugFlags.Static.EnableStackActionButton) {
688             return;
689         }
690 
691         final ReferenceCountedTrigger postAnimationTrigger = new ReferenceCountedTrigger();
692         hideStackActionButton(duration, translate, postAnimationTrigger);
693         postAnimationTrigger.flushLastDecrementRunnables();
694     }
695 
696     /**
697      * Hides the stack action button.
698      */
hideStackActionButton(int duration, boolean translate, final ReferenceCountedTrigger postAnimationTrigger)699     private void hideStackActionButton(int duration, boolean translate,
700                                        final ReferenceCountedTrigger postAnimationTrigger) {
701         if (!RecentsDebugFlags.Static.EnableStackActionButton) {
702             return;
703         }
704 
705         if (mStackActionButton.getVisibility() == View.VISIBLE) {
706             if (translate) {
707                 mStackActionButton.animate()
708                     .translationY(-mStackActionButton.getMeasuredHeight() * 0.25f);
709             }
710             mStackActionButton.animate()
711                     .alpha(0f)
712                     .setDuration(duration)
713                     .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
714                     .withEndAction(new Runnable() {
715                         @Override
716                         public void run() {
717                             mStackActionButton.setVisibility(View.INVISIBLE);
718                             postAnimationTrigger.decrement();
719                         }
720                     })
721                     .start();
722             postAnimationTrigger.increment();
723         }
724     }
725 
726     /**
727      * Updates the dock region to match the specified dock state.
728      */
updateVisibleDockRegions(TaskStack.DockState[] newDockStates, boolean isDefaultDockState, int overrideAreaAlpha, int overrideHintAlpha, boolean animateAlpha, boolean animateBounds)729     private void updateVisibleDockRegions(TaskStack.DockState[] newDockStates,
730             boolean isDefaultDockState, int overrideAreaAlpha, int overrideHintAlpha,
731             boolean animateAlpha, boolean animateBounds) {
732         ArraySet<TaskStack.DockState> newDockStatesSet = Utilities.arrayToSet(newDockStates,
733                 new ArraySet<TaskStack.DockState>());
734         ArrayList<TaskStack.DockState> visDockStates = mTouchHandler.getVisibleDockStates();
735         for (int i = visDockStates.size() - 1; i >= 0; i--) {
736             TaskStack.DockState dockState = visDockStates.get(i);
737             TaskStack.DockState.ViewState viewState = dockState.viewState;
738             if (newDockStates == null || !newDockStatesSet.contains(dockState)) {
739                 // This is no longer visible, so hide it
740                 viewState.startAnimation(null, 0, 0, TaskStackView.SLOW_SYNC_STACK_DURATION,
741                         Interpolators.FAST_OUT_SLOW_IN, animateAlpha, animateBounds);
742             } else {
743                 // This state is now visible, update the bounds and show it
744                 int areaAlpha = overrideAreaAlpha != -1
745                         ? overrideAreaAlpha
746                         : viewState.dockAreaAlpha;
747                 int hintAlpha = overrideHintAlpha != -1
748                         ? overrideHintAlpha
749                         : viewState.hintTextAlpha;
750                 Rect bounds = isDefaultDockState
751                         ? dockState.getPreDockedBounds(getMeasuredWidth(), getMeasuredHeight())
752                         : dockState.getDockedBounds(getMeasuredWidth(), getMeasuredHeight(),
753                         mDividerSize, mSystemInsets, getResources());
754                 if (viewState.dockAreaOverlay.getCallback() != this) {
755                     viewState.dockAreaOverlay.setCallback(this);
756                     viewState.dockAreaOverlay.setBounds(bounds);
757                 }
758                 viewState.startAnimation(bounds, areaAlpha, hintAlpha,
759                         TaskStackView.SLOW_SYNC_STACK_DURATION, Interpolators.FAST_OUT_SLOW_IN,
760                         animateAlpha, animateBounds);
761             }
762         }
763     }
764 
765     /**
766      * Animates the background scrim to the given {@param alpha}.
767      */
animateBackgroundScrim(float alpha, int duration)768     private void animateBackgroundScrim(float alpha, int duration) {
769         Utilities.cancelAnimationWithoutCallbacks(mBackgroundScrimAnimator);
770         // Calculate the absolute alpha to animate from
771         int fromAlpha = (int) ((mBackgroundScrim.getAlpha() / (DEFAULT_SCRIM_ALPHA * 255)) * 255);
772         int toAlpha = (int) (alpha * 255);
773         mBackgroundScrimAnimator = ObjectAnimator.ofInt(mBackgroundScrim, Utilities.DRAWABLE_ALPHA,
774                 fromAlpha, toAlpha);
775         mBackgroundScrimAnimator.setDuration(duration);
776         mBackgroundScrimAnimator.setInterpolator(toAlpha > fromAlpha
777                 ? Interpolators.ALPHA_IN
778                 : Interpolators.ALPHA_OUT);
779         mBackgroundScrimAnimator.start();
780     }
781 
782     /**
783      * @return the bounds of the stack action button.
784      */
getStackActionButtonBoundsFromStackLayout()785     private Rect getStackActionButtonBoundsFromStackLayout() {
786         Rect actionButtonRect = new Rect(mTaskStackView.mLayoutAlgorithm.mStackActionButtonRect);
787         int left = isLayoutRtl()
788                 ? actionButtonRect.left - mStackActionButton.getPaddingLeft()
789                 : actionButtonRect.right + mStackActionButton.getPaddingRight()
790                         - mStackActionButton.getMeasuredWidth();
791         int top = actionButtonRect.top +
792                 (actionButtonRect.height() - mStackActionButton.getMeasuredHeight()) / 2;
793         actionButtonRect.set(left, top, left + mStackActionButton.getMeasuredWidth(),
794                 top + mStackActionButton.getMeasuredHeight());
795         return actionButtonRect;
796     }
797 
dump(String prefix, PrintWriter writer)798     public void dump(String prefix, PrintWriter writer) {
799         String innerPrefix = prefix + "  ";
800         String id = Integer.toHexString(System.identityHashCode(this));
801 
802         writer.print(prefix); writer.print(TAG);
803         writer.print(" awaitingFirstLayout="); writer.print(mAwaitingFirstLayout ? "Y" : "N");
804         writer.print(" insets="); writer.print(Utilities.dumpRect(mSystemInsets));
805         writer.print(" [0x"); writer.print(id); writer.print("]");
806         writer.println();
807 
808         if (mStack != null) {
809             mStack.dump(innerPrefix, writer);
810         }
811         if (mTaskStackView != null) {
812             mTaskStackView.dump(innerPrefix, writer);
813         }
814     }
815 }
816