Loading libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/split/SplitScreenConstants.java +19 −1 Original line number Diff line number Diff line Loading @@ -159,7 +159,8 @@ public class SplitScreenConstants { * {@link PersistentSnapPosition} + {@link #NOT_IN_SPLIT}. */ @IntDef(value = { NOT_IN_SPLIT, NOT_IN_SPLIT, // user is not in split screen SNAP_TO_NONE, // in "free snap mode," where apps are fully resizable SNAP_TO_2_33_66, SNAP_TO_2_50_50, SNAP_TO_2_66_33, Loading @@ -171,6 +172,23 @@ public class SplitScreenConstants { }) public @interface SplitScreenState {} /** Converts a {@link SplitScreenState} to a human-readable string. */ public static String stateToString(@SplitScreenState int state) { return switch (state) { case NOT_IN_SPLIT -> "NOT_IN_SPLIT"; case SNAP_TO_NONE -> "SNAP_TO_NONE"; case SNAP_TO_2_33_66 -> "SNAP_TO_2_33_66"; case SNAP_TO_2_50_50 -> "SNAP_TO_2_50_50"; case SNAP_TO_2_66_33 -> "SNAP_TO_2_66_33"; case SNAP_TO_2_90_10 -> "SNAP_TO_2_90_10"; case SNAP_TO_2_10_90 -> "SNAP_TO_2_10_90"; case SNAP_TO_3_33_33_33 -> "SNAP_TO_3_33_33_33"; case SNAP_TO_3_45_45_10 -> "SNAP_TO_3_45_45_10"; case SNAP_TO_3_10_45_45 -> "SNAP_TO_3_10_45_45"; default -> "UNKNOWN"; }; } /** * Checks if the snapPosition in question is a {@link PersistentSnapPosition}. */ Loading libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerSnapAlgorithm.java +2 −2 Original line number Diff line number Diff line Loading @@ -352,8 +352,8 @@ public class DividerSnapAlgorithm { ? mPinnedTaskbarInsets.right : mPinnedTaskbarInsets.bottom; float ratio = areOffscreenRatiosSupported() ? SplitLayout.OFFSCREEN_ASYMMETRIC_RATIO : SplitLayout.ONSCREEN_ONLY_ASYMMETRIC_RATIO; ? SplitSpec.OFFSCREEN_ASYMMETRIC_RATIO : SplitSpec.ONSCREEN_ONLY_ASYMMETRIC_RATIO; int size = (int) (ratio * (end - start)) - mDividerSize / 2; int leftTopPosition = start + pinnedTaskbarShiftStart + size; Loading libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java +24 −15 Original line number Diff line number Diff line Loading @@ -112,11 +112,6 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange private static final int FLING_EXIT_DURATION = 450; private static final int FLING_OFFSCREEN_DURATION = 500; /** A split ratio used on larger screens, where we can fit both apps onscreen. */ public static final float ONSCREEN_ONLY_ASYMMETRIC_RATIO = 0.33f; /** A split ratio used on smaller screens, where we place one app mostly offscreen. */ public static final float OFFSCREEN_ASYMMETRIC_RATIO = 0.1f; // Here are some (arbitrarily decided) layer definitions used during animations to make sure the // layers stay in order. (During transitions, everything is reparented onto a transition root // and can be freely relayered.) Loading Loading @@ -236,7 +231,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange updateDividerConfig(mContext); mRootBounds.set(configuration.windowConfiguration.getBounds()); mDividerSnapAlgorithm = getSnapAlgorithm(mContext, mRootBounds); updateLayouts(); mInteractionJankMonitor = InteractionJankMonitor.getInstance(); resetDividerPosition(); updateInvisibleRect(); Loading Loading @@ -490,7 +485,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange mIsLargeScreen = configuration.smallestScreenWidthDp >= 600; mIsLeftRightSplit = SplitScreenUtils.isLeftRightSplit(mAllowLeftRightSplitInPortrait, configuration); mDividerSnapAlgorithm = getSnapAlgorithm(mContext, mRootBounds); updateLayouts(); updateDividerConfig(mContext); initDividerPosition(mTempRect, wasLeftRightSplit); updateInvisibleRect(); Loading Loading @@ -518,7 +513,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange mRootBounds.set(tmpRect); mIsLeftRightSplit = SplitScreenUtils.isLeftRightSplit(mAllowLeftRightSplitInPortrait, mIsLargeScreen, mRootBounds.width() >= mRootBounds.height()); mDividerSnapAlgorithm = getSnapAlgorithm(mContext, mRootBounds); updateLayouts(); initDividerPosition(mTempRect, wasLeftRightSplit); } Loading Loading @@ -652,7 +647,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange if (!mPinnedTaskbarInsets.equals(pinnedTaskbarInsets)) { mPinnedTaskbarInsets = pinnedTaskbarInsets; // Refresh the DividerSnapAlgorithm. mDividerSnapAlgorithm = getSnapAlgorithm(mContext, mRootBounds); updateLayouts(); // If the divider is no longer placed on a snap point, animate it to the nearest one. DividerSnapAlgorithm.SnapTarget snapTarget = findSnapTarget(mDividerPosition, 0, false /* hardDismiss */); Loading Loading @@ -824,8 +819,22 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange return mDividerSnapAlgorithm.calculateSnapTarget(position, velocity, hardDismiss); } private DividerSnapAlgorithm getSnapAlgorithm(Context context, Rect rootBounds) { final Rect insets = getDisplayStableInsets(context); /** * (Re)calculates the split screen logic for this particular display/orientation. Refreshes the * DividerSnapAlgorithm, which controls divider snap points, and populates a map in SplitState * with bounds for all valid split layouts. */ private void updateLayouts() { // Update SplitState map if (Flags.enableFlexibleTwoAppSplit()) { mSplitState.populateLayouts( mRootBounds, mDividerSize, mIsLeftRightSplit, mPinnedTaskbarInsets.toRect()); } // Get new DividerSnapAlgorithm final Rect insets = getDisplayStableInsets(mContext); // Make split axis insets value same as the larger one to avoid bounds1 and bounds2 // have difference for avoiding size-compat mode when switching unresizable apps in Loading @@ -835,10 +844,10 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange insets.set(insets.left, largerInsets, insets.right, largerInsets); } return new DividerSnapAlgorithm( context.getResources(), rootBounds.width(), rootBounds.height(), mDividerSnapAlgorithm = new DividerSnapAlgorithm( mContext.getResources(), mRootBounds.width(), mRootBounds.height(), mDividerSize, mIsLeftRightSplit, insets, Loading libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitSpec.java 0 → 100644 +183 −0 Original line number Diff line number Diff line /* * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.wm.shell.common.split; import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_10_90; import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_33_66; import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_50_50; import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_66_33; import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_90_10; import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_3_10_45_45; import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_3_33_33_33; import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_3_45_45_10; import static com.android.wm.shell.shared.split.SplitScreenConstants.stateToString; import android.graphics.Rect; import android.graphics.RectF; import android.util.Log; import com.android.wm.shell.shared.split.SplitScreenConstants.SplitScreenState; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; /** * A reference class that stores the split layouts available in this device/orientation. Layouts are * available as lists of RectFs, where each RectF represents the bounds of an app. */ public class SplitSpec { private static final String TAG = "SplitSpec"; private static final boolean DEBUG = true; /** A split ratio used on larger screens, where we can fit both apps onscreen. */ public static final float ONSCREEN_ONLY_ASYMMETRIC_RATIO = 0.33f; /** A split ratio used on smaller screens, where we place one app mostly offscreen. */ public static final float OFFSCREEN_ASYMMETRIC_RATIO = 0.1f; /** A 50-50 split ratio. */ public static final float MIDDLE_RATIO = 0.5f; private final boolean mIsLeftRightSplit; /** The usable display area, considering insets that affect split bounds. */ private final RectF mUsableArea; /** Half the divider size. */ private final float mHalfDiv; /** A large map that stores all valid split layouts. */ private final Map<Integer, List<RectF>> mLayouts = new HashMap<>(); /** Constructor; initializes the layout map. */ public SplitSpec(Rect displayBounds, int dividerSize, boolean isLeftRightSplit, Rect pinnedTaskbarInsets) { mIsLeftRightSplit = isLeftRightSplit; mUsableArea = new RectF(displayBounds); mUsableArea.left += pinnedTaskbarInsets.left; mUsableArea.top += pinnedTaskbarInsets.top; mUsableArea.right -= pinnedTaskbarInsets.right; mUsableArea.bottom -= pinnedTaskbarInsets.bottom; mHalfDiv = dividerSize / 2f; // The "start" position, considering insets. float s = isLeftRightSplit ? mUsableArea.left : mUsableArea.top; // The "end" position, considering insets. float e = isLeftRightSplit ? mUsableArea.right : mUsableArea.bottom; // The "length" of the usable display (width or height). Apps are arranged along this axis. float l = e - s; float divPos; float divPos2; // SNAP_TO_2_10_90 divPos = s + (l * OFFSCREEN_ASYMMETRIC_RATIO); createAppLayout(SNAP_TO_2_10_90, divPos); // SNAP_TO_2_33_66 divPos = s + (l * ONSCREEN_ONLY_ASYMMETRIC_RATIO); createAppLayout(SNAP_TO_2_33_66, divPos); // SNAP_TO_2_50_50 divPos = s + (l * MIDDLE_RATIO); createAppLayout(SNAP_TO_2_50_50, divPos); // SNAP_TO_2_66_33 divPos = s + (l * (1 - ONSCREEN_ONLY_ASYMMETRIC_RATIO)); createAppLayout(SNAP_TO_2_66_33, divPos); // SNAP_TO_2_90_10 divPos = s + (l * (1 - OFFSCREEN_ASYMMETRIC_RATIO)); createAppLayout(SNAP_TO_2_90_10, divPos); // SNAP_TO_3_10_45_45 divPos = s + (l * OFFSCREEN_ASYMMETRIC_RATIO); divPos2 = e - ((l * (1 - OFFSCREEN_ASYMMETRIC_RATIO)) / 2f); createAppLayout(SNAP_TO_3_10_45_45, divPos, divPos2); // SNAP_TO_3_33_33_33 divPos = s + (l * ONSCREEN_ONLY_ASYMMETRIC_RATIO); divPos2 = e - (l * ONSCREEN_ONLY_ASYMMETRIC_RATIO); createAppLayout(SNAP_TO_3_33_33_33, divPos, divPos2); // SNAP_TO_3_45_45_10 divPos = s + ((l * (1 - OFFSCREEN_ASYMMETRIC_RATIO)) / 2f); divPos2 = e - (l * OFFSCREEN_ASYMMETRIC_RATIO); createAppLayout(SNAP_TO_3_45_45_10, divPos, divPos2); if (DEBUG) { dump(); } } /** * Creates a two-app layout and enters it into the layout map. * @param divPos The position of the divider. */ private void createAppLayout(@SplitScreenState int state, float divPos) { List<RectF> list = new ArrayList<>(); RectF rect1 = new RectF(mUsableArea); RectF rect2 = new RectF(mUsableArea); if (mIsLeftRightSplit) { rect1.right = divPos - mHalfDiv; rect2.left = divPos + mHalfDiv; } else { rect1.top = divPos - mHalfDiv; rect2.bottom = divPos + mHalfDiv; } list.add(rect1); list.add(rect2); mLayouts.put(state, list); } /** * Creates a three-app layout and enters it into the layout map. * @param divPos1 The position of the first divider. * @param divPos2 The position of the second divider. */ private void createAppLayout(@SplitScreenState int state, float divPos1, float divPos2) { List<RectF> list = new ArrayList<>(); RectF rect1 = new RectF(mUsableArea); RectF rect2 = new RectF(mUsableArea); RectF rect3 = new RectF(mUsableArea); if (mIsLeftRightSplit) { rect1.right = divPos1 - mHalfDiv; rect2.left = divPos1 + mHalfDiv; rect2.right = divPos2 - mHalfDiv; rect3.left = divPos2 + mHalfDiv; } else { rect1.right = divPos1 - mHalfDiv; rect2.left = divPos1 + mHalfDiv; rect3.right = divPos2 - mHalfDiv; rect3.left = divPos2 + mHalfDiv; } list.add(rect1); list.add(rect2); list.add(rect3); mLayouts.put(state, list); } /** Logs all calculated layouts */ private void dump() { mLayouts.forEach((k, v) -> { Log.d(TAG, stateToString(k)); v.forEach(rect -> Log.d(TAG, " - " + rect.toShortString())); }); } /** Returns the layout associated with a given split state. */ List<RectF> getSpec(@SplitScreenState int state) { return mLayouts.get(state); } } libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitState.java +18 −0 Original line number Diff line number Diff line Loading @@ -19,11 +19,17 @@ package com.android.wm.shell.common.split; import static com.android.wm.shell.shared.split.SplitScreenConstants.NOT_IN_SPLIT; import static com.android.wm.shell.shared.split.SplitScreenConstants.SplitScreenState; import android.graphics.Rect; import android.graphics.RectF; import java.util.List; /** * A class that manages the "state" of split screen. See {@link SplitScreenState} for definitions. */ public class SplitState { private @SplitScreenState int mState = NOT_IN_SPLIT; private SplitSpec mSplitSpec; /** Updates the current state of split screen on this device. */ public void set(@SplitScreenState int newState) { Loading @@ -39,4 +45,16 @@ public class SplitState { public void exit() { set(NOT_IN_SPLIT); } /** Refresh the valid layouts for this display/orientation. */ public void populateLayouts(Rect displayBounds, int dividerSize, boolean isLeftRightSplit, Rect pinnedTaskbarInsets) { mSplitSpec = new SplitSpec(displayBounds, dividerSize, isLeftRightSplit, pinnedTaskbarInsets); } /** Returns the layout associated with a given split state. */ public List<RectF> getLayout(@SplitScreenState int state) { return mSplitSpec.getSpec(state); } } Loading
libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/split/SplitScreenConstants.java +19 −1 Original line number Diff line number Diff line Loading @@ -159,7 +159,8 @@ public class SplitScreenConstants { * {@link PersistentSnapPosition} + {@link #NOT_IN_SPLIT}. */ @IntDef(value = { NOT_IN_SPLIT, NOT_IN_SPLIT, // user is not in split screen SNAP_TO_NONE, // in "free snap mode," where apps are fully resizable SNAP_TO_2_33_66, SNAP_TO_2_50_50, SNAP_TO_2_66_33, Loading @@ -171,6 +172,23 @@ public class SplitScreenConstants { }) public @interface SplitScreenState {} /** Converts a {@link SplitScreenState} to a human-readable string. */ public static String stateToString(@SplitScreenState int state) { return switch (state) { case NOT_IN_SPLIT -> "NOT_IN_SPLIT"; case SNAP_TO_NONE -> "SNAP_TO_NONE"; case SNAP_TO_2_33_66 -> "SNAP_TO_2_33_66"; case SNAP_TO_2_50_50 -> "SNAP_TO_2_50_50"; case SNAP_TO_2_66_33 -> "SNAP_TO_2_66_33"; case SNAP_TO_2_90_10 -> "SNAP_TO_2_90_10"; case SNAP_TO_2_10_90 -> "SNAP_TO_2_10_90"; case SNAP_TO_3_33_33_33 -> "SNAP_TO_3_33_33_33"; case SNAP_TO_3_45_45_10 -> "SNAP_TO_3_45_45_10"; case SNAP_TO_3_10_45_45 -> "SNAP_TO_3_10_45_45"; default -> "UNKNOWN"; }; } /** * Checks if the snapPosition in question is a {@link PersistentSnapPosition}. */ Loading
libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerSnapAlgorithm.java +2 −2 Original line number Diff line number Diff line Loading @@ -352,8 +352,8 @@ public class DividerSnapAlgorithm { ? mPinnedTaskbarInsets.right : mPinnedTaskbarInsets.bottom; float ratio = areOffscreenRatiosSupported() ? SplitLayout.OFFSCREEN_ASYMMETRIC_RATIO : SplitLayout.ONSCREEN_ONLY_ASYMMETRIC_RATIO; ? SplitSpec.OFFSCREEN_ASYMMETRIC_RATIO : SplitSpec.ONSCREEN_ONLY_ASYMMETRIC_RATIO; int size = (int) (ratio * (end - start)) - mDividerSize / 2; int leftTopPosition = start + pinnedTaskbarShiftStart + size; Loading
libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java +24 −15 Original line number Diff line number Diff line Loading @@ -112,11 +112,6 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange private static final int FLING_EXIT_DURATION = 450; private static final int FLING_OFFSCREEN_DURATION = 500; /** A split ratio used on larger screens, where we can fit both apps onscreen. */ public static final float ONSCREEN_ONLY_ASYMMETRIC_RATIO = 0.33f; /** A split ratio used on smaller screens, where we place one app mostly offscreen. */ public static final float OFFSCREEN_ASYMMETRIC_RATIO = 0.1f; // Here are some (arbitrarily decided) layer definitions used during animations to make sure the // layers stay in order. (During transitions, everything is reparented onto a transition root // and can be freely relayered.) Loading Loading @@ -236,7 +231,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange updateDividerConfig(mContext); mRootBounds.set(configuration.windowConfiguration.getBounds()); mDividerSnapAlgorithm = getSnapAlgorithm(mContext, mRootBounds); updateLayouts(); mInteractionJankMonitor = InteractionJankMonitor.getInstance(); resetDividerPosition(); updateInvisibleRect(); Loading Loading @@ -490,7 +485,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange mIsLargeScreen = configuration.smallestScreenWidthDp >= 600; mIsLeftRightSplit = SplitScreenUtils.isLeftRightSplit(mAllowLeftRightSplitInPortrait, configuration); mDividerSnapAlgorithm = getSnapAlgorithm(mContext, mRootBounds); updateLayouts(); updateDividerConfig(mContext); initDividerPosition(mTempRect, wasLeftRightSplit); updateInvisibleRect(); Loading Loading @@ -518,7 +513,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange mRootBounds.set(tmpRect); mIsLeftRightSplit = SplitScreenUtils.isLeftRightSplit(mAllowLeftRightSplitInPortrait, mIsLargeScreen, mRootBounds.width() >= mRootBounds.height()); mDividerSnapAlgorithm = getSnapAlgorithm(mContext, mRootBounds); updateLayouts(); initDividerPosition(mTempRect, wasLeftRightSplit); } Loading Loading @@ -652,7 +647,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange if (!mPinnedTaskbarInsets.equals(pinnedTaskbarInsets)) { mPinnedTaskbarInsets = pinnedTaskbarInsets; // Refresh the DividerSnapAlgorithm. mDividerSnapAlgorithm = getSnapAlgorithm(mContext, mRootBounds); updateLayouts(); // If the divider is no longer placed on a snap point, animate it to the nearest one. DividerSnapAlgorithm.SnapTarget snapTarget = findSnapTarget(mDividerPosition, 0, false /* hardDismiss */); Loading Loading @@ -824,8 +819,22 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange return mDividerSnapAlgorithm.calculateSnapTarget(position, velocity, hardDismiss); } private DividerSnapAlgorithm getSnapAlgorithm(Context context, Rect rootBounds) { final Rect insets = getDisplayStableInsets(context); /** * (Re)calculates the split screen logic for this particular display/orientation. Refreshes the * DividerSnapAlgorithm, which controls divider snap points, and populates a map in SplitState * with bounds for all valid split layouts. */ private void updateLayouts() { // Update SplitState map if (Flags.enableFlexibleTwoAppSplit()) { mSplitState.populateLayouts( mRootBounds, mDividerSize, mIsLeftRightSplit, mPinnedTaskbarInsets.toRect()); } // Get new DividerSnapAlgorithm final Rect insets = getDisplayStableInsets(mContext); // Make split axis insets value same as the larger one to avoid bounds1 and bounds2 // have difference for avoiding size-compat mode when switching unresizable apps in Loading @@ -835,10 +844,10 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange insets.set(insets.left, largerInsets, insets.right, largerInsets); } return new DividerSnapAlgorithm( context.getResources(), rootBounds.width(), rootBounds.height(), mDividerSnapAlgorithm = new DividerSnapAlgorithm( mContext.getResources(), mRootBounds.width(), mRootBounds.height(), mDividerSize, mIsLeftRightSplit, insets, Loading
libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitSpec.java 0 → 100644 +183 −0 Original line number Diff line number Diff line /* * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.wm.shell.common.split; import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_10_90; import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_33_66; import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_50_50; import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_66_33; import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_90_10; import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_3_10_45_45; import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_3_33_33_33; import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_3_45_45_10; import static com.android.wm.shell.shared.split.SplitScreenConstants.stateToString; import android.graphics.Rect; import android.graphics.RectF; import android.util.Log; import com.android.wm.shell.shared.split.SplitScreenConstants.SplitScreenState; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; /** * A reference class that stores the split layouts available in this device/orientation. Layouts are * available as lists of RectFs, where each RectF represents the bounds of an app. */ public class SplitSpec { private static final String TAG = "SplitSpec"; private static final boolean DEBUG = true; /** A split ratio used on larger screens, where we can fit both apps onscreen. */ public static final float ONSCREEN_ONLY_ASYMMETRIC_RATIO = 0.33f; /** A split ratio used on smaller screens, where we place one app mostly offscreen. */ public static final float OFFSCREEN_ASYMMETRIC_RATIO = 0.1f; /** A 50-50 split ratio. */ public static final float MIDDLE_RATIO = 0.5f; private final boolean mIsLeftRightSplit; /** The usable display area, considering insets that affect split bounds. */ private final RectF mUsableArea; /** Half the divider size. */ private final float mHalfDiv; /** A large map that stores all valid split layouts. */ private final Map<Integer, List<RectF>> mLayouts = new HashMap<>(); /** Constructor; initializes the layout map. */ public SplitSpec(Rect displayBounds, int dividerSize, boolean isLeftRightSplit, Rect pinnedTaskbarInsets) { mIsLeftRightSplit = isLeftRightSplit; mUsableArea = new RectF(displayBounds); mUsableArea.left += pinnedTaskbarInsets.left; mUsableArea.top += pinnedTaskbarInsets.top; mUsableArea.right -= pinnedTaskbarInsets.right; mUsableArea.bottom -= pinnedTaskbarInsets.bottom; mHalfDiv = dividerSize / 2f; // The "start" position, considering insets. float s = isLeftRightSplit ? mUsableArea.left : mUsableArea.top; // The "end" position, considering insets. float e = isLeftRightSplit ? mUsableArea.right : mUsableArea.bottom; // The "length" of the usable display (width or height). Apps are arranged along this axis. float l = e - s; float divPos; float divPos2; // SNAP_TO_2_10_90 divPos = s + (l * OFFSCREEN_ASYMMETRIC_RATIO); createAppLayout(SNAP_TO_2_10_90, divPos); // SNAP_TO_2_33_66 divPos = s + (l * ONSCREEN_ONLY_ASYMMETRIC_RATIO); createAppLayout(SNAP_TO_2_33_66, divPos); // SNAP_TO_2_50_50 divPos = s + (l * MIDDLE_RATIO); createAppLayout(SNAP_TO_2_50_50, divPos); // SNAP_TO_2_66_33 divPos = s + (l * (1 - ONSCREEN_ONLY_ASYMMETRIC_RATIO)); createAppLayout(SNAP_TO_2_66_33, divPos); // SNAP_TO_2_90_10 divPos = s + (l * (1 - OFFSCREEN_ASYMMETRIC_RATIO)); createAppLayout(SNAP_TO_2_90_10, divPos); // SNAP_TO_3_10_45_45 divPos = s + (l * OFFSCREEN_ASYMMETRIC_RATIO); divPos2 = e - ((l * (1 - OFFSCREEN_ASYMMETRIC_RATIO)) / 2f); createAppLayout(SNAP_TO_3_10_45_45, divPos, divPos2); // SNAP_TO_3_33_33_33 divPos = s + (l * ONSCREEN_ONLY_ASYMMETRIC_RATIO); divPos2 = e - (l * ONSCREEN_ONLY_ASYMMETRIC_RATIO); createAppLayout(SNAP_TO_3_33_33_33, divPos, divPos2); // SNAP_TO_3_45_45_10 divPos = s + ((l * (1 - OFFSCREEN_ASYMMETRIC_RATIO)) / 2f); divPos2 = e - (l * OFFSCREEN_ASYMMETRIC_RATIO); createAppLayout(SNAP_TO_3_45_45_10, divPos, divPos2); if (DEBUG) { dump(); } } /** * Creates a two-app layout and enters it into the layout map. * @param divPos The position of the divider. */ private void createAppLayout(@SplitScreenState int state, float divPos) { List<RectF> list = new ArrayList<>(); RectF rect1 = new RectF(mUsableArea); RectF rect2 = new RectF(mUsableArea); if (mIsLeftRightSplit) { rect1.right = divPos - mHalfDiv; rect2.left = divPos + mHalfDiv; } else { rect1.top = divPos - mHalfDiv; rect2.bottom = divPos + mHalfDiv; } list.add(rect1); list.add(rect2); mLayouts.put(state, list); } /** * Creates a three-app layout and enters it into the layout map. * @param divPos1 The position of the first divider. * @param divPos2 The position of the second divider. */ private void createAppLayout(@SplitScreenState int state, float divPos1, float divPos2) { List<RectF> list = new ArrayList<>(); RectF rect1 = new RectF(mUsableArea); RectF rect2 = new RectF(mUsableArea); RectF rect3 = new RectF(mUsableArea); if (mIsLeftRightSplit) { rect1.right = divPos1 - mHalfDiv; rect2.left = divPos1 + mHalfDiv; rect2.right = divPos2 - mHalfDiv; rect3.left = divPos2 + mHalfDiv; } else { rect1.right = divPos1 - mHalfDiv; rect2.left = divPos1 + mHalfDiv; rect3.right = divPos2 - mHalfDiv; rect3.left = divPos2 + mHalfDiv; } list.add(rect1); list.add(rect2); list.add(rect3); mLayouts.put(state, list); } /** Logs all calculated layouts */ private void dump() { mLayouts.forEach((k, v) -> { Log.d(TAG, stateToString(k)); v.forEach(rect -> Log.d(TAG, " - " + rect.toShortString())); }); } /** Returns the layout associated with a given split state. */ List<RectF> getSpec(@SplitScreenState int state) { return mLayouts.get(state); } }
libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitState.java +18 −0 Original line number Diff line number Diff line Loading @@ -19,11 +19,17 @@ package com.android.wm.shell.common.split; import static com.android.wm.shell.shared.split.SplitScreenConstants.NOT_IN_SPLIT; import static com.android.wm.shell.shared.split.SplitScreenConstants.SplitScreenState; import android.graphics.Rect; import android.graphics.RectF; import java.util.List; /** * A class that manages the "state" of split screen. See {@link SplitScreenState} for definitions. */ public class SplitState { private @SplitScreenState int mState = NOT_IN_SPLIT; private SplitSpec mSplitSpec; /** Updates the current state of split screen on this device. */ public void set(@SplitScreenState int newState) { Loading @@ -39,4 +45,16 @@ public class SplitState { public void exit() { set(NOT_IN_SPLIT); } /** Refresh the valid layouts for this display/orientation. */ public void populateLayouts(Rect displayBounds, int dividerSize, boolean isLeftRightSplit, Rect pinnedTaskbarInsets) { mSplitSpec = new SplitSpec(displayBounds, dividerSize, isLeftRightSplit, pinnedTaskbarInsets); } /** Returns the layout associated with a given split state. */ public List<RectF> getLayout(@SplitScreenState int state) { return mSplitSpec.getSpec(state); } }