1use std::fmt;
16
17#[cfg_attr(target_arch = "wasm32", path = "web/mod.rs")]
18#[cfg_attr(not(target_arch = "wasm32"), path = "native/mod.rs")]
19mod imp;
20
21#[cfg(target_os = "android")]
22pub use imp::android;
23
24#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
25#[non_exhaustive]
26pub enum DeviceType {
27 Desktop,
28 Laptop,
29 Phone,
30 Tablet,
31 Headset,
32 Television,
33 Watch,
34 Unknown,
35}
36
37impl fmt::Display for DeviceType {
38 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
39 match self {
40 DeviceType::Desktop => write!(f, "Desktop"),
41 DeviceType::Laptop => write!(f, "Laptop"),
42 DeviceType::Phone => write!(f, "Phone"),
43 DeviceType::Tablet => write!(f, "Tablet"),
44 DeviceType::Headset => write!(f, "Headset"),
45 DeviceType::Television => write!(f, "Television"),
46 DeviceType::Watch => write!(f, "Watch"),
47 DeviceType::Unknown => write!(f, "Unknown"),
48 }
49 }
50}
51
52#[derive(Debug, Clone)]
53pub struct DeviceInfo {
54 pub model: String,
55 pub name: String,
56 pub device_type: DeviceType,
57}
58
59#[derive(Debug, thiserror::Error)]
60#[non_exhaustive]
61pub enum DeviceInfoError {
62 #[error("platform not supported")]
63 Unsupported,
64 #[error("failed to query device info: {0}")]
65 Query(String),
66 #[cfg(target_os = "android")]
67 #[error("android JNI not initialized — call device_info::android::init() first")]
68 NotInitialized,
69 #[cfg(target_os = "android")]
70 #[error("JNI error: {0}")]
71 Jni(String),
72}
73
74pub fn device_info() -> Result<DeviceInfo, DeviceInfoError> {
78 imp::device_info()
79}
80
81const _: () = {
83 fn assert_send_sync<T: Send + Sync>() {}
84 fn assert_all() {
85 assert_send_sync::<DeviceInfo>();
86 assert_send_sync::<DeviceInfoError>();
87 }
88};
89
90#[cfg(test)]
91mod tests {
92 use super::*;
93
94 #[test]
95 fn test_device_info() {
96 let info = device_info().expect("device_info() should succeed");
97 assert!(!info.model.is_empty(), "model should not be empty");
98 assert!(!info.name.is_empty(), "name should not be empty");
99 println!("model: {}", info.model);
100 println!("name: {}", info.name);
101 println!("type: {}", info.device_type);
102 }
103
104 #[test]
105 fn test_device_info_from_multiple_threads() {
106 let handles: Vec<_> = (0..4)
107 .map(|_| std::thread::spawn(|| device_info().expect("device_info() should succeed")))
108 .collect();
109 for handle in handles {
110 let info = handle.join().expect("thread should not panic");
111 assert!(!info.model.is_empty());
112 }
113 }
114
115 #[test]
116 fn test_device_type_display() {
117 assert_eq!(DeviceType::Desktop.to_string(), "Desktop");
118 assert_eq!(DeviceType::Laptop.to_string(), "Laptop");
119 assert_eq!(DeviceType::Phone.to_string(), "Phone");
120 assert_eq!(DeviceType::Tablet.to_string(), "Tablet");
121 assert_eq!(DeviceType::Headset.to_string(), "Headset");
122 assert_eq!(DeviceType::Television.to_string(), "Television");
123 assert_eq!(DeviceType::Watch.to_string(), "Watch");
124 assert_eq!(DeviceType::Unknown.to_string(), "Unknown");
125 }
126}