diff --git a/internal/runner/runner.go b/internal/runner/runner.go index 4cfcf61..2fe6cc0 100644 --- a/internal/runner/runner.go +++ b/internal/runner/runner.go @@ -768,21 +768,20 @@ func screenBounds(tree *hierarchy.Tree) hierarchy.Bounds { return bounds } -// clampGestureToSafeArea keeps a swipe's origin out of the system gesture zones -// at the screen edges. A gesture that starts in the top strip pulls down the -// notification shade; the bottom strip is the home gesture and the side strips -// are the back gesture. Any of these drags the fuzzer out of the app. The -// destination only needs to stay on screen. With an unknown screen size the -// coordinates pass through unchanged. +// clampGestureToSafeArea keeps a swipe's origin below the top status strip, +// where a downward drag pulls the notification shade over the app. Runs force +// 3-button navigation (ForceThreeButtonNav), which disables the side back and +// bottom home gestures at the OS level; on-device probing confirmed side and +// bottom origins then no longer drift, so the shade is the only edge gesture a +// swipe can still trigger. Origin and destination are otherwise only kept on +// screen. With an unknown screen size the coordinates pass through unchanged. func clampGestureToSafeArea(fromX, fromY, toX, toY int, screen hierarchy.Bounds) (int, int, int, int) { width, height := screen.Width(), screen.Height() if width <= 0 || height <= 0 { return fromX, fromY, toX, toY } - // Margins are a fraction of each axis so they scale across devices and - // densities. The vertical margin clears the status bar (calibrated: swipes - // from below ~7% of height no longer open the shade) and the home gesture. - marginX := width / 20 + // The top margin clears the status bar; calibrated as a fraction of height + // so swipes from below ~8% of height no longer open the shade. marginY := height / 12 clamp := func(value, low, high int) int { if value < low { @@ -793,8 +792,8 @@ func clampGestureToSafeArea(fromX, fromY, toX, toY int, screen hierarchy.Bounds) } return value } - fromX = clamp(fromX, screen.Left+marginX, screen.Right-marginX) - fromY = clamp(fromY, screen.Top+marginY, screen.Bottom-marginY) + fromX = clamp(fromX, screen.Left, screen.Right) + fromY = clamp(fromY, screen.Top+marginY, screen.Bottom) toX = clamp(toX, screen.Left, screen.Right) toY = clamp(toY, screen.Top, screen.Bottom) return fromX, fromY, toX, toY diff --git a/internal/runner/runner_test.go b/internal/runner/runner_test.go index 77dbe3e..2f97125 100644 --- a/internal/runner/runner_test.go +++ b/internal/runner/runner_test.go @@ -1805,34 +1805,50 @@ func TestAwaitForeground_RelaunchesThenWaitsForWindow(t *testing.T) { } } -// TestClampGestureToSafeArea_KeepsOriginOutOfGestureZones locks the swipe fix: -// a gesture origin in the top status-bar strip pulls down the notification -// shade and drags the fuzzer out of the app. The origin must be pulled into the -// safe inner area while the destination only stays on screen. -func TestClampGestureToSafeArea_KeepsOriginOutOfGestureZones(t *testing.T) { +// TestClampGestureToSafeArea_KeepsOriginBelowShadeStrip locks the swipe fix: a +// gesture origin in the top status-bar strip pulls down the notification shade +// and drags the fuzzer out of the app. The origin must be pushed below the top +// margin. The side and bottom strips are NOT clamped: runs force 3-button +// navigation, which disables the back and home gestures, so (verified on device) +// those origins no longer drift. +func TestClampGestureToSafeArea_KeepsOriginBelowShadeStrip(t *testing.T) { screen := hierarchy.Bounds{Left: 0, Top: 0, Right: 1080, Bottom: 2400} - // marginX = 1080/20 = 54, marginY = 2400/12 = 200. + // marginY = 2400/12 = 200. - // The real shade-opening swipe captured on device: origin y=62 is pulled to + // The real shade-opening swipe captured on device: origin y=62 is pushed to // the top margin (200); the in-range destination is untouched. fromX, fromY, toX, toY := clampGestureToSafeArea(802, 62, 802, 447, screen) if fromX != 802 || fromY != 200 || toX != 802 || toY != 447 { - t.Errorf("origin not clamped to the safe area: from=(%d,%d) to=(%d,%d), want from=(802,200) to=(802,447)", fromX, fromY, toX, toY) + t.Errorf("origin not pushed below the shade strip: from=(%d,%d) to=(%d,%d), want from=(802,200) to=(802,447)", fromX, fromY, toX, toY) } - // Top-left corner origin pulled inside both margins. - fromX, fromY, _, _ = clampGestureToSafeArea(0, 0, 540, 1200, screen) - if fromX != 54 || fromY != 200 { - t.Errorf("corner origin not clamped to (54,200), got (%d,%d)", fromX, fromY) + // A side origin (left/right edge) passes through untouched: 3-button nav + // disables the back gesture, so only the top y is clamped. + fromX, fromY, _, _ = clampGestureToSafeArea(5, 1200, 540, 1200, screen) + if fromX != 5 || fromY != 1200 { + t.Errorf("side origin must pass through, got (%d,%d), want (5,1200)", fromX, fromY) } - // An off-screen destination is clamped onto the screen (the destination only - // needs to stay on screen, not inside the gesture margins). + // A bottom origin passes through (home gesture disabled by 3-button nav); + // only the top edge is a hazard. + fromX, fromY, _, _ = clampGestureToSafeArea(540, 2399, 540, 1200, screen) + if fromX != 540 || fromY != 2399 { + t.Errorf("bottom origin must pass through, got (%d,%d), want (540,2399)", fromX, fromY) + } + + // An off-screen destination is clamped onto the screen. _, _, toX, toY = clampGestureToSafeArea(540, 1200, -50, 9999, screen) if toX != 0 || toY != 2400 { t.Errorf("off-screen destination not clamped to screen edges: got (%d,%d), want (0,2400)", toX, toY) } + // An off-screen origin x is pulled back on screen even though it is not in a + // gesture zone. + fromX, _, _, _ = clampGestureToSafeArea(-30, 1200, 540, 1200, screen) + if fromX != 0 { + t.Errorf("off-screen origin x must clamp to 0, got %d", fromX) + } + // Unknown screen size leaves coordinates untouched. fromX, fromY, toX, toY = clampGestureToSafeArea(802, 62, 802, 447, hierarchy.Bounds{}) if fromX != 802 || fromY != 62 || toX != 802 || toY != 447 {