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