1use std::ffi::CString;
6use std::io;
7use std::time::Duration;
8
9const ANDROID_PROP_VALUE_MAX: usize = 92;
10const ANDROID_PROP_SERIAL_ERROR: u32 = u32::MAX;
11
12#[repr(C)]
13pub struct AndroidPropertyInfoOpaque {
14 _private: [u8; 0],
15}
16
17#[derive(Clone, Copy, Debug, Eq, PartialEq)]
18pub struct AndroidPropertyInfo {
19 raw: *const AndroidPropertyInfoOpaque,
20}
21
22impl AndroidPropertyInfo {
23 pub fn as_ptr(self) -> *const AndroidPropertyInfoOpaque {
24 self.raw
25 }
26}
27
28#[derive(Clone, Debug, Eq, PartialEq)]
29pub struct AndroidPropertyValue {
30 pub name: String,
31 pub value: String,
32 pub serial: u32,
33}
34
35pub trait AndroidPropertyStore {
36 fn get(&self, key: &str) -> Option<String>;
37 fn set(&self, key: &str, value: &str) -> io::Result<()>;
38}
39
40#[derive(Clone, Copy, Debug, Default)]
41pub struct SystemAndroidPropertyStore;
42
43pub fn android_property_get(key: &str) -> Option<String> {
44 SystemAndroidPropertyStore.get(key)
45}
46
47pub fn android_property_set(key: &str, value: &str) -> io::Result<()> {
53 SystemAndroidPropertyStore.set(key, value)
54}
55
56pub fn android_property_find(key: &str) -> Option<AndroidPropertyInfo> {
57 system_android_property_find(key)
58}
59
60pub fn android_property_read(property: AndroidPropertyInfo) -> io::Result<AndroidPropertyValue> {
65 system_android_property_read(property)
66}
67
68pub fn android_property_serial(property: AndroidPropertyInfo) -> io::Result<u32> {
73 system_android_property_serial(property)
74}
75
76pub fn android_property_wait(
81 property: AndroidPropertyInfo,
82 old_serial: u32,
83 timeout: Option<Duration>,
84) -> io::Result<Option<u32>> {
85 system_android_property_wait(property, old_serial, timeout)
86}
87
88impl AndroidPropertyStore for SystemAndroidPropertyStore {
89 fn get(&self, key: &str) -> Option<String> {
90 system_android_property_get(key)
91 }
92
93 fn set(&self, key: &str, value: &str) -> io::Result<()> {
94 validate_property_c_string(key)?;
95 validate_property_c_string(value)?;
96 system_android_property_set(key, value)
97 }
98}
99
100fn validate_property_c_string(value: &str) -> io::Result<CString> {
101 CString::new(value).map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "embedded NUL"))
102}
103
104#[cfg(not(target_os = "android"))]
105fn android_properties_unsupported() -> io::Error {
106 io::Error::new(
107 io::ErrorKind::Unsupported,
108 "android system properties unavailable on this platform",
109 )
110}
111
112#[cfg(target_os = "android")]
113fn property_info_invalid() -> io::Error {
114 io::Error::new(io::ErrorKind::InvalidInput, "invalid android property info")
115}
116
117#[cfg(target_os = "android")]
118fn serial_result(serial: u32) -> io::Result<u32> {
119 if serial == ANDROID_PROP_SERIAL_ERROR {
120 Err(io::Error::last_os_error())
121 } else {
122 Ok(serial)
123 }
124}
125
126fn duration_to_timespec(duration: Duration) -> io::Result<libc::timespec> {
127 let tv_sec = duration
128 .as_secs()
129 .try_into()
130 .map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "timeout too large"))?;
131 let tv_nsec = duration.subsec_nanos() as _;
132 Ok(libc::timespec { tv_sec, tv_nsec })
133}
134
135#[cfg(target_os = "android")]
136fn system_android_property_get(key: &str) -> Option<String> {
137 use std::ffi::CStr;
138
139 let key = CString::new(key).ok()?;
140 let mut value = [0 as libc::c_char; ANDROID_PROP_VALUE_MAX + 1];
141 let len = unsafe { __system_property_get(key.as_ptr(), value.as_mut_ptr()) };
142 if len <= 0 {
143 return None;
144 }
145 Some(
146 unsafe { CStr::from_ptr(value.as_ptr()) }
147 .to_string_lossy()
148 .into_owned(),
149 )
150}
151
152#[cfg(target_os = "android")]
153fn system_android_property_find(key: &str) -> Option<AndroidPropertyInfo> {
154 let key = CString::new(key).ok()?;
155 let raw = unsafe { __system_property_find(key.as_ptr()) };
156 if raw.is_null() {
157 None
158 } else {
159 Some(AndroidPropertyInfo { raw })
160 }
161}
162
163#[cfg(target_os = "android")]
164fn system_android_property_read(property: AndroidPropertyInfo) -> io::Result<AndroidPropertyValue> {
165 if property.raw.is_null() {
166 return Err(property_info_invalid());
167 }
168 let mut value = AndroidPropertyReadCookie::default();
169 unsafe {
170 __system_property_read_callback(
171 property.raw,
172 android_property_read_callback,
173 (&mut value as *mut AndroidPropertyReadCookie).cast(),
174 );
175 }
176 value
177 .value
178 .ok_or_else(|| io::Error::other("android property read callback did not run"))
179}
180
181#[cfg(target_os = "android")]
182fn system_android_property_serial(property: AndroidPropertyInfo) -> io::Result<u32> {
183 if property.raw.is_null() {
184 return Err(property_info_invalid());
185 }
186 serial_result(unsafe { __system_property_serial(property.raw) })
187}
188
189#[cfg(target_os = "android")]
190fn system_android_property_wait(
191 property: AndroidPropertyInfo,
192 old_serial: u32,
193 timeout: Option<Duration>,
194) -> io::Result<Option<u32>> {
195 if property.raw.is_null() {
196 return Err(property_info_invalid());
197 }
198 let timeout = timeout.map(duration_to_timespec).transpose()?;
199 let timeout_ptr = timeout
200 .as_ref()
201 .map_or(std::ptr::null(), |value| value as *const libc::timespec);
202 let mut new_serial = 0;
203 let changed =
204 unsafe { __system_property_wait(property.raw, old_serial, &mut new_serial, timeout_ptr) };
205 if changed {
206 Ok(Some(new_serial))
207 } else {
208 Ok(None)
209 }
210}
211
212#[cfg(target_os = "android")]
213fn system_android_property_set(key: &str, value: &str) -> io::Result<()> {
214 let key = validate_property_c_string(key)?;
215 let value = validate_property_c_string(value)?;
216 let status = unsafe { __system_property_set(key.as_ptr(), value.as_ptr()) };
217 if status == 0 {
218 Ok(())
219 } else {
220 Err(io::Error::from_raw_os_error(status))
221 }
222}
223
224#[cfg(not(target_os = "android"))]
225fn system_android_property_get(_key: &str) -> Option<String> {
226 let _ = ANDROID_PROP_VALUE_MAX;
227 None
228}
229
230#[cfg(not(target_os = "android"))]
231fn system_android_property_find(_key: &str) -> Option<AndroidPropertyInfo> {
232 None
233}
234
235#[cfg(not(target_os = "android"))]
236fn system_android_property_read(
237 _property: AndroidPropertyInfo,
238) -> io::Result<AndroidPropertyValue> {
239 Err(android_properties_unsupported())
240}
241
242#[cfg(not(target_os = "android"))]
243fn system_android_property_serial(_property: AndroidPropertyInfo) -> io::Result<u32> {
244 let _ = ANDROID_PROP_SERIAL_ERROR;
245 Err(android_properties_unsupported())
246}
247
248#[cfg(not(target_os = "android"))]
249fn system_android_property_wait(
250 _property: AndroidPropertyInfo,
251 _old_serial: u32,
252 timeout: Option<Duration>,
253) -> io::Result<Option<u32>> {
254 if let Some(timeout) = timeout {
255 let _ = duration_to_timespec(timeout)?;
256 }
257 Err(android_properties_unsupported())
258}
259
260#[cfg(not(target_os = "android"))]
261fn system_android_property_set(_key: &str, _value: &str) -> io::Result<()> {
262 Err(android_properties_unsupported())
263}
264
265#[cfg(target_os = "android")]
266unsafe extern "C" {
267 fn __system_property_get(name: *const libc::c_char, value: *mut libc::c_char) -> libc::c_int;
268 fn __system_property_set(name: *const libc::c_char, value: *const libc::c_char) -> libc::c_int;
269 fn __system_property_find(name: *const libc::c_char) -> *const AndroidPropertyInfoOpaque;
270 fn __system_property_read_callback(
271 pi: *const AndroidPropertyInfoOpaque,
272 callback: unsafe extern "C" fn(
273 *mut libc::c_void,
274 *const libc::c_char,
275 *const libc::c_char,
276 u32,
277 ),
278 cookie: *mut libc::c_void,
279 );
280 fn __system_property_serial(pi: *const AndroidPropertyInfoOpaque) -> u32;
281 fn __system_property_wait(
282 pi: *const AndroidPropertyInfoOpaque,
283 old_serial: u32,
284 new_serial_ptr: *mut u32,
285 relative_timeout: *const libc::timespec,
286 ) -> bool;
287}
288
289#[cfg(target_os = "android")]
290#[derive(Default)]
291struct AndroidPropertyReadCookie {
292 value: Option<AndroidPropertyValue>,
293}
294
295#[cfg(target_os = "android")]
296unsafe extern "C" fn android_property_read_callback(
297 cookie: *mut libc::c_void,
298 name: *const libc::c_char,
299 value: *const libc::c_char,
300 serial: u32,
301) {
302 use std::ffi::CStr;
303
304 let cookie = unsafe { &mut *(cookie.cast::<AndroidPropertyReadCookie>()) };
305 let name = unsafe { CStr::from_ptr(name) }
306 .to_string_lossy()
307 .into_owned();
308 let value = unsafe { CStr::from_ptr(value) }
309 .to_string_lossy()
310 .into_owned();
311 cookie.value = Some(AndroidPropertyValue {
312 name,
313 value,
314 serial,
315 });
316}
317
318#[cfg(test)]
319mod tests {
320 use super::*;
321 use std::cell::RefCell;
322 use std::collections::BTreeMap;
323
324 #[derive(Default)]
325 struct FakeStore {
326 entries: RefCell<BTreeMap<String, String>>,
327 }
328
329 impl AndroidPropertyStore for FakeStore {
330 fn get(&self, key: &str) -> Option<String> {
331 self.entries.borrow().get(key).cloned()
332 }
333
334 fn set(&self, key: &str, value: &str) -> io::Result<()> {
335 self.entries
336 .borrow_mut()
337 .insert(key.to_string(), value.to_string());
338 Ok(())
339 }
340 }
341
342 #[test]
343 fn fake_store_round_trips_properties() {
344 let store = FakeStore::default();
345 assert_eq!(store.get("debug.hwui.renderer"), None);
346 store.set("debug.hwui.renderer", "skiagl").unwrap();
347 assert_eq!(store.get("debug.hwui.renderer").as_deref(), Some("skiagl"));
348 }
349
350 #[test]
351 fn system_store_rejects_embedded_nul() {
352 let err = SystemAndroidPropertyStore
353 .set("debug.hwui.renderer", "skia\0gl")
354 .unwrap_err();
355 assert_eq!(err.kind(), io::ErrorKind::InvalidInput);
356 }
357
358 #[test]
359 fn duration_to_timespec_rejects_large_timeout() {
360 let err = duration_to_timespec(Duration::from_secs(u64::MAX)).unwrap_err();
361 assert_eq!(err.kind(), io::ErrorKind::InvalidInput);
362 }
363
364 #[test]
365 #[cfg(target_os = "android")]
366 fn serial_error_maps_to_io_error() {
367 assert!(serial_result(1).is_ok());
368 assert!(serial_result(ANDROID_PROP_SERIAL_ERROR).is_err());
369 }
370}