1use serde::{Deserialize, Serialize};
3
4use crate::error::{DroidrunError, Result};
5use crate::ui::coord;
6use crate::ui::geometry::{find_clear_point, Bounds};
7
8#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct Element {
11 pub index: usize,
12 #[serde(default)]
13 pub class_name: String,
14 #[serde(default)]
15 pub resource_id: String,
16 #[serde(default)]
17 pub text: String,
18 #[serde(default)]
19 pub bounds: String,
20 #[serde(default)]
21 pub checked_state: String,
22 #[serde(default)]
23 pub children: Vec<Element>,
24}
25
26#[derive(Debug, Clone, Serialize, Deserialize, Default)]
28pub struct PhoneState {
29 #[serde(default, rename = "currentApp")]
30 pub current_app: String,
31 #[serde(default, rename = "packageName")]
32 pub package_name: String,
33 #[serde(default, rename = "isEditable")]
34 pub is_editable: bool,
35 #[serde(default, rename = "focusedElement")]
36 pub focused_element: Option<serde_json::Value>,
37}
38
39#[derive(Debug, Clone, Copy)]
41pub struct ScreenDimensions {
42 pub width: i32,
43 pub height: i32,
44}
45
46#[derive(Debug, Clone)]
48pub struct UIState {
49 pub elements: Vec<Element>,
50 pub formatted_text: String,
51 pub focused_text: String,
52 pub phone_state: PhoneState,
53 pub screen: ScreenDimensions,
54 pub use_normalized: bool,
55}
56
57impl UIState {
58 pub fn new(
59 elements: Vec<Element>,
60 formatted_text: String,
61 focused_text: String,
62 phone_state: PhoneState,
63 screen: ScreenDimensions,
64 use_normalized: bool,
65 ) -> Self {
66 Self {
67 elements,
68 formatted_text,
69 focused_text,
70 phone_state,
71 screen,
72 use_normalized,
73 }
74 }
75
76 pub fn get_element(&self, index: usize) -> Option<&Element> {
78 find_by_index(&self.elements, index)
79 }
80
81 pub fn get_element_coords(&self, index: usize) -> Result<(i32, i32)> {
83 let el = self
84 .get_element(index)
85 .ok_or(DroidrunError::ElementNotFound(index))?;
86
87 if el.bounds.is_empty() {
88 return Err(DroidrunError::ElementNoBounds(index));
89 }
90
91 let bounds = Bounds::from_str(&el.bounds)
92 .ok_or_else(|| DroidrunError::InvalidBounds(el.bounds.clone()))?;
93
94 Ok(bounds.center())
95 }
96
97 pub fn get_element_info(&self, index: usize) -> Option<ElementInfo> {
99 let el = self.get_element(index)?;
100 Some(ElementInfo {
101 text: el.text.clone(),
102 class_name: el.class_name.clone(),
103 bounds: el.bounds.clone(),
104 })
105 }
106
107 pub fn get_clear_point(&self, index: usize) -> Result<(i32, i32)> {
109 let el = self
110 .get_element(index)
111 .ok_or(DroidrunError::ElementNotFound(index))?;
112
113 if el.bounds.is_empty() {
114 return Err(DroidrunError::ElementNoBounds(index));
115 }
116
117 let target = Bounds::from_str(&el.bounds)
118 .ok_or_else(|| DroidrunError::InvalidBounds(el.bounds.clone()))?;
119
120 let all_elements = collect_all(&self.elements);
121 let blockers: Vec<Bounds> = all_elements
122 .iter()
123 .filter(|e| e.index > index && !e.bounds.is_empty())
124 .filter_map(|e| Bounds::from_str(&e.bounds))
125 .filter(|b| target.overlaps(b))
126 .collect();
127
128 find_clear_point(&target, &blockers).ok_or(DroidrunError::ElementObscured(index))
129 }
130
131 pub fn convert_point(&self, x: i32, y: i32) -> Result<(i32, i32)> {
133 if self.use_normalized {
134 coord::to_absolute(x, y, self.screen.width, self.screen.height)
135 } else {
136 Ok((x, y))
137 }
138 }
139
140 pub fn all_indices(&self) -> Vec<usize> {
142 collect_indices(&self.elements)
143 }
144}
145
146#[derive(Debug, Clone)]
148pub struct ElementInfo {
149 pub text: String,
150 pub class_name: String,
151 pub bounds: String,
152}
153
154fn find_by_index(elements: &[Element], target: usize) -> Option<&Element> {
157 for el in elements {
158 if el.index == target {
159 return Some(el);
160 }
161 if let Some(found) = find_by_index(&el.children, target) {
162 return Some(found);
163 }
164 }
165 None
166}
167
168fn collect_indices(elements: &[Element]) -> Vec<usize> {
169 let mut indices = Vec::new();
170 for el in elements {
171 indices.push(el.index);
172 indices.extend(collect_indices(&el.children));
173 }
174 indices.sort();
175 indices
176}
177
178fn collect_all(elements: &[Element]) -> Vec<&Element> {
179 let mut result = Vec::new();
180 for el in elements {
181 result.push(el);
182 result.extend(collect_all(&el.children));
183 }
184 result
185}
186
187#[cfg(test)]
188mod tests {
189 use super::*;
190
191 fn sample_elements() -> Vec<Element> {
192 vec![
193 Element {
194 index: 1,
195 class_name: "Button".into(),
196 resource_id: "btn_ok".into(),
197 text: "OK".into(),
198 bounds: "100,200,300,400".into(),
199 checked_state: String::new(),
200 children: vec![],
201 },
202 Element {
203 index: 2,
204 class_name: "TextView".into(),
205 resource_id: "".into(),
206 text: "Hello".into(),
207 bounds: "0,0,1080,100".into(),
208 checked_state: String::new(),
209 children: vec![Element {
210 index: 3,
211 class_name: "ImageView".into(),
212 resource_id: "icon".into(),
213 text: "".into(),
214 bounds: "10,10,50,50".into(),
215 checked_state: String::new(),
216 children: vec![],
217 }],
218 },
219 ]
220 }
221
222 fn sample_state() -> UIState {
223 UIState::new(
224 sample_elements(),
225 "formatted".into(),
226 "focused".into(),
227 PhoneState::default(),
228 ScreenDimensions {
229 width: 1080,
230 height: 2400,
231 },
232 false,
233 )
234 }
235
236 #[test]
237 fn test_get_element() {
238 let state = sample_state();
239 assert!(state.get_element(1).is_some());
240 assert_eq!(state.get_element(1).unwrap().text, "OK");
241 }
242
243 #[test]
244 fn test_get_element_nested() {
245 let state = sample_state();
246 let el = state.get_element(3).unwrap();
247 assert_eq!(el.class_name, "ImageView");
248 }
249
250 #[test]
251 fn test_get_element_not_found() {
252 let state = sample_state();
253 assert!(state.get_element(999).is_none());
254 }
255
256 #[test]
257 fn test_get_element_coords() {
258 let state = sample_state();
259 let (x, y) = state.get_element_coords(1).unwrap();
260 assert_eq!((x, y), (200, 300)); }
262
263 #[test]
264 fn test_all_indices() {
265 let state = sample_state();
266 assert_eq!(state.all_indices(), vec![1, 2, 3]);
267 }
268
269 #[test]
270 fn test_convert_point_absolute() {
271 let state = sample_state();
272 let (x, y) = state.convert_point(540, 1200).unwrap();
273 assert_eq!((x, y), (540, 1200));
274 }
275
276 #[test]
277 fn test_convert_point_normalized() {
278 let mut state = sample_state();
279 state.use_normalized = true;
280 let (x, y) = state.convert_point(500, 500).unwrap();
281 assert_eq!((x, y), (540, 1200));
282 }
283}