1/*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.systemui.stackdivider;
18
19import static android.view.PointerIcon.TYPE_HORIZONTAL_DOUBLE_ARROW;
20import static android.view.PointerIcon.TYPE_VERTICAL_DOUBLE_ARROW;
21
22import android.animation.Animator;
23import android.animation.AnimatorListenerAdapter;
24import android.animation.ValueAnimator;
25import android.annotation.Nullable;
26import android.app.ActivityManager.StackId;
27import android.content.Context;
28import android.content.res.Configuration;
29import android.graphics.Rect;
30import android.graphics.Region.Op;
31import android.hardware.display.DisplayManager;
32import android.os.Bundle;
33import android.os.Handler;
34import android.util.AttributeSet;
35import android.view.Display;
36import android.view.DisplayInfo;
37import android.view.GestureDetector;
38import android.view.GestureDetector.SimpleOnGestureListener;
39import android.view.MotionEvent;
40import android.view.PointerIcon;
41import android.view.VelocityTracker;
42import android.view.View;
43import android.view.View.OnTouchListener;
44import android.view.ViewConfiguration;
45import android.view.ViewTreeObserver.InternalInsetsInfo;
46import android.view.ViewTreeObserver.OnComputeInternalInsetsListener;
47import android.view.WindowInsets;
48import android.view.WindowManager;
49import android.view.accessibility.AccessibilityNodeInfo;
50import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
51import android.view.animation.Interpolator;
52import android.view.animation.PathInterpolator;
53import android.widget.FrameLayout;
54
55import com.android.internal.logging.MetricsLogger;
56import com.android.internal.logging.MetricsProto.MetricsEvent;
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.events.ui.RecentsGrowingEvent;
69import com.android.systemui.recents.misc.SystemServicesProxy;
70import com.android.systemui.stackdivider.events.StartedDragingEvent;
71import com.android.systemui.stackdivider.events.StoppedDragingEvent;
72import com.android.systemui.statusbar.FlingAnimationUtils;
73import com.android.systemui.statusbar.phone.NavigationBarGestureHelper;
74
75/**
76 * Docked stack divider.
77 */
78public class DividerView extends FrameLayout implements OnTouchListener,
79        OnComputeInternalInsetsListener {
80
81    static final long TOUCH_ANIMATION_DURATION = 150;
82    static final long TOUCH_RELEASE_ANIMATION_DURATION = 200;
83
84    public static final int INVALID_RECENTS_GROW_TARGET = -1;
85
86    private static final int LOG_VALUE_RESIZE_50_50 = 0;
87    private static final int LOG_VALUE_RESIZE_DOCKED_SMALLER = 1;
88    private static final int LOG_VALUE_RESIZE_DOCKED_LARGER = 2;
89
90    private static final int LOG_VALUE_UNDOCK_MAX_DOCKED = 0;
91    private static final int LOG_VALUE_UNDOCK_MAX_OTHER = 1;
92
93    private static final int TASK_POSITION_SAME = Integer.MAX_VALUE;
94    private static final boolean SWAPPING_ENABLED = false;
95
96    /**
97     * How much the background gets scaled when we are in the minimized dock state.
98     */
99    private static final float MINIMIZE_DOCK_SCALE = 0f;
100    private static final float ADJUSTED_FOR_IME_SCALE = 0.5f;
101
102    private static final PathInterpolator SLOWDOWN_INTERPOLATOR =
103            new PathInterpolator(0.5f, 1f, 0.5f, 1f);
104    private static final PathInterpolator DIM_INTERPOLATOR =
105            new PathInterpolator(.23f, .87f, .52f, -0.11f);
106    private static final Interpolator IME_ADJUST_INTERPOLATOR =
107            new PathInterpolator(0.2f, 0f, 0.1f, 1f);
108
109    private DividerHandleView mHandle;
110    private View mBackground;
111    private MinimizedDockShadow mMinimizedShadow;
112    private int mStartX;
113    private int mStartY;
114    private int mStartPosition;
115    private int mDockSide;
116    private final int[] mTempInt2 = new int[2];
117    private boolean mMoving;
118    private int mTouchSlop;
119    private boolean mBackgroundLifted;
120
121    private int mDividerInsets;
122    private int mDisplayWidth;
123    private int mDisplayHeight;
124    private int mDividerWindowWidth;
125    private int mDividerSize;
126    private int mTouchElevation;
127    private int mLongPressEntraceAnimDuration;
128
129    private final Rect mDockedRect = new Rect();
130    private final Rect mDockedTaskRect = new Rect();
131    private final Rect mOtherTaskRect = new Rect();
132    private final Rect mOtherRect = new Rect();
133    private final Rect mDockedInsetRect = new Rect();
134    private final Rect mOtherInsetRect = new Rect();
135    private final Rect mLastResizeRect = new Rect();
136    private final Rect mDisplayRect = new Rect();
137    private final WindowManagerProxy mWindowManagerProxy = WindowManagerProxy.getInstance();
138    private DividerWindowManager mWindowManager;
139    private VelocityTracker mVelocityTracker;
140    private FlingAnimationUtils mFlingAnimationUtils;
141    private DividerSnapAlgorithm mSnapAlgorithm;
142    private final Rect mStableInsets = new Rect();
143
144    private boolean mGrowRecents;
145    private ValueAnimator mCurrentAnimator;
146    private boolean mEntranceAnimationRunning;
147    private boolean mExitAnimationRunning;
148    private int mExitStartPosition;
149    private GestureDetector mGestureDetector;
150    private boolean mDockedStackMinimized;
151    private boolean mAdjustedForIme;
152    private DividerState mState;
153    private final Handler mHandler = new Handler();
154
155    private final AccessibilityDelegate mHandleDelegate = new AccessibilityDelegate() {
156        @Override
157        public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
158            super.onInitializeAccessibilityNodeInfo(host, info);
159            if (isHorizontalDivision()) {
160                info.addAction(new AccessibilityAction(R.id.action_move_tl_full,
161                        mContext.getString(R.string.accessibility_action_divider_top_full)));
162                if (mSnapAlgorithm.isFirstSplitTargetAvailable()) {
163                    info.addAction(new AccessibilityAction(R.id.action_move_tl_70,
164                            mContext.getString(R.string.accessibility_action_divider_top_70)));
165                }
166                info.addAction(new AccessibilityAction(R.id.action_move_tl_50,
167                        mContext.getString(R.string.accessibility_action_divider_top_50)));
168                if (mSnapAlgorithm.isLastSplitTargetAvailable()) {
169                    info.addAction(new AccessibilityAction(R.id.action_move_tl_30,
170                            mContext.getString(R.string.accessibility_action_divider_top_30)));
171                }
172                info.addAction(new AccessibilityAction(R.id.action_move_rb_full,
173                        mContext.getString(R.string.accessibility_action_divider_bottom_full)));
174            } else {
175                info.addAction(new AccessibilityAction(R.id.action_move_tl_full,
176                        mContext.getString(R.string.accessibility_action_divider_left_full)));
177                if (mSnapAlgorithm.isFirstSplitTargetAvailable()) {
178                    info.addAction(new AccessibilityAction(R.id.action_move_tl_70,
179                            mContext.getString(R.string.accessibility_action_divider_left_70)));
180                }
181                info.addAction(new AccessibilityAction(R.id.action_move_tl_50,
182                        mContext.getString(R.string.accessibility_action_divider_left_50)));
183                if (mSnapAlgorithm.isLastSplitTargetAvailable()) {
184                    info.addAction(new AccessibilityAction(R.id.action_move_tl_30,
185                            mContext.getString(R.string.accessibility_action_divider_left_30)));
186                }
187                info.addAction(new AccessibilityAction(R.id.action_move_rb_full,
188                        mContext.getString(R.string.accessibility_action_divider_right_full)));
189            }
190        }
191
192        @Override
193        public boolean performAccessibilityAction(View host, int action, Bundle args) {
194            int currentPosition = getCurrentPosition();
195            SnapTarget nextTarget = null;
196            switch (action) {
197                case R.id.action_move_tl_full:
198                    nextTarget = mSnapAlgorithm.getDismissEndTarget();
199                    break;
200                case R.id.action_move_tl_70:
201                    nextTarget = mSnapAlgorithm.getLastSplitTarget();
202                    break;
203                case R.id.action_move_tl_50:
204                    nextTarget = mSnapAlgorithm.getMiddleTarget();
205                    break;
206                case R.id.action_move_tl_30:
207                    nextTarget = mSnapAlgorithm.getFirstSplitTarget();
208                    break;
209                case R.id.action_move_rb_full:
210                    nextTarget = mSnapAlgorithm.getDismissStartTarget();
211                    break;
212            }
213            if (nextTarget != null) {
214                startDragging(true /* animate */, false /* touching */);
215                stopDragging(currentPosition, nextTarget, 250, Interpolators.FAST_OUT_SLOW_IN);
216                return true;
217            }
218            return super.performAccessibilityAction(host, action, args);
219        }
220    };
221
222    private final Runnable mResetBackgroundRunnable = new Runnable() {
223        @Override
224        public void run() {
225            resetBackground();
226        }
227    };
228
229    public DividerView(Context context) {
230        super(context);
231    }
232
233    public DividerView(Context context, @Nullable AttributeSet attrs) {
234        super(context, attrs);
235    }
236
237    public DividerView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
238        super(context, attrs, defStyleAttr);
239    }
240
241    public DividerView(Context context, @Nullable AttributeSet attrs, int defStyleAttr,
242            int defStyleRes) {
243        super(context, attrs, defStyleAttr, defStyleRes);
244    }
245
246    @Override
247    protected void onFinishInflate() {
248        super.onFinishInflate();
249        mHandle = (DividerHandleView) findViewById(R.id.docked_divider_handle);
250        mBackground = findViewById(R.id.docked_divider_background);
251        mMinimizedShadow = (MinimizedDockShadow) findViewById(R.id.minimized_dock_shadow);
252        mHandle.setOnTouchListener(this);
253        mDividerWindowWidth = getResources().getDimensionPixelSize(
254                com.android.internal.R.dimen.docked_stack_divider_thickness);
255        mDividerInsets = getResources().getDimensionPixelSize(
256                com.android.internal.R.dimen.docked_stack_divider_insets);
257        mDividerSize = mDividerWindowWidth - 2 * mDividerInsets;
258        mTouchElevation = getResources().getDimensionPixelSize(
259                R.dimen.docked_stack_divider_lift_elevation);
260        mLongPressEntraceAnimDuration = getResources().getInteger(
261                R.integer.long_press_dock_anim_duration);
262        mGrowRecents = getResources().getBoolean(R.bool.recents_grow_in_multiwindow);
263        mTouchSlop = ViewConfiguration.get(mContext).getScaledTouchSlop();
264        mFlingAnimationUtils = new FlingAnimationUtils(getContext(), 0.3f);
265        updateDisplayInfo();
266        boolean landscape = getResources().getConfiguration().orientation
267                == Configuration.ORIENTATION_LANDSCAPE;
268        mHandle.setPointerIcon(PointerIcon.getSystemIcon(getContext(),
269                landscape ? TYPE_HORIZONTAL_DOUBLE_ARROW : TYPE_VERTICAL_DOUBLE_ARROW));
270        getViewTreeObserver().addOnComputeInternalInsetsListener(this);
271        mHandle.setAccessibilityDelegate(mHandleDelegate);
272        mGestureDetector = new GestureDetector(mContext, new SimpleOnGestureListener() {
273            @Override
274            public boolean onSingleTapUp(MotionEvent e) {
275                if (SWAPPING_ENABLED) {
276                    updateDockSide();
277                    SystemServicesProxy ssp = Recents.getSystemServices();
278                    if (mDockSide != WindowManager.DOCKED_INVALID
279                            && !ssp.isRecentsActivityVisible()) {
280                        mWindowManagerProxy.swapTasks();
281                        return true;
282                    }
283                }
284                return false;
285            }
286        });
287    }
288
289    @Override
290    protected void onAttachedToWindow() {
291        super.onAttachedToWindow();
292        EventBus.getDefault().register(this);
293    }
294
295    @Override
296    protected void onDetachedFromWindow() {
297        super.onDetachedFromWindow();
298        EventBus.getDefault().unregister(this);
299    }
300
301    @Override
302    public WindowInsets onApplyWindowInsets(WindowInsets insets) {
303        if (mStableInsets.left != insets.getStableInsetLeft()
304                || mStableInsets.top != insets.getStableInsetTop()
305                || mStableInsets.right != insets.getStableInsetRight()
306                || mStableInsets.bottom != insets.getStableInsetBottom()) {
307            mStableInsets.set(insets.getStableInsetLeft(), insets.getStableInsetTop(),
308                    insets.getStableInsetRight(), insets.getStableInsetBottom());
309            if (mSnapAlgorithm != null) {
310                mSnapAlgorithm = null;
311                initializeSnapAlgorithm();
312            }
313        }
314        return super.onApplyWindowInsets(insets);
315    }
316
317    @Override
318    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
319        super.onLayout(changed, left, top, right, bottom);
320        int minimizeLeft = 0;
321        int minimizeTop = 0;
322        if (mDockSide == WindowManager.DOCKED_TOP) {
323            minimizeTop = mBackground.getTop();
324        } else if (mDockSide == WindowManager.DOCKED_LEFT) {
325            minimizeLeft = mBackground.getLeft();
326        } else if (mDockSide == WindowManager.DOCKED_RIGHT) {
327            minimizeLeft = mBackground.getRight() - mMinimizedShadow.getWidth();
328        }
329        mMinimizedShadow.layout(minimizeLeft, minimizeTop,
330                minimizeLeft + mMinimizedShadow.getMeasuredWidth(),
331                minimizeTop + mMinimizedShadow.getMeasuredHeight());
332        if (changed) {
333            mWindowManagerProxy.setTouchRegion(new Rect(mHandle.getLeft(), mHandle.getTop(),
334                    mHandle.getRight(), mHandle.getBottom()));
335        }
336    }
337
338    public void injectDependencies(DividerWindowManager windowManager, DividerState dividerState) {
339        mWindowManager = windowManager;
340        mState = dividerState;
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.taskPosition, 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                    mHandler.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            mState.growAfterRecentsDrawn = 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            mState.growAfterRecentsDrawn = false;
1060            mState.animateAfterRecentsDrawn = true;
1061            startDragging(false /* animate */, false /* touching */);
1062        }
1063        updateDockSide();
1064        int position = DockedDividerUtils.calculatePositionForBounds(event.initialRect,
1065                mDockSide, mDividerSize);
1066        mEntranceAnimationRunning = true;
1067
1068        // Insets might not have been fetched yet, so fetch manually if needed.
1069        if (mStableInsets.isEmpty()) {
1070            SystemServicesProxy.getInstance(mContext).getStableInsets(mStableInsets);
1071            mSnapAlgorithm = null;
1072            initializeSnapAlgorithm();
1073        }
1074
1075        resizeStack(position, mSnapAlgorithm.getMiddleTarget().position,
1076                mSnapAlgorithm.getMiddleTarget());
1077    }
1078
1079    public final void onBusEvent(RecentsDrawnEvent drawnEvent) {
1080        if (mState.animateAfterRecentsDrawn) {
1081            mState.animateAfterRecentsDrawn = false;
1082            updateDockSide();
1083
1084            mHandler.post(() -> {
1085                // Delay switching resizing mode because this might cause jank in recents animation
1086                // that's longer than this animation.
1087                stopDragging(getCurrentPosition(), mSnapAlgorithm.getMiddleTarget(),
1088                        mLongPressEntraceAnimDuration, Interpolators.FAST_OUT_SLOW_IN,
1089                        200 /* endDelay */);
1090            });
1091        }
1092        if (mState.growAfterRecentsDrawn) {
1093            mState.growAfterRecentsDrawn = false;
1094            updateDockSide();
1095            EventBus.getDefault().send(new RecentsGrowingEvent());
1096            stopDragging(getCurrentPosition(), mSnapAlgorithm.getMiddleTarget(), 336,
1097                    Interpolators.FAST_OUT_SLOW_IN);
1098        }
1099    }
1100
1101    public final void onBusEvent(UndockingTaskEvent undockingTaskEvent) {
1102        int dockSide = mWindowManagerProxy.getDockSide();
1103        if (dockSide != WindowManager.DOCKED_INVALID && !mDockedStackMinimized) {
1104            startDragging(false /* animate */, false /* touching */);
1105            SnapTarget target = dockSideTopLeft(dockSide)
1106                    ? mSnapAlgorithm.getDismissEndTarget()
1107                    : mSnapAlgorithm.getDismissStartTarget();
1108
1109            // Don't start immediately - give a little bit time to settle the drag resize change.
1110            mExitAnimationRunning = true;
1111            mExitStartPosition = getCurrentPosition();
1112            stopDragging(mExitStartPosition, target, 336 /* duration */, 100 /* startDelay */,
1113                    0 /* endDelay */, Interpolators.FAST_OUT_SLOW_IN);
1114        }
1115    }
1116}
1117