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