Say who closed the picker, because it was us and the KDoc denied it

The commit below describes "a back press aimed at a picker that had closed five seconds
earlier". The timing is right and the agency is wrong, and the agency is the interesting
half. Re-read out of the same logcat:

  20:59:35.686  UiDevice: Retrieving node ... [RES='android:id/button1'].
  20:59:35.689  UiObject2: Clicking on (927, 2274).
  20:59:36.033  MainActivity RESUMED
  20:59:36.350  VRI[PickActivity]: visibilityChanged ... newVisibility=false

`aerr_wait` and `aerr_close` both missed on that iteration and `dismissASystemErrorDialog`
fell through to `android:id/button1` -- the framework's generic AlertDialog positive button,
which is on every AlertDialog on the device. It hit one inside DocumentsUI, and that is what
closed the picker. The picker did not close on its own; this class closed it.

So the destroyed Activity and #271 are one incident rather than two findings that happened
to share a trace, and the loop's shape is three iterations rather than two: iteration 2
closes the picker through `button1` and then presses back into an app that is already in
front, iteration 3 dismisses the launcher's ANR dialog and presses again, and that press
finishes MainActivity.

It also sharpens what the fix does. With the re-read, iteration 2 returns -- the app is
focused within a second of the `button1` click -- so neither of the two presses that
followed it happens at all. The previous message implied the fix caught only the last one.

`requireAReadableScreen` now drops a Boolean return value, which this codebase treats as a
smell. It is correct there -- the `device.wait` on the next line is the re-probe -- and the
call site says so rather than leaving a reader to work out whether it was an oversight.

No behaviour change beyond the comment: the fix itself is unchanged and the sweep is re-run
because `app/src` is touched.

