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