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