Skip to content

Dismiss system ANR dialogs during the emulator screenshot capture - #137

Merged
dzid26 merged 1 commit into
mainfrom
work/capture-anr-dialog
Oct 7, 2026
Merged

dzid26 merged 1 commit into
mainfrom
work/capture-anr-dialog

Conversation

@dzid26

@dzid26 dzid26 commented Oct 7, 2026

Copy link
Copy Markdown
Collaborator

Summary

On a fraction of screenshot captures the first emulator attempt is dead on arrival: a system "Pixel Launcher isn't responding" dialog takes input focus, uiautomator dump returns only that dialog, and every wait in capture-screenshots.sh times out behind it. The attempt spends about 8.5 minutes waiting, fails with all six screenshots missing, and only the retry (a fresh emulator) succeeds. This PR makes the script clear the dialog instead of waiting behind it. Only .github/scripts/capture-screenshots.sh changes (+36/-2).

Related to #125: a stop-gap while the emulator pipeline exists; it does not change that proposal.

Root cause and evidence

Every case has the same signature: ... pull did not lead to 'AA:BB:CC:DD:EE:01' (attempt 3), ! scan results never appeared, state at scan-missing: ... text="Pixel Launcher isn't responding" ... text="Close app" text="Wait", ! simulated car not listed, ! dark cars list not reachable, six missing required screenshot, and a first attempt of about 10 minutes. The same state at ... line repeats at car-not-listed, dark-cars and final, so uiautomator never sees anything but the dialog.

Hit rates (first attempt, verified from the step timings; the dialog itself confirmed in the logs where noted):

Why it is slow: the waits count loop iterations, not seconds. Each iteration is a uiautomator dump + pull + sleep 1, about 3 s on these emulators, so a "20 s" wait takes about 62 s, and the failing path chains several of them.

What raises the dialog is not proven. In all 9 failing attempts the runner's own post-boot input keyevent 82 took 7-10.5 s (6.6 s or less in 9 of 10 clean attempts), which suggests Pixel Launcher is still starting and the unlock key times out, so the dialog is probably already up when this script starts. The failed attempt's logcat is not available: the retry step runs rm -rf screenshots, which deletes screenshots/debug/.

Change

  1. Prevention (CI only). Once the framework is ready: adb shell settings put global hide_error_dialogs 1, plus a one-line read-back in the log (hide_error_dialogs=1). Gated on CI because the setting persists on the AVD, so local runs (tools/demo-emulator.sh --capture) leave it alone.
  2. Fallback. ui_dump taps Wait when the dump contains an "isn't responding" dialog, using the button's bounds from the dump, logs one line naming the app (... dismissing "Pixel Launcher isn't responding" dialog), and returns failure so the caller re-dumps. It stops after five presses, so a dialog that Wait cannot clear ends in the usual wait failures instead of a flood of log lines.

What I checked in AOSP for hide_error_dialogs (sources from the aosp-mirror/platform_frameworks_base mirror):

  • core/java/android/provider/Settings.java: Settings.Global.HIDE_ERROR_DIALOGS = "hide_error_dialogs", documented as "If nonzero, all system error dialogs will be hidden. For example, the crash and ANR dialogs will not be shown, and the system will just proceed as if they had been accepted by the user." (@hide, @TestApi.)
  • ActivityTaskManagerService: a SettingObserver watches that URI and calls updateShouldShowDialogsLocked(...) on change, so a runtime settings put takes effect without a reboot; canShowErrorDialogs() returns mShowDialogs && ....
  • AppErrors.handleShowAnrUi: the dialog is shown only if (canShowErrorDialogs() || showBackground); otherwise "Just kill the app if there is no dialog to be shown" (doKill = true).
  • All three are present in android-14.0.0_r1 (API 34), android14-qpr2-release, android14-qpr3-release, android15-release, android16-release and main. The workflows pin api-level: 34, google_apis, x86_64 (job log: system-images;android-34;google_apis;x86_64).

Hiding the dialog has one cost: the system kills the unresponsive process instead of pausing it. That applies to the app under test too; it has never ANR'd in any log I read, and the fallback alone would have pressed Wait. If you prefer the fallback only, drop the if [ -n "${CI:-}" ] block. In the logged failures the dialog was already up at the first dump, so the fallback does the work and prevention only covers dialogs raised later.

Expected saving

A hit costs about 10 minutes for the dead first attempt plus about 4 for the retry (about 14 in total) against about 4.5 for a clean capture, so about 9-10 minutes per hit on main previews and PR runs. At the rates above that is roughly 1 main preview in 3 and 1 PR run in 7. It also removes the case where both attempts hit the dialog and the run fails (the Demo smoke failure).

