1use 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
29pub const SPP_UUID: &str = "00001101-0000-1000-8000-00805F9B34FB";
31
32#[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#[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
114pub 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
125pub 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#[derive(Clone, Debug)]
157pub struct BluetoothDevice {
158 pub(crate) internal: jni::objects::GlobalRef,
159}
160
161impl BluetoothDevice {
162 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 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 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)?; BluetoothSocket::build(socket)
221 }
222}
223
224pub 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>>>, read_callback: Arc<Mutex<Option<ReadCallback>>>, read_timeout: Duration, 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 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 #[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 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 #[inline(always)]
336 pub fn len_available(&self) -> usize {
337 self.buf_read.lock().unwrap().len()
338 }
339
340 #[inline(always)]
342 pub fn clear_read_buf(&mut self) {
343 self.buf_read.lock().unwrap().clear();
344 }
345
346 pub fn set_read_timeout(&mut self, timeout: Duration) {
348 self.read_timeout = timeout;
349 }
350
351 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 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 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 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 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 self.array_write = env.byte_array_from_slice(buf).global_ref(env)?;
529 } else {
530 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 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}