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