Loading libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/split/SplitScreenConstants.java +20 −1 Original line number Diff line number Diff line Loading @@ -61,11 +61,30 @@ public class SplitScreenConstants { @IntDef(prefix = {"SPLIT_POSITION_"}, value = { SPLIT_POSITION_UNDEFINED, SPLIT_POSITION_TOP_OR_LEFT, SPLIT_POSITION_BOTTOM_OR_RIGHT SPLIT_POSITION_BOTTOM_OR_RIGHT, }) public @interface SplitPosition { } // These SPLIT_INDEX constants will be used in the same way as the above SPLIT_POSITION ints, // but scalable to n apps. Eventually, SPLIT_POSITION can be deprecated and only the below // will be used. public static final int SPLIT_INDEX_UNDEFINED = -1; public static final int SPLIT_INDEX_0 = 0; public static final int SPLIT_INDEX_1 = 1; public static final int SPLIT_INDEX_2 = 2; public static final int SPLIT_INDEX_3 = 3; @IntDef(prefix = {"SPLIT_INDEX_"}, value = { SPLIT_INDEX_UNDEFINED, SPLIT_INDEX_0, SPLIT_INDEX_1, SPLIT_INDEX_2, SPLIT_INDEX_3 }) public @interface SplitIndex { } /** * A snap target for two apps, where the split is 33-66. With FLAG_ENABLE_FLEXIBLE_SPLIT, * only used on tablets. Loading libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerSnapAlgorithm.java +29 −6 Original line number Diff line number Diff line Loading @@ -19,9 +19,11 @@ package com.android.wm.shell.common.split; import static android.view.WindowManager.DOCKED_LEFT; import static android.view.WindowManager.DOCKED_RIGHT; 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_END_AND_DISMISS; import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_MINIMIZE; import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_NONE; Loading @@ -33,6 +35,9 @@ import android.graphics.Rect; import androidx.annotation.Nullable; import com.android.wm.shell.Flags; import com.android.wm.shell.shared.split.SplitScreenConstants.PersistentSnapPosition; import java.util.ArrayList; /** Loading Loading @@ -79,7 +84,9 @@ public class DividerSnapAlgorithm { private final int mTaskHeightInMinimizedMode; private final float mFixedRatio; /** Allows split ratios to calculated dynamically instead of using {@link #mFixedRatio}. */ private final boolean mAllowFlexibleSplitRatios; private final boolean mCalculateRatiosBasedOnAvailableSpace; /** Allows split ratios that go offscreen (a.k.a. "flexible split") */ private final boolean mAllowOffscreenRatios; private final boolean mIsHorizontalDivision; /** The first target which is still splitting the screen */ Loading Loading @@ -119,8 +126,11 @@ public class DividerSnapAlgorithm { com.android.internal.R.fraction.docked_stack_divider_fixed_ratio, 1, 1); mMinimalSizeResizableTask = res.getDimensionPixelSize( com.android.internal.R.dimen.default_minimal_size_resizable_task); mAllowFlexibleSplitRatios = res.getBoolean( mCalculateRatiosBasedOnAvailableSpace = res.getBoolean( com.android.internal.R.bool.config_flexibleSplitRatios); // If this is a small screen or a foldable, use offscreen ratios mAllowOffscreenRatios = Flags.enableFlexibleTwoAppSplit() && SplitScreenUtils.allowOffscreenRatios(res); mTaskHeightInMinimizedMode = isHomeResizable ? res.getDimensionPixelSize( com.android.internal.R.dimen.task_height_of_minimized_mode) : 0; calculateTargets(isHorizontalDivision, dockSide); Loading Loading @@ -233,6 +243,11 @@ public class DividerSnapAlgorithm { return mFirstSplitTarget.position < position && position < mLastSplitTarget.position; } /** Returns if we are currently on a device/screen that supports split apps going offscreen. */ public boolean areOffscreenRatiosSupported() { return mAllowOffscreenRatios; } private SnapTarget snap(int position, boolean hardDismiss) { if (shouldApplyFreeSnapMode(position)) { return new SnapTarget(position, SNAP_TO_NONE); Loading Loading @@ -283,10 +298,14 @@ public class DividerSnapAlgorithm { private void addNonDismissingTargets(boolean isHorizontalDivision, int topPosition, int bottomPosition, int dividerMax) { maybeAddTarget(topPosition, topPosition - getStartInset(), SNAP_TO_2_33_66); @PersistentSnapPosition int firstTarget = mAllowOffscreenRatios ? SNAP_TO_2_10_90 : SNAP_TO_2_33_66; @PersistentSnapPosition int lastTarget = mAllowOffscreenRatios ? SNAP_TO_2_90_10 : SNAP_TO_2_66_33; maybeAddTarget(topPosition, topPosition - getStartInset(), firstTarget); addMiddleTarget(isHorizontalDivision); maybeAddTarget(bottomPosition, dividerMax - getEndInset() - (bottomPosition + mDividerSize), SNAP_TO_2_66_33); dividerMax - getEndInset() - (bottomPosition + mDividerSize), lastTarget); } private void addFixedDivisionTargets(boolean isHorizontalDivision, int dividerMax) { Loading @@ -295,7 +314,11 @@ public class DividerSnapAlgorithm { ? mDisplayHeight - mInsets.bottom : mDisplayWidth - mInsets.right; int size = (int) (mFixedRatio * (end - start)) - mDividerSize / 2; if (mAllowFlexibleSplitRatios) { if (mAllowOffscreenRatios) { // TODO (b/349828130): This is a placeholder value, real measurements to come size = (int) (0.3f * (end - start)) - mDividerSize / 2; } else if (mCalculateRatiosBasedOnAvailableSpace) { size = Math.max(size, mMinimalSizeResizableTask); } int topPosition = start + size; Loading Loading @@ -324,7 +347,7 @@ public class DividerSnapAlgorithm { * meets the minimal size requirement. */ private void maybeAddTarget(int position, int smallerSize, @SnapPosition int snapPosition) { if (smallerSize >= mMinimalSizeResizableTask) { if (smallerSize >= mMinimalSizeResizableTask || mAllowOffscreenRatios) { mTargets.add(new SnapTarget(position, snapPosition)); } } Loading libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java +36 −0 Original line number Diff line number Diff line Loading @@ -30,6 +30,8 @@ import static com.android.wm.shell.shared.animation.Interpolators.DIM_INTERPOLAT import static com.android.wm.shell.shared.animation.Interpolators.EMPHASIZED; import static com.android.wm.shell.shared.animation.Interpolators.LINEAR; import static com.android.wm.shell.shared.animation.Interpolators.SLOWDOWN_INTERPOLATOR; 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_90_10; import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_END_AND_DISMISS; import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_START_AND_DISMISS; import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT; Loading Loading @@ -438,12 +440,31 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange dividerBounds.right = dividerBounds.left + mDividerWindowWidth; bounds1.right = position; bounds2.left = bounds1.right + mDividerSize; // For flexible split, expand app offscreen as well if (mDividerSnapAlgorithm.areOffscreenRatiosSupported()) { if (position <= mDividerSnapAlgorithm.getMiddleTarget().position) { bounds1.left = bounds1.right - bounds2.width(); } else { bounds2.right = bounds2.left + bounds1.width(); } } } else { position += mRootBounds.top; dividerBounds.top = position - mDividerInsets; dividerBounds.bottom = dividerBounds.top + mDividerWindowWidth; bounds1.bottom = position; bounds2.top = bounds1.bottom + mDividerSize; // For flexible split, expand app offscreen as well if (mDividerSnapAlgorithm.areOffscreenRatiosSupported()) { if (position <= mDividerSnapAlgorithm.getMiddleTarget().position) { bounds1.top = bounds1.bottom - bounds2.width(); } else { bounds2.bottom = bounds2.top + bounds1.width(); } } } DockedDividerUtils.sanitizeStackBounds(bounds1, true /** topLeft */); DockedDividerUtils.sanitizeStackBounds(bounds2, false /** topLeft */); Loading Loading @@ -669,6 +690,21 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange }); } /** * Moves the divider to the other side of the screen. Does nothing if the divider is in the * center. * TODO (b/349828130): Currently only supports the two-app case. For n-apps, * DividerSnapAlgorithm will need to be refactored, and this function will change as well. */ public void flingDividerToOtherSide(@PersistentSnapPosition int currentSnapPosition) { switch (currentSnapPosition) { case SNAP_TO_2_10_90 -> snapToTarget(mDividerPosition, mDividerSnapAlgorithm.getLastSplitTarget()); case SNAP_TO_2_90_10 -> snapToTarget(mDividerPosition, mDividerSnapAlgorithm.getFirstSplitTarget()); } } @VisibleForTesting void flingDividerPosition(int from, int to, int duration, @Nullable Runnable flingFinishedCallback) { Loading libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java +44 −2 Original line number Diff line number Diff line Loading @@ -18,6 +18,10 @@ package com.android.wm.shell.common.split; import static com.android.wm.shell.shared.split.SplitScreenConstants.CONTROLLED_ACTIVITY_TYPES; import static com.android.wm.shell.shared.split.SplitScreenConstants.CONTROLLED_WINDOWING_MODES; 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_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_45_45_10; import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT; import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT; import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED; Loading @@ -38,6 +42,8 @@ import com.android.wm.shell.shared.split.SplitScreenConstants; /** Helper utility class for split screen components to use. */ public class SplitScreenUtils { private static final int LARGE_SCREEN_MIN_EDGE_DP = 600; /** Reverse the split position. */ @SplitScreenConstants.SplitPosition public static int reverseSplitPosition(@SplitScreenConstants.SplitPosition int position) { Loading Loading @@ -110,10 +116,9 @@ public class SplitScreenUtils { Configuration config) { // Compare the max bounds sizes as on near-square devices, the insets may result in a // configuration in the other orientation final boolean isLargeScreen = config.smallestScreenWidthDp >= 600; final Rect maxBounds = config.windowConfiguration.getMaxBounds(); final boolean isLandscape = maxBounds.width() >= maxBounds.height(); return isLeftRightSplit(allowLeftRightSplitInPortrait, isLargeScreen, isLandscape); return isLeftRightSplit(allowLeftRightSplitInPortrait, isLargeScreen(config), isLandscape); } /** Loading @@ -128,4 +133,41 @@ public class SplitScreenUtils { return isLandscape; } } /** * Returns whether the current config is a large screen (tablet or unfolded foldable) */ public static boolean isLargeScreen(Configuration config) { return config.smallestScreenWidthDp >= LARGE_SCREEN_MIN_EDGE_DP; } /** * Returns whether we should allow split ratios to go offscreen or not. If the device is a phone * or a foldable (either screen), we allow it. */ public static boolean allowOffscreenRatios(Resources res) { return !isLargeScreen(res.getConfiguration()) || res.getIntArray(com.android.internal.R.array.config_foldedDeviceStates).length != 0; } /** * Within a particular split layout, we label the stages numerically: 0, 1, 2... from left to * right (or top to bottom). This function takes in a stage index (0th, 1st, 2nd...) and a * PersistentSnapPosition and returns if that particular stage is offscreen in that layout. */ public static boolean isPartiallyOffscreen(int stageIndex, @SplitScreenConstants.PersistentSnapPosition int snapPosition) { switch(snapPosition) { case SNAP_TO_2_10_90: case SNAP_TO_3_10_45_45: return stageIndex == 0; case SNAP_TO_2_90_10: return stageIndex == 1; case SNAP_TO_3_45_45_10: return stageIndex == 2; default: return false; } } } libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java +35 −0 Original line number Diff line number Diff line Loading @@ -35,12 +35,19 @@ import static android.window.TransitionInfo.FLAG_IS_DISPLAY; import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER; import static com.android.wm.shell.common.split.SplitLayout.PARALLAX_ALIGN_CENTER; import static com.android.wm.shell.common.split.SplitScreenUtils.isPartiallyOffscreen; import static com.android.wm.shell.common.split.SplitScreenUtils.reverseSplitPosition; import static com.android.wm.shell.common.split.SplitScreenUtils.splitFailureMessage; import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN; import static com.android.wm.shell.shared.TransitionUtil.isClosingType; import static com.android.wm.shell.shared.TransitionUtil.isOpeningType; import static com.android.wm.shell.shared.TransitionUtil.isOrderOnly; import static com.android.wm.shell.shared.split.SplitScreenConstants.FLAG_IS_DIVIDER_BAR; 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_90_10; import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_INDEX_0; import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_INDEX_1; import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_INDEX_UNDEFINED; import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT; import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT; import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED; Loading Loading @@ -117,6 +124,7 @@ import com.android.internal.logging.InstanceId; import com.android.internal.policy.FoldLockSettingsObserver; import com.android.internal.protolog.ProtoLog; import com.android.launcher3.icons.IconProvider; import com.android.wm.shell.Flags; import com.android.wm.shell.R; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.DisplayController; Loading Loading @@ -2053,6 +2061,13 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mSplitLayout.setDividerInteractive(true, false, "onSplitResizeFinish"); }, mMainStage.getSplitDecorManager(), mSideStage.getSplitDecorManager()); if (Flags.enableFlexibleTwoAppSplit()) { switch (layout.calculateCurrentSnapPosition()) { case SNAP_TO_2_10_90 -> grantFocusToPosition(false /* leftOrTop */); case SNAP_TO_2_90_10 -> grantFocusToPosition(true /* leftOrTop */); } } mLogger.logResize(mSplitLayout.getDividerPositionAsFraction()); } Loading Loading @@ -2506,6 +2521,26 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mTaskOrganizer.applyTransaction(wct); } continue; } else if (Flags.enableFlexibleTwoAppSplit() && isOrderOnly(change)) { int focusedStageIndex = SPLIT_INDEX_UNDEFINED; if (taskInfo.token.equals(mMainStage.mRootTaskInfo.token)) { focusedStageIndex = mSideStagePosition == SPLIT_POSITION_BOTTOM_OR_RIGHT ? SPLIT_INDEX_0 : SPLIT_INDEX_1; } else if (taskInfo.token.equals(mSideStage.mRootTaskInfo.token)) { focusedStageIndex = mSideStagePosition == SPLIT_POSITION_BOTTOM_OR_RIGHT ? SPLIT_INDEX_1 : SPLIT_INDEX_0; } if (focusedStageIndex != SPLIT_INDEX_UNDEFINED) { @PersistentSnapPosition int currentSnapPosition = mSplitLayout.calculateCurrentSnapPosition(); boolean offscreenTaskFocused = isPartiallyOffscreen(focusedStageIndex, currentSnapPosition); if (offscreenTaskFocused) { mSplitLayout.flingDividerToOtherSide(currentSnapPosition); } } } final StageTaskListener stage = getStageOfTask(taskInfo); if (stage == null) { Loading Loading
libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/split/SplitScreenConstants.java +20 −1 Original line number Diff line number Diff line Loading @@ -61,11 +61,30 @@ public class SplitScreenConstants { @IntDef(prefix = {"SPLIT_POSITION_"}, value = { SPLIT_POSITION_UNDEFINED, SPLIT_POSITION_TOP_OR_LEFT, SPLIT_POSITION_BOTTOM_OR_RIGHT SPLIT_POSITION_BOTTOM_OR_RIGHT, }) public @interface SplitPosition { } // These SPLIT_INDEX constants will be used in the same way as the above SPLIT_POSITION ints, // but scalable to n apps. Eventually, SPLIT_POSITION can be deprecated and only the below // will be used. public static final int SPLIT_INDEX_UNDEFINED = -1; public static final int SPLIT_INDEX_0 = 0; public static final int SPLIT_INDEX_1 = 1; public static final int SPLIT_INDEX_2 = 2; public static final int SPLIT_INDEX_3 = 3; @IntDef(prefix = {"SPLIT_INDEX_"}, value = { SPLIT_INDEX_UNDEFINED, SPLIT_INDEX_0, SPLIT_INDEX_1, SPLIT_INDEX_2, SPLIT_INDEX_3 }) public @interface SplitIndex { } /** * A snap target for two apps, where the split is 33-66. With FLAG_ENABLE_FLEXIBLE_SPLIT, * only used on tablets. Loading
libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerSnapAlgorithm.java +29 −6 Original line number Diff line number Diff line Loading @@ -19,9 +19,11 @@ package com.android.wm.shell.common.split; import static android.view.WindowManager.DOCKED_LEFT; import static android.view.WindowManager.DOCKED_RIGHT; 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_END_AND_DISMISS; import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_MINIMIZE; import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_NONE; Loading @@ -33,6 +35,9 @@ import android.graphics.Rect; import androidx.annotation.Nullable; import com.android.wm.shell.Flags; import com.android.wm.shell.shared.split.SplitScreenConstants.PersistentSnapPosition; import java.util.ArrayList; /** Loading Loading @@ -79,7 +84,9 @@ public class DividerSnapAlgorithm { private final int mTaskHeightInMinimizedMode; private final float mFixedRatio; /** Allows split ratios to calculated dynamically instead of using {@link #mFixedRatio}. */ private final boolean mAllowFlexibleSplitRatios; private final boolean mCalculateRatiosBasedOnAvailableSpace; /** Allows split ratios that go offscreen (a.k.a. "flexible split") */ private final boolean mAllowOffscreenRatios; private final boolean mIsHorizontalDivision; /** The first target which is still splitting the screen */ Loading Loading @@ -119,8 +126,11 @@ public class DividerSnapAlgorithm { com.android.internal.R.fraction.docked_stack_divider_fixed_ratio, 1, 1); mMinimalSizeResizableTask = res.getDimensionPixelSize( com.android.internal.R.dimen.default_minimal_size_resizable_task); mAllowFlexibleSplitRatios = res.getBoolean( mCalculateRatiosBasedOnAvailableSpace = res.getBoolean( com.android.internal.R.bool.config_flexibleSplitRatios); // If this is a small screen or a foldable, use offscreen ratios mAllowOffscreenRatios = Flags.enableFlexibleTwoAppSplit() && SplitScreenUtils.allowOffscreenRatios(res); mTaskHeightInMinimizedMode = isHomeResizable ? res.getDimensionPixelSize( com.android.internal.R.dimen.task_height_of_minimized_mode) : 0; calculateTargets(isHorizontalDivision, dockSide); Loading Loading @@ -233,6 +243,11 @@ public class DividerSnapAlgorithm { return mFirstSplitTarget.position < position && position < mLastSplitTarget.position; } /** Returns if we are currently on a device/screen that supports split apps going offscreen. */ public boolean areOffscreenRatiosSupported() { return mAllowOffscreenRatios; } private SnapTarget snap(int position, boolean hardDismiss) { if (shouldApplyFreeSnapMode(position)) { return new SnapTarget(position, SNAP_TO_NONE); Loading Loading @@ -283,10 +298,14 @@ public class DividerSnapAlgorithm { private void addNonDismissingTargets(boolean isHorizontalDivision, int topPosition, int bottomPosition, int dividerMax) { maybeAddTarget(topPosition, topPosition - getStartInset(), SNAP_TO_2_33_66); @PersistentSnapPosition int firstTarget = mAllowOffscreenRatios ? SNAP_TO_2_10_90 : SNAP_TO_2_33_66; @PersistentSnapPosition int lastTarget = mAllowOffscreenRatios ? SNAP_TO_2_90_10 : SNAP_TO_2_66_33; maybeAddTarget(topPosition, topPosition - getStartInset(), firstTarget); addMiddleTarget(isHorizontalDivision); maybeAddTarget(bottomPosition, dividerMax - getEndInset() - (bottomPosition + mDividerSize), SNAP_TO_2_66_33); dividerMax - getEndInset() - (bottomPosition + mDividerSize), lastTarget); } private void addFixedDivisionTargets(boolean isHorizontalDivision, int dividerMax) { Loading @@ -295,7 +314,11 @@ public class DividerSnapAlgorithm { ? mDisplayHeight - mInsets.bottom : mDisplayWidth - mInsets.right; int size = (int) (mFixedRatio * (end - start)) - mDividerSize / 2; if (mAllowFlexibleSplitRatios) { if (mAllowOffscreenRatios) { // TODO (b/349828130): This is a placeholder value, real measurements to come size = (int) (0.3f * (end - start)) - mDividerSize / 2; } else if (mCalculateRatiosBasedOnAvailableSpace) { size = Math.max(size, mMinimalSizeResizableTask); } int topPosition = start + size; Loading Loading @@ -324,7 +347,7 @@ public class DividerSnapAlgorithm { * meets the minimal size requirement. */ private void maybeAddTarget(int position, int smallerSize, @SnapPosition int snapPosition) { if (smallerSize >= mMinimalSizeResizableTask) { if (smallerSize >= mMinimalSizeResizableTask || mAllowOffscreenRatios) { mTargets.add(new SnapTarget(position, snapPosition)); } } Loading
libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java +36 −0 Original line number Diff line number Diff line Loading @@ -30,6 +30,8 @@ import static com.android.wm.shell.shared.animation.Interpolators.DIM_INTERPOLAT import static com.android.wm.shell.shared.animation.Interpolators.EMPHASIZED; import static com.android.wm.shell.shared.animation.Interpolators.LINEAR; import static com.android.wm.shell.shared.animation.Interpolators.SLOWDOWN_INTERPOLATOR; 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_90_10; import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_END_AND_DISMISS; import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_START_AND_DISMISS; import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT; Loading Loading @@ -438,12 +440,31 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange dividerBounds.right = dividerBounds.left + mDividerWindowWidth; bounds1.right = position; bounds2.left = bounds1.right + mDividerSize; // For flexible split, expand app offscreen as well if (mDividerSnapAlgorithm.areOffscreenRatiosSupported()) { if (position <= mDividerSnapAlgorithm.getMiddleTarget().position) { bounds1.left = bounds1.right - bounds2.width(); } else { bounds2.right = bounds2.left + bounds1.width(); } } } else { position += mRootBounds.top; dividerBounds.top = position - mDividerInsets; dividerBounds.bottom = dividerBounds.top + mDividerWindowWidth; bounds1.bottom = position; bounds2.top = bounds1.bottom + mDividerSize; // For flexible split, expand app offscreen as well if (mDividerSnapAlgorithm.areOffscreenRatiosSupported()) { if (position <= mDividerSnapAlgorithm.getMiddleTarget().position) { bounds1.top = bounds1.bottom - bounds2.width(); } else { bounds2.bottom = bounds2.top + bounds1.width(); } } } DockedDividerUtils.sanitizeStackBounds(bounds1, true /** topLeft */); DockedDividerUtils.sanitizeStackBounds(bounds2, false /** topLeft */); Loading Loading @@ -669,6 +690,21 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange }); } /** * Moves the divider to the other side of the screen. Does nothing if the divider is in the * center. * TODO (b/349828130): Currently only supports the two-app case. For n-apps, * DividerSnapAlgorithm will need to be refactored, and this function will change as well. */ public void flingDividerToOtherSide(@PersistentSnapPosition int currentSnapPosition) { switch (currentSnapPosition) { case SNAP_TO_2_10_90 -> snapToTarget(mDividerPosition, mDividerSnapAlgorithm.getLastSplitTarget()); case SNAP_TO_2_90_10 -> snapToTarget(mDividerPosition, mDividerSnapAlgorithm.getFirstSplitTarget()); } } @VisibleForTesting void flingDividerPosition(int from, int to, int duration, @Nullable Runnable flingFinishedCallback) { Loading
libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java +44 −2 Original line number Diff line number Diff line Loading @@ -18,6 +18,10 @@ package com.android.wm.shell.common.split; import static com.android.wm.shell.shared.split.SplitScreenConstants.CONTROLLED_ACTIVITY_TYPES; import static com.android.wm.shell.shared.split.SplitScreenConstants.CONTROLLED_WINDOWING_MODES; 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_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_45_45_10; import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT; import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT; import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED; Loading @@ -38,6 +42,8 @@ import com.android.wm.shell.shared.split.SplitScreenConstants; /** Helper utility class for split screen components to use. */ public class SplitScreenUtils { private static final int LARGE_SCREEN_MIN_EDGE_DP = 600; /** Reverse the split position. */ @SplitScreenConstants.SplitPosition public static int reverseSplitPosition(@SplitScreenConstants.SplitPosition int position) { Loading Loading @@ -110,10 +116,9 @@ public class SplitScreenUtils { Configuration config) { // Compare the max bounds sizes as on near-square devices, the insets may result in a // configuration in the other orientation final boolean isLargeScreen = config.smallestScreenWidthDp >= 600; final Rect maxBounds = config.windowConfiguration.getMaxBounds(); final boolean isLandscape = maxBounds.width() >= maxBounds.height(); return isLeftRightSplit(allowLeftRightSplitInPortrait, isLargeScreen, isLandscape); return isLeftRightSplit(allowLeftRightSplitInPortrait, isLargeScreen(config), isLandscape); } /** Loading @@ -128,4 +133,41 @@ public class SplitScreenUtils { return isLandscape; } } /** * Returns whether the current config is a large screen (tablet or unfolded foldable) */ public static boolean isLargeScreen(Configuration config) { return config.smallestScreenWidthDp >= LARGE_SCREEN_MIN_EDGE_DP; } /** * Returns whether we should allow split ratios to go offscreen or not. If the device is a phone * or a foldable (either screen), we allow it. */ public static boolean allowOffscreenRatios(Resources res) { return !isLargeScreen(res.getConfiguration()) || res.getIntArray(com.android.internal.R.array.config_foldedDeviceStates).length != 0; } /** * Within a particular split layout, we label the stages numerically: 0, 1, 2... from left to * right (or top to bottom). This function takes in a stage index (0th, 1st, 2nd...) and a * PersistentSnapPosition and returns if that particular stage is offscreen in that layout. */ public static boolean isPartiallyOffscreen(int stageIndex, @SplitScreenConstants.PersistentSnapPosition int snapPosition) { switch(snapPosition) { case SNAP_TO_2_10_90: case SNAP_TO_3_10_45_45: return stageIndex == 0; case SNAP_TO_2_90_10: return stageIndex == 1; case SNAP_TO_3_45_45_10: return stageIndex == 2; default: return false; } } }
libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java +35 −0 Original line number Diff line number Diff line Loading @@ -35,12 +35,19 @@ import static android.window.TransitionInfo.FLAG_IS_DISPLAY; import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER; import static com.android.wm.shell.common.split.SplitLayout.PARALLAX_ALIGN_CENTER; import static com.android.wm.shell.common.split.SplitScreenUtils.isPartiallyOffscreen; import static com.android.wm.shell.common.split.SplitScreenUtils.reverseSplitPosition; import static com.android.wm.shell.common.split.SplitScreenUtils.splitFailureMessage; import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN; import static com.android.wm.shell.shared.TransitionUtil.isClosingType; import static com.android.wm.shell.shared.TransitionUtil.isOpeningType; import static com.android.wm.shell.shared.TransitionUtil.isOrderOnly; import static com.android.wm.shell.shared.split.SplitScreenConstants.FLAG_IS_DIVIDER_BAR; 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_90_10; import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_INDEX_0; import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_INDEX_1; import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_INDEX_UNDEFINED; import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT; import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT; import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED; Loading Loading @@ -117,6 +124,7 @@ import com.android.internal.logging.InstanceId; import com.android.internal.policy.FoldLockSettingsObserver; import com.android.internal.protolog.ProtoLog; import com.android.launcher3.icons.IconProvider; import com.android.wm.shell.Flags; import com.android.wm.shell.R; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.DisplayController; Loading Loading @@ -2053,6 +2061,13 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mSplitLayout.setDividerInteractive(true, false, "onSplitResizeFinish"); }, mMainStage.getSplitDecorManager(), mSideStage.getSplitDecorManager()); if (Flags.enableFlexibleTwoAppSplit()) { switch (layout.calculateCurrentSnapPosition()) { case SNAP_TO_2_10_90 -> grantFocusToPosition(false /* leftOrTop */); case SNAP_TO_2_90_10 -> grantFocusToPosition(true /* leftOrTop */); } } mLogger.logResize(mSplitLayout.getDividerPositionAsFraction()); } Loading Loading @@ -2506,6 +2521,26 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mTaskOrganizer.applyTransaction(wct); } continue; } else if (Flags.enableFlexibleTwoAppSplit() && isOrderOnly(change)) { int focusedStageIndex = SPLIT_INDEX_UNDEFINED; if (taskInfo.token.equals(mMainStage.mRootTaskInfo.token)) { focusedStageIndex = mSideStagePosition == SPLIT_POSITION_BOTTOM_OR_RIGHT ? SPLIT_INDEX_0 : SPLIT_INDEX_1; } else if (taskInfo.token.equals(mSideStage.mRootTaskInfo.token)) { focusedStageIndex = mSideStagePosition == SPLIT_POSITION_BOTTOM_OR_RIGHT ? SPLIT_INDEX_1 : SPLIT_INDEX_0; } if (focusedStageIndex != SPLIT_INDEX_UNDEFINED) { @PersistentSnapPosition int currentSnapPosition = mSplitLayout.calculateCurrentSnapPosition(); boolean offscreenTaskFocused = isPartiallyOffscreen(focusedStageIndex, currentSnapPosition); if (offscreenTaskFocused) { mSplitLayout.flingDividerToOtherSide(currentSnapPosition); } } } final StageTaskListener stage = getStageOfTask(taskInfo); if (stage == null) { Loading