• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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_LEFT;
22 import static android.view.WindowManager.DOCKED_TOP;
23 
24 import static com.android.internal.jank.InteractionJankMonitor.CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER;
25 import static com.android.internal.jank.InteractionJankMonitor.CUJ_SPLIT_SCREEN_RESIZE;
26 import static com.android.wm.shell.shared.animation.Interpolators.EMPHASIZED;
27 import static com.android.wm.shell.shared.animation.Interpolators.FAST_OUT_SLOW_IN;
28 import static com.android.wm.shell.shared.animation.Interpolators.LINEAR;
29 import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_10_90;
30 import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_90_10;
31 import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_3_10_45_45;
32 import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_3_45_45_10;
33 import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_END_AND_DISMISS;
34 import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_START_AND_DISMISS;
35 import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
36 import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
37 import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
38 import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_DRAG_DIVIDER;
39 
40 import android.animation.Animator;
41 import android.animation.AnimatorListenerAdapter;
42 import android.animation.AnimatorSet;
43 import android.animation.ValueAnimator;
44 import android.annotation.NonNull;
45 import android.app.ActivityManager;
46 import android.content.Context;
47 import android.content.res.Configuration;
48 import android.content.res.Resources;
49 import android.graphics.Insets;
50 import android.graphics.Rect;
51 import android.os.Handler;
52 import android.util.Log;
53 import android.view.Display;
54 import android.view.InsetsController;
55 import android.view.InsetsSource;
56 import android.view.InsetsSourceControl;
57 import android.view.InsetsState;
58 import android.view.RoundedCorner;
59 import android.view.SurfaceControl;
60 import android.view.WindowInsets;
61 import android.view.WindowManager;
62 import android.view.animation.Interpolator;
63 import android.view.animation.PathInterpolator;
64 import android.window.WindowContainerToken;
65 import android.window.WindowContainerTransaction;
66 
67 import androidx.annotation.Nullable;
68 
69 import com.android.internal.annotations.VisibleForTesting;
70 import com.android.internal.jank.InteractionJankMonitor;
71 import com.android.internal.protolog.ProtoLog;
72 import com.android.wm.shell.Flags;
73 import com.android.wm.shell.R;
74 import com.android.wm.shell.ShellTaskOrganizer;
75 import com.android.wm.shell.common.DisplayController;
76 import com.android.wm.shell.common.DisplayImeController;
77 import com.android.wm.shell.common.DisplayInsetsController;
78 import com.android.wm.shell.common.DisplayLayout;
79 import com.android.wm.shell.common.pip.PipUtils;
80 import com.android.wm.shell.common.split.DividerSnapAlgorithm.SnapTarget;
81 import com.android.wm.shell.common.split.SplitWindowManager.ParentContainerCallbacks;
82 import com.android.wm.shell.protolog.ShellProtoLogGroup;
83 import com.android.wm.shell.shared.annotations.ShellMainThread;
84 import com.android.wm.shell.shared.split.SplitScreenConstants.PersistentSnapPosition;
85 import com.android.wm.shell.shared.split.SplitScreenConstants.SnapPosition;
86 import com.android.wm.shell.shared.split.SplitScreenConstants.SplitPosition;
87 import com.android.wm.shell.splitscreen.StageTaskListener;
88 
89 import java.io.PrintWriter;
90 import java.util.ArrayList;
91 import java.util.List;
92 import java.util.function.Consumer;
93 
94 /**
95  * Records and handles layout of splits. Helps to calculate proper bounds when configuration or
96  * divider position changes.
97  */
98 public final class SplitLayout implements DisplayInsetsController.OnInsetsChangedListener {
99     private static final String TAG = "SplitLayout";
100     /** No parallax effect when the user is dragging the divider */
101     public static final int PARALLAX_NONE = 0;
102     public static final int PARALLAX_DISMISSING = 1;
103     /** Parallax effect (center-aligned) when the user is dragging the divider */
104     public static final int PARALLAX_ALIGN_CENTER = 2;
105     /**
106      * A custom parallax effect for flexible split. When an app is being pushed/pulled offscreen,
107      * we use a specific parallax to give the impression that it is stuck to the divider.
108      * Otherwise, we fall back to PARALLAX_ALIGN_CENTER behavior.
109      */
110     public static final int PARALLAX_FLEX = 3;
111 
112     public static final int FLING_RESIZE_DURATION = 250;
113     private static final int FLING_ENTER_DURATION = 450;
114     private static final int FLING_EXIT_DURATION = 450;
115     private static final int FLING_OFFSCREEN_DURATION = 500;
116 
117     // Here are some (arbitrarily decided) layer definitions used during animations to make sure the
118     // layers stay in order. (During transitions, everything is reparented onto a transition root
119     // and can be freely relayered.)
120     public static final int ANIMATING_DIVIDER_LAYER = 0;
121     public static final int ANIMATING_FRONT_APP_VEIL_LAYER = ANIMATING_DIVIDER_LAYER + 20;
122     public static final int ANIMATING_FRONT_APP_LAYER = ANIMATING_DIVIDER_LAYER + 10;
123     public static final int ANIMATING_BACK_APP_VEIL_LAYER = ANIMATING_DIVIDER_LAYER - 10;
124     public static final int ANIMATING_BACK_APP_LAYER = ANIMATING_DIVIDER_LAYER - 20;
125     // The divider is on the split root, and is sibling with the stage roots. We want to keep it
126     // above the app stages.
127     public static final int RESTING_DIVIDER_LAYER = Integer.MAX_VALUE;
128     // The touch layer is on a stage root, and is sibling with things like the app activity itself
129     // and the app veil. We want it to be above all those.
130     public static final int RESTING_TOUCH_LAYER = Integer.MAX_VALUE;
131     // The dim layer is also on the stage root, and stays under the touch layer.
132     public static final int RESTING_DIM_LAYER = RESTING_TOUCH_LAYER - 1;
133 
134     // Animation specs for the swap animation
135     private static final int SWAP_ANIMATION_TOTAL_DURATION = 500;
136     private static final float SWAP_ANIMATION_SHRINK_DURATION = 83;
137     private static final float SWAP_ANIMATION_SHRINK_MARGIN_DP = 14;
138     private static final Interpolator SHRINK_INTERPOLATOR =
139             new PathInterpolator(0.2f, 0f, 0f, 1f);
140     private static final Interpolator GROW_INTERPOLATOR =
141             new PathInterpolator(0.45f, 0f, 0.5f, 1f);
142     @ShellMainThread
143     private final Handler mHandler;
144 
145     /** Singleton source of truth for the current state of split screen on this device. */
146     private final SplitState mSplitState;
147 
148     private int mDividerWindowWidth;
149     private int mDividerInsets;
150     private int mDividerSize;
151 
152     private final Rect mTempRect = new Rect();
153     private final Rect mTempRect2 = new Rect();
154     private final Rect mRootBounds = new Rect();
155     private final Rect mDividerBounds = new Rect();
156     /**
157      * A list of stage bounds, kept in order from top/left to bottom/right. These are the sizes of
158      * the app surfaces, not necessarily the same as the size of the rendered content.
159      * See {@link #mContentBounds}.
160      */
161     private final List<Rect> mStageBounds = List.of(new Rect(), new Rect());
162     /**
163      * A list of app content bounds, kept in order from top/left to bottom/right. These are the
164      * sizes of the rendered app contents, not necessarily the same as the size of the drawn app
165      * surfaces. See {@link #mStageBounds}.
166      */
167     private final List<Rect> mContentBounds = List.of(new Rect(), new Rect());
168     // The temp bounds outside of display bounds for side stage when split screen inactive to avoid
169     // flicker next time active split screen.
170     private final Rect mInvisibleBounds = new Rect();
171     /**
172      * Areas on the screen that the user can touch to shift the layout, bringing offscreen apps
173      * onscreen. If n apps are offscreen, there should be n such areas. Empty otherwise.
174      */
175     private final List<OffscreenTouchZone> mOffscreenTouchZones = new ArrayList<>();
176     private final SplitLayoutHandler mSplitLayoutHandler;
177     private final SplitWindowManager mSplitWindowManager;
178     private final DisplayController mDisplayController;
179     private final DisplayImeController mDisplayImeController;
180     private final ParentContainerCallbacks mParentContainerCallbacks;
181     private final ImePositionProcessor mImePositionProcessor;
182     private final ResizingEffectPolicy mSurfaceEffectPolicy;
183     private final ShellTaskOrganizer mTaskOrganizer;
184     private final InsetsState mInsetsState = new InsetsState();
185     private Insets mPinnedTaskbarInsets = Insets.NONE;
186 
187     private Context mContext;
188     @VisibleForTesting DividerSnapAlgorithm mDividerSnapAlgorithm;
189     private WindowContainerToken mWinToken1;
190     private WindowContainerToken mWinToken2;
191     private int mDividerPosition;
192     private boolean mInitialized = false;
193     private boolean mFreezeDividerWindow = false;
194     private boolean mIsLargeScreen = false;
195     private int mOrientation;
196     private int mRotation;
197     private int mDensity;
198     private int mUiMode;
199 
200     private final boolean mDimNonImeSide;
201     private final boolean mAllowLeftRightSplitInPortrait;
202     private final InteractionJankMonitor mInteractionJankMonitor;
203     private boolean mIsLeftRightSplit;
204     private ValueAnimator mDividerFlingAnimator;
205     private AnimatorSet mSwapAnimator;
206 
SplitLayout(String windowName, Context context, Configuration configuration, SplitLayoutHandler splitLayoutHandler, SplitWindowManager.ParentContainerCallbacks parentContainerCallbacks, DisplayController displayController, DisplayImeController displayImeController, ShellTaskOrganizer taskOrganizer, int parallaxType, SplitState splitState, @ShellMainThread Handler handler)207     public SplitLayout(String windowName, Context context, Configuration configuration,
208             SplitLayoutHandler splitLayoutHandler,
209             SplitWindowManager.ParentContainerCallbacks parentContainerCallbacks,
210             DisplayController displayController, DisplayImeController displayImeController,
211             ShellTaskOrganizer taskOrganizer, int parallaxType, SplitState splitState,
212             @ShellMainThread Handler handler) {
213         mHandler = handler;
214         mContext = context.createConfigurationContext(configuration);
215         mOrientation = configuration.orientation;
216         mRotation = configuration.windowConfiguration.getRotation();
217         mDensity = configuration.densityDpi;
218         mIsLargeScreen = configuration.smallestScreenWidthDp >= 600;
219         mSplitLayoutHandler = splitLayoutHandler;
220         mDisplayController = displayController;
221         mDisplayImeController = displayImeController;
222         mParentContainerCallbacks = parentContainerCallbacks;
223         mSplitWindowManager = new SplitWindowManager(windowName, mContext, configuration,
224                 parentContainerCallbacks);
225         mTaskOrganizer = taskOrganizer;
226         mImePositionProcessor = new ImePositionProcessor(mContext.getDisplayId());
227         mSurfaceEffectPolicy = new ResizingEffectPolicy(parallaxType, this);
228         mSplitState = splitState;
229 
230         final Resources res = mContext.getResources();
231         mDimNonImeSide = res.getBoolean(R.bool.config_dimNonImeAttachedSide);
232         mAllowLeftRightSplitInPortrait = SplitScreenUtils.allowLeftRightSplitInPortrait(res);
233         mIsLeftRightSplit = SplitScreenUtils.isLeftRightSplit(mAllowLeftRightSplitInPortrait,
234                 configuration);
235 
236         updateDividerConfig(mContext);
237 
238         mRootBounds.set(configuration.windowConfiguration.getBounds());
239         updateLayouts();
240         mInteractionJankMonitor = InteractionJankMonitor.getInstance();
241         resetDividerPosition();
242         updateInvisibleRect();
243     }
244 
updateDividerConfig(Context context)245     private void updateDividerConfig(Context context) {
246         final Resources resources = context.getResources();
247         final Display display = context.getDisplay();
248         final int dividerInset = resources.getDimensionPixelSize(
249                 com.android.internal.R.dimen.docked_stack_divider_insets);
250         int radius = 0;
251         RoundedCorner corner = display.getRoundedCorner(RoundedCorner.POSITION_TOP_LEFT);
252         radius = corner != null ? Math.max(radius, corner.getRadius()) : radius;
253         corner = display.getRoundedCorner(RoundedCorner.POSITION_TOP_RIGHT);
254         radius = corner != null ? Math.max(radius, corner.getRadius()) : radius;
255         corner = display.getRoundedCorner(RoundedCorner.POSITION_BOTTOM_RIGHT);
256         radius = corner != null ? Math.max(radius, corner.getRadius()) : radius;
257         corner = display.getRoundedCorner(RoundedCorner.POSITION_BOTTOM_LEFT);
258         radius = corner != null ? Math.max(radius, corner.getRadius()) : radius;
259 
260         mDividerInsets = Math.max(dividerInset, radius);
261         mDividerSize = resources.getDimensionPixelSize(R.dimen.split_divider_bar_width);
262         mDividerWindowWidth = mDividerSize + 2 * mDividerInsets;
263     }
264 
265     /** Gets the bounds of the top/left app in screen-based coordinates. */
getTopLeftBounds()266     public Rect getTopLeftBounds() {
267         return mStageBounds.getFirst();
268     }
269 
270     /** Gets the bounds of the bottom/right app in screen-based coordinates. */
getBottomRightBounds()271     public Rect getBottomRightBounds() {
272         return mStageBounds.getLast();
273     }
274 
275     /** Gets the bounds of the top/left app in parent-based coordinates. */
getTopLeftRefBounds()276     public Rect getTopLeftRefBounds() {
277         Rect outBounds = getTopLeftBounds();
278         outBounds.offset(-mRootBounds.left, -mRootBounds.top);
279         return outBounds;
280     }
281 
282     /** Gets the bounds of the bottom/right app in parent-based coordinates. */
getBottomRightRefBounds()283     public Rect getBottomRightRefBounds() {
284         Rect outBounds = getBottomRightBounds();
285         outBounds.offset(-mRootBounds.left, -mRootBounds.top);
286         return outBounds;
287     }
288 
289     /** Gets root bounds of the whole split layout */
getRootBounds()290     public Rect getRootBounds() {
291         return new Rect(mRootBounds);
292     }
293 
294     /** Copies the top/left bounds to the provided Rect (screen-based coordinates). */
copyTopLeftBounds(Rect rect)295     public void copyTopLeftBounds(Rect rect) {
296         rect.set(getTopLeftBounds());
297     }
298 
299     /** Copies the top/left bounds to the provided Rect (parent-based coordinates). */
copyTopLeftRefBounds(Rect rect)300     public void copyTopLeftRefBounds(Rect rect) {
301         copyTopLeftBounds(rect);
302         rect.offset(-mRootBounds.left, -mRootBounds.top);
303     }
304 
305     /** Copies the bottom/right bounds to the provided Rect (screen-based coordinates). */
copyBottomRightBounds(Rect rect)306     public void copyBottomRightBounds(Rect rect) {
307         rect.set(getBottomRightBounds());
308     }
309 
310     /** Copies the bottom/right bounds to the provided Rect (parent-based coordinates). */
copyBottomRightRefBounds(Rect rect)311     public void copyBottomRightRefBounds(Rect rect) {
312         copyBottomRightBounds(rect);
313         rect.offset(-mRootBounds.left, -mRootBounds.top);
314     }
315 
316     /**
317      * Gets the content bounds of the top/left app (the bounds of where the app contents would be
318      * drawn). Might be larger than the available surface space.
319      */
getTopLeftContentBounds()320     public Rect getTopLeftContentBounds() {
321         return mContentBounds.getFirst();
322     }
323 
324     /**
325      * Gets the content bounds of the bottom/right app (the bounds of where the app contents would
326      * be drawn). Might be larger than the available surface space.
327      */
getBottomRightContentBounds()328     public Rect getBottomRightContentBounds() {
329         return mContentBounds.getLast();
330     }
331 
332     /**
333      * Gets the bounds of divider window, in screen-based coordinates. This is not the visible
334      * bounds you see on screen, but the actual behind-the-scenes window bounds, which is larger.
335      */
getDividerBounds()336     public Rect getDividerBounds() {
337         return new Rect(mDividerBounds);
338     }
339 
340     /**
341      * Gets the bounds of divider window, in parent-based coordinates. This is not the visible
342      * bounds you see on screen, but the actual behind-the-scenes window bounds, which is larger.
343      */
getRefDividerBounds()344     public Rect getRefDividerBounds() {
345         final Rect outBounds = getDividerBounds();
346         outBounds.offset(-mRootBounds.left, -mRootBounds.top);
347         return outBounds;
348     }
349 
350     /**
351      * Gets the bounds of divider window, in screen-based coordinates. This is not the visible
352      * bounds you see on screen, but the actual behind-the-scenes window bounds, which is larger.
353      */
getDividerBounds(Rect rect)354     public void getDividerBounds(Rect rect) {
355         rect.set(mDividerBounds);
356     }
357 
358     /**
359      * Gets the bounds of divider window, in parent-based coordinates. This is not the visible
360      * bounds you see on screen, but the actual behind-the-scenes window bounds, which is larger.
361      */
getRefDividerBounds(Rect rect)362     public void getRefDividerBounds(Rect rect) {
363         getDividerBounds(rect);
364         rect.offset(-mRootBounds.left, -mRootBounds.top);
365     }
366 
367     /** Gets bounds size equal to root bounds but outside of screen, used for position side stage
368      * when split inactive to avoid flicker when next time active. */
getInvisibleBounds(Rect rect)369     public void getInvisibleBounds(Rect rect) {
370         rect.set(mInvisibleBounds);
371     }
372 
373     /** Returns leash of the current divider bar. */
374     @Nullable
getDividerLeash()375     public SurfaceControl getDividerLeash() {
376         return mSplitWindowManager == null ? null : mSplitWindowManager.getSurfaceControl();
377     }
378 
getDividerPosition()379     int getDividerPosition() {
380         return mDividerPosition;
381     }
382 
383     /**
384      * Finds the {@link SnapPosition} nearest to the current divider position.
385      */
calculateCurrentSnapPosition()386     public int calculateCurrentSnapPosition() {
387         return mDividerSnapAlgorithm.calculateNearestSnapPosition(mDividerPosition);
388     }
389 
390     /** Updates the {@link SplitState} using the current divider position. */
updateStateWithCurrentPosition()391     public void updateStateWithCurrentPosition() {
392         mSplitState.set(calculateCurrentSnapPosition());
393     }
394 
395     /**
396      * Returns the divider position as a fraction from 0 to 1.
397      */
getDividerPositionAsFraction()398     public float getDividerPositionAsFraction() {
399         if (Flags.enableFlexibleTwoAppSplit()) {
400             return Math.min(1f, Math.max(0f, mIsLeftRightSplit
401                     ? (getTopLeftBounds().right + getBottomRightBounds().left) / 2f
402                     / getDisplayWidth()
403                     : (getTopLeftBounds().bottom + getBottomRightBounds().top) / 2f
404                             / getDisplayHeight()));
405         } else {
406             return Math.min(1f, Math.max(0f, mIsLeftRightSplit
407                     ? (float) ((getTopLeftBounds().right + getBottomRightBounds().left) / 2f)
408                     / getBottomRightBounds().right
409                     : (float) ((getTopLeftBounds().bottom + getBottomRightBounds().top) / 2f)
410                             / getBottomRightBounds().bottom));
411         }
412     }
413 
updateInvisibleRect()414     private void updateInvisibleRect() {
415         mInvisibleBounds.set(mRootBounds.left, mRootBounds.top,
416                 mIsLeftRightSplit ? mRootBounds.right / 2 : mRootBounds.right,
417                 mIsLeftRightSplit ? mRootBounds.bottom : mRootBounds.bottom / 2);
418         mInvisibleBounds.offset(mIsLeftRightSplit ? mRootBounds.right : 0,
419                 mIsLeftRightSplit ? 0 : mRootBounds.bottom);
420     }
421 
422     /**
423      * (Re)calculates and activates any needed touch zones, so the user can tap them and retrieve
424      * offscreen apps.
425      */
populateTouchZones()426     public void populateTouchZones() {
427         if (!Flags.enableFlexibleTwoAppSplit()) {
428             return;
429         }
430 
431         if (!mOffscreenTouchZones.isEmpty()) {
432             removeTouchZones();
433         }
434 
435         int currentPosition = mSplitState.get();
436         // TODO (b/349828130): Can delete this warning after brief soak time.
437         if (currentPosition != calculateCurrentSnapPosition()) {
438             Log.wtf(TAG, "SplitState is " + mSplitState.get()
439                     + ", expected " + calculateCurrentSnapPosition());
440         }
441 
442         switch (currentPosition) {
443             case SNAP_TO_2_10_90:
444             case SNAP_TO_3_10_45_45:
445                 mOffscreenTouchZones.add(new OffscreenTouchZone(true /* isTopLeft */,
446                         () -> flingDividerToOtherSide(currentPosition)));
447                 break;
448             case SNAP_TO_2_90_10:
449             case SNAP_TO_3_45_45_10:
450                 mOffscreenTouchZones.add(new OffscreenTouchZone(false /* isTopLeft */,
451                         () -> flingDividerToOtherSide(currentPosition)));
452                 break;
453         }
454 
455         mOffscreenTouchZones.forEach(mParentContainerCallbacks::inflateOnStageRoot);
456     }
457 
458     /** Removes all touch zones. */
removeTouchZones()459     public void removeTouchZones() {
460         if (!Flags.enableFlexibleTwoAppSplit()) {
461             return;
462         }
463 
464         // TODO (b/349828130): It would be good to reuse a Transaction from StageCoordinator's
465         //  mTransactionPool here, but passing it through SplitLayout and specifically
466         //  SplitLayout.release() is complicated because that function is purposely called with a
467         //  null value sometimes. When that function is refactored, we should also pass the
468         //  Transaction in here.
469         SurfaceControl.Transaction t = new SurfaceControl.Transaction();
470         mOffscreenTouchZones.forEach(touchZone -> touchZone.release(t));
471         t.apply();
472         mOffscreenTouchZones.clear();
473     }
474 
475     /** Applies new configuration, returns {@code false} if there's no effect to the layout. */
updateConfiguration(Configuration configuration)476     public boolean updateConfiguration(Configuration configuration) {
477         // Update the split bounds when necessary. Besides root bounds changed, split bounds need to
478         // be updated when the rotation changed to cover the case that users rotated the screen 180
479         // degrees.
480         // Make sure to render the divider bar with proper resources that matching the screen
481         // orientation.
482         final int rotation = configuration.windowConfiguration.getRotation();
483         final Rect rootBounds = configuration.windowConfiguration.getBounds();
484         final int orientation = configuration.orientation;
485         final int density = configuration.densityDpi;
486         final int uiMode = configuration.uiMode;
487         final boolean wasLeftRightSplit = mIsLeftRightSplit;
488 
489         if (mOrientation == orientation
490                 && mRotation == rotation
491                 && mDensity == density
492                 && mUiMode == uiMode
493                 && mRootBounds.equals(rootBounds)) {
494             return false;
495         }
496 
497         mContext = mContext.createConfigurationContext(configuration);
498         mSplitWindowManager.setConfiguration(configuration);
499         mOrientation = orientation;
500         mTempRect.set(mRootBounds);
501         mRootBounds.set(rootBounds);
502         mRotation = rotation;
503         mDensity = density;
504         mUiMode = uiMode;
505         mIsLargeScreen = configuration.smallestScreenWidthDp >= 600;
506         mIsLeftRightSplit = SplitScreenUtils.isLeftRightSplit(mAllowLeftRightSplitInPortrait,
507                 configuration);
508         updateLayouts();
509         updateDividerConfig(mContext);
510         initDividerPosition(mTempRect, wasLeftRightSplit);
511         updateInvisibleRect();
512 
513         return true;
514     }
515 
516     /** Rotate the layout to specific rotation and calculate new bounds. The stable insets value
517      *  should be calculated by display layout. */
rotateTo(int newRotation)518     public void rotateTo(int newRotation) {
519         final int rotationDelta = (newRotation - mRotation + 4) % 4;
520         final boolean changeOrient = (rotationDelta % 2) != 0;
521 
522         mRotation = newRotation;
523         Rect tmpRect = new Rect(mRootBounds);
524         if (changeOrient) {
525             tmpRect.set(mRootBounds.top, mRootBounds.left, mRootBounds.bottom, mRootBounds.right);
526         }
527 
528         // We only need new bounds here, other configuration should be update later.
529         final boolean wasLeftRightSplit = SplitScreenUtils.isLeftRightSplit(
530                 mAllowLeftRightSplitInPortrait, mIsLargeScreen,
531                 mRootBounds.width() >= mRootBounds.height());
532         mTempRect.set(mRootBounds);
533         mRootBounds.set(tmpRect);
534         mIsLeftRightSplit = SplitScreenUtils.isLeftRightSplit(mAllowLeftRightSplitInPortrait,
535                 mIsLargeScreen, mRootBounds.width() >= mRootBounds.height());
536         updateLayouts();
537         initDividerPosition(mTempRect, wasLeftRightSplit);
538     }
539 
540     /**
541      * Updates the divider position to the position in the current orientation and bounds using the
542      * snap fraction calculated based on the previous orientation and bounds.
543      */
initDividerPosition(Rect oldBounds, boolean wasLeftRightSplit)544     private void initDividerPosition(Rect oldBounds, boolean wasLeftRightSplit) {
545         final float snapRatio = (float) mDividerPosition
546                 / (float) (wasLeftRightSplit ? oldBounds.width() : oldBounds.height());
547         // Estimate position by previous ratio.
548         final float length =
549                 (float) (mIsLeftRightSplit ? mRootBounds.width() : mRootBounds.height());
550         final int estimatePosition = (int) (length * snapRatio);
551         // Init divider position by estimated position using current bounds snap algorithm.
552         mDividerPosition = mDividerSnapAlgorithm.calculateNonDismissingSnapTarget(
553                 estimatePosition).position;
554         updateBounds(mDividerPosition);
555     }
556 
updateBounds(int position)557     private void updateBounds(int position) {
558         updateBounds(position, getTopLeftBounds(), getBottomRightBounds(), mDividerBounds,
559                 true /* setEffectBounds */);
560     }
561 
562     /**
563      * Updates the bounds of the divider window and both split apps.
564      * @param position The left/top edge of the visual divider, where the edge of app A meets the
565      *                 divider. Not to be confused with the actual divider surface, which is larger
566      *                 and overlaps the apps a bit.
567      */
updateBounds(int position, Rect bounds1, Rect bounds2, Rect dividerBounds, boolean setEffectBounds)568     private void updateBounds(int position, Rect bounds1, Rect bounds2, Rect dividerBounds,
569             boolean setEffectBounds) {
570         dividerBounds.set(mRootBounds);
571         bounds1.set(mRootBounds);
572         bounds2.set(mRootBounds);
573         if (mIsLeftRightSplit) {
574             position += mRootBounds.left;
575             dividerBounds.left = position - mDividerInsets;
576             dividerBounds.right = dividerBounds.left + mDividerWindowWidth;
577             bounds1.right = position;
578             bounds2.left = bounds1.right + mDividerSize;
579 
580             // For flexible split, expand app offscreen as well
581             if (mDividerSnapAlgorithm.areOffscreenRatiosSupported()) {
582                 int distanceToCenter = position - mDividerSnapAlgorithm.getMiddleTarget().position;
583                 if (position < mDividerSnapAlgorithm.getMiddleTarget().position) {
584                     bounds1.left += distanceToCenter * 2;
585                 } else {
586                     bounds2.right += distanceToCenter * 2;
587                 }
588             }
589 
590         } else {
591             position += mRootBounds.top;
592             dividerBounds.top = position - mDividerInsets;
593             dividerBounds.bottom = dividerBounds.top + mDividerWindowWidth;
594             bounds1.bottom = position;
595             bounds2.top = bounds1.bottom + mDividerSize;
596 
597             // For flexible split, expand app offscreen as well
598             if (mDividerSnapAlgorithm.areOffscreenRatiosSupported()) {
599                 int distanceToCenter = position - mDividerSnapAlgorithm.getMiddleTarget().position;
600                 if (position < mDividerSnapAlgorithm.getMiddleTarget().position) {
601                     bounds1.top += distanceToCenter * 2;
602                 } else {
603                     bounds2.bottom += distanceToCenter * 2;
604                 }
605             }
606         }
607         DockedDividerUtils.sanitizeStackBounds(bounds1, true /** topLeft */);
608         DockedDividerUtils.sanitizeStackBounds(bounds2, false /** topLeft */);
609         if (setEffectBounds) {
610             mSurfaceEffectPolicy.applyDividerPosition(
611                     position, mIsLeftRightSplit, mDividerSnapAlgorithm);
612         }
613     }
614 
615     /** Inflates {@link DividerView} on the root surface. */
init()616     public void init() {
617         if (mInitialized) return;
618         mInitialized = true;
619         mSplitWindowManager.init(this, mInsetsState, false /* isRestoring */);
620         populateTouchZones();
621         mDisplayImeController.addPositionProcessor(mImePositionProcessor);
622     }
623 
624     /** Releases the surface holding the current {@link DividerView}. */
release(SurfaceControl.Transaction t)625     public void release(SurfaceControl.Transaction t) {
626         if (!mInitialized) return;
627         mInitialized = false;
628         mSplitWindowManager.release(t);
629         removeTouchZones();
630         mDisplayImeController.removePositionProcessor(mImePositionProcessor);
631         mImePositionProcessor.reset();
632         if (mDividerFlingAnimator != null) {
633             mDividerFlingAnimator.cancel();
634         }
635         resetDividerPosition();
636     }
637 
release()638     public void release() {
639         release(null /* t */);
640     }
641 
642     /** Releases and re-inflates {@link DividerView} on the root surface. */
update(SurfaceControl.Transaction t, boolean resetImePosition)643     public void update(SurfaceControl.Transaction t, boolean resetImePosition) {
644         if (!mInitialized) {
645             init();
646             return;
647         }
648         mSplitWindowManager.release(t);
649         if (resetImePosition) {
650             mImePositionProcessor.reset();
651         }
652         mSplitWindowManager.init(this, mInsetsState, true /* isRestoring */);
653         populateTouchZones();
654         // Update the surface positions again after recreating the divider in case nothing else
655         // triggers it
656         mSplitLayoutHandler.onLayoutPositionChanging(SplitLayout.this);
657     }
658 
659     @Override
insetsChanged(InsetsState insetsState)660     public void insetsChanged(InsetsState insetsState) {
661         mInsetsState.set(insetsState);
662 
663         if (!mInitialized) {
664             return;
665         }
666         if (mFreezeDividerWindow) {
667             // DO NOT change its layout before transition actually run because it might cause
668             // flicker.
669             return;
670         }
671 
672         // Check to see if insets changed in such a way that the divider needs to be animated to
673         // a new position. (We only do this when switching to pinned taskbar mode and back).
674         Insets pinnedTaskbarInsets = calculatePinnedTaskbarInsets(insetsState);
675         if (!mPinnedTaskbarInsets.equals(pinnedTaskbarInsets)) {
676             mPinnedTaskbarInsets = pinnedTaskbarInsets;
677             // Refresh the DividerSnapAlgorithm.
678             updateLayouts();
679             // If the divider is no longer placed on a snap point, animate it to the nearest one
680             DividerSnapAlgorithm.SnapTarget snapTarget =
681                     findSnapTarget(mDividerPosition, 0, false /* hardDismiss */);
682             if (snapTarget.position != mDividerPosition) {
683                 snapToTarget(mDividerPosition, snapTarget,
684                         InsetsController.ANIMATION_DURATION_RESIZE,
685                         InsetsController.RESIZE_INTERPOLATOR);
686             }
687         }
688 
689         mSplitWindowManager.onInsetsChanged(insetsState);
690     }
691 
692     /**
693      * Calculates the insets that might trigger a divider algorithm recalculation.
694      */
calculatePinnedTaskbarInsets(InsetsState insetsState)695     private Insets calculatePinnedTaskbarInsets(InsetsState insetsState) {
696         for (int i = insetsState.sourceSize() - 1; i >= 0; i--) {
697             final InsetsSource source = insetsState.sourceAt(i);
698             // If Taskbar is pinned...
699             if (source.getType() == WindowInsets.Type.navigationBars()
700                     && source.hasFlags(InsetsSource.FLAG_INSETS_ROUNDED_CORNER)) {
701                 // Return Insets representing the pinned taskbar state.
702                 return source.calculateVisibleInsets(mRootBounds);
703             }
704         }
705 
706         // Else, divider can calculate based on the full display.
707         return Insets.NONE;
708     }
709 
710     @Override
insetsControlChanged(InsetsState insetsState, InsetsSourceControl[] activeControls)711     public void insetsControlChanged(InsetsState insetsState,
712             InsetsSourceControl[] activeControls) {
713         if (!mInsetsState.equals(insetsState)) {
714             insetsChanged(insetsState);
715         }
716     }
717 
setFreezeDividerWindow(boolean freezeDividerWindow)718     public void setFreezeDividerWindow(boolean freezeDividerWindow) {
719         mFreezeDividerWindow = freezeDividerWindow;
720     }
721 
722     /** Update current layout as divider put on start or end position. */
setDividerAtBorder(boolean start)723     public void setDividerAtBorder(boolean start) {
724         final int pos = start ? mDividerSnapAlgorithm.getDismissStartTarget().position
725                 : mDividerSnapAlgorithm.getDismissEndTarget().position;
726         setDividerPosition(pos, false /* applyLayoutChange */);
727     }
728 
729     /**
730      * Updates bounds with the passing position. Usually used to update recording bounds while
731      * performing animation or dragging divider bar to resize the splits.
732      */
updateDividerBounds(int position, boolean shouldUseParallaxEffect)733     void updateDividerBounds(int position, boolean shouldUseParallaxEffect) {
734         updateBounds(position);
735         mSplitLayoutHandler.onLayoutSizeChanging(this,
736                 mSurfaceEffectPolicy.mRetreatingSideParallax.x,
737                 mSurfaceEffectPolicy.mRetreatingSideParallax.y, shouldUseParallaxEffect);
738     }
739 
setDividerPosition(int position, boolean applyLayoutChange)740     void setDividerPosition(int position, boolean applyLayoutChange) {
741         mDividerPosition = position;
742         updateBounds(mDividerPosition);
743         if (applyLayoutChange) {
744             mSplitLayoutHandler.onLayoutSizeChanged(this);
745         }
746     }
747 
748     /**
749      * Updates divider position and split bounds base on the ratio within root bounds. Falls back
750      * to middle position if the provided SnapTarget is not supported.
751      */
setDivideRatio(@ersistentSnapPosition int snapPosition)752     public void setDivideRatio(@PersistentSnapPosition int snapPosition) {
753         final SnapTarget snapTarget = mDividerSnapAlgorithm.findSnapTarget(
754                 snapPosition);
755 
756         setDividerPosition(snapTarget != null
757                 ? snapTarget.position
758                 : mDividerSnapAlgorithm.getMiddleTarget().position,
759                 false /* applyLayoutChange */);
760     }
761 
762     /** Resets divider position. */
resetDividerPosition()763     public void resetDividerPosition() {
764         mDividerPosition = mDividerSnapAlgorithm.getMiddleTarget().position;
765         updateBounds(mDividerPosition);
766         mWinToken1 = null;
767         mWinToken2 = null;
768         getTopLeftContentBounds().setEmpty();
769         getBottomRightContentBounds().setEmpty();
770     }
771 
772     /**
773      * Set divider should interactive to user or not.
774      *
775      * @param interactive divider interactive.
776      * @param hideHandle divider handle hidden or not, only work when interactive is false.
777      * @param from caller from where.
778      */
setDividerInteractive(boolean interactive, boolean hideHandle, String from)779     public void setDividerInteractive(boolean interactive, boolean hideHandle, String from) {
780         mSplitWindowManager.setInteractive(interactive, hideHandle, from);
781     }
782 
783     /**
784      * Sets new divider position and updates bounds correspondingly. Notifies listener if the new
785      * target indicates dismissing split.
786      */
snapToTarget(int currentPosition, SnapTarget snapTarget, int duration, Interpolator interpolator)787     public void snapToTarget(int currentPosition, SnapTarget snapTarget, int duration,
788             Interpolator interpolator) {
789         switch (snapTarget.snapPosition) {
790             case SNAP_TO_START_AND_DISMISS:
791                 flingDividerPosition(currentPosition, snapTarget.position, duration, interpolator,
792                         () -> mSplitLayoutHandler.onSnappedToDismiss(false /* bottomOrRight */,
793                                 EXIT_REASON_DRAG_DIVIDER));
794                 break;
795             case SNAP_TO_END_AND_DISMISS:
796                 flingDividerPosition(currentPosition, snapTarget.position, duration, interpolator,
797                         () -> mSplitLayoutHandler.onSnappedToDismiss(true /* bottomOrRight */,
798                                 EXIT_REASON_DRAG_DIVIDER));
799                 break;
800             default:
801                 flingDividerPosition(currentPosition, snapTarget.position, duration, interpolator,
802                         () -> {
803                             setDividerPosition(snapTarget.position, true /* applyLayoutChange */);
804                             mSplitState.set(snapTarget.snapPosition);
805                         });
806                 break;
807         }
808     }
809 
810     /**
811      * Same as {@link #snapToTarget(int, SnapTarget, int, Interpolator)}, with default animation
812      * duration and interpolator.
813      */
snapToTarget(int currentPosition, SnapTarget snapTarget)814     public void snapToTarget(int currentPosition, SnapTarget snapTarget) {
815         snapToTarget(currentPosition, snapTarget, FLING_RESIZE_DURATION,
816                 FAST_OUT_SLOW_IN);
817     }
818 
onStartDragging()819     void onStartDragging() {
820         mInteractionJankMonitor.begin(getDividerLeash(), mContext, mHandler,
821                 CUJ_SPLIT_SCREEN_RESIZE);
822     }
823 
onDraggingCancelled()824     void onDraggingCancelled() {
825         mInteractionJankMonitor.cancel(CUJ_SPLIT_SCREEN_RESIZE);
826     }
827 
onDoubleTappedDivider()828     void onDoubleTappedDivider() {
829         if (isCurrentlySwapping()) {
830             return;
831         }
832 
833         mSplitLayoutHandler.onDoubleTappedDivider();
834     }
835 
836     /**
837      * Returns {@link SnapTarget} which matches passing position and velocity.
838      * If hardDismiss is set to {@code true}, it will be harder to reach dismiss target.
839      */
findSnapTarget(int position, float velocity, boolean hardDismiss)840     public SnapTarget findSnapTarget(int position, float velocity,
841             boolean hardDismiss) {
842         return mDividerSnapAlgorithm.calculateSnapTarget(position, velocity, hardDismiss);
843     }
844 
845     /**
846      * (Re)calculates the split screen logic for this particular display/orientation. Refreshes the
847      * DividerSnapAlgorithm, which controls divider snap points, and populates a map in SplitState
848      * with bounds for all valid split layouts.
849      */
updateLayouts()850     private void updateLayouts() {
851         // Update SplitState map
852 
853         if (Flags.enableFlexibleTwoAppSplit()) {
854             mSplitState.populateLayouts(
855                     mRootBounds, mDividerSize, mIsLeftRightSplit, mPinnedTaskbarInsets.toRect());
856         }
857 
858         // Get new DividerSnapAlgorithm
859 
860         final Rect insets = getDisplayStableInsets(mContext);
861 
862         // Make split axis insets value same as the larger one to avoid bounds1 and bounds2
863         // have difference for avoiding size-compat mode when switching unresizable apps in
864         // landscape while they are letterboxed.
865         if (!mIsLeftRightSplit) {
866             final int largerInsets = Math.max(insets.top, insets.bottom);
867             insets.set(insets.left, largerInsets, insets.right, largerInsets);
868         }
869 
870         mDividerSnapAlgorithm = new DividerSnapAlgorithm(
871                 mContext.getResources(),
872                 mRootBounds.width(),
873                 mRootBounds.height(),
874                 mDividerSize,
875                 mIsLeftRightSplit,
876                 insets,
877                 mPinnedTaskbarInsets.toRect(),
878                 mIsLeftRightSplit ? DOCKED_LEFT : DOCKED_TOP /* dockSide */);
879     }
880 
881     /** Fling divider from current position to end or start position then exit */
flingDividerToDismiss(boolean toEnd, int reason)882     public void flingDividerToDismiss(boolean toEnd, int reason) {
883         final int target = toEnd ? mDividerSnapAlgorithm.getDismissEndTarget().position
884                 : mDividerSnapAlgorithm.getDismissStartTarget().position;
885         flingDividerPosition(getDividerPosition(), target, FLING_EXIT_DURATION, FAST_OUT_SLOW_IN,
886                 () -> mSplitLayoutHandler.onSnappedToDismiss(toEnd, reason));
887     }
888 
889     /** Fling divider from current position to center position. */
flingDividerToCenter(@ullable Runnable finishCallback)890     public void flingDividerToCenter(@Nullable Runnable finishCallback) {
891         final SnapTarget target = mDividerSnapAlgorithm.getMiddleTarget();
892         final int pos = target.position;
893         flingDividerPosition(getDividerPosition(), pos, FLING_ENTER_DURATION, FAST_OUT_SLOW_IN,
894                 () -> {
895                     setDividerPosition(pos, true /* applyLayoutChange */);
896                     mSplitState.set(target.snapPosition);
897                     if (finishCallback != null) {
898                         finishCallback.run();
899                     }
900                 });
901     }
902 
903     /**
904      * Moves the divider to the other side of the screen. Does nothing if the divider is in the
905      * center.
906      * TODO (b/349828130): Currently only supports the two-app case. For n-apps,
907      *  DividerSnapAlgorithm will need to be refactored, and this function will change as well.
908      */
flingDividerToOtherSide(@ersistentSnapPosition int currentSnapPosition)909     public void flingDividerToOtherSide(@PersistentSnapPosition int currentSnapPosition) {
910         // If a fling animation is already running, just return.
911         if (mDividerFlingAnimator != null) return;
912 
913         switch (currentSnapPosition) {
914             case SNAP_TO_2_10_90 ->
915                     snapToTarget(mDividerPosition, mDividerSnapAlgorithm.getLastSplitTarget(),
916                             FLING_OFFSCREEN_DURATION, EMPHASIZED);
917             case SNAP_TO_2_90_10 ->
918                     snapToTarget(mDividerPosition, mDividerSnapAlgorithm.getFirstSplitTarget(),
919                             FLING_OFFSCREEN_DURATION, EMPHASIZED);
920         }
921     }
922 
923     @VisibleForTesting
flingDividerPosition(int from, int to, int duration, Interpolator interpolator, @Nullable Runnable flingFinishedCallback)924     void flingDividerPosition(int from, int to, int duration, Interpolator interpolator,
925             @Nullable Runnable flingFinishedCallback) {
926         if (from == to) {
927             if (flingFinishedCallback != null) {
928                 flingFinishedCallback.run();
929             }
930             mInteractionJankMonitor.end(
931                     CUJ_SPLIT_SCREEN_RESIZE);
932             return;
933         }
934 
935         if (mDividerFlingAnimator != null) {
936             mDividerFlingAnimator.cancel();
937         }
938 
939         mDividerFlingAnimator = ValueAnimator
940                 .ofInt(from, to)
941                 .setDuration(duration);
942         mDividerFlingAnimator.setInterpolator(interpolator);
943 
944         // If the divider is being physically controlled by the user, we use a cool parallax effect
945         // on the task windows. So if this "snap" animation is an extension of a user-controlled
946         // movement, we pass in true here to continue the parallax effect smoothly.
947         boolean isBeingMovedByUser = mSplitWindowManager.getDividerView() != null
948                 && mSplitWindowManager.getDividerView().isMoving();
949 
950         mDividerFlingAnimator.addUpdateListener(
951                 animation -> updateDividerBounds(
952                         (int) animation.getAnimatedValue(),
953                         isBeingMovedByUser /* shouldUseParallaxEffect */
954                 )
955         );
956         mDividerFlingAnimator.addListener(new AnimatorListenerAdapter() {
957             @Override
958             public void onAnimationEnd(Animator animation) {
959                 if (flingFinishedCallback != null) {
960                     flingFinishedCallback.run();
961                 }
962                 mInteractionJankMonitor.end(
963                         CUJ_SPLIT_SCREEN_RESIZE);
964                 mDividerFlingAnimator = null;
965             }
966 
967             @Override
968             public void onAnimationCancel(Animator animation) {
969                 mDividerFlingAnimator = null;
970             }
971         });
972         mDividerFlingAnimator.start();
973     }
974 
975     /** Switch both surface position with animation. */
playSwapAnimation(SurfaceControl.Transaction t, StageTaskListener topLeftStage, StageTaskListener bottomRightStage, Consumer<Rect> finishCallback)976     public void playSwapAnimation(SurfaceControl.Transaction t, StageTaskListener topLeftStage,
977             StageTaskListener bottomRightStage, Consumer<Rect> finishCallback) {
978         final Rect insets = getDisplayStableInsets(mContext);
979         // If we have insets in the direction of the swap, the animation won't look correct because
980         // window contents will shift and redraw again at the end. So we show a veil to hide that.
981         insets.set(mIsLeftRightSplit ? insets.left : 0, mIsLeftRightSplit ? 0 : insets.top,
982                 mIsLeftRightSplit ? insets.right : 0, mIsLeftRightSplit ? 0 : insets.bottom);
983         final boolean shouldVeil =
984                 insets.left != 0 || insets.top != 0 || insets.right != 0 || insets.bottom != 0;
985 
986         // Find the "left/top"-most position of the app surface -- usually 0, but sometimes negative
987         // if the left/top app is offscreen.
988         int leftTop = 0;
989         if (Flags.enableFlexibleTwoAppSplit()) {
990             leftTop = mIsLeftRightSplit ? getTopLeftBounds().left : getTopLeftBounds().top;
991         }
992 
993         final int dividerPos = mDividerSnapAlgorithm.calculateNonDismissingSnapTarget(
994                 leftTop + (mIsLeftRightSplit
995                         ? getBottomRightBounds().width() : getBottomRightBounds().height())
996         ).position;
997         final Rect endBounds1 = new Rect();
998         final Rect endBounds2 = new Rect();
999         final Rect endDividerBounds = new Rect();
1000         // Compute destination bounds.
1001         updateBounds(dividerPos, endBounds2, endBounds1, endDividerBounds,
1002                 false /* setEffectBounds */);
1003         // Offset to real position under root container.
1004         endBounds1.offset(-mRootBounds.left, -mRootBounds.top);
1005         endBounds2.offset(-mRootBounds.left, -mRootBounds.top);
1006         endDividerBounds.offset(-mRootBounds.left, -mRootBounds.top);
1007 
1008         ValueAnimator animator1 = moveSurface(t, topLeftStage, getTopLeftRefBounds(), endBounds1,
1009                 -insets.left, -insets.top, true /* roundCorners */, true /* isGoingBehind */,
1010                 shouldVeil);
1011         ValueAnimator animator2 = moveSurface(t, bottomRightStage, getBottomRightRefBounds(),
1012                 endBounds2, insets.left, insets.top, true /* roundCorners */,
1013                 false /* isGoingBehind */, shouldVeil);
1014         ValueAnimator animator3 = moveSurface(t, null /* stage */, getRefDividerBounds(),
1015                 endDividerBounds, 0 /* offsetX */, 0 /* offsetY */, false /* roundCorners */,
1016                 false /* isGoingBehind */, false /* addVeil */);
1017 
1018         mSwapAnimator = new AnimatorSet();
1019         mSwapAnimator.playTogether(animator1, animator2, animator3);
1020         mSwapAnimator.setDuration(SWAP_ANIMATION_TOTAL_DURATION);
1021         mSwapAnimator.addListener(new AnimatorListenerAdapter() {
1022             @Override
1023             public void onAnimationStart(Animator animation) {
1024                 mInteractionJankMonitor.begin(getDividerLeash(),
1025                         mContext, mHandler, CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER);
1026             }
1027 
1028             @Override
1029             public void onAnimationEnd(Animator animation) {
1030                 mDividerPosition = dividerPos;
1031                 updateBounds(mDividerPosition);
1032                 finishCallback.accept(insets);
1033                 mInteractionJankMonitor.end(CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER);
1034             }
1035 
1036             @Override
1037             public void onAnimationCancel(Animator animation) {
1038                 mInteractionJankMonitor.cancel(CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER);
1039             }
1040         });
1041         mSwapAnimator.start();
1042     }
1043 
1044     /** Returns true if a swap animation is currently playing. */
isCurrentlySwapping()1045     public boolean isCurrentlySwapping() {
1046         return mSwapAnimator != null && mSwapAnimator.isRunning();
1047     }
1048 
1049     /**
1050      * Animates a task leash across the screen. Currently used only for the swap animation.
1051      *
1052      * @param stage The stage holding the task being animated. If null, it is the divider.
1053      * @param roundCorners Whether we should round the corners of the task while animating.
1054      * @param isGoingBehind Whether we should a shrink-and-grow effect to the task while it is
1055      *                           moving. (Simulates moving behind the divider.)
1056      */
moveSurface(SurfaceControl.Transaction t, StageTaskListener stage, Rect start, Rect end, float offsetX, float offsetY, boolean roundCorners, boolean isGoingBehind, boolean addVeil)1057     private ValueAnimator moveSurface(SurfaceControl.Transaction t, StageTaskListener stage,
1058             Rect start, Rect end, float offsetX, float offsetY, boolean roundCorners,
1059             boolean isGoingBehind, boolean addVeil) {
1060         final boolean isApp = stage != null; // check if this is an app or a divider
1061         final SurfaceControl leash = isApp ? stage.getRootLeash() : getDividerLeash();
1062         final ActivityManager.RunningTaskInfo taskInfo = isApp ? stage.getRunningTaskInfo() : null;
1063         final SplitDecorManager decorManager = isApp ? stage.getDecorManager() : null;
1064 
1065         Rect tempStart = new Rect(start);
1066         Rect tempEnd = new Rect(end);
1067         final float diffX = tempEnd.left - tempStart.left;
1068         final float diffY = tempEnd.top - tempStart.top;
1069         final float diffWidth = tempEnd.width() - tempStart.width();
1070         final float diffHeight = tempEnd.height() - tempStart.height();
1071 
1072         // Get display measurements (for possible shrink animation).
1073         final RoundedCorner roundedCorner = mSplitWindowManager.getDividerView().getDisplay()
1074                 .getRoundedCorner(0 /* position */);
1075         float cornerRadius = roundedCorner == null ? 0 : roundedCorner.getRadius();
1076         float shrinkMarginPx = PipUtils.dpToPx(
1077                 SWAP_ANIMATION_SHRINK_MARGIN_DP, mContext.getResources().getDisplayMetrics());
1078         float shrinkAmountPx = shrinkMarginPx * 2;
1079 
1080         // Timing calculations
1081         float shrinkPortion = SWAP_ANIMATION_SHRINK_DURATION / SWAP_ANIMATION_TOTAL_DURATION;
1082         float growPortion = 1 - shrinkPortion;
1083 
1084         ValueAnimator animator = ValueAnimator.ofFloat(0, 1);
1085         // Set the base animation to proceed linearly. Each component of the animation (movement,
1086         // shrinking, growing) overrides it with a different interpolator later.
1087         animator.setInterpolator(LINEAR);
1088         animator.addUpdateListener(animation -> {
1089             if (leash == null) return;
1090             if (roundCorners) {
1091                 // Add rounded corners to the task leash while it is animating.
1092                 t.setCornerRadius(leash, cornerRadius);
1093             }
1094 
1095             final float progress = (float) animation.getAnimatedValue();
1096             final float moveProgress = EMPHASIZED.getInterpolation(progress);
1097             float instantaneousX = tempStart.left + moveProgress * diffX;
1098             float instantaneousY = tempStart.top + moveProgress * diffY;
1099             int width = (int) (tempStart.width() + moveProgress * diffWidth);
1100             int height = (int) (tempStart.height() + moveProgress * diffHeight);
1101 
1102             if (isGoingBehind) {
1103                 float shrinkDiffX; // the position adjustments needed for this frame
1104                 float shrinkDiffY;
1105                 float shrinkScaleX; // the scale adjustments needed for this frame
1106                 float shrinkScaleY;
1107 
1108                 // Find the max amount we will be shrinking this leash, as a proportion (e.g. 0.1f).
1109                 float maxShrinkX = shrinkAmountPx / height;
1110                 float maxShrinkY = shrinkAmountPx / width;
1111 
1112                 // Find if we are in the shrinking part of the animation, or the growing part.
1113                 boolean shrinking = progress <= shrinkPortion;
1114 
1115                 if (shrinking) {
1116                     // Find how far into the shrink portion we are (e.g. 0.5f).
1117                     float shrinkProgress = progress / shrinkPortion;
1118                     // Find how much we should have progressed in shrinking the leash (e.g. 0.8f).
1119                     float interpolatedShrinkProgress =
1120                             SHRINK_INTERPOLATOR.getInterpolation(shrinkProgress);
1121                     // Find how much width proportion we should be taking off (e.g. 0.1f)
1122                     float widthProportionLost =  maxShrinkX * interpolatedShrinkProgress;
1123                     shrinkScaleX = 1 - widthProportionLost;
1124                     // Find how much height proportion we should be taking off (e.g. 0.1f)
1125                     float heightProportionLost =  maxShrinkY * interpolatedShrinkProgress;
1126                     shrinkScaleY = 1 - heightProportionLost;
1127                     // Add a small amount to the leash's position to keep the task centered.
1128                     shrinkDiffX = (width * widthProportionLost) / 2;
1129                     shrinkDiffY = (height * heightProportionLost) / 2;
1130                 } else {
1131                     // Find how far into the grow portion we are (e.g. 0.5f).
1132                     float growProgress = (progress - shrinkPortion) / growPortion;
1133                     // Find how much we should have progressed in growing the leash (e.g. 0.8f).
1134                     float interpolatedGrowProgress =
1135                             GROW_INTERPOLATOR.getInterpolation(growProgress);
1136                     // Find how much width proportion we should be taking off (e.g. 0.1f)
1137                     float widthProportionLost =  maxShrinkX * (1 - interpolatedGrowProgress);
1138                     shrinkScaleX = 1 - widthProportionLost;
1139                     // Find how much height proportion we should be taking off (e.g. 0.1f)
1140                     float heightProportionLost =  maxShrinkY * (1 - interpolatedGrowProgress);
1141                     shrinkScaleY = 1 - heightProportionLost;
1142                     // Add a small amount to the leash's position to keep the task centered.
1143                     shrinkDiffX = (width * widthProportionLost) / 2;
1144                     shrinkDiffY = (height * heightProportionLost) / 2;
1145                 }
1146 
1147                 instantaneousX += shrinkDiffX;
1148                 instantaneousY += shrinkDiffY;
1149                 width *= shrinkScaleX;
1150                 height *= shrinkScaleY;
1151                 // Set scale on the leash's contents.
1152                 t.setScale(leash, shrinkScaleX, shrinkScaleY);
1153             }
1154 
1155             // Set layers
1156             if (taskInfo != null) {
1157                 t.setLayer(leash, isGoingBehind
1158                         ? ANIMATING_BACK_APP_LAYER
1159                         : ANIMATING_FRONT_APP_LAYER);
1160             } else {
1161                 t.setLayer(leash, ANIMATING_DIVIDER_LAYER);
1162             }
1163 
1164             if (offsetX == 0 && offsetY == 0) {
1165                 t.setPosition(leash, instantaneousX, instantaneousY);
1166                 mTempRect.set((int) instantaneousX, (int) instantaneousY,
1167                         (int) (instantaneousX + width), (int) (instantaneousY + height));
1168                 t.setWindowCrop(leash, width, height);
1169                 if (addVeil) {
1170                     decorManager.drawNextVeilFrameForSwapAnimation(
1171                             taskInfo, mTempRect, t, isGoingBehind, leash, 0, 0);
1172                 }
1173             } else {
1174                 final int diffOffsetX = (int) (moveProgress * offsetX);
1175                 final int diffOffsetY = (int) (moveProgress * offsetY);
1176                 t.setPosition(leash, instantaneousX + diffOffsetX, instantaneousY + diffOffsetY);
1177                 mTempRect.set(0, 0, width, height);
1178                 mTempRect.offsetTo(-diffOffsetX, -diffOffsetY);
1179                 t.setCrop(leash, mTempRect);
1180                 if (addVeil) {
1181                     decorManager.drawNextVeilFrameForSwapAnimation(
1182                             taskInfo, mTempRect, t, isGoingBehind, leash, diffOffsetX, diffOffsetY);
1183                 }
1184             }
1185             t.apply();
1186         });
1187         return animator;
1188     }
1189 
getDisplayStableInsets(Context context)1190     private Rect getDisplayStableInsets(Context context) {
1191         final DisplayLayout displayLayout =
1192                 mDisplayController.getDisplayLayout(context.getDisplayId());
1193         return displayLayout != null
1194                 ? displayLayout.stableInsets()
1195                 : context.getSystemService(WindowManager.class)
1196                         .getMaximumWindowMetrics()
1197                         .getWindowInsets()
1198                         .getInsetsIgnoringVisibility(WindowInsets.Type.systemBars()
1199                                 | WindowInsets.Type.displayCutout())
1200                         .toRect();
1201     }
1202 
1203     /**
1204      * @return {@code true} if we should create a left-right split, {@code false} if we should
1205      * create a top-bottom split.
1206      */
isLeftRightSplit()1207     public boolean isLeftRightSplit() {
1208         return mIsLeftRightSplit;
1209     }
1210 
1211     /** Apply recorded surface layout to the {@link SurfaceControl.Transaction}. */
applySurfaceChanges(SurfaceControl.Transaction t, SurfaceControl leash1, SurfaceControl leash2, SurfaceControl dimLayer1, SurfaceControl dimLayer2, boolean applyResizingOffset)1212     public void applySurfaceChanges(SurfaceControl.Transaction t, SurfaceControl leash1,
1213             SurfaceControl leash2, SurfaceControl dimLayer1, SurfaceControl dimLayer2,
1214             boolean applyResizingOffset) {
1215         final SurfaceControl dividerLeash = getDividerLeash();
1216         if (dividerLeash != null) {
1217             getRefDividerBounds(mTempRect);
1218             t.setPosition(dividerLeash, mTempRect.left, mTempRect.top);
1219             // Resets layer of divider bar to make sure it is always on top.
1220             t.setLayer(dividerLeash, RESTING_DIVIDER_LAYER);
1221         }
1222         if (dimLayer1 != null) {
1223             t.setLayer(dimLayer1, RESTING_DIM_LAYER);
1224         }
1225         if (dimLayer2 != null) {
1226             t.setLayer(dimLayer2, RESTING_DIM_LAYER);
1227         }
1228         copyTopLeftRefBounds(mTempRect);
1229         t.setPosition(leash1, mTempRect.left, mTempRect.top)
1230                 .setWindowCrop(leash1, mTempRect.width(), mTempRect.height());
1231         copyBottomRightRefBounds(mTempRect);
1232         t.setPosition(leash2, mTempRect.left, mTempRect.top)
1233                 .setWindowCrop(leash2, mTempRect.width(), mTempRect.height());
1234 
1235         if (mImePositionProcessor.adjustSurfaceLayoutForIme(
1236                 t, dividerLeash, leash1, leash2, dimLayer1, dimLayer2)) {
1237             return;
1238         }
1239 
1240         mSurfaceEffectPolicy.adjustDimSurface(t, dimLayer1, dimLayer2);
1241         if (applyResizingOffset) {
1242             mSurfaceEffectPolicy.adjustRootSurface(t, leash1, leash2);
1243         }
1244     }
1245 
1246     /** Apply recorded task layout to the {@link WindowContainerTransaction}.
1247      *
1248      * @return true if stage bounds actually update.
1249      */
applyTaskChanges(WindowContainerTransaction wct, ActivityManager.RunningTaskInfo task1, ActivityManager.RunningTaskInfo task2)1250     public boolean applyTaskChanges(WindowContainerTransaction wct,
1251             ActivityManager.RunningTaskInfo task1, ActivityManager.RunningTaskInfo task2) {
1252         boolean boundsChanged = false;
1253         if (!getTopLeftBounds().equals(getTopLeftContentBounds())
1254                 || !task1.token.equals(mWinToken1)) {
1255             setTaskBounds(wct, task1, getTopLeftBounds());
1256             getTopLeftContentBounds().set(getTopLeftBounds());
1257             mWinToken1 = task1.token;
1258             boundsChanged = true;
1259         }
1260         if (!getBottomRightBounds().equals(getBottomRightContentBounds())
1261                 || !task2.token.equals(mWinToken2)) {
1262             setTaskBounds(wct, task2, getBottomRightBounds());
1263             getBottomRightContentBounds().set(getBottomRightBounds());
1264             mWinToken2 = task2.token;
1265             boundsChanged = true;
1266         }
1267         return boundsChanged;
1268     }
1269 
1270     /** Set bounds to the {@link WindowContainerTransaction} for single task. */
setTaskBounds(WindowContainerTransaction wct, ActivityManager.RunningTaskInfo task, Rect bounds)1271     public void setTaskBounds(WindowContainerTransaction wct,
1272             ActivityManager.RunningTaskInfo task, Rect bounds) {
1273         wct.setBounds(task.token, bounds);
1274         wct.setSmallestScreenWidthDp(task.token, getSmallestWidthDp(bounds));
1275     }
1276 
getSmallestWidthDp(Rect bounds)1277     private int getSmallestWidthDp(Rect bounds) {
1278         mTempRect.set(bounds);
1279         mTempRect.inset(getDisplayStableInsets(mContext));
1280         final int minWidth = Math.min(mTempRect.width(), mTempRect.height());
1281         final float density = mContext.getResources().getDisplayMetrics().density;
1282         return (int) (minWidth / density);
1283     }
1284 
getDisplayWidth()1285     public int getDisplayWidth() {
1286         return mRootBounds.width();
1287     }
1288 
getDisplayHeight()1289     public int getDisplayHeight() {
1290         return mRootBounds.height();
1291     }
1292 
1293     /**
1294      * Shift configuration bounds to prevent client apps get configuration changed or relaunch. And
1295      * restore shifted configuration bounds if it's no longer shifted.
1296      */
applyLayoutOffsetTarget(WindowContainerTransaction wct, int offsetX, int offsetY, ActivityManager.RunningTaskInfo taskInfo1, ActivityManager.RunningTaskInfo taskInfo2)1297     public void applyLayoutOffsetTarget(WindowContainerTransaction wct, int offsetX, int offsetY,
1298             ActivityManager.RunningTaskInfo taskInfo1, ActivityManager.RunningTaskInfo taskInfo2) {
1299         if (offsetX == 0 && offsetY == 0) {
1300             wct.setBounds(taskInfo1.token, getTopLeftBounds());
1301             wct.setScreenSizeDp(taskInfo1.token,
1302                     SCREEN_WIDTH_DP_UNDEFINED, SCREEN_HEIGHT_DP_UNDEFINED);
1303 
1304             wct.setBounds(taskInfo2.token, getBottomRightBounds());
1305             wct.setScreenSizeDp(taskInfo2.token,
1306                     SCREEN_WIDTH_DP_UNDEFINED, SCREEN_HEIGHT_DP_UNDEFINED);
1307         } else {
1308             copyTopLeftBounds(mTempRect);
1309             mTempRect.offset(offsetX, offsetY);
1310             wct.setBounds(taskInfo1.token, mTempRect);
1311             wct.setScreenSizeDp(taskInfo1.token,
1312                     taskInfo1.configuration.screenWidthDp,
1313                     taskInfo1.configuration.screenHeightDp);
1314 
1315             copyBottomRightBounds(mTempRect);
1316             mTempRect.offset(offsetX, offsetY);
1317             wct.setBounds(taskInfo2.token, mTempRect);
1318             wct.setScreenSizeDp(taskInfo2.token,
1319                     taskInfo2.configuration.screenWidthDp,
1320                     taskInfo2.configuration.screenHeightDp);
1321         }
1322     }
1323 
1324     /** Dumps the current split bounds recorded in this layout. */
dump(@onNull PrintWriter pw, String prefix)1325     public void dump(@NonNull PrintWriter pw, String prefix) {
1326         final String innerPrefix = prefix + "\t";
1327         pw.println(prefix + TAG + ":");
1328         pw.println(innerPrefix + "mAllowLeftRightSplitInPortrait=" + mAllowLeftRightSplitInPortrait);
1329         pw.println(innerPrefix + "mIsLeftRightSplit=" + mIsLeftRightSplit);
1330         pw.println(innerPrefix + "mFreezeDividerWindow=" + mFreezeDividerWindow);
1331         pw.println(innerPrefix + "mDimNonImeSide=" + mDimNonImeSide);
1332         pw.println(innerPrefix + "mDividerPosition=" + mDividerPosition);
1333         pw.println(innerPrefix + "bounds1=" + getTopLeftBounds().toShortString());
1334         pw.println(innerPrefix + "dividerBounds=" + mDividerBounds.toShortString());
1335         pw.println(innerPrefix + "bounds2=" + getBottomRightBounds().toShortString());
1336     }
1337 
1338     /** Handles layout change event. */
1339     public interface SplitLayoutHandler {
1340 
1341         /** Calls when dismissing split. */
onSnappedToDismiss(boolean snappedToEnd, int reason)1342         void onSnappedToDismiss(boolean snappedToEnd, int reason);
1343 
1344         /**
1345          * Calls when resizing the split bounds.
1346          *
1347          * @see #applySurfaceChanges(SurfaceControl.Transaction, SurfaceControl, SurfaceControl,
1348          * SurfaceControl, SurfaceControl, boolean)
1349          */
onLayoutSizeChanging(SplitLayout layout, int offsetX, int offsetY, boolean shouldUseParallaxEffect)1350         void onLayoutSizeChanging(SplitLayout layout, int offsetX, int offsetY,
1351                 boolean shouldUseParallaxEffect);
1352 
1353         /**
1354          * Calls when finish resizing the split bounds.
1355          *
1356          * @see #applyTaskChanges(WindowContainerTransaction, ActivityManager.RunningTaskInfo,
1357          * ActivityManager.RunningTaskInfo)
1358          * @see #applySurfaceChanges(SurfaceControl.Transaction, SurfaceControl, SurfaceControl,
1359          * SurfaceControl, SurfaceControl, boolean)
1360          */
onLayoutSizeChanged(SplitLayout layout)1361         void onLayoutSizeChanged(SplitLayout layout);
1362 
1363         /**
1364          * Calls when re-positioning the split bounds. Like moving split bounds while showing IME
1365          * panel.
1366          *
1367          * @see #applySurfaceChanges(SurfaceControl.Transaction, SurfaceControl, SurfaceControl,
1368          * SurfaceControl, SurfaceControl, boolean)
1369          */
onLayoutPositionChanging(SplitLayout layout)1370         void onLayoutPositionChanging(SplitLayout layout);
1371 
1372         /**
1373          * Notifies the target offset for shifting layout. So layout handler can shift configuration
1374          * bounds correspondingly to make sure client apps won't get configuration changed or
1375          * relaunched. If the layout is no longer shifted, layout handler should restore shifted
1376          * configuration bounds.
1377          *
1378          * @see #applyLayoutOffsetTarget(WindowContainerTransaction, int, int,
1379          * ActivityManager.RunningTaskInfo, ActivityManager.RunningTaskInfo)
1380          */
setLayoutOffsetTarget(int offsetX, int offsetY, SplitLayout layout)1381         void setLayoutOffsetTarget(int offsetX, int offsetY, SplitLayout layout);
1382 
1383         /** Calls when user double tapped on the divider bar. */
onDoubleTappedDivider()1384         default void onDoubleTappedDivider() {
1385         }
1386 
1387         /**
1388          * Sets the excludedInsetsTypes for the IME in the root WindowContainer.
1389          */
setExcludeImeInsets(boolean exclude)1390         void setExcludeImeInsets(boolean exclude);
1391 
1392         /** Returns split position of the token. */
1393         @SplitPosition
getSplitItemPosition(WindowContainerToken token)1394         int getSplitItemPosition(WindowContainerToken token);
1395     }
1396 
1397     /** Records IME top offset changes and updates SplitLayout correspondingly. */
1398     private class ImePositionProcessor implements DisplayImeController.ImePositionProcessor {
1399         /**
1400          * Maximum size of an adjusted split bounds relative to original stack bounds. Used to
1401          * restrict IME adjustment so that a min portion of top split remains visible.
1402          */
1403         private static final float ADJUSTED_SPLIT_FRACTION_MAX = 0.7f;
1404         private static final float ADJUSTED_NONFOCUS_DIM = 0.3f;
1405 
1406         private final int mDisplayId;
1407 
1408         private boolean mHasImeFocus;
1409         private boolean mImeShown;
1410         private int mYOffsetForIme;
1411         private float mDimValue1;
1412         private float mDimValue2;
1413 
1414         private int mStartImeTop;
1415         private int mEndImeTop;
1416 
1417         private int mTargetYOffset;
1418         private int mLastYOffset;
1419         private float mTargetDim1;
1420         private float mTargetDim2;
1421         private float mLastDim1;
1422         private float mLastDim2;
1423 
ImePositionProcessor(int displayId)1424         private ImePositionProcessor(int displayId) {
1425             mDisplayId = displayId;
1426         }
1427 
1428         @Override
onImeRequested(int displayId, boolean isRequested)1429         public void onImeRequested(int displayId, boolean isRequested) {
1430             if (displayId != mDisplayId) return;
1431             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "IME was set to requested=%s",
1432                     isRequested);
1433             mSplitLayoutHandler.setExcludeImeInsets(true);
1434         }
1435 
1436         @Override
onImeStartPositioning(int displayId, int hiddenTop, int shownTop, boolean showing, boolean isFloating, SurfaceControl.Transaction t)1437         public int onImeStartPositioning(int displayId, int hiddenTop, int shownTop,
1438                 boolean showing, boolean isFloating, SurfaceControl.Transaction t) {
1439             if (displayId != mDisplayId || !mInitialized) {
1440                 return 0;
1441             }
1442 
1443             final int imeTargetPosition = getImeTargetPosition();
1444             mHasImeFocus = imeTargetPosition != SPLIT_POSITION_UNDEFINED;
1445             if (!mHasImeFocus) {
1446                 if (!android.view.inputmethod.Flags.refactorInsetsController() || showing) {
1447                     return 0;
1448                 }
1449             }
1450 
1451             mStartImeTop = showing ? hiddenTop : shownTop;
1452             mEndImeTop = showing ? shownTop : hiddenTop;
1453             mImeShown = showing;
1454 
1455             // Update target dim values
1456             mLastDim1 = mDimValue1;
1457             mTargetDim1 = imeTargetPosition == SPLIT_POSITION_BOTTOM_OR_RIGHT && mImeShown
1458                     && mDimNonImeSide ? ADJUSTED_NONFOCUS_DIM : 0.0f;
1459             mLastDim2 = mDimValue2;
1460             mTargetDim2 = imeTargetPosition == SPLIT_POSITION_TOP_OR_LEFT && mImeShown
1461                     && mDimNonImeSide ? ADJUSTED_NONFOCUS_DIM : 0.0f;
1462 
1463             // Calculate target bounds offset for IME
1464             mLastYOffset = mYOffsetForIme;
1465             final boolean needOffset = imeTargetPosition == SPLIT_POSITION_BOTTOM_OR_RIGHT
1466                     && !isFloating && !mIsLeftRightSplit && mImeShown;
1467             mTargetYOffset = needOffset ? getTargetYOffset() : 0;
1468 
1469             if (mTargetYOffset != mLastYOffset) {
1470                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
1471                         "Split IME animation starting, fromY=%d toY=%d",
1472                         mLastYOffset, mTargetYOffset);
1473                 // Freeze the configuration size with offset to prevent app get a configuration
1474                 // changed or relaunch. This is required to make sure client apps will calculate
1475                 // insets properly after layout shifted.
1476                 mSplitLayoutHandler.setLayoutOffsetTarget(0, mTargetYOffset, SplitLayout.this);
1477             }
1478 
1479             // Make {@link DividerView} non-interactive while IME showing in split mode. Listen to
1480             // ImePositionProcessor#onImeVisibilityChanged directly in DividerView is not enough
1481             // because DividerView won't receive onImeVisibilityChanged callback after it being
1482             // re-inflated.
1483             setDividerInteractive(!mImeShown || !mHasImeFocus || isFloating, true,
1484                     "onImeStartPositioning");
1485 
1486             if (android.view.inputmethod.Flags.refactorInsetsController()) {
1487                 if (mImeShown) {
1488                     mSplitLayoutHandler.setExcludeImeInsets(false);
1489                 }
1490             }
1491 
1492             return mTargetYOffset != mLastYOffset ? IME_ANIMATION_NO_ALPHA : 0;
1493         }
1494 
1495         @Override
onImePositionChanged(int displayId, int imeTop, SurfaceControl.Transaction t)1496         public void onImePositionChanged(int displayId, int imeTop, SurfaceControl.Transaction t) {
1497             if (displayId != mDisplayId || !mHasImeFocus) {
1498                 if (!android.view.inputmethod.Flags.refactorInsetsController() || mImeShown) {
1499                     return;
1500                 }
1501             }
1502             onProgress(getProgress(imeTop));
1503             mSplitLayoutHandler.onLayoutPositionChanging(SplitLayout.this);
1504         }
1505 
1506         @Override
onImeEndPositioning(int displayId, boolean cancel, SurfaceControl.Transaction t)1507         public void onImeEndPositioning(int displayId, boolean cancel,
1508                 SurfaceControl.Transaction t) {
1509             if (displayId != mDisplayId || cancel) return;
1510             if (!mHasImeFocus) {
1511                 if (!android.view.inputmethod.Flags.refactorInsetsController() || mImeShown) {
1512                     return;
1513                 }
1514             }
1515             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
1516                     "Split IME animation ending, canceled=%b", cancel);
1517             onProgress(1.0f);
1518             mSplitLayoutHandler.onLayoutPositionChanging(SplitLayout.this);
1519             if (android.view.inputmethod.Flags.refactorInsetsController()) {
1520                 if (!mImeShown) {
1521                     // The IME hide animation is started immediately and at that point, the IME
1522                     // insets are not yet set to hidden. Therefore only resetting the
1523                     // excludedTypes at the end of the animation. Note: InsetsPolicy will only
1524                     // set the IME height to zero, when it is visible. When it becomes invisible,
1525                     // we dispatch the insets (the height there is zero as well)
1526                     mSplitLayoutHandler.setExcludeImeInsets(false);
1527                 }
1528             }
1529         }
1530 
1531         @Override
onImeControlTargetChanged(int displayId, boolean controlling)1532         public void onImeControlTargetChanged(int displayId, boolean controlling) {
1533             if (displayId != mDisplayId) return;
1534             // Restore the split layout when wm-shell is not controlling IME insets anymore.
1535             if (!controlling && mImeShown) {
1536                 reset();
1537                 setDividerInteractive(true, true, "onImeControlTargetChanged");
1538                 mSplitLayoutHandler.setLayoutOffsetTarget(0, 0, SplitLayout.this);
1539                 mSplitLayoutHandler.onLayoutPositionChanging(SplitLayout.this);
1540             }
1541         }
1542 
1543         /**
1544          * When IME is triggered on the bottom app in split screen, we want to translate the bottom
1545          * app up by a certain amount so that it's not covered too much by the IME. But there's also
1546          * an upper limit to the amount we want to translate (since we still need some of the top
1547          * app to be visible too). So this function essentially says "try to translate the bottom
1548          * app up, but stop before you make the top app too small."
1549          */
getTargetYOffset()1550         private int getTargetYOffset() {
1551             // We want to translate up the bottom app by this amount.
1552             final int desiredOffset = Math.abs(mEndImeTop - mStartImeTop);
1553 
1554             // But we also want to keep this much of the top app visible.
1555             final float amountOfTopAppToKeepVisible =
1556                     getTopLeftBounds().height() * (1 - ADJUSTED_SPLIT_FRACTION_MAX);
1557 
1558             // So the current onscreen size of the top app, minus the minimum size, is the max
1559             // translation we will allow.
1560             final float currentOnScreenSizeOfTopApp = getTopLeftBounds().bottom;
1561             final int maxOffset =
1562                     (int) Math.max(currentOnScreenSizeOfTopApp - amountOfTopAppToKeepVisible, 0);
1563 
1564             return -Math.min(desiredOffset, maxOffset);
1565         }
1566 
1567         @SplitPosition
getImeTargetPosition()1568         private int getImeTargetPosition() {
1569             final WindowContainerToken token = mTaskOrganizer.getImeTarget(mDisplayId);
1570             return mSplitLayoutHandler.getSplitItemPosition(token);
1571         }
1572 
getProgress(int currImeTop)1573         private float getProgress(int currImeTop) {
1574             return ((float) currImeTop - mStartImeTop) / (mEndImeTop - mStartImeTop);
1575         }
1576 
onProgress(float progress)1577         private void onProgress(float progress) {
1578             mDimValue1 = getProgressValue(mLastDim1, mTargetDim1, progress);
1579             mDimValue2 = getProgressValue(mLastDim2, mTargetDim2, progress);
1580             mYOffsetForIme =
1581                     (int) getProgressValue((float) mLastYOffset, (float) mTargetYOffset, progress);
1582         }
1583 
getProgressValue(float start, float end, float progress)1584         private float getProgressValue(float start, float end, float progress) {
1585             return start + (end - start) * progress;
1586         }
1587 
reset()1588         void reset() {
1589             mHasImeFocus = false;
1590             mImeShown = false;
1591             mYOffsetForIme = mLastYOffset = mTargetYOffset = 0;
1592             mDimValue1 = mLastDim1 = mTargetDim1 = 0.0f;
1593             mDimValue2 = mLastDim2 = mTargetDim2 = 0.0f;
1594         }
1595 
1596         /**
1597          * Adjusts surface layout while showing IME.
1598          *
1599          * @return {@code false} if there's no need to adjust, otherwise {@code true}
1600          */
adjustSurfaceLayoutForIme(SurfaceControl.Transaction t, SurfaceControl dividerLeash, SurfaceControl leash1, SurfaceControl leash2, SurfaceControl dimLayer1, SurfaceControl dimLayer2)1601         boolean adjustSurfaceLayoutForIme(SurfaceControl.Transaction t,
1602                 SurfaceControl dividerLeash, SurfaceControl leash1, SurfaceControl leash2,
1603                 SurfaceControl dimLayer1, SurfaceControl dimLayer2) {
1604             final boolean showDim = mDimValue1 > 0.001f || mDimValue2 > 0.001f;
1605             boolean adjusted = false;
1606             if (mYOffsetForIme != 0) {
1607                 if (dividerLeash != null) {
1608                     getRefDividerBounds(mTempRect);
1609                     mTempRect.offset(0, mYOffsetForIme);
1610                     t.setPosition(dividerLeash, mTempRect.left, mTempRect.top);
1611                 }
1612 
1613                 copyTopLeftRefBounds(mTempRect);
1614                 mTempRect.offset(0, mYOffsetForIme);
1615                 t.setPosition(leash1, mTempRect.left, mTempRect.top);
1616 
1617                 copyBottomRightRefBounds(mTempRect);
1618                 mTempRect.offset(0, mYOffsetForIme);
1619                 t.setPosition(leash2, mTempRect.left, mTempRect.top);
1620                 adjusted = true;
1621             }
1622 
1623             if (showDim) {
1624                 t.setAlpha(dimLayer1, mDimValue1).setVisibility(dimLayer1, mDimValue1 > 0.001f);
1625                 t.setAlpha(dimLayer2, mDimValue2).setVisibility(dimLayer2, mDimValue2 > 0.001f);
1626                 adjusted = true;
1627             }
1628             return adjusted;
1629         }
1630     }
1631 }
1632