Skip to content

Android: no announcement path for live regions #766

Description

@estekovics

The problem: the Android adapter puts a node's live region on the node info, but nothing in the event path consults live(). It is read in exactly one place:

let live = match self.0.live() {
Live::Off => LIVE_REGION_NONE,
Live::Polite => LIVE_REGION_POLITE,
Live::Assertive => LIVE_REGION_ASSERTIVE,
};
env.call_method(node_info, "setLiveRegion", "(I)V", &[live.into()])
.unwrap();

What gets emitted instead. node_added enqueues only a host-level content-changed event; the gap is already marked (// TODO: live regions?):

impl TreeChangeHandler for AdapterChangeHandler<'_> {
fn node_added(&mut self, _node: &NodeRef) {
self.enqueue_window_content_changed_if_needed();
// TODO: live regions?
}

That event is sourced at HOST_VIEW_ID (-1), never at the changed node, and send_window_content_changed stamps it CONTENT_CHANGE_TYPE_SUBTREE, so setLiveRegion never rides on an event a screen reader could key off:

fn enqueue_window_content_changed(events: &mut Vec<QueuedEvent>) {
events.push(QueuedEvent::WindowContentChanged {
virtual_view_id: HOST_VIEW_ID,
});
}

The one per-node event is the wrong one. TYPE_VIEW_TEXT_CHANGED is pushed from a single site, in node_updated, gated on text():

let old_text = old_wrapper.text();
let new_text = new_wrapper.text();
if old_text != new_text {
let id = self.node_id_map.get_or_create_java_id(new_node);
self.events.push(QueuedEvent::TextChanged {
virtual_view_id: id,
old: old_text.unwrap_or_else(String::new),
new: new_text.clone().unwrap_or_else(String::new),
});
}

and text() is value() or the text-range document, so labels never reach it:

pub(crate) fn text(&self) -> Option<String> {
self.0.value().or_else(|| {
self.0
.supports_text_ranges()
.then(|| self.0.document_range().text())
})
}

It is also an editing event (before-text, added/removed counts), not an announcement, and node_added cannot reach it. TYPE_ANNOUNCEMENT appears nowhere under adapters/android/.

Expected: a Live::Polite/Live::Assertive node whose label or value changes, or that is added already carrying it, produces something TalkBack speaks.

Actual: only the host-sourced TYPE_WINDOW_CONTENT_CHANGED/SUBTREE; neither a label nor a value change produces any node-sourced event. Worse than iOS, which does announce value changes (adapters/ios/src/event.rs gates announcements on value()#765 is the label gap there).

Triggering sequence: the announcement node in winit's simple.rsRole::Label + set_value + set_live(Live::Polite), pushed as a new child, so it lands in node_added.

Tested/untested: every Android claim is from the code at 2dfdd7b, not from a run — I have no Android device, and the repo ships no Android harness (no android_main, no gradle project or manifest). The iOS side of the comparison I have exercised on a physical iPhone. Filing as a report.

(Drafted with AI assistance; the code reading is mine.)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions