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