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