android_bluetooth_serial/
lib.rs

1//! Android API wrapper handling Bluetooth classic RFCOMM/SPP connection.
2//!
3//! This crate is looking for maintainers!
4//!
5//! TODO:
6//! - Add a function and an enum for checking the bluetooth state.
7//! - Add functions for permission request and enabling bluetooth.
8//!   <https://developer.android.com/develop/connectivity/bluetooth/setup>
9//! - Add functions for device discovery and pairing.
10//! - Provide an asynchronous interface.
11
12use std::io::Error;
13
14use jni::{
15    objects::JObject,
16    sys::{jint, jvalue},
17};
18use jni_min_helper::*;
19use std::{
20    collections::VecDeque,
21    io::{ErrorKind, Read, Write},
22    sync::{Arc, Mutex},
23    thread::JoinHandle,
24    time::{Duration, SystemTime},
25};
26
27const BLUETOOTH_SERVICE: &str = "bluetooth";
28
29/// The UUID for the well-known SPP profile.
30pub const SPP_UUID: &str = "00001101-0000-1000-8000-00805F9B34FB";
31
32/// Maps unexpected JNI errors to `std::io::Error`.
33/// (`From<jni::errors::Error>` cannot be implemented for `std::io::Error`
34/// here because of the orphan rule). Side effect: `jni_last_cleared_ex()`.
35#[inline(always)]
36pub(crate) fn jerr(err: jni::errors::Error) -> Error {
37    use jni::errors::Error::*;
38    if let JavaException = err {
39        let err = jni_clear_ex(err);
40        let Some(ex) = jni_last_cleared_ex() else {
41            return Error::other(JavaException);
42        };
43        jni_with_env(|env| Ok((ex.get_class_name(env)?, ex.get_throwable_msg(env)?)))
44            .map(|(cls, msg)| {
45                if cls.contains("SecurityException") {
46                    Error::new(ErrorKind::PermissionDenied, msg)
47                } else if cls.contains("IllegalArgumentException") {
48                    Error::new(ErrorKind::InvalidInput, msg)
49                } else {
50                    Error::other(format!("{cls}: {msg}"))
51                }
52            })
53            .unwrap_or(Error::other(err))
54    } else {
55        Error::other(err)
56    }
57}
58
59/// Returns the global reference of the thread safe `android.bluetooth.BluetoothAdapter`,
60/// created for once in this crate.
61#[inline(always)]
62pub(crate) fn bluetooth_adapter() -> Result<&'static JObject<'static>, Error> {
63    use std::sync::OnceLock;
64    static BT_ADAPTER: OnceLock<jni::objects::GlobalRef> = OnceLock::new();
65    if let Some(ref_adapter) = BT_ADAPTER.get() {
66        Ok(ref_adapter.as_obj())
67    } else {
68        let adapter = get_bluetooth_adapter()?;
69        let _ = BT_ADAPTER.set(adapter.clone());
70        Ok(BT_ADAPTER.get().unwrap().as_obj())
71    }
72}
73
74fn get_bluetooth_adapter() -> Result<jni::objects::GlobalRef, Error> {
75    jni_with_env(|env| {
76        let context = android_context();
77
78        let bluetooth_service = BLUETOOTH_SERVICE.new_jobject(env)?;
79        let manager = env
80            .call_method(
81                context,
82                "getSystemService",
83                "(Ljava/lang/String;)Ljava/lang/Object;",
84                &[(&bluetooth_service).into()],
85            )
86            .get_object(env)?;
87        if manager.is_null() {
88            return Ok(Err(Error::new(
89                ErrorKind::Unsupported,
90                "Cannot get BLUETOOTH_SERVICE",
91            )));
92        }
93        let adapter = env
94            .call_method(
95                manager,
96                "getAdapter",
97                "()Landroid/bluetooth/BluetoothAdapter;",
98                &[],
99            )
100            .get_object(env)
101            .globalize(env)?;
102        if !adapter.is_null() {
103            Ok(Ok(adapter))
104        } else {
105            Ok(Err(Error::new(
106                ErrorKind::Unsupported,
107                "`getAdapter` returned null",
108            )))
109        }
110    })
111    .map_err(jerr)?
112}
113
114/// Return true if Bluetooth is currently enabled and ready for use.
115/// It may return an error of `std::io::ErrorKind::PermissionDenied`, or some other error.
116pub fn is_enabled() -> Result<bool, Error> {
117    let adapter = bluetooth_adapter()?;
118    jni_with_env(|env| {
119        env.call_method(adapter, "isEnabled", "()Z", &[])
120            .get_boolean()
121    })
122    .map_err(jerr)
123}
124
125/// Gets a list of `BluetoothDevice` objects that are bonded (paired) to the adapter.
126/// `is_enabled()` is checked at first; returns an empty list if it is not enabled.
127pub fn get_bonded_devices() -> Result<Vec<BluetoothDevice>, Error> {
128    if !is_enabled()? {
129        return Ok(Vec::new());
130    }
131    let adapter = bluetooth_adapter()?;
132    jni_with_env(|env| {
133        let dev_set = env
134            .call_method(adapter, "getBondedDevices", "()Ljava/util/Set;", &[])
135            .get_object(env)?;
136        if dev_set.is_null() {
137            return Ok(Err(Error::from(ErrorKind::PermissionDenied)));
138        }
139        let jarr = env
140            .call_method(&dev_set, "toArray", "()[Ljava/lang/Object;", &[])
141            .get_object(env)?;
142        let jarr: &jni::objects::JObjectArray = jarr.as_ref().into();
143        let len = env.get_array_length(jarr).map_err(jni_clear_ex)?;
144        let mut vec = Vec::with_capacity(len as usize);
145        for i in 0..len {
146            vec.push(BluetoothDevice {
147                internal: env.get_object_array_element(jarr, i).global_ref(env)?,
148            });
149        }
150        Ok(Ok(vec))
151    })
152    .map_err(jerr)?
153}
154
155/// Corresponds to `android.bluetooth.BluetoothDevice`.
156#[derive(Clone, Debug)]
157pub struct BluetoothDevice {
158    pub(crate) internal: jni::objects::GlobalRef,
159}
160
161impl BluetoothDevice {
162    /// Returns the hardware address of this BluetoothDevice.
163    /// TODO: return some MAC address type instead of `String`.
164    pub fn get_address(&self) -> Result<String, Error> {
165        jni_with_env(|env| {
166            env.call_method(&self.internal, "getAddress", "()Ljava/lang/String;", &[])
167                .get_object(env)?
168                .get_string(env)
169        })
170        .map_err(jerr)
171    }
172
173    /// Gets the friendly Bluetooth name of the remote device.
174    pub fn get_name(&self) -> Result<String, Error> {
175        jni_with_env(|env| {
176            let dev_name = env
177                .call_method(&self.internal, "getName", "()Ljava/lang/String;", &[])
178                .get_object(env)?;
179            if dev_name.is_null() {
180                return Ok(Err(Error::from(ErrorKind::PermissionDenied)));
181            }
182            dev_name.get_string(env).map(Ok)
183        })
184        .map_err(jerr)?
185    }
186
187    /// Creates the Android Bluetooth API socket object for RFCOMM communication.
188    /// `SPP_UUID` can be used. Note that `connect` is not called automatically.
189    pub fn build_rfcomm_socket(
190        &self,
191        uuid: &str,
192        is_secure: bool,
193    ) -> Result<BluetoothSocket, Error> {
194        let socket = jni_with_env(|env| {
195            let uuid = uuid.new_jobject(env)?;
196            let uuid = env
197                .call_static_method(
198                    "java/util/UUID",
199                    "fromString",
200                    "(Ljava/lang/String;)Ljava/util/UUID;",
201                    &[(&uuid).into()],
202                )
203                .get_object(env)?;
204
205            let method_name = if is_secure {
206                "createRfcommSocketToServiceRecord"
207            } else {
208                "createInsecureRfcommSocketToServiceRecord"
209            };
210            env.call_method(
211                &self.internal,
212                method_name,
213                "(Ljava/util/UUID;)Landroid/bluetooth/BluetoothSocket;",
214                &[(&uuid).into()],
215            )
216            .get_object(env)
217            .globalize(env)
218        })
219        .map_err(jerr)?; // TODO: distinguish `IOException` and other unexpected exceptions
220        BluetoothSocket::build(socket)
221    }
222}
223
224/// Manages the Bluetooth socket and IO streams. It uses a read buffer and a background thread,
225/// because the timeout of the Java `InputStream` from the `BluetoothSocket` cannot be set.
226/// The read timeout defaults to 0 (it does not block).
227///
228/// Reference:
229/// <https://developer.android.com/develop/connectivity/bluetooth/transfer-data>
230pub struct BluetoothSocket {
231    internal: jni::objects::GlobalRef,
232
233    input_stream: jni::objects::GlobalRef,
234    buf_read: Arc<Mutex<VecDeque<u8>>>,
235    thread_read: Option<JoinHandle<Result<(), Error>>>, // the returned value is unused
236    read_callback: Arc<Mutex<Option<ReadCallback>>>,    // None by default
237    read_timeout: Duration,                             // set for the standard Read trait
238
239    output_stream: jni::objects::GlobalRef,
240    jmethod_write: jni::objects::JMethodID,
241    jmethod_flush: jni::objects::JMethodID,
242    array_write: jni::objects::GlobalRef,
243}
244
245type ReadCallback = Box<dyn Fn(Option<usize>) + 'static + Send>;
246
247impl BluetoothSocket {
248    const ARRAY_SIZE: usize = 32 * 1024;
249
250    fn build(obj: jni::objects::GlobalRef) -> Result<Self, Error> {
251        jni_with_env(|env| {
252            // the streams may (or may NOT) be usable after reconnection (check Android SDK source)
253            let input_stream = env
254                .call_method(&obj, "getInputStream", "()Ljava/io/InputStream;", &[])
255                .get_object(env)
256                .globalize(env)?;
257            let output_stream = env
258                .call_method(&obj, "getOutputStream", "()Ljava/io/OutputStream;", &[])
259                .get_object(env)
260                .globalize(env)?;
261
262            let jmethod_write = env
263                .get_method_id("java/io/OutputStream", "write", "([BII)V")
264                .map_err(jni_clear_ex)?;
265            let jmethod_flush = env
266                .get_method_id("java/io/OutputStream", "flush", "()V")
267                .map_err(jni_clear_ex)?;
268
269            let array_size = Self::ARRAY_SIZE as i32;
270            let array_write = env.new_byte_array(array_size).global_ref(env)?;
271
272            Ok(Self {
273                internal: obj,
274
275                input_stream,
276                buf_read: Arc::new(Mutex::new(VecDeque::new())),
277                thread_read: None,
278                read_callback: Arc::new(Mutex::new(None)),
279                read_timeout: Duration::from_millis(0),
280
281                output_stream,
282                jmethod_write,
283                jmethod_flush,
284                array_write,
285            })
286        })
287        .map_err(jerr)
288    }
289
290    /// Gets the connection status of this socket.
291    #[inline(always)]
292    pub fn is_connected(&self) -> Result<bool, Error> {
293        jni_with_env(|env| {
294            env.call_method(&self.internal, "isConnected", "()Z", &[])
295                .get_boolean()
296        })
297        .map_err(jerr)
298    }
299
300    /// Attempts to connect to a remote device. When connected, it creates a
301    /// backgrond thread for reading data, which terminates itself on disconnection.
302    /// Do not reuse the socket after disconnection, because the underlying OS
303    /// implementation is probably incapable of reconnecting the device, just like
304    /// `java.net.Socket`.
305    pub fn connect(&mut self) -> Result<(), Error> {
306        if self.is_connected()? {
307            return Ok(());
308        }
309        let adapter = bluetooth_adapter()?;
310
311        jni_with_env(|env| {
312            let _ = env
313                .call_method(adapter, "cancelDiscovery", "()Z", &[])
314                .map_err(jni_clear_ex);
315            env.call_method(&self.internal, "connect", "()V", &[])
316                .clear_ex()
317        })
318        .map_err(jerr)?;
319
320        if self.is_connected()? {
321            let socket = self.internal.clone();
322            let input_stream = self.input_stream.clone();
323            let arc_buf_read = self.buf_read.clone();
324            let arc_callback = self.read_callback.clone();
325            self.thread_read.replace(std::thread::spawn(move || {
326                Self::read_loop(socket, input_stream, arc_buf_read, arc_callback)
327            }));
328            Ok(())
329        } else {
330            Err(Error::from(ErrorKind::NotConnected))
331        }
332    }
333
334    /// Returns number of available bytes that can be read without blocking.
335    #[inline(always)]
336    pub fn len_available(&self) -> usize {
337        self.buf_read.lock().unwrap().len()
338    }
339
340    /// Clears the managed read buffer used by the Rust side background thread.
341    #[inline(always)]
342    pub fn clear_read_buf(&mut self) {
343        self.buf_read.lock().unwrap().clear();
344    }
345
346    /// Sets timeout for the `std::io::Read` implementation.
347    pub fn set_read_timeout(&mut self, timeout: Duration) {
348        self.read_timeout = timeout;
349    }
350
351    /// Sets or replaces the callback to be invoked from the background thread when
352    /// new data becomes available or the socket is disconnected. The length of newly
353    /// arrived data instead of the length of available data in the read buffer will
354    /// be passed to the callback (or `None` if it is disconnected).
355    pub fn set_read_callback(&mut self, f: impl Fn(Option<usize>) + 'static + Send) {
356        self.read_callback.lock().unwrap().replace(Box::new(f));
357    }
358
359    /// Closes this socket and releases any system resources associated with it.
360    /// If the stream is already closed then invoking this method has no effect.
361    pub fn close(&mut self) -> Result<(), Error> {
362        if !self.is_connected()? {
363            return Ok(());
364        }
365        let _ = self.flush();
366        jni_with_env(|env| {
367            env.call_method(&self.internal, "close", "()V", &[])?;
368            if let Some(th) = self.thread_read.take() {
369                let _ = th.join();
370            }
371            Ok(())
372        })
373        .map_err(jerr)
374    }
375}
376
377impl BluetoothSocket {
378    fn read_loop(
379        socket: jni::objects::GlobalRef,
380        input_stream: jni::objects::GlobalRef,
381        buf_read: Arc<Mutex<VecDeque<u8>>>,
382        read_callback: Arc<Mutex<Option<ReadCallback>>>,
383    ) -> Result<(), Error> {
384        jni_with_env(|env| {
385            let jmethod_read = env.get_method_id("java/io/InputStream", "read", "([BII)I")?;
386            let read_size = env
387                .call_method(&socket, "getMaxReceivePacketSize", "()I", &[])
388                .get_int()
389                .map(|i| {
390                    if i > 0 {
391                        let sz = i as usize;
392                        (Self::ARRAY_SIZE / sz) * sz
393                    } else {
394                        Self::ARRAY_SIZE
395                    }
396                })
397                .unwrap_or(Self::ARRAY_SIZE);
398
399            let mut vec_read = vec![0u8; read_size];
400            let array_read = env.new_byte_array(read_size as i32).auto_local(env)?;
401            let array_read: &jni::objects::JByteArray<'_> = array_read.as_ref().into();
402
403            loop {
404                use jni::signature::*;
405                // Safety: arguments passed to `call_method_unchecked` are correct.
406                let read_len = unsafe {
407                    env.call_method_unchecked(
408                        &input_stream,
409                        jmethod_read,
410                        ReturnType::Primitive(Primitive::Int),
411                        &[
412                            jvalue {
413                                l: array_read.as_raw(),
414                            },
415                            jvalue { i: 0 as jint },
416                            jvalue {
417                                i: read_size as jint,
418                            },
419                        ],
420                    )
421                }
422                .get_int();
423                if let Ok(len) = read_len {
424                    let len = if len > 0 {
425                        len as usize
426                    } else {
427                        continue;
428                    };
429                    // Safety: casts `&mut [u8]` to `&mut [i8]` for `get_byte_array_region`,
430                    // `input_stream.read(..)` = `len` <= `read_size` = `vec_read.len()`.
431                    let tmp_read = unsafe {
432                        std::slice::from_raw_parts_mut(vec_read.as_mut_ptr() as *mut i8, len)
433                    };
434                    env.get_byte_array_region(array_read, 0, tmp_read)
435                        .map_err(jni_clear_ex)?;
436                    buf_read
437                        .lock()
438                        .unwrap()
439                        .write_all(&vec_read[..len])
440                        .unwrap();
441                    Self::read_callback(&read_callback, Some(len));
442                } else {
443                    if let Some(ex) = jni_last_cleared_ex() {
444                        let ex_msg = ex.get_throwable_msg(env).unwrap().to_lowercase();
445                        if ex_msg.contains("closed") {
446                            // Note: will it change in future Android versions?
447                            let _ = env
448                                .call_method(&socket, "close", "()V", &[])
449                                .map_err(jni_clear_ex_ignore);
450                            Self::read_callback(&read_callback, None);
451                            return Ok(());
452                        }
453                    }
454                    let is_connected = env
455                        .call_method(&socket, "isConnected", "()Z", &[])
456                        .get_boolean()?;
457                    if !is_connected {
458                        Self::read_callback(&read_callback, None);
459                        return Ok(());
460                    }
461                }
462            }
463        })
464        .map_err(jerr)
465    }
466
467    fn read_callback(cb: impl AsRef<Mutex<Option<ReadCallback>>>, val: Option<usize>) {
468        let mut lck = cb.as_ref().lock().unwrap();
469        if let Some(callback) = lck.take() {
470            drop(lck);
471            callback(val);
472            let mut lck = cb.as_ref().lock().unwrap();
473            if lck.is_none() {
474                lck.replace(callback);
475            }
476        }
477    }
478}
479
480impl Read for BluetoothSocket {
481    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
482        if buf.is_empty() {
483            return Ok(0);
484        }
485
486        let t_timeout = SystemTime::now() + self.read_timeout;
487
488        let mut cnt_read = 0;
489        let mut disconnected = false;
490        while cnt_read == 0 {
491            let mut lck_buf_read = self.buf_read.lock().unwrap();
492            if let Ok(cnt) = lck_buf_read.read(&mut buf[cnt_read..]) {
493                cnt_read += cnt;
494            }
495            drop(lck_buf_read);
496            if cnt_read > 0 {
497                break;
498            } else if !self.is_connected()? {
499                disconnected = true;
500                break;
501            } else if let Ok(dur_rem) = t_timeout.duration_since(SystemTime::now()) {
502                std::thread::sleep(Duration::from_millis(50).min(dur_rem));
503            } else {
504                break;
505            }
506        }
507
508        if cnt_read > 0 {
509            Ok(cnt_read)
510        } else if !disconnected {
511            Err(Error::from(ErrorKind::TimedOut))
512        } else {
513            Err(Error::from(ErrorKind::NotConnected))
514        }
515    }
516}
517
518impl Write for BluetoothSocket {
519    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
520        if buf.is_empty() {
521            return Ok(0);
522        }
523
524        jni_with_env(|env| {
525            let array_write: &jni::objects::JByteArray<'_> = self.array_write.as_obj().into();
526            if (env.get_array_length(array_write).map_err(jni_clear_ex)? as usize) < buf.len() {
527                // replace the prepared reusable Java array with a larger array
528                self.array_write = env.byte_array_from_slice(buf).global_ref(env)?;
529            } else {
530                // Safety: casts `&[u8]` to `&[i8]` for `set_byte_array_region`.
531                let buf =
532                    unsafe { std::slice::from_raw_parts(buf.as_ptr() as *const i8, buf.len()) };
533                env.set_byte_array_region(array_write, 0, buf)
534                    .map_err(jni_clear_ex)?;
535            }
536
537            use jni::signature::*;
538            // Safety: arguments passed to `call_method_unchecked` are correct.
539            unsafe {
540                env.call_method_unchecked(
541                    &self.output_stream,
542                    self.jmethod_write,
543                    ReturnType::Primitive(Primitive::Void),
544                    &[
545                        jvalue {
546                            l: self.array_write.as_raw(),
547                        },
548                        jvalue { i: 0 as jint },
549                        jvalue {
550                            i: buf.len() as jint,
551                        },
552                    ],
553                )
554            }
555            .clear_ex()
556        })
557        .map_err(|e| {
558            if !self.is_connected().unwrap_or(false) {
559                Error::from(ErrorKind::NotConnected)
560            } else {
561                jerr(e)
562            }
563        })
564        .map(|_| buf.len())
565    }
566
567    #[inline]
568    fn flush(&mut self) -> std::io::Result<()> {
569        jni_with_env(|env| {
570            use jni::signature::*;
571            unsafe {
572                env.call_method_unchecked(
573                    &self.output_stream,
574                    self.jmethod_flush,
575                    ReturnType::Primitive(Primitive::Void),
576                    &[],
577                )
578            }
579            .clear_ex()
580        })
581        .map_err(jerr)
582    }
583}
584
585impl Drop for BluetoothSocket {
586    fn drop(&mut self) {
587        let _ = self.close();
588    }
589}