Refs #102, #271

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-07 17:18:23 -05:00
co-authored by Claude Opus 5
parent 89832563e6
commit 842965a479
2 changed files with 35 additions and 14 deletions
@@ -978,6 +978,8 @@ class SafPickerRoundTripTest {
private fun requireAReadableScreen() {
val app = By.pkg(appPackage)
if (device.wait(Until.hasObject(app), READABLE_TIMEOUT_MS) == true) return
// The return value is deliberately dropped here: the wait on the next line IS the re-probe
// that dismissThePicker had to be given, so there is nothing for it to gate.
dismissASystemErrorDialog()
if (device.wait(Until.hasObject(app), READABLE_TIMEOUT_MS) == true) return
unlockTheDevice()
@@ -1171,21 +1173,31 @@ class SafPickerRoundTripTest {
* `34161043035` attempt 1, which is #269's own head:
*
* ```
* 20:59:36.033 MainActivity RESUMED <- the save picker has already returned
* 20:59:41.094 UiDevice: Retrieving node with selector: BySelector [RES='android:id/aerr_wait']
* 20:59:35.689 UiObject2: Clicking on (927, 2274) <- iteration 2's dismissal, on button1
* 20:59:36.033 MainActivity RESUMED <- so the picker is gone, by our hand
* 20:59:36.350 VRI[PickActivity]: visibilityChanged ... newVisibility=false
* 20:59:37.068 UiDevice: Pressing back button. <- iteration 2 presses anyway
* 20:59:41.094 UiDevice: Retrieving node ... [RES='android:id/aerr_wait']
* 20:59:41.169 Input channel object 'Application Not Responding:
* com.google.android.apps.nexuslauncher' was disposed
* 20:59:41.713 UiDevice: Pressing back button.
* 20:59:41.713 UiDevice: Pressing back button. <- iteration 3
* 20:59:41.754 TopTaskTracker: onTaskMovedToFront: ... NexusLauncherActivity
* 20:59:42.278 MainActivity DESTROYED
* ```
*
* The launcher's ANR dialog — #93's occluder, still ambient on these runners — was the only
* reason the focus read false. Removing it made the app focused, and the back press aimed at a
* picker that had closed five seconds earlier finished `MainActivity` instead. Every later
* `onActivity` in the test then threw
* Read the first two lines before the rest, because they are the part that is easy to get
* wrong: **the picker did not close on its own — this function closed it**, on iteration 2,
* when [dismissASystemErrorDialog] fell through to `android:id/button1` and clicked what was
* almost certainly DocumentsUI's own positive button (#271). From `20:59:36.033` onwards there
* was nothing left to back out of. Iteration 2 pressed back regardless, iteration 3 dismissed
* the launcher's ANR dialog — #93's occluder, still ambient on these runners, and the only
* remaining reason the focus read false — and pressed again, and that press finished
* `MainActivity`. Every later `onActivity` in the test then threw
* `NullPointerException: Cannot run onActivity since Activity has been destroyed already`.
*
* **With the re-read below, iteration 2 returns** — the app is focused within a second of the
* `button1` click — and iterations 2 and 3 never press at all.
*
* **So the reading is retaken after the dialog goes, and only then.** This removes a back
* press sent on a stale reading; it does not retry one, and it does not make the dismissal
* more tolerant. A picker that really is in front still leaves the app unfocused, so the press
+16 -7
View File
@@ -4,7 +4,8 @@
with a disposition for each. **1489 gating leg-attempts, 129 failures, 8.7%** — 2026-08-20 to
2026-09-07. This is the standing answer to "my docs-only PR turned an emulator leg red, what is
it?", and it is what #102 asked for before being closed as an umbrella.
**Last verified:** 2026-09-07, against `main` at `ef9d35e`.
**Last verified:** 2026-09-07, against `main` at `ef9d35e`. Mode 6's fix is in #272 and is the only
thing here not yet on `main`.
This document is about **the emulator failing underneath the suite**. It is not a defect record
(`docs/defect-audit.md`), not a coverage read (`docs/coverage-read-findings.md`), and not a
@@ -108,20 +109,28 @@ Traced on the API 35 gating leg of run `34161043035` **attempt 1**, whose head i
commit:
```
20:59:36.033 MainActivity RESUMED <- the save picker has already returned
20:59:37.068 UiDevice: Pressing back button.
20:59:35.689 UiObject2: Clicking on (927, 2274) <- iteration 2's dismissal, on button1
20:59:36.033 MainActivity RESUMED <- the picker is gone, by the test's own hand
20:59:36.350 VRI[PickActivity]: visibilityChanged ... newVisibility=false
20:59:37.068 UiDevice: Pressing back button. <- iteration 2 presses anyway
20:59:41.094 UiDevice: Retrieving node ... [RES='android:id/aerr_wait']
20:59:41.169 Input channel 'Application Not Responding: ...nexuslauncher' was disposed
20:59:41.713 UiDevice: Pressing back button.
20:59:41.713 UiDevice: Pressing back button. <- iteration 3
20:59:41.754 TopTaskTracker: onTaskMovedToFront: ... NexusLauncherActivity
20:59:42.278 MainActivity DESTROYED
```
`dismissThePicker` guarded its back presses on `Activity.hasWindowFocus`. A system app-error dialog
is a fullscreen `system_server` window, so **it makes that false too** — the guard could not tell
"the picker is still up" from "a dialog is on top of an app that is already in front". The loop
dismissed the launcher's ANR dialog (#93's occluder, still ambient) and then pressed back on the
reading it had taken before doing so, into an app with nothing left to go back to.
"the picker is still up" from "a dialog is on top of an app that is already in front".
**And the first two lines are the part to read carefully, because the obvious reading is wrong.**
The picker did not close on its own: `dismissASystemErrorDialog` closed it on iteration 2, by
falling through to `android:id/button1` and clicking DocumentsUI's own positive button (#271). From
`20:59:36.033` there was nothing to back out of — and the loop pressed back on iteration 2 anyway,
then dismissed the launcher's ANR dialog on iteration 3 and pressed again on the reading taken
before doing so. That press finished `MainActivity`. **So this mode and #271 are one incident**, and
the fix stops it at iteration 2, where the re-read now returns.
Fixed by re-reading the focus after a dialog is actually dismissed, and only then —
`SafPickerRoundTripTest.dismissThePicker` carries the trace. That **removes** a press sent on a