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