appscale_core/
platform_android.rs1use crate::platform::*;
15use crate::tree::NodeId;
16use std::sync::Mutex;
17use std::collections::HashMap;
18
19pub struct AndroidPlatform {
24 next_handle: Mutex<u64>,
25 views: Mutex<HashMap<u64, ViewRecord>>,
26 screen: ScreenSize,
27 scale: f32,
28}
29
30struct ViewRecord {
31 view_type: ViewType,
32 node_id: NodeId,
33 children: Vec<NativeHandle>,
34}
35
36impl AndroidPlatform {
37 pub fn new() -> Self {
38 Self {
39 next_handle: Mutex::new(1),
40 views: Mutex::new(HashMap::new()),
41 screen: ScreenSize { width: 412.0, height: 732.0 },
43 scale: 2.625, }
45 }
46
47 pub fn with_screen(mut self, width: f32, height: f32, scale: f32) -> Self {
48 self.screen = ScreenSize { width, height };
49 self.scale = scale;
50 self
51 }
52
53 pub fn view_count(&self) -> usize {
54 self.views.lock().unwrap().len()
55 }
56}
57
58impl Default for AndroidPlatform {
59 fn default() -> Self { Self::new() }
60}
61
62impl PlatformBridge for AndroidPlatform {
63 fn platform_id(&self) -> PlatformId {
64 PlatformId::Android
65 }
66
67 fn create_view(&self, view_type: ViewType, node_id: NodeId) -> NativeHandle {
68 let mut h = self.next_handle.lock().unwrap();
69 let handle = NativeHandle(*h);
70 *h += 1;
71
72 self.views.lock().unwrap().insert(handle.0, ViewRecord {
73 view_type,
74 node_id,
75 children: Vec::new(),
76 });
77
78 handle
79 }
80
81 fn update_view(&self, handle: NativeHandle, _props: &PropsDiff) -> Result<(), PlatformError> {
82 let views = self.views.lock().unwrap();
83 if !views.contains_key(&handle.0) {
84 return Err(PlatformError::ViewNotFound(handle));
85 }
86 Ok(())
88 }
89
90 fn remove_view(&self, handle: NativeHandle) {
91 self.views.lock().unwrap().remove(&handle.0);
92 }
93
94 fn insert_child(&self, parent: NativeHandle, child: NativeHandle, index: usize) {
95 let mut views = self.views.lock().unwrap();
96 if let Some(parent_record) = views.get_mut(&parent.0) {
97 let idx = index.min(parent_record.children.len());
98 parent_record.children.insert(idx, child);
99 }
100 }
101
102 fn remove_child(&self, parent: NativeHandle, child: NativeHandle) {
103 let mut views = self.views.lock().unwrap();
104 if let Some(parent_record) = views.get_mut(&parent.0) {
105 parent_record.children.retain(|c| *c != child);
106 }
107 }
108
109 fn measure_text(&self, text: &str, style: &TextStyle, max_width: f32) -> TextMetrics {
110 let font_size = style.font_size.unwrap_or(14.0); let char_width = font_size * 0.54; let line_height = style.line_height.unwrap_or(font_size * 1.25);
115 let total_width = char_width * text.len() as f32;
116
117 let lines = if max_width > 0.0 && total_width > max_width {
118 (total_width / max_width).ceil() as u32
119 } else {
120 1
121 };
122
123 TextMetrics {
124 width: if lines > 1 { max_width } else { total_width },
125 height: line_height * lines as f32,
126 baseline: font_size * 0.78,
127 line_count: lines,
128 }
129 }
130
131 fn screen_size(&self) -> ScreenSize {
132 self.screen
133 }
134
135 fn scale_factor(&self) -> f32 {
136 self.scale
137 }
138
139 fn supports(&self, capability: PlatformCapability) -> bool {
140 matches!(capability,
141 PlatformCapability::Haptics
142 | PlatformCapability::Biometrics
143 | PlatformCapability::PushNotifications
144 | PlatformCapability::BackgroundFetch
145 | PlatformCapability::NativeFilePicker
146 | PlatformCapability::NativeDatePicker
147 | PlatformCapability::ContextMenu
148 | PlatformCapability::NativeShare
149 )
150 }
151}
152
153#[cfg(test)]
154mod tests {
155 use super::*;
156
157 #[test]
158 fn test_android_create_and_remove() {
159 let platform = AndroidPlatform::new();
160 assert_eq!(platform.platform_id(), PlatformId::Android);
161
162 let h1 = platform.create_view(ViewType::Container, NodeId(1));
163 let h2 = platform.create_view(ViewType::Button, NodeId(2));
164 assert_eq!(platform.view_count(), 2);
165
166 platform.insert_child(h1, h2, 0);
167 platform.remove_child(h1, h2);
168 platform.remove_view(h2);
169 assert_eq!(platform.view_count(), 1);
170 }
171
172 #[test]
173 fn test_android_capabilities() {
174 let platform = AndroidPlatform::new();
175 assert!(platform.supports(PlatformCapability::Haptics));
176 assert!(platform.supports(PlatformCapability::Biometrics));
177 assert!(platform.supports(PlatformCapability::PushNotifications));
178 assert!(platform.supports(PlatformCapability::BackgroundFetch));
179 assert!(platform.supports(PlatformCapability::NativeShare));
180 assert!(!platform.supports(PlatformCapability::MenuBar));
181 assert!(!platform.supports(PlatformCapability::SystemTray));
182 assert!(!platform.supports(PlatformCapability::MultiWindow));
183 }
184
185 #[test]
186 fn test_android_screen_info() {
187 let platform = AndroidPlatform::new();
188 let screen = platform.screen_size();
189 assert_eq!(screen.width, 412.0);
190 assert_eq!(screen.height, 732.0);
191 assert_eq!(platform.scale_factor(), 2.625);
192 }
193
194 #[test]
195 fn test_android_custom_screen() {
196 let platform = AndroidPlatform::new().with_screen(900.0, 1422.0, 1.5);
198 let screen = platform.screen_size();
199 assert_eq!(screen.width, 900.0);
200 assert_eq!(screen.height, 1422.0);
201 assert_eq!(platform.scale_factor(), 1.5);
202 }
203
204 #[test]
205 fn test_android_text_measurement() {
206 let platform = AndroidPlatform::new();
207 let style = TextStyle { font_size: Some(16.0), ..TextStyle::default() };
208 let metrics = platform.measure_text("Hello Android", &style, 200.0);
209 assert!(metrics.width > 0.0);
210 assert!(metrics.height > 0.0);
211 assert_eq!(metrics.line_count, 1);
212 }
213
214 #[test]
215 fn test_android_text_wrapping() {
216 let platform = AndroidPlatform::new();
217 let style = TextStyle { font_size: Some(16.0), ..TextStyle::default() };
218 let metrics = platform.measure_text("This is a longer text that should wrap", &style, 50.0);
219 assert!(metrics.line_count > 1);
220 assert_eq!(metrics.width, 50.0);
221 }
222
223 #[test]
224 fn test_android_update_missing_view() {
225 let platform = AndroidPlatform::new();
226 let result = platform.update_view(NativeHandle(999), &PropsDiff::new());
227 assert!(result.is_err());
228 }
229}