1 /*
2  * Copyright (C) 2015 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.systemui.stackdivider;
18 
19 import static android.view.PointerIcon.TYPE_HORIZONTAL_DOUBLE_ARROW;
20 import static android.view.PointerIcon.TYPE_VERTICAL_DOUBLE_ARROW;
21 import static android.view.WindowManager.DOCKED_RIGHT;
22 
23 import android.animation.AnimationHandler;
24 import android.animation.Animator;
25 import android.animation.AnimatorListenerAdapter;
26 import android.animation.ValueAnimator;
27 import android.annotation.Nullable;
28 import android.content.Context;
29 import android.content.res.Configuration;
30 import android.graphics.Matrix;
31 import android.graphics.Rect;
32 import android.graphics.Region;
33 import android.graphics.Region.Op;
34 import android.hardware.display.DisplayManager;
35 import android.os.Bundle;
36 import android.os.Handler;
37 import android.os.RemoteException;
38 import android.util.AttributeSet;
39 import android.util.Slog;
40 import android.view.Display;
41 import android.view.MotionEvent;
42 import android.view.PointerIcon;
43 import android.view.SurfaceControl;
44 import android.view.SurfaceControl.Transaction;
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.ViewRootImpl;
50 import android.view.ViewTreeObserver.InternalInsetsInfo;
51 import android.view.ViewTreeObserver.OnComputeInternalInsetsListener;
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.graphics.SfVsyncFrameCallbackProvider;
60 import com.android.internal.logging.MetricsLogger;
61 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
62 import com.android.internal.policy.DividerSnapAlgorithm;
63 import com.android.internal.policy.DividerSnapAlgorithm.SnapTarget;
64 import com.android.internal.policy.DockedDividerUtils;
65 import com.android.systemui.Dependency;
66 import com.android.systemui.Interpolators;
67 import com.android.systemui.R;
68 import com.android.systemui.recents.OverviewProxyService;
69 import com.android.systemui.statusbar.FlingAnimationUtils;
70 
71 import java.util.function.Consumer;
72 
73 /**
74  * Docked stack divider.
75  */
76 public class DividerView extends FrameLayout implements OnTouchListener,
77         OnComputeInternalInsetsListener {
78     private static final String TAG = "DividerView";
79     private static final boolean DEBUG = Divider.DEBUG;
80 
81     public interface DividerCallbacks {
onDraggingStart()82         void onDraggingStart();
onDraggingEnd()83         void onDraggingEnd();
growRecents()84         void growRecents();
85     }
86 
87     static final long TOUCH_ANIMATION_DURATION = 150;
88     static final long TOUCH_RELEASE_ANIMATION_DURATION = 200;
89 
90     public static final int INVALID_RECENTS_GROW_TARGET = -1;
91 
92     private static final int LOG_VALUE_RESIZE_50_50 = 0;
93     private static final int LOG_VALUE_RESIZE_DOCKED_SMALLER = 1;
94     private static final int LOG_VALUE_RESIZE_DOCKED_LARGER = 2;
95 
96     private static final int LOG_VALUE_UNDOCK_MAX_DOCKED = 0;
97     private static final int LOG_VALUE_UNDOCK_MAX_OTHER = 1;
98 
99     private static final int TASK_POSITION_SAME = Integer.MAX_VALUE;
100 
101     /**
102      * How much the background gets scaled when we are in the minimized dock state.
103      */
104     private static final float MINIMIZE_DOCK_SCALE = 0f;
105     private static final float ADJUSTED_FOR_IME_SCALE = 0.5f;
106 
107     private static final PathInterpolator SLOWDOWN_INTERPOLATOR =
108             new PathInterpolator(0.5f, 1f, 0.5f, 1f);
109     private static final PathInterpolator DIM_INTERPOLATOR =
110             new PathInterpolator(.23f, .87f, .52f, -0.11f);
111     private static final Interpolator IME_ADJUST_INTERPOLATOR =
112             new PathInterpolator(0.2f, 0f, 0.1f, 1f);
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 boolean mMoving;
122     private int mTouchSlop;
123     private boolean mBackgroundLifted;
124     private boolean mIsInMinimizeInteraction;
125     SnapTarget mSnapTargetBeforeMinimized;
126 
127     private int mDividerInsets;
128     private final Display mDefaultDisplay;
129 
130     private int mDividerSize;
131     private int mTouchElevation;
132     private int mLongPressEntraceAnimDuration;
133 
134     private final Rect mDockedRect = new Rect();
135     private final Rect mDockedTaskRect = new Rect();
136     private final Rect mOtherTaskRect = new Rect();
137     private final Rect mOtherRect = new Rect();
138     private final Rect mDockedInsetRect = new Rect();
139     private final Rect mOtherInsetRect = new Rect();
140     private final Rect mLastResizeRect = new Rect();
141     private final Rect mTmpRect = new Rect();
142     private WindowManagerProxy mWindowManagerProxy;
143     private DividerWindowManager mWindowManager;
144     private VelocityTracker mVelocityTracker;
145     private FlingAnimationUtils mFlingAnimationUtils;
146     private SplitDisplayLayout mSplitLayout;
147     private DividerImeController mImeController;
148     private DividerCallbacks mCallback;
149     private final AnimationHandler mAnimationHandler = new AnimationHandler();
150 
151     private boolean mGrowRecents;
152     private ValueAnimator mCurrentAnimator;
153     private boolean mEntranceAnimationRunning;
154     private boolean mExitAnimationRunning;
155     private int mExitStartPosition;
156     private boolean mDockedStackMinimized;
157     private boolean mHomeStackResizable;
158     private boolean mAdjustedForIme;
159     private DividerState mState;
160 
161     private SplitScreenTaskOrganizer mTiles;
162     boolean mFirstLayout = true;
163     int mDividerPositionX;
164     int mDividerPositionY;
165 
166     private final Matrix mTmpMatrix = new Matrix();
167     private final float[] mTmpValues = new float[9];
168 
169     // The view is removed or in the process of been removed from the system.
170     private boolean mRemoved;
171 
172     // Whether the surface for this view has been hidden regardless of actual visibility. This is
173     // used interact with keyguard.
174     private boolean mSurfaceHidden = false;
175 
176     private final Handler mHandler = new Handler();
177 
178     private final AccessibilityDelegate mHandleDelegate = new AccessibilityDelegate() {
179         @Override
180         public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
181             super.onInitializeAccessibilityNodeInfo(host, info);
182             final DividerSnapAlgorithm snapAlgorithm = getSnapAlgorithm();
183             if (isHorizontalDivision()) {
184                 info.addAction(new AccessibilityAction(R.id.action_move_tl_full,
185                         mContext.getString(R.string.accessibility_action_divider_top_full)));
186                 if (snapAlgorithm.isFirstSplitTargetAvailable()) {
187                     info.addAction(new AccessibilityAction(R.id.action_move_tl_70,
188                             mContext.getString(R.string.accessibility_action_divider_top_70)));
189                 }
190                 if (snapAlgorithm.showMiddleSplitTargetForAccessibility()) {
191                     // Only show the middle target if there are more than 1 split target
192                     info.addAction(new AccessibilityAction(R.id.action_move_tl_50,
193                         mContext.getString(R.string.accessibility_action_divider_top_50)));
194                 }
195                 if (snapAlgorithm.isLastSplitTargetAvailable()) {
196                     info.addAction(new AccessibilityAction(R.id.action_move_tl_30,
197                             mContext.getString(R.string.accessibility_action_divider_top_30)));
198                 }
199                 info.addAction(new AccessibilityAction(R.id.action_move_rb_full,
200                         mContext.getString(R.string.accessibility_action_divider_bottom_full)));
201             } else {
202                 info.addAction(new AccessibilityAction(R.id.action_move_tl_full,
203                         mContext.getString(R.string.accessibility_action_divider_left_full)));
204                 if (snapAlgorithm.isFirstSplitTargetAvailable()) {
205                     info.addAction(new AccessibilityAction(R.id.action_move_tl_70,
206                             mContext.getString(R.string.accessibility_action_divider_left_70)));
207                 }
208                 if (snapAlgorithm.showMiddleSplitTargetForAccessibility()) {
209                     // Only show the middle target if there are more than 1 split target
210                     info.addAction(new AccessibilityAction(R.id.action_move_tl_50,
211                         mContext.getString(R.string.accessibility_action_divider_left_50)));
212                 }
213                 if (snapAlgorithm.isLastSplitTargetAvailable()) {
214                     info.addAction(new AccessibilityAction(R.id.action_move_tl_30,
215                             mContext.getString(R.string.accessibility_action_divider_left_30)));
216                 }
217                 info.addAction(new AccessibilityAction(R.id.action_move_rb_full,
218                         mContext.getString(R.string.accessibility_action_divider_right_full)));
219             }
220         }
221 
222         @Override
223         public boolean performAccessibilityAction(View host, int action, Bundle args) {
224             int currentPosition = getCurrentPosition();
225             SnapTarget nextTarget = null;
226             DividerSnapAlgorithm snapAlgorithm = mSplitLayout.getSnapAlgorithm();
227             if (action == R.id.action_move_tl_full) {
228                 nextTarget = snapAlgorithm.getDismissEndTarget();
229             } else if (action == R.id.action_move_tl_70) {
230                 nextTarget = snapAlgorithm.getLastSplitTarget();
231             } else if (action == R.id.action_move_tl_50) {
232                 nextTarget = snapAlgorithm.getMiddleTarget();
233             } else if (action == R.id.action_move_tl_30) {
234                 nextTarget = snapAlgorithm.getFirstSplitTarget();
235             } else if (action == R.id.action_move_rb_full) {
236                 nextTarget = snapAlgorithm.getDismissStartTarget();
237             }
238             if (nextTarget != null) {
239                 startDragging(true /* animate */, false /* touching */);
240                 stopDragging(currentPosition, nextTarget, 250, Interpolators.FAST_OUT_SLOW_IN);
241                 return true;
242             }
243             return super.performAccessibilityAction(host, action, args);
244         }
245     };
246 
247     private final Runnable mResetBackgroundRunnable = new Runnable() {
248         @Override
249         public void run() {
250             resetBackground();
251         }
252     };
253 
254     private Runnable mUpdateEmbeddedMatrix = () -> {
255         if (getViewRootImpl() == null) {
256             return;
257         }
258         if (isHorizontalDivision()) {
259             mTmpMatrix.setTranslate(0, mDividerPositionY - mDividerInsets);
260         } else {
261             mTmpMatrix.setTranslate(mDividerPositionX - mDividerInsets, 0);
262         }
263         mTmpMatrix.getValues(mTmpValues);
264         try {
265             getViewRootImpl().getAccessibilityEmbeddedConnection().setScreenMatrix(mTmpValues);
266         } catch (RemoteException e) {
267         }
268     };
269 
DividerView(Context context)270     public DividerView(Context context) {
271         this(context, null);
272     }
273 
DividerView(Context context, @Nullable AttributeSet attrs)274     public DividerView(Context context, @Nullable AttributeSet attrs) {
275         this(context, attrs, 0);
276     }
277 
DividerView(Context context, @Nullable AttributeSet attrs, int defStyleAttr)278     public DividerView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
279         this(context, attrs, defStyleAttr, 0);
280     }
281 
DividerView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes)282     public DividerView(Context context, @Nullable AttributeSet attrs, int defStyleAttr,
283             int defStyleRes) {
284         super(context, attrs, defStyleAttr, defStyleRes);
285         final DisplayManager displayManager =
286                 (DisplayManager) mContext.getSystemService(Context.DISPLAY_SERVICE);
287         mDefaultDisplay = displayManager.getDisplay(Display.DEFAULT_DISPLAY);
288         mAnimationHandler.setProvider(new SfVsyncFrameCallbackProvider());
289     }
290 
291     @Override
onFinishInflate()292     protected void onFinishInflate() {
293         super.onFinishInflate();
294         mHandle = findViewById(R.id.docked_divider_handle);
295         mBackground = findViewById(R.id.docked_divider_background);
296         mMinimizedShadow = findViewById(R.id.minimized_dock_shadow);
297         mHandle.setOnTouchListener(this);
298         final int dividerWindowWidth = getResources().getDimensionPixelSize(
299                 com.android.internal.R.dimen.docked_stack_divider_thickness);
300         mDividerInsets = getResources().getDimensionPixelSize(
301                 com.android.internal.R.dimen.docked_stack_divider_insets);
302         mDividerSize = dividerWindowWidth - 2 * mDividerInsets;
303         mTouchElevation = getResources().getDimensionPixelSize(
304                 R.dimen.docked_stack_divider_lift_elevation);
305         mLongPressEntraceAnimDuration = getResources().getInteger(
306                 R.integer.long_press_dock_anim_duration);
307         mGrowRecents = getResources().getBoolean(R.bool.recents_grow_in_multiwindow);
308         mTouchSlop = ViewConfiguration.get(mContext).getScaledTouchSlop();
309         mFlingAnimationUtils = new FlingAnimationUtils(getResources().getDisplayMetrics(), 0.3f);
310         boolean landscape = getResources().getConfiguration().orientation
311                 == Configuration.ORIENTATION_LANDSCAPE;
312         mHandle.setPointerIcon(PointerIcon.getSystemIcon(getContext(),
313                 landscape ? TYPE_HORIZONTAL_DOUBLE_ARROW : TYPE_VERTICAL_DOUBLE_ARROW));
314         getViewTreeObserver().addOnComputeInternalInsetsListener(this);
315         mHandle.setAccessibilityDelegate(mHandleDelegate);
316     }
317 
318     @Override
onAttachedToWindow()319     protected void onAttachedToWindow() {
320         super.onAttachedToWindow();
321 
322         // Save the current target if not minimized once attached to window
323         if (mDockSide != WindowManager.DOCKED_INVALID && !mIsInMinimizeInteraction) {
324             saveSnapTargetBeforeMinimized(mSnapTargetBeforeMinimized);
325         }
326         mFirstLayout = true;
327     }
328 
onDividerRemoved()329     void onDividerRemoved() {
330         mRemoved = true;
331         mCallback = null;
332     }
333 
334     @Override
onLayout(boolean changed, int left, int top, int right, int bottom)335     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
336         super.onLayout(changed, left, top, right, bottom);
337         if (mFirstLayout) {
338             // Wait for first layout so that the ViewRootImpl surface has been created.
339             initializeSurfaceState();
340             mFirstLayout = false;
341         }
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             notifySplitScreenBoundsChanged();
356         }
357     }
358 
injectDependencies(DividerWindowManager windowManager, DividerState dividerState, DividerCallbacks callback, SplitScreenTaskOrganizer tiles, SplitDisplayLayout sdl, DividerImeController imeController, WindowManagerProxy wmProxy)359     public void injectDependencies(DividerWindowManager windowManager, DividerState dividerState,
360             DividerCallbacks callback, SplitScreenTaskOrganizer tiles, SplitDisplayLayout sdl,
361             DividerImeController imeController, WindowManagerProxy wmProxy) {
362         mWindowManager = windowManager;
363         mState = dividerState;
364         mCallback = callback;
365         mTiles = tiles;
366         mSplitLayout = sdl;
367         mImeController = imeController;
368         mWindowManagerProxy = wmProxy;
369 
370         if (mState.mRatioPositionBeforeMinimized == 0) {
371             // Set the middle target as the initial state
372             mSnapTargetBeforeMinimized = mSplitLayout.getSnapAlgorithm().getMiddleTarget();
373         } else {
374             repositionSnapTargetBeforeMinimized();
375         }
376     }
377 
getNonMinimizedSplitScreenSecondaryBounds()378     public Rect getNonMinimizedSplitScreenSecondaryBounds() {
379         mOtherTaskRect.set(mSplitLayout.mSecondary);
380         return mOtherTaskRect;
381     }
382 
inSplitMode()383     private boolean inSplitMode() {
384         return getVisibility() == VISIBLE;
385     }
386 
387     /** Unlike setVisible, this directly hides the surface without changing view visibility. */
setHidden(boolean hidden)388     void setHidden(boolean hidden) {
389         if (mSurfaceHidden == hidden) {
390             return;
391         }
392         mSurfaceHidden = hidden;
393         post(() -> {
394             final SurfaceControl sc = getWindowSurfaceControl();
395             if (sc == null) {
396                 return;
397             }
398             Transaction t = mTiles.getTransaction();
399             if (hidden) {
400                 t.hide(sc);
401             } else {
402                 t.show(sc);
403             }
404             mImeController.setDimsHidden(t, hidden);
405             t.apply();
406             mTiles.releaseTransaction(t);
407         });
408     }
409 
isHidden()410     boolean isHidden() {
411         return mSurfaceHidden;
412     }
413 
startDragging(boolean animate, boolean touching)414     public boolean startDragging(boolean animate, boolean touching) {
415         cancelFlingAnimation();
416         if (touching) {
417             mHandle.setTouching(true, animate);
418         }
419         mDockSide = mSplitLayout.getPrimarySplitSide();
420 
421         mWindowManagerProxy.setResizing(true);
422         if (touching) {
423             mWindowManager.setSlippery(false);
424             liftBackground();
425         }
426         if (mCallback != null) {
427             mCallback.onDraggingStart();
428         }
429         return inSplitMode();
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 = mSplitLayout.getPrimarySplitSide();
466         mMinimizedShadow.setDockSide(mDockSide);
467     }
468 
getSnapAlgorithm()469     public DividerSnapAlgorithm getSnapAlgorithm() {
470         return mDockedStackMinimized ? mSplitLayout.getMinimizedSnapAlgorithm(mHomeStackResizable)
471                         : mSplitLayout.getSnapAlgorithm();
472     }
473 
getCurrentPosition()474     public int getCurrentPosition() {
475         return isHorizontalDivision() ? mDividerPositionY : mDividerPositionX;
476     }
477 
isMinimized()478     public boolean isMinimized() {
479         return mDockedStackMinimized;
480     }
481 
482     @Override
onTouch(View v, MotionEvent event)483     public boolean onTouch(View v, MotionEvent event) {
484         convertToScreenCoordinates(event);
485         final int action = event.getAction() & MotionEvent.ACTION_MASK;
486         switch (action) {
487             case MotionEvent.ACTION_DOWN:
488                 mVelocityTracker = VelocityTracker.obtain();
489                 mVelocityTracker.addMovement(event);
490                 mStartX = (int) event.getX();
491                 mStartY = (int) event.getY();
492                 boolean result = startDragging(true /* animate */, true /* touching */);
493                 if (!result) {
494 
495                     // Weren't able to start dragging successfully, so cancel it again.
496                     stopDragging();
497                 }
498                 mStartPosition = getCurrentPosition();
499                 mMoving = false;
500                 return result;
501             case MotionEvent.ACTION_MOVE:
502                 mVelocityTracker.addMovement(event);
503                 int x = (int) event.getX();
504                 int y = (int) event.getY();
505                 boolean exceededTouchSlop =
506                         isHorizontalDivision() && Math.abs(y - mStartY) > mTouchSlop
507                                 || (!isHorizontalDivision() && Math.abs(x - mStartX) > mTouchSlop);
508                 if (!mMoving && exceededTouchSlop) {
509                     mStartX = x;
510                     mStartY = y;
511                     mMoving = true;
512                 }
513                 if (mMoving && mDockSide != WindowManager.DOCKED_INVALID) {
514                     SnapTarget snapTarget = getSnapAlgorithm().calculateSnapTarget(
515                             mStartPosition, 0 /* velocity */, false /* hardDismiss */);
516                     resizeStackSurfaces(calculatePosition(x, y), mStartPosition, snapTarget,
517                             null /* transaction */);
518                 }
519                 break;
520             case MotionEvent.ACTION_UP:
521             case MotionEvent.ACTION_CANCEL:
522                 mVelocityTracker.addMovement(event);
523 
524                 x = (int) event.getRawX();
525                 y = (int) event.getRawY();
526 
527                 mVelocityTracker.computeCurrentVelocity(1000);
528                 int position = calculatePosition(x, y);
529                 stopDragging(position, isHorizontalDivision() ? mVelocityTracker.getYVelocity()
530                         : mVelocityTracker.getXVelocity(), false /* avoidDismissStart */,
531                         true /* log */);
532                 mMoving = false;
533                 break;
534         }
535         return true;
536     }
537 
logResizeEvent(SnapTarget snapTarget)538     private void logResizeEvent(SnapTarget snapTarget) {
539         if (snapTarget == mSplitLayout.getSnapAlgorithm().getDismissStartTarget()) {
540             MetricsLogger.action(
541                     mContext, MetricsEvent.ACTION_WINDOW_UNDOCK_MAX, dockSideTopLeft(mDockSide)
542                             ? LOG_VALUE_UNDOCK_MAX_OTHER
543                             : LOG_VALUE_UNDOCK_MAX_DOCKED);
544         } else if (snapTarget == mSplitLayout.getSnapAlgorithm().getDismissEndTarget()) {
545             MetricsLogger.action(
546                     mContext, MetricsEvent.ACTION_WINDOW_UNDOCK_MAX, dockSideBottomRight(mDockSide)
547                             ? LOG_VALUE_UNDOCK_MAX_OTHER
548                             : LOG_VALUE_UNDOCK_MAX_DOCKED);
549         } else if (snapTarget == mSplitLayout.getSnapAlgorithm().getMiddleTarget()) {
550             MetricsLogger.action(mContext, MetricsEvent.ACTION_WINDOW_DOCK_RESIZE,
551                     LOG_VALUE_RESIZE_50_50);
552         } else if (snapTarget == mSplitLayout.getSnapAlgorithm().getFirstSplitTarget()) {
553             MetricsLogger.action(mContext, MetricsEvent.ACTION_WINDOW_DOCK_RESIZE,
554                     dockSideTopLeft(mDockSide)
555                             ? LOG_VALUE_RESIZE_DOCKED_SMALLER
556                             : LOG_VALUE_RESIZE_DOCKED_LARGER);
557         } else if (snapTarget == mSplitLayout.getSnapAlgorithm().getLastSplitTarget()) {
558             MetricsLogger.action(mContext, MetricsEvent.ACTION_WINDOW_DOCK_RESIZE,
559                     dockSideTopLeft(mDockSide)
560                             ? LOG_VALUE_RESIZE_DOCKED_LARGER
561                             : LOG_VALUE_RESIZE_DOCKED_SMALLER);
562         }
563     }
564 
convertToScreenCoordinates(MotionEvent event)565     private void convertToScreenCoordinates(MotionEvent event) {
566         event.setLocation(event.getRawX(), event.getRawY());
567     }
568 
fling(int position, float velocity, boolean avoidDismissStart, boolean logMetrics)569     private void fling(int position, float velocity, boolean avoidDismissStart,
570             boolean logMetrics) {
571         DividerSnapAlgorithm currentSnapAlgorithm = getSnapAlgorithm();
572         SnapTarget snapTarget = currentSnapAlgorithm.calculateSnapTarget(position, velocity);
573         if (avoidDismissStart && snapTarget == currentSnapAlgorithm.getDismissStartTarget()) {
574             snapTarget = currentSnapAlgorithm.getFirstSplitTarget();
575         }
576         if (logMetrics) {
577             logResizeEvent(snapTarget);
578         }
579         ValueAnimator anim = getFlingAnimator(position, snapTarget, 0 /* endDelay */);
580         mFlingAnimationUtils.apply(anim, position, snapTarget.position, velocity);
581         anim.start();
582     }
583 
flingTo(int position, SnapTarget target, long duration, long startDelay, long endDelay, Interpolator interpolator)584     private void flingTo(int position, SnapTarget target, long duration, long startDelay,
585             long endDelay, Interpolator interpolator) {
586         ValueAnimator anim = getFlingAnimator(position, target, endDelay);
587         anim.setDuration(duration);
588         anim.setStartDelay(startDelay);
589         anim.setInterpolator(interpolator);
590         anim.start();
591     }
592 
getFlingAnimator(int position, final SnapTarget snapTarget, final long endDelay)593     private ValueAnimator getFlingAnimator(int position, final SnapTarget snapTarget,
594             final long endDelay) {
595         if (mCurrentAnimator != null) {
596             cancelFlingAnimation();
597             updateDockSide();
598         }
599         if (DEBUG) Slog.d(TAG, "Getting fling " + position + "->" + snapTarget.position);
600         final boolean taskPositionSameAtEnd = snapTarget.flag == SnapTarget.FLAG_NONE;
601         ValueAnimator anim = ValueAnimator.ofInt(position, snapTarget.position);
602         anim.addUpdateListener(animation -> resizeStackSurfaces((int) animation.getAnimatedValue(),
603                 taskPositionSameAtEnd && animation.getAnimatedFraction() == 1f
604                         ? TASK_POSITION_SAME
605                         : snapTarget.taskPosition,
606                 snapTarget, null /* transaction */));
607         Consumer<Boolean> endAction = cancelled -> {
608             if (DEBUG) Slog.d(TAG, "End Fling " + cancelled + " min:" + mIsInMinimizeInteraction);
609             final boolean wasMinimizeInteraction = mIsInMinimizeInteraction;
610             // Reset minimized divider position after unminimized state animation finishes.
611             if (!cancelled && !mDockedStackMinimized && mIsInMinimizeInteraction) {
612                 mIsInMinimizeInteraction = false;
613             }
614             boolean dismissed = commitSnapFlags(snapTarget);
615             mWindowManagerProxy.setResizing(false);
616             updateDockSide();
617             mCurrentAnimator = null;
618             mEntranceAnimationRunning = false;
619             mExitAnimationRunning = false;
620             if (!dismissed && !wasMinimizeInteraction) {
621                 WindowManagerProxy.applyResizeSplits(snapTarget.position, mSplitLayout);
622             }
623             if (mCallback != null) {
624                 mCallback.onDraggingEnd();
625             }
626 
627             // Record last snap target the divider moved to
628             if (!mIsInMinimizeInteraction) {
629                 // The last snapTarget position can be negative when the last divider position was
630                 // offscreen. In that case, save the middle (default) SnapTarget so calculating next
631                 // position isn't negative.
632                 final SnapTarget saveTarget;
633                 if (snapTarget.position < 0) {
634                     saveTarget = mSplitLayout.getSnapAlgorithm().getMiddleTarget();
635                 } else {
636                     saveTarget = snapTarget;
637                 }
638                 final DividerSnapAlgorithm snapAlgo = mSplitLayout.getSnapAlgorithm();
639                 if (saveTarget.position != snapAlgo.getDismissEndTarget().position
640                         && saveTarget.position != snapAlgo.getDismissStartTarget().position) {
641                     saveSnapTargetBeforeMinimized(saveTarget);
642                 }
643             }
644             notifySplitScreenBoundsChanged();
645         };
646         anim.addListener(new AnimatorListenerAdapter() {
647 
648             private boolean mCancelled;
649 
650             @Override
651             public void onAnimationCancel(Animator animation) {
652                 mCancelled = true;
653             }
654 
655             @Override
656             public void onAnimationEnd(Animator animation) {
657                 long delay = 0;
658                 if (endDelay != 0) {
659                     delay = endDelay;
660                 } else if (mCancelled) {
661                     delay = 0;
662                 }
663                 if (delay == 0) {
664                     endAction.accept(mCancelled);
665                 } else {
666                     final Boolean cancelled = mCancelled;
667                     if (DEBUG) Slog.d(TAG, "Posting endFling " + cancelled + " d:" + delay + "ms");
668                     mHandler.postDelayed(() -> endAction.accept(cancelled), delay);
669                 }
670             }
671         });
672         anim.setAnimationHandler(mAnimationHandler);
673         mCurrentAnimator = anim;
674         return anim;
675     }
676 
notifySplitScreenBoundsChanged()677     private void notifySplitScreenBoundsChanged() {
678         if (mSplitLayout.mPrimary == null || mSplitLayout.mSecondary == null) {
679             return;
680         }
681         mOtherTaskRect.set(mSplitLayout.mSecondary);
682 
683         mTmpRect.set(mHandle.getLeft(), mHandle.getTop(), mHandle.getRight(), mHandle.getBottom());
684         if (isHorizontalDivision()) {
685             mTmpRect.offsetTo(0, mDividerPositionY);
686         } else {
687             mTmpRect.offsetTo(mDividerPositionX, 0);
688         }
689         mWindowManagerProxy.setTouchRegion(mTmpRect);
690 
691         mTmpRect.set(mSplitLayout.mDisplayLayout.stableInsets());
692         switch (mSplitLayout.getPrimarySplitSide()) {
693             case WindowManager.DOCKED_LEFT:
694                 mTmpRect.left = 0;
695                 break;
696             case WindowManager.DOCKED_RIGHT:
697                 mTmpRect.right = 0;
698                 break;
699             case WindowManager.DOCKED_TOP:
700                 mTmpRect.top = 0;
701                 break;
702         }
703         Dependency.get(OverviewProxyService.class)
704                 .notifySplitScreenBoundsChanged(mOtherTaskRect, mTmpRect);
705     }
706 
cancelFlingAnimation()707     private void cancelFlingAnimation() {
708         if (mCurrentAnimator != null) {
709             mCurrentAnimator.cancel();
710         }
711     }
712 
commitSnapFlags(SnapTarget target)713     private boolean commitSnapFlags(SnapTarget target) {
714         if (target.flag == SnapTarget.FLAG_NONE) {
715             return false;
716         }
717         final boolean dismissOrMaximize;
718         if (target.flag == SnapTarget.FLAG_DISMISS_START) {
719             dismissOrMaximize = mDockSide == WindowManager.DOCKED_LEFT
720                     || mDockSide == WindowManager.DOCKED_TOP;
721         } else {
722             dismissOrMaximize = mDockSide == WindowManager.DOCKED_RIGHT
723                     || mDockSide == WindowManager.DOCKED_BOTTOM;
724         }
725         mWindowManagerProxy.dismissOrMaximizeDocked(mTiles, mSplitLayout, dismissOrMaximize);
726         Transaction t = mTiles.getTransaction();
727         setResizeDimLayer(t, true /* primary */, 0f);
728         setResizeDimLayer(t, false /* primary */, 0f);
729         t.apply();
730         mTiles.releaseTransaction(t);
731         return true;
732     }
733 
liftBackground()734     private void liftBackground() {
735         if (mBackgroundLifted) {
736             return;
737         }
738         if (isHorizontalDivision()) {
739             mBackground.animate().scaleY(1.4f);
740         } else {
741             mBackground.animate().scaleX(1.4f);
742         }
743         mBackground.animate()
744                 .setInterpolator(Interpolators.TOUCH_RESPONSE)
745                 .setDuration(TOUCH_ANIMATION_DURATION)
746                 .translationZ(mTouchElevation)
747                 .start();
748 
749         // Lift handle as well so it doesn't get behind the background, even though it doesn't
750         // cast shadow.
751         mHandle.animate()
752                 .setInterpolator(Interpolators.TOUCH_RESPONSE)
753                 .setDuration(TOUCH_ANIMATION_DURATION)
754                 .translationZ(mTouchElevation)
755                 .start();
756         mBackgroundLifted = true;
757     }
758 
releaseBackground()759     private void releaseBackground() {
760         if (!mBackgroundLifted) {
761             return;
762         }
763         mBackground.animate()
764                 .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
765                 .setDuration(TOUCH_RELEASE_ANIMATION_DURATION)
766                 .translationZ(0)
767                 .scaleX(1f)
768                 .scaleY(1f)
769                 .start();
770         mHandle.animate()
771                 .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
772                 .setDuration(TOUCH_RELEASE_ANIMATION_DURATION)
773                 .translationZ(0)
774                 .start();
775         mBackgroundLifted = false;
776     }
777 
initializeSurfaceState()778     private void initializeSurfaceState() {
779         int midPos = mSplitLayout.getSnapAlgorithm().getMiddleTarget().position;
780         // Recalculate the split-layout's internal tile bounds
781         mSplitLayout.resizeSplits(midPos);
782         Transaction t = mTiles.getTransaction();
783         if (mDockedStackMinimized) {
784             int position = mSplitLayout.getMinimizedSnapAlgorithm(mHomeStackResizable)
785                     .getMiddleTarget().position;
786             calculateBoundsForPosition(position, mDockSide, mDockedRect);
787             calculateBoundsForPosition(position, DockedDividerUtils.invertDockSide(mDockSide),
788                     mOtherRect);
789             mDividerPositionX = mDividerPositionY = position;
790             resizeSplitSurfaces(t, mDockedRect, mSplitLayout.mPrimary,
791                     mOtherRect, mSplitLayout.mSecondary);
792         } else {
793             resizeSplitSurfaces(t, mSplitLayout.mPrimary, null,
794                     mSplitLayout.mSecondary, null);
795         }
796         setResizeDimLayer(t, true /* primary */, 0.f /* alpha */);
797         setResizeDimLayer(t, false /* secondary */, 0.f /* alpha */);
798         t.apply();
799         mTiles.releaseTransaction(t);
800 
801         // Get the actually-visible bar dimensions (relative to full window). This is a thin
802         // bar going through the center.
803         final Rect dividerBar = isHorizontalDivision()
804                 ? new Rect(0, mDividerInsets, mSplitLayout.mDisplayLayout.width(),
805                 mDividerInsets + mDividerSize)
806                 : new Rect(mDividerInsets, 0, mDividerInsets + mDividerSize,
807                 mSplitLayout.mDisplayLayout.height());
808         final Region touchRegion = new Region(dividerBar);
809         // Add in the "draggable" portion. While not visible, this is an expanded area that the
810         // user can interact with.
811         touchRegion.union(new Rect(mHandle.getLeft(), mHandle.getTop(),
812                 mHandle.getRight(), mHandle.getBottom()));
813         mWindowManager.setTouchRegion(touchRegion);
814     }
815 
setMinimizedDockStack(boolean minimized, boolean isHomeStackResizable, Transaction t)816     void setMinimizedDockStack(boolean minimized, boolean isHomeStackResizable,
817             Transaction t) {
818         mHomeStackResizable = isHomeStackResizable;
819         updateDockSide();
820         if (!minimized) {
821             resetBackground();
822         }
823         mMinimizedShadow.setAlpha(minimized ? 1f : 0f);
824         if (mDockedStackMinimized != minimized) {
825             mDockedStackMinimized = minimized;
826             if (mSplitLayout.mDisplayLayout.rotation() != mDefaultDisplay.getRotation()) {
827                 // Splitscreen to minimize is about to starts after rotating landscape to seascape,
828                 // update display info and snap algorithm targets
829                 repositionSnapTargetBeforeMinimized();
830             }
831             if (mIsInMinimizeInteraction != minimized || mCurrentAnimator != null) {
832                 cancelFlingAnimation();
833                 if (minimized) {
834                     // Relayout to recalculate the divider shadow when minimizing
835                     requestLayout();
836                     mIsInMinimizeInteraction = true;
837                     resizeStackSurfaces(mSplitLayout.getMinimizedSnapAlgorithm(mHomeStackResizable)
838                             .getMiddleTarget(), t);
839                 } else {
840                     resizeStackSurfaces(mSnapTargetBeforeMinimized, t);
841                     mIsInMinimizeInteraction = false;
842                 }
843             }
844         }
845     }
846 
enterSplitMode(boolean isHomeStackResizable)847     void enterSplitMode(boolean isHomeStackResizable) {
848         post(() -> {
849             final SurfaceControl sc = getWindowSurfaceControl();
850             if (sc == null) {
851                 return;
852             }
853             Transaction t = mTiles.getTransaction();
854             t.show(sc).apply();
855             mTiles.releaseTransaction(t);
856         });
857 
858         SnapTarget miniMid =
859                 mSplitLayout.getMinimizedSnapAlgorithm(isHomeStackResizable).getMiddleTarget();
860         if (mDockedStackMinimized) {
861             mDividerPositionY = mDividerPositionX = miniMid.position;
862         }
863     }
864 
865     /**
866      * Tries to grab a surface control from ViewRootImpl. If this isn't available for some reason
867      * (ie. the window isn't ready yet), it will get the surfacecontrol that the WindowlessWM has
868      * assigned to it.
869      */
getWindowSurfaceControl()870     private SurfaceControl getWindowSurfaceControl() {
871         final ViewRootImpl root = getViewRootImpl();
872         if (root == null) {
873             return null;
874         }
875         SurfaceControl out = root.getSurfaceControl();
876         if (out != null && out.isValid()) {
877             return out;
878         }
879         return mWindowManager.mSystemWindows.getViewSurface(this);
880     }
881 
exitSplitMode()882     void exitSplitMode() {
883         // Reset tile bounds
884         final SurfaceControl sc = getWindowSurfaceControl();
885         if (sc == null) {
886             return;
887         }
888         Transaction t = mTiles.getTransaction();
889         t.hide(sc).apply();
890         mTiles.releaseTransaction(t);
891         int midPos = mSplitLayout.getSnapAlgorithm().getMiddleTarget().position;
892         WindowManagerProxy.applyResizeSplits(midPos, mSplitLayout);
893     }
894 
setMinimizedDockStack(boolean minimized, long animDuration, boolean isHomeStackResizable)895     public void setMinimizedDockStack(boolean minimized, long animDuration,
896             boolean isHomeStackResizable) {
897         if (DEBUG) Slog.d(TAG, "setMinDock: " + mDockedStackMinimized + "->" + minimized);
898         mHomeStackResizable = isHomeStackResizable;
899         updateDockSide();
900         if (mDockedStackMinimized != minimized) {
901             mIsInMinimizeInteraction = true;
902             mDockedStackMinimized = minimized;
903             stopDragging(minimized
904                             ? mSnapTargetBeforeMinimized.position
905                             : getCurrentPosition(),
906                     minimized
907                             ? mSplitLayout.getMinimizedSnapAlgorithm(mHomeStackResizable)
908                                     .getMiddleTarget()
909                             : mSnapTargetBeforeMinimized,
910                     animDuration, Interpolators.FAST_OUT_SLOW_IN, 0);
911             setAdjustedForIme(false, animDuration);
912         }
913         if (!minimized) {
914             mBackground.animate().withEndAction(mResetBackgroundRunnable);
915         }
916         mBackground.animate()
917                 .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
918                 .setDuration(animDuration)
919                 .start();
920     }
921 
922     // Needed to end any currently playing animations when they might compete with other anims
923     // (specifically, IME adjust animation immediately after leaving minimized). Someday maybe
924     // these can be unified, but not today.
finishAnimations()925     void finishAnimations() {
926         if (mCurrentAnimator != null) {
927             mCurrentAnimator.end();
928         }
929     }
930 
setAdjustedForIme(boolean adjustedForIme, long animDuration)931     public void setAdjustedForIme(boolean adjustedForIme, long animDuration) {
932         if (mAdjustedForIme == adjustedForIme) {
933             return;
934         }
935         updateDockSide();
936         mHandle.animate()
937                 .setInterpolator(IME_ADJUST_INTERPOLATOR)
938                 .setDuration(animDuration)
939                 .alpha(adjustedForIme ? 0f : 1f)
940                 .start();
941         if (mDockSide == WindowManager.DOCKED_TOP) {
942             mBackground.setPivotY(0);
943             mBackground.animate()
944                     .scaleY(adjustedForIme ? ADJUSTED_FOR_IME_SCALE : 1f);
945         }
946         if (!adjustedForIme) {
947             mBackground.animate().withEndAction(mResetBackgroundRunnable);
948         }
949         mBackground.animate()
950                 .setInterpolator(IME_ADJUST_INTERPOLATOR)
951                 .setDuration(animDuration)
952                 .start();
953         mAdjustedForIme = adjustedForIme;
954     }
955 
saveSnapTargetBeforeMinimized(SnapTarget target)956     private void saveSnapTargetBeforeMinimized(SnapTarget target) {
957         mSnapTargetBeforeMinimized = target;
958         mState.mRatioPositionBeforeMinimized = (float) target.position /
959                 (isHorizontalDivision() ? mSplitLayout.mDisplayLayout.height()
960                         : mSplitLayout.mDisplayLayout.width());
961     }
962 
resetBackground()963     private void resetBackground() {
964         mBackground.setPivotX(mBackground.getWidth() / 2);
965         mBackground.setPivotY(mBackground.getHeight() / 2);
966         mBackground.setScaleX(1f);
967         mBackground.setScaleY(1f);
968         mMinimizedShadow.setAlpha(0f);
969     }
970 
971     @Override
onConfigurationChanged(Configuration newConfig)972     protected void onConfigurationChanged(Configuration newConfig) {
973         super.onConfigurationChanged(newConfig);
974     }
975 
repositionSnapTargetBeforeMinimized()976     private void repositionSnapTargetBeforeMinimized() {
977         int position = (int) (mState.mRatioPositionBeforeMinimized *
978                 (isHorizontalDivision() ? mSplitLayout.mDisplayLayout.height()
979                         : mSplitLayout.mDisplayLayout.width()));
980 
981         // Set the snap target before minimized but do not save until divider is attached and not
982         // minimized because it does not know its minimized state yet.
983         mSnapTargetBeforeMinimized =
984                 mSplitLayout.getSnapAlgorithm().calculateNonDismissingSnapTarget(position);
985     }
986 
calculatePosition(int touchX, int touchY)987     private int calculatePosition(int touchX, int touchY) {
988         return isHorizontalDivision() ? calculateYPosition(touchY) : calculateXPosition(touchX);
989     }
990 
isHorizontalDivision()991     public boolean isHorizontalDivision() {
992         return getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT;
993     }
994 
calculateXPosition(int touchX)995     private int calculateXPosition(int touchX) {
996         return mStartPosition + touchX - mStartX;
997     }
998 
calculateYPosition(int touchY)999     private int calculateYPosition(int touchY) {
1000         return mStartPosition + touchY - mStartY;
1001     }
1002 
alignTopLeft(Rect containingRect, Rect rect)1003     private void alignTopLeft(Rect containingRect, Rect rect) {
1004         int width = rect.width();
1005         int height = rect.height();
1006         rect.set(containingRect.left, containingRect.top,
1007                 containingRect.left + width, containingRect.top + height);
1008     }
1009 
alignBottomRight(Rect containingRect, Rect rect)1010     private void alignBottomRight(Rect containingRect, Rect rect) {
1011         int width = rect.width();
1012         int height = rect.height();
1013         rect.set(containingRect.right - width, containingRect.bottom - height,
1014                 containingRect.right, containingRect.bottom);
1015     }
1016 
calculateBoundsForPosition(int position, int dockSide, Rect outRect)1017     public void calculateBoundsForPosition(int position, int dockSide, Rect outRect) {
1018         DockedDividerUtils.calculateBoundsForPosition(position, dockSide, outRect,
1019                 mSplitLayout.mDisplayLayout.width(), mSplitLayout.mDisplayLayout.height(),
1020                 mDividerSize);
1021     }
1022 
resizeStackSurfaces(SnapTarget taskSnapTarget, Transaction t)1023     private void resizeStackSurfaces(SnapTarget taskSnapTarget, Transaction t) {
1024         resizeStackSurfaces(taskSnapTarget.position, taskSnapTarget.position, taskSnapTarget, t);
1025     }
1026 
resizeSplitSurfaces(Transaction t, Rect dockedRect, Rect otherRect)1027     void resizeSplitSurfaces(Transaction t, Rect dockedRect, Rect otherRect) {
1028         resizeSplitSurfaces(t, dockedRect, null, otherRect, null);
1029     }
1030 
resizeSplitSurfaces(Transaction t, Rect dockedRect, Rect dockedTaskRect, Rect otherRect, Rect otherTaskRect)1031     private void resizeSplitSurfaces(Transaction t, Rect dockedRect, Rect dockedTaskRect,
1032             Rect otherRect, Rect otherTaskRect) {
1033         dockedTaskRect = dockedTaskRect == null ? dockedRect : dockedTaskRect;
1034         otherTaskRect = otherTaskRect == null ? otherRect : otherTaskRect;
1035 
1036         mDividerPositionX = mSplitLayout.getPrimarySplitSide() == DOCKED_RIGHT
1037                 ? otherRect.right : dockedRect.right;
1038         mDividerPositionY = dockedRect.bottom;
1039 
1040         if (DEBUG) {
1041             Slog.d(TAG, "Resizing split surfaces: " + dockedRect + " " + dockedTaskRect
1042                     + " " + otherRect + " " + otherTaskRect);
1043         }
1044 
1045         t.setPosition(mTiles.mPrimarySurface, dockedTaskRect.left, dockedTaskRect.top);
1046         Rect crop = new Rect(dockedRect);
1047         crop.offsetTo(-Math.min(dockedTaskRect.left - dockedRect.left, 0),
1048                 -Math.min(dockedTaskRect.top - dockedRect.top, 0));
1049         t.setWindowCrop(mTiles.mPrimarySurface, crop);
1050         t.setPosition(mTiles.mSecondarySurface, otherTaskRect.left, otherTaskRect.top);
1051         crop.set(otherRect);
1052         crop.offsetTo(-(otherTaskRect.left - otherRect.left),
1053                 -(otherTaskRect.top - otherRect.top));
1054         t.setWindowCrop(mTiles.mSecondarySurface, crop);
1055         final SurfaceControl dividerCtrl = getWindowSurfaceControl();
1056         if (dividerCtrl != null) {
1057             if (isHorizontalDivision()) {
1058                 t.setPosition(dividerCtrl, 0, mDividerPositionY - mDividerInsets);
1059             } else {
1060                 t.setPosition(dividerCtrl, mDividerPositionX - mDividerInsets, 0);
1061             }
1062         }
1063         if (getViewRootImpl() != null) {
1064             mHandler.removeCallbacks(mUpdateEmbeddedMatrix);
1065             mHandler.post(mUpdateEmbeddedMatrix);
1066         }
1067     }
1068 
setResizeDimLayer(Transaction t, boolean primary, float alpha)1069     void setResizeDimLayer(Transaction t, boolean primary, float alpha) {
1070         SurfaceControl dim = primary ? mTiles.mPrimaryDim : mTiles.mSecondaryDim;
1071         if (alpha <= 0.001f) {
1072             t.hide(dim);
1073         } else {
1074             t.setAlpha(dim, alpha);
1075             t.show(dim);
1076         }
1077     }
1078 
resizeStackSurfaces(int position, int taskPosition, SnapTarget taskSnapTarget, Transaction transaction)1079     void resizeStackSurfaces(int position, int taskPosition, SnapTarget taskSnapTarget,
1080             Transaction transaction) {
1081         if (mRemoved) {
1082             // This divider view has been removed so shouldn't have any additional influence.
1083             return;
1084         }
1085         calculateBoundsForPosition(position, mDockSide, mDockedRect);
1086         calculateBoundsForPosition(position, DockedDividerUtils.invertDockSide(mDockSide),
1087                 mOtherRect);
1088 
1089         if (mDockedRect.equals(mLastResizeRect) && !mEntranceAnimationRunning) {
1090             return;
1091         }
1092 
1093         // Make sure shadows are updated
1094         if (mBackground.getZ() > 0f) {
1095             mBackground.invalidate();
1096         }
1097 
1098         final boolean ownTransaction = transaction == null;
1099         final Transaction t = ownTransaction ? mTiles.getTransaction() : transaction;
1100         mLastResizeRect.set(mDockedRect);
1101         if (mIsInMinimizeInteraction) {
1102             calculateBoundsForPosition(mSnapTargetBeforeMinimized.position, mDockSide,
1103                     mDockedTaskRect);
1104             calculateBoundsForPosition(mSnapTargetBeforeMinimized.position,
1105                     DockedDividerUtils.invertDockSide(mDockSide), mOtherTaskRect);
1106 
1107             // Move a right-docked-app to line up with the divider while dragging it
1108             if (mDockSide == DOCKED_RIGHT) {
1109                 mDockedTaskRect.offset(Math.max(position, -mDividerSize)
1110                         - mDockedTaskRect.left + mDividerSize, 0);
1111             }
1112             resizeSplitSurfaces(t, mDockedRect, mDockedTaskRect, mOtherRect, mOtherTaskRect);
1113             if (ownTransaction) {
1114                 t.apply();
1115                 mTiles.releaseTransaction(t);
1116             }
1117             return;
1118         }
1119 
1120         if (mEntranceAnimationRunning && taskPosition != TASK_POSITION_SAME) {
1121             calculateBoundsForPosition(taskPosition, mDockSide, mDockedTaskRect);
1122 
1123             // Move a docked app if from the right in position with the divider up to insets
1124             if (mDockSide == DOCKED_RIGHT) {
1125                 mDockedTaskRect.offset(Math.max(position, -mDividerSize)
1126                         - mDockedTaskRect.left + mDividerSize, 0);
1127             }
1128             calculateBoundsForPosition(taskPosition, DockedDividerUtils.invertDockSide(mDockSide),
1129                     mOtherTaskRect);
1130             resizeSplitSurfaces(t, mDockedRect, mDockedTaskRect, mOtherRect, mOtherTaskRect);
1131         } else if (mExitAnimationRunning && taskPosition != TASK_POSITION_SAME) {
1132             calculateBoundsForPosition(taskPosition, mDockSide, mDockedTaskRect);
1133             mDockedInsetRect.set(mDockedTaskRect);
1134             calculateBoundsForPosition(mExitStartPosition,
1135                     DockedDividerUtils.invertDockSide(mDockSide), mOtherTaskRect);
1136             mOtherInsetRect.set(mOtherTaskRect);
1137             applyExitAnimationParallax(mOtherTaskRect, position);
1138 
1139             // Move a right-docked-app to line up with the divider while dragging it
1140             if (mDockSide == DOCKED_RIGHT) {
1141                 mDockedTaskRect.offset(position + mDividerSize, 0);
1142             }
1143             resizeSplitSurfaces(t, mDockedRect, mDockedTaskRect, mOtherRect, mOtherTaskRect);
1144         } else if (taskPosition != TASK_POSITION_SAME) {
1145             calculateBoundsForPosition(position, DockedDividerUtils.invertDockSide(mDockSide),
1146                     mOtherRect);
1147             int dockSideInverted = DockedDividerUtils.invertDockSide(mDockSide);
1148             int taskPositionDocked =
1149                     restrictDismissingTaskPosition(taskPosition, mDockSide, taskSnapTarget);
1150             int taskPositionOther =
1151                     restrictDismissingTaskPosition(taskPosition, dockSideInverted, taskSnapTarget);
1152             calculateBoundsForPosition(taskPositionDocked, mDockSide, mDockedTaskRect);
1153             calculateBoundsForPosition(taskPositionOther, dockSideInverted, mOtherTaskRect);
1154             mTmpRect.set(0, 0, mSplitLayout.mDisplayLayout.width(),
1155                     mSplitLayout.mDisplayLayout.height());
1156             alignTopLeft(mDockedRect, mDockedTaskRect);
1157             alignTopLeft(mOtherRect, mOtherTaskRect);
1158             mDockedInsetRect.set(mDockedTaskRect);
1159             mOtherInsetRect.set(mOtherTaskRect);
1160             if (dockSideTopLeft(mDockSide)) {
1161                 alignTopLeft(mTmpRect, mDockedInsetRect);
1162                 alignBottomRight(mTmpRect, mOtherInsetRect);
1163             } else {
1164                 alignBottomRight(mTmpRect, mDockedInsetRect);
1165                 alignTopLeft(mTmpRect, mOtherInsetRect);
1166             }
1167             applyDismissingParallax(mDockedTaskRect, mDockSide, taskSnapTarget, position,
1168                     taskPositionDocked);
1169             applyDismissingParallax(mOtherTaskRect, dockSideInverted, taskSnapTarget, position,
1170                     taskPositionOther);
1171             resizeSplitSurfaces(t, mDockedRect, mDockedTaskRect, mOtherRect, mOtherTaskRect);
1172         } else {
1173             resizeSplitSurfaces(t, mDockedRect, null, mOtherRect, null);
1174         }
1175         SnapTarget closestDismissTarget = getSnapAlgorithm().getClosestDismissTarget(position);
1176         float dimFraction = getDimFraction(position, closestDismissTarget);
1177         setResizeDimLayer(t, isDismissTargetPrimary(closestDismissTarget), dimFraction);
1178         if (ownTransaction) {
1179             t.apply();
1180             mTiles.releaseTransaction(t);
1181         }
1182     }
1183 
applyExitAnimationParallax(Rect taskRect, int position)1184     private void applyExitAnimationParallax(Rect taskRect, int position) {
1185         if (mDockSide == WindowManager.DOCKED_TOP) {
1186             taskRect.offset(0, (int) ((position - mExitStartPosition) * 0.25f));
1187         } else if (mDockSide == WindowManager.DOCKED_LEFT) {
1188             taskRect.offset((int) ((position - mExitStartPosition) * 0.25f), 0);
1189         } else if (mDockSide == WindowManager.DOCKED_RIGHT) {
1190             taskRect.offset((int) ((mExitStartPosition - position) * 0.25f), 0);
1191         }
1192     }
1193 
getDimFraction(int position, SnapTarget dismissTarget)1194     private float getDimFraction(int position, SnapTarget dismissTarget) {
1195         if (mEntranceAnimationRunning) {
1196             return 0f;
1197         }
1198         float fraction = getSnapAlgorithm().calculateDismissingFraction(position);
1199         fraction = Math.max(0, Math.min(fraction, 1f));
1200         fraction = DIM_INTERPOLATOR.getInterpolation(fraction);
1201         return fraction;
1202     }
1203 
1204     /**
1205      * When the snap target is dismissing one side, make sure that the dismissing side doesn't get
1206      * 0 size.
1207      */
restrictDismissingTaskPosition(int taskPosition, int dockSide, SnapTarget snapTarget)1208     private int restrictDismissingTaskPosition(int taskPosition, int dockSide,
1209             SnapTarget snapTarget) {
1210         if (snapTarget.flag == SnapTarget.FLAG_DISMISS_START && dockSideTopLeft(dockSide)) {
1211             return Math.max(mSplitLayout.getSnapAlgorithm().getFirstSplitTarget().position,
1212                     mStartPosition);
1213         } else if (snapTarget.flag == SnapTarget.FLAG_DISMISS_END
1214                 && dockSideBottomRight(dockSide)) {
1215             return Math.min(mSplitLayout.getSnapAlgorithm().getLastSplitTarget().position,
1216                     mStartPosition);
1217         } else {
1218             return taskPosition;
1219         }
1220     }
1221 
1222     /**
1223      * Applies a parallax to the task when dismissing.
1224      */
applyDismissingParallax(Rect taskRect, int dockSide, SnapTarget snapTarget, int position, int taskPosition)1225     private void applyDismissingParallax(Rect taskRect, int dockSide, SnapTarget snapTarget,
1226             int position, int taskPosition) {
1227         float fraction = Math.min(1, Math.max(0,
1228                 mSplitLayout.getSnapAlgorithm().calculateDismissingFraction(position)));
1229         SnapTarget dismissTarget = null;
1230         SnapTarget splitTarget = null;
1231         int start = 0;
1232         if (position <= mSplitLayout.getSnapAlgorithm().getLastSplitTarget().position
1233                 && dockSideTopLeft(dockSide)) {
1234             dismissTarget = mSplitLayout.getSnapAlgorithm().getDismissStartTarget();
1235             splitTarget = mSplitLayout.getSnapAlgorithm().getFirstSplitTarget();
1236             start = taskPosition;
1237         } else if (position >= mSplitLayout.getSnapAlgorithm().getLastSplitTarget().position
1238                 && dockSideBottomRight(dockSide)) {
1239             dismissTarget = mSplitLayout.getSnapAlgorithm().getDismissEndTarget();
1240             splitTarget = mSplitLayout.getSnapAlgorithm().getLastSplitTarget();
1241             start = splitTarget.position;
1242         }
1243         if (dismissTarget != null && fraction > 0f
1244                 && isDismissing(splitTarget, position, dockSide)) {
1245             fraction = calculateParallaxDismissingFraction(fraction, dockSide);
1246             int offsetPosition = (int) (start +
1247                     fraction * (dismissTarget.position - splitTarget.position));
1248             int width = taskRect.width();
1249             int height = taskRect.height();
1250             switch (dockSide) {
1251                 case WindowManager.DOCKED_LEFT:
1252                     taskRect.left = offsetPosition - width;
1253                     taskRect.right = offsetPosition;
1254                     break;
1255                 case WindowManager.DOCKED_RIGHT:
1256                     taskRect.left = offsetPosition + mDividerSize;
1257                     taskRect.right = offsetPosition + width + mDividerSize;
1258                     break;
1259                 case WindowManager.DOCKED_TOP:
1260                     taskRect.top = offsetPosition - height;
1261                     taskRect.bottom = offsetPosition;
1262                     break;
1263                 case WindowManager.DOCKED_BOTTOM:
1264                     taskRect.top = offsetPosition + mDividerSize;
1265                     taskRect.bottom = offsetPosition + height + mDividerSize;
1266                     break;
1267             }
1268         }
1269     }
1270 
1271     /**
1272      * @return for a specified {@code fraction}, this returns an adjusted value that simulates a
1273      *         slowing down parallax effect
1274      */
calculateParallaxDismissingFraction(float fraction, int dockSide)1275     private static float calculateParallaxDismissingFraction(float fraction, int dockSide) {
1276         float result = SLOWDOWN_INTERPOLATOR.getInterpolation(fraction) / 3.5f;
1277 
1278         // Less parallax at the top, just because.
1279         if (dockSide == WindowManager.DOCKED_TOP) {
1280             result /= 2f;
1281         }
1282         return result;
1283     }
1284 
isDismissing(SnapTarget snapTarget, int position, int dockSide)1285     private static boolean isDismissing(SnapTarget snapTarget, int position, int dockSide) {
1286         if (dockSide == WindowManager.DOCKED_TOP || dockSide == WindowManager.DOCKED_LEFT) {
1287             return position < snapTarget.position;
1288         } else {
1289             return position > snapTarget.position;
1290         }
1291     }
1292 
isDismissTargetPrimary(SnapTarget dismissTarget)1293     private boolean isDismissTargetPrimary(SnapTarget dismissTarget) {
1294         return (dismissTarget.flag == SnapTarget.FLAG_DISMISS_START && dockSideTopLeft(mDockSide))
1295                 || (dismissTarget.flag == SnapTarget.FLAG_DISMISS_END
1296                         && dockSideBottomRight(mDockSide));
1297     }
1298 
1299     /**
1300      * @return true if and only if {@code dockSide} is top or left
1301      */
dockSideTopLeft(int dockSide)1302     private static boolean dockSideTopLeft(int dockSide) {
1303         return dockSide == WindowManager.DOCKED_TOP || dockSide == WindowManager.DOCKED_LEFT;
1304     }
1305 
1306     /**
1307      * @return true if and only if {@code dockSide} is bottom or right
1308      */
dockSideBottomRight(int dockSide)1309     private static boolean dockSideBottomRight(int dockSide) {
1310         return dockSide == WindowManager.DOCKED_BOTTOM || dockSide == WindowManager.DOCKED_RIGHT;
1311     }
1312 
1313     @Override
onComputeInternalInsets(InternalInsetsInfo inoutInfo)1314     public void onComputeInternalInsets(InternalInsetsInfo inoutInfo) {
1315         inoutInfo.setTouchableInsets(InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
1316         inoutInfo.touchableRegion.set(mHandle.getLeft(), mHandle.getTop(), mHandle.getRight(),
1317                 mHandle.getBottom());
1318         inoutInfo.touchableRegion.op(mBackground.getLeft(), mBackground.getTop(),
1319                 mBackground.getRight(), mBackground.getBottom(), Op.UNION);
1320     }
1321 
1322     /**
1323      * Checks whether recents will grow when invoked. This happens in multi-window when recents is
1324      * very small. When invoking recents, we shrink the docked stack so recents has more space.
1325      *
1326      * @return the position of the divider when recents grows, or
1327      *         {@link #INVALID_RECENTS_GROW_TARGET} if recents won't grow
1328      */
growsRecents()1329     public int growsRecents() {
1330         boolean result = mGrowRecents
1331                 && mDockSide == WindowManager.DOCKED_TOP
1332                 && getCurrentPosition() == getSnapAlgorithm().getLastSplitTarget().position;
1333         if (result) {
1334             return getSnapAlgorithm().getMiddleTarget().position;
1335         } else {
1336             return INVALID_RECENTS_GROW_TARGET;
1337         }
1338     }
1339 
onRecentsActivityStarting()1340     void onRecentsActivityStarting() {
1341         if (mGrowRecents && mDockSide == WindowManager.DOCKED_TOP
1342                 && getSnapAlgorithm().getMiddleTarget() != getSnapAlgorithm().getLastSplitTarget()
1343                 && getCurrentPosition() == getSnapAlgorithm().getLastSplitTarget().position) {
1344             mState.growAfterRecentsDrawn = true;
1345             startDragging(false /* animate */, false /* touching */);
1346         }
1347     }
1348 
onDockedFirstAnimationFrame()1349     void onDockedFirstAnimationFrame() {
1350         saveSnapTargetBeforeMinimized(mSplitLayout.getSnapAlgorithm().getMiddleTarget());
1351     }
1352 
onDockedTopTask()1353     void onDockedTopTask() {
1354         mState.growAfterRecentsDrawn = false;
1355         mState.animateAfterRecentsDrawn = true;
1356         startDragging(false /* animate */, false /* touching */);
1357         updateDockSide();
1358         mEntranceAnimationRunning = true;
1359 
1360         resizeStackSurfaces(calculatePositionForInsetBounds(),
1361                 mSplitLayout.getSnapAlgorithm().getMiddleTarget().position,
1362                 mSplitLayout.getSnapAlgorithm().getMiddleTarget(),
1363                 null /* transaction */);
1364     }
1365 
onRecentsDrawn()1366     void onRecentsDrawn() {
1367         updateDockSide();
1368         final int position = calculatePositionForInsetBounds();
1369         if (mState.animateAfterRecentsDrawn) {
1370             mState.animateAfterRecentsDrawn = false;
1371 
1372             mHandler.post(() -> {
1373                 // Delay switching resizing mode because this might cause jank in recents animation
1374                 // that's longer than this animation.
1375                 stopDragging(position, getSnapAlgorithm().getMiddleTarget(),
1376                         mLongPressEntraceAnimDuration, Interpolators.FAST_OUT_SLOW_IN,
1377                         200 /* endDelay */);
1378             });
1379         }
1380         if (mState.growAfterRecentsDrawn) {
1381             mState.growAfterRecentsDrawn = false;
1382             updateDockSide();
1383             if (mCallback != null) {
1384                 mCallback.growRecents();
1385             }
1386             stopDragging(position, getSnapAlgorithm().getMiddleTarget(), 336,
1387                     Interpolators.FAST_OUT_SLOW_IN);
1388         }
1389     }
1390 
onUndockingTask()1391     void onUndockingTask() {
1392         int dockSide = mSplitLayout.getPrimarySplitSide();
1393         if (inSplitMode()) {
1394             startDragging(false /* animate */, false /* touching */);
1395             SnapTarget target = dockSideTopLeft(dockSide)
1396                     ? mSplitLayout.getSnapAlgorithm().getDismissEndTarget()
1397                     : mSplitLayout.getSnapAlgorithm().getDismissStartTarget();
1398 
1399             // Don't start immediately - give a little bit time to settle the drag resize change.
1400             mExitAnimationRunning = true;
1401             mExitStartPosition = getCurrentPosition();
1402             stopDragging(mExitStartPosition, target, 336 /* duration */, 100 /* startDelay */,
1403                     0 /* endDelay */, Interpolators.FAST_OUT_SLOW_IN);
1404         }
1405     }
1406 
calculatePositionForInsetBounds()1407     private int calculatePositionForInsetBounds() {
1408         mSplitLayout.mDisplayLayout.getStableBounds(mTmpRect);
1409         return DockedDividerUtils.calculatePositionForBounds(mTmpRect, mDockSide, mDividerSize);
1410     }
1411 }
1412