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