DividerView.java revision cdb06caebb5f6f554b2ed8c76963970d8cc0ab54
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 android.animation.Animator;
20import android.animation.AnimatorListenerAdapter;
21import android.animation.ValueAnimator;
22import android.animation.ValueAnimator.AnimatorUpdateListener;
23import android.annotation.Nullable;
24import android.app.ActivityManager.StackId;
25import android.content.Context;
26import android.content.res.Configuration;
27import android.graphics.Rect;
28import android.graphics.Region.Op;
29import android.hardware.display.DisplayManager;
30import android.util.AttributeSet;
31import android.view.Display;
32import android.view.DisplayInfo;
33import android.view.MotionEvent;
34import android.view.PointerIcon;
35import android.view.VelocityTracker;
36import android.view.View;
37import android.view.View.OnTouchListener;
38import android.view.ViewConfiguration;
39import android.view.ViewTreeObserver.InternalInsetsInfo;
40import android.view.ViewTreeObserver.OnComputeInternalInsetsListener;
41import android.view.WindowInsets;
42import android.view.WindowManager;
43import android.view.animation.AnimationUtils;
44import android.view.animation.Interpolator;
45import android.view.animation.PathInterpolator;
46import android.widget.FrameLayout;
47
48import com.android.internal.policy.DividerSnapAlgorithm;
49import com.android.internal.policy.DockedDividerUtils;
50import com.android.systemui.R;
51import com.android.internal.policy.DividerSnapAlgorithm.SnapTarget;
52import com.android.systemui.recents.events.EventBus;
53import com.android.systemui.recents.events.activity.DockingTopTaskEvent;
54import com.android.systemui.recents.events.activity.RecentsActivityStartingEvent;
55import com.android.systemui.recents.events.ui.RecentsDrawnEvent;
56import com.android.systemui.statusbar.FlingAnimationUtils;
57import com.android.systemui.statusbar.phone.NavigationBarGestureHelper;
58
59import static android.view.PointerIcon.STYLE_HORIZONTAL_DOUBLE_ARROW;
60import static android.view.PointerIcon.STYLE_VERTICAL_DOUBLE_ARROW;
61
62/**
63 * Docked stack divider.
64 */
65public class DividerView extends FrameLayout implements OnTouchListener,
66        OnComputeInternalInsetsListener {
67
68    static final long TOUCH_ANIMATION_DURATION = 150;
69    static final long TOUCH_RELEASE_ANIMATION_DURATION = 200;
70    static final Interpolator TOUCH_RESPONSE_INTERPOLATOR =
71            new PathInterpolator(0.3f, 0f, 0.1f, 1f);
72
73    private static final String TAG = "DividerView";
74
75    private static final int TASK_POSITION_SAME = Integer.MAX_VALUE;
76    private static final float DIM_START_FRACTION = 0.5f;
77    private static final float DIM_DAMP_FACTOR = 1.7f;
78
79    private static final PathInterpolator SLOWDOWN_INTERPOLATOR =
80            new PathInterpolator(0.5f, 1f, 0.5f, 1f);
81
82    private DividerHandleView mHandle;
83    private View mBackground;
84    private int mStartX;
85    private int mStartY;
86    private int mStartPosition;
87    private int mDockSide;
88    private final int[] mTempInt2 = new int[2];
89    private boolean mMoving;
90    private int mTouchSlop;
91
92    private int mDividerInsets;
93    private int mDisplayWidth;
94    private int mDisplayHeight;
95    private int mDividerWindowWidth;
96    private int mDividerSize;
97    private int mTouchElevation;
98
99    private final Rect mDockedRect = new Rect();
100    private final Rect mDockedTaskRect = new Rect();
101    private final Rect mOtherTaskRect = new Rect();
102    private final Rect mOtherRect = new Rect();
103    private final Rect mDockedInsetRect = new Rect();
104    private final Rect mOtherInsetRect = new Rect();
105    private final Rect mLastResizeRect = new Rect();
106    private final WindowManagerProxy mWindowManagerProxy = WindowManagerProxy.getInstance();
107    private Interpolator mFastOutSlowInInterpolator;
108    private DividerWindowManager mWindowManager;
109    private VelocityTracker mVelocityTracker;
110    private FlingAnimationUtils mFlingAnimationUtils;
111    private DividerSnapAlgorithm mSnapAlgorithm;
112    private final Rect mStableInsets = new Rect();
113
114    private boolean mAnimateAfterRecentsDrawn;
115    private boolean mGrowAfterRecentsDrawn;
116    private boolean mGrowRecents;
117
118    public DividerView(Context context) {
119        super(context);
120    }
121
122    public DividerView(Context context, @Nullable AttributeSet attrs) {
123        super(context, attrs);
124    }
125
126    public DividerView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
127        super(context, attrs, defStyleAttr);
128    }
129
130    public DividerView(Context context, @Nullable AttributeSet attrs, int defStyleAttr,
131            int defStyleRes) {
132        super(context, attrs, defStyleAttr, defStyleRes);
133    }
134
135    @Override
136    protected void onFinishInflate() {
137        super.onFinishInflate();
138        mHandle = (DividerHandleView) findViewById(R.id.docked_divider_handle);
139        mBackground = findViewById(R.id.docked_divider_background);
140        mHandle.setOnTouchListener(this);
141        mDividerWindowWidth = getResources().getDimensionPixelSize(
142                com.android.internal.R.dimen.docked_stack_divider_thickness);
143        mDividerInsets = getResources().getDimensionPixelSize(
144                com.android.internal.R.dimen.docked_stack_divider_insets);
145        mDividerSize = mDividerWindowWidth - 2 * mDividerInsets;
146        mTouchElevation = getResources().getDimensionPixelSize(
147                R.dimen.docked_stack_divider_lift_elevation);
148        mGrowRecents = getResources().getBoolean(R.bool.recents_grow_in_multiwindow);
149        mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(getContext(),
150                android.R.interpolator.fast_out_slow_in);
151        mTouchSlop = ViewConfiguration.get(mContext).getScaledTouchSlop();
152        mFlingAnimationUtils = new FlingAnimationUtils(getContext(), 0.3f);
153        updateDisplayInfo();
154        boolean landscape = getResources().getConfiguration().orientation
155                == Configuration.ORIENTATION_LANDSCAPE;
156        mHandle.setPointerIcon(PointerIcon.getSystemIcon(getContext(),
157                landscape ? STYLE_HORIZONTAL_DOUBLE_ARROW : STYLE_VERTICAL_DOUBLE_ARROW));
158        getViewTreeObserver().addOnComputeInternalInsetsListener(this);
159    }
160
161    @Override
162    protected void onAttachedToWindow() {
163        super.onAttachedToWindow();
164        EventBus.getDefault().register(this);
165    }
166
167    @Override
168    protected void onDetachedFromWindow() {
169        super.onDetachedFromWindow();
170        EventBus.getDefault().unregister(this);
171    }
172
173    @Override
174    public WindowInsets onApplyWindowInsets(WindowInsets insets) {
175        mStableInsets.set(insets.getStableInsetLeft(), insets.getStableInsetTop(),
176                insets.getStableInsetRight(), insets.getStableInsetBottom());
177        return super.onApplyWindowInsets(insets);
178    }
179
180    public void setWindowManager(DividerWindowManager windowManager) {
181        mWindowManager = windowManager;
182    }
183
184    public WindowManagerProxy getWindowManagerProxy() {
185        return mWindowManagerProxy;
186    }
187
188    public boolean startDragging(boolean animate, boolean touching) {
189        if (touching) {
190            mHandle.setTouching(true, animate);
191        }
192        mDockSide = mWindowManagerProxy.getDockSide();
193        getSnapAlgorithm();
194        if (mDockSide != WindowManager.DOCKED_INVALID) {
195            mWindowManagerProxy.setResizing(true);
196            mWindowManager.setSlippery(false);
197            if (touching) {
198                liftBackground();
199            }
200            return true;
201        } else {
202            return false;
203        }
204    }
205
206    public void stopDragging(int position, float velocity, boolean avoidDismissStart) {
207        mHandle.setTouching(false, true /* animate */);
208        fling(position, velocity, avoidDismissStart);
209        mWindowManager.setSlippery(true);
210        releaseBackground();
211    }
212
213    public void stopDragging(int position, SnapTarget target, long duration,
214            Interpolator interpolator) {
215        mHandle.setTouching(false, true /* animate */);
216        flingTo(position, target, duration, interpolator);
217        mWindowManager.setSlippery(true);
218        releaseBackground();
219    }
220
221    public DividerSnapAlgorithm getSnapAlgorithm() {
222        if (mSnapAlgorithm == null) {
223            mSnapAlgorithm = new DividerSnapAlgorithm(getContext().getResources(), mDisplayWidth,
224                    mDisplayHeight, mDividerSize, isHorizontalDivision(), mStableInsets);
225        }
226        return mSnapAlgorithm;
227    }
228
229    public int getCurrentPosition() {
230        getLocationOnScreen(mTempInt2);
231        if (isHorizontalDivision()) {
232            return mTempInt2[1] + mDividerInsets;
233        } else {
234            return mTempInt2[0] + mDividerInsets;
235        }
236    }
237
238    @Override
239    public boolean onTouch(View v, MotionEvent event) {
240        convertToScreenCoordinates(event);
241        final int action = event.getAction() & MotionEvent.ACTION_MASK;
242        switch (action) {
243            case MotionEvent.ACTION_DOWN:
244                mVelocityTracker = VelocityTracker.obtain();
245                mVelocityTracker.addMovement(event);
246                mStartX = (int) event.getX();
247                mStartY = (int) event.getY();
248                boolean result = startDragging(true /* animate */, true /* touching */);
249                mStartPosition = getCurrentPosition();
250                mMoving = false;
251                return result;
252            case MotionEvent.ACTION_MOVE:
253                mVelocityTracker.addMovement(event);
254                int x = (int) event.getX();
255                int y = (int) event.getY();
256                boolean exceededTouchSlop =
257                        isHorizontalDivision() && Math.abs(y - mStartY) > mTouchSlop
258                                || (!isHorizontalDivision() && Math.abs(x - mStartX) > mTouchSlop);
259                if (!mMoving && exceededTouchSlop) {
260                    mStartX = x;
261                    mStartY = y;
262                    mMoving = true;
263                }
264                if (mMoving && mDockSide != WindowManager.DOCKED_INVALID) {
265                    int position = calculatePosition(x, y);
266                    SnapTarget snapTarget = mSnapAlgorithm.calculateSnapTarget(position,
267                            0 /* velocity */, false /* hardDismiss */);
268                    resizeStack(calculatePosition(x, y), snapTarget.position, snapTarget);
269                }
270                break;
271            case MotionEvent.ACTION_UP:
272            case MotionEvent.ACTION_CANCEL:
273                mVelocityTracker.addMovement(event);
274
275                x = (int) event.getRawX();
276                y = (int) event.getRawY();
277
278                mVelocityTracker.computeCurrentVelocity(1000);
279                int position = calculatePosition(x, y);
280                stopDragging(position, isHorizontalDivision() ? mVelocityTracker.getYVelocity()
281                        : mVelocityTracker.getXVelocity(), false /* avoidDismissStart */);
282                mMoving = false;
283                break;
284        }
285        return true;
286    }
287
288    private void convertToScreenCoordinates(MotionEvent event) {
289        event.setLocation(event.getRawX(), event.getRawY());
290    }
291
292    private void fling(int position, float velocity, boolean avoidDismissStart) {
293        SnapTarget snapTarget = mSnapAlgorithm.calculateSnapTarget(position, velocity);
294        if (avoidDismissStart && snapTarget == mSnapAlgorithm.getDismissStartTarget()) {
295            snapTarget = mSnapAlgorithm.getFirstSplitTarget();
296        }
297        ValueAnimator anim = getFlingAnimator(position, snapTarget);
298        mFlingAnimationUtils.apply(anim, position, snapTarget.position, velocity);
299        anim.start();
300    }
301
302    private void flingTo(int position, SnapTarget target, long duration,
303            Interpolator interpolator) {
304        ValueAnimator anim = getFlingAnimator(position, target);
305        anim.setDuration(duration);
306        anim.setInterpolator(interpolator);
307        anim.start();
308    }
309
310    private ValueAnimator getFlingAnimator(int position, final SnapTarget snapTarget) {
311        ValueAnimator anim = ValueAnimator.ofInt(position, snapTarget.position);
312        anim.addUpdateListener(new AnimatorUpdateListener() {
313            @Override
314            public void onAnimationUpdate(ValueAnimator animation) {
315                resizeStack((Integer) animation.getAnimatedValue(),
316                        animation.getAnimatedFraction() == 1f
317                                ? TASK_POSITION_SAME
318                                : snapTarget.position, snapTarget);
319            }
320        });
321        anim.addListener(new AnimatorListenerAdapter() {
322            @Override
323            public void onAnimationEnd(Animator animation) {
324                commitSnapFlags(snapTarget);
325                mWindowManagerProxy.setResizing(false);
326                mDockSide = WindowManager.DOCKED_INVALID;
327            }
328        });
329        return anim;
330    }
331
332    private void commitSnapFlags(SnapTarget target) {
333        if (target.flag == SnapTarget.FLAG_NONE) {
334            return;
335        }
336        boolean dismissOrMaximize;
337        if (target.flag == SnapTarget.FLAG_DISMISS_START) {
338            dismissOrMaximize = mDockSide == WindowManager.DOCKED_LEFT
339                    || mDockSide == WindowManager.DOCKED_TOP;
340        } else {
341            dismissOrMaximize = mDockSide == WindowManager.DOCKED_RIGHT
342                    || mDockSide == WindowManager.DOCKED_BOTTOM;
343        }
344        if (dismissOrMaximize) {
345            mWindowManagerProxy.dismissDockedStack();
346        } else {
347            mWindowManagerProxy.maximizeDockedStack();
348        }
349        mWindowManagerProxy.setResizeDimLayer(false, -1, 0f);
350    }
351
352    private void liftBackground() {
353        if (isHorizontalDivision()) {
354            mBackground.animate().scaleY(1.4f);
355        } else {
356            mBackground.animate().scaleX(1.4f);
357        }
358        mBackground.animate()
359                .setInterpolator(TOUCH_RESPONSE_INTERPOLATOR)
360                .setDuration(TOUCH_ANIMATION_DURATION)
361                .translationZ(mTouchElevation)
362                .start();
363
364        // Lift handle as well so it doesn't get behind the background, even though it doesn't
365        // cast shadow.
366        mHandle.animate()
367                .setInterpolator(TOUCH_RESPONSE_INTERPOLATOR)
368                .setDuration(TOUCH_ANIMATION_DURATION)
369                .translationZ(mTouchElevation)
370                .start();
371    }
372
373    private void releaseBackground() {
374        mBackground.animate()
375                .setInterpolator(mFastOutSlowInInterpolator)
376                .setDuration(TOUCH_RELEASE_ANIMATION_DURATION)
377                .translationZ(0)
378                .scaleX(1f)
379                .scaleY(1f)
380                .start();
381        mHandle.animate()
382                .setInterpolator(mFastOutSlowInInterpolator)
383                .setDuration(TOUCH_RELEASE_ANIMATION_DURATION)
384                .translationZ(0)
385                .start();
386    }
387
388    @Override
389    protected void onConfigurationChanged(Configuration newConfig) {
390        super.onConfigurationChanged(newConfig);
391        updateDisplayInfo();
392    }
393
394    private void updateDisplayInfo() {
395        final DisplayManager displayManager =
396                (DisplayManager) mContext.getSystemService(Context.DISPLAY_SERVICE);
397        Display display = displayManager.getDisplay(Display.DEFAULT_DISPLAY);
398        final DisplayInfo info = new DisplayInfo();
399        display.getDisplayInfo(info);
400        mDisplayWidth = info.logicalWidth;
401        mDisplayHeight = info.logicalHeight;
402        mSnapAlgorithm = null;
403    }
404
405    private int calculatePosition(int touchX, int touchY) {
406        return isHorizontalDivision() ? calculateYPosition(touchY) : calculateXPosition(touchX);
407    }
408
409    public boolean isHorizontalDivision() {
410        return getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT;
411    }
412
413    private int calculateXPosition(int touchX) {
414        return mStartPosition + touchX - mStartX;
415    }
416
417    private int calculateYPosition(int touchY) {
418        return mStartPosition + touchY - mStartY;
419    }
420
421    private void alignTopLeft(Rect containingRect, Rect rect) {
422        int width = rect.width();
423        int height = rect.height();
424        rect.set(containingRect.left, containingRect.top,
425                containingRect.left + width, containingRect.top + height);
426    }
427
428    private void alignBottomRight(Rect containingRect, Rect rect) {
429        int width = rect.width();
430        int height = rect.height();
431        rect.set(containingRect.right - width, containingRect.bottom - height,
432                containingRect.right, containingRect.bottom);
433    }
434
435    public void calculateBoundsForPosition(int position, int dockSide, Rect outRect) {
436        DockedDividerUtils.calculateBoundsForPosition(position, dockSide, outRect, mDisplayWidth,
437                mDisplayHeight, mDividerSize);
438    }
439
440    public void resizeStack(int position, int taskPosition, SnapTarget taskSnapTarget) {
441        calculateBoundsForPosition(position, mDockSide, mDockedRect);
442
443        if (mDockedRect.equals(mLastResizeRect)) {
444            return;
445        }
446
447        // Make sure shadows are updated
448        mBackground.invalidate();
449
450        mLastResizeRect.set(mDockedRect);
451        if (taskPosition != TASK_POSITION_SAME) {
452            calculateBoundsForPosition(position, DockedDividerUtils.invertDockSide(mDockSide),
453                    mOtherRect);
454            int dockSideInverted = DockedDividerUtils.invertDockSide(mDockSide);
455            int taskPositionDocked =
456                    restrictDismissingTaskPosition(taskPosition, mDockSide, taskSnapTarget);
457            int taskPositionOther =
458                    restrictDismissingTaskPosition(taskPosition, dockSideInverted, taskSnapTarget);
459            calculateBoundsForPosition(taskPositionDocked, mDockSide, mDockedTaskRect);
460            calculateBoundsForPosition(taskPositionOther, dockSideInverted, mOtherTaskRect);
461            alignTopLeft(mDockedRect, mDockedTaskRect);
462            alignTopLeft(mOtherRect, mOtherTaskRect);
463            mDockedInsetRect.set(mDockedTaskRect);
464            mOtherInsetRect.set(mOtherTaskRect);
465            if (dockSideTopLeft(mDockSide)) {
466                alignTopLeft(mDockedRect, mDockedInsetRect);
467                alignBottomRight(mOtherRect, mOtherInsetRect);
468            } else {
469                alignBottomRight(mDockedRect, mDockedInsetRect);
470                alignTopLeft(mOtherRect, mOtherInsetRect);
471            }
472            applyDismissingParallax(mDockedTaskRect, mDockSide, taskSnapTarget, position,
473                    taskPositionDocked);
474            applyDismissingParallax(mOtherTaskRect, dockSideInverted, taskSnapTarget, position,
475                    taskPositionOther);
476            mWindowManagerProxy.resizeDockedStack(mDockedRect, mDockedTaskRect, mDockedInsetRect,
477                    mOtherTaskRect, mOtherInsetRect);
478        } else {
479            mWindowManagerProxy.resizeDockedStack(mDockedRect, null, null, null, null);
480        }
481        float fraction = mSnapAlgorithm.calculateDismissingFraction(position);
482        fraction = Math.max(0,
483                Math.min((fraction / DIM_START_FRACTION - 1f) / DIM_DAMP_FACTOR, 1f));
484        mWindowManagerProxy.setResizeDimLayer(fraction != 0f,
485                getStackIdForDismissTarget(mSnapAlgorithm.getClosestDismissTarget(position)),
486                fraction);
487    }
488
489    /**
490     * When the snap target is dismissing one side, make sure that the dismissing side doesn't get
491     * 0 size.
492     */
493    private int restrictDismissingTaskPosition(int taskPosition, int dockSide,
494            SnapTarget snapTarget) {
495        if (snapTarget.flag == SnapTarget.FLAG_DISMISS_START && dockSideTopLeft(dockSide)) {
496            return mSnapAlgorithm.getFirstSplitTarget().position;
497        } else if (snapTarget.flag == SnapTarget.FLAG_DISMISS_END
498                && dockSideBottomRight(dockSide)) {
499            return mSnapAlgorithm.getLastSplitTarget().position;
500        } else {
501            return taskPosition;
502        }
503    }
504
505    /**
506     * Applies a parallax to the task when dismissing.
507     */
508    private void applyDismissingParallax(Rect taskRect, int dockSide, SnapTarget snapTarget,
509            int position, int taskPosition) {
510        float fraction = Math.min(1, Math.max(0,
511                mSnapAlgorithm.calculateDismissingFraction(position)));
512        SnapTarget dismissTarget = null;
513        SnapTarget splitTarget = null;
514        if ((snapTarget.flag == SnapTarget.FLAG_DISMISS_START
515                || snapTarget == mSnapAlgorithm.getFirstSplitTarget())
516                && dockSideTopLeft(dockSide)) {
517            dismissTarget = mSnapAlgorithm.getDismissStartTarget();
518            splitTarget = mSnapAlgorithm.getFirstSplitTarget();
519        } else if ((snapTarget.flag == SnapTarget.FLAG_DISMISS_END
520                || snapTarget == mSnapAlgorithm.getLastSplitTarget())
521                && dockSideBottomRight(dockSide)) {
522            dismissTarget = mSnapAlgorithm.getDismissEndTarget();
523            splitTarget = mSnapAlgorithm.getLastSplitTarget();
524        }
525        if (dismissTarget != null && fraction > 0f
526                && isDismissing(splitTarget, position, dockSide)) {
527            fraction = calculateParallaxDismissingFraction(fraction);
528            int offsetPosition = (int) (taskPosition +
529                    fraction * (dismissTarget.position - splitTarget.position));
530            int width = taskRect.width();
531            int height = taskRect.height();
532            switch (dockSide) {
533                case WindowManager.DOCKED_LEFT:
534                    taskRect.left = offsetPosition - width;
535                    taskRect.right = offsetPosition;
536                    break;
537                case WindowManager.DOCKED_RIGHT:
538                    taskRect.left = offsetPosition + mDividerSize;
539                    taskRect.right = offsetPosition + width + mDividerSize;
540                    break;
541                case WindowManager.DOCKED_TOP:
542                    taskRect.top = offsetPosition - height;
543                    taskRect.bottom = offsetPosition;
544                    break;
545                case WindowManager.DOCKED_BOTTOM:
546                    taskRect.top = offsetPosition + mDividerSize;
547                    taskRect.bottom = offsetPosition + height + mDividerSize;
548                    break;
549            }
550        }
551    }
552
553    /**
554     * @return for a specified {@code fraction}, this returns an adjusted value that simulates a
555     *         slowing down parallax effect
556     */
557    private static float calculateParallaxDismissingFraction(float fraction) {
558        return SLOWDOWN_INTERPOLATOR.getInterpolation(fraction) / 3.5f;
559    }
560
561    private static boolean isDismissing(SnapTarget snapTarget, int position, int dockSide) {
562        if (dockSide == WindowManager.DOCKED_TOP || dockSide == WindowManager.DOCKED_LEFT) {
563            return position < snapTarget.position;
564        } else {
565            return position > snapTarget.position;
566        }
567    }
568
569    private int getStackIdForDismissTarget(SnapTarget dismissTarget) {
570        if (dismissTarget.flag == SnapTarget.FLAG_DISMISS_START &&
571                (mDockSide == WindowManager.DOCKED_LEFT || mDockSide == WindowManager.DOCKED_TOP)) {
572            return StackId.DOCKED_STACK_ID;
573        } else {
574            return StackId.FULLSCREEN_WORKSPACE_STACK_ID;
575        }
576    }
577
578    /**
579     * @return true if and only if {@code dockSide} is top or left
580     */
581    private static boolean dockSideTopLeft(int dockSide) {
582        return dockSide == WindowManager.DOCKED_TOP || dockSide == WindowManager.DOCKED_LEFT;
583    }
584
585    /**
586     * @return true if and only if {@code dockSide} is bottom or right
587     */
588    private static boolean dockSideBottomRight(int dockSide) {
589        return dockSide == WindowManager.DOCKED_BOTTOM || dockSide == WindowManager.DOCKED_RIGHT;
590    }
591
592    @Override
593    public void onComputeInternalInsets(InternalInsetsInfo inoutInfo) {
594        inoutInfo.setTouchableInsets(InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
595        inoutInfo.touchableRegion.set(mHandle.getLeft(), mHandle.getTop(), mHandle.getRight(),
596                mHandle.getBottom());
597        inoutInfo.touchableRegion.op(mBackground.getLeft(), mBackground.getTop(),
598                mBackground.getRight(), mBackground.getBottom(), Op.UNION);
599    }
600
601    public final void onBusEvent(RecentsActivityStartingEvent recentsActivityStartingEvent) {
602        if (mGrowRecents && getWindowManagerProxy().getDockSide() == WindowManager.DOCKED_TOP
603                && getCurrentPosition() == getSnapAlgorithm().getLastSplitTarget().position) {
604            mGrowAfterRecentsDrawn = true;
605            startDragging(false /* animate */, false /* touching */);
606        }
607    }
608
609    public final void onBusEvent(DockingTopTaskEvent dockingEvent) {
610        if (dockingEvent.dragMode == NavigationBarGestureHelper.DRAG_MODE_NONE) {
611            mGrowAfterRecentsDrawn = false;
612            mAnimateAfterRecentsDrawn = true;
613            startDragging(false /* animate */, false /* touching */);
614        }
615    }
616
617    public final void onBusEvent(RecentsDrawnEvent drawnEvent) {
618        if (mAnimateAfterRecentsDrawn) {
619            mAnimateAfterRecentsDrawn = false;
620            stopDragging(getCurrentPosition(), mSnapAlgorithm.getMiddleTarget(), 250,
621                    TOUCH_RESPONSE_INTERPOLATOR);
622        }
623        if (mGrowAfterRecentsDrawn) {
624            mGrowAfterRecentsDrawn = false;
625            stopDragging(getCurrentPosition(), mSnapAlgorithm.getMiddleTarget(), 250,
626                    TOUCH_RESPONSE_INTERPOLATOR);
627        }
628    }
629}
630