diff --git a/scripts/repro_gioui_hang.sh b/scripts/repro_gioui_hang.sh new file mode 100755 index 0000000..bbf7d89 --- /dev/null +++ b/scripts/repro_gioui_hang.sh @@ -0,0 +1,102 @@ +#!/usr/bin/env bash +# Reproduce the gioui Android activity-recreation hang with a STOCK gioui +# example app (the "hello" example from gioui.org/example). +# +# What it does: +# 1. Builds gioui.org/example's hello app (unmodified) with gogio. +# 2. Installs it on an emulator (boots one if needed). +# 3. Recreates the activity via `cmd uimode night yes` (uiMode is not in +# gioui's configChanges mask, so Android destroys/recreates the +# activity in the same process). +# 4. Taps the app and checks logcat for NO_INPUT_CHANNEL. +# +# The bug (gioui v0.10.x, app/os_android.go onCreateView): on re-attach the +# second onCreateView sends the window-creation ack +# (mainWindow.windows <- struct{}{}) on the UI thread, but its only +# receiver (newWindow in Window.init) never re-runs on Android — the driver +# is never reset, so w.Event() never returns to re-init. The UI thread +# blocks forever, the activity never attaches an input channel, and the app +# is black and unresponsive (process still alive). +# +# Usage: +# scripts/repro_gioui_hang.sh [avd-name] [path-to-apk] +# +# Exit codes: 0 = bug REPRODUCED, 1 = not reproduced, 2 = setup failure. +set -euo pipefail + +AVD=${1:-pad_avd} +APK=${2:-} +SDK=${ANDROID_HOME:-$HOME/android-sdk} +export PATH=$PATH:$HOME/go/bin:$SDK/platform-tools:$SDK/build-tools/35.0.0 + +say() { printf '\n=== %s\n' "$*"; } +die() { say "FAIL: $*"; exit 2; } + +# --- 1. Build the stock hello example, unless an APK was supplied --------- +if [ -z "$APK" ]; then + SRC=$(mktemp -d) + trap 'rm -rf "$SRC"' EXIT + say "building stock gioui.org/example hello app (v0.10.3)" + go mod download gioui.org/example@v0.10.3 + cp "$HOME/go/pkg/mod/gioui.org/example@v0.10.3/hello/hello.go" "$SRC/main.go" + cat > "$SRC/go.mod" <<'EOF' +module hello + +go 1.23 + +require gioui.org v0.10.3 +EOF + (cd "$SRC" && go mod tidy && gogio -target android -targetsdk 34 -arch amd64 -o /tmp/hello-gioui-repro.apk .) + APK=/tmp/hello-gioui-repro.apk +fi +[ -f "$APK" ] || die "APK not found: $APK" + +# --- 2. Emulator: reuse a booted one or start $AVD ------------------------ +say "waiting for emulator (avd $AVD)" +EMU="" +for _ in $(seq 1 60); do + EMU=$(adb devices | awk '$1 ~ /^emulator-/ {print $1; exit}') + [ -n "$EMU" ] && break + emulator -avd "$AVD" -no-window -no-audio -no-boot-anim -gpu swiftshader_indirect >/dev/null 2>&1 & + sleep 5 +done +[ -n "$EMU" ] || die "no emulator available" +for _ in $(seq 1 60); do + bc=$(adb -s "$EMU" shell getprop sys.boot_completed 2>/dev/null | tr -d '\r') + [ "$bc" = "1" ] && break + sleep 5 +done +[ "$(adb -s "$EMU" shell getprop sys.boot_completed | tr -d '\r')" = "1" ] || die "emulator did not boot" +say "emulator: $EMU" + +# --- 3. Install + launch --------------------------------------------------- +APPID=localhost.hello # gogio's default app id for module "hello" +adb -s "$EMU" install -r "$APK" >/dev/null +adb -s "$EMU" shell am force-stop "$APPID" >/dev/null 2>&1 || true +adb -s "$EMU" logcat -c +adb -s "$EMU" shell am start -n "$APPID/org.gioui.GioActivity" >/dev/null +sleep 5 +adb -s "$EMU" shell pidof "$APPID" >/dev/null || die "app did not start" +say "baseline: app running" + +# --- 4. Recreate the activity ---------------------------------------------- +# uiMode is not in gioui's configChanges mask -> Android recreates the +# activity. Force a real state change (a no-op toggle recreates nothing). +cur=$(adb -s "$EMU" shell cmd uimode night | tr -d '\r') # "Night mode: yes/no" +case $cur in + *yes*) adb -s "$EMU" shell cmd uimode night no >/dev/null ;; + *) adb -s "$EMU" shell cmd uimode night yes >/dev/null ;; +esac +sleep 5 +adb -s "$EMU" logcat -c +adb -s "$EMU" shell input tap 540 1200 +sleep 2 + +# --- 5. Verdict ------------------------------------------------------------- +adb -s "$EMU" exec-out screencap -p > /tmp/gioui-hang-repro.png +if adb -s "$EMU" logcat -d | grep -q NO_INPUT_CHANNEL; then + say "REPRODUCED: activity recreation left the window with NO_INPUT_CHANNEL (see /tmp/gioui-hang-repro.png)" + exit 0 +fi +say "NOT reproduced: no NO_INPUT_CHANNEL after recreation (gioui handles re-attach correctly)" +exit 1