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:
1 parent
43743ba171
commit
c70a670356
1 file changed
+92
-3
@@ -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
|
||||
|
||||
Reference in new issue
Block a user