DividerView.java revision fc34b73aeff4a8bf84b86e015fa89f484e73ec09
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.STYLE_HORIZONTAL_DOUBLE_ARROW;
20import static android.view.PointerIcon.STYLE_VERTICAL_DOUBLE_ARROW;
21
22import android.animation.Animator;
23import android.animation.AnimatorListenerAdapter;
24import android.animation.ValueAnimator;
25import android.animation.ValueAnimator.AnimatorUpdateListener;
26import android.annotation.Nullable;
27import android.app.ActivityManager.StackId;
28import android.content.Context;
29import android.content.res.Configuration;
30import android.graphics.Rect;
31import android.graphics.Region.Op;
32import android.hardware.display.DisplayManager;
33import android.os.Bundle;
34import android.util.AttributeSet;
35import android.view.Display;
36import android.view.DisplayInfo;
37import android.view.GestureDetector;
38import android.view.GestureDetector.OnDoubleTapListener;
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.ViewTreeObserver.OnGlobalLayoutListener;
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.policy.DividerSnapAlgorithm;
58import com.android.internal.policy.DividerSnapAlgorithm.SnapTarget;
59import com.android.internal.policy.DockedDividerUtils;
60import com.android.systemui.Interpolators;
61import com.android.systemui.R;
62import com.android.systemui.recents.Recents;
63import com.android.systemui.recents.events.EventBus;
64import com.android.systemui.recents.events.activity.DockedTopTaskEvent;
65import com.android.systemui.recents.events.activity.RecentsActivityStartingEvent;
66import com.android.systemui.recents.events.activity.UndockingTaskEvent;
67import com.android.systemui.recents.events.ui.RecentsDrawnEvent;
68import com.android.systemui.recents.misc.SystemServicesProxy;
69import com.android.systemui.statusbar.FlingAnimationUtils;
70import com.android.systemui.statusbar.phone.NavigationBarGestureHelper;
71
72/**
73 * Docked stack divider.
74 */
75public class DividerView extends FrameLayout implements OnTouchListener,
76        OnComputeInternalInsetsListener {
77
78    static final long TOUCH_ANIMATION_DURATION = 150;
79    static final long TOUCH_RELEASE_ANIMATION_DURATION = 200;
80
81    private static final String TAG = "DividerView";
82
83    private static final int TASK_POSITION_SAME = Integer.MAX_VALUE;
84
85    /**
86     * How much the background gets scaled when we are in the minimized dock state.
87     */
88    private static final float MINIMIZE_DOCK_SCALE = 0.375f;
89
90    private static final PathInterpolator SLOWDOWN_INTERPOLATOR =
91            new PathInterpolator(0.5f, 1f, 0.5f, 1f);
92    private static final PathInterpolator DIM_INTERPOLATOR =
93            new PathInterpolator(.23f, .87f, .52f, -0.11f);
94
95    private DividerHandleView mHandle;
96    private View mBackground;
97    private int mStartX;
98    private int mStartY;
99    private int mStartPosition;
100    private int mDockSide;
101    private final int[] mTempInt2 = new int[2];
102    private boolean mMoving;
103    private int mTouchSlop;
104    private boolean mBackgroundLifted;
105
106    private int mDividerInsets;
107    private int mDisplayWidth;
108    private int mDisplayHeight;
109    private int mDividerWindowWidth;
110    private int mDividerSize;
111    private int mTouchElevation;
112
113    private final Rect mDockedRect = new Rect();
114    private final Rect mDockedTaskRect = new Rect();
115    private final Rect mOtherTaskRect = new Rect();
116    private final Rect mOtherRect = new Rect();
117    private final Rect mDockedInsetRect = new Rect();
118    private final Rect mOtherInsetRect = new Rect();
119    private final Rect mLastResizeRect = new Rect();
120    private final Rect mDisplayRect = new Rect();
121    private final WindowManagerProxy mWindowManagerProxy = WindowManagerProxy.getInstance();
122    private DividerWindowManager mWindowManager;
123    private VelocityTracker mVelocityTracker;
124    private FlingAnimationUtils mFlingAnimationUtils;
125    private DividerSnapAlgorithm mSnapAlgorithm;
126    private final Rect mStableInsets = new Rect();
127
128    private boolean mAnimateAfterRecentsDrawn;
129    private boolean mGrowAfterRecentsDrawn;
130    private boolean mGrowRecents;
131    private ValueAnimator mCurrentAnimator;
132    private boolean mEntranceAnimationRunning;
133    private GestureDetector mGestureDetector;
134
135    private final AccessibilityDelegate mHandleDelegate = new AccessibilityDelegate() {
136        @Override
137        public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
138            super.onInitializeAccessibilityNodeInfo(host, info);
139            if (isHorizontalDivision()) {
140                info.addAction(new AccessibilityAction(R.id.action_move_up,
141                        mContext.getString(R.string.accessibility_action_divider_move_up)));
142                info.addAction(new AccessibilityAction(R.id.action_move_down,
143                        mContext.getString(R.string.accessibility_action_divider_move_down)));
144            } else {
145                info.addAction(new AccessibilityAction(R.id.action_move_left,
146                        mContext.getString(R.string.accessibility_action_divider_move_left)));
147                info.addAction(new AccessibilityAction(R.id.action_move_right,
148                        mContext.getString(R.string.accessibility_action_divider_move_right)));
149            }
150        }
151
152        @Override
153        public boolean performAccessibilityAction(View host, int action, Bundle args) {
154            if (action == R.id.action_move_up || action == R.id.action_move_down
155                    || action == R.id.action_move_left || action == R.id.action_move_right) {
156                int position = getCurrentPosition();
157                SnapTarget currentTarget = mSnapAlgorithm.calculateSnapTarget(
158                        position, 0 /* velocity */);
159                SnapTarget nextTarget =
160                        action == R.id.action_move_up || action == R.id.action_move_left
161                                ? mSnapAlgorithm.getPreviousTarget(currentTarget)
162                                : mSnapAlgorithm.getNextTarget(currentTarget);
163                startDragging(true /* animate */, false /* touching */);
164                stopDragging(getCurrentPosition(), nextTarget, 250, Interpolators.FAST_OUT_SLOW_IN);
165                return true;
166            }
167            return super.performAccessibilityAction(host, action, args);
168        }
169    };
170
171    public DividerView(Context context) {
172        super(context);
173    }
174
175    public DividerView(Context context, @Nullable AttributeSet attrs) {
176        super(context, attrs);
177    }
178
179    public DividerView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
180        super(context, attrs, defStyleAttr);
181    }
182
183    public DividerView(Context context, @Nullable AttributeSet attrs, int defStyleAttr,
184            int defStyleRes) {
185        super(context, attrs, defStyleAttr, defStyleRes);
186    }
187
188    @Override
189    protected void onFinishInflate() {
190        super.onFinishInflate();
191        mHandle = (DividerHandleView) findViewById(R.id.docked_divider_handle);
192        mBackground = findViewById(R.id.docked_divider_background);
193        mHandle.setOnTouchListener(this);
194        mDividerWindowWidth = getResources().getDimensionPixelSize(
195                com.android.internal.R.dimen.docked_stack_divider_thickness);
196        mDividerInsets = getResources().getDimensionPixelSize(
197                com.android.internal.R.dimen.docked_stack_divider_insets);
198        mDividerSize = mDividerWindowWidth - 2 * mDividerInsets;
199        mTouchElevation = getResources().getDimensionPixelSize(
200                R.dimen.docked_stack_divider_lift_elevation);
201        mGrowRecents = getResources().getBoolean(R.bool.recents_grow_in_multiwindow);
202        mTouchSlop = ViewConfiguration.get(mContext).getScaledTouchSlop();
203        mFlingAnimationUtils = new FlingAnimationUtils(getContext(), 0.3f);
204        updateDisplayInfo();
205        boolean landscape = getResources().getConfiguration().orientation
206                == Configuration.ORIENTATION_LANDSCAPE;
207        mHandle.setPointerIcon(PointerIcon.getSystemIcon(getContext(),
208                landscape ? STYLE_HORIZONTAL_DOUBLE_ARROW : STYLE_VERTICAL_DOUBLE_ARROW));
209        getViewTreeObserver().addOnComputeInternalInsetsListener(this);
210        mHandle.setAccessibilityDelegate(mHandleDelegate);
211        mGestureDetector = new GestureDetector(mContext, new SimpleOnGestureListener() {
212            @Override
213            public boolean onSingleTapUp(MotionEvent e) {
214                updateDockSide();
215                SystemServicesProxy ssp = Recents.getSystemServices();
216                if (mDockSide != WindowManager.DOCKED_INVALID
217                        && !ssp.isRecentsTopMost(ssp.getTopMostTask(), null /* isTopHome */)) {
218                    mWindowManagerProxy.swapTasks();
219                    return true;
220                }
221                return false;
222            }
223        });
224    }
225
226    @Override
227    protected void onAttachedToWindow() {
228        super.onAttachedToWindow();
229        EventBus.getDefault().register(this);
230        getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
231
232            @Override
233            public void onGlobalLayout() {
234                getViewTreeObserver().removeOnGlobalLayoutListener(this);
235                mWindowManagerProxy.setTouchRegion(new Rect(mHandle.getLeft(), mHandle.getTop(),
236                        mHandle.getLeft() + mHandle.getWidth(),
237                        mHandle.getTop() + mHandle.getHeight()));
238            }
239        });
240    }
241
242    @Override
243    protected void onDetachedFromWindow() {
244        super.onDetachedFromWindow();
245        EventBus.getDefault().unregister(this);
246    }
247
248    @Override
249    public WindowInsets onApplyWindowInsets(WindowInsets insets) {
250        if (mStableInsets.left != insets.getStableInsetLeft()
251                || mStableInsets.top != insets.getStableInsetTop()
252                || mStableInsets.right != insets.getStableInsetRight()
253                || mStableInsets.bottom != insets.getStableInsetBottom()) {
254            mStableInsets.set(insets.getStableInsetLeft(), insets.getStableInsetTop(),
255                    insets.getStableInsetRight(), insets.getStableInsetBottom());
256            if (mSnapAlgorithm != null) {
257                mSnapAlgorithm = null;
258                initializeSnapAlgorithm();
259            }
260        }
261        return super.onApplyWindowInsets(insets);
262    }
263
264    public void setWindowManager(DividerWindowManager windowManager) {
265        mWindowManager = windowManager;
266    }
267
268    public WindowManagerProxy getWindowManagerProxy() {
269        return mWindowManagerProxy;
270    }
271
272    public boolean startDragging(boolean animate, boolean touching) {
273        cancelFlingAnimation();
274        if (touching) {
275            mHandle.setTouching(true, animate);
276        }
277        mDockSide = mWindowManagerProxy.getDockSide();
278        initializeSnapAlgorithm();
279        mWindowManagerProxy.setResizing(true);
280        if (touching) {
281            mWindowManager.setSlippery(false);
282            liftBackground();
283        }
284        return mDockSide != WindowManager.DOCKED_INVALID;
285    }
286
287    public void stopDragging(int position, float velocity, boolean avoidDismissStart) {
288        mHandle.setTouching(false, true /* animate */);
289        fling(position, velocity, avoidDismissStart);
290        mWindowManager.setSlippery(true);
291        releaseBackground();
292    }
293
294    public void stopDragging(int position, SnapTarget target, long duration,
295            Interpolator interpolator) {
296        stopDragging(position, target, duration, 0 /* startDelay*/, interpolator);
297    }
298
299    public void stopDragging(int position, SnapTarget target, long duration, long startDelay,
300            Interpolator interpolator) {
301        mHandle.setTouching(false, true /* animate */);
302        flingTo(position, target, duration, startDelay, interpolator);
303        mWindowManager.setSlippery(true);
304        releaseBackground();
305    }
306
307    private void stopDragging() {
308        mHandle.setTouching(false, true /* animate */);
309        mWindowManager.setSlippery(true);
310        releaseBackground();
311    }
312
313    private void updateDockSide() {
314        mDockSide = mWindowManagerProxy.getDockSide();
315    }
316
317    private void initializeSnapAlgorithm() {
318        if (mSnapAlgorithm == null) {
319            mSnapAlgorithm = new DividerSnapAlgorithm(getContext().getResources(), mDisplayWidth,
320                    mDisplayHeight, mDividerSize, isHorizontalDivision(), mStableInsets);
321        }
322    }
323
324    public DividerSnapAlgorithm getSnapAlgorithm() {
325        initializeSnapAlgorithm();
326        return mSnapAlgorithm;
327    }
328
329    public int getCurrentPosition() {
330        getLocationOnScreen(mTempInt2);
331        if (isHorizontalDivision()) {
332            return mTempInt2[1] + mDividerInsets;
333        } else {
334            return mTempInt2[0] + mDividerInsets;
335        }
336    }
337
338    @Override
339    public boolean onTouch(View v, MotionEvent event) {
340        convertToScreenCoordinates(event);
341        mGestureDetector.onTouchEvent(event);
342        final int action = event.getAction() & MotionEvent.ACTION_MASK;
343        switch (action) {
344            case MotionEvent.ACTION_DOWN:
345                mVelocityTracker = VelocityTracker.obtain();
346                mVelocityTracker.addMovement(event);
347                mStartX = (int) event.getX();
348                mStartY = (int) event.getY();
349                boolean result = startDragging(true /* animate */, true /* touching */);
350                if (!result) {
351
352                    // Weren't able to start dragging successfully, so cancel it again.
353                    stopDragging();
354                }
355                mStartPosition = getCurrentPosition();
356                mMoving = false;
357                return result;
358            case MotionEvent.ACTION_MOVE:
359                mVelocityTracker.addMovement(event);
360                int x = (int) event.getX();
361                int y = (int) event.getY();
362                boolean exceededTouchSlop =
363                        isHorizontalDivision() && Math.abs(y - mStartY) > mTouchSlop
364                                || (!isHorizontalDivision() && Math.abs(x - mStartX) > mTouchSlop);
365                if (!mMoving && exceededTouchSlop) {
366                    mStartX = x;
367                    mStartY = y;
368                    mMoving = true;
369                }
370                if (mMoving && mDockSide != WindowManager.DOCKED_INVALID) {
371                    SnapTarget snapTarget = mSnapAlgorithm.calculateSnapTarget(
372                            mStartPosition, 0 /* velocity */, false /* hardDismiss */);
373                    resizeStack(calculatePosition(x, y), mStartPosition, snapTarget);
374                }
375                break;
376            case MotionEvent.ACTION_UP:
377            case MotionEvent.ACTION_CANCEL:
378                mVelocityTracker.addMovement(event);
379
380                x = (int) event.getRawX();
381                y = (int) event.getRawY();
382
383                if (mMoving && mDockSide != WindowManager.DOCKED_INVALID) {
384                    int position = calculatePosition(x, y);
385                    SnapTarget snapTarget = mSnapAlgorithm.calculateSnapTarget(position,
386                            0 /* velocity */, false /* hardDismiss */);
387                    resizeStack(calculatePosition(x, y), snapTarget.position, snapTarget);
388                }
389
390                mVelocityTracker.computeCurrentVelocity(1000);
391                int position = calculatePosition(x, y);
392                stopDragging(position, isHorizontalDivision() ? mVelocityTracker.getYVelocity()
393                        : mVelocityTracker.getXVelocity(), false /* avoidDismissStart */);
394                mMoving = false;
395                break;
396        }
397        return true;
398    }
399
400    private void convertToScreenCoordinates(MotionEvent event) {
401        event.setLocation(event.getRawX(), event.getRawY());
402    }
403
404    private void fling(int position, float velocity, boolean avoidDismissStart) {
405        SnapTarget snapTarget = mSnapAlgorithm.calculateSnapTarget(position, velocity);
406        if (avoidDismissStart && snapTarget == mSnapAlgorithm.getDismissStartTarget()) {
407            snapTarget = mSnapAlgorithm.getFirstSplitTarget();
408        }
409        ValueAnimator anim = getFlingAnimator(position, snapTarget);
410        mFlingAnimationUtils.apply(anim, position, snapTarget.position, velocity);
411        anim.start();
412    }
413
414    private void flingTo(int position, SnapTarget target, long duration, long startDelay,
415            Interpolator interpolator) {
416        ValueAnimator anim = getFlingAnimator(position, target);
417        anim.setDuration(duration);
418        anim.setStartDelay(startDelay);
419        anim.setInterpolator(interpolator);
420        anim.start();
421    }
422
423    private ValueAnimator getFlingAnimator(int position, final SnapTarget snapTarget) {
424        ValueAnimator anim = ValueAnimator.ofInt(position, snapTarget.position);
425        anim.addUpdateListener(new AnimatorUpdateListener() {
426            @Override
427            public void onAnimationUpdate(ValueAnimator animation) {
428                resizeStack((Integer) animation.getAnimatedValue(),
429                        animation.getAnimatedFraction() == 1f
430                                ? TASK_POSITION_SAME
431                                : snapTarget.position, snapTarget);
432            }
433        });
434        anim.addListener(new AnimatorListenerAdapter() {
435            @Override
436            public void onAnimationEnd(Animator animation) {
437                commitSnapFlags(snapTarget);
438                mWindowManagerProxy.setResizing(false);
439                mDockSide = WindowManager.DOCKED_INVALID;
440                mCurrentAnimator = null;
441                mEntranceAnimationRunning = false;
442            }
443        });
444        mCurrentAnimator = anim;
445        return anim;
446    }
447
448    private void cancelFlingAnimation() {
449        if (mCurrentAnimator != null) {
450            mCurrentAnimator.cancel();
451        }
452    }
453
454    private void commitSnapFlags(SnapTarget target) {
455        if (target.flag == SnapTarget.FLAG_NONE) {
456            return;
457        }
458        boolean dismissOrMaximize;
459        if (target.flag == SnapTarget.FLAG_DISMISS_START) {
460            dismissOrMaximize = mDockSide == WindowManager.DOCKED_LEFT
461                    || mDockSide == WindowManager.DOCKED_TOP;
462        } else {
463            dismissOrMaximize = mDockSide == WindowManager.DOCKED_RIGHT
464                    || mDockSide == WindowManager.DOCKED_BOTTOM;
465        }
466        if (dismissOrMaximize) {
467            mWindowManagerProxy.dismissDockedStack();
468        } else {
469            mWindowManagerProxy.maximizeDockedStack();
470        }
471        mWindowManagerProxy.setResizeDimLayer(false, -1, 0f);
472    }
473
474    private void liftBackground() {
475        if (mBackgroundLifted) {
476            return;
477        }
478        if (isHorizontalDivision()) {
479            mBackground.animate().scaleY(1.4f);
480        } else {
481            mBackground.animate().scaleX(1.4f);
482        }
483        mBackground.animate()
484                .setInterpolator(Interpolators.TOUCH_RESPONSE)
485                .setDuration(TOUCH_ANIMATION_DURATION)
486                .translationZ(mTouchElevation)
487                .start();
488
489        // Lift handle as well so it doesn't get behind the background, even though it doesn't
490        // cast shadow.
491        mHandle.animate()
492                .setInterpolator(Interpolators.TOUCH_RESPONSE)
493                .setDuration(TOUCH_ANIMATION_DURATION)
494                .translationZ(mTouchElevation)
495                .start();
496        mBackgroundLifted = true;
497    }
498
499    private void releaseBackground() {
500        if (!mBackgroundLifted) {
501            return;
502        }
503        mBackground.animate()
504                .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
505                .setDuration(TOUCH_RELEASE_ANIMATION_DURATION)
506                .translationZ(0)
507                .scaleX(1f)
508                .scaleY(1f)
509                .start();
510        mHandle.animate()
511                .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
512                .setDuration(TOUCH_RELEASE_ANIMATION_DURATION)
513                .translationZ(0)
514                .start();
515        mBackgroundLifted = false;
516    }
517
518
519    public void setMinimizedDockStack(boolean minimized) {
520        updateDockSide();
521        mHandle.setAlpha(minimized ? 0f : 1f);
522        if (mDockSide == WindowManager.DOCKED_TOP) {
523            mBackground.setPivotY(0);
524            mBackground.setScaleY(minimized ? MINIMIZE_DOCK_SCALE : 1f);
525        } else if (mDockSide == WindowManager.DOCKED_LEFT
526                || mDockSide == WindowManager.DOCKED_RIGHT) {
527            mBackground.setPivotX(mDockSide == WindowManager.DOCKED_LEFT
528                    ? 0
529                    : mBackground.getWidth());
530            mBackground.setScaleX(minimized ? MINIMIZE_DOCK_SCALE : 1f);
531        }
532    }
533
534    public void setMinimizedDockStack(boolean minimized, long animDuration) {
535        updateDockSide();
536        mHandle.animate()
537                .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
538                .setDuration(animDuration)
539                .alpha(minimized ? 0f : 1f)
540                .start();
541        if (mDockSide == WindowManager.DOCKED_TOP) {
542            mBackground.setPivotY(0);
543            mBackground.animate()
544                    .scaleY(minimized ? MINIMIZE_DOCK_SCALE : 1f);
545        } else if (mDockSide == WindowManager.DOCKED_LEFT
546                || mDockSide == WindowManager.DOCKED_RIGHT) {
547            mBackground.setPivotX(mDockSide == WindowManager.DOCKED_LEFT
548                    ? 0
549                    : mBackground.getWidth());
550            mBackground.animate()
551                    .scaleX(minimized ? MINIMIZE_DOCK_SCALE : 1f);
552        }
553        mBackground.animate()
554                .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
555                .setDuration(animDuration)
556                .start();
557    }
558
559    @Override
560    protected void onConfigurationChanged(Configuration newConfig) {
561        super.onConfigurationChanged(newConfig);
562        updateDisplayInfo();
563    }
564
565    private void updateDisplayInfo() {
566        final DisplayManager displayManager =
567                (DisplayManager) mContext.getSystemService(Context.DISPLAY_SERVICE);
568        Display display = displayManager.getDisplay(Display.DEFAULT_DISPLAY);
569        final DisplayInfo info = new DisplayInfo();
570        display.getDisplayInfo(info);
571        mDisplayWidth = info.logicalWidth;
572        mDisplayHeight = info.logicalHeight;
573        mSnapAlgorithm = null;
574        initializeSnapAlgorithm();
575    }
576
577    private int calculatePosition(int touchX, int touchY) {
578        return isHorizontalDivision() ? calculateYPosition(touchY) : calculateXPosition(touchX);
579    }
580
581    public boolean isHorizontalDivision() {
582        return getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT;
583    }
584
585    private int calculateXPosition(int touchX) {
586        return mStartPosition + touchX - mStartX;
587    }
588
589    private int calculateYPosition(int touchY) {
590        return mStartPosition + touchY - mStartY;
591    }
592
593    private void alignTopLeft(Rect containingRect, Rect rect) {
594        int width = rect.width();
595        int height = rect.height();
596        rect.set(containingRect.left, containingRect.top,
597                containingRect.left + width, containingRect.top + height);
598    }
599
600    private void alignBottomRight(Rect containingRect, Rect rect) {
601        int width = rect.width();
602        int height = rect.height();
603        rect.set(containingRect.right - width, containingRect.bottom - height,
604                containingRect.right, containingRect.bottom);
605    }
606
607    public void calculateBoundsForPosition(int position, int dockSide, Rect outRect) {
608        DockedDividerUtils.calculateBoundsForPosition(position, dockSide, outRect, mDisplayWidth,
609                mDisplayHeight, mDividerSize);
610    }
611
612    public void resizeStack(int position, int taskPosition, SnapTarget taskSnapTarget) {
613        calculateBoundsForPosition(position, mDockSide, mDockedRect);
614
615        if (mDockedRect.equals(mLastResizeRect)) {
616            return;
617        }
618
619        // Make sure shadows are updated
620        if (mBackground.getZ() > 0f) {
621            mBackground.invalidate();
622        }
623
624        mLastResizeRect.set(mDockedRect);
625        if (mEntranceAnimationRunning && taskPosition != TASK_POSITION_SAME) {
626            if (mCurrentAnimator != null) {
627                calculateBoundsForPosition(taskPosition, mDockSide, mDockedTaskRect);
628            } else {
629                calculateBoundsForPosition(isHorizontalDivision() ? mDisplayHeight : mDisplayWidth,
630                        mDockSide, mDockedTaskRect);
631            }
632            calculateBoundsForPosition(taskPosition, DockedDividerUtils.invertDockSide(mDockSide),
633                    mOtherTaskRect);
634            mWindowManagerProxy.resizeDockedStack(mDockedRect, mDockedTaskRect, null,
635                    mOtherTaskRect, null);
636        } else if (taskPosition != TASK_POSITION_SAME) {
637            calculateBoundsForPosition(position, DockedDividerUtils.invertDockSide(mDockSide),
638                    mOtherRect);
639            int dockSideInverted = DockedDividerUtils.invertDockSide(mDockSide);
640            int taskPositionDocked =
641                    restrictDismissingTaskPosition(taskPosition, mDockSide, taskSnapTarget);
642            int taskPositionOther =
643                    restrictDismissingTaskPosition(taskPosition, dockSideInverted, taskSnapTarget);
644            calculateBoundsForPosition(taskPositionDocked, mDockSide, mDockedTaskRect);
645            calculateBoundsForPosition(taskPositionOther, dockSideInverted, mOtherTaskRect);
646            mDisplayRect.set(0, 0, mDisplayWidth, mDisplayHeight);
647            alignTopLeft(mDockedRect, mDockedTaskRect);
648            alignTopLeft(mOtherRect, mOtherTaskRect);
649            mDockedInsetRect.set(mDockedTaskRect);
650            mOtherInsetRect.set(mOtherTaskRect);
651            if (dockSideTopLeft(mDockSide)) {
652                alignTopLeft(mDisplayRect, mDockedInsetRect);
653                alignBottomRight(mDisplayRect, mOtherInsetRect);
654            } else {
655                alignBottomRight(mDisplayRect, mDockedInsetRect);
656                alignTopLeft(mDisplayRect, mOtherInsetRect);
657            }
658            applyDismissingParallax(mDockedTaskRect, mDockSide, taskSnapTarget, position,
659                    taskPositionDocked);
660            applyDismissingParallax(mOtherTaskRect, dockSideInverted, taskSnapTarget, position,
661                    taskPositionOther);
662            mWindowManagerProxy.resizeDockedStack(mDockedRect, mDockedTaskRect, mDockedInsetRect,
663                    mOtherTaskRect, mOtherInsetRect);
664        } else {
665            mWindowManagerProxy.resizeDockedStack(mDockedRect, null, null, null, null);
666        }
667        SnapTarget closestDismissTarget = mSnapAlgorithm.getClosestDismissTarget(position);
668        float dimFraction = getDimFraction(position, closestDismissTarget);
669        mWindowManagerProxy.setResizeDimLayer(dimFraction != 0f,
670                getStackIdForDismissTarget(closestDismissTarget),
671                dimFraction);
672    }
673
674    private float getDimFraction(int position, SnapTarget dismissTarget) {
675        if (mEntranceAnimationRunning) {
676            return 0f;
677        }
678        float fraction = mSnapAlgorithm.calculateDismissingFraction(position);
679        fraction = Math.max(0, Math.min(fraction, 1f));
680        fraction = DIM_INTERPOLATOR.getInterpolation(fraction);
681        if (hasInsetsAtDismissTarget(dismissTarget)) {
682
683            // Less darkening with system insets.
684            fraction *= 0.8f;
685        }
686        return fraction;
687    }
688
689    /**
690     * @return true if and only if there are system insets at the location of the dismiss target
691     */
692    private boolean hasInsetsAtDismissTarget(SnapTarget dismissTarget) {
693        if (isHorizontalDivision()) {
694            if (dismissTarget == mSnapAlgorithm.getDismissStartTarget()) {
695                return mStableInsets.top != 0;
696            } else {
697                return mStableInsets.bottom != 0;
698            }
699        } else {
700            if (dismissTarget == mSnapAlgorithm.getDismissStartTarget()) {
701                return mStableInsets.left != 0;
702            } else {
703                return mStableInsets.right != 0;
704            }
705        }
706    }
707
708    /**
709     * When the snap target is dismissing one side, make sure that the dismissing side doesn't get
710     * 0 size.
711     */
712    private int restrictDismissingTaskPosition(int taskPosition, int dockSide,
713            SnapTarget snapTarget) {
714        if (snapTarget.flag == SnapTarget.FLAG_DISMISS_START && dockSideTopLeft(dockSide)) {
715            return mSnapAlgorithm.getFirstSplitTarget().position;
716        } else if (snapTarget.flag == SnapTarget.FLAG_DISMISS_END
717                && dockSideBottomRight(dockSide)) {
718            return mSnapAlgorithm.getLastSplitTarget().position;
719        } else {
720            return taskPosition;
721        }
722    }
723
724    /**
725     * Applies a parallax to the task when dismissing.
726     */
727    private void applyDismissingParallax(Rect taskRect, int dockSide, SnapTarget snapTarget,
728            int position, int taskPosition) {
729        float fraction = Math.min(1, Math.max(0,
730                mSnapAlgorithm.calculateDismissingFraction(position)));
731        SnapTarget dismissTarget = null;
732        SnapTarget splitTarget = null;
733        if ((snapTarget.flag == SnapTarget.FLAG_DISMISS_START
734                || snapTarget == mSnapAlgorithm.getFirstSplitTarget())
735                && dockSideTopLeft(dockSide)) {
736            dismissTarget = mSnapAlgorithm.getDismissStartTarget();
737            splitTarget = mSnapAlgorithm.getFirstSplitTarget();
738        } else if ((snapTarget.flag == SnapTarget.FLAG_DISMISS_END
739                || snapTarget == mSnapAlgorithm.getLastSplitTarget())
740                && dockSideBottomRight(dockSide)) {
741            dismissTarget = mSnapAlgorithm.getDismissEndTarget();
742            splitTarget = mSnapAlgorithm.getLastSplitTarget();
743        }
744        if (dismissTarget != null && fraction > 0f
745                && isDismissing(splitTarget, position, dockSide)) {
746            fraction = calculateParallaxDismissingFraction(fraction, dockSide);
747            int offsetPosition = (int) (taskPosition +
748                    fraction * (dismissTarget.position - splitTarget.position));
749            int width = taskRect.width();
750            int height = taskRect.height();
751            switch (dockSide) {
752                case WindowManager.DOCKED_LEFT:
753                    taskRect.left = offsetPosition - width;
754                    taskRect.right = offsetPosition;
755                    break;
756                case WindowManager.DOCKED_RIGHT:
757                    taskRect.left = offsetPosition + mDividerSize;
758                    taskRect.right = offsetPosition + width + mDividerSize;
759                    break;
760                case WindowManager.DOCKED_TOP:
761                    taskRect.top = offsetPosition - height;
762                    taskRect.bottom = offsetPosition;
763                    break;
764                case WindowManager.DOCKED_BOTTOM:
765                    taskRect.top = offsetPosition + mDividerSize;
766                    taskRect.bottom = offsetPosition + height + mDividerSize;
767                    break;
768            }
769        }
770    }
771
772    /**
773     * @return for a specified {@code fraction}, this returns an adjusted value that simulates a
774     *         slowing down parallax effect
775     */
776    private static float calculateParallaxDismissingFraction(float fraction, int dockSide) {
777        float result = SLOWDOWN_INTERPOLATOR.getInterpolation(fraction) / 3.5f;
778
779        // Less parallax at the top, just because.
780        if (dockSide == WindowManager.DOCKED_TOP) {
781            result /= 2f;
782        }
783        return result;
784    }
785
786    private static boolean isDismissing(SnapTarget snapTarget, int position, int dockSide) {
787        if (dockSide == WindowManager.DOCKED_TOP || dockSide == WindowManager.DOCKED_LEFT) {
788            return position < snapTarget.position;
789        } else {
790            return position > snapTarget.position;
791        }
792    }
793
794    private int getStackIdForDismissTarget(SnapTarget dismissTarget) {
795        if (dismissTarget.flag == SnapTarget.FLAG_DISMISS_START &&
796                (mDockSide == WindowManager.DOCKED_LEFT || mDockSide == WindowManager.DOCKED_TOP)) {
797            return StackId.DOCKED_STACK_ID;
798        } else {
799            return StackId.FULLSCREEN_WORKSPACE_STACK_ID;
800        }
801    }
802
803    /**
804     * @return true if and only if {@code dockSide} is top or left
805     */
806    private static boolean dockSideTopLeft(int dockSide) {
807        return dockSide == WindowManager.DOCKED_TOP || dockSide == WindowManager.DOCKED_LEFT;
808    }
809
810    /**
811     * @return true if and only if {@code dockSide} is bottom or right
812     */
813    private static boolean dockSideBottomRight(int dockSide) {
814        return dockSide == WindowManager.DOCKED_BOTTOM || dockSide == WindowManager.DOCKED_RIGHT;
815    }
816
817    @Override
818    public void onComputeInternalInsets(InternalInsetsInfo inoutInfo) {
819        inoutInfo.setTouchableInsets(InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
820        inoutInfo.touchableRegion.set(mHandle.getLeft(), mHandle.getTop(), mHandle.getRight(),
821                mHandle.getBottom());
822        inoutInfo.touchableRegion.op(mBackground.getLeft(), mBackground.getTop(),
823                mBackground.getRight(), mBackground.getBottom(), Op.UNION);
824    }
825
826    public final void onBusEvent(RecentsActivityStartingEvent recentsActivityStartingEvent) {
827        if (mGrowRecents && getWindowManagerProxy().getDockSide() == WindowManager.DOCKED_TOP
828                && getCurrentPosition() == getSnapAlgorithm().getLastSplitTarget().position) {
829            mGrowAfterRecentsDrawn = true;
830            startDragging(false /* animate */, false /* touching */);
831        }
832    }
833
834    public final void onBusEvent(DockedTopTaskEvent event) {
835        if (event.dragMode == NavigationBarGestureHelper.DRAG_MODE_NONE) {
836            mGrowAfterRecentsDrawn = false;
837            mAnimateAfterRecentsDrawn = true;
838            startDragging(false /* animate */, false /* touching */);
839        }
840        updateDockSide();
841        int position = DockedDividerUtils.calculatePositionForBounds(event.initialRect,
842                mDockSide, mDividerSize);
843        mEntranceAnimationRunning = true;
844        resizeStack(position, mSnapAlgorithm.getMiddleTarget().position,
845                mSnapAlgorithm.getMiddleTarget());
846    }
847
848    public final void onBusEvent(RecentsDrawnEvent drawnEvent) {
849        if (mAnimateAfterRecentsDrawn) {
850            mAnimateAfterRecentsDrawn = false;
851            updateDockSide();
852            stopDragging(getCurrentPosition(), mSnapAlgorithm.getMiddleTarget(), 250,
853                    Interpolators.TOUCH_RESPONSE);
854        }
855        if (mGrowAfterRecentsDrawn) {
856            mGrowAfterRecentsDrawn = false;
857            updateDockSide();
858            stopDragging(getCurrentPosition(), mSnapAlgorithm.getMiddleTarget(), 250,
859                    Interpolators.TOUCH_RESPONSE);
860        }
861    }
862
863    public final void onBusEvent(UndockingTaskEvent undockingTaskEvent) {
864        int dockSide = mWindowManagerProxy.getDockSide();
865        if (dockSide != WindowManager.DOCKED_INVALID) {
866            startDragging(false /* animate */, false /* touching */);
867            SnapTarget target = dockSideTopLeft(dockSide)
868                    ? mSnapAlgorithm.getDismissEndTarget()
869                    : mSnapAlgorithm.getDismissStartTarget();
870
871            // Don't start immediately - give a little bit time to settle the drag resize change.
872            stopDragging(getCurrentPosition(), target, 336 /* duration */, 100 /* startDelay */,
873                    Interpolators.TOUCH_RESPONSE);
874        }
875    }
876}
877