use std::io::Error;
use jni::{
objects::JObject,
sys::{jint, jvalue},
};
use jni_min_helper::*;
use std::{
collections::VecDeque,
io::{ErrorKind, Read, Write},
sync::{Arc, Mutex},
thread::JoinHandle,
time::{Duration, SystemTime},
};
const BLUETOOTH_SERVICE: &str = "bluetooth";
pub const SPP_UUID: &str = "00001101-0000-1000-8000-00805F9B34FB";
#[inline(always)]
pub(crate) fn jerr(err: jni::errors::Error) -> Error {
use jni::errors::Error::*;
if let JavaException = err {
let err = jni_clear_ex(err);
let Some(ex) = jni_last_cleared_ex() else {
return Error::other(JavaException);
};
jni_with_env(|env| Ok((ex.get_class_name(env)?, ex.get_throwable_msg(env)?)))
.map(|(cls, msg)| {
if cls.contains("SecurityException") {
Error::new(ErrorKind::PermissionDenied, msg)
} else if cls.contains("IllegalArgumentException") {
Error::new(ErrorKind::InvalidInput, msg)
} else {
Error::other(format!("{cls}: {msg}"))
}
})
.unwrap_or(Error::other(err))
} else {
Error::other(err)
}
}
#[inline(always)]
pub(crate) fn bluetooth_adapter() -> Result<&'static JObject<'static>, Error> {
use std::sync::OnceLock;
static BT_ADAPTER: OnceLock<jni::objects::GlobalRef> = OnceLock::new();
if let Some(ref_adapter) = BT_ADAPTER.get() {
Ok(ref_adapter.as_obj())
} else {
let adapter = get_bluetooth_adapter()?;
let _ = BT_ADAPTER.set(adapter.clone());
Ok(BT_ADAPTER.get().unwrap().as_obj())
}
}
fn get_bluetooth_adapter() -> Result<jni::objects::GlobalRef, Error> {
jni_with_env(|env| {
let context = android_context();
let bluetooth_service = BLUETOOTH_SERVICE.new_jobject(env)?;
let manager = env
.call_method(
context,
"getSystemService",
"(Ljava/lang/String;)Ljava/lang/Object;",
&[(&bluetooth_service).into()],
)
.get_object(env)?;
if manager.is_null() {
return Ok(Err(Error::new(
ErrorKind::Unsupported,
"Cannot get BLUETOOTH_SERVICE",
)));
}
let adapter = env
.call_method(
manager,
"getAdapter",
"()Landroid/bluetooth/BluetoothAdapter;",
&[],
)
.get_object(env)
.globalize(env)?;
if !adapter.is_null() {
Ok(Ok(adapter))
} else {
Ok(Err(Error::new(
ErrorKind::Unsupported,
"`getAdapter` returned null",
)))
}
})
.map_err(jerr)?
}
pub fn is_enabled() -> Result<bool, Error> {
let adapter = bluetooth_adapter()?;
jni_with_env(|env| {
env.call_method(adapter, "isEnabled", "()Z", &[])
.get_boolean()
})
.map_err(jerr)
}
pub fn get_bonded_devices() -> Result<Vec<BluetoothDevice>, Error> {
if !is_enabled()? {
return Ok(Vec::new());
}
let adapter = bluetooth_adapter()?;
jni_with_env(|env| {
let dev_set = env
.call_method(adapter, "getBondedDevices", "()Ljava/util/Set;", &[])
.get_object(env)?;
if dev_set.is_null() {
return Ok(Err(Error::from(ErrorKind::PermissionDenied)));
}
let jarr = env
.call_method(&dev_set, "toArray", "()[Ljava/lang/Object;", &[])
.get_object(env)?;
let jarr: &jni::objects::JObjectArray = jarr.as_ref().into();
let len = env.get_array_length(jarr).map_err(jni_clear_ex)?;
let mut vec = Vec::with_capacity(len as usize);
for i in 0..len {
vec.push(BluetoothDevice {
internal: env.get_object_array_element(jarr, i).global_ref(env)?,
});
}
Ok(Ok(vec))
})
.map_err(jerr)?
}
#[derive(Clone, Debug)]
pub struct BluetoothDevice {
pub(crate) internal: jni::objects::GlobalRef,
}
impl BluetoothDevice {
pub fn get_address(&self) -> Result<String, Error> {
jni_with_env(|env| {
env.call_method(&self.internal, "getAddress", "()Ljava/lang/String;", &[])
.get_object(env)?
.get_string(env)
})
.map_err(jerr)
}
pub fn get_name(&self) -> Result<String, Error> {
jni_with_env(|env| {
let dev_name = env
.call_method(&self.internal, "getName", "()Ljava/lang/String;", &[])
.get_object(env)?;
if dev_name.is_null() {
return Ok(Err(Error::from(ErrorKind::PermissionDenied)));
}
dev_name.get_string(env).map(Ok)
})
.map_err(jerr)?
}
pub fn build_rfcomm_socket(
&self,
uuid: &str,
is_secure: bool,
) -> Result<BluetoothSocket, Error> {
let socket = jni_with_env(|env| {
let uuid = uuid.new_jobject(env)?;
let uuid = env
.call_static_method(
"java/util/UUID",
"fromString",
"(Ljava/lang/String;)Ljava/util/UUID;",
&[(&uuid).into()],
)
.get_object(env)?;
let method_name = if is_secure {
"createRfcommSocketToServiceRecord"
} else {
"createInsecureRfcommSocketToServiceRecord"
};
env.call_method(
&self.internal,
method_name,
"(Ljava/util/UUID;)Landroid/bluetooth/BluetoothSocket;",
&[(&uuid).into()],
)
.get_object(env)
.globalize(env)
})
.map_err(jerr)?; BluetoothSocket::build(socket)
}
}
pub struct BluetoothSocket {
internal: jni::objects::GlobalRef,
input_stream: jni::objects::GlobalRef,
buf_read: Arc<Mutex<VecDeque<u8>>>,
thread_read: Option<JoinHandle<Result<(), Error>>>, read_callback: Arc<Mutex<Option<ReadCallback>>>, read_timeout: Duration,
output_stream: jni::objects::GlobalRef,
jmethod_write: jni::objects::JMethodID,
jmethod_flush: jni::objects::JMethodID,
array_write: jni::objects::GlobalRef,
}
type ReadCallback = Box<dyn Fn(Option<usize>) + 'static + Send>;
impl BluetoothSocket {
const ARRAY_SIZE: usize = 32 * 1024;
fn build(obj: jni::objects::GlobalRef) -> Result<Self, Error> {
jni_with_env(|env| {
let input_stream = env
.call_method(&obj, "getInputStream", "()Ljava/io/InputStream;", &[])
.get_object(env)
.globalize(env)?;
let output_stream = env
.call_method(&obj, "getOutputStream", "()Ljava/io/OutputStream;", &[])
.get_object(env)
.globalize(env)?;
let jmethod_write = env
.get_method_id("java/io/OutputStream", "write", "([BII)V")
.map_err(jni_clear_ex)?;
let jmethod_flush = env
.get_method_id("java/io/OutputStream", "flush", "()V")
.map_err(jni_clear_ex)?;
let array_size = Self::ARRAY_SIZE as i32;
let array_write = env.new_byte_array(array_size).global_ref(env)?;
Ok(Self {
internal: obj,
input_stream,
buf_read: Arc::new(Mutex::new(VecDeque::new())),
thread_read: None,
read_callback: Arc::new(Mutex::new(None)),
read_timeout: Duration::from_millis(0),
output_stream,
jmethod_write,
jmethod_flush,
array_write,
})
})
.map_err(jerr)
}
#[inline(always)]
pub fn is_connected(&self) -> Result<bool, Error> {
jni_with_env(|env| {
env.call_method(&self.internal, "isConnected", "()Z", &[])
.get_boolean()
})
.map_err(jerr)
}
pub fn connect(&mut self) -> Result<(), Error> {
if self.is_connected()? {
return Ok(());
}
let adapter = bluetooth_adapter()?;
jni_with_env(|env| {
let _ = env
.call_method(adapter, "cancelDiscovery", "()Z", &[])
.map_err(jni_clear_ex);
env.call_method(&self.internal, "connect", "()V", &[])
.clear_ex()
})
.map_err(jerr)?;
if self.is_connected()? {
let socket = self.internal.clone();
let input_stream = self.input_stream.clone();
let arc_buf_read = self.buf_read.clone();
let arc_callback = self.read_callback.clone();
self.thread_read.replace(std::thread::spawn(move || {
Self::read_loop(socket, input_stream, arc_buf_read, arc_callback)
}));
Ok(())
} else {
Err(Error::from(ErrorKind::NotConnected))
}
}
#[inline(always)]
pub fn len_available(&self) -> usize {
self.buf_read.lock().unwrap().len()
}
#[inline(always)]
pub fn clear_read_buf(&mut self) {
self.buf_read.lock().unwrap().clear();
}
pub fn set_read_timeout(&mut self, timeout: Duration) {
self.read_timeout = timeout;
}
pub fn set_read_callback(&mut self, f: impl Fn(Option<usize>) + 'static + Send) {
self.read_callback.lock().unwrap().replace(Box::new(f));
}
pub fn close(&mut self) -> Result<(), Error> {
if !self.is_connected()? {
return Ok(());
}
let _ = self.flush();
jni_with_env(|env| {
env.call_method(&self.internal, "close", "()V", &[])?;
if let Some(th) = self.thread_read.take() {
let _ = th.join();
}
Ok(())
})
.map_err(jerr)
}
}
impl BluetoothSocket {
fn read_loop(
socket: jni::objects::GlobalRef,
input_stream: jni::objects::GlobalRef,
buf_read: Arc<Mutex<VecDeque<u8>>>,
read_callback: Arc<Mutex<Option<ReadCallback>>>,
) -> Result<(), Error> {
jni_with_env(|env| {
let jmethod_read = env.get_method_id("java/io/InputStream", "read", "([BII)I")?;
let read_size = env
.call_method(&socket, "getMaxReceivePacketSize", "()I", &[])
.get_int()
.map(|i| {
if i > 0 {
let sz = i as usize;
(Self::ARRAY_SIZE / sz) * sz
} else {
Self::ARRAY_SIZE
}
})
.unwrap_or(Self::ARRAY_SIZE);
let mut vec_read = vec![0u8; read_size];
let array_read = env.new_byte_array(read_size as i32).auto_local(env)?;
let array_read: &jni::objects::JByteArray<'_> = array_read.as_ref().into();
loop {
use jni::signature::*;
let read_len = unsafe {
env.call_method_unchecked(
&input_stream,
jmethod_read,
ReturnType::Primitive(Primitive::Int),
&[
jvalue {
l: array_read.as_raw(),
},
jvalue { i: 0 as jint },
jvalue {
i: read_size as jint,
},
],
)
}
.get_int();
if let Ok(len) = read_len {
let len = if len > 0 {
len as usize
} else {
continue;
};
let tmp_read = unsafe {
std::slice::from_raw_parts_mut(vec_read.as_mut_ptr() as *mut i8, len)
};
env.get_byte_array_region(array_read, 0, tmp_read)
.map_err(jni_clear_ex)?;
buf_read
.lock()
.unwrap()
.write_all(&vec_read[..len])
.unwrap();
Self::read_callback(&read_callback, Some(len));
} else {
if let Some(ex) = jni_last_cleared_ex() {
let ex_msg = ex.get_throwable_msg(env).unwrap().to_lowercase();
if ex_msg.contains("closed") {
let _ = env
.call_method(&socket, "close", "()V", &[])
.map_err(jni_clear_ex_ignore);
Self::read_callback(&read_callback, None);
return Ok(());
}
}
let is_connected = env
.call_method(&socket, "isConnected", "()Z", &[])
.get_boolean()?;
if !is_connected {
Self::read_callback(&read_callback, None);
return Ok(());
}
}
}
})
.map_err(jerr)
}
fn read_callback(cb: impl AsRef<Mutex<Option<ReadCallback>>>, val: Option<usize>) {
let mut lck = cb.as_ref().lock().unwrap();
if let Some(callback) = lck.take() {
drop(lck);
callback(val);
let mut lck = cb.as_ref().lock().unwrap();
if lck.is_none() {
lck.replace(callback);
}
}
}
}
impl Read for BluetoothSocket {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
if buf.is_empty() {
return Ok(0);
}
let t_timeout = SystemTime::now() + self.read_timeout;
let mut cnt_read = 0;
let mut disconnected = false;
while cnt_read == 0 {
let mut lck_buf_read = self.buf_read.lock().unwrap();
if let Ok(cnt) = lck_buf_read.read(&mut buf[cnt_read..]) {
cnt_read += cnt;
}
drop(lck_buf_read);
if cnt_read > 0 {
break;
} else if !self.is_connected()? {
disconnected = true;
break;
} else if let Ok(dur_rem) = t_timeout.duration_since(SystemTime::now()) {
std::thread::sleep(Duration::from_millis(50).min(dur_rem));
} else {
break;
}
}
if cnt_read > 0 {
Ok(cnt_read)
} else if !disconnected {
Err(Error::from(ErrorKind::TimedOut))
} else {
Err(Error::from(ErrorKind::NotConnected))
}
}
}
impl Write for BluetoothSocket {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
if buf.is_empty() {
return Ok(0);
}
jni_with_env(|env| {
let array_write: &jni::objects::JByteArray<'_> = self.array_write.as_obj().into();
if (env.get_array_length(array_write).map_err(jni_clear_ex)? as usize) < buf.len() {
self.array_write = env.byte_array_from_slice(buf).global_ref(env)?;
} else {
let buf =
unsafe { std::slice::from_raw_parts(buf.as_ptr() as *const i8, buf.len()) };
env.set_byte_array_region(array_write, 0, buf)
.map_err(jni_clear_ex)?;
}
use jni::signature::*;
unsafe {
env.call_method_unchecked(
&self.output_stream,
self.jmethod_write,
ReturnType::Primitive(Primitive::Void),
&[
jvalue {
l: self.array_write.as_raw(),
},
jvalue { i: 0 as jint },
jvalue {
i: buf.len() as jint,
},
],
)
}
.clear_ex()
})
.map_err(|e| {
if !self.is_connected().unwrap_or(false) {
Error::from(ErrorKind::NotConnected)
} else {
jerr(e)
}
})
.map(|_| buf.len())
}
#[inline]
fn flush(&mut self) -> std::io::Result<()> {
jni_with_env(|env| {
use jni::signature::*;
unsafe {
env.call_method_unchecked(
&self.output_stream,
self.jmethod_flush,
ReturnType::Primitive(Primitive::Void),
&[],
)
}
.clear_ex()
})
.map_err(jerr)
}
}
impl Drop for BluetoothSocket {
fn drop(&mut self) {
let _ = self.close();
}
}