Skip to main content

device_info/
lib.rs

1// Copyright 2025 LiveKit, Inc.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use 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
74/// Query device model, name, and type for the current platform.
75///
76/// This function is safe to call from any thread.
77pub fn device_info() -> Result<DeviceInfo, DeviceInfoError> {
78    imp::device_info()
79}
80
81// Compile-time assertions: DeviceInfo and DeviceInfoError must be Send + Sync.
82const _: () = {
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}