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