Skip to content

feat(android): report the keyboard band on snapshot captures - #3240

Open
pvedula7 wants to merge 7 commits into
callstack:mainfrom
pvedula7:feat/android-keyboard-state
Open

pvedula7 wants to merge 7 commits into
callstack:mainfrom
pvedula7:feat/android-keyboard-state

Conversation

@pvedula7

@pvedula7 pvedula7 commented Oct 5, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Android snapshots now carry the same keyboard fact the Apple runner publishes (visible with a frame, absent, or unmeasurable). Before, only keyboard status could tell Gboard was up; the tree shows it as generic FrameLayout/TextView nodes.

The helper already writes window-type and window-bounds on each window root, so the band is the input method window's bounds (TYPE_INPUT_METHOD). No extra adb call. A capture that fell back to the active window, has a root without window metadata, was truncated, or has an IME window with unreadable bounds reports unmeasurable. The test IME draws no window, so it reads absent.

The helper now also reports missingRootWindowTypes, the window types it listed but could not serialize (null or failing root), published as androidSnapshot.missingRootWindowTypes. If it names an input method window, the band is unmeasurable (window-root-unavailable). An older helper omits it.

The tap guard already prefers a measured band, so Android taps now use the window bounds.

Downstream, tester-army/e2e reads the band to decide when to offer keyboard dismissal.

Validation

Commit 907ed8c. pnpm check:affected --run passes. pnpm test:replay:android: 6/6 on a Pixel 8 emulator (API 35).

Live with the rebuilt helper, Settings search, snapshot -i --json:

  • Gboard (open --no-test-ime), field focused: {"kind":"visible","frame":{"x":0,"y":1517,"width":1080,"height":883}}, matching the IME touchable region in dumpsys window.
  • After keyboard dismiss: {"kind":"absent"}.
  • Default test IME, field filled: {"kind":"absent"}.

All three report missingRootWindowTypes: []. Unit tests cover a null-root IME window, which can't be forced live.

Android captures now carry the same keyboard fact the Apple runner
publishes. The snapshot helper already lists every accessibility window
with its type and screen bounds, so the band is read from the input
method window in the captured tree with no extra adb call. A capture
that could not list every window reports unmeasurable instead of absent.

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 7 files

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread packages/platform-android/src/snapshot-keyboard.ts Outdated
Comment thread packages/platform-android/src/__tests__/snapshot-capture.test.ts
An input method window whose window-bounds did not parse, or parsed to
non-finite numbers, used to fall through to absent. The capture saw the
window but never measured it, so it cannot prove there is no keyboard.
An input method window with parsed empty bounds still reads as absent.
…xture

The keyboard capture fixture has two window roots and four nodes, so the
helper metadata served with it now says so. Other captures keep the
single-window defaults.
Reading the band from its own module added one module to what
platform-android/src/mechanics.ts evaluates on import (177 against 176
at the merge-base). It now lives in snapshot.ts, which that closure
already loads, so the import graph is the same as on main.

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 3 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread packages/platform-android/src/snapshot.ts
Comment thread packages/platform-android/src/snapshot.ts Outdated
…kipped an input method window

The helper drops a listed window whose root reads null or throws without a
record, and windowCount counts only the roots it serialized, so the host
could not tell an input method window it failed to read from one that was
not on screen, and reported the keyboard absent.

The helper now publishes the AccessibilityWindowInfo types of those windows
as missingRootWindowTypes (empty when every listed window was read), and the
host publishes it as androidSnapshot.missingRootWindowTypes. When it names an
input method window (type 2), the keyboard band is unmeasurable with reason
window-root-unavailable. An older helper omits the field and is trusted on
the roots it serialized.

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 15 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread website/docs/docs/snapshots.md Outdated
Comment thread packages/platform-android/src/instrumentation-helper.ts Outdated
… list

Number('') is 0 and Number('0x2') is 2, so a list like "1,,2" or
"0x2" passed as integers and could put a window type the helper never
sent into the keyboard decision. Each entry must now be plain decimal
digits, or the list is absent. The docs now describe the field as
windows the helper could not serialize, with a null or failing root as
examples, since a failure while reading the window's tree also lands
there.
@thymikee

thymikee commented Oct 6, 2026

Copy link
Copy Markdown
Member

The code in f028f3e looks right to me, but I'd like to see one more run before merge. The live emulator run was done at 907ed8c, and the only later change is the host-side parser, so please re-run it on the current head. Please also show that a tap behind the Gboard keyboard now refuses with tap_keyboard_occludes_target through the window-bounds fact, since today that is inferred from the band being the guard's input, not observed. Do the IME window bounds match the touchable keyboard region on non-Pixel keyboards, such as Samsung or a floating Gboard? A full-screen IME window would make the tap guard over-refuse.

Not blocking: the local unionRects in https://github.com/callstack/agent-device/blob/f028f3e/packages/platform-android/src/snapshot.ts#L929 does the same job as the one in capture-kit screenshot-overlay-rects.ts, and there is a third copy in the iOS snapshot transitions, so one could move into @agent-device/kernel/rect next to isPositiveFiniteRect. Also ANDROID_WINDOW_TYPE_INPUT_METHOD at https://github.com/callstack/agent-device/blob/f028f3e/packages/platform-android/src/snapshot.ts#L872 sits apart from ANDROID_WINDOW_TYPE_APPLICATION in snapshot-content-recovery.ts, so the window-type constants could share one module such as ui-hierarchy.ts. Take or leave both.

I looked for a smaller design and found none. The band has to come from the window list the helper already emits, and missingRootWindowTypes is the smallest protocol addition that tells a skipped IME window from an absent one. Is there a smaller way I missed?

The Smoke Tests job failed in "Preflight iOS runner through public CLI" with daemon_startup_failed after a 15000 ms startup timeout. This diff does not touch daemon startup or the iOS runner, so I think the failure is unrelated. Please re-run that job. No conflicts. After the re-run and the evidence above, nothing else blocks this PR.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants