diff --git a/Cargo.lock b/Cargo.lock index b586906e..2eb3f812 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -58,7 +58,7 @@ dependencies = [ "log", "serde", "serde-xml-rs", - "thiserror 2.0.18", + "thiserror", "tokio", "uuid", ] @@ -96,7 +96,7 @@ dependencies = [ "serde_bytes", "serde_json", "static_assertions", - "thiserror 2.0.18", + "thiserror", "tokio", "tokio-stream", "toml", @@ -117,12 +117,6 @@ version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" -[[package]] -name = "cesu8" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" - [[package]] name = "cfg-if" version = "1.0.4" @@ -336,6 +330,12 @@ dependencies = [ "wasip3", ] +[[package]] +name = "glob" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" + [[package]] name = "hashbrown" version = "0.14.5" @@ -401,7 +401,7 @@ checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" dependencies = [ "hermit-abi", "libc", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -419,25 +419,65 @@ version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" +[[package]] +name = "java-locator" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09c46c1fe465c59b1474e665e85e1256c3893dd00927b8d55f63b09044c1e64f" +dependencies = [ + "glob", +] + [[package]] name = "jni" -version = "0.19.0" +version = "0.22.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6df18c2e3db7e453d3c6ac5b3e9d5182664d28788126d39b91f2d1e22b017ec" +checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" dependencies = [ - "cesu8", + "cfg-if", "combine", + "java-locator", + "jni-macros", "jni-sys", + "libloading", "log", - "thiserror 1.0.69", + "simd_cesu8", + "thiserror", "walkdir", + "windows-link", +] + +[[package]] +name = "jni-macros" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3" +dependencies = [ + "proc-macro2", + "quote", + "rustc_version", + "simd_cesu8", + "syn", ] [[package]] name = "jni-sys" -version = "0.3.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" +checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" +dependencies = [ + "jni-sys-macros", +] + +[[package]] +name = "jni-sys-macros" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" +dependencies = [ + "quote", + "syn", +] [[package]] name = "js-sys" @@ -476,6 +516,16 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "libloading" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +dependencies = [ + "cfg-if", + "windows-link", +] + [[package]] name = "lock_api" version = "0.4.14" @@ -683,6 +733,15 @@ version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4c" +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + [[package]] name = "rustversion" version = "1.0.22" @@ -728,7 +787,7 @@ checksum = "cc2215ce3e6a77550b80a1c37251b7d294febaf42e36e21b7b411e0bf54d540d" dependencies = [ "log", "serde", - "thiserror 2.0.18", + "thiserror", "xml", ] @@ -784,6 +843,22 @@ dependencies = [ "serde_core", ] +[[package]] +name = "simd_cesu8" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94f90157bb87cddf702797c5dadfa0be7d266cdf49e22da2fcaa32eff75b2c33" +dependencies = [ + "rustc_version", + "simdutf8", +] + +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + [[package]] name = "slab" version = "0.4.12" @@ -832,33 +907,13 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "thiserror" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" -dependencies = [ - "thiserror-impl 1.0.69", -] - [[package]] name = "thiserror" version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ - "thiserror-impl 2.0.18", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" -dependencies = [ - "proc-macro2", - "quote", - "syn", + "thiserror-impl", ] [[package]] @@ -1103,7 +1158,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index c117aa21..3bbb65e3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,11 +45,11 @@ dbus = "0.9.10" bluez-async = "0.8.2" [target.'cfg(target_os = "android")'.dependencies] -jni = "0.19.0" +jni = "0.22" once_cell = "1.21.3" [target.'cfg(not(target_os = "android"))'.dependencies] -jni = { version = "0.19.0", optional = true } +jni = { version = "0.22", optional = true } once_cell = { version = "1.21.3", optional = true } [target.'cfg(target_vendor = "apple")'.dependencies] diff --git a/src/droidplug/adapter.rs b/src/droidplug/adapter.rs index 5e0a4f2b..af98ce09 100644 --- a/src/droidplug/adapter.rs +++ b/src/droidplug/adapter.rs @@ -1,7 +1,6 @@ -use super::jni_utils::exceptions::try_block; use super::{ jni::{ - global_jvm, + jvm, objects::{JScanFilter, JScanResult}, }, peripheral::{Peripheral, PeripheralId}, @@ -13,11 +12,9 @@ use crate::{ }; use async_trait::async_trait; use futures::stream::Stream; -use jni::objects::JClass; use jni::{ - JNIEnv, - objects::{GlobalRef, JObject, JString}, - strings::JavaStr, + Env, jni_sig, jni_str, + objects::{Global, JObject, JString}, sys::jboolean, }; use std::{ @@ -30,7 +27,7 @@ use std::{ #[derive(Clone)] pub struct Adapter { manager: Arc>, - internal: GlobalRef, + internal: Arc>>, } impl Debug for Adapter { @@ -43,30 +40,32 @@ impl Debug for Adapter { impl Adapter { pub(crate) fn new() -> Result { - let env = global_jvm().get_env()?; - - let obj = env.new_object( - "com/nonpolynomial/btleplug/android/impl/Adapter", - "()V", - &[], - )?; - let internal = env.new_global_ref(obj)?; - let adapter = Self { - manager: Arc::new(AdapterManager::default()), - internal, - }; - env.set_rust_field(obj, "handle", adapter.clone())?; - - Ok(adapter) + jvm()?.attach_current_thread(|env| { + let obj = env.new_object( + jni_str!("com/nonpolynomial/btleplug/android/impl/Adapter"), + jni_sig!("()V"), + &[], + )?; + let internal = Arc::new(env.new_global_ref(&obj)?); + let adapter = Self { + manager: Arc::new(AdapterManager::default()), + internal, + }; + unsafe { env.set_rust_field(&obj, jni_str!("handle"), adapter.clone()) }?; + + Ok(adapter) + }) } - pub fn report_scan_result(&self, scan_result: JObject) -> Result { - use std::convert::TryInto; - - let env = global_jvm().get_env()?; - let scan_result = JScanResult::from_env(&env, scan_result)?; + pub fn report_scan_result<'a>( + &self, + env: &mut Env<'a>, + scan_result: JObject<'a>, - let (addr, properties): (BDAddr, Option) = scan_result.try_into()?; + ) -> Result { + let scan_result = env.cast_local::(scan_result)?; + let (addr, properties): (BDAddr, Option) = + scan_result.to_peripheral_properties(env)?; match self.manager.peripheral(&PeripheralId(addr)) { Some(p) => match properties { @@ -74,10 +73,7 @@ impl Adapter { self.report_properties(&p, properties, false); Ok(p) } - None => { - //self.manager.emit(CentralEvent::DeviceDisconnected(addr)); - Err(Error::DeviceNotFound) - } + None => Err(Error::DeviceNotFound), }, None => match properties { Some(properties) => { @@ -91,10 +87,12 @@ impl Adapter { } fn add(&self, address: BDAddr) -> Result { - let env = global_jvm().get_env()?; - let peripheral = Peripheral::new(&env, self.internal.as_obj(), address)?; - self.manager.add_peripheral(peripheral.clone()); - Ok(peripheral) + jvm()?.attach_current_thread(|env| { + let local_adapter = env.new_local_ref(self.internal.as_obj())?; + let peripheral = Peripheral::new(env, local_adapter, address)?; + self.manager.add_peripheral(peripheral.clone()); + Ok(peripheral) + }) } fn report_properties( @@ -130,7 +128,6 @@ impl Central for Adapter { type Peripheral = Peripheral; async fn adapter_info(&self) -> Result { - // TODO: Get information about the adapter. Ok("Android".to_string()) } @@ -139,41 +136,49 @@ impl Central for Adapter { } async fn start_scan(&self, filter: ScanFilter) -> Result<()> { - let env = global_jvm().get_env()?; - let filter = JScanFilter::new(&env, filter)?; - try_block(&env, || { - env.call_method( - &self.internal, - "startScan", - "(Lcom/nonpolynomial/btleplug/android/impl/ScanFilter;)V", - &[filter.into()], - )?; - Ok(Ok(())) - }) - .catch( - JClass::from( - super::jni_utils::classcache::get_class( - "com/nonpolynomial/btleplug/android/impl/NoBluetoothAdapterException", - ) - .unwrap() - .as_obj(), - ), - |_ex| Ok(Err(Error::NoAdapterAvailable)), - ) - .catch("java/lang/RuntimeException", |ex| { - let msg = env - .call_method(ex, "getMessage", "()Ljava/lang/String;", &[])? - .l()?; - let msgstr: String = env.get_string(msg.into())?.into(); - Ok(Err(Error::RuntimeError(msgstr))) + jvm()?.attach_current_thread(|env| { + let filter = JScanFilter::new(env, filter)?; + let filter_obj: JObject = filter.into(); + match env.call_method( + self.internal.as_obj(), + jni_str!("startScan"), + jni_sig!("(Lcom/nonpolynomial/btleplug/android/impl/ScanFilter;)V"), + &[(&filter_obj).into()], + ) { + Ok(_) => Ok(()), + Err(jni::errors::Error::JavaException) => { + let ex = env.exception_occurred().unwrap(); + env.exception_clear(); + + let no_adapter_class = ::lookup_class( + env, + &Default::default(), + )?; + + if env.is_instance_of(&ex, &*no_adapter_class)? { + Err(Error::NoAdapterAvailable) + } else if env.is_instance_of(&ex, jni_str!("java/lang/RuntimeException"))? { + let msg = env + .call_method(&ex, jni_str!("getMessage"), jni_sig!("()Ljava/lang/String;"), &[])? + .l()?; + let jstr = env.cast_local::(msg)?; + let msgstr = String::from(jstr.mutf8_chars(env)?); + Err(Error::RuntimeError(msgstr)) + } else { + let _ = env.throw(&ex); + Err(jni::errors::Error::JavaException.into()) + } + } + Err(e) => Err(e.into()), + } }) - .result()? } async fn stop_scan(&self) -> Result<()> { - let env = global_jvm().get_env()?; - env.call_method(&self.internal, "stopScan", "()V", &[])?; - Ok(()) + jvm()?.attach_current_thread(|env| { + env.call_method(self.internal.as_obj(), jni_str!("stopScan"), jni_sig!("()V"), &[])?; + Ok(()) + }) } async fn peripherals(&self) -> Result> { @@ -200,27 +205,28 @@ impl Central for Adapter { } } -pub(crate) fn adapter_report_scan_result_internal( - env: &JNIEnv, - obj: JObject, - scan_result: JObject, +pub(crate) fn adapter_report_scan_result_internal<'a>( + env: &mut Env<'a>, + obj: &JObject, + scan_result: JObject<'a>, ) -> crate::Result<()> { - let adapter = env.get_rust_field::<_, _, Adapter>(obj, "handle")?; - adapter.report_scan_result(scan_result)?; + let adapter = unsafe { env.get_rust_field::<_, _, Adapter>(obj, jni_str!("handle")) }?; + let adapter_clone = adapter.clone(); + drop(adapter); + adapter_clone.report_scan_result(env, scan_result)?; Ok(()) } pub(crate) fn adapter_on_connection_state_changed_internal( - env: &JNIEnv, - obj: JObject, + env: &mut Env, + obj: &JObject, addr: JString, connected: jboolean, ) -> crate::Result<()> { - let adapter = env.get_rust_field::<_, _, Adapter>(obj, "handle")?; - let addr_str = JavaStr::from_env(env, addr)?; - let addr_str = addr_str.to_str().map_err(|e| Error::Other(e.into()))?; - let addr = BDAddr::from_str(addr_str)?; - adapter.manager.emit(if connected != 0 { + let addr_str = String::from(addr.mutf8_chars(env)?); + let addr = BDAddr::from_str(&addr_str)?; + let adapter = unsafe { env.get_rust_field::<_, _, Adapter>(obj, jni_str!("handle")) }?; + adapter.manager.emit(if connected { CentralEvent::DeviceConnected(PeripheralId(addr)) } else { CentralEvent::DeviceDisconnected(PeripheralId(addr)) diff --git a/src/droidplug/jni/mod.rs b/src/droidplug/jni/mod.rs index cafe87ad..3046b39c 100644 --- a/src/droidplug/jni/mod.rs +++ b/src/droidplug/jni/mod.rs @@ -1,129 +1,97 @@ pub mod objects; -use ::jni::{JNIEnv, JavaVM, NativeMethod, objects::JObject}; +use ::jni::{Env, EnvUnowned, NativeMethod, jni_str, native_method, objects::{JObject, Reference}}; use jni::{objects::JString, sys::jboolean}; -use once_cell::sync::OnceCell; use std::ffi::c_void; +use std::sync::Once; -static GLOBAL_JVM: OnceCell = OnceCell::new(); +static INIT: Once = Once::new(); -pub fn init(env: &JNIEnv) -> crate::Result<()> { - if let Ok(()) = GLOBAL_JVM.set(env.get_java_vm()?) { - env.register_native_methods( - "com/nonpolynomial/btleplug/android/impl/Adapter", +pub fn init(env: &mut Env) -> crate::Result<()> { + let mut init_result: crate::Result<()> = Ok(()); + INIT.call_once(|| { + if let Err(e) = init_inner(env) { + init_result = Err(e); + } + }); + init_result +} + +fn init_inner(env: &mut Env) -> crate::Result<()> { + // Seed the JavaVM singleton so JavaVM::singleton() works from any thread. + env.get_java_vm()?; + { + let adapter_class = + env.find_class(jni_str!("com/nonpolynomial/btleplug/android/impl/Adapter"))?; + unsafe { env.register_native_methods( + &adapter_class, &[ - NativeMethod { - name: "reportScanResult".into(), - sig: "(Landroid/bluetooth/le/ScanResult;)V".into(), - fn_ptr: adapter_report_scan_result as *mut c_void, - }, - NativeMethod { - name: "onConnectionStateChanged".into(), - sig: "(Ljava/lang/String;Z)V".into(), - fn_ptr: adapter_on_connection_state_changed as *mut c_void, + // Can't use native_method! here — JObject maps to Ljava/lang/Object; but the + // Java side declares the parameter as ScanResult. JNI requires exact signature match. + NativeMethod::from_raw_parts( + jni_str!("reportScanResult"), + jni_str!("(Landroid/bluetooth/le/ScanResult;)V"), + adapter_report_scan_result as *mut c_void, + ), + native_method! { + name = "onConnectionStateChanged", + sig = (addr: JString, connected: jboolean) -> (), + fn = adapter_on_connection_state_changed, }, ], - )?; - super::jni_utils::classcache::find_add_class( - env, - "com/nonpolynomial/btleplug/android/impl/Peripheral", - )?; - super::jni_utils::classcache::find_add_class( - env, - "com/nonpolynomial/btleplug/android/impl/ScanFilter", - )?; - super::jni_utils::classcache::find_add_class( - env, - "com/nonpolynomial/btleplug/android/impl/NotConnectedException", - )?; - super::jni_utils::classcache::find_add_class( - env, - "com/nonpolynomial/btleplug/android/impl/PermissionDeniedException", - )?; - super::jni_utils::classcache::find_add_class( - env, - "com/nonpolynomial/btleplug/android/impl/UnexpectedCallbackException", - )?; - super::jni_utils::classcache::find_add_class( - env, - "com/nonpolynomial/btleplug/android/impl/UnexpectedCharacteristicException", - )?; - super::jni_utils::classcache::find_add_class( - env, - "com/nonpolynomial/btleplug/android/impl/NoSuchCharacteristicException", - )?; - super::jni_utils::classcache::find_add_class( - env, - "com/nonpolynomial/btleplug/android/impl/NoBluetoothAdapterException", - )?; + )? }; + use objects::*; + use super::jni_utils::{ + future::{JFuture, JFutureException}, + ops::{JFnAdapter, JFnRunnableImpl, JFnBiFunctionImpl, JFnFunctionImpl}, + stream::{JStream, JStreamPoll}, + task::{JPollResult, JWaker}, + }; - // jni-utils class caching - super::jni_utils::classcache::find_add_class( - env, - "io/github/gedgygedgy/rust/future/Future", - )?; - super::jni_utils::classcache::find_add_class( - env, - "io/github/gedgygedgy/rust/future/FutureException", - )?; - super::jni_utils::classcache::find_add_class( - env, - "io/github/gedgygedgy/rust/ops/FnAdapter", - )?; - super::jni_utils::classcache::find_add_class( - env, - "io/github/gedgygedgy/rust/stream/Stream", - )?; - super::jni_utils::classcache::find_add_class( - env, - "io/github/gedgygedgy/rust/stream/StreamPoll", - )?; - super::jni_utils::classcache::find_add_class(env, "io/github/gedgygedgy/rust/task/Waker")?; - super::jni_utils::classcache::find_add_class( - env, - "io/github/gedgygedgy/rust/task/PollResult", - )?; - super::jni_utils::classcache::find_add_class( - env, - "io/github/gedgygedgy/rust/ops/FnRunnableImpl", - )?; - super::jni_utils::classcache::find_add_class( - env, - "io/github/gedgygedgy/rust/ops/FnBiFunctionImpl", - )?; - super::jni_utils::classcache::find_add_class( - env, - "io/github/gedgygedgy/rust/ops/FnFunctionImpl", - )?; + let loader = jni::objects::LoaderContext::default(); + ::lookup_class(env, &loader)?; + ::lookup_class(env, &loader)?; + ::lookup_class(env, &loader)?; + ::lookup_class(env, &loader)?; + ::lookup_class(env, &loader)?; + ::lookup_class(env, &loader)?; + ::lookup_class(env, &loader)?; + ::lookup_class(env, &loader)?; + ::lookup_class(env, &loader)?; + ::lookup_class(env, &loader)?; + ::lookup_class(env, &loader)?; + ::lookup_class(env, &loader)?; + ::lookup_class(env, &loader)?; + ::lookup_class(env, &loader)?; + ::lookup_class(env, &loader)?; + ::lookup_class(env, &loader)?; + ::lookup_class(env, &loader)?; + ::lookup_class(env, &loader)?; // FnAdapter native method registration - let fn_adapter_class = - env.auto_local(env.find_class("io/github/gedgygedgy/rust/ops/FnAdapter")?); - env.register_native_methods( - &fn_adapter_class, + let fn_adapter_class = ::lookup_class(env, &loader)?; + unsafe { env.register_native_methods( + &*fn_adapter_class, &[ - NativeMethod { - name: "callInternal".into(), - sig: - "(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;" - .into(), - fn_ptr: super::jni_utils::ops::fn_adapter_call_internal as *mut c_void, - }, - NativeMethod { - name: "closeInternal".into(), - sig: "()V".into(), - fn_ptr: super::jni_utils::ops::fn_adapter_close_internal as *mut c_void, - }, + NativeMethod::from_raw_parts( + jni_str!("callInternal"), + jni_str!("(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;"), + super::jni_utils::ops::fn_adapter_call_internal as *mut c_void, + ), + NativeMethod::from_raw_parts( + jni_str!("closeInternal"), + jni_str!("()V"), + super::jni_utils::ops::fn_adapter_close_internal as *mut c_void, + ), ], - )?; + )? }; + } Ok(()) } -pub fn global_jvm() -> &'static JavaVM { - GLOBAL_JVM.get().expect( - "Droidplug has not been initialized. Please initialize it with btleplug::platform::init().", - ) +pub fn jvm() -> crate::Result { + jni::JavaVM::singleton().map_err(|e| crate::Error::Other(Box::new(e))) } impl From<::jni::errors::Error> for crate::Error { @@ -132,16 +100,24 @@ impl From<::jni::errors::Error> for crate::Error { } } -extern "C" fn adapter_report_scan_result(env: JNIEnv, obj: JObject, scan_result: JObject) { - let _ = super::adapter::adapter_report_scan_result_internal(&env, obj, scan_result); +extern "C" fn adapter_report_scan_result<'local>( + mut env: EnvUnowned<'local>, + obj: JObject<'local>, + scan_result: JObject<'local>, +) { + let outcome = env.with_env(|env| { + let _ = super::adapter::adapter_report_scan_result_internal(env, &obj, scan_result); + Ok::<_, jni::errors::Error>(()) + }); + let _ = outcome.into_outcome(); } -extern "C" fn adapter_on_connection_state_changed( - env: JNIEnv, - obj: JObject, - addr: JString, +fn adapter_on_connection_state_changed<'local>( + env: &mut Env<'local>, + obj: JObject<'local>, + addr: JString<'local>, connected: jboolean, -) { - let _ = - super::adapter::adapter_on_connection_state_changed_internal(&env, obj, addr, connected); +) -> jni::errors::Result<()> { + let _ = super::adapter::adapter_on_connection_state_changed_internal(env, &obj, addr, connected); + Ok(()) } diff --git a/src/droidplug/jni/objects.rs b/src/droidplug/jni/objects.rs index 6e5e051a..3b53cfbe 100644 --- a/src/droidplug/jni/objects.rs +++ b/src/droidplug/jni/objects.rs @@ -1,347 +1,186 @@ use crate::droidplug::jni_utils::{future::JFuture, stream::JStream, uuid::JUuid}; use jni::{ - JNIEnv, + Env, + bind_java_type, errors::Result, - objects::{JClass, JList, JMap, JMethodID, JObject, JString}, - signature::{JavaType, Primitive}, - strings::JavaStr, + jni_sig, jni_str, + objects::{JObject, JString, Reference}, sys::jint, }; -use std::{collections::HashMap, convert::TryFrom, iter::Iterator}; +use std::{collections::HashMap, iter::Iterator}; use uuid::Uuid; use crate::api::{BDAddr, CharPropFlags, PeripheralProperties, ScanFilter}; -pub struct JPeripheral<'a: 'b, 'b> { - internal: JObject<'a>, - connect: JMethodID<'a>, - disconnect: JMethodID<'a>, - is_connected: JMethodID<'a>, - discover_services: JMethodID<'a>, - read: JMethodID<'a>, - write: JMethodID<'a>, - set_characteristic_notification: JMethodID<'a>, - get_notifications: JMethodID<'a>, - read_descriptor: JMethodID<'a>, - write_descriptor: JMethodID<'a>, - get_device_name: JMethodID<'a>, - request_mtu: JMethodID<'a>, - get_connection_parameters: JMethodID<'a>, - request_connection_priority: JMethodID<'a>, - read_remote_rssi: JMethodID<'a>, - env: &'b JNIEnv<'a>, +bind_java_type! { + pub JNotConnectedException => "com.nonpolynomial.btleplug.android.impl.NotConnectedException", } -impl<'a: 'b, 'b> ::std::ops::Deref for JPeripheral<'a, 'b> { - type Target = JObject<'a>; +bind_java_type! { + pub JPermissionDeniedException => "com.nonpolynomial.btleplug.android.impl.PermissionDeniedException", +} - fn deref(&self) -> &Self::Target { - &self.internal - } +bind_java_type! { + pub JUnexpectedCallbackException => "com.nonpolynomial.btleplug.android.impl.UnexpectedCallbackException", } -impl<'a: 'b, 'b> From> for JObject<'a> { - fn from(other: JPeripheral<'a, 'b>) -> JObject<'a> { - other.internal - } +bind_java_type! { + pub JUnexpectedCharacteristicException => "com.nonpolynomial.btleplug.android.impl.UnexpectedCharacteristicException", } -impl<'a: 'b, 'b> JPeripheral<'a, 'b> { - pub fn from_env(env: &'b JNIEnv<'a>, obj: JObject<'a>) -> Result { - //Self::from_env_impl(env, obj) - //let class = env.find_class("com/nonpolynomial/btleplug/android/impl/Peripheral")?; - //Self::from_env_impl(env, obj, class) - Self::from_env_impl(env, obj) - } +bind_java_type! { + pub JNoSuchCharacteristicException => "com.nonpolynomial.btleplug.android.impl.NoSuchCharacteristicException", +} - fn from_env_impl(env: &'b JNIEnv<'a>, obj: JObject<'a>) -> Result { - //let class = env.auto_local(class); - let class_static = crate::droidplug::jni_utils::classcache::get_class( - "com/nonpolynomial/btleplug/android/impl/Peripheral", - ) - .unwrap(); - let class = JClass::from(class_static.as_obj()); +bind_java_type! { + pub JNoBluetoothAdapterException => "com.nonpolynomial.btleplug.android.impl.NoBluetoothAdapterException", +} - let connect = env.get_method_id( - class, - "connect", - "()Lio/github/gedgygedgy/rust/future/Future;", - )?; - let disconnect = env.get_method_id( - class, - "disconnect", - "()Lio/github/gedgygedgy/rust/future/Future;", - )?; - let is_connected = env.get_method_id(class, "isConnected", "()Z")?; - let discover_services = env.get_method_id( - class, - "discoverServices", - "()Lio/github/gedgygedgy/rust/future/Future;", - )?; - let read = env.get_method_id( - class, - "read", - "(Ljava/util/UUID;)Lio/github/gedgygedgy/rust/future/Future;", - )?; - let write = env.get_method_id( - class, - "write", - "(Ljava/util/UUID;[BI)Lio/github/gedgygedgy/rust/future/Future;", - )?; - let set_characteristic_notification = env.get_method_id( - class, - "setCharacteristicNotification", - "(Ljava/util/UUID;Z)Lio/github/gedgygedgy/rust/future/Future;", - )?; - let get_notifications = env.get_method_id( - class, - "getNotifications", - "()Lio/github/gedgygedgy/rust/stream/Stream;", - )?; - let read_descriptor = env.get_method_id( - class, - "readDescriptor", - "(Ljava/util/UUID;Ljava/util/UUID;)Lio/github/gedgygedgy/rust/future/Future;", - )?; - let write_descriptor = env.get_method_id( - class, - "writeDescriptor", - "(Ljava/util/UUID;Ljava/util/UUID;[B)Lio/github/gedgygedgy/rust/future/Future;", - )?; - let get_device_name = env.get_method_id(class, "getDeviceName", "()Ljava/lang/String;")?; - let request_mtu = env.get_method_id( - class, - "requestMtu", - "(I)Lio/github/gedgygedgy/rust/future/Future;", - )?; - let get_connection_parameters = - env.get_method_id(class, "getConnectionParameters", "()[I")?; - let request_connection_priority = - env.get_method_id(class, "requestConnectionPriority", "(I)Z")?; - let read_remote_rssi = env.get_method_id( - class, - "readRemoteRssi", - "()Lio/github/gedgygedgy/rust/future/Future;", - )?; - Ok(Self { - internal: obj, - connect, - disconnect, - is_connected, - discover_services, - read, - write, - set_characteristic_notification, - get_notifications, - read_descriptor, - write_descriptor, - get_device_name, - request_mtu, - get_connection_parameters, - request_connection_priority, - read_remote_rssi, - env, - }) - } +bind_java_type! { + pub JScanFilterClass => "com.nonpolynomial.btleplug.android.impl.ScanFilter", +} - pub fn new(env: &'b JNIEnv<'a>, adapter: JObject<'a>, addr: BDAddr) -> Result { - // let class = env.find_class("com/nonpolynomial/btleplug/android/impl/Peripheral")?; +// JPeripheral: bind_java_type! for class definition only. Methods use domain-specific +// Java types (UUID, Future, Stream, byte[]) whose JNI signatures can't be expressed +// through the macro's Rust-to-JNI type mapping (JObject → Ljava/lang/Object; is wrong). +bind_java_type! { + pub JPeripheral => "com.nonpolynomial.btleplug.android.impl.Peripheral", + methods { + fn is_connected() -> jboolean, + fn request_connection_priority(priority: jint) -> jboolean, + }, +} + +impl JPeripheral<'_> { + pub fn create<'local>(env: &mut Env<'local>, adapter: JObject<'local>, addr: BDAddr) -> Result> { let addr_jstr = env.new_string(format!("{:X}", addr))?; + let class = JPeripheral::lookup_class(env, &Default::default())?; let obj = env.new_object( - JClass::from( - crate::droidplug::jni_utils::classcache::get_class( - "com/nonpolynomial/btleplug/android/impl/Peripheral", - ) - .unwrap() - .as_obj(), - ), - //class.as_obj(), - "(Lcom/nonpolynomial/btleplug/android/impl/Adapter;Ljava/lang/String;)V", - &[adapter.into(), addr_jstr.into()], + &*class, + jni_sig!("(Lcom/nonpolynomial/btleplug/android/impl/Adapter;Ljava/lang/String;)V"), + &[(&adapter).into(), (&addr_jstr).into()], )?; - //Self::from_env_impl(env, obj, class) - Self::from_env_impl(env, obj) - } - - pub fn connect(&self) -> Result> { - let future_obj = self - .env - .call_method_unchecked( - self.internal, - self.connect, - JavaType::Object("Lio/github/gedgygedgy/rust/future/Future;".to_string()), - &[], - )? - .l()?; - JFuture::from_env(self.env, future_obj) + env.cast_local::(obj) } +} - pub fn disconnect(&self) -> Result> { - let future_obj = self - .env - .call_method_unchecked( - self.internal, - self.disconnect, - JavaType::Object("Lio/github/gedgygedgy/rust/future/Future;".to_string()), - &[], - )? - .l()?; - JFuture::from_env(self.env, future_obj) +impl<'local> JPeripheral<'local> { + pub fn connect(&self, env: &mut Env<'local>) -> Result> { + let raw = env.call_method(self, jni_str!("connect"), + jni_sig!("()Lio/github/gedgygedgy/rust/future/Future;"), &[])?.l()?; + env.cast_local::(raw) } - pub fn is_connected(&self) -> Result { - self.env - .call_method_unchecked( - self.internal, - self.is_connected, - JavaType::Primitive(Primitive::Boolean), - &[], - )? - .z() + pub fn disconnect(&self, env: &mut Env<'local>) -> Result> { + let raw = env.call_method(self, jni_str!("disconnect"), + jni_sig!("()Lio/github/gedgygedgy/rust/future/Future;"), &[])?.l()?; + env.cast_local::(raw) } - pub fn discover_services(&self) -> Result> { - let future_obj = self - .env - .call_method_unchecked( - self.internal, - self.discover_services, - JavaType::Object("Lio/github/gedgygedgy/rust/future/Future;".to_string()), - &[], - )? - .l()?; - JFuture::from_env(self.env, future_obj) + pub fn discover_services(&self, env: &mut Env<'local>) -> Result> { + let raw = env.call_method(self, jni_str!("discoverServices"), + jni_sig!("()Lio/github/gedgygedgy/rust/future/Future;"), &[])?.l()?; + env.cast_local::(raw) } - pub fn read(&self, uuid: JUuid<'a, 'b>) -> Result> { - let future_obj = self - .env - .call_method_unchecked( - self.internal, - self.read, - JavaType::Object("Lio/github/gedgygedgy/rust/future/Future;".to_string()), - &[uuid.into()], - )? - .l()?; - JFuture::from_env(self.env, future_obj) + pub fn read(&self, env: &mut Env<'local>, uuid: &JUuid<'local>) -> Result> { + let raw = env.call_method(self, jni_str!("read"), + jni_sig!("(Ljava/util/UUID;)Lio/github/gedgygedgy/rust/future/Future;"), + &[uuid.into()])?.l()?; + env.cast_local::(raw) } pub fn write( &self, - uuid: JUuid<'a, 'b>, - data: JObject<'a>, + env: &mut Env<'local>, + uuid: &JUuid<'local>, + data: &JObject<'local>, write_type: jint, - ) -> Result> { - let future_obj = self - .env - .call_method_unchecked( - self.internal, - self.write, - JavaType::Object("Lio/github/gedgygedgy/rust/future/Future;".to_string()), - &[uuid.into(), data.into(), write_type.into()], - )? - .l()?; - JFuture::from_env(self.env, future_obj) + ) -> Result> { + let raw = env.call_method(self, jni_str!("write"), + jni_sig!("(Ljava/util/UUID;[BI)Lio/github/gedgygedgy/rust/future/Future;"), + &[uuid.into(), data.into(), write_type.into()])?.l()?; + env.cast_local::(raw) } pub fn set_characteristic_notification( &self, - uuid: JUuid<'a, 'b>, + env: &mut Env<'local>, + uuid: &JUuid<'local>, enable: bool, - ) -> Result> { - let future_obj = self - .env - .call_method_unchecked( - self.internal, - self.set_characteristic_notification, - JavaType::Object("Lio/github/gedgygedgy/rust/future/Future;".to_string()), - &[uuid.into(), enable.into()], - )? - .l()?; - JFuture::from_env(self.env, future_obj) + ) -> Result> { + let raw = env.call_method(self, jni_str!("setCharacteristicNotification"), + jni_sig!("(Ljava/util/UUID;Z)Lio/github/gedgygedgy/rust/future/Future;"), + &[uuid.into(), enable.into()])?.l()?; + env.cast_local::(raw) } - pub fn get_notifications(&self) -> Result> { - let stream_obj = self - .env - .call_method_unchecked( - self.internal, - self.get_notifications, - JavaType::Object("Lio/github/gedgygedgy/rust/stream/Stream;".to_string()), - &[], - )? - .l()?; - JStream::from_env(self.env, stream_obj) + pub fn get_notifications(&self, env: &mut Env<'local>) -> Result> { + let raw = env.call_method(self, jni_str!("getNotifications"), + jni_sig!("()Lio/github/gedgygedgy/rust/stream/Stream;"), &[])?.l()?; + env.cast_local::(raw) } pub fn read_descriptor( &self, - characteristic: JUuid<'a, 'b>, - uuid: JUuid<'a, 'b>, - ) -> Result> { - let future_obj = self - .env - .call_method_unchecked( - self.internal, - self.read_descriptor, - JavaType::Object("Lio/github/gedgygedgy/rust/future/Future;".to_string()), - &[characteristic.into(), uuid.into()], - )? - .l()?; - JFuture::from_env(self.env, future_obj) + env: &mut Env<'local>, + characteristic: &JUuid<'local>, + uuid: &JUuid<'local>, + ) -> Result> { + let raw = env.call_method(self, jni_str!("readDescriptor"), + jni_sig!("(Ljava/util/UUID;Ljava/util/UUID;)Lio/github/gedgygedgy/rust/future/Future;"), + &[characteristic.into(), uuid.into()])?.l()?; + env.cast_local::(raw) } - pub fn get_device_name(&self) -> Result> { - let obj = self - .env - .call_method_unchecked( - self.internal, - self.get_device_name, - JavaType::Object("Ljava/lang/String;".to_string()), - &[], - )? - .l()?; + pub fn write_descriptor( + &self, + env: &mut Env<'local>, + characteristic: &JUuid<'local>, + uuid: &JUuid<'local>, + data: &JObject<'local>, + ) -> Result> { + let raw = env.call_method(self, jni_str!("writeDescriptor"), + jni_sig!("(Ljava/util/UUID;Ljava/util/UUID;[B)Lio/github/gedgygedgy/rust/future/Future;"), + &[characteristic.into(), uuid.into(), data.into()])?.l()?; + env.cast_local::(raw) + } + + pub fn get_device_name(&self, env: &mut Env<'local>) -> Result> { + let obj = env.call_method(self, jni_str!("getDeviceName"), + jni_sig!("()Ljava/lang/String;"), &[])?.l()?; if obj.is_null() { Ok(None) } else { - let name_str = self.env.get_string(obj.into())?; - Ok(Some(name_str.into())) + let jstr = env.cast_local::(obj)?; + let name_str = jstr.mutf8_chars(env)?; + Ok(Some(String::from(name_str))) } } - pub fn request_mtu(&self, mtu: jint) -> Result> { - self.env - .call_method_unchecked( - self.internal, - self.request_mtu, - JavaType::Object("Lio/github/gedgygedgy/rust/future/Future;".to_string()), - &[mtu.into()], - )? - .l() + pub fn request_mtu(&self, env: &mut Env<'local>, mtu: jint) -> Result> { + let raw = env.call_method(self, jni_str!("requestMtu"), + jni_sig!("(I)Lio/github/gedgygedgy/rust/future/Future;"), + &[mtu.into()])?.l()?; + env.cast_local::(raw) } - pub fn get_connection_parameters(&self) -> Result> { - let obj = self - .env - .call_method_unchecked( - self.internal, - self.get_connection_parameters, - JavaType::Array(JavaType::Primitive(Primitive::Int).into()), - &[], - )? - .l()?; + pub fn get_connection_parameters( + &self, + env: &mut Env<'local>, + ) -> Result> { + let obj = env.call_method(self, jni_str!("getConnectionParameters"), + jni_sig!("()[I"), &[])?.l()?; if obj.is_null() { return Ok(None); } - let arr = obj.into_inner(); - let len = self.env.get_array_length(arr)?; + let arr = unsafe { jni::objects::JIntArray::from_raw(env, obj.into_raw()) }; + let len = arr.len(env)?; if len < 3 { return Ok(None); } let mut buf = [0i32; 3]; - self.env.get_int_array_region(arr, 0, &mut buf)?; - // interval is in 1.25ms units → microseconds: × 1250 - // timeout is in 10ms units → microseconds: × 10000 + arr.get_region(env, 0, &mut buf)?; Ok(Some(crate::api::ConnectionParameters { interval_us: (buf[0] as u32) * 1250, latency: buf[1] as u16, @@ -349,269 +188,113 @@ impl<'a: 'b, 'b> JPeripheral<'a, 'b> { })) } - pub fn read_remote_rssi(&self) -> Result> { - self.env - .call_method_unchecked( - self.internal, - self.read_remote_rssi, - JavaType::Object("Lio/github/gedgygedgy/rust/future/Future;".to_string()), - &[], - )? - .l() + pub fn read_remote_rssi(&self, env: &mut Env<'local>) -> Result> { + let raw = env.call_method(self, jni_str!("readRemoteRssi"), + jni_sig!("()Lio/github/gedgygedgy/rust/future/Future;"), &[])?.l()?; + env.cast_local::(raw) } - - pub fn request_connection_priority(&self, priority: jint) -> Result { - self.env - .call_method_unchecked( - self.internal, - self.request_connection_priority, - JavaType::Primitive(Primitive::Boolean), - &[priority.into()], - )? - .z() - } - - pub fn write_descriptor( - &self, - characteristic: JUuid<'a, 'b>, - uuid: JUuid<'a, 'b>, - data: JObject<'a>, - ) -> Result> { - let future_obj = self - .env - .call_method_unchecked( - self.internal, - self.write_descriptor, - JavaType::Object("Lio/github/gedgygedgy/rust/future/Future;".to_string()), - &[characteristic.into(), uuid.into(), data.into()], - )? - .l()?; - JFuture::from_env(self.env, future_obj) - } -} - -pub struct JBluetoothGattService<'a: 'b, 'b> { - internal: JObject<'a>, - get_uuid: JMethodID<'a>, - //is_primary: JMethodID<'a>, - get_characteristics: JMethodID<'a>, - env: &'b JNIEnv<'a>, } -impl<'a: 'b, 'b> JBluetoothGattService<'a, 'b> { - pub fn from_env(env: &'b JNIEnv<'a>, obj: JObject<'a>) -> Result { - let class = env.auto_local(env.find_class("android/bluetooth/BluetoothGattService")?); +// Android SDK types: class definition only, methods use manual JNI signatures +// because return types (UUID, List, byte[]) don't map to JObject. - let get_uuid = env.get_method_id(&class, "getUuid", "()Ljava/util/UUID;")?; - //let is_primary = env.get_method_id(&class, "isPrimary", "()Z;")?; - let get_characteristics = - env.get_method_id(&class, "getCharacteristics", "()Ljava/util/List;")?; - Ok(Self { - internal: obj, - get_uuid, - //is_primary, - get_characteristics, - env, - }) - } +bind_java_type! { + pub JBluetoothGattService => android.bluetooth.BluetoothGattService, +} +impl<'local> JBluetoothGattService<'local> { pub fn is_primary(&self) -> Result { - /* - self.env - .call_method_unchecked( - self.internal, - self.is_primary, - JavaType::Primitive(Primitive::Boolean), - &[], - )? - .z() - */ Ok(true) } - pub fn get_uuid(&self) -> Result { - let obj = self - .env - .call_method_unchecked( - self.internal, - self.get_uuid, - JavaType::Object("Ljava/util/UUID;".to_string()), - &[], - )? - .l()?; - let uuid_obj = JUuid::from_env(self.env, obj)?; - Ok(uuid_obj.as_uuid()?) + pub fn get_uuid(&self, env: &mut Env<'local>) -> Result { + let obj = env.call_method(self, jni_str!("getUuid"), + jni_sig!("()Ljava/util/UUID;"), &[])?.l()?; + let uuid_obj = env.cast_local::(obj)?; + uuid_obj.as_uuid(env) } - pub fn get_characteristics(&self) -> Result> { - let obj = self - .env - .call_method_unchecked( - self.internal, - self.get_characteristics, - JavaType::Object("Ljava/util/List;".to_string()), - &[], - )? - .l()?; - let chr_list = JList::from_env(self.env, obj)?; - let mut chr_vec = vec![]; - for chr in chr_list.iter()? { - chr_vec.push(JBluetoothGattCharacteristic::from_env(self.env, chr)?); + pub fn get_characteristics( + &self, + env: &mut Env<'local>, + ) -> Result>> { + let obj = env.call_method(self, jni_str!("getCharacteristics"), + jni_sig!("()Ljava/util/List;"), &[])?.l()?; + let size = env.call_method(&obj, jni_str!("size"), jni_sig!("()I"), &[])?.i()?; + let mut chr_vec = Vec::with_capacity(size as usize); + for i in 0..size { + let chr = env + .call_method(&obj, jni_str!("get"), jni_sig!("(I)Ljava/lang/Object;"), &[jni::objects::JValue::from(i)])? + .l()?; + chr_vec.push(env.cast_local::(chr)?); } Ok(chr_vec) } } -pub struct JBluetoothGattCharacteristic<'a: 'b, 'b> { - internal: JObject<'a>, - get_uuid: JMethodID<'a>, - get_properties: JMethodID<'a>, - get_value: JMethodID<'a>, - get_descriptors: JMethodID<'a>, - env: &'b JNIEnv<'a>, +bind_java_type! { + pub JBluetoothGattCharacteristic => android.bluetooth.BluetoothGattCharacteristic, + methods { + fn get_properties_raw { name = "getProperties", sig = () -> jint }, + }, } -impl<'a: 'b, 'b> JBluetoothGattCharacteristic<'a, 'b> { - pub fn from_env(env: &'b JNIEnv<'a>, obj: JObject<'a>) -> Result { - let class = - env.auto_local(env.find_class("android/bluetooth/BluetoothGattCharacteristic")?); - - let get_uuid = env.get_method_id(&class, "getUuid", "()Ljava/util/UUID;")?; - let get_properties = env.get_method_id(&class, "getProperties", "()I")?; - let get_descriptors = env.get_method_id(&class, "getDescriptors", "()Ljava/util/List;")?; - let get_value = env.get_method_id(&class, "getValue", "()[B")?; - Ok(Self { - internal: obj, - get_uuid, - get_properties, - get_value, - get_descriptors, - env, - }) - } - - pub fn get_uuid(&self) -> Result { - let obj = self - .env - .call_method_unchecked( - self.internal, - self.get_uuid, - JavaType::Object("Ljava/util/UUID;".to_string()), - &[], - )? - .l()?; - let uuid_obj = JUuid::from_env(self.env, obj)?; - Ok(uuid_obj.as_uuid()?) +impl<'local> JBluetoothGattCharacteristic<'local> { + pub fn get_uuid(&self, env: &mut Env<'local>) -> Result { + let obj = env.call_method(self, jni_str!("getUuid"), + jni_sig!("()Ljava/util/UUID;"), &[])?.l()?; + let uuid_obj = env.cast_local::(obj)?; + uuid_obj.as_uuid(env) } - pub fn get_properties(&self) -> Result { - let flags = self - .env - .call_method_unchecked( - self.internal, - self.get_properties, - JavaType::Primitive(Primitive::Int), - &[], - )? - .i()?; + pub fn get_properties(&self, env: &mut Env<'local>) -> Result { + let flags = self.get_properties_raw(env)?; Ok(CharPropFlags::from_bits_truncate(flags as u8)) } - pub fn get_value(&self) -> Result> { - let value = self - .env - .call_method_unchecked( - self.internal, - self.get_value, - JavaType::Array(JavaType::Primitive(Primitive::Byte).into()), - &[], - )? - .l()?; - crate::droidplug::jni_utils::arrays::byte_array_to_vec(self.env, value.into_inner()) + pub fn get_value(&self, env: &mut Env<'local>) -> Result> { + let value = env.call_method(self, jni_str!("getValue"), + jni_sig!("()[B"), &[])?.l()?; + let value_arr = unsafe { jni::objects::JByteArray::from_raw(env, value.into_raw()) }; + crate::droidplug::jni_utils::arrays::byte_array_to_vec(env, &value_arr) } - pub fn get_descriptors(&self) -> Result> { - let obj = self - .env - .call_method_unchecked( - self.internal, - self.get_descriptors, - JavaType::Object("Ljava/util/List;".to_string()), - &[], - )? - .l()?; - let desc_list = JList::from_env(self.env, obj)?; - let mut desc_vec = vec![]; - for desc in desc_list.iter()? { - desc_vec.push(JBluetoothGattDescriptor::from_env(self.env, desc)?); + pub fn get_descriptors( + &self, + env: &mut Env<'local>, + ) -> Result>> { + let obj = env.call_method(self, jni_str!("getDescriptors"), + jni_sig!("()Ljava/util/List;"), &[])?.l()?; + let size = env.call_method(&obj, jni_str!("size"), jni_sig!("()I"), &[])?.i()?; + let mut desc_vec = Vec::with_capacity(size as usize); + for i in 0..size { + let desc = env + .call_method(&obj, jni_str!("get"), jni_sig!("(I)Ljava/lang/Object;"), &[jni::objects::JValue::from(i)])? + .l()?; + desc_vec.push(env.cast_local::(desc)?); } Ok(desc_vec) } } -pub struct JBluetoothGattDescriptor<'a: 'b, 'b> { - internal: JObject<'a>, - get_uuid: JMethodID<'a>, - env: &'b JNIEnv<'a>, +bind_java_type! { + pub JBluetoothGattDescriptor => android.bluetooth.BluetoothGattDescriptor, } -impl<'a: 'b, 'b> JBluetoothGattDescriptor<'a, 'b> { - pub fn from_env(env: &'b JNIEnv<'a>, obj: JObject<'a>) -> Result { - let class = env.auto_local(env.find_class("android/bluetooth/BluetoothGattDescriptor")?); - - let get_uuid = env.get_method_id(&class, "getUuid", "()Ljava/util/UUID;")?; - Ok(Self { - internal: obj, - get_uuid, - env, - }) - } - - pub fn get_uuid(&self) -> Result { - let obj = self - .env - .call_method_unchecked( - self.internal, - self.get_uuid, - JavaType::Object("Ljava/util/UUID;".to_string()), - &[], - )? - .l()?; - let uuid_obj = JUuid::from_env(self.env, obj)?; - Ok(uuid_obj.as_uuid()?) +impl<'local> JBluetoothGattDescriptor<'local> { + pub fn get_uuid(&self, env: &mut Env<'local>) -> Result { + let obj = env.call_method(self, jni_str!("getUuid"), + jni_sig!("()Ljava/util/UUID;"), &[])?.l()?; + let uuid_obj = env.cast_local::(obj)?; + uuid_obj.as_uuid(env) } } -pub struct JBluetoothDevice<'a: 'b, 'b> { - internal: JObject<'a>, - get_address: JMethodID<'a>, - env: &'b JNIEnv<'a>, -} - -impl<'a: 'b, 'b> JBluetoothDevice<'a, 'b> { - pub fn from_env(env: &'b JNIEnv<'a>, obj: JObject<'a>) -> Result { - let class = env.auto_local(env.find_class("android/bluetooth/BluetoothDevice")?); - - let get_address = env.get_method_id(&class, "getAddress", "()Ljava/lang/String;")?; - Ok(Self { - internal: obj, - get_address, - env, - }) - } - - pub fn get_address(&self) -> Result> { - let obj = self - .env - .call_method_unchecked( - self.internal, - self.get_address, - JavaType::Object("Ljava/lang/String;".to_string()), - &[], - )? - .l()?; - Ok(obj.into()) - } +bind_java_type! { + pub JBluetoothDevice => android.bluetooth.BluetoothDevice, + methods { + fn get_address() -> JString, + }, } pub struct JScanFilter<'a> { @@ -619,27 +302,24 @@ pub struct JScanFilter<'a> { } impl<'a> JScanFilter<'a> { - pub fn new(env: &'a JNIEnv<'a>, filter: ScanFilter) -> Result { - let uuids = env.new_object_array( - filter.services.len() as i32, - env.find_class("java/lang/String")?, - JObject::null(), + pub fn new(env: &mut Env<'a>, filter: ScanFilter) -> Result { + let uuids = jni::objects::JObjectArray::::new( + env, + filter.services.len(), + &JString::default(), )?; for (idx, uuid) in filter.services.into_iter().enumerate() { let uuid_str = env.new_string(uuid.to_string())?; - env.set_object_array_element(uuids, idx as i32, uuid_str)?; + uuids.set_element(env, idx, &uuid_str)?; } + let class = ::lookup_class( + env, + &Default::default(), + )?; let obj = env.new_object( - JClass::from( - crate::droidplug::jni_utils::classcache::get_class( - "com/nonpolynomial/btleplug/android/impl/ScanFilter", - ) - .unwrap() - .as_obj(), - ), - //class.as_obj(), - "([Ljava/lang/String;)V", - &[uuids.into()], + &*class, + jni_sig!("([Ljava/lang/String;)V"), + &[(&uuids).into()], )?; Ok(Self { internal: obj }) } @@ -651,191 +331,138 @@ impl<'a> From> for JObject<'a> { } } -pub struct JScanResult<'a: 'b, 'b> { - internal: JObject<'a>, - get_device: JMethodID<'a>, - get_scan_record: JMethodID<'a>, - get_tx_power: JMethodID<'a>, - get_rssi: JMethodID<'a>, - env: &'b JNIEnv<'a>, +bind_java_type! { + pub JScanResult => android.bluetooth.le.ScanResult, + methods { + fn get_tx_power() -> jint, + fn get_rssi() -> jint, + }, } -impl<'a: 'b, 'b> JScanResult<'a, 'b> { - pub fn from_env(env: &'b JNIEnv<'a>, obj: JObject<'a>) -> Result { - let class = env.auto_local(env.find_class("android/bluetooth/le/ScanResult")?); - - let get_device = - env.get_method_id(&class, "getDevice", "()Landroid/bluetooth/BluetoothDevice;")?; - let get_scan_record = env.get_method_id( - &class, - "getScanRecord", - "()Landroid/bluetooth/le/ScanRecord;", - )?; - let get_tx_power = env.get_method_id(&class, "getTxPower", "()I")?; - let get_rssi = env.get_method_id(&class, "getRssi", "()I")?; - Ok(Self { - internal: obj, - get_device, - get_scan_record, - get_tx_power, - get_rssi, - env, - }) - } - - pub fn get_device(&self) -> Result> { - let obj = self - .env - .call_method_unchecked( - self.internal, - self.get_device, - JavaType::Object("Landroid/bluetooth/BluetoothDevice;".to_string()), - &[], - )? - .l()?; - JBluetoothDevice::from_env(self.env, obj) +impl<'local> JScanResult<'local> { + pub fn get_device(&self, env: &mut Env<'local>) -> Result> { + let obj = env.call_method(self, jni_str!("getDevice"), + jni_sig!("()Landroid/bluetooth/BluetoothDevice;"), &[])?.l()?; + env.cast_local::(obj) } - pub fn get_scan_record(&self) -> Result> { - let obj = self - .env - .call_method_unchecked( - self.internal, - self.get_scan_record, - JavaType::Object("Landroid/bluetooth/le/ScanRecord;".to_string()), - &[], - )? - .l()?; - JScanRecord::from_env(self.env, obj) + pub fn get_scan_record(&self, env: &mut Env<'local>) -> Result> { + env.call_method(self, jni_str!("getScanRecord"), + jni_sig!("()Landroid/bluetooth/le/ScanRecord;"), &[])?.l() } - pub fn get_tx_power(&self) -> Result { - self.env - .call_method_unchecked( - self.internal, - self.get_tx_power, - JavaType::Primitive(Primitive::Int), - &[], - )? - .i() - } - - pub fn get_rssi(&self) -> Result { - self.env - .call_method_unchecked( - self.internal, - self.get_rssi, - JavaType::Primitive(Primitive::Int), - &[], - )? - .i() - } -} - -impl<'a: 'b, 'b> TryFrom> for (BDAddr, Option) { - type Error = crate::Error; - - fn try_from(result: JScanResult<'a, 'b>) -> std::result::Result { + pub fn to_peripheral_properties( + &self, + env: &mut Env<'local>, + ) -> std::result::Result<(BDAddr, Option), crate::Error> { use std::str::FromStr; - let device = result.get_device()?; - - let addr_obj = device.get_address()?; - let addr_str = JavaStr::from_env(result.env, addr_obj)?; - let addr = BDAddr::from_str( - addr_str - .to_str() - .map_err(|e| Self::Error::Other(e.into()))?, - )?; + let device = self.get_device(env)?; + let addr_jstr = device.get_address(env)?; + let addr_str = String::from(addr_jstr.mutf8_chars(env)?); + let addr = BDAddr::from_str(&addr_str)?; - let record = result.get_scan_record()?; - let record_obj: &JObject = &record; - let properties = if result - .env - .is_same_object(record_obj.clone(), JObject::null())? - { + let record_obj = self.get_scan_record(env)?; + let properties = if record_obj.is_null() { None } else { - let device_name_obj = record.get_device_name()?; - let device_name = if result - .env - .is_same_object(device_name_obj, JObject::null())? - { + let record = env.cast_local::(record_obj)?; + let device_name_obj = record.get_device_name(env)?; + let device_name = if env.is_same_object(&device_name_obj, JObject::null())? { None } else { - let device_name_str = JavaStr::from_env(result.env, device_name_obj)?; - // On Android, there is a chance that a device name may not actually be valid UTF-8. - // We're given the full buffer, regardless of if it's just UTF-8 characters, - // possibly c str with null characters, or whatever. We should try UTF-8 first, if - // that doesn't work out, see if there's a null termination character in it and try - // parsing that. + let device_name_jstr = env.cast_local::(device_name_obj)?; + let device_name_str = String::from(device_name_jstr.mutf8_chars(env)?); Some( - String::from_utf8_lossy(device_name_str.to_bytes()) + device_name_str .chars() .filter(|&c| c != '\u{fffd}') .collect(), ) }; - let tx_power_level = result.get_tx_power()?; - const TX_POWER_NOT_PRESENT: jint = 127; // from ScanResult documentation + let tx_power_level = self.get_tx_power(env)?; + const TX_POWER_NOT_PRESENT: jint = 127; let tx_power_level = if tx_power_level == TX_POWER_NOT_PRESENT { None } else { Some(tx_power_level as i16) }; - let rssi = Some(result.get_rssi()? as i16); + let rssi = Some(self.get_rssi(env)? as i16); - let manufacturer_specific_data_array = record.get_manufacturer_specific_data()?; - let manufacturer_specific_data_obj: &JObject = &manufacturer_specific_data_array; + let mfr_data_obj = record.get_manufacturer_specific_data(env)?; let mut manufacturer_data = HashMap::new(); - if !result - .env - .is_same_object(manufacturer_specific_data_obj.clone(), JObject::null())? - { - for item in manufacturer_specific_data_array.iter() { - let (index, data) = item?; - - let index = index as u16; - let data = crate::droidplug::jni_utils::arrays::byte_array_to_vec( - result.env, - data.into_inner(), - )?; - manufacturer_data.insert(index, data); + if !mfr_data_obj.is_null() { + let sparse_arr = env.cast_local::(mfr_data_obj)?; + let size = sparse_arr.size(env)?; + for i in 0..size { + let key = sparse_arr.key_at(env, i)?; + let value = sparse_arr.value_at(env, i)?; + let value_arr = + unsafe { jni::objects::JByteArray::from_raw(env, value.into_raw()) }; + let data = + crate::droidplug::jni_utils::arrays::byte_array_to_vec(env, &value_arr)?; + manufacturer_data.insert(key as u16, data); } } - let service_data_map = record.get_service_data()?; - let service_data_obj: &JObject = &service_data_map; + let service_data_obj = record.get_service_data(env)?; let mut service_data = HashMap::new(); - if !result - .env - .is_same_object(service_data_obj.clone(), JObject::null())? - { - for (key, value) in service_data_map.iter()? { - let uuid = JParcelUuid::from_env(result.env, key)? - .get_uuid()? - .as_uuid()?; - let data = crate::droidplug::jni_utils::arrays::byte_array_to_vec( - result.env, - value.into_inner(), - )?; + if !env.is_same_object(&service_data_obj, JObject::null())? { + let entry_set = env + .call_method(&service_data_obj, jni_str!("entrySet"), jni_sig!("()Ljava/util/Set;"), &[])? + .l()?; + let iter_obj = env + .call_method( + &entry_set, + jni_str!("iterator"), + jni_sig!("()Ljava/util/Iterator;"), + &[], + )? + .l()?; + while env + .call_method(&iter_obj, jni_str!("hasNext"), jni_sig!("()Z"), &[])? + .z()? + { + let entry = env + .call_method(&iter_obj, jni_str!("next"), jni_sig!("()Ljava/lang/Object;"), &[])? + .l()?; + let key = env + .call_method(&entry, jni_str!("getKey"), jni_sig!("()Ljava/lang/Object;"), &[])? + .l()?; + let value = env + .call_method(&entry, jni_str!("getValue"), jni_sig!("()Ljava/lang/Object;"), &[])? + .l()?; + let parcel_uuid = env.cast_local::(key)?; + let juuid = parcel_uuid.get_uuid(env)?; + let uuid = juuid.as_uuid(env)?; + let value_arr = + unsafe { jni::objects::JByteArray::from_raw(env, value.into_raw()) }; + let data = + crate::droidplug::jni_utils::arrays::byte_array_to_vec(env, &value_arr)?; service_data.insert(uuid, data); } } - let services_list = record.get_service_uuids()?; - let services_obj: &JObject = &services_list; + let services_obj = record.get_service_uuids(env)?; let mut services = Vec::new(); - if !result - .env - .is_same_object(services_obj.clone(), JObject::null())? - { - for obj in services_list.iter()? { - let uuid = JParcelUuid::from_env(result.env, obj)? - .get_uuid()? - .as_uuid()?; + if !env.is_same_object(&services_obj, JObject::null())? { + let size = env + .call_method(&services_obj, jni_str!("size"), jni_sig!("()I"), &[])? + .i()?; + for i in 0..size { + let obj = env + .call_method( + &services_obj, + jni_str!("get"), + jni_sig!("(I)Ljava/lang/Object;"), + &[jni::objects::JValue::from(i)], + )? + .l()?; + let parcel_uuid = env.cast_local::(obj)?; + let juuid = parcel_uuid.get_uuid(env)?; + let uuid = juuid.as_uuid(env)?; services.push(uuid); } } @@ -857,253 +484,52 @@ impl<'a: 'b, 'b> TryFrom> for (BDAddr, Option { - internal: JObject<'a>, - get_device_name: JMethodID<'a>, - get_tx_power_level: JMethodID<'a>, - get_manufacturer_specific_data: JMethodID<'a>, - get_service_data: JMethodID<'a>, - get_service_uuids: JMethodID<'a>, - env: &'b JNIEnv<'a>, -} - -impl<'a: 'b, 'b> From> for JObject<'a> { - fn from(scan_record: JScanRecord<'a, 'b>) -> Self { - scan_record.internal - } -} - -impl<'a: 'b, 'b> ::std::ops::Deref for JScanRecord<'a, 'b> { - type Target = JObject<'a>; - - fn deref(&self) -> &Self::Target { - &self.internal - } +bind_java_type! { + pub JScanRecord => android.bluetooth.le.ScanRecord, + methods { + fn get_tx_power_level() -> jint, + }, } -impl<'a: 'b, 'b> JScanRecord<'a, 'b> { - pub fn from_env(env: &'b JNIEnv<'a>, obj: JObject<'a>) -> Result { - let class = env.auto_local(env.find_class("android/bluetooth/le/ScanRecord")?); - - let get_device_name = env.get_method_id(&class, "getDeviceName", "()Ljava/lang/String;")?; - let get_tx_power_level = env.get_method_id(&class, "getTxPowerLevel", "()I")?; - let get_manufacturer_specific_data = env.get_method_id( - &class, - "getManufacturerSpecificData", - "()Landroid/util/SparseArray;", - )?; - let get_service_data = env.get_method_id(&class, "getServiceData", "()Ljava/util/Map;")?; - let get_service_uuids = - env.get_method_id(&class, "getServiceUuids", "()Ljava/util/List;")?; - Ok(Self { - internal: obj, - get_device_name, - get_tx_power_level, - get_manufacturer_specific_data, - get_service_data, - get_service_uuids, - env, - }) +impl<'local> JScanRecord<'local> { + pub fn get_device_name(&self, env: &mut Env<'local>) -> Result> { + env.call_method(self, jni_str!("getDeviceName"), + jni_sig!("()Ljava/lang/String;"), &[])?.l() } - pub fn get_device_name(&self) -> Result> { - let obj = self - .env - .call_method_unchecked( - self.internal, - self.get_device_name, - JavaType::Object("Ljava/lang/String;".to_string()), - &[], - )? - .l()?; - Ok(obj.into()) + pub fn get_manufacturer_specific_data(&self, env: &mut Env<'local>) -> Result> { + env.call_method(self, jni_str!("getManufacturerSpecificData"), + jni_sig!("()Landroid/util/SparseArray;"), &[])?.l() } - pub fn get_tx_power_level(&self) -> Result { - self.env - .call_method_unchecked( - self.internal, - self.get_tx_power_level, - JavaType::Primitive(Primitive::Int), - &[], - )? - .i() + pub fn get_service_data(&self, env: &mut Env<'local>) -> Result> { + env.call_method(self, jni_str!("getServiceData"), + jni_sig!("()Ljava/util/Map;"), &[])?.l() } - pub fn get_manufacturer_specific_data(&self) -> Result> { - let obj = self - .env - .call_method_unchecked( - self.internal, - self.get_manufacturer_specific_data, - JavaType::Object("Landroid/util/SparseArray;".to_string()), - &[], - )? - .l()?; - JSparseArray::from_env(self.env, obj) - } - - pub fn get_service_data(&self) -> Result> { - let obj = self - .env - .call_method_unchecked( - self.internal, - self.get_service_data, - JavaType::Object("Ljava/util/Map;".to_string()), - &[], - )? - .l()?; - JMap::from_env(self.env, obj) - } - - pub fn get_service_uuids(&self) -> Result> { - let obj = self - .env - .call_method_unchecked( - self.internal, - self.get_service_uuids, - JavaType::Object("Ljava/util/List;".to_string()), - &[], - )? - .l()?; - JList::from_env(self.env, obj) + pub fn get_service_uuids(&self, env: &mut Env<'local>) -> Result> { + env.call_method(self, jni_str!("getServiceUuids"), + jni_sig!("()Ljava/util/List;"), &[])?.l() } } -#[derive(Clone)] -pub struct JSparseArray<'a: 'b, 'b> { - internal: JObject<'a>, - size: JMethodID<'a>, - key_at: JMethodID<'a>, - value_at: JMethodID<'a>, - env: &'b JNIEnv<'a>, +bind_java_type! { + pub JSparseArray => android.util.SparseArray, + methods { + fn size() -> jint, + fn key_at(index: jint) -> jint, + fn value_at(index: jint) -> JObject, + }, } -impl<'a: 'b, 'b> From> for JObject<'a> { - fn from(sparse_array: JSparseArray<'a, 'b>) -> Self { - sparse_array.internal - } +bind_java_type! { + pub JParcelUuid => android.os.ParcelUuid, } -impl<'a: 'b, 'b> ::std::ops::Deref for JSparseArray<'a, 'b> { - type Target = JObject<'a>; - - fn deref(&self) -> &Self::Target { - &self.internal - } -} - -impl<'a: 'b, 'b> JSparseArray<'a, 'b> { - pub fn from_env(env: &'b JNIEnv<'a>, obj: JObject<'a>) -> Result { - let class = env.auto_local(env.find_class("android/util/SparseArray")?); - - let size = env.get_method_id(&class, "size", "()I")?; - let key_at = env.get_method_id(&class, "keyAt", "(I)I")?; - let value_at = env.get_method_id(&class, "valueAt", "(I)Ljava/lang/Object;")?; - Ok(Self { - internal: obj, - size, - key_at, - value_at, - env, - }) - } - - pub fn size(&self) -> Result { - self.env - .call_method_unchecked( - self.internal, - self.size, - JavaType::Primitive(Primitive::Int), - &[], - )? - .i() - } - - pub fn key_at(&self, index: jint) -> Result { - self.env - .call_method_unchecked( - self.internal, - self.key_at, - JavaType::Primitive(Primitive::Int), - &[index.into()], - )? - .i() - } - - pub fn value_at(&self, index: jint) -> Result> { - self.env - .call_method_unchecked( - self.internal, - self.value_at, - JavaType::Object("Ljava/lang/Object;".to_string()), - &[index.into()], - )? - .l() - } - - pub fn iter(&self) -> JSparseArrayIter<'a, 'b> { - JSparseArrayIter { - internal: self.clone(), - index: 0, - } - } -} - -pub struct JSparseArrayIter<'a: 'b, 'b> { - internal: JSparseArray<'a, 'b>, - index: jint, -} - -impl<'a: 'b, 'b> JSparseArrayIter<'a, 'b> { - fn next_internal(&mut self) -> Result)>> { - let size = self.internal.size()?; - Ok(if self.index >= size { - None - } else { - let key = self.internal.key_at(self.index)?; - let value = self.internal.value_at(self.index)?; - self.index += 1; - Some((key, value)) - }) - } -} - -impl<'a: 'b, 'b> Iterator for JSparseArrayIter<'a, 'b> { - type Item = Result<(jint, JObject<'a>)>; - - fn next(&mut self) -> Option { - self.next_internal().transpose() - } -} -pub struct JParcelUuid<'a: 'b, 'b> { - internal: JObject<'a>, - get_uuid: JMethodID<'a>, - env: &'b JNIEnv<'a>, -} - -impl<'a: 'b, 'b> JParcelUuid<'a, 'b> { - pub fn from_env(env: &'b JNIEnv<'a>, obj: JObject<'a>) -> Result { - let class = env.auto_local(env.find_class("android/os/ParcelUuid")?); - - let get_uuid = env.get_method_id(&class, "getUuid", "()Ljava/util/UUID;")?; - Ok(Self { - internal: obj, - get_uuid, - env, - }) - } - - pub fn get_uuid(&self) -> Result> { - let obj = self - .env - .call_method_unchecked( - self.internal, - self.get_uuid, - JavaType::Object("Ljava/util/UUID;".to_string()), - &[], - )? - .l()?; - JUuid::from_env(self.env, obj) +impl<'local> JParcelUuid<'local> { + pub fn get_uuid(&self, env: &mut Env<'local>) -> Result> { + let obj = env.call_method(self, jni_str!("getUuid"), + jni_sig!("()Ljava/util/UUID;"), &[])?.l()?; + env.cast_local::(obj) } } diff --git a/src/droidplug/jni_utils/arrays.rs b/src/droidplug/jni_utils/arrays.rs index e471fb9d..eb208be1 100644 --- a/src/droidplug/jni_utils/arrays.rs +++ b/src/droidplug/jni_utils/arrays.rs @@ -1,25 +1,24 @@ use jni::{ - JNIEnv, + Env, errors::Result, - sys::{jbyte, jbyteArray, jint}, + objects::JByteArray, + sys::jbyte, }; use std::slice; -/// Create a new Java byte array from the given slice. -pub fn slice_to_byte_array<'a, 'b>(env: &'a JNIEnv<'a>, slice: &'b [u8]) -> Result { - let obj = env.new_byte_array(slice.len() as jint)?; +pub fn slice_to_byte_array<'local>(env: &mut Env<'local>, slice: &[u8]) -> Result> { + let obj = env.new_byte_array(slice.len())?; let slice = unsafe { &*(slice as *const [u8] as *const [jbyte]) }; - env.set_byte_array_region(obj, 0, slice)?; + obj.set_region(env, 0, slice)?; Ok(obj) } -/// Get a [`Vec`] of bytes from the given Java byte array. -pub fn byte_array_to_vec<'a>(env: &'a JNIEnv<'a>, obj: jbyteArray) -> Result> { - let size = env.get_array_length(obj)? as usize; +pub fn byte_array_to_vec(env: &Env, array: &JByteArray) -> Result> { + let size = array.len(env)?; let mut result = Vec::with_capacity(size); unsafe { let result_slice = slice::from_raw_parts_mut(result.as_mut_ptr() as *mut jbyte, size); - env.get_byte_array_region(obj, 0, result_slice)?; + array.get_region(env, 0, result_slice)?; result.set_len(size); } Ok(result) @@ -31,24 +30,27 @@ mod test { #[test] fn test_slice_to_byte_array() { - test_utils::JVM_ENV.with(|env| { + test_utils::with_env(|env| { let obj = super::slice_to_byte_array(env, &[1, 2, 3, 4, 5]).unwrap(); - assert_eq!(env.get_array_length(obj).unwrap(), 5); + assert_eq!(obj.len(env).unwrap(), 5); let mut bytes = [0i8; 5]; - env.get_byte_array_region(obj, 0, &mut bytes).unwrap(); + obj.get_region(env, 0, &mut bytes).unwrap(); assert_eq!(bytes, [1, 2, 3, 4, 5]); - }); + Ok(()) + }).unwrap(); } #[test] fn test_byte_array_to_vec() { - test_utils::JVM_ENV.with(|env| { + test_utils::with_env(|env| { let obj = env.new_byte_array(5).unwrap(); - env.set_byte_array_region(obj, 0, &[1, 2, 3, 4, 5]).unwrap(); + obj.set_region(env, 0, &[1, 2, 3, 4, 5]) + .unwrap(); - let vec = super::byte_array_to_vec(env, obj).unwrap(); + let vec = super::byte_array_to_vec(env, &obj).unwrap(); assert_eq!(vec, vec![1, 2, 3, 4, 5]); - }); + Ok(()) + }).unwrap(); } } diff --git a/src/droidplug/jni_utils/classcache.rs b/src/droidplug/jni_utils/classcache.rs deleted file mode 100644 index 3b0f63ff..00000000 --- a/src/droidplug/jni_utils/classcache.rs +++ /dev/null @@ -1,22 +0,0 @@ -use dashmap::DashMap; -use jni::{JNIEnv, errors::Result, objects::GlobalRef}; -use once_cell::sync::OnceCell; - -static CLASSCACHE: OnceCell> = OnceCell::new(); - -pub fn find_add_class(env: &JNIEnv, classname: &str) -> Result<()> { - let cache = CLASSCACHE.get_or_init(|| DashMap::new()); - cache.insert( - classname.to_owned(), - env.new_global_ref(env.find_class(classname).unwrap()) - .unwrap(), - ); - Ok(()) -} - -pub fn get_class(classname: &str) -> Option { - let cache = CLASSCACHE.get_or_init(|| DashMap::new()); - cache - .get(classname) - .and_then(|pair| Some(pair.value().clone())) -} diff --git a/src/droidplug/jni_utils/exceptions.rs b/src/droidplug/jni_utils/exceptions.rs index 6f014d52..28afbc88 100644 --- a/src/droidplug/jni_utils/exceptions.rs +++ b/src/droidplug/jni_utils/exceptions.rs @@ -1,20 +1,19 @@ use jni::{ - JNIEnv, + Env, descriptors::Desc, errors::Error, + jni_str, jni_sig, objects::{JClass, JObject, JThrowable}, }; use std::{ any::Any, - convert::TryFrom, panic::{UnwindSafe, catch_unwind, resume_unwind}, sync::MutexGuard, }; /// Result from [`try_block`]. This object can be chained into /// [`catch`](TryCatchResult::catch) calls to catch exceptions. -pub struct TryCatchResult<'a: 'b, 'b, T> { - env: &'b JNIEnv<'a>, +pub struct TryCatchResult { try_result: Result, Error>, catch_result: Option>, } @@ -22,68 +21,67 @@ pub struct TryCatchResult<'a: 'b, 'b, T> { /// Attempt to execute a block of JNI code. If the code causes an exception /// to be thrown, it will be stored in the resulting [`TryCatchResult`] for /// matching with [`catch`](TryCatchResult::catch). -pub fn try_block<'a: 'b, 'b, T>( - env: &'b JNIEnv<'a>, - block: impl FnOnce() -> Result, -) -> TryCatchResult<'a, 'b, T> { +pub fn try_block( + env: &mut Env, + block: impl FnOnce(&mut Env) -> Result, +) -> TryCatchResult { TryCatchResult { - env, try_result: (|| { - if env.exception_check()? { + if env.exception_check() { Err(Error::JavaException) } else { - Ok(block()) + Ok(block(env)) } })(), catch_result: None, } } -impl<'a: 'b, 'b, T> TryCatchResult<'a, 'b, T> { - pub fn catch( +impl TryCatchResult { + pub fn catch<'local>( self, - class: impl Desc<'a, JClass<'a>>, - block: impl FnOnce(JThrowable<'a>) -> Result, + env: &mut Env<'local>, + class: impl Desc<'local, JClass<'local>>, + block: impl FnOnce(&mut Env<'local>, JThrowable<'local>) -> Result, ) -> Self { match (self.try_result, self.catch_result) { (Err(e), _) => Self { - env: self.env, try_result: Err(e), catch_result: None, }, (Ok(Ok(r)), _) => Self { - env: self.env, try_result: Ok(Ok(r)), catch_result: None, }, (Ok(Err(e)), Some(r)) => Self { - env: self.env, try_result: Ok(Err(e)), catch_result: Some(r), }, (Ok(Err(Error::JavaException)), None) => { - let env = self.env; let catch_result = (|| { - if env.exception_check()? { - let ex = env.exception_occurred()?; - let _auto_local = env.auto_local(ex.clone()); - env.exception_clear()?; - if env.is_instance_of(ex, class)? { - return block(ex).map(|o| Some(o)); + if env.exception_check() { + if let Some(ex) = env.exception_occurred() { + env.exception_clear(); + if env.is_instance_of(&ex, class)? { + return block(env, ex).map(|o| Some(o)); + } + // Rethrow — throw() returns Err(JavaException) on success + match env.throw(&ex) { + Err(Error::JavaException) => {} + Err(e) => return Err(e), + Ok(()) => {} + } } - env.throw(ex)?; } Ok(None) })() .transpose(); Self { - env, try_result: Ok(Err(Error::JavaException)), catch_result, } } (Ok(Err(e)), None) => Self { - env: self.env, try_result: Ok(Err(e)), catch_result: None, }, @@ -102,62 +100,59 @@ impl<'a: 'b, 'b, T> TryCatchResult<'a, 'b, T> { /// Wrapper for [`JObject`]s that implement /// `io.github.gedgygedgy.rust.panic.PanicException`. -pub struct JPanicException<'a: 'b, 'b> { +pub struct JPanicException<'a> { internal: JThrowable<'a>, - env: &'b JNIEnv<'a>, } -impl<'a: 'b, 'b> JPanicException<'a, 'b> { - pub fn from_env(env: &'b JNIEnv<'a>, obj: JThrowable<'a>) -> Result { - Ok(Self { internal: obj, env }) +impl<'a> JPanicException<'a> { + pub fn from_env(obj: JThrowable<'a>) -> Self { + Self { internal: obj } } - pub fn new(env: &'b JNIEnv<'a>, any: Box) -> Result { + pub fn new(env: &mut Env<'a>, any: Box) -> Result { let msg = if let Some(s) = any.downcast_ref::<&str>() { - env.new_string(s)? + env.new_string(s)?.into() } else if let Some(s) = any.downcast_ref::() { - env.new_string(s)? + env.new_string(s)?.into() } else { - JObject::null().into() + JObject::null() }; let obj = env.new_object( - "io/github/gedgygedgy/rust/panic/PanicException", - "(Ljava/lang/String;)V", - &[msg.into()], + jni_str!("io/github/gedgygedgy/rust/panic/PanicException"), + jni_sig!("(Ljava/lang/String;)V"), + &[(&msg).into()], )?; - env.set_rust_field(obj, "any", any)?; - Self::from_env(env, obj.into()) + unsafe { env.set_rust_field(&obj, jni_str!("any"), any) }?; + let throwable = env.cast_local::(obj)?; + Ok(Self { + internal: throwable, + }) } - pub fn get(&self) -> Result>, Error> { - self.env.get_rust_field(self.internal, "any") + pub fn get<'b>( + &self, + env: &'b mut Env, + ) -> Result>, Error> { + unsafe { env.get_rust_field(&self.internal, jni_str!("any")) } } - pub fn take(&self) -> Result, Error> { - self.env.take_rust_field(self.internal, "any") + pub fn take(&self, env: &mut Env) -> Result, Error> { + unsafe { env.take_rust_field(&self.internal, jni_str!("any")) } } - pub fn resume_unwind(&self) -> Result<(), Error> { - resume_unwind(self.take()?); + pub fn resume_unwind(&self, env: &mut Env) -> Result<(), Error> { + resume_unwind(self.take(env)?); } } -impl<'a: 'b, 'b> TryFrom> for Box { - type Error = Error; - - fn try_from(ex: JPanicException<'a, 'b>) -> Result { - ex.take() - } -} - -impl<'a: 'b, 'b> From> for JThrowable<'a> { - fn from(ex: JPanicException<'a, 'b>) -> Self { +impl<'a> From> for JThrowable<'a> { + fn from(ex: JPanicException<'a>) -> Self { ex.internal } } -impl<'a: 'b, 'b> ::std::ops::Deref for JPanicException<'a, 'b> { +impl<'a> ::std::ops::Deref for JPanicException<'a> { type Target = JThrowable<'a>; fn deref(&self) -> &Self::Target { @@ -165,112 +160,136 @@ impl<'a: 'b, 'b> ::std::ops::Deref for JPanicException<'a, 'b> { } } +/// Wraps a caught panic payload in a +/// `io.github.gedgygedgy.rust.panic.PanicException` and throws it. If a Java +/// exception is already pending, it will be added as a suppressed exception. +pub fn throw_panic( + env: &mut Env, + panic: Box, +) -> Result<(), Error> { + let old_ex = if env.exception_check() { + let ex = env.exception_occurred(); + env.exception_clear(); + ex + } else { + None + }; + let ex = JPanicException::new(env, panic)?; + + if let Some(old_ex) = old_ex { + env.call_method( + &*ex, + jni_str!("addSuppressed"), + jni_sig!("(Ljava/lang/Throwable;)V"), + &[(&old_ex).into()], + )?; + } + let ex: JThrowable = ex.into(); + // throw() returns Err(JavaException) on success in jni 0.22 + match env.throw(&ex) { + Err(Error::JavaException) => Ok(()), + Err(e) => Err(e), + Ok(()) => Ok(()), + } +} + /// Calls the given closure. If it panics, catch the unwind, wrap it in a /// `io.github.gedgygedgy.rust.panic.PanicException`, and throw it. -pub fn throw_unwind<'a: 'b, 'b, R>( - env: &'b JNIEnv<'a>, +pub fn throw_unwind( + env: &mut Env, f: impl FnOnce() -> R + UnwindSafe, ) -> Result> { - catch_unwind(f).map_err(|e| { - let old_ex = if env.exception_check()? { - let ex = env.exception_occurred()?; - env.exception_clear()?; - Some(ex) - } else { - None - }; - let ex = JPanicException::new(env, e)?; - - if let Some(old_ex) = old_ex { - env.call_method( - ex.clone(), - "addSuppressed", - "(Ljava/lang/Throwable;)V", - &[old_ex.into()], - )?; - } - let ex: JThrowable = ex.into(); - env.throw(ex)?; - Ok(()) - }) + catch_unwind(f).map_err(|e| throw_panic(env, e)) } #[cfg(test)] mod test { - use jni::{JNIEnv, errors::Error, objects::JThrowable}; + use jni::{Env, errors::Error, jni_str, jni_sig, objects::{JObject, JThrowable}, strings::JNIString}; use super::super::test_utils; use super::try_block; - fn test_catch<'a: 'b, 'b>( - env: &'b JNIEnv<'a>, + fn test_catch( + env: &mut Env, throw_class: Option<&str>, try_result: Result, rethrow: bool, ) -> Result { - let old_ex = if env.exception_check().unwrap() { - let ex = env.exception_occurred().unwrap(); - env.exception_clear().unwrap(); - Some(ex) + let old_ex = if env.exception_check() { + let ex = env.exception_occurred(); + env.exception_clear(); + ex } else { None }; let illegal_argument_exception = env - .find_class("java/lang/IllegalArgumentException") + .find_class(jni_str!("java/lang/IllegalArgumentException")) .unwrap(); - if let Some(ex) = old_ex { - env.throw(ex).unwrap(); + if let Some(ref ex) = old_ex { + let _ = env.throw(ex); } let ex = throw_class.map(|c| { - let ex: JThrowable = env.new_object(c, "()V", &[]).unwrap().into(); - ex + let obj = env.new_object(JNIString::from(c), jni_sig!("()V"), &[]).unwrap(); + env.cast_local::(obj).unwrap() }); - try_block(env, || { - if let Some(t) = ex { - env.throw(t).unwrap(); + try_block(env, |env| { + if let Some(ref t) = ex { + let _ = env.throw(t); } try_result }) - .catch(illegal_argument_exception, |caught| { - assert!(!env.exception_check().unwrap()); - assert!(env.is_same_object(ex.unwrap(), caught).unwrap()); + .catch(env, illegal_argument_exception, |env, caught| { + assert!(!env.exception_check()); + assert!(env.is_same_object(&caught, ex.as_ref().unwrap()).unwrap()); Ok(1) }) - .catch("java/lang/ArrayIndexOutOfBoundsException", |caught| { - assert!(!env.exception_check().unwrap()); - assert!(env.is_same_object(ex.unwrap(), caught).unwrap()); - if rethrow { - Err(Error::JavaException) - } else { - Ok(2) - } - }) - .catch("java/lang/IndexOutOfBoundsException", |caught| { - assert!(!env.exception_check().unwrap()); - assert!(env.is_same_object(ex.unwrap(), caught).unwrap()); - if rethrow { - env.throw(caught).unwrap(); - Err(Error::JavaException) - } else { - Ok(3) - } - }) - .catch("java/lang/StringIndexOutOfBoundsException", |caught| { - assert!(!env.exception_check().unwrap()); - assert!(env.is_same_object(ex.unwrap(), caught).unwrap()); - Ok(4) - }) + .catch( + env, + jni_str!("java/lang/ArrayIndexOutOfBoundsException"), + |env, caught| { + assert!(!env.exception_check()); + assert!(env.is_same_object(&caught, ex.as_ref().unwrap()).unwrap()); + if rethrow { + Err(Error::JavaException) + } else { + Ok(2) + } + }, + ) + .catch( + env, + jni_str!("java/lang/IndexOutOfBoundsException"), + |env, caught| { + assert!(!env.exception_check()); + assert!(env.is_same_object(&caught, ex.as_ref().unwrap()).unwrap()); + if rethrow { + let _ = env.throw(&caught); + Err(Error::JavaException) + } else { + Ok(3) + } + }, + ) + .catch( + env, + jni_str!("java/lang/StringIndexOutOfBoundsException"), + |env, caught| { + assert!(!env.exception_check()); + assert!(env.is_same_object(&caught, ex.as_ref().unwrap()).unwrap()); + Ok(4) + }, + ) .result() } #[test] fn test_catch_first() { - test_utils::JVM_ENV.with(|env| { + test_utils::with_env(|env| { assert_eq!( test_catch( - &env, + env, Some("java/lang/IllegalArgumentException"), Err(Error::JavaException), false, @@ -278,16 +297,17 @@ mod test { .unwrap(), 1 ); - assert!(!env.exception_check().unwrap()); - }); + assert!(!env.exception_check()); + Ok(()) + }).unwrap(); } #[test] fn test_catch_second() { - test_utils::JVM_ENV.with(|env| { + test_utils::with_env(|env| { assert_eq!( test_catch( - &env, + env, Some("java/lang/ArrayIndexOutOfBoundsException"), Err(Error::JavaException), false, @@ -295,16 +315,17 @@ mod test { .unwrap(), 2 ); - assert!(!env.exception_check().unwrap()); - }); + assert!(!env.exception_check()); + Ok(()) + }).unwrap(); } #[test] fn test_catch_third() { - test_utils::JVM_ENV.with(|env| { + test_utils::with_env(|env| { assert_eq!( test_catch( - &env, + env, Some("java/lang/StringIndexOutOfBoundsException"), Err(Error::JavaException), false, @@ -312,294 +333,301 @@ mod test { .unwrap(), 3 ); - assert!(!env.exception_check().unwrap()); - }); + assert!(!env.exception_check()); + Ok(()) + }).unwrap(); } #[test] fn test_catch_ok() { - test_utils::JVM_ENV.with(|env| { - assert_eq!(test_catch(&env, None, Ok(0), false).unwrap(), 0); - assert!(!env.exception_check().unwrap()); - }); + test_utils::with_env(|env| { + assert_eq!(test_catch(env, None, Ok(0), false).unwrap(), 0); + assert!(!env.exception_check()); + Ok(()) + }).unwrap(); } #[test] fn test_catch_none() { - test_utils::JVM_ENV.with(|env| { + test_utils::with_env(|env| { if let Error::JavaException = test_catch( - &env, + env, Some("java/lang/SecurityException"), Err(Error::JavaException), false, ) .unwrap_err() { - assert!(env.exception_check().unwrap()); + assert!(env.exception_check()); let ex = env.exception_occurred().unwrap(); - env.exception_clear().unwrap(); + env.exception_clear(); assert!( - env.is_instance_of(ex, "java/lang/SecurityException") + env.is_instance_of(&ex, jni_str!("java/lang/SecurityException")) .unwrap() ); } else { panic!("No JavaException"); } - }); + Ok(()) + }).unwrap(); } #[test] fn test_catch_other() { - test_utils::JVM_ENV.with(|env| { + test_utils::with_env(|env| { if let Error::InvalidCtorReturn = test_catch(env, None, Err(Error::InvalidCtorReturn), false).unwrap_err() { - assert!(!env.exception_check().unwrap()); + assert!(!env.exception_check()); } else { panic!("InvalidCtorReturn not found"); } - }); + Ok(()) + }).unwrap(); } #[test] fn test_catch_bogus_exception() { - test_utils::JVM_ENV.with(|env| { + test_utils::with_env(|env| { if let Error::JavaException = test_catch(env, None, Err(Error::JavaException), false).unwrap_err() { - assert!(!env.exception_check().unwrap()); + assert!(!env.exception_check()); } else { panic!("JavaException not found"); } - }); + Ok(()) + }).unwrap(); } #[test] fn test_catch_prior_exception() { - test_utils::JVM_ENV.with(|env| { - let ex: JThrowable = env - .new_object("java/lang/IllegalArgumentException", "()V", &[]) - .unwrap() - .into(); - env.throw(ex).unwrap(); + test_utils::with_env(|env| { + let obj = env.new_object(jni_str!("java/lang/IllegalArgumentException"), jni_sig!("()V"), &[]).unwrap(); + let ex = env.cast_local::(obj).unwrap(); + let _ = env.throw(&ex); - if let Error::JavaException = test_catch(&env, None, Ok(0), false).unwrap_err() { - assert!(env.exception_check().unwrap()); + if let Error::JavaException = test_catch(env, None, Ok(0), false).unwrap_err() { + assert!(env.exception_check()); let actual_ex = env.exception_occurred().unwrap(); - env.exception_clear().unwrap(); - assert!(env.is_same_object(actual_ex, ex).unwrap()); + env.exception_clear(); + assert!(env.is_same_object(&actual_ex, &ex).unwrap()); } else { panic!("JavaException not found"); } - }); + Ok(()) + }).unwrap(); } #[test] fn test_catch_rethrow() { - test_utils::JVM_ENV.with(|env| { + test_utils::with_env(|env| { if let Error::JavaException = test_catch( - &env, + env, Some("java/lang/StringIndexOutOfBoundsException"), Err(Error::JavaException), true, ) .unwrap_err() { - assert!(env.exception_check().unwrap()); + assert!(env.exception_check()); let ex = env.exception_occurred().unwrap(); - env.exception_clear().unwrap(); + env.exception_clear(); assert!( - env.is_instance_of(ex, "java/lang/StringIndexOutOfBoundsException") + env.is_instance_of(&ex, jni_str!("java/lang/StringIndexOutOfBoundsException")) .unwrap() ); } else { panic!("JavaException not found"); } - }); + Ok(()) + }).unwrap(); } #[test] fn test_catch_bogus_rethrow() { - test_utils::JVM_ENV.with(|env| { + test_utils::with_env(|env| { if let Error::JavaException = test_catch( - &env, + env, Some("java/lang/ArrayIndexOutOfBoundsException"), Err(Error::JavaException), true, ) .unwrap_err() { - assert!(!env.exception_check().unwrap()); + assert!(!env.exception_check()); } else { panic!("JavaException not found"); } - }); + Ok(()) + }).unwrap(); } #[test] fn test_panic_exception_static_str() { - test_utils::JVM_ENV.with(|env| { - use jni::{objects::JString, strings::JavaStr}; + test_utils::with_env(|env| { + use jni::objects::JString; - const STATIC_MSG: &'static str = "This is a &'static str"; + const STATIC_MSG: &str = "This is a &'static str"; let ex = super::JPanicException::new(env, Box::new(STATIC_MSG)).unwrap(); { - let any = ex.get().unwrap(); + let any = ex.get(env).unwrap(); assert_eq!(*any.downcast_ref::<&str>().unwrap(), STATIC_MSG); } - let msg: JString = env - .call_method(ex.clone(), "getMessage", "()Ljava/lang/String;", &[]) + let msg_obj = env + .call_method(&*ex, jni_str!("getMessage"), jni_sig!("()Ljava/lang/String;"), &[]) .unwrap() .l() - .unwrap() - .into(); - let str = JavaStr::from_env(env, msg).unwrap(); - assert_eq!(str.to_str().unwrap(), STATIC_MSG); - }); + .unwrap(); + let msg = env.cast_local::(msg_obj).unwrap(); + let chars = msg.mutf8_chars(env).unwrap(); + assert_eq!(String::from(chars), STATIC_MSG); + Ok(()) + }).unwrap(); } #[test] fn test_panic_exception_string() { - test_utils::JVM_ENV.with(|env| { - use jni::{objects::JString, strings::JavaStr}; + test_utils::with_env(|env| { + use jni::objects::JString; use std::any::Any; - const STRING_MSG: &'static str = "This is a String"; + const STRING_MSG: &str = "This is a String"; let ex = super::JPanicException::new(env, Box::new(STRING_MSG.to_string())).unwrap(); { - let any = ex.get().unwrap(); + let any = ex.get(env).unwrap(); assert_eq!(*any.downcast_ref::().unwrap(), STRING_MSG); } - let msg: JString = env - .call_method(ex.clone(), "getMessage", "()Ljava/lang/String;", &[]) + let msg_obj = env + .call_method(&*ex, jni_str!("getMessage"), jni_sig!("()Ljava/lang/String;"), &[]) .unwrap() .l() - .unwrap() - .into(); - let str = JavaStr::from_env(env, msg).unwrap(); - assert_eq!(str.to_str().unwrap(), STRING_MSG); + .unwrap(); + let msg = env.cast_local::(msg_obj).unwrap(); + let chars = msg.mutf8_chars(env).unwrap(); + assert_eq!(String::from(chars), STRING_MSG); - let any: Box = ex.take().unwrap(); + let any: Box = ex.take(env).unwrap(); assert_eq!(*any.downcast::().unwrap(), STRING_MSG); - }); + Ok(()) + }).unwrap(); } #[test] fn test_panic_exception_other() { - test_utils::JVM_ENV.with(|env| { + test_utils::with_env(|env| { use jni::objects::JObject; - use std::{any::Any, convert::TryInto}; + use std::any::Any; let ex = super::JPanicException::new(env, Box::new(42)).unwrap(); { - let any = ex.get().unwrap(); + let any = ex.get(env).unwrap(); assert_eq!(*any.downcast_ref::().unwrap(), 42); } let msg = env - .call_method(ex.clone(), "getMessage", "()Ljava/lang/String;", &[]) + .call_method(&*ex, jni_str!("getMessage"), jni_sig!("()Ljava/lang/String;"), &[]) .unwrap() .l() .unwrap(); - assert!(env.is_same_object(msg, JObject::null()).unwrap()); + assert!(env.is_same_object(&msg, JObject::null()).unwrap()); - let any: Box = ex.try_into().unwrap(); + let any: Box = ex.take(env).unwrap(); assert_eq!(*any.downcast::().unwrap(), 42); - }); + Ok(()) + }).unwrap(); } #[test] fn test_throw_unwind_ok() { - test_utils::JVM_ENV.with(|env| { + test_utils::with_env(|env| { let result = super::throw_unwind(env, || 42).unwrap(); assert_eq!(result, 42); - assert!(!env.exception_check().unwrap()); - }); + assert!(!env.exception_check()); + Ok(()) + }).unwrap(); } #[test] fn test_throw_unwind_panic() { - test_utils::JVM_ENV.with(|env| { + test_utils::with_env(|env| { super::throw_unwind(env, || panic!("This is a panic")) .unwrap_err() .unwrap(); - assert!(env.exception_check().unwrap()); + assert!(env.exception_check()); let ex = env.exception_occurred().unwrap(); - env.exception_clear().unwrap(); + env.exception_clear(); assert!( - env.is_instance_of(ex, "io/github/gedgygedgy/rust/panic/PanicException") + env.is_instance_of(&ex, jni_str!("io/github/gedgygedgy/rust/panic/PanicException")) .unwrap() ); let suppressed_list = env - .call_method(ex, "getSuppressed", "()[Ljava/lang/Throwable;", &[]) + .call_method(&ex, jni_str!("getSuppressed"), jni_sig!("()[Ljava/lang/Throwable;"), &[]) .unwrap() .l() .unwrap(); - assert_eq!( - env.get_array_length(suppressed_list.into_inner()).unwrap(), - 0 - ); + let suppressed_array = + unsafe { jni::objects::JObjectArray::::from_raw(env, suppressed_list.into_raw()) }; + assert_eq!(suppressed_array.len(env).unwrap(), 0); - let ex = super::JPanicException::from_env(env, ex).unwrap(); - let any = ex.take().unwrap(); + let ex = super::JPanicException::from_env(ex); + let any = ex.take(env).unwrap(); let str = any.downcast::<&str>().unwrap(); assert_eq!(*str, "This is a panic"); - }); + Ok(()) + }).unwrap(); } #[test] fn test_throw_unwind_panic_suppress() { - test_utils::JVM_ENV.with(|env| { - let old_ex: JThrowable = env - .new_object("java/lang/Exception", "()V", &[]) - .unwrap() - .into(); - env.throw(old_ex).unwrap(); + test_utils::with_env(|env| { + let obj = env.new_object(jni_str!("java/lang/Exception"), jni_sig!("()V"), &[]).unwrap(); + let old_ex = env.cast_local::(obj).unwrap(); + let _ = env.throw(&old_ex); super::throw_unwind(env, || panic!("This is a panic")) .unwrap_err() .unwrap(); - assert!(env.exception_check().unwrap()); + assert!(env.exception_check()); let ex = env.exception_occurred().unwrap(); - env.exception_clear().unwrap(); + env.exception_clear(); assert!( - env.is_instance_of(ex, "io/github/gedgygedgy/rust/panic/PanicException") + env.is_instance_of(&ex, jni_str!("io/github/gedgygedgy/rust/panic/PanicException")) .unwrap() ); let suppressed_list = env - .call_method(ex, "getSuppressed", "()[Ljava/lang/Throwable;", &[]) + .call_method(&ex, jni_str!("getSuppressed"), jni_sig!("()[Ljava/lang/Throwable;"), &[]) .unwrap() .l() .unwrap(); - assert_eq!( - env.get_array_length(suppressed_list.into_inner()).unwrap(), - 1 - ); - let suppressed_ex = env - .get_object_array_element(suppressed_list.into_inner(), 0) - .unwrap(); - assert!(env.is_same_object(old_ex, suppressed_ex).unwrap()); - - let ex = super::JPanicException::from_env(env, ex).unwrap(); - let any = ex.take().unwrap(); + let suppressed_array = + unsafe { jni::objects::JObjectArray::::from_raw(env, suppressed_list.into_raw()) }; + assert_eq!(suppressed_array.len(env).unwrap(), 1); + let suppressed_ex = suppressed_array.get_element(env, 0).unwrap(); + assert!(env.is_same_object(&old_ex, &suppressed_ex).unwrap()); + + let ex = super::JPanicException::from_env(ex); + let any = ex.take(env).unwrap(); let str = any.downcast::<&str>().unwrap(); assert_eq!(*str, "This is a panic"); - }); + Ok(()) + }).unwrap(); } #[test] #[should_panic(expected = "This is a panic")] fn test_panic_exception_resume_unwind() { - test_utils::JVM_ENV.with(|env| { + test_utils::with_env(|env| { let ex = super::JPanicException::new(env, Box::new("This is a panic")).unwrap(); - ex.resume_unwind().unwrap(); - }); + ex.resume_unwind(env).unwrap(); + Ok(()) + }).unwrap(); } } diff --git a/src/droidplug/jni_utils/future.rs b/src/droidplug/jni_utils/future.rs index 34587a80..5abcd5f8 100644 --- a/src/droidplug/jni_utils/future.rs +++ b/src/droidplug/jni_utils/future.rs @@ -1,159 +1,81 @@ -use super::task::JPollResult; use ::jni::{ - JNIEnv, JavaVM, - errors::{Error, Result}, - objects::{GlobalRef, JClass, JMethodID, JObject}, - signature::JavaType, + Env, JavaVM, + bind_java_type, + errors::Result, + jni_sig, jni_str, + objects::{Global, JObject}, }; use static_assertions::assert_impl_all; use std::{ - convert::TryFrom, future::Future, pin::Pin, task::{Context, Poll}, }; -/// Wrapper for [`JObject`]s that implement -/// `io.github.gedgygedgy.rust.future.Future`. Implements -/// [`Future`](std::future::Future) to allow asynchronous Rust code to wait for -/// a result from Java code. -/// -/// For a [`Send`] version of this, use [`JSendFuture`]. -pub struct JFuture<'a: 'b, 'b> { - internal: JObject<'a>, - poll: JMethodID<'a>, - env: &'b JNIEnv<'a>, +bind_java_type! { + pub JFuture => io.github.gedgygedgy.rust.future.Future, } -impl<'a: 'b, 'b> JFuture<'a, 'b> { - pub fn from_env(env: &'b JNIEnv<'a>, obj: JObject<'a>) -> Result { - let poll = env.get_method_id( - JClass::from( - super::classcache::get_class("io/github/gedgygedgy/rust/future/Future") - .unwrap() - .as_obj(), - ), - "poll", - "(Lio/github/gedgygedgy/rust/task/Waker;)Lio/github/gedgygedgy/rust/task/PollResult;", - )?; - Ok(Self { - internal: obj, - poll, - env, - }) - } - - pub fn poll(&self, waker: JObject<'a>) -> Result> { - let result = self - .env - .call_method_unchecked( - self.internal, - self.poll, - JavaType::Object("io/github/gedgygedgy/rust/task/PollResult".into()), - &[waker.into()], - )? - .l()?; - JPollResult::from_env(self.env, result) - } - - pub fn into_future(self) -> JFutureIntoFuture<'a, 'b> { - JFutureIntoFuture(self) +impl<'local> JFuture<'local> { + pub fn poll(&self, env: &mut Env<'local>, waker: &JObject<'local>) -> Result> { + env.call_method( + self, + jni_str!("poll"), + jni_sig!("(Lio/github/gedgygedgy/rust/task/Waker;)Lio/github/gedgygedgy/rust/task/PollResult;"), + &[waker.into()], + )?.l() } } -impl<'a: 'b, 'b> ::std::ops::Deref for JFuture<'a, 'b> { - type Target = JObject<'a>; - - fn deref(&self) -> &Self::Target { - &self.internal - } -} - -impl<'a: 'b, 'b> From> for JObject<'a> { - fn from(other: JFuture<'a, 'b>) -> JObject<'a> { - other.internal - } -} - -pub struct JFutureIntoFuture<'a: 'b, 'b>(JFuture<'a, 'b>); - -impl<'a: 'b, 'b> JFutureIntoFuture<'a, 'b> { - fn poll_internal(&self, context: &mut Context<'_>) -> Result>> { - use super::task::waker; - let result = self.0.poll(waker(self.0.env, context.waker().clone())?)?; - Ok( - if self.0.env.is_same_object(result.clone(), JObject::null())? { - Poll::Pending - } else { - Poll::Ready(result) - }, - ) - } -} - -impl<'a: 'b, 'b> Future for JFutureIntoFuture<'a, 'b> { - type Output = Result>; - - fn poll(self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll { - match self.poll_internal(context) { - Ok(Poll::Ready(result)) => Poll::Ready(Ok(result)), - Ok(Poll::Pending) => Poll::Pending, - Err(err) => Poll::Ready(Err(err)), - } - } -} - -impl<'a: 'b, 'b> From> for JFuture<'a, 'b> { - fn from(fut: JFutureIntoFuture<'a, 'b>) -> Self { - fut.0 - } -} - -impl<'a: 'b, 'b> std::ops::Deref for JFutureIntoFuture<'a, 'b> { - type Target = JFuture<'a, 'b>; - - fn deref(&self) -> &Self::Target { - &self.0 - } +bind_java_type! { + pub JFutureException => io.github.gedgygedgy.rust.future.FutureException, } -/// [`Send`] version of [`JFuture`]. pub struct JSendFuture { - internal: GlobalRef, + internal: Global>, vm: JavaVM, } -impl<'a: 'b, 'b> TryFrom> for JSendFuture { - type Error = Error; +impl JSendFuture { + pub fn new(env: &mut Env, future: &JFuture) -> Result { + Ok(Self { + internal: env.new_global_ref(&**future)?, + vm: env.get_java_vm()?, + }) + } - fn try_from(future: JFuture<'a, 'b>) -> Result { + pub fn from_env(env: &mut Env, obj: &JObject) -> Result { Ok(Self { - internal: future.env.new_global_ref(future.internal)?, - vm: future.env.get_java_vm()?, + internal: env.new_global_ref(obj)?, + vm: env.get_java_vm()?, + }) + } + + fn poll_internal(&self, context: &mut Context<'_>) -> Result>>>> { + self.vm.attach_current_thread(|env| { + let jwaker = super::task::waker(env, context.waker().clone())?; + let local = env.new_local_ref(self.internal.as_obj())?; + let jfuture = env.cast_local::(local)?; + let result = jfuture.poll(env, &jwaker)?; + Ok(if env.is_same_object(&result, JObject::null())? { + Poll::Pending + } else { + Poll::Ready(Ok(env.new_global_ref(result)?)) + }) }) } } impl ::std::ops::Deref for JSendFuture { - type Target = GlobalRef; + type Target = Global>; fn deref(&self) -> &Self::Target { &self.internal } } -impl JSendFuture { - fn poll_internal(&self, context: &mut Context<'_>) -> Result>> { - let env = self.vm.get_env()?; - let jfuture = JFuture::from_env(&env, self.internal.as_obj())?.into_future(); - jfuture - .poll_internal(context) - .map(|result| result.map(|result| Ok(env.new_global_ref(result)?))) - } -} - impl Future for JSendFuture { - type Output = Result; + type Output = Result>>; fn poll(self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll { match self.poll_internal(context) { @@ -167,8 +89,9 @@ assert_impl_all!(JSendFuture: Send); #[cfg(test)] mod test { - use super::super::{task::JPollResult, test_utils}; + use super::super::test_utils; use super::{JFuture, JSendFuture}; + use jni::{jni_sig, jni_str}; use std::{ future::Future, pin::Pin, @@ -177,9 +100,10 @@ mod test { #[test] fn test_jfuture() { + use super::super::task::JPollResult; use std::sync::Arc; - test_utils::JVM_ENV.with(|env| { + test_utils::with_env(|env| { let data = Arc::new(test_utils::TestWakerData::new()); assert_eq!(Arc::strong_count(&data), 1); assert_eq!(data.value(), false); @@ -189,9 +113,11 @@ mod test { assert_eq!(data.value(), false); let future_obj = env - .new_object("io/github/gedgygedgy/rust/future/SimpleFuture", "()V", &[]) + .new_object(jni_str!("io/github/gedgygedgy/rust/future/SimpleFuture"), jni_sig!("()V"), &[]) .unwrap(); - let mut future = JFuture::from_env(env, future_obj).unwrap().into_future(); + let future_local = env.new_local_ref(&future_obj).unwrap(); + let jfuture = env.cast_local::(future_local).unwrap(); + let mut future = JSendFuture::new(env, &jfuture).unwrap(); assert!( Future::poll(Pin::new(&mut future), &mut Context::from_waker(&waker)).is_pending() @@ -205,18 +131,24 @@ mod test { assert_eq!(Arc::strong_count(&data), 3); assert_eq!(data.value(), false); - let obj = env.new_object("java/lang/Object", "()V", &[]).unwrap(); - env.call_method(future_obj, "wake", "(Ljava/lang/Object;)V", &[obj.into()]) - .unwrap(); + let obj = env.new_object(jni_str!("java/lang/Object"), jni_sig!("()V"), &[]).unwrap(); + env.call_method( + &future_obj, + jni_str!("wake"), + jni_sig!("(Ljava/lang/Object;)V"), + &[(&obj).into()], + ) + .unwrap(); assert_eq!(Arc::strong_count(&data), 2); assert_eq!(data.value(), true); let poll = Future::poll(Pin::new(&mut future), &mut Context::from_waker(&waker)); if let Poll::Ready(result) = poll { - assert!( - env.is_same_object(result.unwrap().get().unwrap(), obj) - .unwrap() - ); + let global = result.unwrap(); + let local = env.new_local_ref(global.as_obj()).unwrap(); + let poll_result = env.cast_local::(local).unwrap(); + let result_obj = poll_result.get(env).unwrap(); + assert!(env.is_same_object(&result_obj, &obj).unwrap()); } else { panic!("Poll result should be ready"); } @@ -225,46 +157,67 @@ mod test { let poll = Future::poll(Pin::new(&mut future), &mut Context::from_waker(&waker)); if let Poll::Ready(result) = poll { - assert!( - env.is_same_object(result.unwrap().get().unwrap(), obj) - .unwrap() - ); + let global = result.unwrap(); + let local = env.new_local_ref(global.as_obj()).unwrap(); + let poll_result = env.cast_local::(local).unwrap(); + let result_obj = poll_result.get(env).unwrap(); + assert!(env.is_same_object(&result_obj, &obj).unwrap()); } else { panic!("Poll result should be ready"); } assert_eq!(Arc::strong_count(&data), 2); assert_eq!(data.value(), true); - }); + + Ok(()) + }).unwrap(); } #[test] fn test_jfuture_await() { + use super::super::task::JPollResult; use futures::{executor::block_on, join}; - test_utils::JVM_ENV.with(|env| { + let (future, future_obj_global, obj_global) = test_utils::with_env(|env| { let future_obj = env - .new_object("io/github/gedgygedgy/rust/future/SimpleFuture", "()V", &[]) + .new_object(jni_str!("io/github/gedgygedgy/rust/future/SimpleFuture"), jni_sig!("()V"), &[]) .unwrap(); - let future = JFuture::from_env(env, future_obj).unwrap(); - let obj = env.new_object("java/lang/Object", "()V", &[]).unwrap(); - - block_on(async { - join!( - async { - env.call_method(future_obj, "wake", "(Ljava/lang/Object;)V", &[obj.into()]) - .unwrap(); - }, - async { - assert!( - env.is_same_object( - future.into_future().await.unwrap().get().unwrap(), - obj - ) - .unwrap() - ); - } - ); - }); + let future_obj_global = env.new_global_ref(&future_obj).unwrap(); + let future_local = env.new_local_ref(&future_obj).unwrap(); + let jfuture = env.cast_local::(future_local).unwrap(); + let future = JSendFuture::new(env, &jfuture).unwrap(); + let obj = env.new_object(jni_str!("java/lang/Object"), jni_sig!("()V"), &[]).unwrap(); + let obj_global = env.new_global_ref(&obj).unwrap(); + Ok((future, future_obj_global, obj_global)) + }).unwrap(); + + block_on(async { + join!( + async { + test_utils::with_env(|env| { + let future_local = env.new_local_ref(future_obj_global.as_obj()).unwrap(); + let obj_local = env.new_local_ref(obj_global.as_obj()).unwrap(); + env.call_method( + &future_local, + jni_str!("wake"), + jni_sig!("(Ljava/lang/Object;)V"), + &[(&obj_local).into()], + ) + .unwrap(); + Ok(()) + }).unwrap(); + }, + async { + let global = future.await.unwrap(); + test_utils::with_env(|env| { + let local = env.new_local_ref(global.as_obj()).unwrap(); + let poll_result = env.cast_local::(local).unwrap(); + let result_obj = poll_result.get(env).unwrap(); + let obj_local = env.new_local_ref(obj_global.as_obj()).unwrap(); + assert!(env.is_same_object(&result_obj, &obj_local).unwrap()); + Ok(()) + }).unwrap(); + } + ); }); } @@ -272,66 +225,104 @@ mod test { fn test_jfuture_await_throw() { use futures::{executor::block_on, join}; - test_utils::JVM_ENV.with(|env| { + let (future, future_obj_global, ex_global) = test_utils::with_env(|env| { let future_obj = env - .new_object("io/github/gedgygedgy/rust/future/SimpleFuture", "()V", &[]) + .new_object(jni_str!("io/github/gedgygedgy/rust/future/SimpleFuture"), jni_sig!("()V"), &[]) .unwrap(); - let future = JFuture::from_env(env, future_obj).unwrap(); - let ex = env.new_object("java/lang/Exception", "()V", &[]).unwrap(); - - block_on(async { - join!( - async { + let future_obj_global = env.new_global_ref(&future_obj).unwrap(); + let future_local = env.new_local_ref(&future_obj).unwrap(); + let jfuture = env.cast_local::(future_local).unwrap(); + let future = JSendFuture::new(env, &jfuture).unwrap(); + let ex = env.new_object(jni_str!("java/lang/Exception"), jni_sig!("()V"), &[]).unwrap(); + let ex_global = env.new_global_ref(&ex).unwrap(); + Ok((future, future_obj_global, ex_global)) + }).unwrap(); + + block_on(async { + join!( + async { + test_utils::with_env(|env| { + let future_local = env.new_local_ref(future_obj_global.as_obj()).unwrap(); + let ex_local = env.new_local_ref(ex_global.as_obj()).unwrap(); env.call_method( - future_obj, - "wakeWithThrowable", - "(Ljava/lang/Throwable;)V", - &[ex.into()], + &future_local, + jni_str!("wakeWithThrowable"), + jni_sig!("(Ljava/lang/Throwable;)V"), + &[(&ex_local).into()], ) .unwrap(); - }, - async { - future.into_future().await.unwrap().get().unwrap_err(); + Ok(()) + }).unwrap(); + }, + async { + use super::super::task::JPollResult; + + let global = future.await.unwrap(); + test_utils::with_env(|env| { + let local = env.new_local_ref(global.as_obj()).unwrap(); + let poll_result = env.cast_local::(local).unwrap(); + let _err = poll_result.get(env).unwrap_err(); + let future_ex = env.exception_occurred().unwrap(); - env.exception_clear().unwrap(); + env.exception_clear(); let actual_ex = env - .call_method(future_ex, "getCause", "()Ljava/lang/Throwable;", &[]) + .call_method(&future_ex, jni_str!("getCause"), jni_sig!("()Ljava/lang/Throwable;"), &[]) .unwrap() .l() .unwrap(); - assert!(env.is_same_object(actual_ex, ex).unwrap()); - } - ); - }); + let ex_local = env.new_local_ref(ex_global.as_obj()).unwrap(); + assert!(env.is_same_object(&actual_ex, &ex_local).unwrap()); + Ok(()) + }).unwrap(); + } + ); }); } #[test] fn test_jsendfuture_await() { + use super::super::task::JPollResult; use futures::{executor::block_on, join}; - use std::convert::TryInto; - test_utils::JVM_ENV.with(|env| { + let (future, future_obj_global, obj_global) = test_utils::with_env(|env| { let future_obj = env - .new_object("io/github/gedgygedgy/rust/future/SimpleFuture", "()V", &[]) + .new_object(jni_str!("io/github/gedgygedgy/rust/future/SimpleFuture"), jni_sig!("()V"), &[]) .unwrap(); - let future = JFuture::from_env(env, future_obj).unwrap(); - let future: JSendFuture = future.try_into().unwrap(); - let obj = env.new_object("java/lang/Object", "()V", &[]).unwrap(); - - block_on(async { - join!( - async { - env.call_method(future_obj, "wake", "(Ljava/lang/Object;)V", &[obj.into()]) - .unwrap(); - }, - async { - let global_ref = future.await.unwrap(); - let jpoll = JPollResult::from_env(env, global_ref.as_obj()).unwrap(); - assert!(env.is_same_object(jpoll.get().unwrap(), obj).unwrap()); - } - ); - }); + let future_obj_global = env.new_global_ref(&future_obj).unwrap(); + let future = JSendFuture::from_env(env, &future_obj).unwrap(); + let obj = env.new_object(jni_str!("java/lang/Object"), jni_sig!("()V"), &[]).unwrap(); + let obj_global = env.new_global_ref(&obj).unwrap(); + Ok((future, future_obj_global, obj_global)) + }).unwrap(); + + block_on(async { + join!( + async { + test_utils::with_env(|env| { + let future_local = env.new_local_ref(future_obj_global.as_obj()).unwrap(); + let obj_local = env.new_local_ref(obj_global.as_obj()).unwrap(); + env.call_method( + &future_local, + jni_str!("wake"), + jni_sig!("(Ljava/lang/Object;)V"), + &[(&obj_local).into()], + ) + .unwrap(); + Ok(()) + }).unwrap(); + }, + async { + let global_ref = future.await.unwrap(); + test_utils::with_env(|env| { + let local = env.new_local_ref(global_ref.as_obj()).unwrap(); + let jpoll = env.cast_local::(local).unwrap(); + let result_obj = jpoll.get(env).unwrap(); + let obj_local = env.new_local_ref(obj_global.as_obj()).unwrap(); + assert!(env.is_same_object(&result_obj, &obj_local).unwrap()); + Ok(()) + }).unwrap(); + } + ); }); } } diff --git a/src/droidplug/jni_utils/mod.rs b/src/droidplug/jni_utils/mod.rs index 84bae6b2..0102d8d8 100644 --- a/src/droidplug/jni_utils/mod.rs +++ b/src/droidplug/jni_utils/mod.rs @@ -1,5 +1,4 @@ pub mod arrays; -pub mod classcache; pub mod exceptions; pub mod future; pub mod ops; @@ -9,46 +8,51 @@ pub mod uuid; #[cfg(test)] pub(crate) mod test_utils { - use jni::{JNIEnv, JavaVM, objects::GlobalRef}; + use jni::{Env, JavaVM, NativeMethod, jni_str, jni_sig, objects::{Global, JObject, Reference}}; use lazy_static::lazy_static; use std::{ + cell::Cell, + ffi::c_void, sync::{Arc, Mutex}, task::{Wake, Waker}, }; - use jni::NativeMethod; - - fn test_init(env: &JNIEnv) -> jni::errors::Result<()> { - use std::ffi::c_void; - super::classcache::find_add_class(env, "io/github/gedgygedgy/rust/future/Future")?; - super::classcache::find_add_class(env, "io/github/gedgygedgy/rust/future/FutureException")?; - super::classcache::find_add_class(env, "io/github/gedgygedgy/rust/ops/FnAdapter")?; - super::classcache::find_add_class(env, "io/github/gedgygedgy/rust/stream/Stream")?; - super::classcache::find_add_class(env, "io/github/gedgygedgy/rust/stream/StreamPoll")?; - super::classcache::find_add_class(env, "io/github/gedgygedgy/rust/task/Waker")?; - super::classcache::find_add_class(env, "io/github/gedgygedgy/rust/task/PollResult")?; - super::classcache::find_add_class(env, "io/github/gedgygedgy/rust/ops/FnRunnableImpl")?; - super::classcache::find_add_class(env, "io/github/gedgygedgy/rust/ops/FnBiFunctionImpl")?; - super::classcache::find_add_class(env, "io/github/gedgygedgy/rust/ops/FnFunctionImpl")?; - - let class = env.auto_local(env.find_class("io/github/gedgygedgy/rust/ops/FnAdapter")?); - env.register_native_methods( - &class, + fn test_init(env: &mut Env) -> jni::errors::Result<()> { + use super::{ + future::{JFuture, JFutureException}, + ops::{JFnAdapter, JFnRunnableImpl, JFnBiFunctionImpl, JFnFunctionImpl}, + stream::{JStream, JStreamPoll}, + task::{JPollResult, JWaker}, + }; + + let loader = jni::objects::LoaderContext::default(); + ::lookup_class(env, &loader)?; + ::lookup_class(env, &loader)?; + ::lookup_class(env, &loader)?; + ::lookup_class(env, &loader)?; + ::lookup_class(env, &loader)?; + ::lookup_class(env, &loader)?; + ::lookup_class(env, &loader)?; + ::lookup_class(env, &loader)?; + ::lookup_class(env, &loader)?; + ::lookup_class(env, &loader)?; + + let fn_adapter_class = ::lookup_class(env, &loader)?; + unsafe { env.register_native_methods( + &*fn_adapter_class, &[ - NativeMethod { - name: "callInternal".into(), - sig: - "(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;" - .into(), - fn_ptr: super::ops::fn_adapter_call_internal as *mut c_void, - }, - NativeMethod { - name: "closeInternal".into(), - sig: "()V".into(), - fn_ptr: super::ops::fn_adapter_close_internal as *mut c_void, - }, + NativeMethod::from_raw_parts( + jni_str!("callInternal"), + jni_str!("(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;"), + super::ops::fn_adapter_call_internal as *mut c_void, + ), + NativeMethod::from_raw_parts( + jni_str!("closeInternal"), + jni_str!("()V"), + super::ops::fn_adapter_close_internal as *mut c_void, + ), ], - )?; + )? }; Ok(()) } @@ -85,32 +89,37 @@ pub(crate) mod test_utils { struct GlobalJVM { jvm: JavaVM, - class_loader: GlobalRef, + class_loader: Global>, } thread_local! { - pub static JVM_ENV: JNIEnv<'static> = { - let env = JVM.jvm.attach_current_thread_permanently().unwrap(); - - let thread = env - .call_static_method( - "java/lang/Thread", - "currentThread", - "()Ljava/lang/Thread;", - &[], - ) - .unwrap() - .l() - .unwrap(); - env.call_method( - thread, - "setContextClassLoader", - "(Ljava/lang/ClassLoader;)V", - &[JVM.class_loader.as_obj().into()] - ).unwrap(); - - env - } + static CLASS_LOADER_SET: Cell = const { Cell::new(false) }; + } + + pub fn with_env(f: F) -> jni::errors::Result + where + F: FnOnce(&mut Env) -> jni::errors::Result, + { + JVM.jvm.attach_current_thread(|env| { + if !CLASS_LOADER_SET.with(|c| c.get()) { + let thread = env + .call_static_method( + jni_str!("java/lang/Thread"), + jni_str!("currentThread"), + jni_sig!("()Ljava/lang/Thread;"), + &[], + )? + .l()?; + env.call_method( + &thread, + jni_str!("setContextClassLoader"), + jni_sig!("(Ljava/lang/ClassLoader;)V"), + &[JVM.class_loader.as_obj().into()], + )?; + CLASS_LOADER_SET.with(|c| c.set(true)); + } + f(env) + }) } lazy_static! { @@ -125,39 +134,41 @@ pub(crate) mod test_utils { jni_utils_jar.push("libs"); jni_utils_jar.push("btleplug-jni.jar"); + let classpath = format!( + "-Djava.class.path={}", + jni_utils_jar.to_str().unwrap() + ); let jvm_args = InitArgsBuilder::new() - .option(&format!( - "-Djava.class.path={}", - jni_utils_jar.to_str().unwrap() - )) + .option(&classpath) .build() .unwrap(); let jvm = JavaVM::new(jvm_args).unwrap(); - let env = jvm.attach_current_thread_permanently().unwrap(); - test_init(&env).unwrap(); - - let thread = env - .call_static_method( - "java/lang/Thread", - "currentThread", - "()Ljava/lang/Thread;", - &[], - ) - .unwrap() - .l() - .unwrap(); - let class_loader = env - .call_method( - thread, - "getContextClassLoader", - "()Ljava/lang/ClassLoader;", - &[], - ) - .unwrap() - .l() - .unwrap(); - let class_loader = env.new_global_ref(class_loader).unwrap(); + let class_loader = jvm.attach_current_thread(|env| { + test_init(env).unwrap(); + + let thread = env + .call_static_method( + jni_str!("java/lang/Thread"), + jni_str!("currentThread"), + jni_sig!("()Ljava/lang/Thread;"), + &[], + ) + .unwrap() + .l() + .unwrap(); + let class_loader = env + .call_method( + &thread, + jni_str!("getContextClassLoader"), + jni_sig!("()Ljava/lang/ClassLoader;"), + &[], + ) + .unwrap() + .l() + .unwrap(); + Ok::<_, jni::errors::Error>(env.new_global_ref(class_loader).unwrap()) + }).unwrap(); GlobalJVM { jvm, class_loader } }; diff --git a/src/droidplug/jni_utils/ops.rs b/src/droidplug/jni_utils/ops.rs index 3b896251..7ed0db1a 100644 --- a/src/droidplug/jni_utils/ops.rs +++ b/src/droidplug/jni_utils/ops.rs @@ -1,10 +1,28 @@ use ::jni::{ - JNIEnv, + Env, EnvUnowned, + bind_java_type, errors::Result, - objects::{JClass, JObject}, + jni_sig, jni_str, + objects::{JObject, Reference}, }; use std::sync::{Arc, Mutex}; +bind_java_type! { + pub JFnAdapter => io.github.gedgygedgy.rust.ops.FnAdapter, +} + +bind_java_type! { + pub JFnRunnableImpl => io.github.gedgygedgy.rust.ops.FnRunnableImpl, +} + +bind_java_type! { + pub JFnBiFunctionImpl => io.github.gedgygedgy.rust.ops.FnBiFunctionImpl, +} + +bind_java_type! { + pub JFnFunctionImpl => io.github.gedgygedgy.rust.ops.FnFunctionImpl, +} + macro_rules! define_fn_adapter { ( fn_once: $fo:ident, @@ -16,7 +34,7 @@ macro_rules! define_fn_adapter { fn: $f:ident, fn_local: $fl:ident, fn_internal: $fi:ident, - impl_class: $ic:literal, + impl_type: $it:ty, doc_class: $dc:literal, doc_method: $dm:literal, doc_fn_once: $dfo:literal, @@ -25,89 +43,92 @@ macro_rules! define_fn_adapter { signature: $closure_name:ident: impl for<'c, 'd> Fn$args:tt -> $ret:ty, closure: $closure:expr, ) => { - fn $foi<'a: 'b, 'b>( - env: &'b JNIEnv<'a>, + fn $foi<'local>( + env: &mut Env<'local>, $closure_name: impl for<'c, 'd> FnOnce$args -> $ret + 'static, local: bool, - ) -> Result> { - let adapter = env.auto_local(fn_once_adapter(env, $closure, local)?); + ) -> Result> { + let adapter = fn_once_adapter(env, $closure, local)?; + let class = <$it as Reference>::lookup_class(env, &Default::default())?; env.new_object( - JClass::from(super::classcache::get_class($ic).unwrap().as_obj()), - "(Lio/github/gedgygedgy/rust/ops/FnAdapter;)V", + &*class, + jni_sig!("(Lio/github/gedgygedgy/rust/ops/FnAdapter;)V"), &[(&adapter).into()], ) } - pub fn $fo<'a: 'b, 'b>( - env: &'b JNIEnv<'a>, + pub fn $fo<'local>( + env: &mut Env<'local>, f: impl for<'c, 'd> FnOnce$args -> $ret + Send + 'static, - ) -> Result> { + ) -> Result> { $foi(env, f, false) } #[allow(dead_code)] - pub fn $fol<'a: 'b, 'b>( - env: &'b JNIEnv<'a>, + pub fn $fol<'local>( + env: &mut Env<'local>, f: impl for<'c, 'd> FnOnce$args -> $ret + 'static, - ) -> Result> { + ) -> Result> { $foi(env, f, true) } - fn $fmi<'a: 'b, 'b>( - env: &'b JNIEnv<'a>, + fn $fmi<'local>( + env: &mut Env<'local>, mut $closure_name: impl for<'c, 'd> FnMut$args -> $ret + 'static, local: bool, - ) -> Result> { - let adapter = env.auto_local(fn_mut_adapter(env, $closure, local)?); + ) -> Result> { + let adapter = fn_mut_adapter(env, $closure, local)?; + let class = <$it as Reference>::lookup_class(env, &Default::default())?; env.new_object( - JClass::from(super::classcache::get_class($ic).unwrap().as_obj()), - "(Lio/github/gedgygedgy/rust/ops/FnAdapter;)V", + &*class, + jni_sig!("(Lio/github/gedgygedgy/rust/ops/FnAdapter;)V"), &[(&adapter).into()], ) } #[allow(dead_code)] - pub fn $fm<'a: 'b, 'b>( - env: &'b JNIEnv<'a>, + pub fn $fm<'local>( + env: &mut Env<'local>, f: impl for<'c, 'd> FnMut$args -> $ret + Send + 'static, - ) -> Result> { + ) -> Result> { $fmi(env, f, false) } #[allow(dead_code)] - pub fn $fml<'a: 'b, 'b>( - env: &'b JNIEnv<'a>, + pub fn $fml<'local>( + env: &mut Env<'local>, f: impl for<'c, 'd> FnMut$args -> $ret + 'static, - ) -> Result> { + ) -> Result> { $fmi(env, f, true) } - fn $fi<'a: 'b, 'b>( - env: &'b JNIEnv<'a>, + fn $fi<'local>( + env: &mut Env<'local>, $closure_name: impl for<'c, 'd> Fn$args -> $ret + 'static, local: bool, - ) -> Result> { - let adapter = env.auto_local(fn_adapter(env, $closure, local)?); + ) -> Result> { + let adapter = fn_adapter(env, $closure, local)?; + let class = <$it as Reference>::lookup_class(env, &Default::default())?; env.new_object( - JClass::from(super::classcache::get_class($ic).unwrap().as_obj()), - "(Lio/github/gedgygedgy/rust/ops/FnAdapter;)V", + &*class, + jni_sig!("(Lio/github/gedgygedgy/rust/ops/FnAdapter;)V"), &[(&adapter).into()], ) } #[allow(dead_code)] - pub fn $f<'a: 'b, 'b>( - env: &'b JNIEnv<'a>, + pub fn $f<'local>( + env: &mut Env<'local>, f: impl for<'c, 'd> Fn$args -> $ret + Send + Sync + 'static, - ) -> Result> { + ) -> Result> { $fi(env, f, false) } #[allow(dead_code)] - pub fn $fl<'a: 'b, 'b>( - env: &'b JNIEnv<'a>, + pub fn $fl<'local>( + env: &mut Env<'local>, f: impl for<'c, 'd> Fn$args -> $ret + 'static, - ) -> Result> { + ) -> Result> { $fi(env, f, true) } }; @@ -123,13 +144,13 @@ define_fn_adapter! { fn: fn_runnable, fn_local: fn_runnable_local, fn_internal: fn_runnable_internal, - impl_class: "io/github/gedgygedgy/rust/ops/FnRunnableImpl", + impl_type: JFnRunnableImpl, doc_class: "io.github.gedgygedgy.rust.ops.FnRunnable", doc_method: "run()", doc_fn_once: "fn_once_runnable", doc_fn: "fn_runnable", doc_noop: "be a no-op", - signature: f: impl for<'c, 'd> Fn(&'d JNIEnv<'c>, JObject<'c>) -> (), + signature: f: impl for<'c, 'd> Fn(&'d mut Env<'c>, JObject<'c>) -> (), closure: move |env, _obj1, obj2, _arg1, _arg2| { f(env, obj2); JObject::null() @@ -146,13 +167,13 @@ define_fn_adapter! { fn: fn_bi_function, fn_local: fn_bi_function_local, fn_internal: fn_bi_function_internal, - impl_class: "io/github/gedgygedgy/rust/ops/FnBiFunctionImpl", + impl_type: JFnBiFunctionImpl, doc_class: "io.github.gedgygedgy.rust.ops.FnBiFunction", doc_method: "apply()", doc_fn_once: "fn_once_bi_function", doc_fn: "fn_bi_funciton", doc_noop: "return `null`", - signature: f: impl for<'c, 'd> Fn(&'d JNIEnv<'c>, JObject<'c>, JObject<'c>, JObject<'c>) -> JObject<'c>, + signature: f: impl for<'c, 'd> Fn(&'d mut Env<'c>, JObject<'c>, JObject<'c>, JObject<'c>) -> JObject<'c>, closure: move |env, _obj1, obj2, arg1, arg2| { f(env, obj2, arg1, arg2) }, @@ -168,13 +189,13 @@ define_fn_adapter! { fn: fn_function, fn_local: fn_function_local, fn_internal: fn_function_internal, - impl_class: "io/github/gedgygedgy/rust/ops/FnFunctionImpl", + impl_type: JFnFunctionImpl, doc_class: "io.github.gedgygedgy.rust.ops.FnFunction", doc_method: "apply()", doc_fn_once: "fn_once_function", doc_fn: "fn_function", doc_noop: "return `null`", - signature: f: impl for<'c, 'd> Fn(&'d JNIEnv<'c>, JObject<'c>, JObject<'c>) -> JObject<'c>, + signature: f: impl for<'c, 'd> Fn(&'d mut Env<'c>, JObject<'c>, JObject<'c>) -> JObject<'c>, closure: move |env, _obj1, obj2, arg1, _arg2| { f(env, obj2, arg1) }, @@ -188,7 +209,7 @@ unsafe impl Sync for SendSyncWrapper {} type FnWrapper = SendSyncWrapper< Arc< dyn for<'a, 'b> Fn( - &'b JNIEnv<'a>, + &'b mut Env<'a>, JObject<'a>, JObject<'a>, JObject<'a>, @@ -198,10 +219,10 @@ type FnWrapper = SendSyncWrapper< >, >; -fn fn_once_adapter<'a: 'b, 'b>( - env: &'b JNIEnv<'a>, +fn fn_once_adapter<'local>( + env: &mut Env<'local>, f: impl for<'c, 'd> FnOnce( - &'d JNIEnv<'c>, + &'d mut Env<'c>, JObject<'c>, JObject<'c>, JObject<'c>, @@ -209,7 +230,7 @@ fn fn_once_adapter<'a: 'b, 'b>( ) -> JObject<'c> + 'static, local: bool, -) -> Result> { +) -> Result> { let mutex = Mutex::new(Some(f)); fn_adapter( env, @@ -228,10 +249,10 @@ fn fn_once_adapter<'a: 'b, 'b>( ) } -fn fn_mut_adapter<'a: 'b, 'b>( - env: &'b JNIEnv<'a>, +fn fn_mut_adapter<'local>( + env: &mut Env<'local>, f: impl for<'c, 'd> FnMut( - &'d JNIEnv<'c>, + &'d mut Env<'c>, JObject<'c>, JObject<'c>, JObject<'c>, @@ -239,7 +260,7 @@ fn fn_mut_adapter<'a: 'b, 'b>( ) -> JObject<'c> + 'static, local: bool, -) -> Result> { +) -> Result> { let mutex = Mutex::new(f); fn_adapter( env, @@ -251,10 +272,10 @@ fn fn_mut_adapter<'a: 'b, 'b>( ) } -fn fn_adapter<'a: 'b, 'b>( - env: &'b JNIEnv<'a>, +fn fn_adapter<'local>( + env: &mut Env<'local>, f: impl for<'c, 'd> Fn( - &'d JNIEnv<'c>, + &'d mut Env<'c>, JObject<'c>, JObject<'c>, JObject<'c>, @@ -262,10 +283,10 @@ fn fn_adapter<'a: 'b, 'b>( ) -> JObject<'c> + 'static, local: bool, -) -> Result> { +) -> Result> { let arc: Arc< dyn for<'c, 'd> Fn( - &'d JNIEnv<'c>, + &'d mut Env<'c>, JObject<'c>, JObject<'c>, JObject<'c>, @@ -273,39 +294,57 @@ fn fn_adapter<'a: 'b, 'b>( ) -> JObject<'c>, > = Arc::from(f); + let class = ::lookup_class(env, &Default::default())?; let obj = env.new_object( - JClass::from( - super::classcache::get_class("io/github/gedgygedgy/rust/ops/FnAdapter") - .unwrap() - .as_obj(), - ), - "(Z)V", + &*class, + jni_sig!("(Z)V"), &[local.into()], )?; - env.set_rust_field::<_, _, FnWrapper>(obj, "data", SendSyncWrapper(arc))?; + unsafe { env.set_rust_field::<_, _, FnWrapper>(&obj, jni_str!("data"), SendSyncWrapper(arc)) }?; Ok(obj) } -pub(crate) extern "C" fn fn_adapter_call_internal<'a>( - env: JNIEnv<'a>, - obj1: JObject<'a>, - obj2: JObject<'a>, - arg1: JObject<'a>, - arg2: JObject<'a>, -) -> JObject<'a> { - use std::panic::AssertUnwindSafe; +pub(crate) extern "C" fn fn_adapter_call_internal<'local>( + mut env: EnvUnowned<'local>, + obj1: JObject<'local>, + obj2: JObject<'local>, + arg1: JObject<'local>, + arg2: JObject<'local>, +) -> JObject<'local> { + use std::panic::{AssertUnwindSafe, catch_unwind}; - let arc = if let Ok(f) = env.get_rust_field::<_, _, FnWrapper>(obj1, "data") { - AssertUnwindSafe(f.0.clone()) - } else { - return JObject::null(); - }; - super::exceptions::throw_unwind(&env, || arc(&env, obj1, obj2, arg1, arg2)) - .unwrap_or_else(|_| JObject::null()) + let outcome = env.with_env(|env| -> std::result::Result, jni::errors::Error> { + let arc = + if let Ok(f) = unsafe { env.get_rust_field::<_, _, FnWrapper>(&obj1, jni_str!("data")) } { + AssertUnwindSafe(f.0.clone()) + } else { + return Ok(JObject::null()); + }; + match catch_unwind(AssertUnwindSafe(|| arc(env, obj1, obj2, arg1, arg2))) { + Ok(result) => Ok(result), + Err(panic) => { + let _ = super::exceptions::throw_panic(env, panic); + Ok(JObject::null()) + } + } + }); + match outcome.into_outcome() { + jni::Outcome::Ok(obj) => obj, + _ => JObject::null(), + } } -pub(crate) extern "C" fn fn_adapter_close_internal(env: JNIEnv, obj: JObject) { - let _ = super::exceptions::throw_unwind(&env, || { - let _ = env.take_rust_field::<_, _, FnWrapper>(obj, "data"); +pub(crate) extern "C" fn fn_adapter_close_internal(mut env: EnvUnowned, obj: JObject) { + use std::panic::{AssertUnwindSafe, catch_unwind}; + + let outcome = env.with_env(|env| { + let result = catch_unwind(AssertUnwindSafe(|| { + let _ = unsafe { env.take_rust_field::<_, _, FnWrapper>(&obj, jni_str!("data")) }; + })); + if let Err(panic) = result { + let _ = super::exceptions::throw_panic(env, panic); + } + Ok::<(), jni::errors::Error>(()) }); + let _ = outcome.into_outcome(); } diff --git a/src/droidplug/jni_utils/stream.rs b/src/droidplug/jni_utils/stream.rs index c108247c..512918d9 100644 --- a/src/droidplug/jni_utils/stream.rs +++ b/src/droidplug/jni_utils/stream.rs @@ -1,145 +1,98 @@ use super::task::JPollResult; use ::jni::{ - JNIEnv, JavaVM, - errors::{Error, Result}, - objects::{GlobalRef, JClass, JMethodID, JObject}, - signature::JavaType, + Env, JavaVM, + bind_java_type, + errors::Result, + jni_sig, jni_str, + objects::{Global, JObject}, }; use futures::stream::Stream; use static_assertions::assert_impl_all; use std::{ - convert::TryFrom, pin::Pin, task::{Context, Poll}, }; -/// Wrapper for [`JObject`]s that implement -/// `io.github.gedgygedgy.rust.stream.Stream`. -/// -/// For a [`Send`] version of this, use [`JSendStream`]. -pub struct JStream<'a: 'b, 'b> { - internal: JObject<'a>, - poll_next: JMethodID<'a>, - env: &'b JNIEnv<'a>, +bind_java_type! { + pub JStream => io.github.gedgygedgy.rust.stream.Stream, } -impl<'a: 'b, 'b> JStream<'a, 'b> { - pub fn from_env(env: &'b JNIEnv<'a>, obj: JObject<'a>) -> Result { - let poll_next = env.get_method_id( - JClass::from( - super::classcache::get_class("io/github/gedgygedgy/rust/stream/Stream") - .unwrap() - .as_obj(), - ), - "pollNext", - "(Lio/github/gedgygedgy/rust/task/Waker;)Lio/github/gedgygedgy/rust/task/PollResult;", - )?; - Ok(Self { - internal: obj, - poll_next, - env, - }) - } - - fn j_poll_next(&self, waker: JObject<'a>) -> Result>>> { - let result = self - .env - .call_method_unchecked( - self.internal, - self.poll_next, - JavaType::Object("io/github/gedgygedgy/rust/task/PollResult".to_string()), - &[waker.into()], - )? - .l()?; - let _auto_local = self.env.auto_local(result); - Ok(if self.env.is_same_object(result, JObject::null())? { - Poll::Pending - } else { - Poll::Ready({ - let poll = JPollResult::from_env(self.env, result)?; - let stream_poll_obj = poll.get()?; - if self.env.is_same_object(stream_poll_obj, JObject::null())? { - None - } else { - let stream_poll = JStreamPoll::from_env(self.env, stream_poll_obj)?; - Some(stream_poll.get()?) - } - }) - }) - } - - fn poll_next_internal(&self, context: &mut Context) -> Result>>> { - use super::task::waker; - self.j_poll_next(waker(self.env, context.waker().clone())?) +impl<'local> JStream<'local> { + pub fn poll_next(&self, env: &mut Env<'local>, waker: &JObject<'local>) -> Result> { + env.call_method( + self, + jni_str!("pollNext"), + jni_sig!("(Lio/github/gedgygedgy/rust/task/Waker;)Lio/github/gedgygedgy/rust/task/PollResult;"), + &[waker.into()], + )?.l() } } -impl<'a: 'b, 'b> ::std::ops::Deref for JStream<'a, 'b> { - type Target = JObject<'a>; - - fn deref(&self) -> &Self::Target { - &self.internal - } +bind_java_type! { + pub JStreamPoll => io.github.gedgygedgy.rust.stream.StreamPoll, + methods { + fn get() -> JObject, + }, } -impl<'a: 'b, 'b> From> for JObject<'a> { - fn from(other: JStream<'a, 'b>) -> JObject<'a> { - other.internal - } +pub struct JSendStream { + internal: Global>, + vm: JavaVM, } -impl<'a: 'b, 'b> Stream for JStream<'a, 'b> { - type Item = Result>; +impl JSendStream { + pub fn new(env: &mut Env, stream: &JStream) -> Result { + Ok(Self { + internal: env.new_global_ref(&**stream)?, + vm: env.get_java_vm()?, + }) + } - fn poll_next(self: Pin<&mut Self>, context: &mut Context) -> Poll> { - match self.poll_next_internal(context) { - Ok(Poll::Ready(result)) => Poll::Ready(result.map(|o| Ok(o))), - Ok(Poll::Pending) => Poll::Pending, - Err(err) => Poll::Ready(Some(Err(err))), - } + pub fn from_env(env: &mut Env, obj: &JObject) -> Result { + Ok(Self { + internal: env.new_global_ref(obj)?, + vm: env.get_java_vm()?, + }) } -} -/// [`Send`] version of [`JStream`]. -pub struct JSendStream { - internal: GlobalRef, - vm: JavaVM, -} + fn poll_next_internal( + &self, + context: &mut Context<'_>, + ) -> Result>>>>> { + self.vm.attach_current_thread(|env| { + let jwaker = super::task::waker(env, context.waker().clone())?; + let local = env.new_local_ref(self.internal.as_obj())?; + let jstream = env.cast_local::(local)?; + let result = jstream.poll_next(env, &jwaker)?; + + if env.is_same_object(&result, JObject::null())? { + return Ok(Poll::Pending); + } -impl<'a: 'b, 'b> TryFrom> for JSendStream { - type Error = Error; + let poll_result = env.cast_local::(result)?; + let stream_poll_obj = poll_result.get(env)?; - fn try_from(stream: JStream<'a, 'b>) -> Result { - Ok(Self { - internal: stream.env.new_global_ref(stream.internal)?, - vm: stream.env.get_java_vm()?, + if env.is_same_object(&stream_poll_obj, JObject::null())? { + return Ok(Poll::Ready(None)); + } + + let stream_poll = env.cast_local::(stream_poll_obj)?; + let obj = stream_poll.get(env)?; + Ok(Poll::Ready(Some(Ok(env.new_global_ref(obj)?)))) }) } } impl ::std::ops::Deref for JSendStream { - type Target = GlobalRef; + type Target = Global>; fn deref(&self) -> &Self::Target { &self.internal } } -impl JSendStream { - fn poll_next_internal( - &self, - context: &mut Context<'_>, - ) -> Result>>> { - let env = self.vm.get_env()?; - let jstream = JStream::from_env(&env, self.internal.as_obj())?; - jstream - .poll_next_internal(context) - .map(|result| result.map(|result| result.map(|obj| env.new_global_ref(obj)))) - } -} - impl Stream for JSendStream { - type Item = Result; + type Item = Result>>; fn poll_next(self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll> { match self.poll_next_internal(context) { @@ -151,47 +104,12 @@ impl Stream for JSendStream { assert_impl_all!(JSendStream: Send); -struct JStreamPoll<'a: 'b, 'b> { - internal: JObject<'a>, - get: JMethodID<'a>, - env: &'b JNIEnv<'a>, -} - -impl<'a: 'b, 'b> JStreamPoll<'a, 'b> { - pub fn from_env(env: &'b JNIEnv<'a>, obj: JObject<'a>) -> Result { - let get = env.get_method_id( - JClass::from( - super::classcache::get_class("io/github/gedgygedgy/rust/stream/StreamPoll") - .unwrap() - .as_obj(), - ), - "get", - "()Ljava/lang/Object;", - )?; - Ok(Self { - internal: obj, - get, - env, - }) - } - - pub fn get(&self) -> Result> { - self.env - .call_method_unchecked( - self.internal, - self.get, - JavaType::Object("java/lang/Object".into()), - &[], - )? - .l() - } -} - #[cfg(test)] mod test { use super::super::test_utils; - use super::JStream; + use super::{JSendStream, JStream}; use futures::stream::Stream; + use jni::{jni_sig, jni_str}; use std::{ pin::Pin, task::{Context, Poll}, @@ -201,7 +119,7 @@ mod test { fn test_jstream() { use std::sync::Arc; - test_utils::JVM_ENV.with(|env| { + test_utils::with_env(|env| { let data = Arc::new(test_utils::TestWakerData::new()); assert_eq!(Arc::strong_count(&data), 1); assert_eq!(data.value(), false); @@ -211,9 +129,11 @@ mod test { assert_eq!(data.value(), false); let stream_obj = env - .new_object("io/github/gedgygedgy/rust/stream/QueueStream", "()V", &[]) + .new_object(jni_str!("io/github/gedgygedgy/rust/stream/QueueStream"), jni_sig!("()V"), &[]) .unwrap(); - let mut stream = JStream::from_env(env, stream_obj).unwrap(); + let stream_local = env.new_local_ref(&stream_obj).unwrap(); + let jstream = env.cast_local::(stream_local).unwrap(); + let mut stream = JSendStream::new(env, &jstream).unwrap(); assert!( Pin::new(&mut stream) @@ -223,23 +143,32 @@ mod test { assert_eq!(Arc::strong_count(&data), 3); assert_eq!(data.value(), false); - let obj1 = env.new_object("java/lang/Object", "()V", &[]).unwrap(); - env.call_method(stream_obj, "add", "(Ljava/lang/Object;)V", &[obj1.into()]) - .unwrap(); + let obj1 = env.new_object(jni_str!("java/lang/Object"), jni_sig!("()V"), &[]).unwrap(); + env.call_method( + &stream_obj, + jni_str!("add"), + jni_sig!("(Ljava/lang/Object;)V"), + &[(&obj1).into()], + ) + .unwrap(); assert_eq!(Arc::strong_count(&data), 2); assert_eq!(data.value(), true); data.set_value(false); - let obj2 = env.new_object("java/lang/Object", "()V", &[]).unwrap(); - env.call_method(stream_obj, "add", "(Ljava/lang/Object;)V", &[obj2.into()]) - .unwrap(); + let obj2 = env.new_object(jni_str!("java/lang/Object"), jni_sig!("()V"), &[]).unwrap(); + env.call_method( + &stream_obj, + jni_str!("add"), + jni_sig!("(Ljava/lang/Object;)V"), + &[(&obj2).into()], + ) + .unwrap(); assert_eq!(Arc::strong_count(&data), 2); assert_eq!(data.value(), false); - data.set_value(false); let poll = Pin::new(&mut stream).poll_next(&mut Context::from_waker(&waker)); if let Poll::Ready(Some(Ok(actual_obj1))) = poll { - assert!(env.is_same_object(actual_obj1, obj1).unwrap()); + assert!(env.is_same_object(actual_obj1.as_obj(), &obj1).unwrap()); } else { panic!("Poll result should be ready"); } @@ -248,7 +177,7 @@ mod test { let poll = Pin::new(&mut stream).poll_next(&mut Context::from_waker(&waker)); if let Poll::Ready(Some(Ok(actual_obj2))) = poll { - assert!(env.is_same_object(actual_obj2, obj2).unwrap()); + assert!(env.is_same_object(actual_obj2.as_obj(), &obj2).unwrap()); } else { panic!("Poll result should be ready"); } @@ -263,7 +192,7 @@ mod test { assert_eq!(Arc::strong_count(&data), 3); assert_eq!(data.value(), false); - env.call_method(stream_obj, "finish", "()V", &[]).unwrap(); + env.call_method(&stream_obj, jni_str!("finish"), jni_sig!("()V"), &[]).unwrap(); assert_eq!(Arc::strong_count(&data), 2); assert_eq!(data.value(), true); data.set_value(false); @@ -275,91 +204,138 @@ mod test { } assert_eq!(Arc::strong_count(&data), 2); assert_eq!(data.value(), false); - }); + + Ok(()) + }).unwrap(); } #[test] fn test_jstream_await() { use futures::{executor::block_on, join}; - test_utils::JVM_ENV.with(|env| { + let (mut stream, stream_obj_global, obj1_global, obj2_global) = test_utils::with_env(|env| { let stream_obj = env - .new_object("io/github/gedgygedgy/rust/stream/QueueStream", "()V", &[]) + .new_object(jni_str!("io/github/gedgygedgy/rust/stream/QueueStream"), jni_sig!("()V"), &[]) .unwrap(); - let mut stream = JStream::from_env(env, stream_obj).unwrap(); - let obj1 = env.new_object("java/lang/Object", "()V", &[]).unwrap(); - let obj2 = env.new_object("java/lang/Object", "()V", &[]).unwrap(); - - block_on(async { - join!( - async { - env.call_method(stream_obj, "add", "(Ljava/lang/Object;)V", &[obj1.into()]) - .unwrap(); - env.call_method(stream_obj, "add", "(Ljava/lang/Object;)V", &[obj2.into()]) - .unwrap(); - env.call_method(stream_obj, "finish", "()V", &[]).unwrap(); - }, - async { - use futures::StreamExt; - assert!( - env.is_same_object(stream.next().await.unwrap().unwrap(), obj1) - .unwrap() - ); - assert!( - env.is_same_object(stream.next().await.unwrap().unwrap(), obj2) - .unwrap() - ); - assert!(stream.next().await.is_none()); - } - ); - }); + let stream_obj_global = env.new_global_ref(&stream_obj).unwrap(); + let stream_local = env.new_local_ref(&stream_obj).unwrap(); + let jstream = env.cast_local::(stream_local).unwrap(); + let stream = JSendStream::new(env, &jstream).unwrap(); + let obj1 = env.new_object(jni_str!("java/lang/Object"), jni_sig!("()V"), &[]).unwrap(); + let obj1_global = env.new_global_ref(&obj1).unwrap(); + let obj2 = env.new_object(jni_str!("java/lang/Object"), jni_sig!("()V"), &[]).unwrap(); + let obj2_global = env.new_global_ref(&obj2).unwrap(); + Ok((stream, stream_obj_global, obj1_global, obj2_global)) + }).unwrap(); + + block_on(async { + join!( + async { + test_utils::with_env(|env| { + let s = env.new_local_ref(stream_obj_global.as_obj()).unwrap(); + let o1 = env.new_local_ref(obj1_global.as_obj()).unwrap(); + let o2 = env.new_local_ref(obj2_global.as_obj()).unwrap(); + env.call_method( + &s, + jni_str!("add"), + jni_sig!("(Ljava/lang/Object;)V"), + &[(&o1).into()], + ) + .unwrap(); + env.call_method( + &s, + jni_str!("add"), + jni_sig!("(Ljava/lang/Object;)V"), + &[(&o2).into()], + ) + .unwrap(); + env.call_method(&s, jni_str!("finish"), jni_sig!("()V"), &[]).unwrap(); + Ok(()) + }).unwrap(); + }, + async { + use futures::StreamExt; + let g1 = stream.next().await.unwrap().unwrap(); + test_utils::with_env(|env| { + let o1 = env.new_local_ref(obj1_global.as_obj()).unwrap(); + assert!(env.is_same_object(g1.as_obj(), &o1).unwrap()); + Ok(()) + }).unwrap(); + + let g2 = stream.next().await.unwrap().unwrap(); + test_utils::with_env(|env| { + let o2 = env.new_local_ref(obj2_global.as_obj()).unwrap(); + assert!(env.is_same_object(g2.as_obj(), &o2).unwrap()); + Ok(()) + }).unwrap(); + + assert!(stream.next().await.is_none()); + } + ); }); } #[test] fn test_jsendstream_await() { - use super::JSendStream; use futures::{executor::block_on, join}; - use std::convert::TryInto; - test_utils::JVM_ENV.with(|env| { + let (mut stream, stream_obj_global, obj1_global, obj2_global) = test_utils::with_env(|env| { let stream_obj = env - .new_object("io/github/gedgygedgy/rust/stream/QueueStream", "()V", &[]) + .new_object(jni_str!("io/github/gedgygedgy/rust/stream/QueueStream"), jni_sig!("()V"), &[]) .unwrap(); - let stream = JStream::from_env(env, stream_obj).unwrap(); - let mut stream: JSendStream = stream.try_into().unwrap(); - let obj1 = env.new_object("java/lang/Object", "()V", &[]).unwrap(); - let obj2 = env.new_object("java/lang/Object", "()V", &[]).unwrap(); - - block_on(async { - join!( - async { - env.call_method(stream_obj, "add", "(Ljava/lang/Object;)V", &[obj1.into()]) - .unwrap(); - env.call_method(stream_obj, "add", "(Ljava/lang/Object;)V", &[obj2.into()]) - .unwrap(); - env.call_method(stream_obj, "finish", "()V", &[]).unwrap(); - }, - async { - use futures::StreamExt; - assert!( - env.is_same_object( - stream.next().await.unwrap().unwrap().as_obj(), - obj1 - ) - .unwrap() - ); - assert!( - env.is_same_object( - stream.next().await.unwrap().unwrap().as_obj(), - obj2 - ) - .unwrap() - ); - assert!(stream.next().await.is_none()); - } - ); - }); + let stream_obj_global = env.new_global_ref(&stream_obj).unwrap(); + let stream = JSendStream::from_env(env, &stream_obj).unwrap(); + let obj1 = env.new_object(jni_str!("java/lang/Object"), jni_sig!("()V"), &[]).unwrap(); + let obj1_global = env.new_global_ref(&obj1).unwrap(); + let obj2 = env.new_object(jni_str!("java/lang/Object"), jni_sig!("()V"), &[]).unwrap(); + let obj2_global = env.new_global_ref(&obj2).unwrap(); + Ok((stream, stream_obj_global, obj1_global, obj2_global)) + }).unwrap(); + + block_on(async { + join!( + async { + test_utils::with_env(|env| { + let s = env.new_local_ref(stream_obj_global.as_obj()).unwrap(); + let o1 = env.new_local_ref(obj1_global.as_obj()).unwrap(); + let o2 = env.new_local_ref(obj2_global.as_obj()).unwrap(); + env.call_method( + &s, + jni_str!("add"), + jni_sig!("(Ljava/lang/Object;)V"), + &[(&o1).into()], + ) + .unwrap(); + env.call_method( + &s, + jni_str!("add"), + jni_sig!("(Ljava/lang/Object;)V"), + &[(&o2).into()], + ) + .unwrap(); + env.call_method(&s, jni_str!("finish"), jni_sig!("()V"), &[]).unwrap(); + Ok(()) + }).unwrap(); + }, + async { + use futures::StreamExt; + let g1 = stream.next().await.unwrap().unwrap(); + test_utils::with_env(|env| { + let o1 = env.new_local_ref(obj1_global.as_obj()).unwrap(); + assert!(env.is_same_object(g1.as_obj(), &o1).unwrap()); + Ok(()) + }).unwrap(); + + let g2 = stream.next().await.unwrap().unwrap(); + test_utils::with_env(|env| { + let o2 = env.new_local_ref(obj2_global.as_obj()).unwrap(); + assert!(env.is_same_object(g2.as_obj(), &o2).unwrap()); + Ok(()) + }).unwrap(); + + assert!(stream.next().await.is_none()); + } + ); }); } } diff --git a/src/droidplug/jni_utils/task.rs b/src/droidplug/jni_utils/task.rs index c0b3c038..587dba93 100644 --- a/src/droidplug/jni_utils/task.rs +++ b/src/droidplug/jni_utils/task.rs @@ -1,87 +1,44 @@ use ::jni::{ - JNIEnv, + Env, + bind_java_type, errors::Result, - objects::{JClass, JMethodID, JObject}, - signature::JavaType, + jni_sig, + objects::{JObject, Reference}, }; use std::task::Waker; -/// Wraps the given waker in a `io.github.gedgygedgy.rust.task.Waker` object. -pub fn waker<'a: 'b, 'b>(env: &'b JNIEnv<'a>, waker: Waker) -> Result> { +bind_java_type! { + pub JWaker => io.github.gedgygedgy.rust.task.Waker, +} + +pub fn waker<'a>(env: &mut Env<'a>, waker: Waker) -> Result> { let runnable = super::ops::fn_once_runnable(env, |_e, _o| waker.wake())?; + let class = ::lookup_class(env, &Default::default())?; let obj = env.new_object( - JClass::from( - super::classcache::get_class("io/github/gedgygedgy/rust/task/Waker") - .unwrap() - .as_obj(), - ), - "(Lio/github/gedgygedgy/rust/ops/FnRunnable;)V", - &[runnable.into()], + &*class, + jni_sig!("(Lio/github/gedgygedgy/rust/ops/FnRunnable;)V"), + &[(&runnable).into()], )?; Ok(obj) } -/// Wrapper for [`JObject`]s that implement -/// `io.github.gedgygedgy.rust.task.PollResult`. -pub struct JPollResult<'a: 'b, 'b> { - internal: JObject<'a>, - get: JMethodID<'a>, - env: &'b JNIEnv<'a>, -} - -impl<'a: 'b, 'b> JPollResult<'a, 'b> { - pub fn from_env(env: &'b JNIEnv<'a>, obj: JObject<'a>) -> Result { - let get = env.get_method_id( - JClass::from( - super::classcache::get_class("io/github/gedgygedgy/rust/task/PollResult") - .unwrap() - .as_obj(), - ), - "get", - "()Ljava/lang/Object;", - )?; - Ok(Self { - internal: obj, - get, - env, - }) - } - - pub fn get(&self) -> Result> { - self.env - .call_method_unchecked( - self.internal, - self.get, - JavaType::Object("java/lang/Object".into()), - &[], - )? - .l() - } -} - -impl<'a: 'b, 'b> ::std::ops::Deref for JPollResult<'a, 'b> { - type Target = JObject<'a>; - - fn deref(&self) -> &Self::Target { - &self.internal - } -} - -impl<'a: 'b, 'b> From> for JObject<'a> { - fn from(other: JPollResult<'a, 'b>) -> JObject<'a> { - other.internal - } +bind_java_type! { + pub JPollResult => io.github.gedgygedgy.rust.task.PollResult, + methods { + fn get() -> JObject, + }, } #[cfg(test)] mod test { use super::super::test_utils; + use jni::{jni_sig, jni_str}; use std::sync::Arc; #[test] fn test_waker_wake() { - test_utils::JVM_ENV.with(|env| { + test_utils::with_env(|env| { let data = Arc::new(test_utils::TestWakerData::new()); assert_eq!(Arc::strong_count(&data), 1); assert_eq!(data.value(), false); @@ -94,20 +51,21 @@ mod test { assert_eq!(Arc::strong_count(&data), 2); assert_eq!(data.value(), false); - env.call_method(jwaker, "wake", "()V", &[]).unwrap(); + env.call_method(&jwaker, jni_str!("wake"), jni_sig!("()V"), &[]).unwrap(); assert_eq!(Arc::strong_count(&data), 1); assert_eq!(data.value(), true); data.set_value(false); - env.call_method(jwaker, "wake", "()V", &[]).unwrap(); + env.call_method(&jwaker, jni_str!("wake"), jni_sig!("()V"), &[]).unwrap(); assert_eq!(Arc::strong_count(&data), 1); assert_eq!(data.value(), false); - }); + Ok(()) + }).unwrap(); } #[test] fn test_waker_close_wake() { - test_utils::JVM_ENV.with(|env| { + test_utils::with_env(|env| { let data = Arc::new(test_utils::TestWakerData::new()); assert_eq!(Arc::strong_count(&data), 1); assert_eq!(data.value(), false); @@ -120,13 +78,14 @@ mod test { assert_eq!(Arc::strong_count(&data), 2); assert_eq!(data.value(), false); - env.call_method(jwaker, "close", "()V", &[]).unwrap(); + env.call_method(&jwaker, jni_str!("close"), jni_sig!("()V"), &[]).unwrap(); assert_eq!(Arc::strong_count(&data), 1); assert_eq!(data.value(), false); - env.call_method(jwaker, "wake", "()V", &[]).unwrap(); + env.call_method(&jwaker, jni_str!("wake"), jni_sig!("()V"), &[]).unwrap(); assert_eq!(Arc::strong_count(&data), 1); assert_eq!(data.value(), false); - }); + Ok(()) + }).unwrap(); } } diff --git a/src/droidplug/jni_utils/uuid.rs b/src/droidplug/jni_utils/uuid.rs index d5fdf6fa..840c1538 100644 --- a/src/droidplug/jni_utils/uuid.rs +++ b/src/droidplug/jni_utils/uuid.rs @@ -1,97 +1,45 @@ use jni::{ - JNIEnv, + Env, + bind_java_type, errors::Result, - objects::{AutoLocal, JMethodID, JObject}, - signature::{JavaType, Primitive}, sys::jlong, }; use uuid::Uuid; -/// Wrapper for [`JObject`]s that contain `java.util.UUID`. Provides methods -/// to convert to and from a [`Uuid`]. -pub struct JUuid<'a: 'b, 'b> { - internal: JObject<'a>, - get_least_significant_bits: JMethodID<'a>, - get_most_significant_bits: JMethodID<'a>, - env: &'b JNIEnv<'a>, +bind_java_type! { + pub JUuid => java.util.UUID, + constructors { + fn with_bits(most_significant_bits: jlong, least_significant_bits: jlong), + }, + methods { + fn get_least_significant_bits() -> jlong, + fn get_most_significant_bits() -> jlong, + }, } -impl<'a: 'b, 'b> JUuid<'a, 'b> { - pub fn from_env(env: &'b JNIEnv<'a>, obj: JObject<'a>) -> Result { - let class = env.auto_local(env.find_class("java/util/UUID")?); - Self::from_env_impl(env, obj, class) - } - - pub fn new(env: &'b JNIEnv<'a>, uuid: Uuid) -> Result { +impl JUuid<'_> { + pub fn new<'local>(env: &mut Env<'local>, uuid: Uuid) -> Result> { let val = uuid.as_u128(); let least = (val & 0xFFFFFFFFFFFFFFFF) as jlong; let most = ((val >> 64) & 0xFFFFFFFFFFFFFFFF) as jlong; - - let class = env.auto_local(env.find_class("java/util/UUID")?); - let obj = env.new_object(&class, "(JJ)V", &[most.into(), least.into()])?; - Self::from_env_impl(env, obj, class) + JUuid::with_bits(env, most, least) } +} - pub fn as_uuid(&self) -> Result { - let least = self - .env - .call_method_unchecked( - self.internal, - self.get_least_significant_bits, - JavaType::Primitive(Primitive::Long), - &[], - )? - .j()? as u64; - let most = self - .env - .call_method_unchecked( - self.internal, - self.get_most_significant_bits, - JavaType::Primitive(Primitive::Long), - &[], - )? - .j()? as u64; +impl<'local> JUuid<'local> { + pub fn as_uuid(&self, env: &mut Env<'local>) -> Result { + let least = self.get_least_significant_bits(env)? as u64; + let most = self.get_most_significant_bits(env)? as u64; let val = ((most as u128) << 64) | (least as u128); Ok(Uuid::from_u128(val)) } - - fn from_env_impl( - env: &'b JNIEnv<'a>, - obj: JObject<'a>, - class: AutoLocal<'a, 'b>, - ) -> Result { - let get_least_significant_bits = - env.get_method_id(&class, "getLeastSignificantBits", "()J")?; - let get_most_significant_bits = - env.get_method_id(&class, "getMostSignificantBits", "()J")?; - Ok(Self { - internal: obj, - get_least_significant_bits, - get_most_significant_bits, - env, - }) - } -} - -impl<'a: 'b, 'b> ::std::ops::Deref for JUuid<'a, 'b> { - type Target = JObject<'a>; - - fn deref(&self) -> &Self::Target { - &self.internal - } -} - -impl<'a: 'b, 'b> From> for JObject<'a> { - fn from(other: JUuid<'a, 'b>) -> JObject<'a> { - other.internal - } } #[cfg(test)] mod test { use super::super::test_utils; use super::JUuid; - use jni::{objects::JObject, sys::jlong}; + use jni::{jni_str, jni_sig, objects::JObject, sys::jlong}; use uuid::Uuid; struct UuidTest { @@ -115,7 +63,7 @@ mod test { #[test] fn test_uuid_new() { - test_utils::JVM_ENV.with(|env| { + test_utils::with_env(|env| { for test in TESTS { let most = test.most as jlong; let least = test.least as jlong; @@ -124,35 +72,37 @@ mod test { let obj: JObject = uuid_obj.into(); let actual_most = env - .call_method(obj, "getMostSignificantBits", "()J", &[]) + .call_method(&obj, jni_str!("getMostSignificantBits"), jni_sig!("()J"), &[]) .unwrap() .j() .unwrap(); let actual_least = env - .call_method(obj, "getLeastSignificantBits", "()J", &[]) + .call_method(&obj, jni_str!("getLeastSignificantBits"), jni_sig!("()J"), &[]) .unwrap() .j() .unwrap(); assert_eq!(actual_most, most); assert_eq!(actual_least, least); } - }); + Ok(()) + }).unwrap(); } #[test] fn test_uuid_as_uuid() { - test_utils::JVM_ENV.with(|env| { + test_utils::with_env(|env| { for test in TESTS { let most = test.most as jlong; let least = test.least as jlong; let obj = env - .new_object("java/util/UUID", "(JJ)V", &[most.into(), least.into()]) + .new_object(jni_str!("java/util/UUID"), jni_sig!("(JJ)V"), &[most.into(), least.into()]) .unwrap(); - let uuid_obj = JUuid::from_env(env, obj).unwrap(); + let uuid_obj = env.cast_local::(obj).unwrap(); - assert_eq!(uuid_obj.as_uuid().unwrap(), Uuid::from_u128(test.uuid)); + assert_eq!(uuid_obj.as_uuid(env).unwrap(), Uuid::from_u128(test.uuid)); } - }); + Ok(()) + }).unwrap(); } } diff --git a/src/droidplug/mod.rs b/src/droidplug/mod.rs index eed9db6a..220ba518 100644 --- a/src/droidplug/mod.rs +++ b/src/droidplug/mod.rs @@ -2,7 +2,7 @@ pub mod adapter; pub mod manager; pub mod peripheral; -use ::jni::JNIEnv; +use ::jni::Env; use once_cell::sync::OnceCell; mod jni; @@ -10,7 +10,7 @@ mod jni_utils; static GLOBAL_ADAPTER: OnceCell = OnceCell::new(); -pub fn init(env: &JNIEnv) -> crate::Result<()> { +pub fn init(env: &mut Env) -> crate::Result<()> { self::jni::init(env)?; GLOBAL_ADAPTER.get_or_try_init(|| adapter::Adapter::new())?; Ok(()) diff --git a/src/droidplug/peripheral.rs b/src/droidplug/peripheral.rs index 8554376e..13361b78 100644 --- a/src/droidplug/peripheral.rs +++ b/src/droidplug/peripheral.rs @@ -1,6 +1,5 @@ use super::jni_utils::{ arrays::byte_array_to_vec, - exceptions::try_block, future::{JFuture, JSendFuture}, stream::JSendStream, task::JPollResult, @@ -16,8 +15,8 @@ use crate::{ use async_trait::async_trait; use futures::stream::Stream; use jni::{ - JNIEnv, - objects::{GlobalRef, JList, JObject}, + Env, jni_sig, jni_str, + objects::{Global, JObject, JString, JValue}, }; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; @@ -25,19 +24,16 @@ use serde::{Deserialize, Serialize}; use serde_cr as serde; use std::{ collections::BTreeSet, - convert::TryFrom, fmt::{self, Debug, Display, Formatter}, pin::Pin, sync::atomic::{AtomicU16, Ordering}, sync::{Arc, Mutex}, }; -use uuid::Uuid; - use super::jni::{ - global_jvm, + jvm, objects::{JBluetoothGattCharacteristic, JBluetoothGattService, JPeripheral}, }; -use jni::objects::JClass; + #[cfg_attr( feature = "serde", derive(Serialize, Deserialize), @@ -51,107 +47,70 @@ impl Display for PeripheralId { } } -fn get_poll_result<'a: 'b, 'b>( - env: &'b JNIEnv<'a>, - result: JPollResult<'a, 'b>, +fn get_poll_result<'a>( + env: &mut Env<'a>, + result_ref: &Global>, ) -> Result> { - try_block(env, || Ok(Ok(result.get()?))) - .catch( - JClass::from( - super::jni_utils::classcache::get_class( - "io/github/gedgygedgy/rust/future/FutureException", - ) - .unwrap() - .as_obj(), - ), - |ex| { + let result_obj = env.new_local_ref(result_ref)?; + let poll_result = env.cast_local::(result_obj)?; + + match poll_result.get(env) { + Ok(obj) => Ok(obj), + Err(jni::errors::Error::JavaException) => { + let ex = env.exception_occurred().unwrap(); + env.exception_clear(); + + use jni::objects::Reference; + use super::jni::objects::*; + + let future_ex_class = ::lookup_class( + env, &Default::default(), + )?; + + if env.is_instance_of(&ex, &*future_ex_class)? { let cause = env - .call_method(ex, "getCause", "()Ljava/lang/Throwable;", &[])? + .call_method(&ex, jni_str!("getCause"), jni_sig!("()Ljava/lang/Throwable;"), &[])? .l()?; - if env.is_instance_of( - cause, - JClass::from( - super::jni_utils::classcache::get_class( - "com/nonpolynomial/btleplug/android/impl/NotConnectedException", - ) - .unwrap() - .as_obj(), - ), - )? { - Ok(Err(Error::NotConnected)) - } else if env.is_instance_of( - cause, - JClass::from( - super::jni_utils::classcache::get_class( - "com/nonpolynomial/btleplug/android/impl/PermissionDeniedException", - ) - .unwrap() - .as_obj(), - ), - )? { - Ok(Err(Error::PermissionDenied)) - } else if env.is_instance_of( - cause, - JClass::from( - super::jni_utils::classcache::get_class( - "com/nonpolynomial/btleplug/android/impl/UnexpectedCallbackException", - ) - .unwrap() - .as_obj(), - ), - )? { - Ok(Err(Error::UnexpectedCallback)) - } else if env.is_instance_of( - cause, - JClass::from( - super::jni_utils::classcache::get_class( - "com/nonpolynomial/btleplug/android/impl/UnexpectedCharacteristicException", - ) - .unwrap() - .as_obj(), - ), - )? { - Ok(Err(Error::UnexpectedCharacteristic)) - } else if env.is_instance_of( - cause, - JClass::from( - super::jni_utils::classcache::get_class( - "com/nonpolynomial/btleplug/android/impl/NoSuchCharacteristicException", - ) - .unwrap() - .as_obj(), - ), - )? { - Ok(Err(Error::NoSuchCharacteristic)) - } else if env.is_instance_of( - cause, - JClass::from( - super::jni_utils::classcache::get_class( - "com/nonpolynomial/btleplug/android/impl/NoBluetoothAdapterException", - ) - .unwrap() - .as_obj(), - ), - )? { - Ok(Err(Error::NoAdapterAvailable)) - } else if env.is_instance_of( - cause, - "java/lang/RuntimeException", - )? { + + macro_rules! check_exception { + ($type:ty, $env:expr, $cause:expr) => { + $env.is_instance_of( + $cause, + &*<$type as Reference>::lookup_class($env, &Default::default())?, + )? + }; + } + + if check_exception!(JNotConnectedException, env, &cause) { + Err(Error::NotConnected) + } else if check_exception!(JPermissionDeniedException, env, &cause) { + Err(Error::PermissionDenied) + } else if check_exception!(JUnexpectedCallbackException, env, &cause) { + Err(Error::UnexpectedCallback) + } else if check_exception!(JUnexpectedCharacteristicException, env, &cause) { + Err(Error::UnexpectedCharacteristic) + } else if check_exception!(JNoSuchCharacteristicException, env, &cause) { + Err(Error::NoSuchCharacteristic) + } else if check_exception!(JNoBluetoothAdapterException, env, &cause) { + Err(Error::NoAdapterAvailable) + } else if env.is_instance_of(&cause, jni_str!("java/lang/RuntimeException"))? { let msg = env - .call_method(cause, "getMessage", "()Ljava/lang/String;", &[]) - .unwrap() - .l() - .unwrap(); - let msgstr:String = env.get_string(msg.into()).unwrap().into(); - Ok(Err(Error::RuntimeError(msgstr))) + .call_method(&cause, jni_str!("getMessage"), jni_sig!("()Ljava/lang/String;"), &[])? + .l()?; + let jstr = env.cast_local::(msg)?; + let msgstr = String::from(jstr.mutf8_chars(env)?); + Err(Error::RuntimeError(msgstr)) } else { - env.throw(ex)?; - Err(jni::errors::Error::JavaException) + let _ = env.throw(&ex); + Err(jni::errors::Error::JavaException.into()) } - }, - ) - .result()? + } else { + let _ = env.throw(&ex); + Err(jni::errors::Error::JavaException.into()) + } + } + Err(e) => Err(e.into()), + } } #[derive(Debug)] @@ -165,17 +124,18 @@ struct PeripheralShared { #[derive(Clone)] pub struct Peripheral { addr: BDAddr, - internal: GlobalRef, + internal: Arc>>, shared: Arc>, mtu: Arc, } impl Peripheral { - pub(crate) fn new(env: &JNIEnv, adapter: JObject, addr: BDAddr) -> Result { - let obj = JPeripheral::new(env, adapter, addr)?; + pub(crate) fn new<'a>(env: &mut Env<'a>, adapter: JObject<'a>, addr: BDAddr) -> Result { + let obj = JPeripheral::create(env, adapter, addr)?; + let internal = Arc::new(env.new_global_ref(&*obj)?); Ok(Self { addr, - internal: env.new_global_ref(obj)?, + internal, shared: Arc::new(Mutex::new(PeripheralShared { services: BTreeSet::new(), characteristics: BTreeSet::new(), @@ -188,20 +148,18 @@ impl Peripheral { pub(crate) fn report_properties(&self, properties: PeripheralProperties) { let mut guard = self.shared.lock().unwrap(); - guard.properties = Some(properties); } - fn with_obj( + fn with_obj( &self, - f: impl FnOnce(&JNIEnv, JPeripheral) -> std::result::Result, - ) -> std::result::Result - where - E: From<::jni::errors::Error>, - { - let env = global_jvm().get_env()?; - let obj = JPeripheral::from_env(&env, self.internal.as_obj())?; - f(&env, obj) + f: impl for<'env> FnOnce(&mut Env<'env>, &JPeripheral<'env>) -> Result, + ) -> Result { + jvm()?.attach_current_thread(|env| { + let local_obj = env.new_local_ref(self.internal.as_obj())?; + let obj = env.cast_local::(local_obj)?; + f(env, &obj) + }) } async fn set_characteristic_notification( @@ -211,13 +169,11 @@ impl Peripheral { ) -> Result<()> { let future = self.with_obj(|env, obj| { let uuid_obj = JUuid::new(env, characteristic.uuid)?; - JSendFuture::try_from(obj.set_characteristic_notification(uuid_obj, enable)?) + let future = obj.set_characteristic_notification(env, &uuid_obj, enable)?; + Ok(JSendFuture::new(env, &future)?) })?; let result_ref = future.await?; - self.with_obj(|env, _obj| { - let result = JPollResult::from_env(env, result_ref.as_obj())?; - get_poll_result(env, result).map(|_| {}) - }) + self.with_obj(|env, _obj| get_poll_result(env, &result_ref).map(|_| {})) } } @@ -229,7 +185,6 @@ impl Debug for Peripheral { #[async_trait] impl api::Peripheral for Peripheral { - /// Returns the unique identifier of the peripheral. fn id(&self) -> PeripheralId { PeripheralId(self.addr) } @@ -253,19 +208,21 @@ impl api::Peripheral for Peripheral { } async fn is_connected(&self) -> Result { - self.with_obj(|_env, obj| Ok(obj.is_connected()?)) + self.with_obj(|env, obj| Ok(obj.is_connected(env)?)) } async fn connect(&self) -> Result<()> { - let future = self.with_obj(|env, obj| JSendFuture::try_from(obj.connect()?))?; - let result_ref = future.await?; - self.with_obj(|env, _obj| { - let result = JPollResult::from_env(env, result_ref.as_obj())?; - get_poll_result(env, result).map(|_| {}) - })?; + { + let future = self.with_obj(|env, obj| { + let future = obj.connect(env)?; + Ok(JSendFuture::new(env, &future)?) + })?; + let result_ref = future.await?; + self.with_obj(|env, _obj| get_poll_result(env, &result_ref).map(|_| {}))?; + } // Query the system-cached device name and update local_name - self.with_obj(|_env, obj| -> std::result::Result<(), Error> { - if let Ok(Some(name)) = obj.get_device_name() { + self.with_obj(|env, obj| -> std::result::Result<(), Error> { + if let Ok(Some(name)) = obj.get_device_name(env) { let mut guard = self.shared.lock().map_err(Into::::into)?; if let Some(ref mut props) = guard.properties { props.local_name = Some(name); @@ -274,68 +231,72 @@ impl api::Peripheral for Peripheral { Ok(()) })?; // Auto-negotiate maximum MTU (517) after connection - let mtu_future = self.with_obj(|env, obj| { - JSendFuture::try_from(JFuture::from_env(env, obj.request_mtu(517)?)?) - })?; - let mtu_result_ref = mtu_future.await?; - self.with_obj(|env, _obj| -> Result<()> { - let mtu_result = JPollResult::from_env(env, mtu_result_ref.as_obj())?; - let mtu_obj = get_poll_result(env, mtu_result)?; - let mtu_val = env.call_method(mtu_obj, "intValue", "()I", &[])?.i()?; - self.mtu.store(mtu_val as u16, Ordering::Relaxed); - Ok(()) - })?; + { + let mtu_future = self.with_obj(|env, obj| { + let mtu_obj = obj.request_mtu(env, 517)?; + let mtu_future = env.cast_local::(mtu_obj)?; + Ok(JSendFuture::new(env, &mtu_future)?) + })?; + let mtu_result_ref = mtu_future.await?; + self.with_obj(|env, _obj| -> Result<()> { + let mtu_obj = get_poll_result(env, &mtu_result_ref)?; + let mtu_val = env.call_method(&mtu_obj, jni_str!("intValue"), jni_sig!("()I"), &[])?.i()?; + self.mtu.store(mtu_val as u16, Ordering::Relaxed); + Ok(()) + })?; + } Ok(()) } async fn disconnect(&self) -> Result<()> { - let future = self.with_obj(|env, obj| JSendFuture::try_from(obj.disconnect()?))?; + let future = self.with_obj(|env, obj| { + let future = obj.disconnect(env)?; + Ok(JSendFuture::new(env, &future)?) + })?; let result_ref = future.await?; - self.with_obj(|env, _obj| { - let result = JPollResult::from_env(env, result_ref.as_obj())?; - get_poll_result(env, result).map(|_| {}) - }) + self.with_obj(|env, _obj| get_poll_result(env, &result_ref).map(|_| {})) } - /// The set of services we've discovered for this device. This will be empty until - /// `discover_services` is called. fn services(&self) -> BTreeSet { let guard = self.shared.lock().unwrap(); (&guard.services).clone() } async fn discover_services(&self) -> Result<()> { - let future = self.with_obj(|env, obj| JSendFuture::try_from(obj.discover_services()?))?; + let future = self.with_obj(|env, obj| { + let future = obj.discover_services(env)?; + Ok(JSendFuture::new(env, &future)?) + })?; let result_ref = future.await?; self.with_obj(|env, _obj| { use std::iter::FromIterator; - let result = JPollResult::from_env(env, result_ref.as_obj())?; - let obj = get_poll_result(env, result)?; - let list = JList::from_env(env, obj)?; + let obj = get_poll_result(env, &result_ref)?; + let size = env.call_method(&obj, jni_str!("size"), jni_sig!("()I"), &[])?.i()?; let mut peripheral_services = Vec::new(); let mut peripheral_characteristics = Vec::new(); - for service in list.iter()? { - let service = JBluetoothGattService::from_env(env, service)?; + for i in 0..size { + let svc_obj = env + .call_method(&obj, jni_str!("get"), jni_sig!("(I)Ljava/lang/Object;"), &[JValue::from(i)])? + .l()?; + let service = env.cast_local::(svc_obj)?; let mut characteristics = BTreeSet::::new(); - for characteristic in service.get_characteristics()? { + for characteristic in service.get_characteristics(env)? { let mut descriptors = BTreeSet::new(); - for descriptor in characteristic.get_descriptors()? { + for descriptor in characteristic.get_descriptors(env)? { descriptors.insert(Descriptor { - uuid: descriptor.get_uuid()?, - service_uuid: service.get_uuid()?, - characteristic_uuid: characteristic.get_uuid()?, + uuid: descriptor.get_uuid(env)?, + service_uuid: service.get_uuid(env)?, + characteristic_uuid: characteristic.get_uuid(env)?, }); } let char = Characteristic { - service_uuid: service.get_uuid()?, - uuid: characteristic.get_uuid()?, - properties: characteristic.get_properties()?, + service_uuid: service.get_uuid(env)?, + uuid: characteristic.get_uuid(env)?, + properties: characteristic.get_properties(env)?, descriptors: descriptors.clone(), }; - // Only consider the first characteristic of each UUID - // This "should" be unique, but of course it's not enforced if characteristics .iter() .filter(|c| c.service_uuid == char.service_uuid && c.uuid == char.uuid) @@ -347,7 +308,7 @@ impl api::Peripheral for Peripheral { } } peripheral_services.push(Service { - uuid: service.get_uuid()?, + uuid: service.get_uuid(env)?, primary: service.is_primary()?, characteristics, }) @@ -372,25 +333,24 @@ impl api::Peripheral for Peripheral { WriteType::WithResponse => 2, WriteType::WithoutResponse => 1, }; - JSendFuture::try_from(obj.write(uuid, data_obj.into(), write_type)?) + let future = obj.write(env, &uuid, &data_obj.into(), write_type)?; + Ok(JSendFuture::new(env, &future)?) })?; let result_ref = future.await?; - self.with_obj(|env, _obj| { - let result = JPollResult::from_env(env, result_ref.as_obj())?; - get_poll_result(env, result).map(|_| {}) - }) + self.with_obj(|env, _obj| get_poll_result(env, &result_ref).map(|_| {})) } async fn read(&self, characteristic: &Characteristic) -> Result> { let future = self.with_obj(|env, obj| { let uuid = JUuid::new(env, characteristic.uuid)?; - JSendFuture::try_from(obj.read(uuid)?) + let future = obj.read(env, &uuid)?; + Ok(JSendFuture::new(env, &future)?) })?; let result_ref = future.await?; self.with_obj(|env, _obj| { - let result = JPollResult::from_env(env, result_ref.as_obj())?; - let bytes = get_poll_result(env, result)?; - Ok(byte_array_to_vec(env, bytes.into_inner())?) + let bytes_obj = get_poll_result(env, &result_ref)?; + let bytes_arr = unsafe { jni::objects::JByteArray::from_raw(env, bytes_obj.into_raw()) }; + Ok(byte_array_to_vec(env, &bytes_arr)?) }) } @@ -407,33 +367,40 @@ impl api::Peripheral for Peripheral { async fn notifications(&self) -> Result + Send>>> { use futures::stream::StreamExt; let shared = self.shared.clone(); - let stream = self.with_obj(|_env, obj| JSendStream::try_from(obj.get_notifications()?))?; + let stream = self.with_obj(|env, obj| { + let stream = obj.get_notifications(env)?; + Ok(JSendStream::new(env, &stream)?) + })?; let stream = stream .map(move |item| match item { Ok(item) => { - let env = global_jvm().get_env()?; - let item = item.as_obj(); - let characteristic = JBluetoothGattCharacteristic::from_env(&env, item)?; - let uuid = characteristic.get_uuid()?; - let value = characteristic.get_value()?; - let service_uuid = shared - .lock() - .ok() - .and_then(|guard| { - guard - .services - .iter() - .find(|s| s.characteristics.iter().any(|c| c.uuid == uuid)) - .map(|s| s.uuid) + let vm = jvm()?; + let result: crate::Result<_> = vm.attach_current_thread(|env| -> jni::errors::Result<_> { + let local_obj = env.new_local_ref(item.as_obj())?; + let characteristic = + env.cast_local::(local_obj)?; + let uuid = characteristic.get_uuid(env)?; + let value = characteristic.get_value(env)?; + let service_uuid = shared + .lock() + .ok() + .and_then(|guard| { + guard + .services + .iter() + .find(|s| s.characteristics.iter().any(|c| c.uuid == uuid)) + .map(|s| s.uuid) + }) + .unwrap_or_default(); + Ok(ValueNotification { + uuid, + service_uuid, + value, }) - .unwrap_or_default(); - Ok(ValueNotification { - uuid, - service_uuid, - value, - }) + }).map_err(Into::into); + result } - Err(err) => Err(err), + Err(err) => Err(err.into()), }) .filter_map(|item| async { item.ok() }); Ok(Box::pin(stream)) @@ -441,13 +408,14 @@ impl api::Peripheral for Peripheral { async fn read_rssi(&self) -> Result { let future = self.with_obj(|env, obj| { - JSendFuture::try_from(JFuture::from_env(env, obj.read_remote_rssi()?)?) + let rssi_obj = obj.read_remote_rssi(env)?; + let rssi_future = env.cast_local::(rssi_obj)?; + Ok(JSendFuture::new(env, &rssi_future)?) })?; let result_ref = future.await?; self.with_obj(|env, _obj| { - let result = JPollResult::from_env(env, result_ref.as_obj())?; - let rssi_obj = get_poll_result(env, result)?; - let rssi_val = env.call_method(rssi_obj, "intValue", "()I", &[])?.i()?; + let rssi_obj = get_poll_result(env, &result_ref)?; + let rssi_val = env.call_method(&rssi_obj, jni_str!("intValue"), jni_sig!("()I"), &[])?.i()?; Ok(rssi_val as i16) }) } @@ -457,46 +425,45 @@ impl api::Peripheral for Peripheral { let characteristic = JUuid::new(env, descriptor.characteristic_uuid)?; let uuid = JUuid::new(env, descriptor.uuid)?; let data_obj = super::jni_utils::arrays::slice_to_byte_array(env, data)?; - JSendFuture::try_from(obj.write_descriptor(characteristic, uuid, data_obj.into())?) + let future = obj.write_descriptor(env, &characteristic, &uuid, &data_obj.into())?; + Ok(JSendFuture::new(env, &future)?) })?; let result_ref = future.await?; - self.with_obj(|env, _obj| { - let result = JPollResult::from_env(env, result_ref.as_obj())?; - get_poll_result(env, result).map(|_| {}) - }) + self.with_obj(|env, _obj| get_poll_result(env, &result_ref).map(|_| {})) } async fn read_descriptor(&self, descriptor: &Descriptor) -> Result> { let future = self.with_obj(|env, obj| { let characteristic = JUuid::new(env, descriptor.characteristic_uuid)?; let uuid = JUuid::new(env, descriptor.uuid)?; - JSendFuture::try_from(obj.read_descriptor(characteristic, uuid)?) + let future = obj.read_descriptor(env, &characteristic, &uuid)?; + Ok(JSendFuture::new(env, &future)?) })?; let result_ref = future.await?; self.with_obj(|env, _obj| { - let result = JPollResult::from_env(env, result_ref.as_obj())?; - let bytes = get_poll_result(env, result)?; - Ok(byte_array_to_vec(env, bytes.into_inner())?) + let bytes_obj = get_poll_result(env, &result_ref)?; + let bytes_arr = unsafe { jni::objects::JByteArray::from_raw(env, bytes_obj.into_raw()) }; + Ok(byte_array_to_vec(env, &bytes_arr)?) }) } async fn connection_parameters(&self) -> Result> { - self.with_obj(|_env, obj| { + self.with_obj(|env, obj| { Ok(obj - .get_connection_parameters() + .get_connection_parameters(env) .map_err(|e| Error::Other(format!("{:?}", e).into()))?) }) } async fn request_connection_parameters(&self, preset: ConnectionParameterPreset) -> Result<()> { let priority = match preset { - ConnectionParameterPreset::Balanced => 0, // CONNECTION_PRIORITY_BALANCED - ConnectionParameterPreset::ThroughputOptimized => 1, // CONNECTION_PRIORITY_HIGH - ConnectionParameterPreset::PowerOptimized => 2, // CONNECTION_PRIORITY_LOW_POWER + ConnectionParameterPreset::Balanced => 0, + ConnectionParameterPreset::ThroughputOptimized => 1, + ConnectionParameterPreset::PowerOptimized => 2, }; - self.with_obj(|_env, obj| { + self.with_obj(|env, obj| { let success = obj - .request_connection_priority(priority) + .request_connection_priority(env, priority) .map_err(|e| Error::Other(format!("{:?}", e).into()))?; if success { Ok(()) diff --git a/tests/android/rust/Cargo.lock b/tests/android/rust/Cargo.lock index 4d1091fc..ea6c57cf 100644 --- a/tests/android/rust/Cargo.lock +++ b/tests/android/rust/Cargo.lock @@ -69,7 +69,7 @@ dependencies = [ "log", "serde", "serde-xml-rs", - "thiserror 2.0.18", + "thiserror", "tokio", "uuid", ] @@ -100,7 +100,7 @@ dependencies = [ "objc2-foundation", "once_cell", "static_assertions", - "thiserror 2.0.18", + "thiserror", "tokio", "tokio-stream", "uuid", @@ -134,12 +134,6 @@ version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" -[[package]] -name = "cesu8" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" - [[package]] name = "cfg-if" version = "1.0.4" @@ -331,23 +325,52 @@ dependencies = [ [[package]] name = "jni" -version = "0.19.0" +version = "0.22.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6df18c2e3db7e453d3c6ac5b3e9d5182664d28788126d39b91f2d1e22b017ec" +checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" dependencies = [ - "cesu8", + "cfg-if", "combine", + "jni-macros", "jni-sys", "log", - "thiserror 1.0.69", + "simd_cesu8", + "thiserror", "walkdir", + "windows-link", +] + +[[package]] +name = "jni-macros" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3" +dependencies = [ + "proc-macro2", + "quote", + "rustc_version", + "simd_cesu8", + "syn", ] [[package]] name = "jni-sys" -version = "0.3.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" +checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" +dependencies = [ + "jni-sys-macros", +] + +[[package]] +name = "jni-sys-macros" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" +dependencies = [ + "quote", + "syn", +] [[package]] name = "js-sys" @@ -548,6 +571,15 @@ version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + [[package]] name = "rustversion" version = "1.0.22" @@ -569,6 +601,12 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + [[package]] name = "serde" version = "1.0.228" @@ -587,7 +625,7 @@ checksum = "cc2215ce3e6a77550b80a1c37251b7d294febaf42e36e21b7b411e0bf54d540d" dependencies = [ "log", "serde", - "thiserror 2.0.18", + "thiserror", "xml", ] @@ -621,6 +659,22 @@ dependencies = [ "libc", ] +[[package]] +name = "simd_cesu8" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94f90157bb87cddf702797c5dadfa0be7d266cdf49e22da2fcaa32eff75b2c33" +dependencies = [ + "rustc_version", + "simdutf8", +] + +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + [[package]] name = "slab" version = "0.4.12" @@ -660,33 +714,13 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "thiserror" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" -dependencies = [ - "thiserror-impl 1.0.69", -] - [[package]] name = "thiserror" version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ - "thiserror-impl 2.0.18", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" -dependencies = [ - "proc-macro2", - "quote", - "syn", + "thiserror-impl", ] [[package]] diff --git a/tests/android/rust/Cargo.toml b/tests/android/rust/Cargo.toml index 9afc3077..0ebfb89b 100644 --- a/tests/android/rust/Cargo.toml +++ b/tests/android/rust/Cargo.toml @@ -9,7 +9,7 @@ crate-type = ["cdylib"] [dependencies] btleplug = { path = "../../.." } -jni = "0.19" +jni = "0.22" once_cell = "1" tokio = { version = "1", features = ["full"] } uuid = "1" diff --git a/tests/android/rust/src/lib.rs b/tests/android/rust/src/lib.rs index f56d58aa..a57ef926 100644 --- a/tests/android/rust/src/lib.rs +++ b/tests/android/rust/src/lib.rs @@ -41,7 +41,7 @@ pub fn find_descriptor( } use jni::objects::JClass; -use jni::JNIEnv; +use jni::{Env, EnvUnowned, jni_str}; use std::sync::OnceLock; use tokio::runtime::Runtime; @@ -58,7 +58,7 @@ fn runtime() -> &'static Runtime { const TEST_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(55); /// Run a test function on the global runtime, converting panics to JNI exceptions. -fn run_test(env: &JNIEnv, test_name: &str, f: impl std::future::Future) { +fn run_test(env: &mut Env, test_name: &str, f: impl std::future::Future) { log::info!("[START] {}", test_name); let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { runtime().block_on(async { @@ -78,7 +78,8 @@ fn run_test(env: &JNIEnv, test_name: &str, f: impl std::future::Future(()) + }); + let _ = outcome.into_outcome(); } // ── Test JNI exports ──────────────────────────────────────────────── @@ -105,8 +110,12 @@ pub extern "system" fn Java_com_nonpolynomial_btleplug_test_NativeTests_initBtle macro_rules! jni_test { ($jni_name:ident, $test_fn:path) => { #[unsafe(no_mangle)] - pub extern "system" fn $jni_name(env: JNIEnv, _class: JClass) { - run_test(&env, stringify!($test_fn), $test_fn()); + pub extern "system" fn $jni_name(mut env: EnvUnowned, _class: JClass) { + let outcome = env.with_env(|env| { + run_test(env, stringify!($test_fn), $test_fn()); + Ok::<_, jni::errors::Error>(()) + }); + let _ = outcome.into_outcome(); } }; } diff --git a/tests/common/peripheral_finder.rs b/tests/common/peripheral_finder.rs index 3806c3cb..9d54f224 100644 --- a/tests/common/peripheral_finder.rs +++ b/tests/common/peripheral_finder.rs @@ -33,24 +33,36 @@ pub async fn get_adapter() -> &'static Adapter { std::thread::Builder::new() .name("btleplug-test-adapter".into()) .spawn(move || { - let rt = tokio::runtime::Builder::new_current_thread() - .enable_all() - .build() - .expect("failed to create adapter runtime"); - rt.block_on(async { - let manager = Manager::new().await.expect("failed to create BLE manager"); - let adapters = manager.adapters().await.expect("failed to get adapters"); - // Leak the manager so it (and the underlying CBCentralManager) - // lives forever. OnceCell keeps the Adapter alive; we need the - // Manager alive too since the Adapter borrows from it internally - // on some platforms. - std::mem::forget(manager); - let adapter = adapters.into_iter().next().expect("no BLE adapters found"); - tx.send(adapter).ok(); - // Block forever so the runtime (and its spawned event loop) - // stays alive. - std::future::pending::<()>().await; - }); + log::info!("adapter thread: starting"); + let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { + let rt = tokio::runtime::Builder::new_current_thread() + .enable_all() + .build() + .expect("failed to create adapter runtime"); + rt.block_on(async { + log::info!("adapter thread: creating manager"); + let manager = Manager::new().await.expect("failed to create BLE manager"); + log::info!("adapter thread: getting adapters"); + let adapters = manager.adapters().await.expect("failed to get adapters"); + log::info!("adapter thread: got {} adapters", adapters.len()); + std::mem::forget(manager); + let adapter = adapters.into_iter().next().expect("no BLE adapters found"); + log::info!("adapter thread: sending adapter"); + tx.send(adapter).ok(); + log::info!("adapter thread: blocking forever"); + std::future::pending::<()>().await; + }); + })); + if let Err(panic) = result { + let msg = if let Some(s) = panic.downcast_ref::<&str>() { + s.to_string() + } else if let Some(s) = panic.downcast_ref::() { + s.clone() + } else { + "unknown panic".to_string() + }; + log::error!("adapter thread PANICKED: {}", msg); + } }) .expect("failed to spawn adapter thread"); rx.await