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