From 4b8239fe8982252575f0c5ddb4a22ff9cd7612ec Mon Sep 17 00:00:00 2001 From: Arnold Loubriat Date: Sun, 22 Feb 2026 14:34:05 +0100 Subject: [PATCH] fix: Populate range info on Android --- platforms/android/src/adapter.rs | 49 +++++++++++++++++++++++++++++--- platforms/android/src/event.rs | 46 ++++++++++++++++++++++++++++++ platforms/android/src/node.rs | 34 ++++++++++++++++++++++ platforms/android/src/util.rs | 3 ++ 4 files changed, 128 insertions(+), 4 deletions(-) diff --git a/platforms/android/src/adapter.rs b/platforms/android/src/adapter.rs index a39d56fa..0b5bcffe 100644 --- a/platforms/android/src/adapter.rs +++ b/platforms/android/src/adapter.rs @@ -52,14 +52,20 @@ fn enqueue_focus_event_if_applicable( struct AdapterChangeHandler<'a> { events: &'a mut Vec, node_id_map: &'a mut NodeIdMap, + accessibility_focus: Option, enqueued_window_content_changed: bool, } impl<'a> AdapterChangeHandler<'a> { - fn new(events: &'a mut Vec, node_id_map: &'a mut NodeIdMap) -> Self { + fn new( + events: &'a mut Vec, + node_id_map: &'a mut NodeIdMap, + accessibility_focus: Option, + ) -> Self { Self { events, node_id_map, + accessibility_focus, enqueued_window_content_changed: false, } } @@ -142,6 +148,28 @@ impl TreeChangeHandler for AdapterChangeHandler<'_> { y: scroll_y, }); } + if old_node.numeric_value() != new_node.numeric_value() && new_node.data().value().is_none() + { + if let (Some(current), Some(min), Some(max)) = ( + new_node.numeric_value(), + new_node.min_numeric_value(), + new_node.max_numeric_value(), + ) { + let id = self.node_id_map.get_or_create_java_id(new_node); + let event_type = if self.accessibility_focus == Some(id) { + EVENT_VIEW_SELECTED + } else { + EVENT_VIEW_SCROLLED + }; + self.events.push(QueuedEvent::RangeValueChanged { + virtual_view_id: id, + event_type, + current, + min, + max, + }); + } + } // TODO: other events } @@ -205,10 +233,11 @@ impl State { fn update_tree( events: &mut Vec, node_id_map: &mut NodeIdMap, + accessibility_focus: Option, tree: &mut Tree, update: TreeUpdate, ) { - let mut handler = AdapterChangeHandler::new(events, node_id_map); + let mut handler = AdapterChangeHandler::new(events, node_id_map, accessibility_focus); tree.update_and_process_changes(update, &mut handler); } @@ -261,7 +290,13 @@ impl Adapter { } State::Active(tree) => { let mut events = Vec::new(); - update_tree(&mut events, &mut self.node_id_map, tree, update_factory()); + update_tree( + &mut events, + &mut self.node_id_map, + self.accessibility_focus, + tree, + update_factory(), + ); Some(QueuedEvents(events)) } } @@ -518,7 +553,13 @@ impl Adapter { tree_id, focus: focus_id, }; - update_tree(events, &mut self.node_id_map, tree, update); + update_tree( + events, + &mut self.node_id_map, + self.accessibility_focus, + tree, + update, + ); let request = ActionRequest { action: Action::SetTextSelection, target_tree: tree_id, diff --git a/platforms/android/src/event.rs b/platforms/android/src/event.rs index 8353bb7a..a48af3b1 100644 --- a/platforms/android/src/event.rs +++ b/platforms/android/src/event.rs @@ -233,6 +233,28 @@ pub(crate) struct ScrollDimension { pub(crate) max: Option, } +fn send_range_value_changed( + env: &mut JNIEnv, + host: &JObject, + virtual_view_id: jint, + event_type: jint, + current: f64, + min: f64, + max: f64, +) { + let event = new_event(env, host, virtual_view_id, event_type); + let item_index = if max > min && current >= min && current <= max { + ((current - min) * 100.0 / (max - min)) as jint + } else { + 0 + }; + env.call_method(&event, "setItemCount", "(I)V", &[100i32.into()]) + .unwrap(); + env.call_method(&event, "setCurrentItemIndex", "(I)V", &[item_index.into()]) + .unwrap(); + send_completed_event(env, host, event); +} + fn send_scrolled( env: &mut JNIEnv, host: &JObject, @@ -297,6 +319,13 @@ pub(crate) enum QueuedEvent { x: Option, y: Option, }, + RangeValueChanged { + virtual_view_id: jint, + event_type: jint, + current: f64, + min: f64, + max: f64, + }, InvalidateHost, } @@ -365,6 +394,23 @@ impl QueuedEvents { } => { send_scrolled(env, host, virtual_view_id, x, y); } + QueuedEvent::RangeValueChanged { + virtual_view_id, + event_type, + current, + min, + max, + } => { + send_range_value_changed( + env, + host, + virtual_view_id, + event_type, + current, + min, + max, + ); + } QueuedEvent::InvalidateHost => { env.call_method(host, "invalidate", "()V", &[]).unwrap(); } diff --git a/platforms/android/src/node.rs b/platforms/android/src/node.rs index ccb1d98a..ed89e24c 100644 --- a/platforms/android/src/node.rs +++ b/platforms/android/src/node.rs @@ -389,6 +389,40 @@ impl NodeWrapper<'_> { add_action(env, node_info, ACTION_SCROLL_FORWARD); } + if self.0.data().value().is_none() { + if let (Some(current), Some(min), Some(max)) = ( + self.0.numeric_value(), + self.0.min_numeric_value(), + self.0.max_numeric_value(), + ) { + let range_info_class = env + .find_class("android/view/accessibility/AccessibilityNodeInfo$RangeInfo") + .unwrap(); + let range_info = env + .call_static_method( + &range_info_class, + "obtain", + "(IFFF)Landroid/view/accessibility/AccessibilityNodeInfo$RangeInfo;", + &[ + RANGE_TYPE_FLOAT.into(), + (min as f32).into(), + (max as f32).into(), + (current as f32).into(), + ], + ) + .unwrap() + .l() + .unwrap(); + env.call_method( + node_info, + "setRangeInfo", + "(Landroid/view/accessibility/AccessibilityNodeInfo$RangeInfo;)V", + &[(&range_info).into()], + ) + .unwrap(); + } + } + let live = match self.0.live() { Live::Off => LIVE_REGION_NONE, Live::Polite => LIVE_REGION_POLITE, diff --git a/platforms/android/src/util.rs b/platforms/android/src/util.rs index 0f9c708e..acb50ad6 100644 --- a/platforms/android/src/util.rs +++ b/platforms/android/src/util.rs @@ -27,6 +27,7 @@ pub(crate) const ACTION_ARGUMENT_SELECTION_END_INT: &str = "ACTION_ARGUMENT_SELE pub(crate) const CONTENT_CHANGE_TYPE_SUBTREE: jint = 1 << 0; pub(crate) const EVENT_VIEW_CLICKED: jint = 1; +pub(crate) const EVENT_VIEW_SELECTED: jint = 1 << 2; pub(crate) const EVENT_VIEW_FOCUSED: jint = 1 << 3; pub(crate) const EVENT_VIEW_TEXT_CHANGED: jint = 1 << 4; pub(crate) const EVENT_VIEW_HOVER_ENTER: jint = 1 << 7; @@ -56,6 +57,8 @@ pub(crate) const MOVEMENT_GRANULARITY_WORD: jint = 1 << 1; pub(crate) const MOVEMENT_GRANULARITY_LINE: jint = 1 << 2; pub(crate) const MOVEMENT_GRANULARITY_PARAGRAPH: jint = 1 << 3; +pub(crate) const RANGE_TYPE_FLOAT: jint = 1; + #[derive(Debug, Default)] pub(crate) struct NodeIdMap { java_to_accesskit: HashMap,