OSDN Git Service

DO NOT MERGE. Grant MMS Uri permissions as the calling UID.
[android-x86/frameworks-base.git] / packages / SystemUI / src / com / android / systemui / stackdivider / DividerView.java
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
17 package com.android.systemui.stackdivider;
18
19 import static android.view.PointerIcon.TYPE_HORIZONTAL_DOUBLE_ARROW;
20 import static android.view.PointerIcon.TYPE_VERTICAL_DOUBLE_ARROW;
21
22 import android.animation.Animator;
23 import android.animation.AnimatorListenerAdapter;
24 import android.animation.ValueAnimator;
25 import android.annotation.Nullable;
26 import android.app.ActivityManager.StackId;
27 import android.content.Context;
28 import android.content.res.Configuration;
29 import android.graphics.Rect;
30 import android.graphics.Region.Op;
31 import android.hardware.display.DisplayManager;
32 import android.os.Bundle;
33 import android.os.Handler;
34 import android.util.AttributeSet;
35 import android.view.Display;
36 import android.view.DisplayInfo;
37 import android.view.GestureDetector;
38 import android.view.GestureDetector.SimpleOnGestureListener;
39 import android.view.MotionEvent;
40 import android.view.PointerIcon;
41 import android.view.VelocityTracker;
42 import android.view.View;
43 import android.view.View.OnTouchListener;
44 import android.view.ViewConfiguration;
45 import android.view.ViewTreeObserver.InternalInsetsInfo;
46 import android.view.ViewTreeObserver.OnComputeInternalInsetsListener;
47 import android.view.WindowInsets;
48 import android.view.WindowManager;
49 import android.view.accessibility.AccessibilityNodeInfo;
50 import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
51 import android.view.animation.Interpolator;
52 import android.view.animation.PathInterpolator;
53 import android.widget.FrameLayout;
54
55 import com.android.internal.logging.MetricsLogger;
56 import com.android.internal.logging.MetricsProto.MetricsEvent;
57 import com.android.internal.policy.DividerSnapAlgorithm;
58 import com.android.internal.policy.DividerSnapAlgorithm.SnapTarget;
59 import com.android.internal.policy.DockedDividerUtils;
60 import com.android.systemui.Interpolators;
61 import com.android.systemui.R;
62 import com.android.systemui.recents.Recents;
63 import com.android.systemui.recents.events.EventBus;
64 import com.android.systemui.recents.events.activity.DockedTopTaskEvent;
65 import com.android.systemui.recents.events.activity.RecentsActivityStartingEvent;
66 import com.android.systemui.recents.events.activity.UndockingTaskEvent;
67 import com.android.systemui.recents.events.ui.RecentsDrawnEvent;
68 import com.android.systemui.recents.events.ui.RecentsGrowingEvent;
69 import com.android.systemui.recents.misc.SystemServicesProxy;
70 import com.android.systemui.stackdivider.events.StartedDragingEvent;
71 import com.android.systemui.stackdivider.events.StoppedDragingEvent;
72 import com.android.systemui.statusbar.FlingAnimationUtils;
73 import com.android.systemui.statusbar.phone.NavigationBarGestureHelper;
74
75 /**
76  * Docked stack divider.
77  */
78 public 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 }