Pad/tools/touchinject/run_tests.sh
Greg Pomerantz 180fa966c8 Pinch-to-font-size (continuous, content-point pinned) + IME-open scroll fix
Two feature bodies accumulated in the working tree:

1. Pinch to change the app font size, continuously (no snapping):
   - internal/ui/pinch_tracker.go: logic-free touch state machine.
     Two-mover formation (the resting palm can land first or last;
     movement is the only signal valid for both), pair = the mover
     pair whose distance changed most, baseline = press distance
     (formDist), lazy pending releases, survivor-scroll forwarding
     after a pair break. Robust to ~1 fps frames: a whole pinch can
     land in one drain (formDist/brokeFactor/lazy releases).
   - render.go: pinch probe (raw pointer events) + grab lifecycle so
     the pair is exclusive (scroll sees nothing of the pair) and the
     survivor's finger keeps working as a scroll after the pinch.
   - state.go/logic.go/session.go/frame.go: app-local float font
     scale, content-point pin (buffer byte + offset from baseline,
     not a layout point, so rewrap keeps the same character under
     the center), restore/font pins, session persistence.
   - pinch_test.go, pinch_font_test.go, tag_identity_test.go,
     real_draw_probe_test.go: unit + real-Renderer/real-Router tests.

2. Soft keyboard must not shift content:
   - Root cause: gioui.org/app calls Router.RevealFocus on any frame
     the viewport shrinks (IME open under adjustResize) and
     synthesizes a pointer.Scroll nudge aimed at the focused field's
     stale pre-resize bounds; gesture.Scroll consumed it -> a 32 dp
     content jump.
   - Fix: main.go flags the shrink frame; render.go drains that one
     synthetic scroll for the gesture's tag before Update (scroll-
     range clamping cannot work: the router UNIONs ranges across
     frames). Finger scroll (pointer.Drag) and the flinger are
     untouched. reveal_focus_drain_test.go reproduces RevealFocus at
     the router level and verifies the drain + zero delta.

Also: tools/touchinject (platform-signed emulator multi-touch
injection harness + e2e script, adb has no two-finger input),
docs (spec 2.2 + development_plan 18-20), .gitignore, gofmt.
2026-08-23 09:00:51 -04:00

129 lines
4.7 KiB
Bash
Executable File

