DividerView.java revision 5b49464dc6cae55c240edf794d5b0da988774151
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 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        super(context);
254    }
255
256    public DividerView(Context context, @Nullable AttributeSet attrs) {
257        super(context, attrs);
258    }
259
260    public DividerView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
261        super(context, attrs, defStyleAttr);
262    }
263
264    public DividerView(Context context, @Nullable AttributeSet attrs, int defStyleAttr,
265            int defStyleRes) {
266        super(context, attrs, defStyleAttr, defStyleRes);
267    }
268
269    @Override
270    protected void onFinishInflate() {
271        super.onFinishInflate();
272        mHandle = findViewById(R.id.docked_divider_handle);
273        mBackground = findViewById(R.id.docked_divider_background);
274        mMinimizedShadow = findViewById(R.id.minimized_dock_shadow);
275        mHandle.setOnTouchListener(this);
276        mDividerWindowWidth = getResources().getDimensionPixelSize(
277                com.android.internal.R.dimen.docked_stack_divider_thickness);
278        mDividerInsets = getResources().getDimensionPixelSize(
279                com.android.internal.R.dimen.docked_stack_divider_insets);
280        mDividerSize = mDividerWindowWidth - 2 * mDividerInsets;
281        mTouchElevation = getResources().getDimensionPixelSize(
282                R.dimen.docked_stack_divider_lift_elevation);
283        mLongPressEntraceAnimDuration = getResources().getInteger(
284                R.integer.long_press_dock_anim_duration);
285        mGrowRecents = getResources().getBoolean(R.bool.recents_grow_in_multiwindow);
286        mTouchSlop = ViewConfiguration.get(mContext).getScaledTouchSlop();
287        mFlingAnimationUtils = new FlingAnimationUtils(getContext(), 0.3f);
288        updateDisplayInfo();
289        boolean landscape = getResources().getConfiguration().orientation
290                == Configuration.ORIENTATION_LANDSCAPE;
291        mHandle.setPointerIcon(PointerIcon.getSystemIcon(getContext(),
292                landscape ? TYPE_HORIZONTAL_DOUBLE_ARROW : TYPE_VERTICAL_DOUBLE_ARROW));
293        getViewTreeObserver().addOnComputeInternalInsetsListener(this);
294        mHandle.setAccessibilityDelegate(mHandleDelegate);
295        mGestureDetector = new GestureDetector(mContext, new SimpleOnGestureListener() {
296            @Override
297            public boolean onSingleTapUp(MotionEvent e) {
298                if (SWAPPING_ENABLED) {
299                    updateDockSide();
300                    SystemServicesProxy ssp = Recents.getSystemServices();
301                    if (mDockSide != WindowManager.DOCKED_INVALID
302                            && !ssp.isRecentsActivityVisible()) {
303                        mWindowManagerProxy.swapTasks();
304                        return true;
305                    }
306                }
307                return false;
308            }
309        });
310    }
311
312    @Override
313    protected void onAttachedToWindow() {
314        super.onAttachedToWindow();
315        EventBus.getDefault().register(this);
316        mSfChoreographer = new SurfaceFlingerVsyncChoreographer(mHandler, getDisplay(),
317                Choreographer.getInstance());
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                if (minimized) {
730                    mIsInMinimizeInteraction = true;
731                    mDividerPositionBeforeMinimized = DockedDividerUtils.calculateMiddlePosition(
732                            isHorizontalDivision(), mStableInsets, mDisplayWidth, mDisplayHeight,
733                            mDividerSize);
734
735                    int position = mMinimizedSnapAlgorithm.getMiddleTarget().position;
736                    resizeStack(position, position, mMinimizedSnapAlgorithm.getMiddleTarget());
737                } else {
738                    resizeStack(mDividerPositionBeforeMinimized, mDividerPositionBeforeMinimized,
739                            mSnapAlgorithm.calculateNonDismissingSnapTarget(
740                                    mDividerPositionBeforeMinimized));
741                    mIsInMinimizeInteraction = false;
742                }
743            }
744        }
745    }
746
747    public void setMinimizedDockStack(boolean minimized, long animDuration,
748            boolean isHomeStackResizable) {
749        mHomeStackResizable = isHomeStackResizable;
750        updateDockSide();
751        if (!isHomeStackResizable) {
752            mMinimizedShadow.animate()
753                    .alpha(minimized ? 1f : 0f)
754                    .setInterpolator(Interpolators.ALPHA_IN)
755                    .setDuration(animDuration)
756                    .start();
757            mHandle.animate()
758                    .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
759                    .setDuration(animDuration)
760                    .alpha(minimized ? 0f : 1f)
761                    .start();
762            if (mDockSide == WindowManager.DOCKED_TOP) {
763                mBackground.setPivotY(0);
764                mBackground.animate()
765                        .scaleY(minimized ? MINIMIZE_DOCK_SCALE : 1f);
766            } else if (mDockSide == WindowManager.DOCKED_LEFT
767                    || mDockSide == WindowManager.DOCKED_RIGHT) {
768                mBackground.setPivotX(mDockSide == WindowManager.DOCKED_LEFT
769                        ? 0
770                        : mBackground.getWidth());
771                mBackground.animate()
772                        .scaleX(minimized ? MINIMIZE_DOCK_SCALE : 1f);
773            }
774            mDockedStackMinimized = minimized;
775        } else if (mDockedStackMinimized != minimized) {
776            mIsInMinimizeInteraction = true;
777            if (minimized && (mCurrentAnimator == null || !mCurrentAnimator.isRunning())
778                    && (mDividerPositionBeforeMinimized <= 0 || !mAdjustedForIme)) {
779                mDividerPositionBeforeMinimized = getCurrentPosition();
780            }
781            mMinimizedSnapAlgorithm = null;
782            mDockedStackMinimized = minimized;
783            initializeSnapAlgorithm();
784            stopDragging(minimized
785                            ? mDividerPositionBeforeMinimized
786                            : getCurrentPosition(),
787                    minimized
788                            ? mMinimizedSnapAlgorithm.getMiddleTarget()
789                            : mSnapAlgorithm.calculateNonDismissingSnapTarget(
790                                    mDividerPositionBeforeMinimized),
791                    animDuration, Interpolators.FAST_OUT_SLOW_IN, 0);
792            setAdjustedForIme(false, animDuration);
793        }
794        if (!minimized) {
795            mBackground.animate().withEndAction(mResetBackgroundRunnable);
796        }
797        mBackground.animate()
798                .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
799                .setDuration(animDuration)
800                .start();
801    }
802
803    public void setAdjustedForIme(boolean adjustedForIme) {
804        updateDockSide();
805        mHandle.setAlpha(adjustedForIme ? 0f : 1f);
806        if (!adjustedForIme) {
807            resetBackground();
808        } else if (mDockSide == WindowManager.DOCKED_TOP) {
809            mBackground.setPivotY(0);
810            mBackground.setScaleY(ADJUSTED_FOR_IME_SCALE);
811        }
812        mAdjustedForIme = adjustedForIme;
813    }
814
815    public void setAdjustedForIme(boolean adjustedForIme, long animDuration) {
816        updateDockSide();
817        mHandle.animate()
818                .setInterpolator(IME_ADJUST_INTERPOLATOR)
819                .setDuration(animDuration)
820                .alpha(adjustedForIme ? 0f : 1f)
821                .start();
822        if (mDockSide == WindowManager.DOCKED_TOP) {
823            mBackground.setPivotY(0);
824            mBackground.animate()
825                    .scaleY(adjustedForIme ? ADJUSTED_FOR_IME_SCALE : 1f);
826        }
827        if (!adjustedForIme) {
828            mBackground.animate().withEndAction(mResetBackgroundRunnable);
829        }
830        mBackground.animate()
831                .setInterpolator(IME_ADJUST_INTERPOLATOR)
832                .setDuration(animDuration)
833                .start();
834        mAdjustedForIme = adjustedForIme;
835        if (mHomeStackResizable && adjustedForIme) {
836            mDividerPositionBeforeMinimized = getCurrentPosition();
837        }
838    }
839
840    private void resetBackground() {
841        mBackground.setPivotX(mBackground.getWidth() / 2);
842        mBackground.setPivotY(mBackground.getHeight() / 2);
843        mBackground.setScaleX(1f);
844        mBackground.setScaleY(1f);
845        mMinimizedShadow.setAlpha(0f);
846    }
847
848    @Override
849    protected void onConfigurationChanged(Configuration newConfig) {
850        super.onConfigurationChanged(newConfig);
851        updateDisplayInfo();
852    }
853
854
855    public void notifyDockSideChanged(int newDockSide) {
856        mDockSide = newDockSide;
857        mMinimizedShadow.setDockSide(mDockSide);
858        requestLayout();
859    }
860
861    private void updateDisplayInfo() {
862        final DisplayManager displayManager =
863                (DisplayManager) mContext.getSystemService(Context.DISPLAY_SERVICE);
864        Display display = displayManager.getDisplay(Display.DEFAULT_DISPLAY);
865        final DisplayInfo info = new DisplayInfo();
866        display.getDisplayInfo(info);
867        mDisplayWidth = info.logicalWidth;
868        mDisplayHeight = info.logicalHeight;
869        mSnapAlgorithm = null;
870        mMinimizedSnapAlgorithm = null;
871        initializeSnapAlgorithm();
872    }
873
874    private int calculatePosition(int touchX, int touchY) {
875        return isHorizontalDivision() ? calculateYPosition(touchY) : calculateXPosition(touchX);
876    }
877
878    public boolean isHorizontalDivision() {
879        return getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT;
880    }
881
882    private int calculateXPosition(int touchX) {
883        return mStartPosition + touchX - mStartX;
884    }
885
886    private int calculateYPosition(int touchY) {
887        return mStartPosition + touchY - mStartY;
888    }
889
890    private void alignTopLeft(Rect containingRect, Rect rect) {
891        int width = rect.width();
892        int height = rect.height();
893        rect.set(containingRect.left, containingRect.top,
894                containingRect.left + width, containingRect.top + height);
895    }
896
897    private void alignBottomRight(Rect containingRect, Rect rect) {
898        int width = rect.width();
899        int height = rect.height();
900        rect.set(containingRect.right - width, containingRect.bottom - height,
901                containingRect.right, containingRect.bottom);
902    }
903
904    public void calculateBoundsForPosition(int position, int dockSide, Rect outRect) {
905        DockedDividerUtils.calculateBoundsForPosition(position, dockSide, outRect, mDisplayWidth,
906                mDisplayHeight, mDividerSize);
907    }
908
909    public void resizeStackDelayed(int position, int taskPosition, SnapTarget taskSnapTarget) {
910        Message message = mHandler.obtainMessage(MSG_RESIZE_STACK, position, taskPosition,
911                taskSnapTarget);
912        message.setAsynchronous(true);
913        mSfChoreographer.scheduleAtSfVsync(mHandler, message);
914    }
915
916    public void resizeStack(int position, int taskPosition, SnapTarget taskSnapTarget) {
917        calculateBoundsForPosition(position, mDockSide, mDockedRect);
918
919        if (mDockedRect.equals(mLastResizeRect) && !mEntranceAnimationRunning) {
920            return;
921        }
922
923        // Make sure shadows are updated
924        if (mBackground.getZ() > 0f) {
925            mBackground.invalidate();
926        }
927
928        mLastResizeRect.set(mDockedRect);
929        if (mHomeStackResizable && mIsInMinimizeInteraction) {
930            calculateBoundsForPosition(mDividerPositionBeforeMinimized, mDockSide, mDockedTaskRect);
931            calculateBoundsForPosition(mDividerPositionBeforeMinimized,
932                    DockedDividerUtils.invertDockSide(mDockSide), mOtherTaskRect);
933            mWindowManagerProxy.resizeDockedStack(mDockedRect, mDockedTaskRect, mDockedTaskRect,
934                    mOtherTaskRect, null);
935            return;
936        }
937
938        if (mEntranceAnimationRunning && taskPosition != TASK_POSITION_SAME) {
939            if (mCurrentAnimator != null) {
940                calculateBoundsForPosition(taskPosition, mDockSide, mDockedTaskRect);
941            } else {
942                calculateBoundsForPosition(isHorizontalDivision() ? mDisplayHeight : mDisplayWidth,
943                        mDockSide, mDockedTaskRect);
944            }
945            calculateBoundsForPosition(taskPosition, DockedDividerUtils.invertDockSide(mDockSide),
946                    mOtherTaskRect);
947            mWindowManagerProxy.resizeDockedStack(mDockedRect, mDockedTaskRect, null,
948                    mOtherTaskRect, null);
949        } else if (mExitAnimationRunning && taskPosition != TASK_POSITION_SAME) {
950            calculateBoundsForPosition(taskPosition,
951                    mDockSide, mDockedTaskRect);
952            calculateBoundsForPosition(mExitStartPosition,
953                    DockedDividerUtils.invertDockSide(mDockSide), mOtherTaskRect);
954            mOtherInsetRect.set(mOtherTaskRect);
955            applyExitAnimationParallax(mOtherTaskRect, position);
956            mWindowManagerProxy.resizeDockedStack(mDockedRect, mDockedTaskRect, null,
957                    mOtherTaskRect, mOtherInsetRect);
958        } else if (taskPosition != TASK_POSITION_SAME) {
959            calculateBoundsForPosition(position, DockedDividerUtils.invertDockSide(mDockSide),
960                    mOtherRect);
961            int dockSideInverted = DockedDividerUtils.invertDockSide(mDockSide);
962            int taskPositionDocked =
963                    restrictDismissingTaskPosition(taskPosition, mDockSide, taskSnapTarget);
964            int taskPositionOther =
965                    restrictDismissingTaskPosition(taskPosition, dockSideInverted, taskSnapTarget);
966            calculateBoundsForPosition(taskPositionDocked, mDockSide, mDockedTaskRect);
967            calculateBoundsForPosition(taskPositionOther, dockSideInverted, mOtherTaskRect);
968            mDisplayRect.set(0, 0, mDisplayWidth, mDisplayHeight);
969            alignTopLeft(mDockedRect, mDockedTaskRect);
970            alignTopLeft(mOtherRect, mOtherTaskRect);
971            mDockedInsetRect.set(mDockedTaskRect);
972            mOtherInsetRect.set(mOtherTaskRect);
973            if (dockSideTopLeft(mDockSide)) {
974                alignTopLeft(mDisplayRect, mDockedInsetRect);
975                alignBottomRight(mDisplayRect, mOtherInsetRect);
976            } else {
977                alignBottomRight(mDisplayRect, mDockedInsetRect);
978                alignTopLeft(mDisplayRect, mOtherInsetRect);
979            }
980            applyDismissingParallax(mDockedTaskRect, mDockSide, taskSnapTarget, position,
981                    taskPositionDocked);
982            applyDismissingParallax(mOtherTaskRect, dockSideInverted, taskSnapTarget, position,
983                    taskPositionOther);
984            mWindowManagerProxy.resizeDockedStack(mDockedRect, mDockedTaskRect, mDockedInsetRect,
985                    mOtherTaskRect, mOtherInsetRect);
986        } else {
987            mWindowManagerProxy.resizeDockedStack(mDockedRect, null, null, null, null);
988        }
989        SnapTarget closestDismissTarget = getSnapAlgorithm().getClosestDismissTarget(position);
990        float dimFraction = getDimFraction(position, closestDismissTarget);
991        mWindowManagerProxy.setResizeDimLayer(dimFraction != 0f,
992                getStackIdForDismissTarget(closestDismissTarget),
993                dimFraction);
994    }
995
996    private void applyExitAnimationParallax(Rect taskRect, int position) {
997        if (mDockSide == WindowManager.DOCKED_TOP) {
998            taskRect.offset(0, (int) ((position - mExitStartPosition) * 0.25f));
999        } else if (mDockSide == WindowManager.DOCKED_LEFT) {
1000            taskRect.offset((int) ((position - mExitStartPosition) * 0.25f), 0);
1001        } else if (mDockSide == WindowManager.DOCKED_RIGHT) {
1002            taskRect.offset((int) ((mExitStartPosition - position) * 0.25f), 0);
1003        }
1004    }
1005
1006    private float getDimFraction(int position, SnapTarget dismissTarget) {
1007        if (mEntranceAnimationRunning) {
1008            return 0f;
1009        }
1010        float fraction = getSnapAlgorithm().calculateDismissingFraction(position);
1011        fraction = Math.max(0, Math.min(fraction, 1f));
1012        fraction = DIM_INTERPOLATOR.getInterpolation(fraction);
1013        if (hasInsetsAtDismissTarget(dismissTarget)) {
1014
1015            // Less darkening with system insets.
1016            fraction *= 0.8f;
1017        }
1018        return fraction;
1019    }
1020
1021    /**
1022     * @return true if and only if there are system insets at the location of the dismiss target
1023     */
1024    private boolean hasInsetsAtDismissTarget(SnapTarget dismissTarget) {
1025        if (isHorizontalDivision()) {
1026            if (dismissTarget == getSnapAlgorithm().getDismissStartTarget()) {
1027                return mStableInsets.top != 0;
1028            } else {
1029                return mStableInsets.bottom != 0;
1030            }
1031        } else {
1032            if (dismissTarget == getSnapAlgorithm().getDismissStartTarget()) {
1033                return mStableInsets.left != 0;
1034            } else {
1035                return mStableInsets.right != 0;
1036            }
1037        }
1038    }
1039
1040    /**
1041     * When the snap target is dismissing one side, make sure that the dismissing side doesn't get
1042     * 0 size.
1043     */
1044    private int restrictDismissingTaskPosition(int taskPosition, int dockSide,
1045            SnapTarget snapTarget) {
1046        if (snapTarget.flag == SnapTarget.FLAG_DISMISS_START && dockSideTopLeft(dockSide)) {
1047            return Math.max(mSnapAlgorithm.getFirstSplitTarget().position, mStartPosition);
1048        } else if (snapTarget.flag == SnapTarget.FLAG_DISMISS_END
1049                && dockSideBottomRight(dockSide)) {
1050            return Math.min(mSnapAlgorithm.getLastSplitTarget().position, mStartPosition);
1051        } else {
1052            return taskPosition;
1053        }
1054    }
1055
1056    /**
1057     * Applies a parallax to the task when dismissing.
1058     */
1059    private void applyDismissingParallax(Rect taskRect, int dockSide, SnapTarget snapTarget,
1060            int position, int taskPosition) {
1061        float fraction = Math.min(1, Math.max(0,
1062                mSnapAlgorithm.calculateDismissingFraction(position)));
1063        SnapTarget dismissTarget = null;
1064        SnapTarget splitTarget = null;
1065        int start = 0;
1066        if (position <= mSnapAlgorithm.getLastSplitTarget().position
1067                && dockSideTopLeft(dockSide)) {
1068            dismissTarget = mSnapAlgorithm.getDismissStartTarget();
1069            splitTarget = mSnapAlgorithm.getFirstSplitTarget();
1070            start = taskPosition;
1071        } else if (position >= mSnapAlgorithm.getLastSplitTarget().position
1072                && dockSideBottomRight(dockSide)) {
1073            dismissTarget = mSnapAlgorithm.getDismissEndTarget();
1074            splitTarget = mSnapAlgorithm.getLastSplitTarget();
1075            start = splitTarget.position;
1076        }
1077        if (dismissTarget != null && fraction > 0f
1078                && isDismissing(splitTarget, position, dockSide)) {
1079            fraction = calculateParallaxDismissingFraction(fraction, dockSide);
1080            int offsetPosition = (int) (start +
1081                    fraction * (dismissTarget.position - splitTarget.position));
1082            int width = taskRect.width();
1083            int height = taskRect.height();
1084            switch (dockSide) {
1085                case WindowManager.DOCKED_LEFT:
1086                    taskRect.left = offsetPosition - width;
1087                    taskRect.right = offsetPosition;
1088                    break;
1089                case WindowManager.DOCKED_RIGHT:
1090                    taskRect.left = offsetPosition + mDividerSize;
1091                    taskRect.right = offsetPosition + width + mDividerSize;
1092                    break;
1093                case WindowManager.DOCKED_TOP:
1094                    taskRect.top = offsetPosition - height;
1095                    taskRect.bottom = offsetPosition;
1096                    break;
1097                case WindowManager.DOCKED_BOTTOM:
1098                    taskRect.top = offsetPosition + mDividerSize;
1099                    taskRect.bottom = offsetPosition + height + mDividerSize;
1100                    break;
1101            }
1102        }
1103    }
1104
1105    /**
1106     * @return for a specified {@code fraction}, this returns an adjusted value that simulates a
1107     *         slowing down parallax effect
1108     */
1109    private static float calculateParallaxDismissingFraction(float fraction, int dockSide) {
1110        float result = SLOWDOWN_INTERPOLATOR.getInterpolation(fraction) / 3.5f;
1111
1112        // Less parallax at the top, just because.
1113        if (dockSide == WindowManager.DOCKED_TOP) {
1114            result /= 2f;
1115        }
1116        return result;
1117    }
1118
1119    private static boolean isDismissing(SnapTarget snapTarget, int position, int dockSide) {
1120        if (dockSide == WindowManager.DOCKED_TOP || dockSide == WindowManager.DOCKED_LEFT) {
1121            return position < snapTarget.position;
1122        } else {
1123            return position > snapTarget.position;
1124        }
1125    }
1126
1127    private int getStackIdForDismissTarget(SnapTarget dismissTarget) {
1128        if ((dismissTarget.flag == SnapTarget.FLAG_DISMISS_START && dockSideTopLeft(mDockSide))
1129                || (dismissTarget.flag == SnapTarget.FLAG_DISMISS_END
1130                        && dockSideBottomRight(mDockSide))) {
1131            return StackId.DOCKED_STACK_ID;
1132        } else {
1133            return StackId.RECENTS_STACK_ID;
1134        }
1135    }
1136
1137    /**
1138     * @return true if and only if {@code dockSide} is top or left
1139     */
1140    private static boolean dockSideTopLeft(int dockSide) {
1141        return dockSide == WindowManager.DOCKED_TOP || dockSide == WindowManager.DOCKED_LEFT;
1142    }
1143
1144    /**
1145     * @return true if and only if {@code dockSide} is bottom or right
1146     */
1147    private static boolean dockSideBottomRight(int dockSide) {
1148        return dockSide == WindowManager.DOCKED_BOTTOM || dockSide == WindowManager.DOCKED_RIGHT;
1149    }
1150
1151    @Override
1152    public void onComputeInternalInsets(InternalInsetsInfo inoutInfo) {
1153        inoutInfo.setTouchableInsets(InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
1154        inoutInfo.touchableRegion.set(mHandle.getLeft(), mHandle.getTop(), mHandle.getRight(),
1155                mHandle.getBottom());
1156        inoutInfo.touchableRegion.op(mBackground.getLeft(), mBackground.getTop(),
1157                mBackground.getRight(), mBackground.getBottom(), Op.UNION);
1158    }
1159
1160    /**
1161     * Checks whether recents will grow when invoked. This happens in multi-window when recents is
1162     * very small. When invoking recents, we shrink the docked stack so recents has more space.
1163     *
1164     * @return the position of the divider when recents grows, or
1165     *         {@link #INVALID_RECENTS_GROW_TARGET} if recents won't grow
1166     */
1167    public int growsRecents() {
1168        boolean result = mGrowRecents
1169                && mDockSide == WindowManager.DOCKED_TOP
1170                && getCurrentPosition() == getSnapAlgorithm().getLastSplitTarget().position;
1171        if (result) {
1172            return getSnapAlgorithm().getMiddleTarget().position;
1173        } else {
1174            return INVALID_RECENTS_GROW_TARGET;
1175        }
1176    }
1177
1178    public final void onBusEvent(RecentsActivityStartingEvent recentsActivityStartingEvent) {
1179        if (mGrowRecents && mDockSide == WindowManager.DOCKED_TOP
1180                && getSnapAlgorithm().getMiddleTarget() != getSnapAlgorithm().getLastSplitTarget()
1181                && getCurrentPosition() == getSnapAlgorithm().getLastSplitTarget().position) {
1182            mState.growAfterRecentsDrawn = true;
1183            startDragging(false /* animate */, false /* touching */);
1184        }
1185    }
1186
1187    public final void onBusEvent(DockedTopTaskEvent event) {
1188        if (event.dragMode == NavigationBarGestureHelper.DRAG_MODE_NONE) {
1189            mState.growAfterRecentsDrawn = false;
1190            mState.animateAfterRecentsDrawn = true;
1191            startDragging(false /* animate */, false /* touching */);
1192        }
1193        updateDockSide();
1194        int position = DockedDividerUtils.calculatePositionForBounds(event.initialRect,
1195                mDockSide, mDividerSize);
1196        mEntranceAnimationRunning = true;
1197
1198        // Insets might not have been fetched yet, so fetch manually if needed.
1199        if (mStableInsets.isEmpty()) {
1200            SystemServicesProxy.getInstance(mContext).getStableInsets(mStableInsets);
1201            mSnapAlgorithm = null;
1202            mMinimizedSnapAlgorithm = null;
1203            initializeSnapAlgorithm();
1204        }
1205
1206        resizeStack(position, mSnapAlgorithm.getMiddleTarget().position,
1207                mSnapAlgorithm.getMiddleTarget());
1208    }
1209
1210    public final void onBusEvent(RecentsDrawnEvent drawnEvent) {
1211        if (mState.animateAfterRecentsDrawn) {
1212            mState.animateAfterRecentsDrawn = false;
1213            updateDockSide();
1214
1215            mHandler.post(() -> {
1216                // Delay switching resizing mode because this might cause jank in recents animation
1217                // that's longer than this animation.
1218                stopDragging(getCurrentPosition(), mSnapAlgorithm.getMiddleTarget(),
1219                        mLongPressEntraceAnimDuration, Interpolators.FAST_OUT_SLOW_IN,
1220                        200 /* endDelay */);
1221            });
1222        }
1223        if (mState.growAfterRecentsDrawn) {
1224            mState.growAfterRecentsDrawn = false;
1225            updateDockSide();
1226            EventBus.getDefault().send(new RecentsGrowingEvent());
1227            stopDragging(getCurrentPosition(), mSnapAlgorithm.getMiddleTarget(), 336,
1228                    Interpolators.FAST_OUT_SLOW_IN);
1229        }
1230    }
1231
1232    public final void onBusEvent(UndockingTaskEvent undockingTaskEvent) {
1233        int dockSide = mWindowManagerProxy.getDockSide();
1234        if (dockSide != WindowManager.DOCKED_INVALID && (mHomeStackResizable
1235                || !mDockedStackMinimized)) {
1236            startDragging(false /* animate */, false /* touching */);
1237            SnapTarget target = dockSideTopLeft(dockSide)
1238                    ? mSnapAlgorithm.getDismissEndTarget()
1239                    : mSnapAlgorithm.getDismissStartTarget();
1240
1241            // Don't start immediately - give a little bit time to settle the drag resize change.
1242            mExitAnimationRunning = true;
1243            mExitStartPosition = getCurrentPosition();
1244            stopDragging(mExitStartPosition, target, 336 /* duration */, 100 /* startDelay */,
1245                    0 /* endDelay */, Interpolators.FAST_OUT_SLOW_IN);
1246        }
1247    }
1248}
1249