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