Loading libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerSnapAlgorithm.java +107 −98 Original line number Diff line number Diff line Loading @@ -19,6 +19,9 @@ 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.common.split.SplitSpec.DISMISS_TARGETS; import static com.android.wm.shell.common.split.SplitSpec.ONE_TARGET; import static com.android.wm.shell.common.split.SplitSpec.THREE_TARGETS_ONSCREEN; 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_END_AND_DISMISS; import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_MINIMIZE; Loading @@ -33,7 +36,6 @@ import androidx.annotation.Nullable; import com.android.mechanics.spec.MotionSpec; import com.android.wm.shell.Flags; import com.android.wm.shell.shared.split.SplitScreenConstants.PersistentSnapPosition; import java.util.ArrayList; import java.util.List; Loading Loading @@ -69,14 +71,10 @@ public class DividerSnapAlgorithm { * 1 snap target: minimized height, (1 - minimized height) */ static final int SNAP_MODE_MINIMIZED = 3; /** * A mode where apps can be "flexibly offscreen" on smaller displays. */ static final int SNAP_FLEXIBLE_SPLIT = 4; /** * A mode combining {@link #SNAP_FIXED_RATIO} with {@link #SNAP_FLEXIBLE_SPLIT}. Has 5 * split screen snap points on smaller devices. * A mode that has 5 split screen snap points on smaller devices (10:90, 33:66, 50:50, 66:33, * 90:10). Except if it's *too* small, then it will only have 3 (10:90, 50:50, 90:10). Larger * devices (tablet size) will also have 3 (33:66, 50:50, 66:33). */ static final int SNAP_FLEXIBLE_HYBRID = 5; Loading @@ -102,19 +100,31 @@ public class DividerSnapAlgorithm { private final int mDockSide; /** * The first, and usually only, snap target between the left/top screen edge and center. * The first snap target, counting from the top/left, and not including dismiss targets. * On modes with 5 snap targets, this will refer to the 10% target. * On modes with 3 snap targets, this will refer to the 10% or 33% target. * On modes with only 1 target, it will refer to the middle target. */ private final SnapTarget mFirstSplitTarget; /** * Another snap target on the top/left side (closer to center than the "first"). * The second snap target, counting from the top/left, and not including dismiss targets. * On modes with 5 snap targets, this will refer to the 33% target. * On modes with 3 snap targets, this will refer to the middle target. * On modes with only 1 target, it will also refer to the middle target. */ private final SnapTarget mSecondSplitTarget; /** * The last, and usually only, snap target between the center and the right/bottom screen edge. * The last snap target, counting from the top/left, and not including dismiss targets. * On modes with 5 snap targets, this will refer to the 90% target. * On modes with 3 snap targets, this will refer to the 66% or 90% target. * On modes with only 1 target, it will refer to the middle target. */ private final SnapTarget mLastSplitTarget; /** * Another snap target on the right/bottom side (closer to center than the "last"). * The second-last snap target, counting from the top/left, and not including dismiss targets. * On modes with 5 snap targets, this will refer to the 66% target. * On modes with 3 snap targets, this will refer to the middle target. * On modes with only 1 target, it will also refer to the middle target. */ private final SnapTarget mSecondLastSplitTarget; Loading Loading @@ -170,16 +180,20 @@ public class DividerSnapAlgorithm { mTaskHeightInMinimizedMode = isHomeResizable ? res.getDimensionPixelSize( com.android.internal.R.dimen.task_height_of_minimized_mode) : 0; calculateTargets(); mMiddleTarget = mTargets.get(mTargets.size() / 2); mMiddleTarget.isMiddleTarget = true; mDismissStartTarget = mTargets.get(0); mFirstSplitTarget = mTargets.get(1); mSecondSplitTarget = mSnapMode == SNAP_FLEXIBLE_HYBRID && areOffscreenRatiosSupported() ? mTargets.get(2) : null; mSecondLastSplitTarget = mSnapMode == SNAP_FLEXIBLE_HYBRID && areOffscreenRatiosSupported() ? mTargets.get(mTargets.size() - 3) : null; if (mTargets.size() == ONE_TARGET.size() + DISMISS_TARGETS.size()) { mSecondSplitTarget = mSecondLastSplitTarget = null; } else if (mTargets.size() == THREE_TARGETS_ONSCREEN.size() + DISMISS_TARGETS.size()) { mSecondSplitTarget = mSecondLastSplitTarget = mMiddleTarget; } else { mSecondSplitTarget = mTargets.get(2); mSecondLastSplitTarget = mTargets.get(mTargets.size() - 3); } mLastSplitTarget = mTargets.get(mTargets.size() - 2); mDismissStartTarget = mTargets.get(0); mDismissEndTarget = mTargets.get(mTargets.size() - 1); mMiddleTarget = mTargets.get(mTargets.size() / 2); mMiddleTarget.isMiddleTarget = true; } /** Loading Loading @@ -340,9 +354,6 @@ public class DividerSnapAlgorithm { case SNAP_MODE_MINIMIZED: addMinimizedTarget(mDockSide); break; case SNAP_FLEXIBLE_SPLIT: addFlexSplitTargets(dividerMax); break; case SNAP_FLEXIBLE_HYBRID: addFlexHybridSplitTargets(dividerMax); break; Loading @@ -357,30 +368,14 @@ public class DividerSnapAlgorithm { * 3-target layout, and size 5 for a 5-target layout.) Should always be in * ascending order. */ private void addNonDismissingTargets(List<Integer> positions, int dividerMax) { // Get the desired layout for our snap mode. List<Integer> targetSpec = SplitSpec.getSnapTargetLayout(mSnapMode, areOffscreenRatiosSupported()); private void addNonDismissingTargets(List<Integer> positions, List<Integer> targetSpec) { if (positions.size() != targetSpec.size()) { throw new IllegalStateException("unexpected number of snap positions"); } // Iterate through the spec, adding a target for each. boolean midpointPassed = false; for (int i = 0; i < targetSpec.size(); i++) { @PersistentSnapPosition int target = targetSpec.get(i); int position = positions.get(i); if (!midpointPassed) { maybeAddTarget(position, position - getStartInset(), target); } else if (target == SNAP_TO_2_50_50) { mTargets.add(new SnapTarget(position, target)); midpointPassed = true; } else { maybeAddTarget(position, dividerMax - getEndInset() - (position + mDividerSize), target); } mTargets.add(new SnapTarget(positions.get(i), targetSpec.get(i))); } } Loading @@ -397,72 +392,72 @@ public class DividerSnapAlgorithm { int topPosition = start + size; int bottomPosition = end - size - mDividerSize; addNonDismissingTargets(List.of(topPosition, getMiddleTargetPos(), bottomPosition), dividerMax); } private void addFlexSplitTargets(int dividerMax) { int start = 0; int end = mIsLeftRightSplit ? mDisplayWidth : mDisplayHeight; int pinnedTaskbarShiftStart = mIsLeftRightSplit ? mPinnedTaskbarInsets.left : mPinnedTaskbarInsets.top; int pinnedTaskbarShiftEnd = mIsLeftRightSplit ? mPinnedTaskbarInsets.right : mPinnedTaskbarInsets.bottom; // Get the desired layout for our current device/display/rotation. boolean bigEnoughFor33 = size >= mMinimalSizeResizableTask; List<Integer> targetSpec = SplitSpec.getSnapTargetLayout(SNAP_FIXED_RATIO, areOffscreenRatiosSupported(), bigEnoughFor33); float ratio = areOffscreenRatiosSupported() ? SplitSpec.OFFSCREEN_ASYMMETRIC_RATIO : SplitSpec.ONSCREEN_ONLY_ASYMMETRIC_RATIO; // The intended size of the smaller app, in pixels int size = (int) (ratio * (end - start)) - mDividerSize / 2; // If there are insets that interfere with the smaller app (visually or blocking touch // targets), make the smaller app bigger by that amount to compensate. This applies to // pinned taskbar, 3-button nav (both create an opaque bar at bottom) and status bar (blocks // touch targets at top). int extraSpace = IntStream.of( getStartInset(), getEndInset(), pinnedTaskbarShiftStart, pinnedTaskbarShiftEnd ).max().getAsInt(); int leftTopPosition = start + extraSpace + size; int rightBottomPosition = end - extraSpace - size - mDividerSize; addNonDismissingTargets(List.of(leftTopPosition, getMiddleTargetPos(), rightBottomPosition), dividerMax); if (bigEnoughFor33) { // Add 3 targets addNonDismissingTargets(List.of(topPosition, getMiddleTargetPos(), bottomPosition), targetSpec); } else { // Add 1 target addNonDismissingTargets(List.of(getMiddleTargetPos()), targetSpec); } } private void addFlexHybridSplitTargets(int dividerMax) { int start = 0; int end = mIsLeftRightSplit ? mDisplayWidth : mDisplayHeight; int start = mIsLeftRightSplit ? mInsets.left : mInsets.top; int end = mIsLeftRightSplit ? mDisplayWidth - mInsets.right : mDisplayHeight - mInsets.bottom; int pinnedTaskbarShiftStart = mIsLeftRightSplit ? mPinnedTaskbarInsets.left : mPinnedTaskbarInsets.top; int pinnedTaskbarShiftEnd = mIsLeftRightSplit ? mPinnedTaskbarInsets.right : mPinnedTaskbarInsets.bottom; // If offscreen apps are supported, add 5 targets instead of 3. // If offscreen apps are supported, we are looking to add 3-5 targets. if (areOffscreenRatiosSupported()) { // Find the desired sizes for a 10% app and a 33% app. float ratio10 = SplitSpec.OFFSCREEN_ASYMMETRIC_RATIO; float ratio33 = SplitSpec.ONSCREEN_ONLY_ASYMMETRIC_RATIO; int size10 = (int) (ratio10 * (end - start)) - mDividerSize / 2; int size33 = (int) (ratio33 * (end - start)) - mDividerSize / 2; // If there are insets that interfere with the smaller app (visually or blocking touch // targets), make the 10% app ratio bigger by that amount to compensate. This applies to // pinned taskbar, 3-button nav (both create an opaque bar at bottom) and status bar // Insets affect where we want to put the snap targets. For the 10% target: If there are // insets that interfere with the divider position (visually or blocking touch targets), // we make the size bigger by that amount to compensate. This happens with an enabled // pinned taskbar, 3-button nav (both create an opaque bar at bottom) or status bar // (blocks touch targets at top). int extraSpaceFor10 = IntStream.of( getStartInset(), getEndInset(), pinnedTaskbarShiftStart, pinnedTaskbarShiftEnd ).max().getAsInt(); int size10 = (int) (ratio10 * dividerMax) + extraSpaceFor10 - mDividerSize / 2; // For the 33% target, we bake the insets into the position calculation below. int size33 = (int) (ratio33 * (end - start)) - mDividerSize / 2; int leftTop10Position = start + extraSpaceFor10 + size10; int rightBottom10Position = end - extraSpaceFor10 - size10 - mDividerSize; int leftTop10Position = size10; int rightBottom10Position = dividerMax - size10 - mDividerSize; int leftTop33Position = start + size33; int rightBottom33Position = end - size33 - mDividerSize; // Get the desired layout for our current device/display/rotation. boolean bigEnoughFor33 = size33 >= mMinimalSizeResizableTask; List<Integer> targetSpec = SplitSpec.getSnapTargetLayout(SNAP_FLEXIBLE_HYBRID, areOffscreenRatiosSupported(), bigEnoughFor33); if (bigEnoughFor33) { // Add 5 targets addNonDismissingTargets(List.of(leftTop10Position, leftTop33Position, getMiddleTargetPos(), rightBottom33Position, rightBottom10Position), dividerMax); targetSpec); } else { // If offscreen apps are not supported, just add the regular 3 targets. // Add 3 targets addNonDismissingTargets(List.of(leftTop10Position, getMiddleTargetPos(), rightBottom10Position), targetSpec); } } else { // If offscreen apps are not supported, just add the regular 1-3 targets. float ratio = SplitSpec.ONSCREEN_ONLY_ASYMMETRIC_RATIO; // The intended size of the smaller app, in pixels Loading @@ -470,8 +465,20 @@ public class DividerSnapAlgorithm { int leftTopPosition = start + size; int rightBottomPosition = end - size - mDividerSize; // Get the desired layout for our current device/display/rotation. boolean bigEnoughFor33 = size >= mMinimalSizeResizableTask; List<Integer> targetSpec = SplitSpec.getSnapTargetLayout(SNAP_FLEXIBLE_HYBRID, areOffscreenRatiosSupported(), bigEnoughFor33); if (bigEnoughFor33) { // Add 3 targets addNonDismissingTargets(List.of(leftTopPosition, getMiddleTargetPos(), rightBottomPosition), dividerMax); rightBottomPosition), targetSpec); } else { // Add 1 target addNonDismissingTargets(List.of(getMiddleTargetPos()), targetSpec); } } } Loading @@ -488,17 +495,19 @@ public class DividerSnapAlgorithm { int sizeInt = (int) Math.floor(size); int topPosition = start + sizeInt; int bottomPosition = end - sizeInt - mDividerSize; addNonDismissingTargets(List.of(topPosition, getMiddleTargetPos(), bottomPosition), dividerMax); } /** * Adds a target at {@param position} but only if the area with size of {@param smallerSize} * meets the minimal size requirement. */ private void maybeAddTarget(int position, int smallerSize, @SnapPosition int snapPosition) { if (smallerSize >= mMinimalSizeResizableTask || areOffscreenRatiosSupported()) { mTargets.add(new SnapTarget(position, snapPosition)); // Get the desired layout for our current device/display/rotation. boolean bigEnoughFor33 = sizeInt >= mMinimalSizeResizableTask; List<Integer> targetSpec = SplitSpec.getSnapTargetLayout(SNAP_MODE_16_9, areOffscreenRatiosSupported(), bigEnoughFor33); if (bigEnoughFor33) { // Add 3 targets addNonDismissingTargets(List.of(topPosition, getMiddleTargetPos(), bottomPosition), targetSpec); } else { // Add 1 target addNonDismissingTargets(List.of(getMiddleTargetPos()), targetSpec); } } Loading Loading @@ -543,11 +552,11 @@ public class DividerSnapAlgorithm { } public boolean isSecondSplitTargetAvailable() { return mSecondSplitTarget != mMiddleTarget; return mSecondSplitTarget != mMiddleTarget && mSecondSplitTarget != null; } public boolean isSecondLastSplitTargetAvailable() { return mSecondLastSplitTarget != mMiddleTarget; return mSecondLastSplitTarget != mMiddleTarget && mSecondLastSplitTarget != null; } /** Loading libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java +2 −21 Original line number Diff line number Diff line Loading @@ -24,7 +24,6 @@ import static android.view.WindowManager.DOCKED_TOP; import static com.android.internal.jank.InteractionJankMonitor.CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER; import static com.android.internal.jank.InteractionJankMonitor.CUJ_SPLIT_SCREEN_RESIZE; import static com.android.wm.shell.common.split.DividerSnapAlgorithm.SNAP_FLEXIBLE_HYBRID; import static com.android.wm.shell.common.split.DividerSnapAlgorithm.SNAP_FLEXIBLE_SPLIT; import static com.android.wm.shell.shared.animation.Interpolators.EMPHASIZED; import static com.android.wm.shell.shared.animation.Interpolators.FAST_OUT_SLOW_IN; import static com.android.wm.shell.shared.animation.Interpolators.LINEAR; Loading Loading @@ -607,16 +606,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange bounds2.left = bounds1.right + mDividerSize; if (mDividerSnapAlgorithm.areOffscreenRatiosSupported()) { if (mDividerSnapAlgorithm.getSnapMode() == SNAP_FLEXIBLE_SPLIT) { // In flexible split, also extend app offscreen. int distanceToCenter = position - mDividerSnapAlgorithm.getMiddleTarget().position; if (position < mDividerSnapAlgorithm.getMiddleTarget().position) { bounds1.left += distanceToCenter * 2; } else { bounds2.right += distanceToCenter * 2; } } else if (mDividerSnapAlgorithm.getSnapMode() == SNAP_FLEXIBLE_HYBRID) { if (mDividerSnapAlgorithm.getSnapMode() == SNAP_FLEXIBLE_HYBRID) { // In flex hybrid split, extend offscreen only if it is at the flex breakpoint. int leftFlexTargetPos = mDividerSnapAlgorithm.getFirstSplitTarget().position; int rightFlexTargetPos = mDividerSnapAlgorithm.getLastSplitTarget().position; Loading @@ -636,16 +626,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange bounds2.top = bounds1.bottom + mDividerSize; if (mDividerSnapAlgorithm.areOffscreenRatiosSupported()) { if (mDividerSnapAlgorithm.getSnapMode() == SNAP_FLEXIBLE_SPLIT) { // In flexible split, also extend app offscreen. int distanceToCenter = position - mDividerSnapAlgorithm.getMiddleTarget().position; if (position < mDividerSnapAlgorithm.getMiddleTarget().position) { bounds1.top += distanceToCenter * 2; } else { bounds2.bottom += distanceToCenter * 2; } } else if (mDividerSnapAlgorithm.getSnapMode() == SNAP_FLEXIBLE_HYBRID) { if (mDividerSnapAlgorithm.getSnapMode() == SNAP_FLEXIBLE_HYBRID) { // In flex hybrid split, extend offscreen only if it is at the flex breakpoint. int topFlexTargetPos = mDividerSnapAlgorithm.getFirstSplitTarget().position; int bottomFlexTargetPos = mDividerSnapAlgorithm.getLastSplitTarget().position; Loading libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitSpec.java +31 −14 Original line number Diff line number Diff line Loading @@ -18,7 +18,6 @@ package com.android.wm.shell.common.split; import static com.android.wm.shell.common.split.DividerSnapAlgorithm.SNAP_FIXED_RATIO; import static com.android.wm.shell.common.split.DividerSnapAlgorithm.SNAP_FLEXIBLE_HYBRID; import static com.android.wm.shell.common.split.DividerSnapAlgorithm.SNAP_FLEXIBLE_SPLIT; import static com.android.wm.shell.common.split.DividerSnapAlgorithm.SNAP_MODE_16_9; import static com.android.wm.shell.common.split.DividerSnapAlgorithm.SNAP_MODE_MINIMIZED; import static com.android.wm.shell.common.split.DividerSnapAlgorithm.SNAP_ONLY_1_1; Loading @@ -30,7 +29,9 @@ import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_9 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.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_START_AND_DISMISS; import static com.android.wm.shell.shared.split.SplitScreenConstants.stateToString; import android.graphics.Rect; Loading Loading @@ -68,6 +69,8 @@ public class SplitSpec { public static final List<Integer> FIVE_TARGETS = List.of(SNAP_TO_2_10_90, SNAP_TO_2_33_66, SNAP_TO_2_50_50, SNAP_TO_2_66_33, SNAP_TO_2_90_10); public static final List<Integer> DISMISS_TARGETS = List.of(SNAP_TO_START_AND_DISMISS, SNAP_TO_END_AND_DISMISS); private final boolean mIsLeftRightSplit; /** The physical size of the display. */ Loading Loading @@ -207,20 +210,34 @@ public class SplitSpec { } /** * Returns the expected layout of snap targets for a particular snap mode as a List of * SnapPositions. * Returns the expected layout of snap targets as a List of SnapPositions. * @param snapMode An int representing the "type" of snapping currently active on this device. * @param areOffscreenRatiosSupported {@code true} if the current {@code snapMode} and display * size allow for apps to go flexibly offscreen. * @param bigEnoughFor33 {@code true} if the display is large enough for apps to be shown at * 33% of their full size without violating minimum app size * requirements. * @return An ordered List of ints representing the number and type of SnapTargets that should * be created. */ public static List<Integer> getSnapTargetLayout( int snapMode, boolean areOffscreenRatiosSupported) { return switch (snapMode) { case SNAP_ONLY_1_1 -> ONE_TARGET; case SNAP_MODE_MINIMIZED -> ONE_TARGET_MINIMIZED; case SNAP_MODE_16_9, SNAP_FIXED_RATIO -> THREE_TARGETS_ONSCREEN; case SNAP_FLEXIBLE_SPLIT -> areOffscreenRatiosSupported ? THREE_TARGETS_OFFSCREEN : THREE_TARGETS_ONSCREEN; case SNAP_FLEXIBLE_HYBRID -> areOffscreenRatiosSupported ? FIVE_TARGETS : THREE_TARGETS_ONSCREEN; default -> throw new IllegalStateException("unrecognized snap mode"); }; int snapMode, boolean areOffscreenRatiosSupported, boolean bigEnoughFor33) { switch (snapMode) { case SNAP_ONLY_1_1: return ONE_TARGET; case SNAP_MODE_MINIMIZED: return ONE_TARGET_MINIMIZED; case SNAP_MODE_16_9: case SNAP_FIXED_RATIO: return bigEnoughFor33 ? THREE_TARGETS_ONSCREEN : ONE_TARGET; case SNAP_FLEXIBLE_HYBRID: if (areOffscreenRatiosSupported) { return bigEnoughFor33 ? FIVE_TARGETS : THREE_TARGETS_OFFSCREEN; } else { return bigEnoughFor33 ? THREE_TARGETS_ONSCREEN : ONE_TARGET; } default: throw new IllegalStateException("unrecognized snap mode"); } } } Loading
libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerSnapAlgorithm.java +107 −98 Original line number Diff line number Diff line Loading @@ -19,6 +19,9 @@ 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.common.split.SplitSpec.DISMISS_TARGETS; import static com.android.wm.shell.common.split.SplitSpec.ONE_TARGET; import static com.android.wm.shell.common.split.SplitSpec.THREE_TARGETS_ONSCREEN; 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_END_AND_DISMISS; import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_MINIMIZE; Loading @@ -33,7 +36,6 @@ import androidx.annotation.Nullable; import com.android.mechanics.spec.MotionSpec; import com.android.wm.shell.Flags; import com.android.wm.shell.shared.split.SplitScreenConstants.PersistentSnapPosition; import java.util.ArrayList; import java.util.List; Loading Loading @@ -69,14 +71,10 @@ public class DividerSnapAlgorithm { * 1 snap target: minimized height, (1 - minimized height) */ static final int SNAP_MODE_MINIMIZED = 3; /** * A mode where apps can be "flexibly offscreen" on smaller displays. */ static final int SNAP_FLEXIBLE_SPLIT = 4; /** * A mode combining {@link #SNAP_FIXED_RATIO} with {@link #SNAP_FLEXIBLE_SPLIT}. Has 5 * split screen snap points on smaller devices. * A mode that has 5 split screen snap points on smaller devices (10:90, 33:66, 50:50, 66:33, * 90:10). Except if it's *too* small, then it will only have 3 (10:90, 50:50, 90:10). Larger * devices (tablet size) will also have 3 (33:66, 50:50, 66:33). */ static final int SNAP_FLEXIBLE_HYBRID = 5; Loading @@ -102,19 +100,31 @@ public class DividerSnapAlgorithm { private final int mDockSide; /** * The first, and usually only, snap target between the left/top screen edge and center. * The first snap target, counting from the top/left, and not including dismiss targets. * On modes with 5 snap targets, this will refer to the 10% target. * On modes with 3 snap targets, this will refer to the 10% or 33% target. * On modes with only 1 target, it will refer to the middle target. */ private final SnapTarget mFirstSplitTarget; /** * Another snap target on the top/left side (closer to center than the "first"). * The second snap target, counting from the top/left, and not including dismiss targets. * On modes with 5 snap targets, this will refer to the 33% target. * On modes with 3 snap targets, this will refer to the middle target. * On modes with only 1 target, it will also refer to the middle target. */ private final SnapTarget mSecondSplitTarget; /** * The last, and usually only, snap target between the center and the right/bottom screen edge. * The last snap target, counting from the top/left, and not including dismiss targets. * On modes with 5 snap targets, this will refer to the 90% target. * On modes with 3 snap targets, this will refer to the 66% or 90% target. * On modes with only 1 target, it will refer to the middle target. */ private final SnapTarget mLastSplitTarget; /** * Another snap target on the right/bottom side (closer to center than the "last"). * The second-last snap target, counting from the top/left, and not including dismiss targets. * On modes with 5 snap targets, this will refer to the 66% target. * On modes with 3 snap targets, this will refer to the middle target. * On modes with only 1 target, it will also refer to the middle target. */ private final SnapTarget mSecondLastSplitTarget; Loading Loading @@ -170,16 +180,20 @@ public class DividerSnapAlgorithm { mTaskHeightInMinimizedMode = isHomeResizable ? res.getDimensionPixelSize( com.android.internal.R.dimen.task_height_of_minimized_mode) : 0; calculateTargets(); mMiddleTarget = mTargets.get(mTargets.size() / 2); mMiddleTarget.isMiddleTarget = true; mDismissStartTarget = mTargets.get(0); mFirstSplitTarget = mTargets.get(1); mSecondSplitTarget = mSnapMode == SNAP_FLEXIBLE_HYBRID && areOffscreenRatiosSupported() ? mTargets.get(2) : null; mSecondLastSplitTarget = mSnapMode == SNAP_FLEXIBLE_HYBRID && areOffscreenRatiosSupported() ? mTargets.get(mTargets.size() - 3) : null; if (mTargets.size() == ONE_TARGET.size() + DISMISS_TARGETS.size()) { mSecondSplitTarget = mSecondLastSplitTarget = null; } else if (mTargets.size() == THREE_TARGETS_ONSCREEN.size() + DISMISS_TARGETS.size()) { mSecondSplitTarget = mSecondLastSplitTarget = mMiddleTarget; } else { mSecondSplitTarget = mTargets.get(2); mSecondLastSplitTarget = mTargets.get(mTargets.size() - 3); } mLastSplitTarget = mTargets.get(mTargets.size() - 2); mDismissStartTarget = mTargets.get(0); mDismissEndTarget = mTargets.get(mTargets.size() - 1); mMiddleTarget = mTargets.get(mTargets.size() / 2); mMiddleTarget.isMiddleTarget = true; } /** Loading Loading @@ -340,9 +354,6 @@ public class DividerSnapAlgorithm { case SNAP_MODE_MINIMIZED: addMinimizedTarget(mDockSide); break; case SNAP_FLEXIBLE_SPLIT: addFlexSplitTargets(dividerMax); break; case SNAP_FLEXIBLE_HYBRID: addFlexHybridSplitTargets(dividerMax); break; Loading @@ -357,30 +368,14 @@ public class DividerSnapAlgorithm { * 3-target layout, and size 5 for a 5-target layout.) Should always be in * ascending order. */ private void addNonDismissingTargets(List<Integer> positions, int dividerMax) { // Get the desired layout for our snap mode. List<Integer> targetSpec = SplitSpec.getSnapTargetLayout(mSnapMode, areOffscreenRatiosSupported()); private void addNonDismissingTargets(List<Integer> positions, List<Integer> targetSpec) { if (positions.size() != targetSpec.size()) { throw new IllegalStateException("unexpected number of snap positions"); } // Iterate through the spec, adding a target for each. boolean midpointPassed = false; for (int i = 0; i < targetSpec.size(); i++) { @PersistentSnapPosition int target = targetSpec.get(i); int position = positions.get(i); if (!midpointPassed) { maybeAddTarget(position, position - getStartInset(), target); } else if (target == SNAP_TO_2_50_50) { mTargets.add(new SnapTarget(position, target)); midpointPassed = true; } else { maybeAddTarget(position, dividerMax - getEndInset() - (position + mDividerSize), target); } mTargets.add(new SnapTarget(positions.get(i), targetSpec.get(i))); } } Loading @@ -397,72 +392,72 @@ public class DividerSnapAlgorithm { int topPosition = start + size; int bottomPosition = end - size - mDividerSize; addNonDismissingTargets(List.of(topPosition, getMiddleTargetPos(), bottomPosition), dividerMax); } private void addFlexSplitTargets(int dividerMax) { int start = 0; int end = mIsLeftRightSplit ? mDisplayWidth : mDisplayHeight; int pinnedTaskbarShiftStart = mIsLeftRightSplit ? mPinnedTaskbarInsets.left : mPinnedTaskbarInsets.top; int pinnedTaskbarShiftEnd = mIsLeftRightSplit ? mPinnedTaskbarInsets.right : mPinnedTaskbarInsets.bottom; // Get the desired layout for our current device/display/rotation. boolean bigEnoughFor33 = size >= mMinimalSizeResizableTask; List<Integer> targetSpec = SplitSpec.getSnapTargetLayout(SNAP_FIXED_RATIO, areOffscreenRatiosSupported(), bigEnoughFor33); float ratio = areOffscreenRatiosSupported() ? SplitSpec.OFFSCREEN_ASYMMETRIC_RATIO : SplitSpec.ONSCREEN_ONLY_ASYMMETRIC_RATIO; // The intended size of the smaller app, in pixels int size = (int) (ratio * (end - start)) - mDividerSize / 2; // If there are insets that interfere with the smaller app (visually or blocking touch // targets), make the smaller app bigger by that amount to compensate. This applies to // pinned taskbar, 3-button nav (both create an opaque bar at bottom) and status bar (blocks // touch targets at top). int extraSpace = IntStream.of( getStartInset(), getEndInset(), pinnedTaskbarShiftStart, pinnedTaskbarShiftEnd ).max().getAsInt(); int leftTopPosition = start + extraSpace + size; int rightBottomPosition = end - extraSpace - size - mDividerSize; addNonDismissingTargets(List.of(leftTopPosition, getMiddleTargetPos(), rightBottomPosition), dividerMax); if (bigEnoughFor33) { // Add 3 targets addNonDismissingTargets(List.of(topPosition, getMiddleTargetPos(), bottomPosition), targetSpec); } else { // Add 1 target addNonDismissingTargets(List.of(getMiddleTargetPos()), targetSpec); } } private void addFlexHybridSplitTargets(int dividerMax) { int start = 0; int end = mIsLeftRightSplit ? mDisplayWidth : mDisplayHeight; int start = mIsLeftRightSplit ? mInsets.left : mInsets.top; int end = mIsLeftRightSplit ? mDisplayWidth - mInsets.right : mDisplayHeight - mInsets.bottom; int pinnedTaskbarShiftStart = mIsLeftRightSplit ? mPinnedTaskbarInsets.left : mPinnedTaskbarInsets.top; int pinnedTaskbarShiftEnd = mIsLeftRightSplit ? mPinnedTaskbarInsets.right : mPinnedTaskbarInsets.bottom; // If offscreen apps are supported, add 5 targets instead of 3. // If offscreen apps are supported, we are looking to add 3-5 targets. if (areOffscreenRatiosSupported()) { // Find the desired sizes for a 10% app and a 33% app. float ratio10 = SplitSpec.OFFSCREEN_ASYMMETRIC_RATIO; float ratio33 = SplitSpec.ONSCREEN_ONLY_ASYMMETRIC_RATIO; int size10 = (int) (ratio10 * (end - start)) - mDividerSize / 2; int size33 = (int) (ratio33 * (end - start)) - mDividerSize / 2; // If there are insets that interfere with the smaller app (visually or blocking touch // targets), make the 10% app ratio bigger by that amount to compensate. This applies to // pinned taskbar, 3-button nav (both create an opaque bar at bottom) and status bar // Insets affect where we want to put the snap targets. For the 10% target: If there are // insets that interfere with the divider position (visually or blocking touch targets), // we make the size bigger by that amount to compensate. This happens with an enabled // pinned taskbar, 3-button nav (both create an opaque bar at bottom) or status bar // (blocks touch targets at top). int extraSpaceFor10 = IntStream.of( getStartInset(), getEndInset(), pinnedTaskbarShiftStart, pinnedTaskbarShiftEnd ).max().getAsInt(); int size10 = (int) (ratio10 * dividerMax) + extraSpaceFor10 - mDividerSize / 2; // For the 33% target, we bake the insets into the position calculation below. int size33 = (int) (ratio33 * (end - start)) - mDividerSize / 2; int leftTop10Position = start + extraSpaceFor10 + size10; int rightBottom10Position = end - extraSpaceFor10 - size10 - mDividerSize; int leftTop10Position = size10; int rightBottom10Position = dividerMax - size10 - mDividerSize; int leftTop33Position = start + size33; int rightBottom33Position = end - size33 - mDividerSize; // Get the desired layout for our current device/display/rotation. boolean bigEnoughFor33 = size33 >= mMinimalSizeResizableTask; List<Integer> targetSpec = SplitSpec.getSnapTargetLayout(SNAP_FLEXIBLE_HYBRID, areOffscreenRatiosSupported(), bigEnoughFor33); if (bigEnoughFor33) { // Add 5 targets addNonDismissingTargets(List.of(leftTop10Position, leftTop33Position, getMiddleTargetPos(), rightBottom33Position, rightBottom10Position), dividerMax); targetSpec); } else { // If offscreen apps are not supported, just add the regular 3 targets. // Add 3 targets addNonDismissingTargets(List.of(leftTop10Position, getMiddleTargetPos(), rightBottom10Position), targetSpec); } } else { // If offscreen apps are not supported, just add the regular 1-3 targets. float ratio = SplitSpec.ONSCREEN_ONLY_ASYMMETRIC_RATIO; // The intended size of the smaller app, in pixels Loading @@ -470,8 +465,20 @@ public class DividerSnapAlgorithm { int leftTopPosition = start + size; int rightBottomPosition = end - size - mDividerSize; // Get the desired layout for our current device/display/rotation. boolean bigEnoughFor33 = size >= mMinimalSizeResizableTask; List<Integer> targetSpec = SplitSpec.getSnapTargetLayout(SNAP_FLEXIBLE_HYBRID, areOffscreenRatiosSupported(), bigEnoughFor33); if (bigEnoughFor33) { // Add 3 targets addNonDismissingTargets(List.of(leftTopPosition, getMiddleTargetPos(), rightBottomPosition), dividerMax); rightBottomPosition), targetSpec); } else { // Add 1 target addNonDismissingTargets(List.of(getMiddleTargetPos()), targetSpec); } } } Loading @@ -488,17 +495,19 @@ public class DividerSnapAlgorithm { int sizeInt = (int) Math.floor(size); int topPosition = start + sizeInt; int bottomPosition = end - sizeInt - mDividerSize; addNonDismissingTargets(List.of(topPosition, getMiddleTargetPos(), bottomPosition), dividerMax); } /** * Adds a target at {@param position} but only if the area with size of {@param smallerSize} * meets the minimal size requirement. */ private void maybeAddTarget(int position, int smallerSize, @SnapPosition int snapPosition) { if (smallerSize >= mMinimalSizeResizableTask || areOffscreenRatiosSupported()) { mTargets.add(new SnapTarget(position, snapPosition)); // Get the desired layout for our current device/display/rotation. boolean bigEnoughFor33 = sizeInt >= mMinimalSizeResizableTask; List<Integer> targetSpec = SplitSpec.getSnapTargetLayout(SNAP_MODE_16_9, areOffscreenRatiosSupported(), bigEnoughFor33); if (bigEnoughFor33) { // Add 3 targets addNonDismissingTargets(List.of(topPosition, getMiddleTargetPos(), bottomPosition), targetSpec); } else { // Add 1 target addNonDismissingTargets(List.of(getMiddleTargetPos()), targetSpec); } } Loading Loading @@ -543,11 +552,11 @@ public class DividerSnapAlgorithm { } public boolean isSecondSplitTargetAvailable() { return mSecondSplitTarget != mMiddleTarget; return mSecondSplitTarget != mMiddleTarget && mSecondSplitTarget != null; } public boolean isSecondLastSplitTargetAvailable() { return mSecondLastSplitTarget != mMiddleTarget; return mSecondLastSplitTarget != mMiddleTarget && mSecondLastSplitTarget != null; } /** Loading
libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java +2 −21 Original line number Diff line number Diff line Loading @@ -24,7 +24,6 @@ import static android.view.WindowManager.DOCKED_TOP; import static com.android.internal.jank.InteractionJankMonitor.CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER; import static com.android.internal.jank.InteractionJankMonitor.CUJ_SPLIT_SCREEN_RESIZE; import static com.android.wm.shell.common.split.DividerSnapAlgorithm.SNAP_FLEXIBLE_HYBRID; import static com.android.wm.shell.common.split.DividerSnapAlgorithm.SNAP_FLEXIBLE_SPLIT; import static com.android.wm.shell.shared.animation.Interpolators.EMPHASIZED; import static com.android.wm.shell.shared.animation.Interpolators.FAST_OUT_SLOW_IN; import static com.android.wm.shell.shared.animation.Interpolators.LINEAR; Loading Loading @@ -607,16 +606,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange bounds2.left = bounds1.right + mDividerSize; if (mDividerSnapAlgorithm.areOffscreenRatiosSupported()) { if (mDividerSnapAlgorithm.getSnapMode() == SNAP_FLEXIBLE_SPLIT) { // In flexible split, also extend app offscreen. int distanceToCenter = position - mDividerSnapAlgorithm.getMiddleTarget().position; if (position < mDividerSnapAlgorithm.getMiddleTarget().position) { bounds1.left += distanceToCenter * 2; } else { bounds2.right += distanceToCenter * 2; } } else if (mDividerSnapAlgorithm.getSnapMode() == SNAP_FLEXIBLE_HYBRID) { if (mDividerSnapAlgorithm.getSnapMode() == SNAP_FLEXIBLE_HYBRID) { // In flex hybrid split, extend offscreen only if it is at the flex breakpoint. int leftFlexTargetPos = mDividerSnapAlgorithm.getFirstSplitTarget().position; int rightFlexTargetPos = mDividerSnapAlgorithm.getLastSplitTarget().position; Loading @@ -636,16 +626,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange bounds2.top = bounds1.bottom + mDividerSize; if (mDividerSnapAlgorithm.areOffscreenRatiosSupported()) { if (mDividerSnapAlgorithm.getSnapMode() == SNAP_FLEXIBLE_SPLIT) { // In flexible split, also extend app offscreen. int distanceToCenter = position - mDividerSnapAlgorithm.getMiddleTarget().position; if (position < mDividerSnapAlgorithm.getMiddleTarget().position) { bounds1.top += distanceToCenter * 2; } else { bounds2.bottom += distanceToCenter * 2; } } else if (mDividerSnapAlgorithm.getSnapMode() == SNAP_FLEXIBLE_HYBRID) { if (mDividerSnapAlgorithm.getSnapMode() == SNAP_FLEXIBLE_HYBRID) { // In flex hybrid split, extend offscreen only if it is at the flex breakpoint. int topFlexTargetPos = mDividerSnapAlgorithm.getFirstSplitTarget().position; int bottomFlexTargetPos = mDividerSnapAlgorithm.getLastSplitTarget().position; Loading
libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitSpec.java +31 −14 Original line number Diff line number Diff line Loading @@ -18,7 +18,6 @@ package com.android.wm.shell.common.split; import static com.android.wm.shell.common.split.DividerSnapAlgorithm.SNAP_FIXED_RATIO; import static com.android.wm.shell.common.split.DividerSnapAlgorithm.SNAP_FLEXIBLE_HYBRID; import static com.android.wm.shell.common.split.DividerSnapAlgorithm.SNAP_FLEXIBLE_SPLIT; import static com.android.wm.shell.common.split.DividerSnapAlgorithm.SNAP_MODE_16_9; import static com.android.wm.shell.common.split.DividerSnapAlgorithm.SNAP_MODE_MINIMIZED; import static com.android.wm.shell.common.split.DividerSnapAlgorithm.SNAP_ONLY_1_1; Loading @@ -30,7 +29,9 @@ import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_9 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.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_START_AND_DISMISS; import static com.android.wm.shell.shared.split.SplitScreenConstants.stateToString; import android.graphics.Rect; Loading Loading @@ -68,6 +69,8 @@ public class SplitSpec { public static final List<Integer> FIVE_TARGETS = List.of(SNAP_TO_2_10_90, SNAP_TO_2_33_66, SNAP_TO_2_50_50, SNAP_TO_2_66_33, SNAP_TO_2_90_10); public static final List<Integer> DISMISS_TARGETS = List.of(SNAP_TO_START_AND_DISMISS, SNAP_TO_END_AND_DISMISS); private final boolean mIsLeftRightSplit; /** The physical size of the display. */ Loading Loading @@ -207,20 +210,34 @@ public class SplitSpec { } /** * Returns the expected layout of snap targets for a particular snap mode as a List of * SnapPositions. * Returns the expected layout of snap targets as a List of SnapPositions. * @param snapMode An int representing the "type" of snapping currently active on this device. * @param areOffscreenRatiosSupported {@code true} if the current {@code snapMode} and display * size allow for apps to go flexibly offscreen. * @param bigEnoughFor33 {@code true} if the display is large enough for apps to be shown at * 33% of their full size without violating minimum app size * requirements. * @return An ordered List of ints representing the number and type of SnapTargets that should * be created. */ public static List<Integer> getSnapTargetLayout( int snapMode, boolean areOffscreenRatiosSupported) { return switch (snapMode) { case SNAP_ONLY_1_1 -> ONE_TARGET; case SNAP_MODE_MINIMIZED -> ONE_TARGET_MINIMIZED; case SNAP_MODE_16_9, SNAP_FIXED_RATIO -> THREE_TARGETS_ONSCREEN; case SNAP_FLEXIBLE_SPLIT -> areOffscreenRatiosSupported ? THREE_TARGETS_OFFSCREEN : THREE_TARGETS_ONSCREEN; case SNAP_FLEXIBLE_HYBRID -> areOffscreenRatiosSupported ? FIVE_TARGETS : THREE_TARGETS_ONSCREEN; default -> throw new IllegalStateException("unrecognized snap mode"); }; int snapMode, boolean areOffscreenRatiosSupported, boolean bigEnoughFor33) { switch (snapMode) { case SNAP_ONLY_1_1: return ONE_TARGET; case SNAP_MODE_MINIMIZED: return ONE_TARGET_MINIMIZED; case SNAP_MODE_16_9: case SNAP_FIXED_RATIO: return bigEnoughFor33 ? THREE_TARGETS_ONSCREEN : ONE_TARGET; case SNAP_FLEXIBLE_HYBRID: if (areOffscreenRatiosSupported) { return bigEnoughFor33 ? FIVE_TARGETS : THREE_TARGETS_OFFSCREEN; } else { return bigEnoughFor33 ? THREE_TARGETS_ONSCREEN : ONE_TARGET; } default: throw new IllegalStateException("unrecognized snap mode"); } } }