1 /*
2  * Copyright (C) 2020 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.common.split;
18 
19 import static android.content.res.Configuration.SCREEN_HEIGHT_DP_UNDEFINED;
20 import static android.content.res.Configuration.SCREEN_WIDTH_DP_UNDEFINED;
21 import static android.view.WindowManager.DOCKED_BOTTOM;
22 import static android.view.WindowManager.DOCKED_INVALID;
23 import static android.view.WindowManager.DOCKED_LEFT;
24 import static android.view.WindowManager.DOCKED_RIGHT;
25 import static android.view.WindowManager.DOCKED_TOP;
26 
27 import static com.android.internal.jank.InteractionJankMonitor.CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER;
28 import static com.android.internal.jank.InteractionJankMonitor.CUJ_SPLIT_SCREEN_RESIZE;
29 import static com.android.wm.shell.common.split.DividerSnapAlgorithm.SnapTarget.FLAG_DISMISS_END;
30 import static com.android.wm.shell.common.split.DividerSnapAlgorithm.SnapTarget.FLAG_DISMISS_START;
31 import static com.android.wm.shell.animation.Interpolators.DIM_INTERPOLATOR;
32 import static com.android.wm.shell.animation.Interpolators.SLOWDOWN_INTERPOLATOR;
33 import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
34 import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
35 import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
36 import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_DRAG_DIVIDER;
37 
38 import android.animation.Animator;
39 import android.animation.AnimatorListenerAdapter;
40 import android.animation.AnimatorSet;
41 import android.animation.ValueAnimator;
42 import android.annotation.NonNull;
43 import android.app.ActivityManager;
44 import android.content.Context;
45 import android.content.res.Configuration;
46 import android.content.res.Resources;
47 import android.graphics.Point;
48 import android.graphics.Rect;
49 import android.view.Display;
50 import android.view.InsetsSourceControl;
51 import android.view.InsetsState;
52 import android.view.RoundedCorner;
53 import android.view.SurfaceControl;
54 import android.view.WindowInsets;
55 import android.view.WindowManager;
56 import android.window.WindowContainerToken;
57 import android.window.WindowContainerTransaction;
58 
59 import androidx.annotation.Nullable;
60 
61 import com.android.internal.annotations.VisibleForTesting;
62 import com.android.wm.shell.R;
63 import com.android.wm.shell.ShellTaskOrganizer;
64 import com.android.wm.shell.animation.Interpolators;
65 import com.android.wm.shell.common.DisplayController;
66 import com.android.wm.shell.common.DisplayImeController;
67 import com.android.wm.shell.common.DisplayInsetsController;
68 import com.android.wm.shell.common.DisplayLayout;
69 import com.android.wm.shell.common.InteractionJankMonitorUtils;
70 import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition;
71 
72 import java.io.PrintWriter;
73 import java.util.function.Consumer;
74 
75 /**
76  * Records and handles layout of splits. Helps to calculate proper bounds when configuration or
77  * divide position changes.
78  */
79 public final class SplitLayout implements DisplayInsetsController.OnInsetsChangedListener {
80 
81     public static final int PARALLAX_NONE = 0;
82     public static final int PARALLAX_DISMISSING = 1;
83     public static final int PARALLAX_ALIGN_CENTER = 2;
84 
85     public static final int FLING_RESIZE_DURATION = 250;
86     private static final int FLING_SWITCH_DURATION = 350;
87     private static final int FLING_ENTER_DURATION = 450;
88     private static final int FLING_EXIT_DURATION = 450;
89 
90     private int mDividerWindowWidth;
91     private int mDividerInsets;
92     private int mDividerSize;
93 
94     private final Rect mTempRect = new Rect();
95     private final Rect mRootBounds = new Rect();
96     private final Rect mDividerBounds = new Rect();
97     // Bounds1 final position should be always at top or left
98     private final Rect mBounds1 = new Rect();
99     // Bounds2 final position should be always at bottom or right
100     private final Rect mBounds2 = new Rect();
101     // The temp bounds outside of display bounds for side stage when split screen inactive to avoid
102     // flicker next time active split screen.
103     private final Rect mInvisibleBounds = new Rect();
104     private final Rect mWinBounds1 = new Rect();
105     private final Rect mWinBounds2 = new Rect();
106     private final SplitLayoutHandler mSplitLayoutHandler;
107     private final SplitWindowManager mSplitWindowManager;
108     private final DisplayController mDisplayController;
109     private final DisplayImeController mDisplayImeController;
110     private final ImePositionProcessor mImePositionProcessor;
111     private final ResizingEffectPolicy mSurfaceEffectPolicy;
112     private final ShellTaskOrganizer mTaskOrganizer;
113     private final InsetsState mInsetsState = new InsetsState();
114 
115     private Context mContext;
116     @VisibleForTesting DividerSnapAlgorithm mDividerSnapAlgorithm;
117     private WindowContainerToken mWinToken1;
118     private WindowContainerToken mWinToken2;
119     private int mDividePosition;
120     private boolean mInitialized = false;
121     private boolean mFreezeDividerWindow = false;
122     private int mOrientation;
123     private int mRotation;
124     private int mDensity;
125     private int mUiMode;
126 
127     private final boolean mDimNonImeSide;
128     private ValueAnimator mDividerFlingAnimator;
129 
SplitLayout(String windowName, Context context, Configuration configuration, SplitLayoutHandler splitLayoutHandler, SplitWindowManager.ParentContainerCallbacks parentContainerCallbacks, DisplayController displayController, DisplayImeController displayImeController, ShellTaskOrganizer taskOrganizer, int parallaxType)130     public SplitLayout(String windowName, Context context, Configuration configuration,
131             SplitLayoutHandler splitLayoutHandler,
132             SplitWindowManager.ParentContainerCallbacks parentContainerCallbacks,
133             DisplayController displayController, DisplayImeController displayImeController,
134             ShellTaskOrganizer taskOrganizer, int parallaxType) {
135         mContext = context.createConfigurationContext(configuration);
136         mOrientation = configuration.orientation;
137         mRotation = configuration.windowConfiguration.getRotation();
138         mDensity = configuration.densityDpi;
139         mSplitLayoutHandler = splitLayoutHandler;
140         mDisplayController = displayController;
141         mDisplayImeController = displayImeController;
142         mSplitWindowManager = new SplitWindowManager(windowName, mContext, configuration,
143                 parentContainerCallbacks);
144         mTaskOrganizer = taskOrganizer;
145         mImePositionProcessor = new ImePositionProcessor(mContext.getDisplayId());
146         mSurfaceEffectPolicy = new ResizingEffectPolicy(parallaxType);
147 
148         updateDividerConfig(mContext);
149 
150         mRootBounds.set(configuration.windowConfiguration.getBounds());
151         mDividerSnapAlgorithm = getSnapAlgorithm(mContext, mRootBounds);
152         resetDividerPosition();
153 
154         mDimNonImeSide = mContext.getResources().getBoolean(R.bool.config_dimNonImeAttachedSide);
155 
156         updateInvisibleRect();
157     }
158 
updateDividerConfig(Context context)159     private void updateDividerConfig(Context context) {
160         final Resources resources = context.getResources();
161         final Display display = context.getDisplay();
162         final int dividerInset = resources.getDimensionPixelSize(
163                 com.android.internal.R.dimen.docked_stack_divider_insets);
164         int radius = 0;
165         RoundedCorner corner = display.getRoundedCorner(RoundedCorner.POSITION_TOP_LEFT);
166         radius = corner != null ? Math.max(radius, corner.getRadius()) : radius;
167         corner = display.getRoundedCorner(RoundedCorner.POSITION_TOP_RIGHT);
168         radius = corner != null ? Math.max(radius, corner.getRadius()) : radius;
169         corner = display.getRoundedCorner(RoundedCorner.POSITION_BOTTOM_RIGHT);
170         radius = corner != null ? Math.max(radius, corner.getRadius()) : radius;
171         corner = display.getRoundedCorner(RoundedCorner.POSITION_BOTTOM_LEFT);
172         radius = corner != null ? Math.max(radius, corner.getRadius()) : radius;
173 
174         mDividerInsets = Math.max(dividerInset, radius);
175         mDividerSize = resources.getDimensionPixelSize(R.dimen.split_divider_bar_width);
176         mDividerWindowWidth = mDividerSize + 2 * mDividerInsets;
177     }
178 
179     /** Gets bounds of the primary split with screen based coordinate. */
getBounds1()180     public Rect getBounds1() {
181         return new Rect(mBounds1);
182     }
183 
184     /** Gets bounds of the primary split with parent based coordinate. */
getRefBounds1()185     public Rect getRefBounds1() {
186         Rect outBounds = getBounds1();
187         outBounds.offset(-mRootBounds.left, -mRootBounds.top);
188         return outBounds;
189     }
190 
191     /** Gets bounds of the secondary split with screen based coordinate. */
getBounds2()192     public Rect getBounds2() {
193         return new Rect(mBounds2);
194     }
195 
196     /** Gets bounds of the secondary split with parent based coordinate. */
getRefBounds2()197     public Rect getRefBounds2() {
198         final Rect outBounds = getBounds2();
199         outBounds.offset(-mRootBounds.left, -mRootBounds.top);
200         return outBounds;
201     }
202 
203     /** Gets root bounds of the whole split layout */
getRootBounds()204     public Rect getRootBounds() {
205         return new Rect(mRootBounds);
206     }
207 
208     /** Gets bounds of divider window with screen based coordinate. */
getDividerBounds()209     public Rect getDividerBounds() {
210         return new Rect(mDividerBounds);
211     }
212 
213     /** Gets bounds of divider window with parent based coordinate. */
getRefDividerBounds()214     public Rect getRefDividerBounds() {
215         final Rect outBounds = getDividerBounds();
216         outBounds.offset(-mRootBounds.left, -mRootBounds.top);
217         return outBounds;
218     }
219 
220     /** Gets bounds of the primary split with screen based coordinate on the param Rect. */
getBounds1(Rect rect)221     public void getBounds1(Rect rect) {
222         rect.set(mBounds1);
223     }
224 
225     /** Gets bounds of the primary split with parent based coordinate on the param Rect. */
getRefBounds1(Rect rect)226     public void getRefBounds1(Rect rect) {
227         getBounds1(rect);
228         rect.offset(-mRootBounds.left, -mRootBounds.top);
229     }
230 
231     /** Gets bounds of the secondary split with screen based coordinate on the param Rect. */
getBounds2(Rect rect)232     public void getBounds2(Rect rect) {
233         rect.set(mBounds2);
234     }
235 
236     /** Gets bounds of the secondary split with parent based coordinate on the param Rect. */
getRefBounds2(Rect rect)237     public void getRefBounds2(Rect rect) {
238         getBounds2(rect);
239         rect.offset(-mRootBounds.left, -mRootBounds.top);
240     }
241 
242     /** Gets root bounds of the whole split layout on the param Rect. */
getRootBounds(Rect rect)243     public void getRootBounds(Rect rect) {
244         rect.set(mRootBounds);
245     }
246 
247     /** Gets bounds of divider window with screen based coordinate on the param Rect. */
getDividerBounds(Rect rect)248     public void getDividerBounds(Rect rect) {
249         rect.set(mDividerBounds);
250     }
251 
252     /** Gets bounds of divider window with parent based coordinate on the param Rect. */
getRefDividerBounds(Rect rect)253     public void getRefDividerBounds(Rect rect) {
254         getDividerBounds(rect);
255         rect.offset(-mRootBounds.left, -mRootBounds.top);
256     }
257 
258     /** Gets bounds size equal to root bounds but outside of screen, used for position side stage
259      * when split inactive to avoid flicker when next time active. */
getInvisibleBounds(Rect rect)260     public void getInvisibleBounds(Rect rect) {
261         rect.set(mInvisibleBounds);
262     }
263 
264     /** Returns leash of the current divider bar. */
265     @Nullable
getDividerLeash()266     public SurfaceControl getDividerLeash() {
267         return mSplitWindowManager == null ? null : mSplitWindowManager.getSurfaceControl();
268     }
269 
getDividePosition()270     int getDividePosition() {
271         return mDividePosition;
272     }
273 
274     /**
275      * Returns the divider position as a fraction from 0 to 1.
276      */
getDividerPositionAsFraction()277     public float getDividerPositionAsFraction() {
278         return Math.min(1f, Math.max(0f, isLandscape()
279                 ? (float) ((mBounds1.right + mBounds2.left) / 2f) / mBounds2.right
280                 : (float) ((mBounds1.bottom + mBounds2.top) / 2f) / mBounds2.bottom));
281     }
282 
updateInvisibleRect()283     private void updateInvisibleRect() {
284         mInvisibleBounds.set(mRootBounds.left, mRootBounds.top,
285                 isLandscape() ? mRootBounds.right / 2 : mRootBounds.right,
286                 isLandscape() ? mRootBounds.bottom : mRootBounds.bottom / 2);
287         mInvisibleBounds.offset(isLandscape() ? mRootBounds.right : 0,
288                 isLandscape() ? 0 : mRootBounds.bottom);
289     }
290 
291     /** Applies new configuration, returns {@code false} if there's no effect to the layout. */
updateConfiguration(Configuration configuration)292     public boolean updateConfiguration(Configuration configuration) {
293         // Update the split bounds when necessary. Besides root bounds changed, split bounds need to
294         // be updated when the rotation changed to cover the case that users rotated the screen 180
295         // degrees.
296         // Make sure to render the divider bar with proper resources that matching the screen
297         // orientation.
298         final int rotation = configuration.windowConfiguration.getRotation();
299         final Rect rootBounds = configuration.windowConfiguration.getBounds();
300         final int orientation = configuration.orientation;
301         final int density = configuration.densityDpi;
302         final int uiMode = configuration.uiMode;
303 
304         if (mOrientation == orientation
305                 && mRotation == rotation
306                 && mDensity == density
307                 && mUiMode == uiMode
308                 && mRootBounds.equals(rootBounds)) {
309             return false;
310         }
311 
312         mContext = mContext.createConfigurationContext(configuration);
313         mSplitWindowManager.setConfiguration(configuration);
314         mOrientation = orientation;
315         mTempRect.set(mRootBounds);
316         mRootBounds.set(rootBounds);
317         mRotation = rotation;
318         mDensity = density;
319         mUiMode = uiMode;
320         mDividerSnapAlgorithm = getSnapAlgorithm(mContext, mRootBounds);
321         updateDividerConfig(mContext);
322         initDividerPosition(mTempRect);
323         updateInvisibleRect();
324 
325         return true;
326     }
327 
328     /** Rotate the layout to specific rotation and calculate new bounds. The stable insets value
329      *  should be calculated by display layout. */
rotateTo(int newRotation)330     public void rotateTo(int newRotation) {
331         final int rotationDelta = (newRotation - mRotation + 4) % 4;
332         final boolean changeOrient = (rotationDelta % 2) != 0;
333 
334         mRotation = newRotation;
335         Rect tmpRect = new Rect(mRootBounds);
336         if (changeOrient) {
337             tmpRect.set(mRootBounds.top, mRootBounds.left, mRootBounds.bottom, mRootBounds.right);
338         }
339 
340         // We only need new bounds here, other configuration should be update later.
341         mTempRect.set(mRootBounds);
342         mRootBounds.set(tmpRect);
343         mDividerSnapAlgorithm = getSnapAlgorithm(mContext, mRootBounds);
344         initDividerPosition(mTempRect);
345     }
346 
initDividerPosition(Rect oldBounds)347     private void initDividerPosition(Rect oldBounds) {
348         final float snapRatio = (float) mDividePosition
349                 / (float) (isLandscape(oldBounds) ? oldBounds.width() : oldBounds.height());
350         // Estimate position by previous ratio.
351         final float length =
352                 (float) (isLandscape() ? mRootBounds.width() : mRootBounds.height());
353         final int estimatePosition = (int) (length * snapRatio);
354         // Init divider position by estimated position using current bounds snap algorithm.
355         mDividePosition = mDividerSnapAlgorithm.calculateNonDismissingSnapTarget(
356                 estimatePosition).position;
357         updateBounds(mDividePosition);
358     }
359 
updateBounds(int position)360     private void updateBounds(int position) {
361         updateBounds(position, mBounds1, mBounds2, mDividerBounds, true /* setEffectBounds */);
362     }
363 
364     /** Updates recording bounds of divider window and both of the splits. */
updateBounds(int position, Rect bounds1, Rect bounds2, Rect dividerBounds, boolean setEffectBounds)365     private void updateBounds(int position, Rect bounds1, Rect bounds2, Rect dividerBounds,
366             boolean setEffectBounds) {
367         dividerBounds.set(mRootBounds);
368         bounds1.set(mRootBounds);
369         bounds2.set(mRootBounds);
370         final boolean isLandscape = isLandscape(mRootBounds);
371         if (isLandscape) {
372             position += mRootBounds.left;
373             dividerBounds.left = position - mDividerInsets;
374             dividerBounds.right = dividerBounds.left + mDividerWindowWidth;
375             bounds1.right = position;
376             bounds2.left = bounds1.right + mDividerSize;
377         } else {
378             position += mRootBounds.top;
379             dividerBounds.top = position - mDividerInsets;
380             dividerBounds.bottom = dividerBounds.top + mDividerWindowWidth;
381             bounds1.bottom = position;
382             bounds2.top = bounds1.bottom + mDividerSize;
383         }
384         DockedDividerUtils.sanitizeStackBounds(bounds1, true /** topLeft */);
385         DockedDividerUtils.sanitizeStackBounds(bounds2, false /** topLeft */);
386         if (setEffectBounds) {
387             mSurfaceEffectPolicy.applyDividerPosition(position, isLandscape);
388         }
389     }
390 
391     /** Inflates {@link DividerView} on the root surface. */
init()392     public void init() {
393         if (mInitialized) return;
394         mInitialized = true;
395         mSplitWindowManager.init(this, mInsetsState);
396         mDisplayImeController.addPositionProcessor(mImePositionProcessor);
397     }
398 
399     /** Releases the surface holding the current {@link DividerView}. */
release(SurfaceControl.Transaction t)400     public void release(SurfaceControl.Transaction t) {
401         if (!mInitialized) return;
402         mInitialized = false;
403         mSplitWindowManager.release(t);
404         mDisplayImeController.removePositionProcessor(mImePositionProcessor);
405         mImePositionProcessor.reset();
406         if (mDividerFlingAnimator != null) {
407             mDividerFlingAnimator.cancel();
408         }
409         resetDividerPosition();
410     }
411 
release()412     public void release() {
413         release(null /* t */);
414     }
415 
416     /** Releases and re-inflates {@link DividerView} on the root surface. */
update(SurfaceControl.Transaction t)417     public void update(SurfaceControl.Transaction t) {
418         if (!mInitialized) {
419             init();
420             return;
421         }
422         mSplitWindowManager.release(t);
423         mImePositionProcessor.reset();
424         mSplitWindowManager.init(this, mInsetsState);
425     }
426 
427     @Override
insetsChanged(InsetsState insetsState)428     public void insetsChanged(InsetsState insetsState) {
429         mInsetsState.set(insetsState);
430         if (!mInitialized) {
431             return;
432         }
433         if (mFreezeDividerWindow) {
434             // DO NOT change its layout before transition actually run because it might cause
435             // flicker.
436             return;
437         }
438         mSplitWindowManager.onInsetsChanged(insetsState);
439     }
440 
441     @Override
insetsControlChanged(InsetsState insetsState, InsetsSourceControl[] activeControls)442     public void insetsControlChanged(InsetsState insetsState,
443             InsetsSourceControl[] activeControls) {
444         if (!mInsetsState.equals(insetsState)) {
445             insetsChanged(insetsState);
446         }
447     }
448 
setFreezeDividerWindow(boolean freezeDividerWindow)449     public void setFreezeDividerWindow(boolean freezeDividerWindow) {
450         mFreezeDividerWindow = freezeDividerWindow;
451     }
452 
453     /** Update current layout as divider put on start or end position. */
setDividerAtBorder(boolean start)454     public void setDividerAtBorder(boolean start) {
455         final int pos = start ? mDividerSnapAlgorithm.getDismissStartTarget().position
456                 : mDividerSnapAlgorithm.getDismissEndTarget().position;
457         setDividePosition(pos, false /* applyLayoutChange */);
458     }
459 
460     /**
461      * Updates bounds with the passing position. Usually used to update recording bounds while
462      * performing animation or dragging divider bar to resize the splits.
463      */
updateDivideBounds(int position)464     void updateDivideBounds(int position) {
465         updateBounds(position);
466         mSplitLayoutHandler.onLayoutSizeChanging(this, mSurfaceEffectPolicy.mParallaxOffset.x,
467                 mSurfaceEffectPolicy.mParallaxOffset.y);
468     }
469 
setDividePosition(int position, boolean applyLayoutChange)470     void setDividePosition(int position, boolean applyLayoutChange) {
471         mDividePosition = position;
472         updateBounds(mDividePosition);
473         if (applyLayoutChange) {
474             mSplitLayoutHandler.onLayoutSizeChanged(this);
475         }
476     }
477 
478     /** Updates divide position and split bounds base on the ratio within root bounds. */
setDivideRatio(float ratio)479     public void setDivideRatio(float ratio) {
480         final int position = isLandscape()
481                 ? mRootBounds.left + (int) (mRootBounds.width() * ratio)
482                 : mRootBounds.top + (int) (mRootBounds.height() * ratio);
483         final DividerSnapAlgorithm.SnapTarget snapTarget =
484                 mDividerSnapAlgorithm.calculateNonDismissingSnapTarget(position);
485         setDividePosition(snapTarget.position, false /* applyLayoutChange */);
486     }
487 
488     /** Resets divider position. */
resetDividerPosition()489     public void resetDividerPosition() {
490         mDividePosition = mDividerSnapAlgorithm.getMiddleTarget().position;
491         updateBounds(mDividePosition);
492         mWinToken1 = null;
493         mWinToken2 = null;
494         mWinBounds1.setEmpty();
495         mWinBounds2.setEmpty();
496     }
497 
498     /**
499      * Set divider should interactive to user or not.
500      *
501      * @param interactive divider interactive.
502      * @param hideHandle divider handle hidden or not, only work when interactive is false.
503      * @param from caller from where.
504      */
setDividerInteractive(boolean interactive, boolean hideHandle, String from)505     public void setDividerInteractive(boolean interactive, boolean hideHandle, String from) {
506         mSplitWindowManager.setInteractive(interactive, hideHandle, from);
507     }
508 
509     /**
510      * Sets new divide position and updates bounds correspondingly. Notifies listener if the new
511      * target indicates dismissing split.
512      */
snapToTarget(int currentPosition, DividerSnapAlgorithm.SnapTarget snapTarget)513     public void snapToTarget(int currentPosition, DividerSnapAlgorithm.SnapTarget snapTarget) {
514         switch (snapTarget.flag) {
515             case FLAG_DISMISS_START:
516                 flingDividePosition(currentPosition, snapTarget.position, FLING_RESIZE_DURATION,
517                         () -> mSplitLayoutHandler.onSnappedToDismiss(false /* bottomOrRight */,
518                                 EXIT_REASON_DRAG_DIVIDER));
519                 break;
520             case FLAG_DISMISS_END:
521                 flingDividePosition(currentPosition, snapTarget.position, FLING_RESIZE_DURATION,
522                         () -> mSplitLayoutHandler.onSnappedToDismiss(true /* bottomOrRight */,
523                                 EXIT_REASON_DRAG_DIVIDER));
524                 break;
525             default:
526                 flingDividePosition(currentPosition, snapTarget.position, FLING_RESIZE_DURATION,
527                         () -> setDividePosition(snapTarget.position, true /* applyLayoutChange */));
528                 break;
529         }
530     }
531 
onStartDragging()532     void onStartDragging() {
533         InteractionJankMonitorUtils.beginTracing(CUJ_SPLIT_SCREEN_RESIZE, mContext,
534                 getDividerLeash(), null /* tag */);
535     }
536 
onDraggingCancelled()537     void onDraggingCancelled() {
538         InteractionJankMonitorUtils.cancelTracing(CUJ_SPLIT_SCREEN_RESIZE);
539     }
540 
onDoubleTappedDivider()541     void onDoubleTappedDivider() {
542         mSplitLayoutHandler.onDoubleTappedDivider();
543     }
544 
545     /**
546      * Returns {@link DividerSnapAlgorithm.SnapTarget} which matches passing position and velocity.
547      * If hardDismiss is set to {@code true}, it will be harder to reach dismiss target.
548      */
findSnapTarget(int position, float velocity, boolean hardDismiss)549     public DividerSnapAlgorithm.SnapTarget findSnapTarget(int position, float velocity,
550             boolean hardDismiss) {
551         return mDividerSnapAlgorithm.calculateSnapTarget(position, velocity, hardDismiss);
552     }
553 
getSnapAlgorithm(Context context, Rect rootBounds)554     private DividerSnapAlgorithm getSnapAlgorithm(Context context, Rect rootBounds) {
555         final boolean isLandscape = isLandscape(rootBounds);
556         final Rect insets = getDisplayStableInsets(context);
557 
558         // Make split axis insets value same as the larger one to avoid bounds1 and bounds2
559         // have difference for avoiding size-compat mode when switching unresizable apps in
560         // landscape while they are letterboxed.
561         if (!isLandscape) {
562             final int largerInsets = Math.max(insets.top, insets.bottom);
563             insets.set(insets.left, largerInsets, insets.right, largerInsets);
564         }
565 
566         return new DividerSnapAlgorithm(
567                 context.getResources(),
568                 rootBounds.width(),
569                 rootBounds.height(),
570                 mDividerSize,
571                 !isLandscape,
572                 insets,
573                 isLandscape ? DOCKED_LEFT : DOCKED_TOP /* dockSide */);
574     }
575 
576     /** Fling divider from current position to end or start position then exit */
flingDividerToDismiss(boolean toEnd, int reason)577     public void flingDividerToDismiss(boolean toEnd, int reason) {
578         final int target = toEnd ? mDividerSnapAlgorithm.getDismissEndTarget().position
579                 : mDividerSnapAlgorithm.getDismissStartTarget().position;
580         flingDividePosition(getDividePosition(), target, FLING_EXIT_DURATION,
581                 () -> mSplitLayoutHandler.onSnappedToDismiss(toEnd, reason));
582     }
583 
584     /** Fling divider from current position to center position. */
flingDividerToCenter()585     public void flingDividerToCenter() {
586         final int pos = mDividerSnapAlgorithm.getMiddleTarget().position;
587         flingDividePosition(getDividePosition(), pos, FLING_ENTER_DURATION,
588                 () -> setDividePosition(pos, true /* applyLayoutChange */));
589     }
590 
591     @VisibleForTesting
flingDividePosition(int from, int to, int duration, @Nullable Runnable flingFinishedCallback)592     void flingDividePosition(int from, int to, int duration,
593             @Nullable Runnable flingFinishedCallback) {
594         if (from == to) {
595             if (flingFinishedCallback != null) {
596                 flingFinishedCallback.run();
597             }
598             InteractionJankMonitorUtils.endTracing(
599                     CUJ_SPLIT_SCREEN_RESIZE);
600             return;
601         }
602 
603         if (mDividerFlingAnimator != null) {
604             mDividerFlingAnimator.cancel();
605         }
606 
607         mDividerFlingAnimator = ValueAnimator
608                 .ofInt(from, to)
609                 .setDuration(duration);
610         mDividerFlingAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
611         mDividerFlingAnimator.addUpdateListener(
612                 animation -> updateDivideBounds((int) animation.getAnimatedValue()));
613         mDividerFlingAnimator.addListener(new AnimatorListenerAdapter() {
614             @Override
615             public void onAnimationEnd(Animator animation) {
616                 if (flingFinishedCallback != null) {
617                     flingFinishedCallback.run();
618                 }
619                 InteractionJankMonitorUtils.endTracing(
620                         CUJ_SPLIT_SCREEN_RESIZE);
621                 mDividerFlingAnimator = null;
622             }
623 
624             @Override
625             public void onAnimationCancel(Animator animation) {
626                 mDividerFlingAnimator = null;
627             }
628         });
629         mDividerFlingAnimator.start();
630     }
631 
632     /** Switch both surface position with animation. */
splitSwitching(SurfaceControl.Transaction t, SurfaceControl leash1, SurfaceControl leash2, Consumer<Rect> finishCallback)633     public void splitSwitching(SurfaceControl.Transaction t, SurfaceControl leash1,
634             SurfaceControl leash2, Consumer<Rect> finishCallback) {
635         final boolean isLandscape = isLandscape();
636         final Rect insets = getDisplayStableInsets(mContext);
637         insets.set(isLandscape ? insets.left : 0, isLandscape ? 0 : insets.top,
638                 isLandscape ? insets.right : 0, isLandscape ? 0 : insets.bottom);
639 
640         final int dividerPos = mDividerSnapAlgorithm.calculateNonDismissingSnapTarget(
641                 isLandscape ? mBounds2.width() : mBounds2.height()).position;
642         final Rect distBounds1 = new Rect();
643         final Rect distBounds2 = new Rect();
644         final Rect distDividerBounds = new Rect();
645         // Compute dist bounds.
646         updateBounds(dividerPos, distBounds2, distBounds1, distDividerBounds,
647                 false /* setEffectBounds */);
648         // Offset to real position under root container.
649         distBounds1.offset(-mRootBounds.left, -mRootBounds.top);
650         distBounds2.offset(-mRootBounds.left, -mRootBounds.top);
651         distDividerBounds.offset(-mRootBounds.left, -mRootBounds.top);
652 
653         ValueAnimator animator1 = moveSurface(t, leash1, getRefBounds1(), distBounds1,
654                 -insets.left, -insets.top);
655         ValueAnimator animator2 = moveSurface(t, leash2, getRefBounds2(), distBounds2,
656                 insets.left, insets.top);
657         ValueAnimator animator3 = moveSurface(t, getDividerLeash(), getRefDividerBounds(),
658                 distDividerBounds, 0 /* offsetX */, 0 /* offsetY */);
659 
660         AnimatorSet set = new AnimatorSet();
661         set.playTogether(animator1, animator2, animator3);
662         set.setDuration(FLING_SWITCH_DURATION);
663         set.addListener(new AnimatorListenerAdapter() {
664             @Override
665             public void onAnimationStart(Animator animation) {
666                 InteractionJankMonitorUtils.beginTracing(CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER,
667                         mContext, getDividerLeash(), null /*tag*/);
668             }
669 
670             @Override
671             public void onAnimationEnd(Animator animation) {
672                 mDividePosition = dividerPos;
673                 updateBounds(mDividePosition);
674                 finishCallback.accept(insets);
675                 InteractionJankMonitorUtils.endTracing(CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER);
676             }
677 
678             @Override
679             public void onAnimationCancel(Animator animation) {
680                 InteractionJankMonitorUtils.cancelTracing(CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER);
681             }
682         });
683         set.start();
684     }
685 
moveSurface(SurfaceControl.Transaction t, SurfaceControl leash, Rect start, Rect end, float offsetX, float offsetY)686     private ValueAnimator moveSurface(SurfaceControl.Transaction t, SurfaceControl leash,
687             Rect start, Rect end, float offsetX, float offsetY) {
688         Rect tempStart = new Rect(start);
689         Rect tempEnd = new Rect(end);
690         final float diffX = tempEnd.left - tempStart.left;
691         final float diffY = tempEnd.top - tempStart.top;
692         final float diffWidth = tempEnd.width() - tempStart.width();
693         final float diffHeight = tempEnd.height() - tempStart.height();
694         ValueAnimator animator = ValueAnimator.ofFloat(0, 1);
695         animator.addUpdateListener(animation -> {
696             if (leash == null) return;
697 
698             final float scale = (float) animation.getAnimatedValue();
699             final float distX = tempStart.left + scale * diffX;
700             final float distY = tempStart.top + scale * diffY;
701             final int width = (int) (tempStart.width() + scale * diffWidth);
702             final int height = (int) (tempStart.height() + scale * diffHeight);
703             if (offsetX == 0 && offsetY == 0) {
704                 t.setPosition(leash, distX, distY);
705                 t.setWindowCrop(leash, width, height);
706             } else {
707                 final int diffOffsetX = (int) (scale * offsetX);
708                 final int diffOffsetY = (int) (scale * offsetY);
709                 t.setPosition(leash, distX + diffOffsetX, distY + diffOffsetY);
710                 mTempRect.set(0, 0, width, height);
711                 mTempRect.offsetTo(-diffOffsetX, -diffOffsetY);
712                 t.setCrop(leash, mTempRect);
713             }
714             t.apply();
715         });
716         return animator;
717     }
718 
getDisplayStableInsets(Context context)719     private Rect getDisplayStableInsets(Context context) {
720         final DisplayLayout displayLayout =
721                 mDisplayController.getDisplayLayout(context.getDisplayId());
722         return displayLayout != null
723                 ? displayLayout.stableInsets()
724                 : context.getSystemService(WindowManager.class)
725                         .getMaximumWindowMetrics()
726                         .getWindowInsets()
727                         .getInsetsIgnoringVisibility(WindowInsets.Type.systemBars()
728                                 | WindowInsets.Type.displayCutout())
729                         .toRect();
730     }
731 
isLandscape(Rect bounds)732     private static boolean isLandscape(Rect bounds) {
733         return bounds.width() > bounds.height();
734     }
735 
736     /**
737      * Return if this layout is landscape.
738      */
isLandscape()739     public boolean isLandscape() {
740         return isLandscape(mRootBounds);
741     }
742 
743     /** Apply recorded surface layout to the {@link SurfaceControl.Transaction}. */
applySurfaceChanges(SurfaceControl.Transaction t, SurfaceControl leash1, SurfaceControl leash2, SurfaceControl dimLayer1, SurfaceControl dimLayer2, boolean applyResizingOffset)744     public void applySurfaceChanges(SurfaceControl.Transaction t, SurfaceControl leash1,
745             SurfaceControl leash2, SurfaceControl dimLayer1, SurfaceControl dimLayer2,
746             boolean applyResizingOffset) {
747         final SurfaceControl dividerLeash = getDividerLeash();
748         if (dividerLeash != null) {
749             getRefDividerBounds(mTempRect);
750             t.setPosition(dividerLeash, mTempRect.left, mTempRect.top);
751             // Resets layer of divider bar to make sure it is always on top.
752             t.setLayer(dividerLeash, Integer.MAX_VALUE);
753         }
754         getRefBounds1(mTempRect);
755         t.setPosition(leash1, mTempRect.left, mTempRect.top)
756                 .setWindowCrop(leash1, mTempRect.width(), mTempRect.height());
757         getRefBounds2(mTempRect);
758         t.setPosition(leash2, mTempRect.left, mTempRect.top)
759                 .setWindowCrop(leash2, mTempRect.width(), mTempRect.height());
760 
761         if (mImePositionProcessor.adjustSurfaceLayoutForIme(
762                 t, dividerLeash, leash1, leash2, dimLayer1, dimLayer2)) {
763             return;
764         }
765 
766         mSurfaceEffectPolicy.adjustDimSurface(t, dimLayer1, dimLayer2);
767         if (applyResizingOffset) {
768             mSurfaceEffectPolicy.adjustRootSurface(t, leash1, leash2);
769         }
770     }
771 
772     /** Apply recorded task layout to the {@link WindowContainerTransaction}.
773      *
774      * @return true if stage bounds actually update.
775      */
applyTaskChanges(WindowContainerTransaction wct, ActivityManager.RunningTaskInfo task1, ActivityManager.RunningTaskInfo task2)776     public boolean applyTaskChanges(WindowContainerTransaction wct,
777             ActivityManager.RunningTaskInfo task1, ActivityManager.RunningTaskInfo task2) {
778         boolean boundsChanged = false;
779         if (!mBounds1.equals(mWinBounds1) || !task1.token.equals(mWinToken1)) {
780             setTaskBounds(wct, task1, mBounds1);
781             mWinBounds1.set(mBounds1);
782             mWinToken1 = task1.token;
783             boundsChanged = true;
784         }
785         if (!mBounds2.equals(mWinBounds2) || !task2.token.equals(mWinToken2)) {
786             setTaskBounds(wct, task2, mBounds2);
787             mWinBounds2.set(mBounds2);
788             mWinToken2 = task2.token;
789             boundsChanged = true;
790         }
791         return boundsChanged;
792     }
793 
794     /** Set bounds to the {@link WindowContainerTransaction} for single task. */
setTaskBounds(WindowContainerTransaction wct, ActivityManager.RunningTaskInfo task, Rect bounds)795     public void setTaskBounds(WindowContainerTransaction wct,
796             ActivityManager.RunningTaskInfo task, Rect bounds) {
797         wct.setBounds(task.token, bounds);
798         wct.setSmallestScreenWidthDp(task.token, getSmallestWidthDp(bounds));
799     }
800 
getSmallestWidthDp(Rect bounds)801     private int getSmallestWidthDp(Rect bounds) {
802         mTempRect.set(bounds);
803         mTempRect.inset(getDisplayStableInsets(mContext));
804         final int minWidth = Math.min(mTempRect.width(), mTempRect.height());
805         final float density = mContext.getResources().getDisplayMetrics().density;
806         return (int) (minWidth / density);
807     }
808 
809     /**
810      * Shift configuration bounds to prevent client apps get configuration changed or relaunch. And
811      * restore shifted configuration bounds if it's no longer shifted.
812      */
applyLayoutOffsetTarget(WindowContainerTransaction wct, int offsetX, int offsetY, ActivityManager.RunningTaskInfo taskInfo1, ActivityManager.RunningTaskInfo taskInfo2)813     public void applyLayoutOffsetTarget(WindowContainerTransaction wct, int offsetX, int offsetY,
814             ActivityManager.RunningTaskInfo taskInfo1, ActivityManager.RunningTaskInfo taskInfo2) {
815         if (offsetX == 0 && offsetY == 0) {
816             wct.setBounds(taskInfo1.token, mBounds1);
817             wct.setScreenSizeDp(taskInfo1.token,
818                     SCREEN_WIDTH_DP_UNDEFINED, SCREEN_HEIGHT_DP_UNDEFINED);
819 
820             wct.setBounds(taskInfo2.token, mBounds2);
821             wct.setScreenSizeDp(taskInfo2.token,
822                     SCREEN_WIDTH_DP_UNDEFINED, SCREEN_HEIGHT_DP_UNDEFINED);
823         } else {
824             getBounds1(mTempRect);
825             mTempRect.offset(offsetX, offsetY);
826             wct.setBounds(taskInfo1.token, mTempRect);
827             wct.setScreenSizeDp(taskInfo1.token,
828                     taskInfo1.configuration.screenWidthDp,
829                     taskInfo1.configuration.screenHeightDp);
830 
831             getBounds2(mTempRect);
832             mTempRect.offset(offsetX, offsetY);
833             wct.setBounds(taskInfo2.token, mTempRect);
834             wct.setScreenSizeDp(taskInfo2.token,
835                     taskInfo2.configuration.screenWidthDp,
836                     taskInfo2.configuration.screenHeightDp);
837         }
838     }
839 
840     /** Dumps the current split bounds recorded in this layout. */
dump(@onNull PrintWriter pw, String prefix)841     public void dump(@NonNull PrintWriter pw, String prefix) {
842         pw.println(prefix + "bounds1=" + mBounds1.toShortString());
843         pw.println(prefix + "dividerBounds=" + mDividerBounds.toShortString());
844         pw.println(prefix + "bounds2=" + mBounds2.toShortString());
845     }
846 
847     /** Handles layout change event. */
848     public interface SplitLayoutHandler {
849 
850         /** Calls when dismissing split. */
onSnappedToDismiss(boolean snappedToEnd, int reason)851         void onSnappedToDismiss(boolean snappedToEnd, int reason);
852 
853         /**
854          * Calls when resizing the split bounds.
855          *
856          * @see #applySurfaceChanges(SurfaceControl.Transaction, SurfaceControl, SurfaceControl,
857          * SurfaceControl, SurfaceControl, boolean)
858          */
onLayoutSizeChanging(SplitLayout layout, int offsetX, int offsetY)859         void onLayoutSizeChanging(SplitLayout layout, int offsetX, int offsetY);
860 
861         /**
862          * Calls when finish resizing the split bounds.
863          *
864          * @see #applyTaskChanges(WindowContainerTransaction, ActivityManager.RunningTaskInfo,
865          * ActivityManager.RunningTaskInfo)
866          * @see #applySurfaceChanges(SurfaceControl.Transaction, SurfaceControl, SurfaceControl,
867          * SurfaceControl, SurfaceControl, boolean)
868          */
onLayoutSizeChanged(SplitLayout layout)869         void onLayoutSizeChanged(SplitLayout layout);
870 
871         /**
872          * Calls when re-positioning the split bounds. Like moving split bounds while showing IME
873          * panel.
874          *
875          * @see #applySurfaceChanges(SurfaceControl.Transaction, SurfaceControl, SurfaceControl,
876          * SurfaceControl, SurfaceControl, boolean)
877          */
onLayoutPositionChanging(SplitLayout layout)878         void onLayoutPositionChanging(SplitLayout layout);
879 
880         /**
881          * Notifies the target offset for shifting layout. So layout handler can shift configuration
882          * bounds correspondingly to make sure client apps won't get configuration changed or
883          * relaunched. If the layout is no longer shifted, layout handler should restore shifted
884          * configuration bounds.
885          *
886          * @see #applyLayoutOffsetTarget(WindowContainerTransaction, int, int,
887          * ActivityManager.RunningTaskInfo, ActivityManager.RunningTaskInfo)
888          */
setLayoutOffsetTarget(int offsetX, int offsetY, SplitLayout layout)889         void setLayoutOffsetTarget(int offsetX, int offsetY, SplitLayout layout);
890 
891         /** Calls when user double tapped on the divider bar. */
onDoubleTappedDivider()892         default void onDoubleTappedDivider() {
893         }
894 
895         /** Returns split position of the token. */
896         @SplitPosition
getSplitItemPosition(WindowContainerToken token)897         int getSplitItemPosition(WindowContainerToken token);
898     }
899 
900     /**
901      * Calculates and applies proper dismissing parallax offset and dimming value to hint users
902      * dismissing gesture.
903      */
904     private class ResizingEffectPolicy {
905         /** Indicates whether to offset splitting bounds to hint dismissing progress or not. */
906         private final int mParallaxType;
907 
908         int mShrinkSide = DOCKED_INVALID;
909 
910         // The current dismissing side.
911         int mDismissingSide = DOCKED_INVALID;
912 
913         // The parallax offset to hint the dismissing side and progress.
914         final Point mParallaxOffset = new Point();
915 
916         // The dimming value to hint the dismissing side and progress.
917         float mDismissingDimValue = 0.0f;
918         final Rect mContentBounds = new Rect();
919         final Rect mSurfaceBounds = new Rect();
920 
ResizingEffectPolicy(int parallaxType)921         ResizingEffectPolicy(int parallaxType) {
922             mParallaxType = parallaxType;
923         }
924 
925         /**
926          * Applies a parallax to the task to hint dismissing progress.
927          *
928          * @param position    the split position to apply dismissing parallax effect
929          * @param isLandscape indicates whether it's splitting horizontally or vertically
930          */
applyDividerPosition(int position, boolean isLandscape)931         void applyDividerPosition(int position, boolean isLandscape) {
932             mDismissingSide = DOCKED_INVALID;
933             mParallaxOffset.set(0, 0);
934             mDismissingDimValue = 0;
935 
936             int totalDismissingDistance = 0;
937             if (position < mDividerSnapAlgorithm.getFirstSplitTarget().position) {
938                 mDismissingSide = isLandscape ? DOCKED_LEFT : DOCKED_TOP;
939                 totalDismissingDistance = mDividerSnapAlgorithm.getDismissStartTarget().position
940                         - mDividerSnapAlgorithm.getFirstSplitTarget().position;
941             } else if (position > mDividerSnapAlgorithm.getLastSplitTarget().position) {
942                 mDismissingSide = isLandscape ? DOCKED_RIGHT : DOCKED_BOTTOM;
943                 totalDismissingDistance = mDividerSnapAlgorithm.getLastSplitTarget().position
944                         - mDividerSnapAlgorithm.getDismissEndTarget().position;
945             }
946 
947             final boolean topLeftShrink = isLandscape
948                     ? position < mWinBounds1.right : position < mWinBounds1.bottom;
949             if (topLeftShrink) {
950                 mShrinkSide = isLandscape ? DOCKED_LEFT : DOCKED_TOP;
951                 mContentBounds.set(mWinBounds1);
952                 mSurfaceBounds.set(mBounds1);
953             } else {
954                 mShrinkSide = isLandscape ? DOCKED_RIGHT : DOCKED_BOTTOM;
955                 mContentBounds.set(mWinBounds2);
956                 mSurfaceBounds.set(mBounds2);
957             }
958 
959             if (mDismissingSide != DOCKED_INVALID) {
960                 float fraction = Math.max(0,
961                         Math.min(mDividerSnapAlgorithm.calculateDismissingFraction(position), 1f));
962                 mDismissingDimValue = DIM_INTERPOLATOR.getInterpolation(fraction);
963                 if (mParallaxType == PARALLAX_DISMISSING) {
964                     fraction = calculateParallaxDismissingFraction(fraction, mDismissingSide);
965                     if (isLandscape) {
966                         mParallaxOffset.x = (int) (fraction * totalDismissingDistance);
967                     } else {
968                         mParallaxOffset.y = (int) (fraction * totalDismissingDistance);
969                     }
970                 }
971             }
972 
973             if (mParallaxType == PARALLAX_ALIGN_CENTER) {
974                 if (isLandscape) {
975                     mParallaxOffset.x =
976                             (mSurfaceBounds.width() - mContentBounds.width()) / 2;
977                 } else {
978                     mParallaxOffset.y =
979                             (mSurfaceBounds.height() - mContentBounds.height()) / 2;
980                 }
981             }
982         }
983 
984         /**
985          * @return for a specified {@code fraction}, this returns an adjusted value that simulates a
986          * slowing down parallax effect
987          */
988         private float calculateParallaxDismissingFraction(float fraction, int dockSide) {
989             float result = SLOWDOWN_INTERPOLATOR.getInterpolation(fraction) / 3.5f;
990 
991             // Less parallax at the top, just because.
992             if (dockSide == WindowManager.DOCKED_TOP) {
993                 result /= 2f;
994             }
995             return result;
996         }
997 
998         /** Applies parallax offset and dimming value to the root surface at the dismissing side. */
999         void adjustRootSurface(SurfaceControl.Transaction t,
1000                 SurfaceControl leash1, SurfaceControl leash2) {
1001             SurfaceControl targetLeash = null;
1002 
1003             if (mParallaxType == PARALLAX_DISMISSING) {
1004                 switch (mDismissingSide) {
1005                     case DOCKED_TOP:
1006                     case DOCKED_LEFT:
1007                         targetLeash = leash1;
1008                         mTempRect.set(mBounds1);
1009                         break;
1010                     case DOCKED_BOTTOM:
1011                     case DOCKED_RIGHT:
1012                         targetLeash = leash2;
1013                         mTempRect.set(mBounds2);
1014                         break;
1015                 }
1016             } else if (mParallaxType == PARALLAX_ALIGN_CENTER) {
1017                 switch (mShrinkSide) {
1018                     case DOCKED_TOP:
1019                     case DOCKED_LEFT:
1020                         targetLeash = leash1;
1021                         mTempRect.set(mBounds1);
1022                         break;
1023                     case DOCKED_BOTTOM:
1024                     case DOCKED_RIGHT:
1025                         targetLeash = leash2;
1026                         mTempRect.set(mBounds2);
1027                         break;
1028                 }
1029             }
1030             if (mParallaxType != PARALLAX_NONE && targetLeash != null) {
1031                 t.setPosition(targetLeash,
1032                         mTempRect.left + mParallaxOffset.x, mTempRect.top + mParallaxOffset.y);
1033                 // Transform the screen-based split bounds to surface-based crop bounds.
1034                 mTempRect.offsetTo(-mParallaxOffset.x, -mParallaxOffset.y);
1035                 t.setWindowCrop(targetLeash, mTempRect);
1036             }
1037         }
1038 
1039         void adjustDimSurface(SurfaceControl.Transaction t,
1040                 SurfaceControl dimLayer1, SurfaceControl dimLayer2) {
1041             SurfaceControl targetDimLayer;
1042             switch (mDismissingSide) {
1043                 case DOCKED_TOP:
1044                 case DOCKED_LEFT:
1045                     targetDimLayer = dimLayer1;
1046                     break;
1047                 case DOCKED_BOTTOM:
1048                 case DOCKED_RIGHT:
1049                     targetDimLayer = dimLayer2;
1050                     break;
1051                 case DOCKED_INVALID:
1052                 default:
1053                     t.setAlpha(dimLayer1, 0).hide(dimLayer1);
1054                     t.setAlpha(dimLayer2, 0).hide(dimLayer2);
1055                     return;
1056             }
1057             t.setAlpha(targetDimLayer, mDismissingDimValue)
1058                     .setVisibility(targetDimLayer, mDismissingDimValue > 0.001f);
1059         }
1060     }
1061 
1062     /** Records IME top offset changes and updates SplitLayout correspondingly. */
1063     private class ImePositionProcessor implements DisplayImeController.ImePositionProcessor {
1064         /**
1065          * Maximum size of an adjusted split bounds relative to original stack bounds. Used to
1066          * restrict IME adjustment so that a min portion of top split remains visible.
1067          */
1068         private static final float ADJUSTED_SPLIT_FRACTION_MAX = 0.7f;
1069         private static final float ADJUSTED_NONFOCUS_DIM = 0.3f;
1070 
1071         private final int mDisplayId;
1072 
1073         private boolean mHasImeFocus;
1074         private boolean mImeShown;
1075         private int mYOffsetForIme;
1076         private float mDimValue1;
1077         private float mDimValue2;
1078 
1079         private int mStartImeTop;
1080         private int mEndImeTop;
1081 
1082         private int mTargetYOffset;
1083         private int mLastYOffset;
1084         private float mTargetDim1;
1085         private float mTargetDim2;
1086         private float mLastDim1;
1087         private float mLastDim2;
1088 
1089         private ImePositionProcessor(int displayId) {
1090             mDisplayId = displayId;
1091         }
1092 
1093         @Override
1094         public int onImeStartPositioning(int displayId, int hiddenTop, int shownTop,
1095                 boolean showing, boolean isFloating, SurfaceControl.Transaction t) {
1096             if (displayId != mDisplayId || !mInitialized) {
1097                 return 0;
1098             }
1099 
1100             final int imeTargetPosition = getImeTargetPosition();
1101             mHasImeFocus = imeTargetPosition != SPLIT_POSITION_UNDEFINED;
1102             if (!mHasImeFocus) {
1103                 return 0;
1104             }
1105 
1106             mStartImeTop = showing ? hiddenTop : shownTop;
1107             mEndImeTop = showing ? shownTop : hiddenTop;
1108             mImeShown = showing;
1109 
1110             // Update target dim values
1111             mLastDim1 = mDimValue1;
1112             mTargetDim1 = imeTargetPosition == SPLIT_POSITION_BOTTOM_OR_RIGHT && mImeShown
1113                     && mDimNonImeSide ? ADJUSTED_NONFOCUS_DIM : 0.0f;
1114             mLastDim2 = mDimValue2;
1115             mTargetDim2 = imeTargetPosition == SPLIT_POSITION_TOP_OR_LEFT && mImeShown
1116                     && mDimNonImeSide ? ADJUSTED_NONFOCUS_DIM : 0.0f;
1117 
1118             // Calculate target bounds offset for IME
1119             mLastYOffset = mYOffsetForIme;
1120             final boolean needOffset = imeTargetPosition == SPLIT_POSITION_BOTTOM_OR_RIGHT
1121                     && !isFloating && !isLandscape(mRootBounds) && mImeShown;
1122             mTargetYOffset = needOffset ? getTargetYOffset() : 0;
1123 
1124             if (mTargetYOffset != mLastYOffset) {
1125                 // Freeze the configuration size with offset to prevent app get a configuration
1126                 // changed or relaunch. This is required to make sure client apps will calculate
1127                 // insets properly after layout shifted.
1128                 if (mTargetYOffset == 0) {
1129                     mSplitLayoutHandler.setLayoutOffsetTarget(0, 0, SplitLayout.this);
1130                 } else {
1131                     mSplitLayoutHandler.setLayoutOffsetTarget(0, mTargetYOffset - mLastYOffset,
1132                             SplitLayout.this);
1133                 }
1134             }
1135 
1136             // Make {@link DividerView} non-interactive while IME showing in split mode. Listen to
1137             // ImePositionProcessor#onImeVisibilityChanged directly in DividerView is not enough
1138             // because DividerView won't receive onImeVisibilityChanged callback after it being
1139             // re-inflated.
1140             setDividerInteractive(!mImeShown || !mHasImeFocus || isFloating, true,
1141                     "onImeStartPositioning");
1142 
1143             return mTargetYOffset != mLastYOffset ? IME_ANIMATION_NO_ALPHA : 0;
1144         }
1145 
1146         @Override
1147         public void onImePositionChanged(int displayId, int imeTop, SurfaceControl.Transaction t) {
1148             if (displayId != mDisplayId || !mHasImeFocus) return;
1149             onProgress(getProgress(imeTop));
1150             mSplitLayoutHandler.onLayoutPositionChanging(SplitLayout.this);
1151         }
1152 
1153         @Override
1154         public void onImeEndPositioning(int displayId, boolean cancel,
1155                 SurfaceControl.Transaction t) {
1156             if (displayId != mDisplayId || !mHasImeFocus || cancel) return;
1157             onProgress(1.0f);
1158             mSplitLayoutHandler.onLayoutPositionChanging(SplitLayout.this);
1159         }
1160 
1161         @Override
1162         public void onImeControlTargetChanged(int displayId, boolean controlling) {
1163             if (displayId != mDisplayId) return;
1164             // Restore the split layout when wm-shell is not controlling IME insets anymore.
1165             if (!controlling && mImeShown) {
1166                 reset();
1167                 setDividerInteractive(true, true, "onImeControlTargetChanged");
1168                 mSplitLayoutHandler.setLayoutOffsetTarget(0, 0, SplitLayout.this);
1169                 mSplitLayoutHandler.onLayoutPositionChanging(SplitLayout.this);
1170             }
1171         }
1172 
1173         private int getTargetYOffset() {
1174             final int desireOffset = Math.abs(mEndImeTop - mStartImeTop);
1175             // Make sure to keep at least 30% visible for the top split.
1176             final int maxOffset = (int) (mBounds1.height() * ADJUSTED_SPLIT_FRACTION_MAX);
1177             return -Math.min(desireOffset, maxOffset);
1178         }
1179 
1180         @SplitPosition
1181         private int getImeTargetPosition() {
1182             final WindowContainerToken token = mTaskOrganizer.getImeTarget(mDisplayId);
1183             return mSplitLayoutHandler.getSplitItemPosition(token);
1184         }
1185 
1186         private float getProgress(int currImeTop) {
1187             return ((float) currImeTop - mStartImeTop) / (mEndImeTop - mStartImeTop);
1188         }
1189 
1190         private void onProgress(float progress) {
1191             mDimValue1 = getProgressValue(mLastDim1, mTargetDim1, progress);
1192             mDimValue2 = getProgressValue(mLastDim2, mTargetDim2, progress);
1193             mYOffsetForIme =
1194                     (int) getProgressValue((float) mLastYOffset, (float) mTargetYOffset, progress);
1195         }
1196 
1197         private float getProgressValue(float start, float end, float progress) {
1198             return start + (end - start) * progress;
1199         }
1200 
1201         void reset() {
1202             mHasImeFocus = false;
1203             mImeShown = false;
1204             mYOffsetForIme = mLastYOffset = mTargetYOffset = 0;
1205             mDimValue1 = mLastDim1 = mTargetDim1 = 0.0f;
1206             mDimValue2 = mLastDim2 = mTargetDim2 = 0.0f;
1207         }
1208 
1209         /**
1210          * Adjusts surface layout while showing IME.
1211          *
1212          * @return {@code false} if there's no need to adjust, otherwise {@code true}
1213          */
1214         boolean adjustSurfaceLayoutForIme(SurfaceControl.Transaction t,
1215                 SurfaceControl dividerLeash, SurfaceControl leash1, SurfaceControl leash2,
1216                 SurfaceControl dimLayer1, SurfaceControl dimLayer2) {
1217             final boolean showDim = mDimValue1 > 0.001f || mDimValue2 > 0.001f;
1218             boolean adjusted = false;
1219             if (mYOffsetForIme != 0) {
1220                 if (dividerLeash != null) {
1221                     getRefDividerBounds(mTempRect);
1222                     mTempRect.offset(0, mYOffsetForIme);
1223                     t.setPosition(dividerLeash, mTempRect.left, mTempRect.top);
1224                 }
1225 
1226                 getRefBounds1(mTempRect);
1227                 mTempRect.offset(0, mYOffsetForIme);
1228                 t.setPosition(leash1, mTempRect.left, mTempRect.top);
1229 
1230                 getRefBounds2(mTempRect);
1231                 mTempRect.offset(0, mYOffsetForIme);
1232                 t.setPosition(leash2, mTempRect.left, mTempRect.top);
1233                 adjusted = true;
1234             }
1235 
1236             if (showDim) {
1237                 t.setAlpha(dimLayer1, mDimValue1).setVisibility(dimLayer1, mDimValue1 > 0.001f);
1238                 t.setAlpha(dimLayer2, mDimValue2).setVisibility(dimLayer2, mDimValue2 > 0.001f);
1239                 adjusted = true;
1240             }
1241             return adjusted;
1242         }
1243     }
1244 }
1245