droidrun_core/ui/
provider.rs1use tracing::{debug, warn};
3
4use crate::driver::DeviceDriver;
5use crate::error::{DroidrunError, Result};
6use crate::ui::filter::TreeFilter;
7use crate::ui::formatter::TreeFormatter;
8use crate::ui::state::{ScreenDimensions, UIState};
9
10pub struct AndroidStateProvider<F: TreeFilter, M: TreeFormatter> {
12 filter: F,
13 formatter: M,
14 use_normalized: bool,
15}
16
17impl<F: TreeFilter, M: TreeFormatter> AndroidStateProvider<F, M> {
18 pub fn new(filter: F, formatter: M, use_normalized: bool) -> Self {
19 Self {
20 filter,
21 formatter,
22 use_normalized,
23 }
24 }
25
26 pub async fn get_state(&self, driver: &dyn DeviceDriver) -> Result<UIState> {
30 let max_retries = 3;
31 let mut last_error = None;
32
33 for attempt in 1..=max_retries {
34 debug!("getting state (attempt {attempt}/{max_retries})");
35
36 match self.get_state_inner(driver).await {
37 Ok(state) => return Ok(state),
38 Err(e) => {
39 warn!("get_state attempt {attempt} failed: {e}");
40 last_error = Some(e);
41 if attempt < max_retries {
42 tokio::time::sleep(std::time::Duration::from_millis(500)).await;
43 }
44 }
45 }
46 }
47
48 Err(last_error.unwrap_or_else(|| {
49 DroidrunError::PortalCommError("get_state failed after retries".into())
50 }))
51 }
52
53 async fn get_state_inner(&self, driver: &dyn DeviceDriver) -> Result<UIState> {
54 let combined = driver.get_ui_tree().await?;
55
56 if combined.get("error").is_some() {
58 let msg = combined
59 .get("message")
60 .and_then(|v| v.as_str())
61 .unwrap_or("Unknown error");
62 return Err(DroidrunError::PortalCommError(format!(
63 "Portal returned error: {msg}"
64 )));
65 }
66
67 for key in &["a11y_tree", "phone_state", "device_context"] {
69 if combined.get(*key).is_none() {
70 return Err(DroidrunError::Parse(format!("Missing data in state: {key}")));
71 }
72 }
73
74 let device_context = &combined["device_context"];
75 let screen_bounds = device_context
76 .get("screen_bounds")
77 .cloned()
78 .unwrap_or_default();
79 let screen_width = screen_bounds
80 .get("width")
81 .and_then(|v| v.as_i64())
82 .unwrap_or(1080) as i32;
83 let screen_height = screen_bounds
84 .get("height")
85 .and_then(|v| v.as_i64())
86 .unwrap_or(2400) as i32;
87
88 let filtered = self
90 .filter
91 .filter(&combined["a11y_tree"], device_context);
92
93 let (formatted_text, focused_text, elements, phone_state) = self.formatter.format(
95 filtered.as_ref(),
96 &combined["phone_state"],
97 screen_width,
98 screen_height,
99 self.use_normalized,
100 );
101
102 Ok(UIState::new(
103 elements,
104 formatted_text,
105 focused_text,
106 phone_state,
107 ScreenDimensions {
108 width: screen_width,
109 height: screen_height,
110 },
111 self.use_normalized,
112 ))
113 }
114}