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