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.ActivityTaskManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
20 
21 import static com.android.systemui.statusbar.phone.StatusBar.SYSTEM_DIALOG_REASON_RECENT_APPS;
22 
23 import android.animation.ValueAnimator;
24 import android.animation.ValueAnimator.AnimatorUpdateListener;
25 import android.annotation.Nullable;
26 import android.app.ActivityOptions;
27 import android.content.Context;
28 import android.content.res.ColorStateList;
29 import android.graphics.Canvas;
30 import android.graphics.Color;
31 import android.graphics.Point;
32 import android.graphics.PointF;
33 import android.graphics.Rect;
34 import android.graphics.drawable.ColorDrawable;
35 import android.graphics.drawable.Drawable;
36 import android.os.Handler;
37 import android.util.ArraySet;
38 import android.util.AttributeSet;
39 import android.util.Log;
40 import android.util.MathUtils;
41 import android.view.LayoutInflater;
42 import android.view.MotionEvent;
43 import android.view.View;
44 import android.view.ViewDebug;
45 import android.view.ViewPropertyAnimator;
46 import android.view.Window;
47 import android.view.WindowInsets;
48 import android.widget.FrameLayout;
49 import android.widget.TextView;
50 
51 import com.android.internal.colorextraction.ColorExtractor;
52 import com.android.internal.colorextraction.drawable.ScrimDrawable;
53 import com.android.internal.logging.MetricsLogger;
54 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
55 import com.android.settingslib.Utils;
56 import com.android.systemui.Interpolators;
57 import com.android.systemui.R;
58 import com.android.systemui.recents.LegacyRecentsImpl;
59 import com.android.systemui.recents.RecentsActivity;
60 import com.android.systemui.recents.RecentsActivityLaunchState;
61 import com.android.systemui.recents.RecentsConfiguration;
62 import com.android.systemui.recents.events.EventBus;
63 import com.android.systemui.recents.events.activity.CancelEnterRecentsWindowAnimationEvent;
64 import com.android.systemui.recents.events.activity.DismissRecentsToHomeAnimationStarted;
65 import com.android.systemui.recents.events.activity.DockedFirstAnimationFrameEvent;
66 import com.android.systemui.recents.events.activity.EnterRecentsWindowAnimationCompletedEvent;
67 import com.android.systemui.recents.events.activity.ExitRecentsWindowFirstAnimationFrameEvent;
68 import com.android.systemui.recents.events.activity.HideStackActionButtonEvent;
69 import com.android.systemui.recents.events.activity.LaunchTaskEvent;
70 import com.android.systemui.recents.events.activity.LaunchTaskFailedEvent;
71 import com.android.systemui.recents.events.activity.LaunchTaskStartedEvent;
72 import com.android.systemui.recents.events.activity.LaunchTaskSucceededEvent;
73 import com.android.systemui.recents.events.activity.MultiWindowStateChangedEvent;
74 import com.android.systemui.recents.events.activity.ShowEmptyViewEvent;
75 import com.android.systemui.recents.events.activity.ShowStackActionButtonEvent;
76 import com.android.systemui.recents.events.component.ExpandPipEvent;
77 import com.android.systemui.recents.events.component.ScreenPinningRequestEvent;
78 import com.android.systemui.recents.events.component.SetWaitingForTransitionStartEvent;
79 import com.android.systemui.recents.events.ui.AllTaskViewsDismissedEvent;
80 import com.android.systemui.recents.events.ui.DismissAllTaskViewsEvent;
81 import com.android.systemui.recents.events.ui.DraggingInRecentsEndedEvent;
82 import com.android.systemui.recents.events.ui.DraggingInRecentsEvent;
83 import com.android.systemui.recents.events.ui.dragndrop.DragDropTargetChangedEvent;
84 import com.android.systemui.recents.events.ui.dragndrop.DragEndCancelledEvent;
85 import com.android.systemui.recents.events.ui.dragndrop.DragEndEvent;
86 import com.android.systemui.recents.events.ui.dragndrop.DragStartEvent;
87 import com.android.systemui.recents.misc.ReferenceCountedTrigger;
88 import com.android.systemui.recents.misc.SystemServicesProxy;
89 import com.android.systemui.recents.model.TaskStack;
90 import com.android.systemui.recents.utilities.Utilities;
91 import com.android.systemui.shared.recents.model.Task;
92 import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecCompat;
93 import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecsFuture;
94 import com.android.systemui.shared.recents.view.RecentsTransition;
95 import com.android.systemui.shared.system.ActivityManagerWrapper;
96 import com.android.systemui.shared.system.ActivityOptionsCompat;
97 import com.android.systemui.shared.system.WindowManagerWrapper;
98 import com.android.systemui.stackdivider.WindowManagerProxy;
99 import com.android.systemui.statusbar.FlingAnimationUtils;
100 import com.android.systemui.statusbar.phone.ScrimController;
101 
102 import java.io.PrintWriter;
103 import java.util.ArrayList;
104 import java.util.List;
105 
106 /**
107  * This view is the the top level layout that contains TaskStacks (which are laid out according
108  * to their SpaceNode bounds.
109  */
110 public class RecentsView extends FrameLayout {
111 
112     private static final String TAG = "RecentsView";
113 
114     private static final int DEFAULT_UPDATE_SCRIM_DURATION = 200;
115 
116     private static final int SHOW_STACK_ACTION_BUTTON_DURATION = 134;
117     private static final int HIDE_STACK_ACTION_BUTTON_DURATION = 100;
118 
119     private static final int BUSY_RECENTS_TASK_COUNT = 3;
120 
121     private Handler mHandler;
122     private TaskStackView mTaskStackView;
123     private TextView mStackActionButton;
124     private TextView mEmptyView;
125     private final float mStackButtonShadowRadius;
126     private final PointF mStackButtonShadowDistance;
127     private final int mStackButtonShadowColor;
128 
129     private boolean mAwaitingFirstLayout = true;
130 
131     @ViewDebug.ExportedProperty(category="recents")
132     Rect mSystemInsets = new Rect();
133     private int mDividerSize;
134 
135     private float mBusynessFactor;
136     private ScrimDrawable mBackgroundScrim;
137     private ColorDrawable mMultiWindowBackgroundScrim;
138     private ValueAnimator mBackgroundScrimAnimator;
139     private Point mTmpDisplaySize = new Point();
140 
141     private final AnimatorUpdateListener mUpdateBackgroundScrimAlpha = (animation) -> {
142         int alpha = (Integer) animation.getAnimatedValue();
143         mBackgroundScrim.setAlpha(alpha);
144         mMultiWindowBackgroundScrim.setAlpha(alpha);
145     };
146 
147     private RecentsTransitionComposer mTransitionHelper;
148     @ViewDebug.ExportedProperty(deepExport=true, prefix="touch_")
149     private RecentsViewTouchHandler mTouchHandler;
150     private final FlingAnimationUtils mFlingAnimationUtils;
151 
RecentsView(Context context)152     public RecentsView(Context context) {
153         this(context, null);
154     }
155 
RecentsView(Context context, AttributeSet attrs)156     public RecentsView(Context context, AttributeSet attrs) {
157         this(context, attrs, 0);
158     }
159 
RecentsView(Context context, AttributeSet attrs, int defStyleAttr)160     public RecentsView(Context context, AttributeSet attrs, int defStyleAttr) {
161         this(context, attrs, defStyleAttr, 0);
162     }
163 
RecentsView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)164     public RecentsView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
165         super(context, attrs, defStyleAttr, defStyleRes);
166         setWillNotDraw(false);
167 
168         SystemServicesProxy ssp = LegacyRecentsImpl.getSystemServices();
169         mHandler = new Handler();
170         mTransitionHelper = new RecentsTransitionComposer(getContext());
171         mDividerSize = ssp.getDockedDividerSize(context);
172         mTouchHandler = new RecentsViewTouchHandler(this);
173         mFlingAnimationUtils = new FlingAnimationUtils(context, 0.3f);
174         mBackgroundScrim = new ScrimDrawable();
175         mMultiWindowBackgroundScrim = new ColorDrawable();
176 
177         LayoutInflater inflater = LayoutInflater.from(context);
178         mEmptyView = (TextView) inflater.inflate(R.layout.recents_empty, this, false);
179         addView(mEmptyView);
180 
181         if (mStackActionButton != null) {
182             removeView(mStackActionButton);
183         }
184         mStackActionButton = (TextView) inflater.inflate(LegacyRecentsImpl.getConfiguration()
185                         .isLowRamDevice
186                     ? R.layout.recents_low_ram_stack_action_button
187                     : R.layout.recents_stack_action_button,
188                 this, false);
189 
190         mStackButtonShadowRadius = mStackActionButton.getShadowRadius();
191         mStackButtonShadowDistance = new PointF(mStackActionButton.getShadowDx(),
192                 mStackActionButton.getShadowDy());
193         mStackButtonShadowColor = mStackActionButton.getShadowColor();
194         addView(mStackActionButton);
195 
196         reevaluateStyles();
197     }
198 
reevaluateStyles()199     public void reevaluateStyles() {
200         int textColor = Utils.getColorAttrDefaultColor(mContext, R.attr.wallpaperTextColor);
201         boolean usingDarkText = Color.luminance(textColor) < 0.5f;
202 
203         mEmptyView.setTextColor(textColor);
204         mEmptyView.setCompoundDrawableTintList(new ColorStateList(new int[][]{
205                 {android.R.attr.state_enabled}}, new int[]{textColor}));
206 
207         if (mStackActionButton != null) {
208             mStackActionButton.setTextColor(textColor);
209             // Enable/disable shadow if text color is already dark.
210             if (usingDarkText) {
211                 mStackActionButton.setShadowLayer(0, 0, 0, 0);
212             } else {
213                 mStackActionButton.setShadowLayer(mStackButtonShadowRadius,
214                         mStackButtonShadowDistance.x, mStackButtonShadowDistance.y,
215                         mStackButtonShadowColor);
216             }
217         }
218 
219         // Let's also require dark status and nav bars if the text is dark
220         int systemBarsStyle = usingDarkText ? View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR |
221                 View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR : 0;
222 
223         setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE |
224                 View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN |
225                 View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
226                 systemBarsStyle);
227     }
228 
229     /**
230      * Called from RecentsActivity when it is relaunched.
231      */
232     public void onReload(TaskStack stack, boolean isResumingFromVisible) {
233         final RecentsConfiguration config = LegacyRecentsImpl.getConfiguration();
234         final RecentsActivityLaunchState launchState = config.getLaunchState();
235         final boolean isTaskStackEmpty = stack.getTaskCount() == 0;
236 
237         if (mTaskStackView == null) {
238             isResumingFromVisible = false;
239             mTaskStackView = new TaskStackView(getContext());
240             mTaskStackView.setSystemInsets(mSystemInsets);
241             addView(mTaskStackView);
242         }
243 
244         // Reset the state
245         mAwaitingFirstLayout = !isResumingFromVisible;
246 
247         // Update the stack
248         mTaskStackView.onReload(isResumingFromVisible);
249         updateStack(stack, true /* setStackViewTasks */);
250         updateBusyness();
251 
252         if (isResumingFromVisible) {
253             // If we are already visible, then restore the background scrim
254             animateBackgroundScrim(getOpaqueScrimAlpha(), DEFAULT_UPDATE_SCRIM_DURATION);
255         } else {
256             // If we are already occluded by the app, then set the final background scrim alpha now.
257             // Otherwise, defer until the enter animation completes to animate the scrim alpha with
258             // the tasks for the home animation.
259             if (launchState.launchedViaDockGesture || launchState.launchedFromApp
260                     || isTaskStackEmpty) {
261                 mBackgroundScrim.setAlpha((int) (getOpaqueScrimAlpha() * 255));
262             } else {
263                 mBackgroundScrim.setAlpha(0);
264             }
265             mMultiWindowBackgroundScrim.setAlpha(mBackgroundScrim.getAlpha());
266         }
267     }
268 
269     /**
270      * Called from RecentsActivity when the task stack is updated.
271      */
272     public void updateStack(TaskStack stack, boolean setStackViewTasks) {
273         if (setStackViewTasks) {
274             mTaskStackView.setTasks(stack, true /* allowNotifyStackChanges */);
275         }
276 
277         // Update the top level view's visibilities
278         if (stack.getTaskCount() > 0) {
279             hideEmptyView();
280         } else {
281             showEmptyView(R.string.recents_empty_message);
282         }
283     }
284 
285     /**
286      * Animates the scrim opacity based on how many tasks are visible.
287      * Called from {@link RecentsActivity} when tasks are dismissed.
288      */
289     public void updateScrimOpacity() {
290         if (updateBusyness()) {
291             animateBackgroundScrim(getOpaqueScrimAlpha(), DEFAULT_UPDATE_SCRIM_DURATION);
292         }
293     }
294 
295     /**
296      * Updates the busyness factor.
297      *
298      * @return True if it changed.
299      */
300     private boolean updateBusyness() {
301         final int taskCount = mTaskStackView.getStack().getTaskCount();
302         final float busyness = Math.min(taskCount, BUSY_RECENTS_TASK_COUNT)
303                 / (float) BUSY_RECENTS_TASK_COUNT;
304         if (mBusynessFactor == busyness) {
305             return false;
306         } else {
307             mBusynessFactor = busyness;
308             return true;
309         }
310     }
311 
312     /**
313      * Returns the current TaskStack.
314      */
315     public TaskStack getStack() {
316         return mTaskStackView.getStack();
317     }
318 
319     /**
320      * Returns the window background scrim.
321      */
322     public void updateBackgroundScrim(Window window, boolean isInMultiWindow) {
323         if (isInMultiWindow) {
324             mBackgroundScrim.setCallback(null);
325             window.setBackgroundDrawable(mMultiWindowBackgroundScrim);
326         } else {
327             mMultiWindowBackgroundScrim.setCallback(null);
328             window.setBackgroundDrawable(mBackgroundScrim);
329         }
330     }
331 
332     /** Launches the focused task from the first stack if possible */
333     public boolean launchFocusedTask(int logEvent) {
334         if (mTaskStackView != null) {
335             Task task = mTaskStackView.getFocusedTask();
336             if (task != null) {
337                 TaskView taskView = mTaskStackView.getChildViewForTask(task);
338                 EventBus.getDefault().send(new LaunchTaskEvent(taskView, task, null, false));
339 
340                 if (logEvent != 0) {
341                     MetricsLogger.action(getContext(), logEvent,
342                             task.key.getComponent().toString());
343                 }
344                 return true;
345             }
346         }
347         return false;
348     }
349 
350     /** Launches the task that recents was launched from if possible */
351     public boolean launchPreviousTask() {
352         if (LegacyRecentsImpl.getConfiguration().getLaunchState().launchedFromPipApp) {
353             // If the app auto-entered PiP on the way to Recents, then just re-expand it
354             EventBus.getDefault().send(new ExpandPipEvent());
355             return true;
356         }
357 
358         if (mTaskStackView != null) {
359             Task task = getStack().getLaunchTarget();
360             if (task != null) {
361                 TaskView taskView = mTaskStackView.getChildViewForTask(task);
362                 EventBus.getDefault().send(new LaunchTaskEvent(taskView, task, null, false));
363                 return true;
364             }
365         }
366         return false;
367     }
368 
369     /**
370      * Hides the task stack and shows the empty view.
371      */
372     public void showEmptyView(int msgResId) {
373         mTaskStackView.setVisibility(View.INVISIBLE);
374         mEmptyView.setText(msgResId);
375         mEmptyView.setVisibility(View.VISIBLE);
376         mEmptyView.bringToFront();
377         mStackActionButton.bringToFront();
378     }
379 
380     /**
381      * Shows the task stack and hides the empty view.
382      */
383     public void hideEmptyView() {
384         mEmptyView.setVisibility(View.INVISIBLE);
385         mTaskStackView.setVisibility(View.VISIBLE);
386         mTaskStackView.bringToFront();
387         mStackActionButton.bringToFront();
388     }
389 
390     /**
391      * Set the color of the scrim.
392      *
393      * @param scrimColors Colors to use.
394      * @param animated Interpolate colors if true.
395      */
396     public void setScrimColors(ColorExtractor.GradientColors scrimColors, boolean animated) {
397         mBackgroundScrim.setColor(scrimColors.getMainColor(), animated);
398         int alpha = mMultiWindowBackgroundScrim.getAlpha();
399         mMultiWindowBackgroundScrim.setColor(scrimColors.getMainColor());
400         mMultiWindowBackgroundScrim.setAlpha(alpha);
401     }
402 
403     @Override
404     protected void onAttachedToWindow() {
405         EventBus.getDefault().register(this, RecentsActivity.EVENT_BUS_PRIORITY + 1);
406         EventBus.getDefault().register(mTouchHandler, RecentsActivity.EVENT_BUS_PRIORITY + 2);
407         super.onAttachedToWindow();
408     }
409 
410     @Override
411     protected void onDetachedFromWindow() {
412         super.onDetachedFromWindow();
413         EventBus.getDefault().unregister(this);
414         EventBus.getDefault().unregister(mTouchHandler);
415     }
416 
417     /**
418      * This is called with the full size of the window since we are handling our own insets.
419      */
420     @Override
421     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
422         int width = MeasureSpec.getSize(widthMeasureSpec);
423         int height = MeasureSpec.getSize(heightMeasureSpec);
424 
425         if (mTaskStackView.getVisibility() != GONE) {
426             mTaskStackView.measure(widthMeasureSpec, heightMeasureSpec);
427         }
428 
429         // Measure the empty view to the full size of the screen
430         if (mEmptyView.getVisibility() != GONE) {
431             measureChild(mEmptyView, MeasureSpec.makeMeasureSpec(width, MeasureSpec.AT_MOST),
432                     MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST));
433         }
434 
435         // Measure the stack action button within the constraints of the space above the stack
436         Rect buttonBounds = mTaskStackView.mLayoutAlgorithm.getStackActionButtonRect();
437         measureChild(mStackActionButton,
438                 MeasureSpec.makeMeasureSpec(buttonBounds.width(), MeasureSpec.AT_MOST),
439                 MeasureSpec.makeMeasureSpec(buttonBounds.height(), MeasureSpec.AT_MOST));
440 
441         setMeasuredDimension(width, height);
442     }
443 
444     /**
445      * This is called with the full size of the window since we are handling our own insets.
446      */
447     @Override
448     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
449         if (mTaskStackView.getVisibility() != GONE) {
450             mTaskStackView.layout(left, top, left + getMeasuredWidth(), top + getMeasuredHeight());
451         }
452 
453         // Layout the empty view
454         if (mEmptyView.getVisibility() != GONE) {
455             int leftRightInsets = mSystemInsets.left + mSystemInsets.right;
456             int topBottomInsets = mSystemInsets.top + mSystemInsets.bottom;
457             int childWidth = mEmptyView.getMeasuredWidth();
458             int childHeight = mEmptyView.getMeasuredHeight();
459             int childLeft = left + mSystemInsets.left +
460                     Math.max(0, (right - left - leftRightInsets - childWidth)) / 2;
461             int childTop = top + mSystemInsets.top +
462                     Math.max(0, (bottom - top - topBottomInsets - childHeight)) / 2;
463             mEmptyView.layout(childLeft, childTop, childLeft + childWidth, childTop + childHeight);
464         }
465 
466         // Needs to know the screen size since the gradient never scales up or down
467         // even when bounds change.
468         mContext.getDisplay().getRealSize(mTmpDisplaySize);
469         mBackgroundScrim.setBounds(left, top, right, bottom);
470         mMultiWindowBackgroundScrim.setBounds(0, 0, mTmpDisplaySize.x, mTmpDisplaySize.y);
471 
472         // Layout the stack action button such that its drawable is start-aligned with the
473         // stack, vertically centered in the available space above the stack
474         Rect buttonBounds = getStackActionButtonBoundsFromStackLayout();
475         mStackActionButton.layout(buttonBounds.left, buttonBounds.top, buttonBounds.right,
476                 buttonBounds.bottom);
477 
478         if (mAwaitingFirstLayout) {
479             mAwaitingFirstLayout = false;
480             // If launched via dragging from the nav bar, then we should translate the whole view
481             // down offscreen
482             RecentsActivityLaunchState launchState = LegacyRecentsImpl.getConfiguration().getLaunchState();
483             if (launchState.launchedViaDragGesture) {
484                 setTranslationY(getMeasuredHeight());
485             } else {
486                 setTranslationY(0f);
487             }
488 
489             if (LegacyRecentsImpl.getConfiguration().isLowRamDevice
490                     && mEmptyView.getVisibility() == View.VISIBLE) {
491                 animateEmptyView(true /* show */, null /* postAnimationTrigger */);
492             }
493         }
494     }
495 
496     @Override
497     public WindowInsets onApplyWindowInsets(WindowInsets insets) {
498         mSystemInsets.set(insets.getSystemWindowInsetsAsRect());
499         mTaskStackView.setSystemInsets(mSystemInsets);
500         requestLayout();
501         return insets;
502     }
503 
504     @Override
505     public boolean onInterceptTouchEvent(MotionEvent ev) {
506         return mTouchHandler.onInterceptTouchEvent(ev);
507     }
508 
509     @Override
510     public boolean onTouchEvent(MotionEvent ev) {
511         return mTouchHandler.onTouchEvent(ev);
512     }
513 
514     @Override
515     public void onDrawForeground(Canvas canvas) {
516         super.onDrawForeground(canvas);
517 
518         ArrayList<DockState> visDockStates = mTouchHandler.getVisibleDockStates();
519         for (int i = visDockStates.size() - 1; i >= 0; i--) {
520             visDockStates.get(i).viewState.draw(canvas);
521         }
522     }
523 
524     @Override
525     protected boolean verifyDrawable(Drawable who) {
526         ArrayList<DockState> visDockStates = mTouchHandler.getVisibleDockStates();
527         for (int i = visDockStates.size() - 1; i >= 0; i--) {
528             Drawable d = visDockStates.get(i).viewState.dockAreaOverlay;
529             if (d == who) {
530                 return true;
531             }
532         }
533         return super.verifyDrawable(who);
534     }
535 
536     /**** EventBus Events ****/
537 
538     public final void onBusEvent(LaunchTaskEvent event) {
539         launchTaskFromRecents(getStack(), event.task, mTaskStackView, event.taskView,
540                 event.screenPinningRequested, event.targetWindowingMode, event.targetActivityType);
541         if (LegacyRecentsImpl.getConfiguration().isLowRamDevice) {
542             EventBus.getDefault().send(new HideStackActionButtonEvent(false /* translate */));
543         }
544     }
545 
546     public final void onBusEvent(DismissRecentsToHomeAnimationStarted event) {
547         int taskViewExitToHomeDuration = TaskStackAnimationHelper.EXIT_TO_HOME_TRANSLATION_DURATION;
548         // Hide the stack action button
549         EventBus.getDefault().send(new HideStackActionButtonEvent());
550         animateBackgroundScrim(0f, taskViewExitToHomeDuration);
551 
552         if (LegacyRecentsImpl.getConfiguration().isLowRamDevice) {
553             animateEmptyView(false /* show */, event.getAnimationTrigger());
554         }
555     }
556 
557     public final void onBusEvent(DragStartEvent event) {
558         updateVisibleDockRegions(LegacyRecentsImpl.getConfiguration().getDockStatesForCurrentOrientation(),
559                 true /* isDefaultDockState */, DockState.NONE.viewState.dockAreaAlpha,
560                 DockState.NONE.viewState.hintTextAlpha,
561                 true /* animateAlpha */, false /* animateBounds */);
562 
563         // Temporarily hide the stack action button without changing visibility
564         if (mStackActionButton != null) {
565             mStackActionButton.animate()
566                     .alpha(0f)
567                     .setDuration(HIDE_STACK_ACTION_BUTTON_DURATION)
568                     .setInterpolator(Interpolators.ALPHA_OUT)
569                     .start();
570         }
571     }
572 
573     public final void onBusEvent(DragDropTargetChangedEvent event) {
574         if (event.dropTarget == null || !(event.dropTarget instanceof DockState)) {
575             updateVisibleDockRegions(
576                     LegacyRecentsImpl.getConfiguration().getDockStatesForCurrentOrientation(),
577                     true /* isDefaultDockState */, DockState.NONE.viewState.dockAreaAlpha,
578                     DockState.NONE.viewState.hintTextAlpha,
579                     true /* animateAlpha */, true /* animateBounds */);
580         } else {
581             final DockState dockState = (DockState) event.dropTarget;
582             updateVisibleDockRegions(new DockState[] {dockState},
583                     false /* isDefaultDockState */, -1, -1, true /* animateAlpha */,
584                     true /* animateBounds */);
585         }
586         if (mStackActionButton != null) {
587             event.addPostAnimationCallback(new Runnable() {
588                 @Override
589                 public void run() {
590                     // Move the clear all button to its new position
591                     Rect buttonBounds = getStackActionButtonBoundsFromStackLayout();
592                     mStackActionButton.setLeftTopRightBottom(buttonBounds.left, buttonBounds.top,
593                             buttonBounds.right, buttonBounds.bottom);
594                 }
595             });
596         }
597     }
598 
599     public final void onBusEvent(final DragEndEvent event) {
600         // Handle the case where we drop onto a dock region
601         if (event.dropTarget instanceof DockState) {
602             final DockState dockState = (DockState) event.dropTarget;
603 
604             // Hide the dock region
605             updateVisibleDockRegions(null, false /* isDefaultDockState */, -1, -1,
606                     false /* animateAlpha */, false /* animateBounds */);
607 
608             // We translated the view but we need to animate it back from the current layout-space
609             // rect to its final layout-space rect
610             Utilities.setViewFrameFromTranslation(event.taskView);
611 
612             final ActivityOptions options = ActivityOptionsCompat.makeSplitScreenOptions(
613                     dockState.createMode == SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT);
614             if (ActivityManagerWrapper.getInstance().startActivityFromRecents(event.task.key.id,
615                     options)) {
616                 final Runnable animStartedListener = () -> {
617                     EventBus.getDefault().send(new DockedFirstAnimationFrameEvent());
618                     // Remove the task and don't bother relaying out, as all the tasks
619                     // will be relaid out when the stack changes on the multiwindow
620                     // change event
621                     getStack().removeTask(event.task, null, true /* fromDockGesture */);
622                 };
623                 final Rect taskRect = getTaskRect(event.taskView);
624                 AppTransitionAnimationSpecsFuture future = new AppTransitionAnimationSpecsFuture(
625                         getHandler()) {
626                     @Override
627                     public List<AppTransitionAnimationSpecCompat> composeSpecs() {
628                         return mTransitionHelper.composeDockAnimationSpec(event.taskView, taskRect);
629                     }
630                 };
631                 WindowManagerWrapper.getInstance().overridePendingAppTransitionMultiThumbFuture(
632                         future, animStartedListener, getHandler(), true /* scaleUp */,
633                         getContext().getDisplayId());
634                 MetricsLogger.action(mContext, MetricsEvent.ACTION_WINDOW_DOCK_DRAG_DROP,
635                         event.task.getTopComponent().flattenToShortString());
636             } else {
637                 EventBus.getDefault().send(new DragEndCancelledEvent(getStack(), event.task,
638                         event.taskView));
639             }
640         } else {
641             // Animate the overlay alpha back to 0
642             updateVisibleDockRegions(null, true /* isDefaultDockState */, -1, -1,
643                     true /* animateAlpha */, false /* animateBounds */);
644         }
645 
646         // Show the stack action button again without changing visibility
647         if (mStackActionButton != null) {
648             mStackActionButton.animate()
649                     .alpha(1f)
650                     .setDuration(SHOW_STACK_ACTION_BUTTON_DURATION)
651                     .setInterpolator(Interpolators.ALPHA_IN)
652                     .start();
653         }
654     }
655 
656     public final void onBusEvent(final DragEndCancelledEvent event) {
657         // Animate the overlay alpha back to 0
658         updateVisibleDockRegions(null, true /* isDefaultDockState */, -1, -1,
659                 true /* animateAlpha */, false /* animateBounds */);
660     }
661 
662     private Rect getTaskRect(TaskView taskView) {
663         int[] location = taskView.getLocationOnScreen();
664         int viewX = location[0];
665         int viewY = location[1];
666         return new Rect(viewX, viewY,
667                 (int) (viewX + taskView.getWidth() * taskView.getScaleX()),
668                 (int) (viewY + taskView.getHeight() * taskView.getScaleY()));
669     }
670 
671     public final void onBusEvent(DraggingInRecentsEvent event) {
672         if (mTaskStackView.getTaskViews().size() > 0) {
673             setTranslationY(event.distanceFromTop - mTaskStackView.getTaskViews().get(0).getY());
674         }
675     }
676 
677     public final void onBusEvent(DraggingInRecentsEndedEvent event) {
678         ViewPropertyAnimator animator = animate();
679         if (event.velocity > mFlingAnimationUtils.getMinVelocityPxPerSecond()) {
680             animator.translationY(getHeight());
681             animator.withEndAction(new Runnable() {
682                 @Override
683                 public void run() {
684                     WindowManagerProxy.getInstance().maximizeDockedStack();
685                 }
686             });
687             mFlingAnimationUtils.apply(animator, getTranslationY(), getHeight(), event.velocity);
688         } else {
689             animator.translationY(0f);
690             animator.setListener(null);
691             mFlingAnimationUtils.apply(animator, getTranslationY(), 0, event.velocity);
692         }
693         animator.start();
694     }
695 
696     public final void onBusEvent(EnterRecentsWindowAnimationCompletedEvent event) {
697         RecentsActivityLaunchState launchState = LegacyRecentsImpl.getConfiguration().getLaunchState();
698         if (!launchState.launchedViaDockGesture && !launchState.launchedFromApp
699                 && getStack().getTaskCount() > 0) {
700             animateBackgroundScrim(getOpaqueScrimAlpha(),
701                     TaskStackAnimationHelper.ENTER_FROM_HOME_TRANSLATION_DURATION);
702         }
703     }
704 
705     public final void onBusEvent(AllTaskViewsDismissedEvent event) {
706         EventBus.getDefault().send(new HideStackActionButtonEvent());
707     }
708 
709     public final void onBusEvent(DismissAllTaskViewsEvent event) {
710         SystemServicesProxy ssp = LegacyRecentsImpl.getSystemServices();
711         if (!ssp.hasDockedTask()) {
712             // Animate the background away only if we are dismissing Recents to home
713             animateBackgroundScrim(0f, DEFAULT_UPDATE_SCRIM_DURATION);
714         }
715     }
716 
717     public final void onBusEvent(ShowStackActionButtonEvent event) {
718         showStackActionButton(SHOW_STACK_ACTION_BUTTON_DURATION, event.translate);
719     }
720 
721     public final void onBusEvent(HideStackActionButtonEvent event) {
722         hideStackActionButton(HIDE_STACK_ACTION_BUTTON_DURATION, true /* translate */);
723     }
724 
725     public final void onBusEvent(MultiWindowStateChangedEvent event) {
726         updateStack(event.stack, false /* setStackViewTasks */);
727     }
728 
729     public final void onBusEvent(ShowEmptyViewEvent event) {
730         showEmptyView(R.string.recents_empty_message);
731     }
732 
733     /**
734      * Shows the stack action button.
735      */
736     private void showStackActionButton(final int duration, final boolean translate) {
737         final ReferenceCountedTrigger postAnimationTrigger = new ReferenceCountedTrigger();
738         if (mStackActionButton.getVisibility() == View.INVISIBLE) {
739             mStackActionButton.setVisibility(View.VISIBLE);
740             mStackActionButton.setAlpha(0f);
741             if (translate) {
742                 mStackActionButton.setTranslationY(mStackActionButton.getMeasuredHeight() *
743                         (LegacyRecentsImpl.getConfiguration().isLowRamDevice ? 1 : -0.25f));
744             } else {
745                 mStackActionButton.setTranslationY(0f);
746             }
747             postAnimationTrigger.addLastDecrementRunnable(new Runnable() {
748                 @Override
749                 public void run() {
750                     if (translate) {
751                         mStackActionButton.animate()
752                             .translationY(0f);
753                     }
754                     mStackActionButton.animate()
755                             .alpha(1f)
756                             .setDuration(duration)
757                             .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
758                             .start();
759                 }
760             });
761         }
762         postAnimationTrigger.flushLastDecrementRunnables();
763     }
764 
765     /**
766      * Hides the stack action button.
767      */
768     private void hideStackActionButton(int duration, boolean translate) {
769         final ReferenceCountedTrigger postAnimationTrigger = new ReferenceCountedTrigger();
770         hideStackActionButton(duration, translate, postAnimationTrigger);
771         postAnimationTrigger.flushLastDecrementRunnables();
772     }
773 
774     /**
775      * Hides the stack action button.
776      */
777     private void hideStackActionButton(int duration, boolean translate,
778                                        final ReferenceCountedTrigger postAnimationTrigger) {
779         if (mStackActionButton.getVisibility() == View.VISIBLE) {
780             if (translate) {
781                 mStackActionButton.animate().translationY(mStackActionButton.getMeasuredHeight()
782                         * (LegacyRecentsImpl.getConfiguration().isLowRamDevice ? 1 : -0.25f));
783             }
784             mStackActionButton.animate()
785                     .alpha(0f)
786                     .setDuration(duration)
787                     .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
788                     .withEndAction(new Runnable() {
789                         @Override
790                         public void run() {
791                             mStackActionButton.setVisibility(View.INVISIBLE);
792                             postAnimationTrigger.decrement();
793                         }
794                     })
795                     .start();
796             postAnimationTrigger.increment();
797         }
798     }
799 
800     /**
801      * Animates a translation in the Y direction and fades in/out for empty view to show or hide it.
802      * @param show whether to translate up and fade in the empty view to the center of the screen
803      * @param postAnimationTrigger to keep track of the animation
804      */
805     private void animateEmptyView(boolean show, ReferenceCountedTrigger postAnimationTrigger) {
806         float start = mTaskStackView.getStackAlgorithm().getTaskRect().height() / 4;
807         mEmptyView.setTranslationY(show ? start : 0);
808         mEmptyView.setAlpha(show ? 0f : 1f);
809         ViewPropertyAnimator animator = mEmptyView.animate()
810                 .setDuration(150)
811                 .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
812                 .translationY(show ? 0 : start)
813                 .alpha(show ? 1f : 0f);
814 
815         if (postAnimationTrigger != null) {
816             animator.setListener(postAnimationTrigger.decrementOnAnimationEnd());
817             postAnimationTrigger.increment();
818         }
819         animator.start();
820     }
821 
822     /**
823      * Updates the dock region to match the specified dock state.
824      */
825     private void updateVisibleDockRegions(DockState[] newDockStates,
826             boolean isDefaultDockState, int overrideAreaAlpha, int overrideHintAlpha,
827             boolean animateAlpha, boolean animateBounds) {
828         ArraySet<DockState> newDockStatesSet = Utilities.arrayToSet(newDockStates,
829                 new ArraySet<DockState>());
830         ArrayList<DockState> visDockStates = mTouchHandler.getVisibleDockStates();
831         for (int i = visDockStates.size() - 1; i >= 0; i--) {
832             DockState dockState = visDockStates.get(i);
833             DockState.ViewState viewState = dockState.viewState;
834             if (newDockStates == null || !newDockStatesSet.contains(dockState)) {
835                 // This is no longer visible, so hide it
836                 viewState.startAnimation(null, 0, 0, TaskStackView.SLOW_SYNC_STACK_DURATION,
837                         Interpolators.FAST_OUT_SLOW_IN, animateAlpha, animateBounds);
838             } else {
839                 // This state is now visible, update the bounds and show it
840                 int areaAlpha = overrideAreaAlpha != -1
841                         ? overrideAreaAlpha
842                         : viewState.dockAreaAlpha;
843                 int hintAlpha = overrideHintAlpha != -1
844                         ? overrideHintAlpha
845                         : viewState.hintTextAlpha;
846                 Rect bounds = isDefaultDockState
847                         ? dockState.getPreDockedBounds(getMeasuredWidth(), getMeasuredHeight(),
848                                 mSystemInsets)
849                         : dockState.getDockedBounds(getMeasuredWidth(), getMeasuredHeight(),
850                                 mDividerSize, mSystemInsets, getResources());
851                 if (viewState.dockAreaOverlay.getCallback() != this) {
852                     viewState.dockAreaOverlay.setCallback(this);
853                     viewState.dockAreaOverlay.setBounds(bounds);
854                 }
855                 viewState.startAnimation(bounds, areaAlpha, hintAlpha,
856                         TaskStackView.SLOW_SYNC_STACK_DURATION, Interpolators.FAST_OUT_SLOW_IN,
857                         animateAlpha, animateBounds);
858             }
859         }
860     }
861 
862     /**
863      * Scrim alpha based on how busy recents is:
864      * Scrim will be {@link ScrimController#GRADIENT_SCRIM_ALPHA} when the stack is empty,
865      * and {@link ScrimController#GRADIENT_SCRIM_ALPHA_BUSY} when it's full.
866      *
867      * @return Alpha from 0 to 1.
868      */
869     private float getOpaqueScrimAlpha() {
870         return MathUtils.map(0, 1, ScrimController.GRADIENT_SCRIM_ALPHA,
871                 ScrimController.GRADIENT_SCRIM_ALPHA_BUSY, mBusynessFactor);
872     }
873 
874     /**
875      * Animates the background scrim to the given {@param alpha}.
876      */
877     private void animateBackgroundScrim(float alpha, int duration) {
878         Utilities.cancelAnimationWithoutCallbacks(mBackgroundScrimAnimator);
879         // Calculate the absolute alpha to animate from
880         final int fromAlpha = mBackgroundScrim.getAlpha();
881         final int toAlpha = (int) (alpha * 255);
882         mBackgroundScrimAnimator = ValueAnimator.ofInt(fromAlpha, toAlpha);
883         mBackgroundScrimAnimator.setDuration(duration);
884         mBackgroundScrimAnimator.setInterpolator(toAlpha > fromAlpha
885                 ? Interpolators.ALPHA_IN
886                 : Interpolators.ALPHA_OUT);
887         mBackgroundScrimAnimator.addUpdateListener(mUpdateBackgroundScrimAlpha);
888         mBackgroundScrimAnimator.start();
889     }
890 
891     /**
892      * @return the bounds of the stack action button.
893      */
894     Rect getStackActionButtonBoundsFromStackLayout() {
895         Rect actionButtonRect = new Rect(
896                 mTaskStackView.mLayoutAlgorithm.getStackActionButtonRect());
897         int left, top;
898         if (LegacyRecentsImpl.getConfiguration().isLowRamDevice) {
899             Rect windowRect = LegacyRecentsImpl.getSystemServices().getWindowRect();
900             int spaceLeft = windowRect.width() - mSystemInsets.left - mSystemInsets.right;
901             left = (spaceLeft - mStackActionButton.getMeasuredWidth()) / 2 + mSystemInsets.left;
902             top = windowRect.height() - (mStackActionButton.getMeasuredHeight()
903                     + mSystemInsets.bottom + mStackActionButton.getPaddingBottom() / 2);
904         } else {
905             left = isLayoutRtl()
906                 ? actionButtonRect.left - mStackActionButton.getPaddingLeft()
907                 : actionButtonRect.right + mStackActionButton.getPaddingRight()
908                         - mStackActionButton.getMeasuredWidth();
909             top = actionButtonRect.top +
910                 (actionButtonRect.height() - mStackActionButton.getMeasuredHeight()) / 2;
911         }
912         actionButtonRect.set(left, top, left + mStackActionButton.getMeasuredWidth(),
913                 top + mStackActionButton.getMeasuredHeight());
914         return actionButtonRect;
915     }
916 
917     View getStackActionButton() {
918         return mStackActionButton;
919     }
920 
921     /**
922      * Launches the specified {@link Task}.
923      */
924     public void launchTaskFromRecents(final TaskStack stack, @Nullable final Task task,
925             final TaskStackView stackView, final TaskView taskView,
926             final boolean screenPinningRequested, final int windowingMode, final int activityType) {
927 
928         final Runnable animStartedListener;
929         final AppTransitionAnimationSpecsFuture transitionFuture;
930         if (taskView != null) {
931 
932             // Fetch window rect here already in order not to be blocked on lock contention in WM
933             // when the future calls it.
934             final Rect windowRect = LegacyRecentsImpl.getSystemServices().getWindowRect();
935             transitionFuture = new AppTransitionAnimationSpecsFuture(stackView.getHandler()) {
936                 @Override
937                 public List<AppTransitionAnimationSpecCompat> composeSpecs() {
938                     return mTransitionHelper.composeAnimationSpecs(task, stackView, windowingMode,
939                             activityType, windowRect);
940                 }
941             };
942             animStartedListener = new Runnable() {
943                 private boolean mHandled;
944 
945                 @Override
946                 public void run() {
947                     if (mHandled) {
948                         return;
949                     }
950                     mHandled = true;
951 
952                     // If we are launching into another task, cancel the previous task's
953                     // window transition
954                     EventBus.getDefault().send(new CancelEnterRecentsWindowAnimationEvent(task));
955                     EventBus.getDefault().send(new ExitRecentsWindowFirstAnimationFrameEvent());
956                     stackView.cancelAllTaskViewAnimations();
957 
958                     if (screenPinningRequested) {
959                         // Request screen pinning after the animation runs
960                         mHandler.postDelayed(() -> {
961                             EventBus.getDefault().send(new ScreenPinningRequestEvent(mContext,
962                                     task.key.id));
963                         }, 350);
964                     }
965 
966                     if (!LegacyRecentsImpl.getConfiguration().isLowRamDevice) {
967                         // Reset the state where we are waiting for the transition to start
968                         EventBus.getDefault().send(new SetWaitingForTransitionStartEvent(false));
969                     }
970                 }
971             };
972         } else {
973             // This is only the case if the task is not on screen (scrolled offscreen for example)
974             transitionFuture = null;
975             animStartedListener = new Runnable() {
976                 private boolean mHandled;
977 
978                 @Override
979                 public void run() {
980                     if (mHandled) {
981                         return;
982                     }
983                     mHandled = true;
984 
985                     // If we are launching into another task, cancel the previous task's
986                     // window transition
987                     EventBus.getDefault().send(new CancelEnterRecentsWindowAnimationEvent(task));
988                     EventBus.getDefault().send(new ExitRecentsWindowFirstAnimationFrameEvent());
989                     stackView.cancelAllTaskViewAnimations();
990 
991                     if (!LegacyRecentsImpl.getConfiguration().isLowRamDevice) {
992                         // Reset the state where we are waiting for the transition to start
993                         EventBus.getDefault().send(new SetWaitingForTransitionStartEvent(false));
994                     }
995                 }
996             };
997         }
998 
999         EventBus.getDefault().send(new SetWaitingForTransitionStartEvent(true));
1000         final ActivityOptions opts = RecentsTransition.createAspectScaleAnimation(mContext,
1001                 mHandler, true /* scaleUp */, transitionFuture != null ? transitionFuture : null,
1002                 animStartedListener);
1003         if (taskView == null) {
1004             // If there is no task view, then we do not need to worry about animating out occluding
1005             // task views, and we can launch immediately
1006             startTaskActivity(stack, task, taskView, opts, transitionFuture,
1007                     windowingMode, activityType);
1008         } else {
1009             LaunchTaskStartedEvent launchStartedEvent = new LaunchTaskStartedEvent(taskView,
1010                     screenPinningRequested);
1011             EventBus.getDefault().send(launchStartedEvent);
1012             startTaskActivity(stack, task, taskView, opts, transitionFuture, windowingMode,
1013                     activityType);
1014         }
1015         ActivityManagerWrapper.getInstance().closeSystemWindows(SYSTEM_DIALOG_REASON_RECENT_APPS);
1016     }
1017 
1018     /**
1019      * Starts the activity for the launch task.
1020      *
1021      * @param taskView this is the {@link TaskView} that we are launching from. This can be null if
1022      *                 we are toggling recents and the launch-to task is now offscreen.
1023      */
1024     private void startTaskActivity(TaskStack stack, Task task, @Nullable TaskView taskView,
1025             ActivityOptions opts, AppTransitionAnimationSpecsFuture transitionFuture,
1026             int windowingMode, int activityType) {
1027         ActivityManagerWrapper.getInstance().startActivityFromRecentsAsync(task.key, opts,
1028                 windowingMode, activityType, succeeded -> {
1029             if (succeeded) {
1030                 // Keep track of the index of the task launch
1031                 int taskIndexFromFront = 0;
1032                 int taskIndex = stack.indexOfTask(task);
1033                 if (taskIndex > -1) {
1034                     taskIndexFromFront = stack.getTaskCount() - taskIndex - 1;
1035                 }
1036                 EventBus.getDefault().send(new LaunchTaskSucceededEvent(taskIndexFromFront));
1037             } else {
1038                 Log.e(TAG, mContext.getString(R.string.recents_launch_error_message, task.title));
1039 
1040                 // Dismiss the task if we fail to launch it
1041                 if (taskView != null) {
1042                     taskView.dismissTask();
1043                 }
1044 
1045                 // Keep track of failed launches
1046                 EventBus.getDefault().send(new LaunchTaskFailedEvent());
1047             }
1048         }, getHandler());
1049         if (transitionFuture != null) {
1050             mHandler.post(transitionFuture::composeSpecsSynchronous);
1051         }
1052     }
1053 
1054     @Override
1055     public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
1056         super.requestDisallowInterceptTouchEvent(disallowIntercept);
1057         mTouchHandler.cancelStackActionButtonClick();
1058     }
1059 
1060     public void dump(String prefix, PrintWriter writer) {
1061         String innerPrefix = prefix + "  ";
1062         String id = Integer.toHexString(System.identityHashCode(this));
1063 
1064         writer.print(prefix); writer.print(TAG);
1065         writer.print(" awaitingFirstLayout="); writer.print(mAwaitingFirstLayout ? "Y" : "N");
1066         writer.print(" insets="); writer.print(Utilities.dumpRect(mSystemInsets));
1067         writer.print(" [0x"); writer.print(id); writer.print("]");
1068         writer.println();
1069 
1070         if (getStack() != null) {
1071             getStack().dump(innerPrefix, writer);
1072         }
1073         if (mTaskStackView != null) {
1074             mTaskStackView.dump(innerPrefix, writer);
1075         }
1076     }
1077 }
1078