Not changed (possible follow-ups)

  • Wait timeouts keep their iteration semantics (about 3 s per iteration). Tighter waits could make slow emulators flaky, so they are left for a separate change.
  • The first attempt's logcat and window dumps are still deleted by rm -rf screenshots in the four retry steps (android.yml x2, demo-smoke.yml, pr-screenshots.yml). Moving them aside needs edits in three workflows, so it is out of scope here; it is also why the trigger above is unproven.

Type

  • Bug fix
  • Feature
  • Protocol / crypto change
  • Docs / CI / tooling
  • Refactor (no behavior change)

Testing

Verified locally:

  • bash -n and shellcheck 0.10.0 (-S style) are clean on the whole script (it was clean before).
  • A fake adb drives the whole script end to end (a small state machine for the cars list, car view and Settings; the dialog is modelled on the structure in the CI dumps: six empty containers, the title, "Close app", "Wait"). Sleeps are stubbed out because the waits count iterations.
Scenario Script Result
Dialog up before the first dump (the CI failure) original exit 1, 0 screenshots; ! scan results never appeared, state at scan-missing: ... "Pixel Launcher isn't responding" ... "Wait", ! simulated car not listed, ! dark cars list not reachable, 6x missing required screenshot (the CI signature reproduced)
same this PR exit 0, 6 screenshots, one ... dismissing "Pixel Launcher isn't responding" dialog, no screenshot shows the dialog
Dialog appears mid-run this PR exit 0, 6 screenshots, one dismissal
No dialog, CI set original and this PR exit 0, 6 screenshots, no dismissal; hide_error_dialogs=1 read back
CI unset (local), with and without dialog this PR hide_error_dialogs untouched; dialog still dismissed
Dialog that Wait cannot clear this PR gives up after 5 presses: exit 1, 23 stderr lines (157 dismissal lines before the cap)

Not verified:

  • Nothing ran on a real emulator: there is no Android SDK or emulator here (dl.google.com is blocked). The hide_error_dialogs effect is verified from AOSP source only, and the real dialog's Wait bounds and the tap on API 34 are untested.

  • This PR's own PR screenshots run will most likely not hit the dialog (5 of 36 PR runs did), so a green run proves little; the fixtures above are the proof. The capture log will show hide_error_dialogs=1, confirming the prevention step ran, and a ... dismissing ... dialog line if the dialog does appear.

  • ./gradlew :core:test (not applicable: CI script only)

  • ./gradlew :app:testDebugUnitTest (not applicable)

  • ./gradlew :app:lintDebug (not applicable)

  • Real car (not applicable)

Checklist

  • Protocol changes ship test vectors; expected.txt regenerated, not hand-edited (not applicable)
  • No VINs, keys, or decrypted payloads in logs (the new lines print the dialog title and a setting value)
  • Docs/master-plan updated where relevant (not applicable: no user-visible change, no plan item)
  • New source files carry SPDX-License-Identifier: AGPL-3.0-only (no new files)

🤖 Generated with Claude Code

https://claude.ai/code/session_0129RzhbEE2cboMcYyZMuxqX


Generated by Claude Code

On a fraction of captures the first emulator attempt shows a "Pixel
Launcher isn't responding" dialog. It takes input focus, so uiautomator
dumps only the dialog and every wait times out behind it for the rest of
the attempt: about 8.5 minutes of dead waiting before the retry boots a
fresh emulator and succeeds.

Two layers, both in the shared capture script:

- On CI, set Settings.Global hide_error_dialogs=1 once the framework is
  up, so an ANR makes the system kill the stuck process instead of
  asking. The key, its live observer and that behaviour are in AOSP 14
  through main.
- ui_dump taps Wait on any "isn't responding" dialog it sees, logs one
  line naming the app, and reports the dump as stale so the caller dumps
  again. It stops after five presses, so a dialog that Wait cannot clear
  ends in the usual wait failures instead of a flood of log lines.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0129RzhbEE2cboMcYyZMuxqX
@dzid26
dzid26 merged commit ea38fcb into main Oct 7, 2026
5 checks passed
@github-actions

github-actions Bot commented Oct 7, 2026

Copy link
Copy Markdown
Contributor

Screenshots

App screenshots: light and dark

Captured from the demo build at af5a180.

@dzid26
dzid26 deleted the work/capture-anr-dialog branch October 7, 2026 22:03
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