mirror of
https://github.com/priyanshujain/sanderling.git
synced 2026-10-04 12:07:09 +00:00
fix(runner): clamp swipe/scroll origin out of edge gesture zones
A gesture starting in the top status-bar strip pulls down the notification shade; the bottom and side strips are the home and back gestures. Any of them drags the fuzzer out of the app. Swipe and scroll origins are now clamped into a safe inner area sized from the maximum element extent (the Android hierarchy root reports zero bounds, so the extent is the reliable screen size). Calibrated on device: origins below ~7% of height no longer open the shade.
This commit is contained in:
1 parent
d965ed4575
commit
9c9d3c934e
2 files changed
+103
-1
No files matched your search
@@ -537,6 +537,7 @@ func applyAction(ctx context.Context, drv driver.DeviceDriver, action verifier.A
|
|||||||
return drv.LongPress(ctx, x, y)
|
return drv.LongPress(ctx, x, y)
|
||||||
case verifier.ActionKindScroll:
|
case verifier.ActionKindScroll:
|
||||||
fromX, fromY, toX, toY := scrollEndpoints(action, tree)
|
fromX, fromY, toX, toY := scrollEndpoints(action, tree)
|
||||||
|
fromX, fromY, toX, toY = clampGestureToSafeArea(fromX, fromY, toX, toY, screenBounds(tree))
|
||||||
duration := time.Duration(action.DurationMillis) * time.Millisecond
|
duration := time.Duration(action.DurationMillis) * time.Millisecond
|
||||||
if duration <= 0 {
|
if duration <= 0 {
|
||||||
duration = 300 * time.Millisecond
|
duration = 300 * time.Millisecond
|
||||||
@@ -581,7 +582,8 @@ func applyAction(ctx context.Context, drv driver.DeviceDriver, action verifier.A
|
|||||||
if duration <= 0 {
|
if duration <= 0 {
|
||||||
duration = 250 * time.Millisecond
|
duration = 250 * time.Millisecond
|
||||||
}
|
}
|
||||||
return drv.Swipe(ctx, action.FromX, action.FromY, action.ToX, action.ToY, duration)
|
fromX, fromY, toX, toY := clampGestureToSafeArea(action.FromX, action.FromY, action.ToX, action.ToY, screenBounds(tree))
|
||||||
|
return drv.Swipe(ctx, fromX, fromY, toX, toY, duration)
|
||||||
case verifier.ActionKindPressKey:
|
case verifier.ActionKindPressKey:
|
||||||
if action.Key == "" {
|
if action.Key == "" {
|
||||||
return nil
|
return nil
|
||||||
@@ -707,6 +709,58 @@ func scrollEndpoints(action verifier.Action, tree *hierarchy.Tree) (fromX, fromY
|
|||||||
|
|
||||||
// scrollBounds returns the container bounds for an authored Scroll: the node
|
// scrollBounds returns the container bounds for an authored Scroll: the node
|
||||||
// named by On when it resolves, otherwise the root (whole-screen) bounds.
|
// named by On when it resolves, otherwise the root (whole-screen) bounds.
|
||||||
|
// screenBounds returns the device screen rectangle as the maximum extent across
|
||||||
|
// all elements. The hierarchy root often reports zero bounds on Android, so the
|
||||||
|
// extent (driven by full-screen containers and the navigation bar) is the
|
||||||
|
// reliable screen size. Returns a zero rectangle when unknown.
|
||||||
|
func screenBounds(tree *hierarchy.Tree) hierarchy.Bounds {
|
||||||
|
if tree == nil {
|
||||||
|
return hierarchy.Bounds{}
|
||||||
|
}
|
||||||
|
var bounds hierarchy.Bounds
|
||||||
|
for _, element := range tree.Elements {
|
||||||
|
if element.Bounds.Right > bounds.Right {
|
||||||
|
bounds.Right = element.Bounds.Right
|
||||||
|
}
|
||||||
|
if element.Bounds.Bottom > bounds.Bottom {
|
||||||
|
bounds.Bottom = element.Bounds.Bottom
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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.
|
||||||
|
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
|
||||||
|
marginY := height / 12
|
||||||
|
clamp := func(value, low, high int) int {
|
||||||
|
if value < low {
|
||||||
|
return low
|
||||||
|
}
|
||||||
|
if value > high {
|
||||||
|
return high
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
fromX = clamp(fromX, screen.Left+marginX, screen.Right-marginX)
|
||||||
|
fromY = clamp(fromY, screen.Top+marginY, screen.Bottom-marginY)
|
||||||
|
toX = clamp(toX, screen.Left, screen.Right)
|
||||||
|
toY = clamp(toY, screen.Top, screen.Bottom)
|
||||||
|
return fromX, fromY, toX, toY
|
||||||
|
}
|
||||||
|
|
||||||
func scrollBounds(action verifier.Action, tree *hierarchy.Tree) hierarchy.Bounds {
|
func scrollBounds(action verifier.Action, tree *hierarchy.Tree) hierarchy.Bounds {
|
||||||
if tree == nil {
|
if tree == nil {
|
||||||
return hierarchy.Bounds{}
|
return hierarchy.Bounds{}
|
||||||
|
|||||||
@@ -1807,6 +1807,54 @@ 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) {
|
||||||
|
screen := hierarchy.Bounds{Left: 0, Top: 0, Right: 1080, Bottom: 2400}
|
||||||
|
// marginX = 1080/20 = 54, marginY = 2400/12 = 200.
|
||||||
|
|
||||||
|
// The real shade-opening swipe captured on device: origin y=62.
|
||||||
|
fromX, fromY, toX, toY := clampGestureToSafeArea(802, 62, 802, 447, screen)
|
||||||
|
if fromY < 200 {
|
||||||
|
t.Errorf("origin Y %d still inside the status-bar strip (need >= 200)", fromY)
|
||||||
|
}
|
||||||
|
if fromX != 802 || toX != 802 || toY != 447 {
|
||||||
|
t.Errorf("unexpected clamp of in-range coords: from=(%d,%d) to=(%d,%d)", 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
t.Error("coordinates must pass through unchanged when screen size is unknown")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestScreenBounds_UsesMaxExtentNotRoot guards the screen-size source: the
|
||||||
|
// Android hierarchy root reports zero bounds, so the screen rectangle must come
|
||||||
|
// from the maximum element extent or the gesture clamp silently no-ops.
|
||||||
|
func TestScreenBounds_UsesMaxExtentNotRoot(t *testing.T) {
|
||||||
|
tree := &hierarchy.Tree{
|
||||||
|
Root: &hierarchy.Node{Element: hierarchy.Element{Bounds: hierarchy.Bounds{}}},
|
||||||
|
Elements: []*hierarchy.Element{
|
||||||
|
{Bounds: hierarchy.Bounds{}},
|
||||||
|
{Bounds: hierarchy.Bounds{Left: 0, Top: 0, Right: 1080, Bottom: 2160}},
|
||||||
|
{Bounds: hierarchy.Bounds{Left: 0, Top: 2268, Right: 1080, Bottom: 2400}},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
got := screenBounds(tree)
|
||||||
|
if got.Right != 1080 || got.Bottom != 2400 {
|
||||||
|
t.Fatalf("screenBounds = %+v, want right=1080 bottom=2400", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TestEnsureForeground_DismissesSystemOverlay locks the shade fix: when the app
|
// TestEnsureForeground_DismissesSystemOverlay locks the shade fix: when the app
|
||||||
// is still the resumed activity but a system overlay (notification shade) holds
|
// is still the resumed activity but a system overlay (notification shade) holds
|
||||||
// the focused window, the guard must dismiss it with back rather than relaunch
|
// the focused window, the guard must dismiss it with back rather than relaunch
|
||||||
|
|||||||
Reference in new issue
Block a user