Files
Michał Pierzchała 778349a991 fix: stabilize Android Maestro replay reliability (#799)
* docs: clarify Android snapshot helper idle waits

* fix: stabilize Android Maestro replay context
2026-06-13 19:08:57 +02:00
..

Android Snapshot Helper

Small instrumentation APK used to capture Android accessibility snapshots without relying on uiautomator dump's fixed idle wait behavior. The helper enables Android's interactive-window retrieval flag and serializes every accessible window root returned by UiAutomation.getWindows() so keyboards and system overlays can appear in the same snapshot. If interactive window roots are unavailable, it falls back to the active-window root.

The helper is intentionally provider-neutral. Local adb, cloud ADB tunnels, and remote device providers can all install and run the same APK as long as they can execute ADB-style operations. Released helper APKs use the committed debug.keystore; do not rotate it casually, because Android requires a stable signing certificate for adb install -r upgrades.

Build

VERSION="$(node -p 'require("./package.json").version')"
sh ./scripts/build-android-snapshot-helper.sh "$VERSION" .tmp/android-snapshot-helper

The build uses Android SDK command-line tools directly. It expects ANDROID_HOME or ANDROID_SDK_ROOT to point at an SDK with platforms/android-36 and matching build tools. pnpm prepack builds the npm-bundled helper into android-snapshot-helper/dist; npm users get that APK in the package and the first helper-backed snapshot installs it automatically when missing or outdated.

Run

VERSION="$(node -p 'require("./package.json").version')"
adb install -r -t ".tmp/android-snapshot-helper/agent-device-android-snapshot-helper-$VERSION.apk"
adb shell am instrument -w \
  -e waitForIdleTimeoutMs 500 \
  -e waitForIdleQuietMs 100 \
  -e timeoutMs 8000 \
  -e maxDepth 128 \
  -e maxNodes 5000 \
  com.callstack.agentdevice.snapshothelper/.SnapshotInstrumentation

maxDepth also caps recursive traversal depth inside the helper. The -t install flag is required because the helper is a test-only instrumentation APK. Devices or providers that block test-package installs must allow this package before helper capture can run.

waitForIdleTimeoutMs defaults to 500, which is a maximum wait, not a fixed sleep. Direct helper invocations can pass 0 when immediate capture during ongoing animation is preferred.

Output Contract

The APK emits instrumentation status records using agentDeviceProtocol=android-snapshot-helper-v1.

The XML node attributes intentionally mirror fields consumed by the host parser, including visible-to-user, drawing-order, bounds, text/description/id, interaction booleans, and window metadata on window roots. drawing-order lets the host suppress covered same-window surfaces that the helper traversal can receive even when they are not user-reachable. The helper emits drawing-order on Android API 24+ and omits it on API 23, where the platform API is unavailable.

Each XML chunk is sent with:

  • outputFormat=uiautomator-xml
  • chunkIndex
  • chunkCount
  • payloadBase64

The final instrumentation result includes:

  • ok=true
  • helperApiVersion=1
  • waitForIdleTimeoutMs
  • waitForIdleQuietMs
  • timeoutMs
  • maxDepth
  • maxNodes
  • rootPresent
  • captureMode (interactive-windows or active-window)
  • windowCount
  • nodeCount
  • truncated
  • elapsedMs

Failures return ok=false, errorType, and message in the final result.

The release manifest is a stable provider contract for the current helper protocol. Providers should resolve the APK from apkUrl, verify sha256, install using installArgs, and run instrumentationRunner. installArgs must start with install; extra arguments are limited to the allowlisted adb install flags -r, -t, -d, and -g, and the consumer appends the APK path.