iris: android-app's Gradle shell, and the emulator run for I2
The Gradle side of RUST.md's I2: MainActivity, IrisView (extending
android-view's RustView with the two native methods it has no hook
for -- window insets, and unregistering this view's entry in
iris::android::insets's side table), and RustView.java/
RustInputConnection.java vendored from android-view (no published AAR
to depend on) with one deliberate diff noted in a comment: mViewPeer
is protected rather than package-private, so a subclass in a different
package can reach it.
Measured on the emulator (x86_64, API 26, SwiftShader Vulkan):
dumpsys input_method shows the served InputConnection is ours, and
Gboard's suggestion strip reads real buffer content back through
text_before_cursor ("hi | Hi | HI" after typing "hi") -- the same bar
E1 set, met. Not met: nothing draws. The clear colour reaches the
screen (confirmed by swapping it to magenta) and the layout engine
reports the correct widget count and pixel regions (log::debug! calls
left in view.rs's render() for exactly this), but no primitive shows
up, on both Vulkan/SwiftShader and GLES/virgl. Root cause not found;
one unconfirmed lead (a GLES-only D2/D2Array warning that could point
at the glyph atlas) is written up in RUST.md's I2 rather than chased
into core/src/render/, which is mid-flight in a separate benchmark
branch this session.
I2 is therefore built and wired but not tickable -- RUST.md has the
full writeup, what was ruled out, and where to pick this up.
Co-Authored-By: Claude Sonnet <noreply@anthropic.com>
This commit is contained in:
1 parent
f79bd7ca71
commit
6317685d1a
15 files changed
+838
-10
No files matched your search
@@ -82,8 +82,22 @@ session spending an afternoon on them again.
|
||||
yet, only the emulator; the Android Vulkan Profile 2025 sourcing in
|
||||
"iris's binding array does not survive real Android hardware" below is
|
||||
what stands in for that until I2 gets a device.
|
||||
- **Next**: **I2** — iris on android-view. **E2** (a transcript in
|
||||
Masonry) can go in parallel in another session.
|
||||
- **I2 — iris on android-view: built 2026-09-05, not tickable.** The
|
||||
android-view backend (`iris/src/android/`), the `iris-android-app`
|
||||
cdylib and Gradle shell, insets, the back gesture, and the full
|
||||
`InputConnection` bridge are all in and measured working — Gboard's
|
||||
suggestion strip reads real buffer content through it, the same bar E1
|
||||
set. What is not working: **nothing draws**. The screen shows only the
|
||||
clear colour on both Vulkan/SwiftShader and GLES/virgl, though the
|
||||
layout engine reports the correct widget count and pixel regions —
|
||||
see I2's own entry below for what was ruled out and the one open lead
|
||||
(a GLES-only `D2`/`D2Array` warning from the glyph atlas, unconfirmed
|
||||
as the cause, and deliberately not chased into `core/src/render/`
|
||||
while that tree is mid-flight in a separate benchmark branch this
|
||||
session). **Next**: root-cause the blank render — start with a single
|
||||
hardcoded rect and no text, to separate "nothing renders" from "the
|
||||
atlas path specifically is broken" — then revisit I2's tick. **E2** (a
|
||||
transcript in Masonry) can go in parallel in another session.
|
||||
- **`client-core` built (2026-09-04)**, item 1 of the recommendation:
|
||||
`event-model/` (the event types, now shared with `server/`) and
|
||||
`client-core/` (REST and SSE clients, transcript fold, cache, highlighter,
|
||||
@@ -991,14 +1005,151 @@ since I2's pass condition is the phone, not just the emulator, and this
|
||||
is exactly the kind of thing that passes on a desktop GPU and fails
|
||||
silently on real hardware.
|
||||
|
||||
- [ ] **I2 — iris on android-view.** An `android-view` surface as a second
|
||||
backend beside winit: `wgpu` on the view's surface (GLES here, see
|
||||
the Vulkan section; Vulkan on the phone), touch as pointer events,
|
||||
window insets and the keyboard inset as layout inputs, the back
|
||||
gesture as an event, the IME bridge feeding the editor from I1.
|
||||
Pass: the `tabs` example and a text field run on the emulator, and
|
||||
the phone's own keyboard types into the field with autocorrect and
|
||||
suggestions — the same bar as E1.
|
||||
- [ ] **I2 — iris on android-view. Built and wired 2026-09-05; the IME half
|
||||
passes, rendering does not yet.** Not tickable: the pass condition
|
||||
names the tabs example running, and today it runs invisibly.
|
||||
|
||||
**Layout.** `iris/src/android/` mirrors `default/`'s module split
|
||||
(`view.rs` is `app.rs`+`state.rs` combined, since android-view has one
|
||||
harness type where winit splits `ApplicationHandler` from per-window
|
||||
state; `render.rs`, `input.rs`, `attr.rs` correspond directly;
|
||||
`ime.rs` and `insets.rs` have no winit counterpart). What used to live
|
||||
only in `default/` and had no winit dependency — `WidgetState`,
|
||||
`CursorState`/the sense machinery, `Tasks`, `Selector`/`Selectable`'s
|
||||
focus handling — moved to crate-root modules (`state.rs`, `sense.rs`,
|
||||
`task.rs`, `attr.rs`) so both backends use one copy; `Tasks`' redraw
|
||||
nudge is now behind a `RequestRedraw` trait (`Window` for winit, a
|
||||
`JavaVM`+`GlobalRef` attach-and-call for android-view) rather than a
|
||||
concrete `winit::window::Window`. `winit`/`arboard` and
|
||||
`android-view`/`send_wrapper` are now `[target.'cfg(...)']`
|
||||
dependencies, and `default`/`android` are target-gated modules,
|
||||
because winit's own Android support needs `android-activity` with a
|
||||
backend feature selected — exactly what `iris-core` was kept free of.
|
||||
Confirmed by trying it before the split (`cargo ndk -t x86_64 -P 26
|
||||
build -p iris` failed inside `android-activity` itself) and after
|
||||
(clean). `iris/tabs-ui` is the tabs example's widget tree factored out
|
||||
of `examples/tabs/main.rs` into a crate generic over `Rsc: HasEvents`
|
||||
+ `Rsc::State: FocusHost`, so the winit example and
|
||||
`iris/android-app` (the new cdylib, excluded from the `iris` workspace
|
||||
because android-view needs the NDK sysroot to link — see that
|
||||
`Cargo.toml`'s comment) call the same `build()`.
|
||||
|
||||
android-view pinned to `bec6c62a96cef8239b0fd7fedeef9b184d02e3a1`, the
|
||||
commit E1 measured against. `RustView.java`/`RustInputConnection.java`
|
||||
are vendored (no published AAR to depend on) into
|
||||
`iris/android-app/app/src/.../org/linebender/android/rustview/`, with
|
||||
one deliberate diff from upstream noted in a comment: `mViewPeer` is
|
||||
`protected` rather than package-private, so `IrisView` (a different
|
||||
package) can pass it to the window-insets native call android-view
|
||||
has no hook for.
|
||||
|
||||
**Insets and the back gesture**, both without touching android-view.
|
||||
The back gesture takes no new plumbing at all: with no
|
||||
`OnBackPressedCallback` registered, Android still delivers it as an
|
||||
ordinary `KEYCODE_BACK` `KeyEvent` through the existing key path (the
|
||||
legacy behaviour every view-based app gets by default), handled in
|
||||
`view.rs`'s `on_key_down`. Insets have no such stand-in, so
|
||||
`android/insets.rs` registers one more native method
|
||||
(`applyWindowInsetsNative`) directly on `IrisView`, writing into an
|
||||
`Rc<RefCell<Shared>>` a second copy of which lives in
|
||||
`AndroidUiState` — the peer id android-view hands back from
|
||||
`register_view_peer` is opaque outside that crate, so this is a
|
||||
side table keyed on the same id rather than a way to reach the peer
|
||||
itself. `MainActivity` wires `setOnApplyWindowInsetsListener`,
|
||||
including the API 30+ `ime()` inset specifically (falls back to 0
|
||||
below that). Not yet consumed by any widget's layout — `insets()` is
|
||||
exposed on `AndroidUiState` but nothing reads it yet, since the tabs
|
||||
example has no chrome that needs to avoid the keyboard.
|
||||
|
||||
**The IME bridge is implemented and its pass condition holds.**
|
||||
`android/ime.rs` implements the full `InputConnection` trait
|
||||
(`text_before_cursor`/`after_cursor`/`selected_text`,
|
||||
`cursor_caps_mode`, `delete_surrounding_text[_in_code_points]`,
|
||||
`set_composing_text`/`_region`, `finish_composing_text`,
|
||||
`set_selection`, `begin`/`end_batch_edit`, `send_key_event`,
|
||||
`request_cursor_updates`) directly against `TextEdit` — the same
|
||||
preedit-replace bookkeeping `default`'s `Ime::Preedit` handling uses
|
||||
(`compose_len`, in chars), with new byte<->UTF-16 conversion helpers
|
||||
since parley (since I1) is byte-indexed and Java strings are not.
|
||||
Two approximations, both commented in place rather than silently
|
||||
dropped: `set_composing_region` declines (no separate composing range
|
||||
exists to move) and `set_selection`/`delete_surrounding_text_in_code_points`
|
||||
collapse to an approximation rather than a real span/code-point
|
||||
count. `TextEdit` gained `text()`/`selection_range()`/`caret()`
|
||||
getters and `TextEditCtx::delete_byte_range`/`set_cursor_byte`, all
|
||||
unconditional (no winit dependency added); `apply_event`/
|
||||
`TextInputResult`, which do take a `winit::event::KeyEvent`, are now
|
||||
`#[cfg(not(target_os = "android"))]` instead of being ported, since
|
||||
android's own `input.rs` calls `TextEdit`'s primitives
|
||||
(`backspace`/`delete`/`motion`/`insert`) directly from
|
||||
`ndk::event::Keycode` and never needed a winit `KeyEvent` shape.
|
||||
|
||||
**Measured on the emulator, 2026-09-05, x86_64 API 26,
|
||||
`-feature Vulkan` + SwiftShader per the Vulkan section below.**
|
||||
`adb shell dumpsys input_method` after tapping the composer field:
|
||||
`mInputShown=true`, `mServedInputConnection` is
|
||||
`org.linebender.android.rustview.RustInputConnection` attached to
|
||||
`IrisView`. `adb shell input text "hi"` followed by a screenshot
|
||||
shows **Gboard's suggestion strip populated with "hi | Hi | HI"** —
|
||||
capitalization variants read back out of the real buffer through
|
||||
`text_before_cursor`, the same kind of evidence E1 recorded (there:
|
||||
"dolor | Dolores | door"). That is the bar this box asks for, met.
|
||||
|
||||
**What is not met: nothing is visible.** The screen shows only the
|
||||
clear colour (confirmed black, then swapped to magenta and
|
||||
reconfirmed via screenshot — the presentation pipeline itself works)
|
||||
with zero widgets drawn on top, on **both** backends tried:
|
||||
`Backends::PRIMARY` (Vulkan via SwiftShader, `AdapterInfo` logged as
|
||||
`SwiftShader Device (Subzero)`) and `Backends::GL` (GLES via virgl on
|
||||
the real host GPU, logged as `Android Emulator OpenGL ES Translator
|
||||
(virgl (AMD Radeon RX 7900 XT ...`). Ruled out: the layout engine
|
||||
itself -- `log::debug!` calls left in `android/view.rs`'s `render()`
|
||||
show `active=39` widgets after `UiRenderState::update` and
|
||||
`window_region` reporting the root at the *correct* full-surface
|
||||
pixel rect (`(0, 0)..(1080, 2298)`, later `(1080, 1478)` once the
|
||||
keyboard's `adjustResize` shrank the window) -- so this is not a
|
||||
zero-size-widgets bug. One suspicious but unconfirmed lead: the GLES
|
||||
run logged `wgpu_hal::gles: wgpu-hal heuristics assumed that the view
|
||||
dimension will be equal to D2 rather than D2Array` right before the
|
||||
first `render()` call, which is exactly the shape of a bug in the
|
||||
glyph atlas's `texture_2d_array` (`core/src/render/texture.rs`,
|
||||
recently reworked per TEXTURES.md) if its array texture is ever
|
||||
created with a single layer -- wgpu-hal's GL backend is documented to
|
||||
guess the GL texture target from layer count at *texture* creation
|
||||
time, which can disagree with a view later requesting `D2Array`
|
||||
explicitly. Not chased further: this warning is GL-specific and the
|
||||
*same* blank result occurs under Vulkan too, where view dimension is
|
||||
always explicit and this class of ambiguity should not exist, so it
|
||||
may be a red herring rather than the cause. **`iris/core/src/render/`
|
||||
is mid-flight in a separate benchmark branch as of this session**, so
|
||||
deliberately not touched here beyond reading it — the next session
|
||||
should re-check this finding against whatever lands from that branch
|
||||
before spending more time on it, and reach first for the simplest
|
||||
possible reproduction (a single hardcoded coloured rect, no text, no
|
||||
atlas) to separate "nothing renders" from "the atlas path specifically
|
||||
is broken."
|
||||
|
||||
**Not built yet**: anything consuming `insets()`, a real phone
|
||||
measurement (only the emulator so far — matches every other Android
|
||||
finding in this file), and AccessKit (I4's job, so `ui-trace`
|
||||
couldn't be used here; a raw `adb shell input tap`/`input text` stood
|
||||
in for driving the UI, which is why this section says "the same bar
|
||||
as E1" rather than citing a `ui-trace` transcript).
|
||||
|
||||
**Verification.** Host: `cargo fmt --all -- --check`,
|
||||
`cargo build --workspace --all-targets`, `cargo clippy --all-targets`,
|
||||
`cargo test --workspace` (19 tests) all clean in `iris/`; `iris/run-headless.sh
|
||||
tabs --shot` still renders pixel-identically (27266 bytes, byte-for-byte
|
||||
unchanged). Android cross-compile: `cargo ndk -t x86_64 -P 26 build`
|
||||
and `... clippy` clean for both `iris` (with the android module) and
|
||||
`iris/android-app`. Emulator: `emu up` with
|
||||
`VK_DRIVER_FILES=.../vk_swiftshader_icd.json` and
|
||||
`GPU_HOST_FEATURES="-feature Vulkan -no-snapshot-load -no-snapshot-save"`
|
||||
per the Vulkan section; `cd android-app && cargo ndk -t x86_64 -P 26
|
||||
-o app/src/main/jniLibs/ build --release && gradle :app:assembleDebug`
|
||||
(release native lib per E1's segfault finding, debug Gradle variant --
|
||||
the jniLibs contents are what matters, not the Gradle build type);
|
||||
`adb install -r app/build/outputs/apk/debug/app-debug.apk`.
|
||||
- [ ] **I3 — a virtualised, bottom-anchored list.** Variable-height rows,
|
||||
keyed, composed only while visible, paged in both directions with a
|
||||
"more" sentinel at each end, a scroll anchor that survives rows
|
||||
|
||||
Reference in new issue
Block a user