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