Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 20c48dbe authored by Jeremy Sim's avatar Jeremy Sim
Browse files

Fix issue where 70:30 ratio was showing up outside the flag

In ag/33362389, the introduction of flexible "hybrid" split (with both 70:30 and 90:10 snap ratios available at the same time) unintentionally caused a bug outside of the flexible_two_app_split flag.

In some display sizes, the lack of screen space causes the 70:30 ratio to be disabled. ag/33362389 caused the 70:30 ratio to show up even in these scenarios.

This CL fixes the logic that caused that error, and also attempts to clean up and standardize all of the logic paths used in determining split layouts.

Fixes: 422912855
Flag: com.android.wm.shell.enable_flexible_two_app_split
Test: Manual repro with affected devices
Change-Id: I3e94b96fab8899de4672a86ce2cec2c54e9d6a54
parent 6cdc5cd2
Loading
Loading
Loading
Loading
+107 −98
Original line number Diff line number Diff line
@@ -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;
@@ -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;
@@ -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;

@@ -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;

@@ -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;
    }

    /**
@@ -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;
@@ -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)));
        }
    }

@@ -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
@@ -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);
            }
        }
    }

@@ -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);
        }
    }

@@ -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;
    }

    /**
+2 −21
Original line number Diff line number Diff line
@@ -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;
@@ -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;
@@ -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;
+31 −14
Original line number Diff line number Diff line
@@ -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;
@@ -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;
@@ -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. */
@@ -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");
        }
    }
}