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