#!/usr/bin/env bash
# Pinch e2e test harness for the Pad emulator (aosp_atd AVD).
#
# The platform-signed touch.inject app (real MotionEvents through
# InputManager — the only multi-touch path that works in the emulator)
# drives the renderer's pinch probe; each gesture below is ONE script in
# ONE process (the injector's finger map is per-process).
#
# KNOWN FLAKES (both verified, both handled below):
# 1. The injector process is "cached" while its script thread runs and the
# 1.5 GB emulator OOM-kills it mid-script occasionally — the script
# never reaches "=== done". ti() checks logcat for the done line and
# re-runs (a re-run starts a fresh gesture; a stuck pointer from a
# half-run is replaced by the next ACTION_DOWN).
# 2. The app is on-demand-rendering at ~1 fps: a burst of moves within
# one frame's drain does not scroll (single-frame delta pattern).
# Scroll tests therefore space the moves >= 40 ms apart — which is
# also what a real finger produces over several frames.
#
# READING RESULTS: AppFontScale in the session file is the reliable pinch
# signal. Scroll in the session is NOT (the restore re-derives it from the
# pinned line anchor); for scroll tests compare the before/after
# screenshots instead.
SER=emulator-5554
export ANDROID_SERIAL=$SER
ti() {
local script="$1"
for attempt in 1 2 3; do
adb logcat -c 2>/dev/null
adb shell 'am broadcast -n "touch.inject/.Injector$ScriptReceiver" --es script "'"$script"'"' >/dev/null 2>&1
sleep 4
if adb shell logcat -d 2>/dev/null | grep -aq "TouchInject: === done"; then
return 0
fi
echo " (injection incomplete, attempt $attempt — retrying)"
done
echo " !! injection failed after 3 attempts"
return 1
}
# ensure Pad is in the foreground (the ATD launcher intermittently holds
# focus after a cold start; a backgrounded app receives no touch)
fg() {
for i in 1 2 3 4 5; do
FOC=$(adb shell 'dumpsys window | grep mCurrentFocus')
case "$FOC" in *pad.pad*) return 0;; esac
adb shell am start -n pad.pad/org.gioui.GioActivity >/dev/null 2>&1
sleep 8
done
echo " !! Pad never gained focus: $FOC"
return 1
}
dbg() { adb shell "echo '$1' > /sdcard/PadPerf/cmd"; sleep 1.5; }
shot() { adb exec-out screencap -p > "$1"; }
# force-stop (flushes the session), print it, relaunch + reopen the file
measure() {
adb shell am force-stop pad.pad
sleep 4
echo "session: $(adb shell cat /sdcard/Pad/session.json 2>/dev/null)"
adb shell am start -n pad.pad/org.gioui.GioActivity >/dev/null 2>&1
sleep 4
}
case "$1" in
t1) # single-finger scroll must NEVER change the font (device regression)
echo "=== T1: single-finger scroll (font must not change) ==="
fg || exit 1
shot /tmp/t1_before.png
S="down 1 720 1400 wait 80"
for y in 1320 1240 1160 1080 1000 920 840 760 680 600 520 440 360 280 200; do
S="$S move 1 720 $y wait 40"
done
S="$S wait 100 up 1"
ti "$S"
sleep 2
shot /tmp/t1_after.png # compare to before: content moved, same font size
measure
;;
t2) # two-finger pinch-out: font must scale, center anchored
echo "=== T2: two-finger pinch-out (font must grow) ==="
fg || exit 1
shot /tmp/t2_before.png
S="down 1 570 1200 wait 40 down 2 870 1200 wait 60"
for i in 1 2 3 4 5 6 7 8; do
x1=$((570 - i*37)); x2=$((870 + i*37))
S="$S move 1 $x1 1200 move 2 $x2 1200 wait 50"
done
S="$S wait 150 up 1 up 2"
ti "$S"
sleep 1
shot /tmp/t2_after.png
measure
;;
t3) # palm-first: the resting finger lands FIRST, the pinch pair is the
# two that move — the palm must never enter the distance
echo "=== T3: palm-first three fingers (pinch pair = the two movers) ==="
fg || exit 1
shot /tmp/t3_before.png
S="down 1 720 400 wait 100 down 2 570 1200 wait 60 down 3 870 1200 wait 150"
for i in 1 2 3 4 5 6 7 8; do
x2=$((570 - i*37)); x3=$((870 + i*37))
S="$S move 2 $x2 1200 move 3 $x3 1200 wait 50"
done
S="$S wait 150 up 2 up 3 wait 50 up 1"
ti "$S"
sleep 1
shot /tmp/t3_after.png
measure
;;
t4) # lift one finger mid-pinch: survivor must SCROLL, not zoom
echo "=== T4: lift finger mid-pinch (survivor scrolls, no more zoom) ==="
fg || exit 1
shot /tmp/t4_before.png
S="down 1 570 1200 wait 40 down 2 870 1200 wait 60 move 1 533 1200 move 2 907 1200 wait 80 up 2 wait 80"
for y in 1280 1360 1440 1520 1600 1680 1760 1840 1920 2000; do
S="$S move 1 533 $y wait 40" # survivor (finger 1) keeps its own x
done
S="$S wait 100 up 1"
ti "$S"
sleep 1
shot /tmp/t4_after.png # compare to before: content scrolled, font frozen
measure
;;
reset) # back to a known state (one command per file — no pipes)
dbg "fontsize 1"; dbg "top"
;;
*) echo "usage: $0 t1|t2|t3|t4|reset" ;;
esac