use jni::objects::JObject;
use jni::refs::Global;
use winit::platform::android::activity::AndroidApp;
#[cfg(feature = "async")]
compile_error!("Android does not support async");
static JAVA_CONTEXT: OnceLock<Arc<Global<::jni::objects::JObject>>> = OnceLock::new();
#[inline(always)]
pub(crate) fn jerr(err: jni::errors::Error) -> std::io::Error {
std::io::Error::other(err)
}
pub struct Java {
app: AndroidApp,
java: jni::JavaVM,
}
impl Java {
pub fn use_env<T, F: FnOnce(&mut jni::Env, jni::objects::JObject) -> T>(&mut self, f: F) -> T {
self.java
.attach_current_thread(|e| {
let context = unsafe {
jni::objects::JObject::from_raw(
e,
self.app.activity_as_ptr() as *mut jni::sys::_jobject,
)
};
Ok::<T, jni::errors::Error>(f(e, context))
})
.unwrap()
}
pub fn get_app(&self) -> AndroidApp {
self.app.clone()
}
pub fn make(app: AndroidApp) -> Self {
let vm = unsafe {
jni::JavaVM::from_raw(app.vm_as_ptr() as *mut *const jni::sys::JNIInvokeInterface_)
};
Java { app, java: vm }
}
}
use std::convert::TryInto;
use std::sync::Arc;
use std::sync::Mutex;
use std::sync::OnceLock;
mod socket;
pub use socket::BluetoothSocket;
use crate::bluetooth_uuid::ParcelUuid;
mod device;
pub use device::BluetoothDevice;
pub struct BluetoothDiscovery {
adapter: jni::refs::Global<JObject<'static>>,
}
impl BluetoothDiscovery {
fn new(adapter: jni::refs::Global<JObject<'static>>) -> Self {
let mut s = Self { adapter };
s.start();
s
}
fn start(&mut self) {
let java = jni_min_helper::jni_get_vm();
let _ = java.attach_current_thread(|env| {
let a = env.call_method(
&self.adapter,
jni::jni_str!("startDiscovery"),
jni::jni_sig!("()Z"),
&[],
);
log::error!("Start discovery {:?}", a);
Ok::<(), jni::errors::Error>(())
});
}
}
impl Drop for BluetoothDiscovery {
fn drop(&mut self) {
let java = jni_min_helper::jni_get_vm();
java.attach_current_thread(|env| {
let _ = env.call_method(
&self.adapter,
jni::jni_str!("cancelDiscovery"),
jni::jni_sig!("()Z"),
&[],
);
Ok::<(), jni::errors::Error>(())
})
.unwrap();
}
}
pub struct RfcommStream {
socket: jni::refs::Global<JObject<'static>>,
}
impl RfcommStream {
pub fn new(socket: jni::refs::Global<JObject<'static>>) -> Result<Self, String> {
Ok(Self {
socket,
})
}
}
impl std::io::Read for RfcommStream {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
let java = jni_min_helper::jni_get_vm();
java.attach_current_thread(|env| {
let ba = env.new_byte_array(buf.len() as usize)?;
let socket = self.socket.as_obj();
let l = env
.call_method(
socket,
jni::jni_str!("readNBytes"),
jni::jni_sig!("([BII)I"),
&[(&ba).into()],
)?
.into_int()?;
let a = env.convert_byte_array(ba)?;
buf[0..l as usize].copy_from_slice(&a);
Ok(l as usize)
})
.map_err(jerr)
}
}
impl std::io::Write for RfcommStream {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
let java = jni_min_helper::jni_get_vm();
java.attach_current_thread(|env| {
let ba = env.byte_array_from_slice(buf)?;
let socket = self.socket.as_obj();
let _ = env
.call_method(
socket,
jni::jni_str!("write"),
jni::jni_sig!("([B)V"),
&[(&ba).into()],
)?;
Ok(buf.len())
})
.map_err(jerr)
}
fn flush(&mut self) -> std::io::Result<()> {
let java = jni_min_helper::jni_get_vm();
java.attach_current_thread(|env| {
let socket = self.socket.as_obj();
let _ = env
.call_method(socket, jni::jni_str!("flush"), jni::jni_sig!("()V"), &[])?;
Ok(())
})
.map_err(jerr)
}
}
pub struct BluetoothRfcommConnectable {
socket: jni::refs::Global<JObject<'static>>,
}
impl BluetoothRfcommConnectable {
pub fn accept(
self,
timeout: std::time::Duration,
) -> Result<(crate::BluetoothStream, [u8; 6], u8), String> {
let java = jni_min_helper::jni_get_vm();
let a = java
.attach_current_thread(|env| {
let millis = (timeout.as_millis() as i32).into();
let e = env
.call_method(
self.socket.as_obj(),
jni::jni_str!("accept"),
jni::jni_sig!("(I)Landroid/bluetooth/BluetoothSocket;"),
&[millis],
)?
.l()?;
let socket = env.new_global_ref(&e)?;
Ok(socket)
})
.map_err(|e| jerr(e).to_string())?;
let s = RfcommStream::new(a)?;
Ok((s, [0; 6], 42))
}
}
pub struct BluetoothRfcommProfile {
socket: OnceLock<::jni::refs::Global<JObject<'static>>>,
}
impl BluetoothRfcommProfile {
pub fn connectable(&mut self) -> Result<crate::BluetoothRfcommConnectable, String> {
let java = jni_min_helper::jni_get_vm();
let ss = java
.attach_current_thread(|env| env.new_global_ref(self.socket.get().unwrap()))
.map_err(|e| jerr(e).to_string())?;
Ok(BluetoothRfcommConnectable { socket: ss })
}
}
pub struct Bluetooth {
adapter: jni::refs::Global<JObject<'static>>,
receiver: Option<::jni::refs::Global<JObject<'static>>>,
blue_uuid_receiver: Option<jni_min_helper::BroadcastReceiver>,
}
impl Bluetooth {
pub fn get_remote_device<'local>(
&self,
address: &str,
) -> Result<BluetoothDevice, jni::errors::Error> {
let java = jni_min_helper::jni_get_vm();
java.attach_current_thread(|env| {
let mac = env.new_string(address)?;
let device = env.call_method(
&self.adapter,
jni::jni_str!("getRemoteDevice"),
jni::jni_sig!("(Ljava/lang/String;)Landroid/bluetooth/BluetoothDevice;"),
&[jni::JValue::Object(&mac)],
)?.l()?;
Ok(BluetoothDevice::new(
env.new_global_ref(device)?,
))
})
}
pub fn register_rfcomm_profile(
&self,
settings: crate::BluetoothRfcommProfileSettings,
) -> Result<crate::BluetoothRfcommProfile, String> {
let java = jni_min_helper::jni_get_vm();
java.attach_current_thread(|env| {
let jsettings = {
log::error!("Register rfcomm 1");
log::error!("Finding builder class");
let ss = env
.find_class(jni::jni_str!("android/bluetooth/BluetoothSocketSettings$Builder"))?;
log::error!("Found builder class");
log::error!("Got constructor");
let obj = env
.new_object(&ss, jni::jni_sig!("()V"), &[])?;
log::error!("Success in making socket settings builder?");
let mut jsettings = obj;
log::error!("Register rfcomm 2");
if let Some(auth) = settings.authenticate {
let e = env
.call_method(
jsettings,
jni::jni_str!("setAuthenticationRequired"),
jni::jni_sig!("(Z)Landroid/bluetooth/BluetoothSocketSettings$Builder;"),
&[auth.into()],
)?
.l()?;
jsettings = env
.new_local_ref(&e)?;
}
log::error!("Register rfcomm 3");
if let Some(val) = settings.psm {
let e = env
.call_method(
jsettings,
jni::jni_str!("setL2capPsm"),
jni::jni_sig!("(I)Landroid/bluetooth/BluetoothSocketSettings$Builder;"),
&[val.into()],
)?
.l()?;
jsettings = env
.new_local_ref(&e)?;
}
log::error!("Register rfcomm 4");
if let Some(name) = &settings.name {
let arg = env.new_string(name)?;
let e = env
.call_method(jsettings, jni::jni_str!("setRfcommServiceName"), jni::jni_sig!("(Ljava/lang/String;)Landroid/bluetooth/BluetoothSocketSettings$Builder;"), &[(&arg).into()])?
.l()?;
jsettings = env
.new_local_ref(&e)?;
}
log::error!("Register rfcomm 5");
{
let arg = env.new_string(settings
.uuid)?;
let uuid_class = env
.find_class(jni::jni_str!("java/util/UUID"))?;
let uuid = env
.call_static_method(
uuid_class,
jni::jni_str!("fromString"),
jni::jni_sig!("(Ljava/lang/String;)Ljava/util/UUID;"),
&[(&arg).into()],
)?;
let e = env
.call_method(
jsettings,
jni::jni_str!("setRfcommUuid"),
jni::jni_sig!("(Ljava/util/UUID;)Landroid/bluetooth/BluetoothSocketSettings$Builder;"),
&[uuid.borrow()],
)?
.l()?;
jsettings = env
.new_local_ref(&e)?;
}
log::error!("Register rfcomm 6");
let e = env
.call_method(
jsettings,
jni::jni_str!("build"),
jni::jni_sig!("()Landroid/bluetooth/BluetoothSocketSettings;"),
&[],
)?
.l()?;
jsettings = env
.new_local_ref(&e)?;
log::error!("Register rfcomm 7");
Ok::<jni::objects::JObject<'_>, jni::errors::Error>(jsettings)
}?;
log::error!("Register rfcomm 8");
let jsettings = jni::objects::JValue::Object(&jsettings);
log::error!("Register rfcomm 9");
let e = env
.call_method(
&self.adapter,
jni::jni_str!("listenUsingSocketSettings"),
jni::jni_sig!("(Landroid/bluetooth/BluetoothSocketSettings;)Landroid/bluetooth/BluetoothServerSocket;"),
&[jsettings],
)?
.l()?;
log::error!("Register rfcomm 10");
let socket = env
.new_global_ref(&e)?;
log::error!("Register rfcomm 11");
Ok(BluetoothRfcommProfile {
socket: socket.into(),
})
}).map_err(|e| jerr(e).to_string())
}
pub fn set_discoverable(&self, d: bool) -> Result<(), ()> {
if d {
let java = jni_min_helper::jni_get_vm();
java.attach_current_thread(|env| {
let context = JAVA_CONTEXT.get().unwrap();
let context = env.new_local_ref(context.as_obj())?;
let arg = env.new_string("android.bluetooth.adapter.action.REQUEST_DISCOVERABLE")?;
let intent = env
.new_object(
jni::jni_str!("android/content/Intent"),
jni::jni_sig!("(Ljava/lang/String;)V"),
&[(&arg).into()],
)
.unwrap();
let mut args = Vec::new();
args.push(&intent);
let mut args2: Vec<jni::objects::JValue> =
args.iter().map(|a| a.try_into().unwrap()).collect();
args2.push(1.into());
let a = env.call_method(
context,
jni::jni_str!("startActivityForResult"),
jni::jni_sig!("(Landroid/content/Intent;I)V"),
args2.as_slice(),
);
log::error!("Results of bluetooth enable discoverable is {:?}", a);
Ok(())
}).map_err(|_e: jni::errors::Error| ())
}
else {
Ok(())
}
}
pub fn get_paired_devices(&self) -> Option<Vec<crate::BluetoothDevice>> {
let bd = self.get_bonded_devices();
if let Ok(bd) = bd {
let mut devs = Vec::new();
for b in bd {
devs.push(b);
}
Some(devs)
} else {
None
}
}
pub fn start_discovery(&self) -> crate::BluetoothDiscovery {
let java = jni_min_helper::jni_get_vm();
let ss = java
.attach_current_thread(|env| env.new_global_ref(self.adapter.as_obj()))
.map_err(|e| jerr(e).to_string()).unwrap();
BluetoothDiscovery::new(ss).into()
}
pub fn addresses(&self) -> Option<super::BluetoothAdapterAddress> {
let java = jni_min_helper::jni_get_vm();
java.attach_current_thread(|env| {
let dev_name = env
.call_method(
&self.adapter,
jni::jni_str!("getAddress"),
jni::jni_sig!("()Ljava/lang/String;"),
&[],
)?
.l()?;
let temp = env.new_local_ref(&dev_name)?;
let n = env.cast_local::<jni::objects::JString>(temp)?;
Ok::<_, jni::errors::Error>(super::BluetoothAdapterAddress::String(n.to_string()))
}).ok()
}
}
type ReadCallback = Box<dyn Fn(Option<usize>) + 'static + Send>;
const BLUETOOTH_SERVICE: &str = "bluetooth";
impl Bluetooth {
pub fn new(app: AndroidApp) -> Self {
let java = Arc::new(Mutex::new(Java::make(app)));
let adapter = {
let mut java2 = java.lock().unwrap();
java2.use_env(|env, context| Self::get_adapter(env, &context).unwrap())
};
Self {
adapter,
receiver: None,
blue_uuid_receiver: None,
}
}
fn check_adapter(&mut self) {
if self.receiver.is_none() {
let arg1 = jni_min_helper::BroadcastReceiver::build(|env, _context, intent| {
let action = env
.call_method(intent, jni::jni_str!("getAction"), jni::jni_sig!("()Ljava/lang/String;"), &[])?
.l()?;
if action.is_null() {
return Err(jni::errors::Error::NullPtr("No action"));
}
let temp = env.new_local_ref(&action)?;
let jstr = env.cast_local::<jni::objects::JString>(temp)?;
let _ = jstr.to_string();
Ok(())
})
.unwrap();
let r = register_receiver(&arg1, "android.bluetooth.device.action.UUID");
self.blue_uuid_receiver.replace(arg1);
if let Some(r) = r {
log::error!("Receiver is {:?}", r);
self.receiver.replace(r);
}
}
}
pub fn try_get_permissions(
&self,
permission: &str,
) -> Result<bool, std::io::Error> {
let java = jni_min_helper::jni_get_vm();
java.attach_current_thread(|env| {
let context = JAVA_CONTEXT.get().unwrap();
if !self.check_permission2(env, &context, permission)? {
self.get_permission2(env, &context, permission)
} else {
Ok(true)
}
}).map_err(jerr)
}
pub fn check_permission(&self, permission: &str) -> Result<bool, std::io::Error> {
let java = jni_min_helper::jni_get_vm();
java.attach_current_thread(|env| {
let context = JAVA_CONTEXT.get().unwrap();
self.check_permission2(env, &context, permission)
}).map_err(jerr)
}
pub fn get_permission2(
&self,
env: &mut jni::Env,
context: &jni::objects::JObject,
permission: &str,
) -> Result<bool, jni::errors::Error> {
let class_loader_obj = env
.call_method(&context, jni::jni_str!("getClassLoader"), jni::jni_sig!("()Ljava/lang/ClassLoader;"), &[])
.expect("Failed to get ClassLoader")
.l()?;
let class_name = env.new_string("com.example.android.JniBridge")?;
let jni_bridge_class_obj : jni::objects::JObject = env
.call_method(
class_loader_obj,
jni::jni_str!("loadClass"),
jni::jni_sig!("(Ljava/lang/String;)Ljava/lang/Class;"),
&[(&class_name).into()],
)
.expect("Failed to call loadClass")
.l()?;
let jni_bridge_class_obj: jni::objects::JClass = env.cast_local::<jni::objects::JClass>(jni_bridge_class_obj)?;
let jni_bridge_class = jni::objects::JClass::from(jni_bridge_class_obj);
let jni_bridge_obj = env
.new_object(jni_bridge_class, jni::jni_sig!("()V"), &[])?;
let activity = env.new_local_ref(JAVA_CONTEXT.get().unwrap().as_obj())?;
let arg2 = env.new_string(permission)?;
let asdf = env
.call_method(
jni_bridge_obj,
jni::jni_str!("requestPermission"),
jni::jni_sig!("(Landroid/app/Activity;Ljava/lang/String;)I"),
&[jni::objects::JValue::Object(&activity), (&arg2).try_into().unwrap()],
)?;
asdf.i().map(|v| v == 0)
}
pub fn check_permission2(
&self,
env: &mut jni::Env,
context: &jni::objects::JObject,
permission: &str,
) -> Result<bool, jni::errors::Error> {
let class_loader_obj = env
.call_method(&context, jni::jni_str!("getClassLoader"), jni::jni_sig!("()Ljava/lang/ClassLoader;"), &[])
.expect("Failed to get ClassLoader")
.l()?;
let class_name = env.new_string("com.example.android.JniBridge")?;
let jni_bridge_class_obj : jni::objects::JObject = env
.call_method(
class_loader_obj,
jni::jni_str!("loadClass"),
jni::jni_sig!("(Ljava/lang/String;)Ljava/lang/Class;"),
&[(&class_name).into()],
)
.expect("Failed to call loadClass")
.l()?;
let jni_bridge_class_obj: jni::objects::JClass = env.cast_local::<jni::objects::JClass>(jni_bridge_class_obj)?;
let jni_bridge_class = jni::objects::JClass::from(jni_bridge_class_obj);
let jni_bridge_obj = env
.new_object(jni_bridge_class, jni::jni_sig!("()V"), &[])?;
let activity = env.new_local_ref(JAVA_CONTEXT.get().unwrap().as_obj())?;
let arg2 = env.new_string(permission)?;
let asdf = env
.call_method(
jni_bridge_obj,
jni::jni_str!("checkSelfPermission"),
jni::jni_sig!("(Landroid/content/Context;Ljava/lang/String;)I"),
&[jni::objects::JValue::Object(&activity), (&arg2).try_into().unwrap()],
)?;
asdf.i().map(|v| v == 0)
}
pub fn enable(&mut self) {
if !self.is_enabled() {
log::error!("Bluetooth not enabled. Requesting it to be enabled");
let java = jni_min_helper::jni_get_vm();
java.attach_current_thread(|env| {
let context = env.new_local_ref(JAVA_CONTEXT.get().unwrap().as_obj())?;
let arg = env.new_string("android.bluetooth.adapter.action.REQUEST_ENABLE")?;
let intent = env
.new_object(
jni::jni_str!("android/content/Intent"),
jni::jni_sig!("(Ljava/lang/String;)V"),
&[(&arg).into()],
)
.unwrap();
let mut args = Vec::new();
args.push(&intent);
let mut args2: Vec<jni::objects::JValue> = args
.iter()
.map(|a: &&jni::objects::JObject| jni::objects::JValue::Object(*a))
.collect();
args2.push(1.into());
let a = env.call_method(
context,
jni::jni_str!("startActivityForResult"),
jni::jni_sig!("(Landroid/content/Intent;I)V"),
args2.as_slice(),
);
log::error!("Results of bluetooth enable is {:?}", a);
Ok::<(), jni::errors::Error>(())
}).unwrap();
}
}
pub fn is_enabled(&mut self) -> bool {
self.check_adapter();
let java = jni_min_helper::jni_get_vm();
java.attach_current_thread(|env| {
env.call_method(&self.adapter, jni::jni_str!("isEnabled"), jni::jni_sig!("()Z"), &[])?.into_bool()
}).unwrap()
}
pub fn get_bonded_devices(&self) -> Result<Vec<BluetoothDevice>, std::io::Error> {
let java = jni_min_helper::jni_get_vm();
java.attach_current_thread(|env| {
let dev_set = env
.call_method(&self.adapter, jni::jni_str!("getBondedDevices"), jni::jni_sig!("()Ljava/util/Set;"), &[])?
.l()?;
let jarr = env
.call_method(&dev_set, jni::jni_str!("toArray"), jni::jni_sig!("()[Ljava/lang/Object;"), &[])?
.l()?;
let temp = env.new_local_ref(&jarr)?;
let jarr = env.cast_local::<jni::objects::JObjectArray>(temp)?;
let len = jarr.len(env)?;
let mut vec = Vec::with_capacity(len);
for i in 0..len {
let temp = jarr.get_element(env, i)?;
vec.push(BluetoothDevice::new(
env.new_global_ref(temp)?,
));
}
Ok(vec)
}).map_err(jerr)
}
fn get_adapter<'a>(
env: &mut jni::Env,
context: &jni::objects::JObject,
) -> Result<::jni::refs::Global<JObject<'static>>, jni::errors::Error> {
let t = env.new_string(BLUETOOTH_SERVICE)?;
let bluetooth_service = env.new_local_ref(t)?;
let manager = env
.call_method(
context,
jni::jni_str!("getSystemService"),
jni::jni_sig!("(Ljava/lang/String;)Ljava/lang/Object;"),
&[(&bluetooth_service).into()],
)?
.l()?;
let adapter = env
.call_method(
manager,
jni::jni_str!("getAdapter"),
jni::jni_sig!("()Landroid/bluetooth/BluetoothAdapter;"),
&[],
)?
.l()?;
Ok(env.new_global_ref(&adapter)?)
}
}
fn register_receiver(
arg1: &jni_min_helper::BroadcastReceiver,
intent_str: &str,
) -> Option<::jni::refs::Global<JObject<'static>>> {
let java = jni_min_helper::jni_get_vm();
java.attach_current_thread(|env| {
let context = env.new_local_ref(JAVA_CONTEXT.get().unwrap().as_obj())?;
let mut args = Vec::new();
let intent_str = env.new_string(intent_str)?;
let arg2 = env.new_object(
jni::jni_str!("android/content/IntentFilter"),
jni::jni_sig!("(Ljava/lang/String;)V"),
&[(&intent_str).into()],
);
let arg2 = arg2.unwrap();
args.push(arg1.as_ref());
args.push(&arg2);
let args2: Vec<jni::objects::JValue> =
args.iter().map(|a| a.try_into().unwrap()).collect();
let e = env
.call_method(context, jni::jni_str!("registerReceiver"),
jni::jni_sig!("(Landroid/content/BroadcastReceiver;Landroid/content/IntentFilter;)Landroid/content/Intent;"),
args2.as_slice())?
.l()?;
env.new_global_ref(&e)
}).ok()
}
pub fn init_java(app: &AndroidApp) {
let java = jni_min_helper::jni_get_vm();
java.attach_current_thread(|env| {
let context = unsafe {
jni::objects::JObject::from_raw(env, app.activity_as_ptr() as *mut jni::sys::_jobject)
};
JAVA_CONTEXT
.set(Arc::new(env.new_global_ref(context)?))
.unwrap();
Ok::<_, jni::errors::Error>(())
}).unwrap();
}