RUST.md: the Masonry IME gap is a TODO, and the accesskit abort is reproducible

Two things E1 left open, both settled on the emulator.

The missing autocorrect is Masonry's as_input_connection returning None,
not android-view and not EditorInfo: android-view's own demo implements
the trait over a parley editor and Gboard suggests from that buffer.

The abort seen once is a client *detaching*: accesskit_android's adapter
never returns to Inactive, so the first tree change after a ui-trace run
sends an accessibility event with accessibility off, which throws.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
irisandClaude Opus 5 committed 2026-09-04 19:44:05 -04:00
1 parent 43743ba171
commit c70a670356
1 file changed
+92 -3
+92 -3
View File
@@ -542,14 +542,103 @@ accepted.
composition, autocorrect and suggestions are exactly what the
composer in this app needs and exactly what `winit` cannot do at all.
*One crash seen once and not reproduced.* With an accessibility
client attached the app aborted, stack:
**Cause found 2026-09-04, and it is Masonry's, not android-view's.**
`~/src/android-view/masonry/src/lib.rs:531` is
fn as_input_connection(&mut self) -> Option<&mut dyn InputConnection> {
// TODO
None
}
so the Masonry demo has **no `InputConnection` at all**; `RustView`
returns null from `onCreateInputConnection` and the IME falls back to
dispatching raw key events, which is exactly the behaviour observed —
keys arrive, composition does not exist, so there is nothing for
Gboard to suggest against. It is not a wrong `EditorInfo`, and the
guess above that it was is withdrawn.
android-view's **own** demo (`demo/src/lib.rs`, packaged by `app/`)
implements the whole trait against a parley editor and asks for
`INPUT_TYPE_CLASS_TEXT | CAP_SENTENCES | AUTO_CORRECT | MULTI_LINE`
with `IME_FLAG_NO_FULLSCREEN | NO_EXTRACT_UI | NO_ENTER_ACTION`
(`demo/src/lib.rs:588`). So the capability is present in the layer
iris would sit on, and the 30-odd method `InputConnection` trait in
`src/ime.rs``set_composing_text`, `set_composing_region`,
`finish_composing_text`, `text_before_cursor`, `cursor_caps_mode`,
`request_cursor_updates`, and `InputMethodManager::update_selection`
to push the selection back — is the full surface an IME needs.
**This changes what I2 costs**: the IME bridge is a trait to
implement over iris's parley editor, not a gap to fund upstream. It
also means E2 inherits Masonry's TODO, so a Masonry transcript will
have the same dead composer until somebody fills that in.
**Measured on the emulator, same session, same device.** Built
android-view's own demo — `cargo ndk -t x86_64 -P 26 -o
app/src/main/jniLibs/ build -p android-view-demo --release`, then
`./gradlew :app:assembleDebug`, installed with `ANDROID_SERIAL=$(emu
serial)` — and tapped into its editor. `dumpsys input_method` reports
`mInputShown=true` with `mServedView=…viewdemo.DemoView`, and the
screenshot shows **Gboard's suggestion strip populated with "dolor |
Dolores | door"**: the caret had landed inside the word *dolor* in
the demo's lorem ipsum, and Gboard read that word out of the Rust
editor through `text_before_cursor`. So on this emulator, through
android-view, a parley editor gets a real IME with real suggestions
drawn from its own buffer. That is the bar E1 could not reach and the
bar I2 is written against, and it is now known to be reachable.
*One crash seen once — reproduced and diagnosed 2026-09-04.* With an
accessibility client attached the app aborted, stack:
`android_view::view::do_frame``CallbackCtx::finish`
`accesskit_android::event::QueuedEvents::raise`
`send_completed_event``unwrap()` on `Err(JavaException)`.
android-view builds `panic = "abort"`, so a JNI call that throws
takes the process. Two later `ui-trace record` runs left the app
alive, so the trigger is narrower than "a client is attached".
alive, so the trigger looked narrower than "a client is attached".
**It is the opposite of "a client is attached": it is a client
having *detached*.** `accesskit_android`'s `State` enum
(`adapter.rs:161` in 0.4.0, `:192` in 0.8.0) is
`Inactive | Placeholder | Active`, and **nothing ever moves it back
to `Inactive`**. A client — `ui-trace`, which is uiautomator — calls
into the node provider once, `get_or_init_tree` promotes the adapter
to `Active`, and it stays there for the life of the process. Every
later change then returns `Some(QueuedEvents)`, `raise` calls
`ViewParent.requestSendAccessibilityEvent`, and that reaches
`AccessibilityManager.sendAccessibilityEvent`, which on the main
looper **throws `IllegalStateException("Accessibility off. Did you
forget to check that?")` when accessibility is disabled**. jni-rs
returns `Err(JavaException)`, `send_completed_event` unwraps it, and
`panic = "abort"` ends the process.
*The controlled run*, one process (pid 4085), `settings get secure
accessibility_enabled` = 0 throughout:
- tapped the editor and typed three keys with `adb shell input tap`,
no client ever attached — **alive**;
- one `ui-trace record -d 800` with no gesture at all, then two
seconds' wait — **still alive** (the queue was raised while the
client was still there);
- the very next three keystrokes, same process — **aborted**, same
stack.
So the failure is not the recording; it is the **first thing that
changes the accessibility tree after a recording ends**. That makes
it a standing hazard for this project rather than an oddity:
`transcript-bench.sh`, `stream-bench.sh` and `bench-lib.sh`'s
tap-by-name all attach and detach uiautomator, so on a Rust app the
typing or scrolling *after* a bench run is what dies, several
seconds away from anything that looks like a cause.
**Still present at head**: 0.8.0 is the newest `accesskit_android`
(the demo resolves 0.4.0) and both the unconditional `unwrap` in
`send_completed_event` and the one-way `State` are unchanged there,
so upgrading is not the fix. **Our mitigation for I2/I4 is a gate we
own**: ask `AccessibilityManager.isEnabled()` before calling
`raise`, and drop the events when it says no. Worth reporting
upstream as well — the honest fix is for `raise` to clear a pending
exception rather than unwrap it, since a view can be detached or
accessibility switched off between queueing and raising no matter
who is calling.
*A rig trap that cost a wrong conclusion.* Several bounded runs were
given `sleep N; emu down` watchdogs, and one armed for an earlier