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