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