Skip to main content

android_ble/
characteristic.rs

1use std::sync::Arc;
2
3use futures_core::Stream;
4use java_spaghetti::ByteArray;
5use uuid::Uuid;
6
7use super::bindings::android::bluetooth::BluetoothGattCharacteristic;
8use super::descriptor::Descriptor;
9use super::error::ErrorKind;
10use super::gatt_tree::{CachedWeak, CharacteristicInner, GattTree};
11use super::jni::{ByteArrayExt, Monitor};
12use super::util::{BoolExt, IntExt, OptionExt};
13use super::vm_context::{android_api_level, jni_with_env};
14use super::{CharacteristicProperties, DeviceId, Result};
15
16/// A Bluetooth GATT characteristic.
17#[derive(Debug, Clone)]
18pub struct Characteristic {
19    dev_id: DeviceId,
20    service_id: Uuid,
21    char_id: Uuid,
22    inner: CachedWeak<CharacteristicInner>,
23}
24
25impl PartialEq for Characteristic {
26    fn eq(&self, other: &Self) -> bool {
27        self.dev_id == other.dev_id
28            && self.service_id == other.service_id
29            && self.char_id == other.char_id
30    }
31}
32
33impl Eq for Characteristic {}
34
35impl std::hash::Hash for Characteristic {
36    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
37        self.dev_id.hash(state);
38        self.service_id.hash(state);
39        self.char_id.hash(state);
40    }
41}
42
43impl Characteristic {
44    pub(crate) fn new(dev_id: DeviceId, service_id: Uuid, char_id: Uuid) -> Self {
45        Self {
46            dev_id,
47            service_id,
48            char_id,
49            inner: CachedWeak::new(),
50        }
51    }
52
53    /// The [Uuid] identifying the type of this GATT characteristic.
54    pub fn uuid(&self) -> Uuid {
55        self.char_id
56    }
57
58    /// This method is kept for compatibility with `bluest`.
59    pub async fn uuid_async(&self) -> Result<Uuid> {
60        Ok(self.char_id)
61    }
62
63    /// The properties of this this GATT characteristic.
64    ///
65    /// Characteristic properties indicate which operations (e.g. read, write, notify, etc)
66    /// may be performed on this characteristic.
67    pub async fn properties(&self) -> Result<CharacteristicProperties> {
68        jni_with_env(|env| {
69            let val = self.get_inner()?.char.as_ref(env).getProperties()?;
70            Ok(CharacteristicProperties::from_bits(val.cast_unsigned()))
71        })
72    }
73
74    /// The cached value of this characteristic. Returns an error if the value has not yet been read.
75    pub async fn value(&self) -> Result<Vec<u8>> {
76        self.get_inner()?
77            .read
78            .last_value()
79            .ok_or(crate::Error::new(
80                ErrorKind::NotReady,
81                None,
82                "please call `Characteristic::read` at first",
83            ))?
84    }
85
86    // NOTE: the sequence of gaining read lock and write lock should be the same
87    // in `read` and `write` methods, otherwise deadlock may occur.
88    //
89    // To make `wait_unlock` exit on device disconnection, `drop((conn, inner))`
90    // cannot be removed here.
91
92    /// Read the value of this characteristic from the device.
93    pub async fn read(&self) -> Result<Vec<u8>> {
94        let conn = GattTree::check_connection(&self.dev_id)?;
95        let inner = self.get_inner()?;
96        let read_lock = inner.read.lock().await;
97        let _write_lock = inner.write.lock().await;
98        jni_with_env(|env| {
99            let gatt = &conn.gatt.as_ref(env);
100            let gatt = Monitor::new(gatt);
101            gatt.readCharacteristic(inner.char.as_ref(env))
102                .map_err(|e| e.into())
103                .and_then(|b| b.non_false())
104        })?;
105        drop((conn, inner));
106        read_lock
107            .wait_unlock()
108            .await
109            .ok_or_check_conn(&self.dev_id)?
110    }
111
112    /// Write `value` to this characteristic on the device and request the device to return a response
113    /// indicating a successful write.
114    pub async fn write(&self, value: &[u8]) -> Result<()> {
115        // NOTE: It is tested that `AttError::INVALID_ATTRIBUTE_VALUE_LENGTH` is returned if the data length
116        // is too long; a successful write means it is not truncated. Is this really guaranteed?
117        self.write_internal(value, true).await
118    }
119
120    /// Write `value` to this characteristic on the device without requesting a response.
121    pub async fn write_without_response(&self, value: &[u8]) -> Result<()> {
122        // NOTE: It is tested that writing *without response* may never cause an error from the Android API
123        // even if the write length is horrible.
124        //
125        // See <https://developer.android.com/reference/android/bluetooth/BluetoothGatt#requestMtu(int)>:
126        // When performing a write request operation (write without response), the data sent is truncated
127        // to the MTU size.
128        if value.len() <= self.max_write_len()? {
129            self.write_internal(value, false).await
130        } else {
131            Err(crate::Error::new(
132                ErrorKind::InvalidParameter,
133                None,
134                "write length probably exceeded the MTU's limitation",
135            ))
136        }
137    }
138
139    async fn write_internal(&self, value: &[u8], with_response: bool) -> Result<()> {
140        let conn = GattTree::check_connection(&self.dev_id)?;
141        let inner = self.get_inner()?;
142        let _read_lock = inner.read.lock().await;
143        let write_lock = inner.write.lock().await;
144        jni_with_env(|env| {
145            let gatt = conn.gatt.as_ref(env);
146            let gatt = Monitor::new(&gatt);
147            let char = inner.char.as_ref(env);
148            let array = ByteArray::from_slice(env, value);
149            let write_type = if with_response {
150                BluetoothGattCharacteristic::WRITE_TYPE_DEFAULT
151            } else {
152                BluetoothGattCharacteristic::WRITE_TYPE_NO_RESPONSE
153            };
154            char.setWriteType(write_type)?;
155            if android_api_level() >= 33 {
156                gatt.writeCharacteristic_BluetoothGattCharacteristic_byte_array_int(
157                    char, array, write_type,
158                )?
159                .check_status_code()
160            } else {
161                #[allow(deprecated)]
162                char.setValue_byte_array(array)?;
163                #[allow(deprecated)]
164                gatt.writeCharacteristic_BluetoothGattCharacteristic(char)
165                    .map_err(|e| e.into())
166                    .and_then(|b| b.non_false())
167            }
168        })?;
169        drop((conn, inner));
170        write_lock
171            .wait_unlock()
172            .await
173            .ok_or_check_conn(&self.dev_id)?
174    }
175
176    /// Get the maximum amount of data that can be written in a single packet for this characteristic.
177    ///
178    /// The Android API does not provide a method to query the current MTU value directly;
179    /// instead, `BluetoothGatt.requestMtu()` may be called in `Adapter::connect_device`
180    /// to have a possible maximum MTU in the callback. This can be configured with
181    /// [crate::AdapterConfig::request_mtu_on_connect].
182    pub fn max_write_len(&self) -> Result<usize> {
183        let conn = GattTree::check_connection(&self.dev_id)?;
184        let mtu = conn.mtu_changed_received.last_value().unwrap_or(23);
185        Ok(mtu - 5)
186    }
187
188    /// This method is kept for compatibility with `bluest`.
189    pub async fn max_write_len_async(&self) -> Result<usize> {
190        self.max_write_len()
191    }
192
193    /// Enables notification of value changes for this GATT characteristic.
194    ///
195    /// Returns a stream of values for the characteristic sent from the device.
196    pub async fn notify(&self) -> Result<impl Stream<Item = Result<Vec<u8>>> + Send + Unpin + '_> {
197        let conn = GattTree::check_connection(&self.dev_id)?;
198        let inner = self.get_inner()?;
199        let inner_2 = inner.clone();
200        let (gatt_for_stop, char_for_stop) = (conn.gatt.clone(), inner.char.clone());
201        inner
202            .notify
203            .subscribe(
204                move || {
205                    jni_with_env(|env| {
206                        let gatt = conn.gatt.as_ref(env);
207                        let gatt = Monitor::new(&gatt);
208                        let result =
209                            gatt.setCharacteristicNotification(inner_2.char.as_ref(env), true)?;
210                        result.non_false()
211                    })
212                },
213                move || {
214                    jni_with_env(|env| {
215                        let gatt = gatt_for_stop.as_ref(env);
216                        let gatt = Monitor::new(&gatt);
217                        let _ =
218                            gatt.setCharacteristicNotification(char_for_stop.as_ref(env), false);
219                    })
220                },
221            )
222            .await
223    }
224
225    /// Is the device currently sending notifications for this characteristic?
226    pub async fn is_notifying(&self) -> Result<bool> {
227        Ok(self.get_inner()?.notify.is_notifying())
228    }
229
230    /// This method is kept for compatibility with `bluest`.
231    pub async fn discover_descriptors(&self) -> Result<Vec<Descriptor>> {
232        self.descriptors().await
233    }
234
235    /// Get previously discovered descriptors.
236    pub async fn descriptors(&self) -> Result<Vec<Descriptor>> {
237        Ok(self
238            .get_inner()?
239            .descs
240            .keys()
241            .map(|id| Descriptor::new(self.dev_id.clone(), self.service_id, self.char_id, *id))
242            .collect())
243    }
244
245    fn get_inner(&self) -> Result<Arc<CharacteristicInner>, crate::Error> {
246        self.inner.get_or_find(|| {
247            GattTree::find_characteristic(&self.dev_id, self.service_id, self.char_id)
248                .ok_or_check_conn(&self.dev_id)
249        })
250    }
251}