Home | History | Annotate | Download | only in stackdivider
      1 /*
      2  * Copyright (C) 2015 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.stackdivider;
     18 
     19 import static android.view.PointerIcon.TYPE_HORIZONTAL_DOUBLE_ARROW;
     20 import static android.view.PointerIcon.TYPE_VERTICAL_DOUBLE_ARROW;
     21 
     22 import android.animation.Animator;
     23 import android.animation.AnimatorListenerAdapter;
     24 import android.animation.ValueAnimator;
     25 import android.annotation.Nullable;
     26 import android.app.ActivityManager.StackId;
     27 import android.content.Context;
     28 import android.content.res.Configuration;
     29 import android.graphics.Rect;
     30 import android.graphics.Region.Op;
     31 import android.hardware.display.DisplayManager;
     32 import android.os.Bundle;
     33 import android.os.Handler;
     34 import android.util.AttributeSet;
     35 import android.view.Display;
     36 import android.view.DisplayInfo;
     37 import android.view.GestureDetector;
     38 import android.view.GestureDetector.SimpleOnGestureListener;
     39 import android.view.MotionEvent;
     40 import android.view.PointerIcon;
     41 import android.view.VelocityTracker;
     42 import android.view.View;
     43 import android.view.View.OnTouchListener;
     44 import android.view.ViewConfiguration;
     45 import android.view.ViewTreeObserver.InternalInsetsInfo;
     46 import android.view.ViewTreeObserver.OnComputeInternalInsetsListener;
     47 import android.view.WindowInsets;
     48 import android.view.WindowManager;
     49 import android.view.accessibility.AccessibilityNodeInfo;
     50 import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
     51 import android.view.animation.Interpolator;
     52 import android.view.animation.PathInterpolator;
     53 import android.widget.FrameLayout;
     54 
     55 import com.android.internal.logging.MetricsLogger;
     56 import com.android.internal.logging.MetricsProto.MetricsEvent;
     57 import com.android.internal.policy.DividerSnapAlgorithm;
     58 import com.android.internal.policy.DividerSnapAlgorithm.SnapTarget;
     59 import com.android.internal.policy.DockedDividerUtils;
     60 import com.android.systemui.Interpolators;
     61 import com.android.systemui.R;
     62 import com.android.systemui.recents.Recents;
     63 import com.android.systemui.recents.events.EventBus;
     64 import com.android.systemui.recents.events.activity.DockedTopTaskEvent;
     65 import com.android.systemui.recents.events.activity.RecentsActivityStartingEvent;
     66 import com.android.systemui.recents.events.activity.UndockingTaskEvent;
     67 import com.android.systemui.recents.events.ui.RecentsDrawnEvent;
     68 import com.android.systemui.recents.events.ui.RecentsGrowingEvent;
     69 import com.android.systemui.recents.misc.SystemServicesProxy;
     70 import com.android.systemui.stackdivider.events.StartedDragingEvent;
     71 import com.android.systemui.stackdivider.events.StoppedDragingEvent;
     72 import com.android.systemui.statusbar.FlingAnimationUtils;
     73 import com.android.systemui.statusbar.phone.NavigationBarGestureHelper;
     74 
     75 /**
     76  * Docked stack divider.
     77  */
     78 public class DividerView extends FrameLayout implements OnTouchListener,
     79         OnComputeInternalInsetsListener {
     80 
     81     static final long TOUCH_ANIMATION_DURATION = 150;
     82     static final long TOUCH_RELEASE_ANIMATION_DURATION = 200;
     83 
     84     public static final int INVALID_RECENTS_GROW_TARGET = -1;
     85 
     86     private static final int LOG_VALUE_RESIZE_50_50 = 0;
     87     private static final int LOG_VALUE_RESIZE_DOCKED_SMALLER = 1;
     88     private static final int LOG_VALUE_RESIZE_DOCKED_LARGER = 2;
     89 
     90     private static final int LOG_VALUE_UNDOCK_MAX_DOCKED = 0;
     91     private static final int LOG_VALUE_UNDOCK_MAX_OTHER = 1;
     92 
     93     private static final int TASK_POSITION_SAME = Integer.MAX_VALUE;
     94     private static final boolean SWAPPING_ENABLED = false;
     95 
     96     /**
     97      * How much the background gets scaled when we are in the minimized dock state.
     98      */
     99     private static final float MINIMIZE_DOCK_SCALE = 0f;
    100     private static final float ADJUSTED_FOR_IME_SCALE = 0.5f;
    101 
    102     private static final PathInterpolator SLOWDOWN_INTERPOLATOR =
    103             new PathInterpolator(0.5f, 1f, 0.5f, 1f);
    104     private static final PathInterpolator DIM_INTERPOLATOR =
    105             new PathInterpolator(.23f, .87f, .52f, -0.11f);
    106     private static final Interpolator IME_ADJUST_INTERPOLATOR =
    107             new PathInterpolator(0.2f, 0f, 0.1f, 1f);
    108 
    109     private DividerHandleView mHandle;
    110     private View mBackground;
    111     private MinimizedDockShadow mMinimizedShadow;
    112     private int mStartX;
    113     private int mStartY;
    114     private int mStartPosition;
    115     private int mDockSide;
    116     private final int[] mTempInt2 = new int[2];
    117     private boolean mMoving;
    118     private int mTouchSlop;
    119     private boolean mBackgroundLifted;
    120 
    121     private int mDividerInsets;
    122     private int mDisplayWidth;
    123     private int mDisplayHeight;
    124     private int mDividerWindowWidth;
    125     private int mDividerSize;
    126     private int mTouchElevation;
    127     private int mLongPressEntraceAnimDuration;
    128 
    129     private final Rect mDockedRect = new Rect();
    130     private final Rect mDockedTaskRect = new Rect();
    131     private final Rect mOtherTaskRect = new Rect();
    132     private final Rect mOtherRect = new Rect();
    133     private final Rect mDockedInsetRect = new Rect();
    134     private final Rect mOtherInsetRect = new Rect();
    135     private final Rect mLastResizeRect = new Rect();
    136     private final Rect mDisplayRect = new Rect();
    137     private final WindowManagerProxy mWindowManagerProxy = WindowManagerProxy.getInstance();
    138     private DividerWindowManager mWindowManager;
    139     private VelocityTracker mVelocityTracker;
    140     private FlingAnimationUtils mFlingAnimationUtils;
    141     private DividerSnapAlgorithm mSnapAlgorithm;
    142     private final Rect mStableInsets = new Rect();
    143 
    144     private boolean mGrowRecents;
    145     private ValueAnimator mCurrentAnimator;
    146     private boolean mEntranceAnimationRunning;
    147     private boolean mExitAnimationRunning;
    148     private int mExitStartPosition;
    149     private GestureDetector mGestureDetector;
    150     private boolean mDockedStackMinimized;
    151     private boolean mAdjustedForIme;
    152     private DividerState mState;
    153     private final Handler mHandler = new Handler();
    154 
    155     private final AccessibilityDelegate mHandleDelegate = new AccessibilityDelegate() {
    156         @Override
    157         public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
    158             super.onInitializeAccessibilityNodeInfo(host, info);
    159             if (isHorizontalDivision()) {
    160                 info.addAction(new AccessibilityAction(R.id.action_move_tl_full,
    161                         mContext.getString(R.string.accessibility_action_divider_top_full)));
    162                 if (mSnapAlgorithm.isFirstSplitTargetAvailable()) {
    163                     info.addAction(new AccessibilityAction(R.id.action_move_tl_70,
    164                             mContext.getString(R.string.accessibility_action_divider_top_70)));
    165                 }
    166                 info.addAction(new AccessibilityAction(R.id.action_move_tl_50,
    167                         mContext.getString(R.string.accessibility_action_divider_top_50)));
    168                 if (mSnapAlgorithm.isLastSplitTargetAvailable()) {
    169                     info.addAction(new AccessibilityAction(R.id.action_move_tl_30,
    170                             mContext.getString(R.string.accessibility_action_divider_top_30)));
    171                 }
    172                 info.addAction(new AccessibilityAction(R.id.action_move_rb_full,
    173                         mContext.getString(R.string.accessibility_action_divider_bottom_full)));
    174             } else {
    175                 info.addAction(new AccessibilityAction(R.id.action_move_tl_full,
    176                         mContext.getString(R.string.accessibility_action_divider_left_full)));
    177                 if (mSnapAlgorithm.isFirstSplitTargetAvailable()) {
    178                     info.addAction(new AccessibilityAction(R.id.action_move_tl_70,
    179                             mContext.getString(R.string.accessibility_action_divider_left_70)));
    180                 }
    181                 info.addAction(new AccessibilityAction(R.id.action_move_tl_50,
    182                         mContext.getString(R.string.accessibility_action_divider_left_50)));
    183                 if (mSnapAlgorithm.isLastSplitTargetAvailable()) {
    184                     info.addAction(new AccessibilityAction(R.id.action_move_tl_30,
    185                             mContext.getString(R.string.accessibility_action_divider_left_30)));
    186                 }
    187                 info.addAction(new AccessibilityAction(R.id.action_move_rb_full,
    188                         mContext.getString(R.string.accessibility_action_divider_right_full)));
    189             }
    190         }
    191 
    192         @Override
    193         public boolean performAccessibilityAction(View host, int action, Bundle args) {
    194             int currentPosition = getCurrentPosition();
    195             SnapTarget nextTarget = null;
    196             switch (action) {
    197                 case R.id.action_move_tl_full:
    198                     nextTarget = mSnapAlgorithm.getDismissEndTarget();
    199                     break;
    200                 case R.id.action_move_tl_70:
    201                     nextTarget = mSnapAlgorithm.getLastSplitTarget();
    202                     break;
    203                 case R.id.action_move_tl_50:
    204                     nextTarget = mSnapAlgorithm.getMiddleTarget();
    205                     break;
    206                 case R.id.action_move_tl_30:
    207                     nextTarget = mSnapAlgorithm.getFirstSplitTarget();
    208                     break;
    209                 case R.id.action_move_rb_full:
    210                     nextTarget = mSnapAlgorithm.getDismissStartTarget();
    211                     break;
    212             }
    213             if (nextTarget != null) {
    214                 startDragging(true /* animate */, false /* touching */);
    215                 stopDragging(currentPosition, nextTarget, 250, Interpolators.FAST_OUT_SLOW_IN);
    216                 return true;
    217             }
    218             return super.performAccessibilityAction(host, action, args);
    219         }
    220     };
    221 
    222     private final Runnable mResetBackgroundRunnable = new Runnable() {
    223         @Override
    224         public void run() {
    225             resetBackground();
    226         }
    227     };
    228 
    229     public DividerView(Context context) {
    230         super(context);
    231     }
    232 
    233     public DividerView(Context context, @Nullable AttributeSet attrs) {
    234         super(context, attrs);
    235     }
    236 
    237     public DividerView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
    238         super(context, attrs, defStyleAttr);
    239     }
    240 
    241     public DividerView(Context context, @Nullable AttributeSet attrs, int defStyleAttr,
    242             int defStyleRes) {
    243         super(context, attrs, defStyleAttr, defStyleRes);
    244     }
    245 
    246     @Override
    247     protected void onFinishInflate() {
    248         super.onFinishInflate();
    249         mHandle = (DividerHandleView) findViewById(R.id.docked_divider_handle);
    250         mBackground = findViewById(R.id.docked_divider_background);
    251         mMinimizedShadow = (MinimizedDockShadow) findViewById(R.id.minimized_dock_shadow);
    252         mHandle.setOnTouchListener(this);
    253         mDividerWindowWidth = getResources().getDimensionPixelSize(
    254                 com.android.internal.R.dimen.docked_stack_divider_thickness);
    255         mDividerInsets = getResources().getDimensionPixelSize(
    256                 com.android.internal.R.dimen.docked_stack_divider_insets);
    257         mDividerSize = mDividerWindowWidth - 2 * mDividerInsets;
    258         mTouchElevation = getResources().getDimensionPixelSize(
    259                 R.dimen.docked_stack_divider_lift_elevation);
    260         mLongPressEntraceAnimDuration = getResources().getInteger(
    261                 R.integer.long_press_dock_anim_duration);
    262         mGrowRecents = getResources().getBoolean(R.bool.recents_grow_in_multiwindow);
    263         mTouchSlop = ViewConfiguration.get(mContext).getScaledTouchSlop();
    264         mFlingAnimationUtils = new FlingAnimationUtils(getContext(), 0.3f);
    265         updateDisplayInfo();
    266         boolean landscape = getResources().getConfiguration().orientation
    267                 == Configuration.ORIENTATION_LANDSCAPE;
    268         mHandle.setPointerIcon(PointerIcon.getSystemIcon(getContext(),
    269                 landscape ? TYPE_HORIZONTAL_DOUBLE_ARROW : TYPE_VERTICAL_DOUBLE_ARROW));
    270         getViewTreeObserver().addOnComputeInternalInsetsListener(this);
    271         mHandle.setAccessibilityDelegate(mHandleDelegate);
    272         mGestureDetector = new GestureDetector(mContext, new SimpleOnGestureListener() {
    273             @Override
    274             public boolean onSingleTapUp(MotionEvent e) {
    275                 if (SWAPPING_ENABLED) {
    276                     updateDockSide();
    277                     SystemServicesProxy ssp = Recents.getSystemServices();
    278                     if (mDockSide != WindowManager.DOCKED_INVALID
    279                             && !ssp.isRecentsActivityVisible()) {
    280                         mWindowManagerProxy.swapTasks();
    281                         return true;
    282                     }
    283                 }
    284                 return false;
    285             }
    286         });
    287     }
    288 
    289     @Override
    290     protected void onAttachedToWindow() {
    291         super.onAttachedToWindow();
    292         EventBus.getDefault().register(this);
    293     }
    294 
    295     @Override
    296     protected void onDetachedFromWindow() {
    297         super.onDetachedFromWindow();
    298         EventBus.getDefault().unregister(this);
    299     }
    300 
    301     @Override
    302     public WindowInsets onApplyWindowInsets(WindowInsets insets) {
    303         if (mStableInsets.left != insets.getStableInsetLeft()
    304                 || mStableInsets.top != insets.getStableInsetTop()
    305                 || mStableInsets.right != insets.getStableInsetRight()
    306                 || mStableInsets.bottom != insets.getStableInsetBottom()) {
    307             mStableInsets.set(insets.getStableInsetLeft(), insets.getStableInsetTop(),
    308                     insets.getStableInsetRight(), insets.getStableInsetBottom());
    309             if (mSnapAlgorithm != null) {
    310                 mSnapAlgorithm = null;
    311                 initializeSnapAlgorithm();
    312             }
    313         }
    314         return super.onApplyWindowInsets(insets);
    315     }
    316 
    317     @Override
    318     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    319         super.onLayout(changed, left, top, right, bottom);
    320         int minimizeLeft = 0;
    321         int minimizeTop = 0;
    322         if (mDockSide == WindowManager.DOCKED_TOP) {
    323             minimizeTop = mBackground.getTop();
    324         } else if (mDockSide == WindowManager.DOCKED_LEFT) {
    325             minimizeLeft = mBackground.getLeft();
    326         } else if (mDockSide == WindowManager.DOCKED_RIGHT) {
    327             minimizeLeft = mBackground.getRight() - mMinimizedShadow.getWidth();
    328         }
    329         mMinimizedShadow.layout(minimizeLeft, minimizeTop,
    330                 minimizeLeft + mMinimizedShadow.getMeasuredWidth(),
    331                 minimizeTop + mMinimizedShadow.getMeasuredHeight());
    332         if (changed) {
    333             mWindowManagerProxy.setTouchRegion(new Rect(mHandle.getLeft(), mHandle.getTop(),
    334                     mHandle.getRight(), mHandle.getBottom()));
    335         }
    336     }
    337 
    338     public void injectDependencies(DividerWindowManager windowManager, DividerState dividerState) {
    339         mWindowManager = windowManager;
    340         mState = dividerState;
    341     }
    342 
    343     public WindowManagerProxy getWindowManagerProxy() {
    344         return mWindowManagerProxy;
    345     }
    346 
    347     public boolean startDragging(boolean animate, boolean touching) {
    348         cancelFlingAnimation();
    349         if (touching) {
    350             mHandle.setTouching(true, animate);
    351         }
    352         mDockSide = mWindowManagerProxy.getDockSide();
    353         initializeSnapAlgorithm();
    354         mWindowManagerProxy.setResizing(true);
    355         if (touching) {
    356             mWindowManager.setSlippery(false);
    357             liftBackground();
    358         }
    359         EventBus.getDefault().send(new StartedDragingEvent());
    360         return mDockSide != WindowManager.DOCKED_INVALID;
    361     }
    362 
    363     public void stopDragging(int position, float velocity, boolean avoidDismissStart,
    364             boolean logMetrics) {
    365         mHandle.setTouching(false, true /* animate */);
    366         fling(position, velocity, avoidDismissStart, logMetrics);
    367         mWindowManager.setSlippery(true);
    368         releaseBackground();
    369     }
    370 
    371     public void stopDragging(int position, SnapTarget target, long duration,
    372             Interpolator interpolator) {
    373         stopDragging(position, target, duration, 0 /* startDelay*/, 0 /* endDelay */, interpolator);
    374     }
    375 
    376     public void stopDragging(int position, SnapTarget target, long duration,
    377             Interpolator interpolator, long endDelay) {
    378         stopDragging(position, target, duration, 0 /* startDelay*/, endDelay, interpolator);
    379     }
    380 
    381     public void stopDragging(int position, SnapTarget target, long duration, long startDelay,
    382             long endDelay, Interpolator interpolator) {
    383         mHandle.setTouching(false, true /* animate */);
    384         flingTo(position, target, duration, startDelay, endDelay, interpolator);
    385         mWindowManager.setSlippery(true);
    386         releaseBackground();
    387     }
    388 
    389     private void stopDragging() {
    390         mHandle.setTouching(false, true /* animate */);
    391         mWindowManager.setSlippery(true);
    392         releaseBackground();
    393     }
    394 
    395     private void updateDockSide() {
    396         mDockSide = mWindowManagerProxy.getDockSide();
    397         mMinimizedShadow.setDockSide(mDockSide);
    398     }
    399 
    400     private void initializeSnapAlgorithm() {
    401         if (mSnapAlgorithm == null) {
    402             mSnapAlgorithm = new DividerSnapAlgorithm(getContext().getResources(), mDisplayWidth,
    403                     mDisplayHeight, mDividerSize, isHorizontalDivision(), mStableInsets);
    404         }
    405     }
    406 
    407     public DividerSnapAlgorithm getSnapAlgorithm() {
    408         initializeSnapAlgorithm();
    409         return mSnapAlgorithm;
    410     }
    411 
    412     public int getCurrentPosition() {
    413         getLocationOnScreen(mTempInt2);
    414         if (isHorizontalDivision()) {
    415             return mTempInt2[1] + mDividerInsets;
    416         } else {
    417             return mTempInt2[0] + mDividerInsets;
    418         }
    419     }
    420 
    421     @Override
    422     public boolean onTouch(View v, MotionEvent event) {
    423         convertToScreenCoordinates(event);
    424         mGestureDetector.onTouchEvent(event);
    425         final int action = event.getAction() & MotionEvent.ACTION_MASK;
    426         switch (action) {
    427             case MotionEvent.ACTION_DOWN:
    428                 mVelocityTracker = VelocityTracker.obtain();
    429                 mVelocityTracker.addMovement(event);
    430                 mStartX = (int) event.getX();
    431                 mStartY = (int) event.getY();
    432                 boolean result = startDragging(true /* animate */, true /* touching */);
    433                 if (!result) {
    434 
    435                     // Weren't able to start dragging successfully, so cancel it again.
    436                     stopDragging();
    437                 }
    438                 mStartPosition = getCurrentPosition();
    439                 mMoving = false;
    440                 return result;
    441             case MotionEvent.ACTION_MOVE:
    442                 mVelocityTracker.addMovement(event);
    443                 int x = (int) event.getX();
    444                 int y = (int) event.getY();
    445                 boolean exceededTouchSlop =
    446                         isHorizontalDivision() && Math.abs(y - mStartY) > mTouchSlop
    447                                 || (!isHorizontalDivision() && Math.abs(x - mStartX) > mTouchSlop);
    448                 if (!mMoving && exceededTouchSlop) {
    449                     mStartX = x;
    450                     mStartY = y;
    451                     mMoving = true;
    452                 }
    453                 if (mMoving && mDockSide != WindowManager.DOCKED_INVALID) {
    454                     SnapTarget snapTarget = mSnapAlgorithm.calculateSnapTarget(
    455                             mStartPosition, 0 /* velocity */, false /* hardDismiss */);
    456                     resizeStack(calculatePosition(x, y), mStartPosition, snapTarget);
    457                 }
    458                 break;
    459             case MotionEvent.ACTION_UP:
    460             case MotionEvent.ACTION_CANCEL:
    461                 mVelocityTracker.addMovement(event);
    462 
    463                 x = (int) event.getRawX();
    464                 y = (int) event.getRawY();
    465 
    466                 mVelocityTracker.computeCurrentVelocity(1000);
    467                 int position = calculatePosition(x, y);
    468                 stopDragging(position, isHorizontalDivision() ? mVelocityTracker.getYVelocity()
    469                         : mVelocityTracker.getXVelocity(), false /* avoidDismissStart */,
    470                         true /* log */);
    471                 mMoving = false;
    472                 break;
    473         }
    474         return true;
    475     }
    476 
    477     private void logResizeEvent(SnapTarget snapTarget) {
    478         if (snapTarget == mSnapAlgorithm.getDismissStartTarget()) {
    479             MetricsLogger.action(
    480                     mContext, MetricsEvent.ACTION_WINDOW_UNDOCK_MAX, dockSideTopLeft(mDockSide)
    481                             ? LOG_VALUE_UNDOCK_MAX_OTHER
    482                             : LOG_VALUE_UNDOCK_MAX_DOCKED);
    483         } else if (snapTarget == mSnapAlgorithm.getDismissEndTarget()) {
    484             MetricsLogger.action(
    485                     mContext, MetricsEvent.ACTION_WINDOW_UNDOCK_MAX, dockSideBottomRight(mDockSide)
    486                             ? LOG_VALUE_UNDOCK_MAX_OTHER
    487                             : LOG_VALUE_UNDOCK_MAX_DOCKED);
    488         } else if (snapTarget == mSnapAlgorithm.getMiddleTarget()) {
    489             MetricsLogger.action(mContext, MetricsEvent.ACTION_WINDOW_DOCK_RESIZE,
    490                     LOG_VALUE_RESIZE_50_50);
    491         } else if (snapTarget == mSnapAlgorithm.getFirstSplitTarget()) {
    492             MetricsLogger.action(mContext, MetricsEvent.ACTION_WINDOW_DOCK_RESIZE,
    493                     dockSideTopLeft(mDockSide)
    494                             ? LOG_VALUE_RESIZE_DOCKED_SMALLER
    495                             : LOG_VALUE_RESIZE_DOCKED_LARGER);
    496         } else if (snapTarget == mSnapAlgorithm.getLastSplitTarget()) {
    497             MetricsLogger.action(mContext, MetricsEvent.ACTION_WINDOW_DOCK_RESIZE,
    498                     dockSideTopLeft(mDockSide)
    499                             ? LOG_VALUE_RESIZE_DOCKED_LARGER
    500                             : LOG_VALUE_RESIZE_DOCKED_SMALLER);
    501         }
    502     }
    503 
    504     private void convertToScreenCoordinates(MotionEvent event) {
    505         event.setLocation(event.getRawX(), event.getRawY());
    506     }
    507 
    508     private void fling(int position, float velocity, boolean avoidDismissStart,
    509             boolean logMetrics) {
    510         SnapTarget snapTarget = mSnapAlgorithm.calculateSnapTarget(position, velocity);
    511         if (avoidDismissStart && snapTarget == mSnapAlgorithm.getDismissStartTarget()) {
    512             snapTarget = mSnapAlgorithm.getFirstSplitTarget();
    513         }
    514         if (logMetrics) {
    515             logResizeEvent(snapTarget);
    516         }
    517         ValueAnimator anim = getFlingAnimator(position, snapTarget, 0 /* endDelay */);
    518         mFlingAnimationUtils.apply(anim, position, snapTarget.position, velocity);
    519         anim.start();
    520     }
    521 
    522     private void flingTo(int position, SnapTarget target, long duration, long startDelay,
    523             long endDelay, Interpolator interpolator) {
    524         ValueAnimator anim = getFlingAnimator(position, target, endDelay);
    525         anim.setDuration(duration);
    526         anim.setStartDelay(startDelay);
    527         anim.setInterpolator(interpolator);
    528         anim.start();
    529     }
    530 
    531     private ValueAnimator getFlingAnimator(int position, final SnapTarget snapTarget,
    532             final long endDelay) {
    533         final boolean taskPositionSameAtEnd = snapTarget.flag == SnapTarget.FLAG_NONE;
    534         ValueAnimator anim = ValueAnimator.ofInt(position, snapTarget.position);
    535         anim.addUpdateListener(animation -> resizeStack((Integer) animation.getAnimatedValue(),
    536                 taskPositionSameAtEnd && animation.getAnimatedFraction() == 1f
    537                         ? TASK_POSITION_SAME
    538                         : snapTarget.taskPosition, snapTarget));
    539         Runnable endAction = () -> {
    540             commitSnapFlags(snapTarget);
    541             mWindowManagerProxy.setResizing(false);
    542             mDockSide = WindowManager.DOCKED_INVALID;
    543             mCurrentAnimator = null;
    544             mEntranceAnimationRunning = false;
    545             mExitAnimationRunning = false;
    546             EventBus.getDefault().send(new StoppedDragingEvent());
    547         };
    548         anim.addListener(new AnimatorListenerAdapter() {
    549 
    550             private boolean mCancelled;
    551 
    552             @Override
    553             public void onAnimationCancel(Animator animation) {
    554                 mCancelled = true;
    555             }
    556 
    557             @Override
    558             public void onAnimationEnd(Animator animation) {
    559                 if (endDelay == 0 || mCancelled) {
    560                     endAction.run();
    561                 } else {
    562                     mHandler.postDelayed(endAction, endDelay);
    563                 }
    564             }
    565         });
    566         mCurrentAnimator = anim;
    567         return anim;
    568     }
    569 
    570     private void cancelFlingAnimation() {
    571         if (mCurrentAnimator != null) {
    572             mCurrentAnimator.cancel();
    573         }
    574     }
    575 
    576     private void commitSnapFlags(SnapTarget target) {
    577         if (target.flag == SnapTarget.FLAG_NONE) {
    578             return;
    579         }
    580         boolean dismissOrMaximize;
    581         if (target.flag == SnapTarget.FLAG_DISMISS_START) {
    582             dismissOrMaximize = mDockSide == WindowManager.DOCKED_LEFT
    583                     || mDockSide == WindowManager.DOCKED_TOP;
    584         } else {
    585             dismissOrMaximize = mDockSide == WindowManager.DOCKED_RIGHT
    586                     || mDockSide == WindowManager.DOCKED_BOTTOM;
    587         }
    588         if (dismissOrMaximize) {
    589             mWindowManagerProxy.dismissDockedStack();
    590         } else {
    591             mWindowManagerProxy.maximizeDockedStack();
    592         }
    593         mWindowManagerProxy.setResizeDimLayer(false, -1, 0f);
    594     }
    595 
    596     private void liftBackground() {
    597         if (mBackgroundLifted) {
    598             return;
    599         }
    600         if (isHorizontalDivision()) {
    601             mBackground.animate().scaleY(1.4f);
    602         } else {
    603             mBackground.animate().scaleX(1.4f);
    604         }
    605         mBackground.animate()
    606                 .setInterpolator(Interpolators.TOUCH_RESPONSE)
    607                 .setDuration(TOUCH_ANIMATION_DURATION)
    608                 .translationZ(mTouchElevation)
    609                 .start();
    610 
    611         // Lift handle as well so it doesn't get behind the background, even though it doesn't
    612         // cast shadow.
    613         mHandle.animate()
    614                 .setInterpolator(Interpolators.TOUCH_RESPONSE)
    615                 .setDuration(TOUCH_ANIMATION_DURATION)
    616                 .translationZ(mTouchElevation)
    617                 .start();
    618         mBackgroundLifted = true;
    619     }
    620 
    621     private void releaseBackground() {
    622         if (!mBackgroundLifted) {
    623             return;
    624         }
    625         mBackground.animate()
    626                 .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
    627                 .setDuration(TOUCH_RELEASE_ANIMATION_DURATION)
    628                 .translationZ(0)
    629                 .scaleX(1f)
    630                 .scaleY(1f)
    631                 .start();
    632         mHandle.animate()
    633                 .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
    634                 .setDuration(TOUCH_RELEASE_ANIMATION_DURATION)
    635                 .translationZ(0)
    636                 .start();
    637         mBackgroundLifted = false;
    638     }
    639 
    640 
    641     public void setMinimizedDockStack(boolean minimized) {
    642         updateDockSide();
    643         mHandle.setAlpha(minimized ? 0f : 1f);
    644         if (!minimized) {
    645             resetBackground();
    646         } else if (mDockSide == WindowManager.DOCKED_TOP) {
    647             mBackground.setPivotY(0);
    648             mBackground.setScaleY(MINIMIZE_DOCK_SCALE);
    649         } else if (mDockSide == WindowManager.DOCKED_LEFT
    650                 || mDockSide == WindowManager.DOCKED_RIGHT) {
    651             mBackground.setPivotX(mDockSide == WindowManager.DOCKED_LEFT
    652                     ? 0
    653                     : mBackground.getWidth());
    654             mBackground.setScaleX(MINIMIZE_DOCK_SCALE);
    655         }
    656         mMinimizedShadow.setAlpha(minimized ? 1f : 0f);
    657         mDockedStackMinimized = minimized;
    658     }
    659 
    660     public void setMinimizedDockStack(boolean minimized, long animDuration) {
    661         updateDockSide();
    662         mHandle.animate()
    663                 .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
    664                 .setDuration(animDuration)
    665                 .alpha(minimized ? 0f : 1f)
    666                 .start();
    667         if (mDockSide == WindowManager.DOCKED_TOP) {
    668             mBackground.setPivotY(0);
    669             mBackground.animate()
    670                     .scaleY(minimized ? MINIMIZE_DOCK_SCALE : 1f);
    671         } else if (mDockSide == WindowManager.DOCKED_LEFT
    672                 || mDockSide == WindowManager.DOCKED_RIGHT) {
    673             mBackground.setPivotX(mDockSide == WindowManager.DOCKED_LEFT
    674                     ? 0
    675                     : mBackground.getWidth());
    676             mBackground.animate()
    677                     .scaleX(minimized ? MINIMIZE_DOCK_SCALE : 1f);
    678         }
    679         if (!minimized) {
    680             mBackground.animate().withEndAction(mResetBackgroundRunnable);
    681         }
    682         mMinimizedShadow.animate()
    683                 .alpha(minimized ? 1f : 0f)
    684                 .setInterpolator(Interpolators.ALPHA_IN)
    685                 .setDuration(animDuration)
    686                 .start();
    687         mBackground.animate()
    688                 .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
    689                 .setDuration(animDuration)
    690                 .start();
    691         mDockedStackMinimized = minimized;
    692     }
    693 
    694     public void setAdjustedForIme(boolean adjustedForIme) {
    695         updateDockSide();
    696         mHandle.setAlpha(adjustedForIme ? 0f : 1f);
    697         if (!adjustedForIme) {
    698             resetBackground();
    699         } else if (mDockSide == WindowManager.DOCKED_TOP) {
    700             mBackground.setPivotY(0);
    701             mBackground.setScaleY(ADJUSTED_FOR_IME_SCALE);
    702         }
    703         mAdjustedForIme = adjustedForIme;
    704     }
    705 
    706     public void setAdjustedForIme(boolean adjustedForIme, long animDuration) {
    707         updateDockSide();
    708         mHandle.animate()
    709                 .setInterpolator(IME_ADJUST_INTERPOLATOR)
    710                 .setDuration(animDuration)
    711                 .alpha(adjustedForIme ? 0f : 1f)
    712                 .start();
    713         if (mDockSide == WindowManager.DOCKED_TOP) {
    714             mBackground.setPivotY(0);
    715             mBackground.animate()
    716                     .scaleY(adjustedForIme ? ADJUSTED_FOR_IME_SCALE : 1f);
    717         }
    718         if (!adjustedForIme) {
    719             mBackground.animate().withEndAction(mResetBackgroundRunnable);
    720         }
    721         mBackground.animate()
    722                 .setInterpolator(IME_ADJUST_INTERPOLATOR)
    723                 .setDuration(animDuration)
    724                 .start();
    725         mAdjustedForIme = adjustedForIme;
    726     }
    727 
    728     private void resetBackground() {
    729         mBackground.setPivotX(mBackground.getWidth() / 2);
    730         mBackground.setPivotY(mBackground.getHeight() / 2);
    731         mBackground.setScaleX(1f);
    732         mBackground.setScaleY(1f);
    733         mMinimizedShadow.setAlpha(0f);
    734     }
    735 
    736     @Override
    737     protected void onConfigurationChanged(Configuration newConfig) {
    738         super.onConfigurationChanged(newConfig);
    739         updateDisplayInfo();
    740     }
    741 
    742 
    743     public void notifyDockSideChanged(int newDockSide) {
    744         mDockSide = newDockSide;
    745         mMinimizedShadow.setDockSide(mDockSide);
    746         requestLayout();
    747     }
    748 
    749     private void updateDisplayInfo() {
    750         final DisplayManager displayManager =
    751                 (DisplayManager) mContext.getSystemService(Context.DISPLAY_SERVICE);
    752         Display display = displayManager.getDisplay(Display.DEFAULT_DISPLAY);
    753         final DisplayInfo info = new DisplayInfo();
    754         display.getDisplayInfo(info);
    755         mDisplayWidth = info.logicalWidth;
    756         mDisplayHeight = info.logicalHeight;
    757         mSnapAlgorithm = null;
    758         initializeSnapAlgorithm();
    759     }
    760 
    761     private int calculatePosition(int touchX, int touchY) {
    762         return isHorizontalDivision() ? calculateYPosition(touchY) : calculateXPosition(touchX);
    763     }
    764 
    765     public boolean isHorizontalDivision() {
    766         return getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT;
    767     }
    768 
    769     private int calculateXPosition(int touchX) {
    770         return mStartPosition + touchX - mStartX;
    771     }
    772 
    773     private int calculateYPosition(int touchY) {
    774         return mStartPosition + touchY - mStartY;
    775     }
    776 
    777     private void alignTopLeft(Rect containingRect, Rect rect) {
    778         int width = rect.width();
    779         int height = rect.height();
    780         rect.set(containingRect.left, containingRect.top,
    781                 containingRect.left + width, containingRect.top + height);
    782     }
    783 
    784     private void alignBottomRight(Rect containingRect, Rect rect) {
    785         int width = rect.width();
    786         int height = rect.height();
    787         rect.set(containingRect.right - width, containingRect.bottom - height,
    788                 containingRect.right, containingRect.bottom);
    789     }
    790 
    791     public void calculateBoundsForPosition(int position, int dockSide, Rect outRect) {
    792         DockedDividerUtils.calculateBoundsForPosition(position, dockSide, outRect, mDisplayWidth,
    793                 mDisplayHeight, mDividerSize);
    794     }
    795 
    796     public void resizeStack(int position, int taskPosition, SnapTarget taskSnapTarget) {
    797         calculateBoundsForPosition(position, mDockSide, mDockedRect);
    798 
    799         if (mDockedRect.equals(mLastResizeRect) && !mEntranceAnimationRunning) {
    800             return;
    801         }
    802 
    803         // Make sure shadows are updated
    804         if (mBackground.getZ() > 0f) {
    805             mBackground.invalidate();
    806         }
    807 
    808         mLastResizeRect.set(mDockedRect);
    809         if (mEntranceAnimationRunning && taskPosition != TASK_POSITION_SAME) {
    810             if (mCurrentAnimator != null) {
    811                 calculateBoundsForPosition(taskPosition, mDockSide, mDockedTaskRect);
    812             } else {
    813                 calculateBoundsForPosition(isHorizontalDivision() ? mDisplayHeight : mDisplayWidth,
    814                         mDockSide, mDockedTaskRect);
    815             }
    816             calculateBoundsForPosition(taskPosition, DockedDividerUtils.invertDockSide(mDockSide),
    817                     mOtherTaskRect);
    818             mWindowManagerProxy.resizeDockedStack(mDockedRect, mDockedTaskRect, null,
    819                     mOtherTaskRect, null);
    820         } else if (mExitAnimationRunning && taskPosition != TASK_POSITION_SAME) {
    821             calculateBoundsForPosition(taskPosition,
    822                     mDockSide, mDockedTaskRect);
    823             calculateBoundsForPosition(mExitStartPosition,
    824                     DockedDividerUtils.invertDockSide(mDockSide), mOtherTaskRect);
    825             mOtherInsetRect.set(mOtherTaskRect);
    826             applyExitAnimationParallax(mOtherTaskRect, position);
    827             mWindowManagerProxy.resizeDockedStack(mDockedRect, mDockedTaskRect, null,
    828                     mOtherTaskRect, mOtherInsetRect);
    829         } else if (taskPosition != TASK_POSITION_SAME) {
    830             calculateBoundsForPosition(position, DockedDividerUtils.invertDockSide(mDockSide),
    831                     mOtherRect);
    832             int dockSideInverted = DockedDividerUtils.invertDockSide(mDockSide);
    833             int taskPositionDocked =
    834                     restrictDismissingTaskPosition(taskPosition, mDockSide, taskSnapTarget);
    835             int taskPositionOther =
    836                     restrictDismissingTaskPosition(taskPosition, dockSideInverted, taskSnapTarget);
    837             calculateBoundsForPosition(taskPositionDocked, mDockSide, mDockedTaskRect);
    838             calculateBoundsForPosition(taskPositionOther, dockSideInverted, mOtherTaskRect);
    839             mDisplayRect.set(0, 0, mDisplayWidth, mDisplayHeight);
    840             alignTopLeft(mDockedRect, mDockedTaskRect);
    841             alignTopLeft(mOtherRect, mOtherTaskRect);
    842             mDockedInsetRect.set(mDockedTaskRect);
    843             mOtherInsetRect.set(mOtherTaskRect);
    844             if (dockSideTopLeft(mDockSide)) {
    845                 alignTopLeft(mDisplayRect, mDockedInsetRect);
    846                 alignBottomRight(mDisplayRect, mOtherInsetRect);
    847             } else {
    848                 alignBottomRight(mDisplayRect, mDockedInsetRect);
    849                 alignTopLeft(mDisplayRect, mOtherInsetRect);
    850             }
    851             applyDismissingParallax(mDockedTaskRect, mDockSide, taskSnapTarget, position,
    852                     taskPositionDocked);
    853             applyDismissingParallax(mOtherTaskRect, dockSideInverted, taskSnapTarget, position,
    854                     taskPositionOther);
    855             mWindowManagerProxy.resizeDockedStack(mDockedRect, mDockedTaskRect, mDockedInsetRect,
    856                     mOtherTaskRect, mOtherInsetRect);
    857         } else {
    858             mWindowManagerProxy.resizeDockedStack(mDockedRect, null, null, null, null);
    859         }
    860         SnapTarget closestDismissTarget = mSnapAlgorithm.getClosestDismissTarget(position);
    861         float dimFraction = getDimFraction(position, closestDismissTarget);
    862         mWindowManagerProxy.setResizeDimLayer(dimFraction != 0f,
    863                 getStackIdForDismissTarget(closestDismissTarget),
    864                 dimFraction);
    865     }
    866 
    867     private void applyExitAnimationParallax(Rect taskRect, int position) {
    868         if (mDockSide == WindowManager.DOCKED_TOP) {
    869             taskRect.offset(0, (int) ((position - mExitStartPosition) * 0.25f));
    870         } else if (mDockSide == WindowManager.DOCKED_LEFT) {
    871             taskRect.offset((int) ((position - mExitStartPosition) * 0.25f), 0);
    872         } else if (mDockSide == WindowManager.DOCKED_RIGHT) {
    873             taskRect.offset((int) ((mExitStartPosition - position) * 0.25f), 0);
    874         }
    875     }
    876 
    877     private float getDimFraction(int position, SnapTarget dismissTarget) {
    878         if (mEntranceAnimationRunning) {
    879             return 0f;
    880         }
    881         float fraction = mSnapAlgorithm.calculateDismissingFraction(position);
    882         fraction = Math.max(0, Math.min(fraction, 1f));
    883         fraction = DIM_INTERPOLATOR.getInterpolation(fraction);
    884         if (hasInsetsAtDismissTarget(dismissTarget)) {
    885 
    886             // Less darkening with system insets.
    887             fraction *= 0.8f;
    888         }
    889         return fraction;
    890     }
    891 
    892     /**
    893      * @return true if and only if there are system insets at the location of the dismiss target
    894      */
    895     private boolean hasInsetsAtDismissTarget(SnapTarget dismissTarget) {
    896         if (isHorizontalDivision()) {
    897             if (dismissTarget == mSnapAlgorithm.getDismissStartTarget()) {
    898                 return mStableInsets.top != 0;
    899             } else {
    900                 return mStableInsets.bottom != 0;
    901             }
    902         } else {
    903             if (dismissTarget == mSnapAlgorithm.getDismissStartTarget()) {
    904                 return mStableInsets.left != 0;
    905             } else {
    906                 return mStableInsets.right != 0;
    907             }
    908         }
    909     }
    910 
    911     /**
    912      * When the snap target is dismissing one side, make sure that the dismissing side doesn't get
    913      * 0 size.
    914      */
    915     private int restrictDismissingTaskPosition(int taskPosition, int dockSide,
    916             SnapTarget snapTarget) {
    917         if (snapTarget.flag == SnapTarget.FLAG_DISMISS_START && dockSideTopLeft(dockSide)) {
    918             return Math.max(mSnapAlgorithm.getFirstSplitTarget().position, mStartPosition);
    919         } else if (snapTarget.flag == SnapTarget.FLAG_DISMISS_END
    920                 && dockSideBottomRight(dockSide)) {
    921             return Math.min(mSnapAlgorithm.getLastSplitTarget().position, mStartPosition);
    922         } else {
    923             return taskPosition;
    924         }
    925     }
    926 
    927     /**
    928      * Applies a parallax to the task when dismissing.
    929      */
    930     private void applyDismissingParallax(Rect taskRect, int dockSide, SnapTarget snapTarget,
    931             int position, int taskPosition) {
    932         float fraction = Math.min(1, Math.max(0,
    933                 mSnapAlgorithm.calculateDismissingFraction(position)));
    934         SnapTarget dismissTarget = null;
    935         SnapTarget splitTarget = null;
    936         int start = 0;
    937         if (position <= mSnapAlgorithm.getLastSplitTarget().position
    938                 && dockSideTopLeft(dockSide)) {
    939             dismissTarget = mSnapAlgorithm.getDismissStartTarget();
    940             splitTarget = mSnapAlgorithm.getFirstSplitTarget();
    941             start = taskPosition;
    942         } else if (position >= mSnapAlgorithm.getLastSplitTarget().position
    943                 && dockSideBottomRight(dockSide)) {
    944             dismissTarget = mSnapAlgorithm.getDismissEndTarget();
    945             splitTarget = mSnapAlgorithm.getLastSplitTarget();
    946             start = splitTarget.position;
    947         }
    948         if (dismissTarget != null && fraction > 0f
    949                 && isDismissing(splitTarget, position, dockSide)) {
    950             fraction = calculateParallaxDismissingFraction(fraction, dockSide);
    951             int offsetPosition = (int) (start +
    952                     fraction * (dismissTarget.position - splitTarget.position));
    953             int width = taskRect.width();
    954             int height = taskRect.height();
    955             switch (dockSide) {
    956                 case WindowManager.DOCKED_LEFT:
    957                     taskRect.left = offsetPosition - width;
    958                     taskRect.right = offsetPosition;
    959                     break;
    960                 case WindowManager.DOCKED_RIGHT:
    961                     taskRect.left = offsetPosition + mDividerSize;
    962                     taskRect.right = offsetPosition + width + mDividerSize;
    963                     break;
    964                 case WindowManager.DOCKED_TOP:
    965                     taskRect.top = offsetPosition - height;
    966                     taskRect.bottom = offsetPosition;
    967                     break;
    968                 case WindowManager.DOCKED_BOTTOM:
    969                     taskRect.top = offsetPosition + mDividerSize;
    970                     taskRect.bottom = offsetPosition + height + mDividerSize;
    971                     break;
    972             }
    973         }
    974     }
    975 
    976     /**
    977      * @return for a specified {@code fraction}, this returns an adjusted value that simulates a
    978      *         slowing down parallax effect
    979      */
    980     private static float calculateParallaxDismissingFraction(float fraction, int dockSide) {
    981         float result = SLOWDOWN_INTERPOLATOR.getInterpolation(fraction) / 3.5f;
    982 
    983         // Less parallax at the top, just because.
    984         if (dockSide == WindowManager.DOCKED_TOP) {
    985             result /= 2f;
    986         }
    987         return result;
    988     }
    989 
    990     private static boolean isDismissing(SnapTarget snapTarget, int position, int dockSide) {
    991         if (dockSide == WindowManager.DOCKED_TOP || dockSide == WindowManager.DOCKED_LEFT) {
    992             return position < snapTarget.position;
    993         } else {
    994             return position > snapTarget.position;
    995         }
    996     }
    997 
    998     private int getStackIdForDismissTarget(SnapTarget dismissTarget) {
    999         if ((dismissTarget.flag == SnapTarget.FLAG_DISMISS_START && dockSideTopLeft(mDockSide))
   1000                 || (dismissTarget.flag == SnapTarget.FLAG_DISMISS_END
   1001                         && dockSideBottomRight(mDockSide))) {
   1002             return StackId.DOCKED_STACK_ID;
   1003         } else {
   1004             return StackId.HOME_STACK_ID;
   1005         }
   1006     }
   1007 
   1008     /**
   1009      * @return true if and only if {@code dockSide} is top or left
   1010      */
   1011     private static boolean dockSideTopLeft(int dockSide) {
   1012         return dockSide == WindowManager.DOCKED_TOP || dockSide == WindowManager.DOCKED_LEFT;
   1013     }
   1014 
   1015     /**
   1016      * @return true if and only if {@code dockSide} is bottom or right
   1017      */
   1018     private static boolean dockSideBottomRight(int dockSide) {
   1019         return dockSide == WindowManager.DOCKED_BOTTOM || dockSide == WindowManager.DOCKED_RIGHT;
   1020     }
   1021 
   1022     @Override
   1023     public void onComputeInternalInsets(InternalInsetsInfo inoutInfo) {
   1024         inoutInfo.setTouchableInsets(InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
   1025         inoutInfo.touchableRegion.set(mHandle.getLeft(), mHandle.getTop(), mHandle.getRight(),
   1026                 mHandle.getBottom());
   1027         inoutInfo.touchableRegion.op(mBackground.getLeft(), mBackground.getTop(),
   1028                 mBackground.getRight(), mBackground.getBottom(), Op.UNION);
   1029     }
   1030 
   1031     /**
   1032      * Checks whether recents will grow when invoked. This happens in multi-window when recents is
   1033      * very small. When invoking recents, we shrink the docked stack so recents has more space.
   1034      *
   1035      * @return the position of the divider when recents grows, or
   1036      *         {@link #INVALID_RECENTS_GROW_TARGET} if recents won't grow
   1037      */
   1038     public int growsRecents() {
   1039         boolean result = mGrowRecents
   1040                 && mWindowManagerProxy.getDockSide() == WindowManager.DOCKED_TOP
   1041                 && getCurrentPosition() == getSnapAlgorithm().getLastSplitTarget().position;
   1042         if (result) {
   1043             return getSnapAlgorithm().getMiddleTarget().position;
   1044         } else {
   1045             return INVALID_RECENTS_GROW_TARGET;
   1046         }
   1047     }
   1048 
   1049     public final void onBusEvent(RecentsActivityStartingEvent recentsActivityStartingEvent) {
   1050         if (mGrowRecents && getWindowManagerProxy().getDockSide() == WindowManager.DOCKED_TOP
   1051                 && getCurrentPosition() == getSnapAlgorithm().getLastSplitTarget().position) {
   1052             mState.growAfterRecentsDrawn = true;
   1053             startDragging(false /* animate */, false /* touching */);
   1054         }
   1055     }
   1056 
   1057     public final void onBusEvent(DockedTopTaskEvent event) {
   1058         if (event.dragMode == NavigationBarGestureHelper.DRAG_MODE_NONE) {
   1059             mState.growAfterRecentsDrawn = false;
   1060             mState.animateAfterRecentsDrawn = true;
   1061             startDragging(false /* animate */, false /* touching */);
   1062         }
   1063         updateDockSide();
   1064         int position = DockedDividerUtils.calculatePositionForBounds(event.initialRect,
   1065                 mDockSide, mDividerSize);
   1066         mEntranceAnimationRunning = true;
   1067 
   1068         // Insets might not have been fetched yet, so fetch manually if needed.
   1069         if (mStableInsets.isEmpty()) {
   1070             SystemServicesProxy.getInstance(mContext).getStableInsets(mStableInsets);
   1071             mSnapAlgorithm = null;
   1072             initializeSnapAlgorithm();
   1073         }
   1074 
   1075         resizeStack(position, mSnapAlgorithm.getMiddleTarget().position,
   1076                 mSnapAlgorithm.getMiddleTarget());
   1077     }
   1078 
   1079     public final void onBusEvent(RecentsDrawnEvent drawnEvent) {
   1080         if (mState.animateAfterRecentsDrawn) {
   1081             mState.animateAfterRecentsDrawn = false;
   1082             updateDockSide();
   1083 
   1084             mHandler.post(() -> {
   1085                 // Delay switching resizing mode because this might cause jank in recents animation
   1086                 // that's longer than this animation.
   1087                 stopDragging(getCurrentPosition(), mSnapAlgorithm.getMiddleTarget(),
   1088                         mLongPressEntraceAnimDuration, Interpolators.FAST_OUT_SLOW_IN,
   1089                         200 /* endDelay */);
   1090             });
   1091         }
   1092         if (mState.growAfterRecentsDrawn) {
   1093             mState.growAfterRecentsDrawn = false;
   1094             updateDockSide();
   1095             EventBus.getDefault().send(new RecentsGrowingEvent());
   1096             stopDragging(getCurrentPosition(), mSnapAlgorithm.getMiddleTarget(), 336,
   1097                     Interpolators.FAST_OUT_SLOW_IN);
   1098         }
   1099     }
   1100 
   1101     public final void onBusEvent(UndockingTaskEvent undockingTaskEvent) {
   1102         int dockSide = mWindowManagerProxy.getDockSide();
   1103         if (dockSide != WindowManager.DOCKED_INVALID && !mDockedStackMinimized) {
   1104             startDragging(false /* animate */, false /* touching */);
   1105             SnapTarget target = dockSideTopLeft(dockSide)
   1106                     ? mSnapAlgorithm.getDismissEndTarget()
   1107                     : mSnapAlgorithm.getDismissStartTarget();
   1108 
   1109             // Don't start immediately - give a little bit time to settle the drag resize change.
   1110             mExitAnimationRunning = true;
   1111             mExitStartPosition = getCurrentPosition();
   1112             stopDragging(mExitStartPosition, target, 336 /* duration */, 100 /* startDelay */,
   1113                     0 /* endDelay */, Interpolators.FAST_OUT_SLOW_IN);
   1114         }
   1115     }
   1116 }
   1117