Skip to main content

android_ble/
device.rs

1use std::sync::{Arc, OnceLock};
2
3use futures_core::Stream;
4use futures_lite::StreamExt;
5use java_spaghetti::Global;
6use log::info;
7use uuid::Uuid;
8
9use super::bindings::android::bluetooth::BluetoothDevice;
10use super::error::ErrorKind;
11use super::event_receiver::GlobalEvent;
12use super::gatt_tree::{CachedWeak, GattConnection, GattTree};
13use super::jni::Monitor;
14use super::service::Service;
15use super::util::{BoolExt, OptionExt};
16use super::vm_context::{android_api_level, jni_with_env};
17use super::{DeviceId, Result};
18
19/// A Bluetooth LE device.
20#[derive(Clone)]
21pub struct Device {
22    pub(super) id: DeviceId,
23    pub(super) device: Global<BluetoothDevice>,
24    pub(super) connection: CachedWeak<GattConnection>,
25    pub(super) once_connected: Arc<OnceLock<()>>,
26}
27
28impl PartialEq for Device {
29    fn eq(&self, other: &Self) -> bool {
30        self.id() == other.id()
31    }
32}
33
34impl Eq for Device {}
35
36impl std::hash::Hash for Device {
37    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
38        self.id().hash(state);
39    }
40}
41
42impl std::fmt::Debug for Device {
43    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
44        let mut f = f.debug_struct("Device");
45        f.field("name", &self.name().unwrap_or("(Unknown name)".into()));
46        f.field("id", &self.id());
47        f.finish()
48    }
49}
50
51impl std::fmt::Display for Device {
52    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
53        f.write_str(self.name().as_deref().unwrap_or("(Unknown name)"))
54    }
55}
56
57impl Device {
58    /// Returns this device’s unique identifier.
59    pub fn id(&self) -> DeviceId {
60        self.id.clone()
61    }
62
63    /// The local name for this device.
64    pub fn name(&self) -> Result<String> {
65        jni_with_env(|env| {
66            self.device
67                .as_ref(env)
68                .getName()
69                .map_err(|e| e.into())
70                .and_then(|s| s.non_null())
71                .map(|s| s.to_string_lossy())
72        })
73    }
74
75    /// This method is kept for compatibility with `bluest`.
76    pub async fn name_async(&self) -> Result<String> {
77        self.name()
78    }
79
80    /// The connection status for this device.
81    ///
82    /// NOTE: currently this just checks if it is registered in this library instance.
83    pub async fn is_connected(&self) -> bool {
84        self.get_connection().is_ok()
85    }
86
87    /// The pairing status for this device.
88    pub async fn is_paired(&self) -> Result<bool> {
89        jni_with_env(|env| {
90            self.device
91                .as_ref(env)
92                .getBondState()
93                .map_err(|e| e.into())
94                .map(|i| i == BluetoothDevice::BOND_BONDED)
95        })
96    }
97
98    /// Attempt to pair this device using the system default pairing UI.
99    pub async fn pair(&self) -> Result<()> {
100        let conn = self.get_connection()?;
101        let mut receiver = self
102            .get_connection()?
103            .global_event_receiver
104            .subscribe()
105            .await?;
106
107        let bond_state = jni_with_env(|env| {
108            let device = self.device.as_ref(env);
109            device.getBondState().map_err(crate::Error::from)
110        })?;
111        match bond_state {
112            BluetoothDevice::BOND_BONDED => return Ok(()),
113            BluetoothDevice::BOND_BONDING => (),
114            _ => {
115                jni_with_env(|env| {
116                    let device = self.device.as_ref(env);
117                    let gatt = conn.gatt.as_ref(env);
118                    let _lock = Monitor::new(&gatt);
119                    device.createBond()?.non_false()?;
120                    Ok::<_, crate::Error>(())
121                })?;
122            }
123        }
124        drop(conn);
125
126        // Inspired by <https://github.com/NordicSemiconductor/Android-BLE-Library>, BleManagerHandler.java
127        while let Some(event) = receiver.next().await {
128            match event {
129                GlobalEvent::BondStateChanged(dev_id, prev_st, st) if dev_id == self.id => match st
130                {
131                    BluetoothDevice::BOND_BONDED => return Ok(()),
132                    BluetoothDevice::BOND_NONE => {
133                        if prev_st == BluetoothDevice::BOND_BONDING {
134                            return Err(crate::Error::new(
135                                ErrorKind::NotAuthorized,
136                                None,
137                                "pairing process failed",
138                            ));
139                        } else if prev_st == BluetoothDevice::BOND_BONDED {
140                            info!("deregistered connection with {dev_id} in Device::pair");
141                            GattTree::deregister_connection(&dev_id);
142                            return Err(ErrorKind::NotConnected.into());
143                        }
144                    }
145                    _ => (),
146                },
147                _ => (),
148            }
149        }
150        Err(ErrorKind::NotConnected.into())
151    }
152
153    /// Discover the primary services of this device.
154    pub async fn discover_services(&self) -> Result<Vec<Service>> {
155        let conn = self.get_connection()?;
156        let disc_lock = conn.discover_services.lock().await;
157        jni_with_env(|env| {
158            let gatt = conn.gatt.as_ref(env);
159            let gatt = Monitor::new(&gatt);
160            gatt.discoverServices()?.non_false()?;
161            Ok::<_, crate::Error>(())
162        })?;
163        drop(conn);
164        disc_lock.wait_unlock().await.ok_or_check_conn(&self.id)??;
165        self.collect_discovered_services()
166    }
167
168    /// Discover the primary service(s) of this device with the given [Uuid].
169    pub async fn discover_services_with_uuid(&self, uuid: Uuid) -> Result<Vec<Service>> {
170        Ok(self
171            .discover_services()
172            .await?
173            .into_iter()
174            .filter(|serv| serv.uuid() == uuid)
175            .collect())
176    }
177
178    /// Get previously discovered services.
179    ///
180    /// If no services have been discovered yet, this method will perform service discovery.
181    pub async fn services(&self) -> Result<Vec<Service>> {
182        let conn = self.get_connection()?;
183        if conn.discover_services.last_value().is_some() {
184            self.collect_discovered_services()
185        } else {
186            self.discover_services().await
187        }
188    }
189
190    fn collect_discovered_services(&self) -> Result<Vec<Service>> {
191        Ok(self
192            .get_connection()?
193            .services
194            .lock()
195            .unwrap()
196            .keys()
197            .map(|&service_id| Service::new(self.id.clone(), service_id))
198            .collect())
199    }
200
201    /// **(Experimental)** Monitors the device for service changed indications.
202    ///
203    /// This requires Android API level 31 or higher.
204    pub async fn service_changed_indications(
205        &self,
206    ) -> Result<impl Stream<Item = Result<ServicesChanged>> + Send + Unpin + '_> {
207        if android_api_level() < 31 {
208            return Err(crate::Error::new(
209                ErrorKind::NotSupported,
210                None,
211                "this requires BluetoothGattCallback.onServiceChanged() introduced in API level 31",
212            ));
213        }
214        let receiver = self
215            .get_connection()?
216            .services_changes
217            .subscribe(|| Ok::<_, crate::Error>(()), || ())
218            .await?;
219        Ok(receiver.map(|_| {
220            Ok(ServicesChanged {
221                dev_id: self.id.clone(),
222            })
223        }))
224    }
225
226    /// Get the current signal strength from the device in dBm.
227    pub async fn rssi(&self) -> Result<i16> {
228        let conn = self.get_connection()?;
229        let read_rssi_lock = conn.read_rssi.lock().await;
230        jni_with_env(|env| {
231            let gatt = conn.gatt.as_ref(env);
232            let gatt = Monitor::new(&gatt);
233            gatt.readRemoteRssi()?.non_false()?;
234            Ok::<_, crate::Error>(())
235        })?;
236        drop(conn);
237        read_rssi_lock
238            .wait_unlock()
239            .await
240            .ok_or_check_conn(&self.id)?
241    }
242
243    /// Open an L2CAP connection-oriented channel (CoC) to this device.
244    ///
245    /// This requires Android API level 29 or higher.
246    pub async fn open_l2cap_channel(
247        &self,
248        psm: u16,
249        secure: bool,
250    ) -> Result<super::l2cap_channel::L2capChannel> {
251        use log::warn;
252        if self.get_connection().is_ok() {
253            warn!("trying to open L2CAP channel while there is a GATT connection.");
254        }
255        let (reader, writer) =
256            super::l2cap_channel::open_l2cap_channel(self.device.clone(), psm, secure)?;
257        Ok(super::l2cap_channel::L2capChannel { reader, writer })
258    }
259
260    pub(crate) fn get_connection(&self) -> Result<Arc<GattConnection>, crate::Error> {
261        self.connection
262            .get_or_find(|| GattTree::check_connection(&self.id))
263    }
264}
265
266/// A services changed notification.
267#[derive(Debug, Clone, PartialEq, Eq, Hash)]
268pub struct ServicesChanged {
269    dev_id: DeviceId, // XXX: this is not enough for a unique hash value
270}
271
272impl ServicesChanged {
273    /// Check if `service` is currently invalidated.
274    pub fn was_invalidated(&self, service: &Service) -> bool {
275        GattTree::find_service(&self.dev_id, service.uuid()).is_none()
276    }
277}