From 1135250219c3ea8caa40fd3f165ad3511558bd8f Mon Sep 17 00:00:00 2001 From: Kyle Machulis Date: Sat, 25 Apr 2026 17:47:22 -0700 Subject: [PATCH 01/16] build: Upgrade jni from 0.19 to 0.20 Breaking changes addressed: - call_method_unchecked takes &[jni::sys::jvalue] instead of &[JValue]; added JValue::to_jni() at all ~40 call sites - JavaType replaced by ReturnType in call_method_unchecked return type parameter (Object/Array/Primitive instead of carrying class strings) - JMethodID no longer has a lifetime parameter - JObject::into_inner() renamed to into_raw() - set_rust_field/get_rust_field/take_rust_field marked unsafe; wrapped all 9 call sites in unsafe blocks Co-Authored-By: Claude Opus 4.6 --- Cargo.lock | 8 +- Cargo.toml | 4 +- src/droidplug/adapter.rs | 6 +- src/droidplug/jni/objects.rs | 254 ++++++++++---------------- src/droidplug/jni_utils/exceptions.rs | 12 +- src/droidplug/jni_utils/future.rs | 10 +- src/droidplug/jni_utils/ops.rs | 6 +- src/droidplug/jni_utils/stream.rs | 19 +- src/droidplug/jni_utils/task.rs | 11 +- src/droidplug/jni_utils/uuid.rs | 10 +- src/droidplug/peripheral.rs | 4 +- tests/android/rust/Cargo.toml | 2 +- 12 files changed, 141 insertions(+), 205 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b586906e..3800e7a8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -401,7 +401,7 @@ checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" dependencies = [ "hermit-abi", "libc", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -421,9 +421,9 @@ checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" [[package]] name = "jni" -version = "0.19.0" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6df18c2e3db7e453d3c6ac5b3e9d5182664d28788126d39b91f2d1e22b017ec" +checksum = "039022cdf4d7b1cf548d31f60ae783138e5fd42013f6271049d7df7afadef96c" dependencies = [ "cesu8", "combine", @@ -1103,7 +1103,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..627bc5eb 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.20.0" once_cell = "1.21.3" [target.'cfg(not(target_os = "android"))'.dependencies] -jni = { version = "0.19.0", optional = true } +jni = { version = "0.20.0", 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..1ad9f5f4 100644 --- a/src/droidplug/adapter.rs +++ b/src/droidplug/adapter.rs @@ -55,7 +55,7 @@ impl Adapter { manager: Arc::new(AdapterManager::default()), internal, }; - env.set_rust_field(obj, "handle", adapter.clone())?; + unsafe { env.set_rust_field(obj, "handle", adapter.clone()) }?; Ok(adapter) } @@ -205,7 +205,7 @@ pub(crate) fn adapter_report_scan_result_internal( obj: JObject, scan_result: JObject, ) -> crate::Result<()> { - let adapter = env.get_rust_field::<_, _, Adapter>(obj, "handle")?; + let adapter = unsafe { env.get_rust_field::<_, _, Adapter>(obj, "handle") }?; adapter.report_scan_result(scan_result)?; Ok(()) } @@ -216,7 +216,7 @@ pub(crate) fn adapter_on_connection_state_changed_internal( addr: JString, connected: jboolean, ) -> crate::Result<()> { - let adapter = env.get_rust_field::<_, _, Adapter>(obj, "handle")?; + let adapter = unsafe { 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)?; diff --git a/src/droidplug/jni/objects.rs b/src/droidplug/jni/objects.rs index 6e5e051a..839d608e 100644 --- a/src/droidplug/jni/objects.rs +++ b/src/droidplug/jni/objects.rs @@ -2,8 +2,8 @@ use crate::droidplug::jni_utils::{future::JFuture, stream::JStream, uuid::JUuid} use jni::{ JNIEnv, errors::Result, - objects::{JClass, JList, JMap, JMethodID, JObject, JString}, - signature::{JavaType, Primitive}, + objects::{JClass, JList, JMap, JMethodID, JObject, JString, JValue}, + signature::{Primitive, ReturnType}, strings::JavaStr, sys::jint, }; @@ -14,21 +14,21 @@ 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>, + connect: JMethodID, + disconnect: JMethodID, + is_connected: JMethodID, + discover_services: JMethodID, + read: JMethodID, + write: JMethodID, + set_characteristic_notification: JMethodID, + get_notifications: JMethodID, + read_descriptor: JMethodID, + write_descriptor: JMethodID, + get_device_name: JMethodID, + request_mtu: JMethodID, + get_connection_parameters: JMethodID, + request_connection_priority: JMethodID, + read_remote_rssi: JMethodID, env: &'b JNIEnv<'a>, } @@ -166,12 +166,7 @@ impl<'a: 'b, 'b> JPeripheral<'a, 'b> { 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()), - &[], - )? + .call_method_unchecked(self.internal, self.connect, ReturnType::Object, &[])? .l()?; JFuture::from_env(self.env, future_obj) } @@ -179,12 +174,7 @@ impl<'a: 'b, 'b> JPeripheral<'a, 'b> { 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()), - &[], - )? + .call_method_unchecked(self.internal, self.disconnect, ReturnType::Object, &[])? .l()?; JFuture::from_env(self.env, future_obj) } @@ -194,7 +184,7 @@ impl<'a: 'b, 'b> JPeripheral<'a, 'b> { .call_method_unchecked( self.internal, self.is_connected, - JavaType::Primitive(Primitive::Boolean), + ReturnType::Primitive(Primitive::Boolean), &[], )? .z() @@ -206,7 +196,7 @@ impl<'a: 'b, 'b> JPeripheral<'a, 'b> { .call_method_unchecked( self.internal, self.discover_services, - JavaType::Object("Lio/github/gedgygedgy/rust/future/Future;".to_string()), + ReturnType::Object, &[], )? .l()?; @@ -219,8 +209,8 @@ impl<'a: 'b, 'b> JPeripheral<'a, 'b> { .call_method_unchecked( self.internal, self.read, - JavaType::Object("Lio/github/gedgygedgy/rust/future/Future;".to_string()), - &[uuid.into()], + ReturnType::Object, + &[JValue::from(uuid).to_jni()], )? .l()?; JFuture::from_env(self.env, future_obj) @@ -237,8 +227,12 @@ impl<'a: 'b, 'b> JPeripheral<'a, 'b> { .call_method_unchecked( self.internal, self.write, - JavaType::Object("Lio/github/gedgygedgy/rust/future/Future;".to_string()), - &[uuid.into(), data.into(), write_type.into()], + ReturnType::Object, + &[ + JValue::from(uuid).to_jni(), + JValue::from(data).to_jni(), + JValue::from(write_type).to_jni(), + ], )? .l()?; JFuture::from_env(self.env, future_obj) @@ -254,8 +248,8 @@ impl<'a: 'b, 'b> JPeripheral<'a, 'b> { .call_method_unchecked( self.internal, self.set_characteristic_notification, - JavaType::Object("Lio/github/gedgygedgy/rust/future/Future;".to_string()), - &[uuid.into(), enable.into()], + ReturnType::Object, + &[JValue::from(uuid).to_jni(), JValue::from(enable).to_jni()], )? .l()?; JFuture::from_env(self.env, future_obj) @@ -267,7 +261,7 @@ impl<'a: 'b, 'b> JPeripheral<'a, 'b> { .call_method_unchecked( self.internal, self.get_notifications, - JavaType::Object("Lio/github/gedgygedgy/rust/stream/Stream;".to_string()), + ReturnType::Object, &[], )? .l()?; @@ -284,8 +278,11 @@ impl<'a: 'b, 'b> JPeripheral<'a, 'b> { .call_method_unchecked( self.internal, self.read_descriptor, - JavaType::Object("Lio/github/gedgygedgy/rust/future/Future;".to_string()), - &[characteristic.into(), uuid.into()], + ReturnType::Object, + &[ + JValue::from(characteristic).to_jni(), + JValue::from(uuid).to_jni(), + ], )? .l()?; JFuture::from_env(self.env, future_obj) @@ -294,12 +291,7 @@ impl<'a: 'b, 'b> JPeripheral<'a, 'b> { 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()), - &[], - )? + .call_method_unchecked(self.internal, self.get_device_name, ReturnType::Object, &[])? .l()?; if obj.is_null() { Ok(None) @@ -314,8 +306,8 @@ impl<'a: 'b, 'b> JPeripheral<'a, 'b> { .call_method_unchecked( self.internal, self.request_mtu, - JavaType::Object("Lio/github/gedgygedgy/rust/future/Future;".to_string()), - &[mtu.into()], + ReturnType::Object, + &[JValue::from(mtu).to_jni()], )? .l() } @@ -326,14 +318,14 @@ impl<'a: 'b, 'b> JPeripheral<'a, 'b> { .call_method_unchecked( self.internal, self.get_connection_parameters, - JavaType::Array(JavaType::Primitive(Primitive::Int).into()), + ReturnType::Array, &[], )? .l()?; if obj.is_null() { return Ok(None); } - let arr = obj.into_inner(); + let arr = obj.into_raw(); let len = self.env.get_array_length(arr)?; if len < 3 { return Ok(None); @@ -354,7 +346,7 @@ impl<'a: 'b, 'b> JPeripheral<'a, 'b> { .call_method_unchecked( self.internal, self.read_remote_rssi, - JavaType::Object("Lio/github/gedgygedgy/rust/future/Future;".to_string()), + ReturnType::Object, &[], )? .l() @@ -365,8 +357,8 @@ impl<'a: 'b, 'b> JPeripheral<'a, 'b> { .call_method_unchecked( self.internal, self.request_connection_priority, - JavaType::Primitive(Primitive::Boolean), - &[priority.into()], + ReturnType::Primitive(Primitive::Boolean), + &[JValue::from(priority).to_jni()], )? .z() } @@ -382,8 +374,12 @@ impl<'a: 'b, 'b> JPeripheral<'a, 'b> { .call_method_unchecked( self.internal, self.write_descriptor, - JavaType::Object("Lio/github/gedgygedgy/rust/future/Future;".to_string()), - &[characteristic.into(), uuid.into(), data.into()], + ReturnType::Object, + &[ + JValue::from(characteristic).to_jni(), + JValue::from(uuid).to_jni(), + JValue::from(data).to_jni(), + ], )? .l()?; JFuture::from_env(self.env, future_obj) @@ -392,9 +388,9 @@ impl<'a: 'b, 'b> JPeripheral<'a, 'b> { pub struct JBluetoothGattService<'a: 'b, 'b> { internal: JObject<'a>, - get_uuid: JMethodID<'a>, - //is_primary: JMethodID<'a>, - get_characteristics: JMethodID<'a>, + get_uuid: JMethodID, + //is_primary: JMethodID, + get_characteristics: JMethodID, env: &'b JNIEnv<'a>, } @@ -421,7 +417,7 @@ impl<'a: 'b, 'b> JBluetoothGattService<'a, 'b> { .call_method_unchecked( self.internal, self.is_primary, - JavaType::Primitive(Primitive::Boolean), + ReturnType::Primitive(Primitive::Boolean), &[], )? .z() @@ -432,12 +428,7 @@ impl<'a: 'b, 'b> JBluetoothGattService<'a, 'b> { 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()), - &[], - )? + .call_method_unchecked(self.internal, self.get_uuid, ReturnType::Object, &[])? .l()?; let uuid_obj = JUuid::from_env(self.env, obj)?; Ok(uuid_obj.as_uuid()?) @@ -449,7 +440,7 @@ impl<'a: 'b, 'b> JBluetoothGattService<'a, 'b> { .call_method_unchecked( self.internal, self.get_characteristics, - JavaType::Object("Ljava/util/List;".to_string()), + ReturnType::Object, &[], )? .l()?; @@ -464,10 +455,10 @@ impl<'a: 'b, 'b> JBluetoothGattService<'a, 'b> { 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>, + get_uuid: JMethodID, + get_properties: JMethodID, + get_value: JMethodID, + get_descriptors: JMethodID, env: &'b JNIEnv<'a>, } @@ -493,12 +484,7 @@ impl<'a: 'b, 'b> JBluetoothGattCharacteristic<'a, 'b> { 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()), - &[], - )? + .call_method_unchecked(self.internal, self.get_uuid, ReturnType::Object, &[])? .l()?; let uuid_obj = JUuid::from_env(self.env, obj)?; Ok(uuid_obj.as_uuid()?) @@ -510,7 +496,7 @@ impl<'a: 'b, 'b> JBluetoothGattCharacteristic<'a, 'b> { .call_method_unchecked( self.internal, self.get_properties, - JavaType::Primitive(Primitive::Int), + ReturnType::Primitive(Primitive::Int), &[], )? .i()?; @@ -520,25 +506,15 @@ impl<'a: 'b, 'b> JBluetoothGattCharacteristic<'a, 'b> { 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()), - &[], - )? + .call_method_unchecked(self.internal, self.get_value, ReturnType::Array, &[])? .l()?; - crate::droidplug::jni_utils::arrays::byte_array_to_vec(self.env, value.into_inner()) + crate::droidplug::jni_utils::arrays::byte_array_to_vec(self.env, value.into_raw()) } 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()), - &[], - )? + .call_method_unchecked(self.internal, self.get_descriptors, ReturnType::Object, &[])? .l()?; let desc_list = JList::from_env(self.env, obj)?; let mut desc_vec = vec![]; @@ -551,7 +527,7 @@ impl<'a: 'b, 'b> JBluetoothGattCharacteristic<'a, 'b> { pub struct JBluetoothGattDescriptor<'a: 'b, 'b> { internal: JObject<'a>, - get_uuid: JMethodID<'a>, + get_uuid: JMethodID, env: &'b JNIEnv<'a>, } @@ -570,12 +546,7 @@ impl<'a: 'b, 'b> JBluetoothGattDescriptor<'a, 'b> { 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()), - &[], - )? + .call_method_unchecked(self.internal, self.get_uuid, ReturnType::Object, &[])? .l()?; let uuid_obj = JUuid::from_env(self.env, obj)?; Ok(uuid_obj.as_uuid()?) @@ -584,7 +555,7 @@ impl<'a: 'b, 'b> JBluetoothGattDescriptor<'a, 'b> { pub struct JBluetoothDevice<'a: 'b, 'b> { internal: JObject<'a>, - get_address: JMethodID<'a>, + get_address: JMethodID, env: &'b JNIEnv<'a>, } @@ -603,12 +574,7 @@ impl<'a: 'b, 'b> JBluetoothDevice<'a, 'b> { 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()), - &[], - )? + .call_method_unchecked(self.internal, self.get_address, ReturnType::Object, &[])? .l()?; Ok(obj.into()) } @@ -653,10 +619,10 @@ 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>, + get_device: JMethodID, + get_scan_record: JMethodID, + get_tx_power: JMethodID, + get_rssi: JMethodID, env: &'b JNIEnv<'a>, } @@ -686,12 +652,7 @@ impl<'a: 'b, 'b> JScanResult<'a, 'b> { 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()), - &[], - )? + .call_method_unchecked(self.internal, self.get_device, ReturnType::Object, &[])? .l()?; JBluetoothDevice::from_env(self.env, obj) } @@ -699,12 +660,7 @@ impl<'a: 'b, 'b> JScanResult<'a, 'b> { 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()), - &[], - )? + .call_method_unchecked(self.internal, self.get_scan_record, ReturnType::Object, &[])? .l()?; JScanRecord::from_env(self.env, obj) } @@ -714,7 +670,7 @@ impl<'a: 'b, 'b> JScanResult<'a, 'b> { .call_method_unchecked( self.internal, self.get_tx_power, - JavaType::Primitive(Primitive::Int), + ReturnType::Primitive(Primitive::Int), &[], )? .i() @@ -725,7 +681,7 @@ impl<'a: 'b, 'b> JScanResult<'a, 'b> { .call_method_unchecked( self.internal, self.get_rssi, - JavaType::Primitive(Primitive::Int), + ReturnType::Primitive(Primitive::Int), &[], )? .i() @@ -800,7 +756,7 @@ impl<'a: 'b, 'b> TryFrom> for (BDAddr, Option TryFrom> for (BDAddr, Option 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>, + get_device_name: JMethodID, + get_tx_power_level: JMethodID, + get_manufacturer_specific_data: JMethodID, + get_service_data: JMethodID, + get_service_uuids: JMethodID, env: &'b JNIEnv<'a>, } @@ -909,12 +865,7 @@ impl<'a: 'b, 'b> JScanRecord<'a, 'b> { 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()), - &[], - )? + .call_method_unchecked(self.internal, self.get_device_name, ReturnType::Object, &[])? .l()?; Ok(obj.into()) } @@ -924,7 +875,7 @@ impl<'a: 'b, 'b> JScanRecord<'a, 'b> { .call_method_unchecked( self.internal, self.get_tx_power_level, - JavaType::Primitive(Primitive::Int), + ReturnType::Primitive(Primitive::Int), &[], )? .i() @@ -936,7 +887,7 @@ impl<'a: 'b, 'b> JScanRecord<'a, 'b> { .call_method_unchecked( self.internal, self.get_manufacturer_specific_data, - JavaType::Object("Landroid/util/SparseArray;".to_string()), + ReturnType::Object, &[], )? .l()?; @@ -949,7 +900,7 @@ impl<'a: 'b, 'b> JScanRecord<'a, 'b> { .call_method_unchecked( self.internal, self.get_service_data, - JavaType::Object("Ljava/util/Map;".to_string()), + ReturnType::Object, &[], )? .l()?; @@ -962,7 +913,7 @@ impl<'a: 'b, 'b> JScanRecord<'a, 'b> { .call_method_unchecked( self.internal, self.get_service_uuids, - JavaType::Object("Ljava/util/List;".to_string()), + ReturnType::Object, &[], )? .l()?; @@ -973,9 +924,9 @@ impl<'a: 'b, 'b> JScanRecord<'a, 'b> { #[derive(Clone)] pub struct JSparseArray<'a: 'b, 'b> { internal: JObject<'a>, - size: JMethodID<'a>, - key_at: JMethodID<'a>, - value_at: JMethodID<'a>, + size: JMethodID, + key_at: JMethodID, + value_at: JMethodID, env: &'b JNIEnv<'a>, } @@ -1014,7 +965,7 @@ impl<'a: 'b, 'b> JSparseArray<'a, 'b> { .call_method_unchecked( self.internal, self.size, - JavaType::Primitive(Primitive::Int), + ReturnType::Primitive(Primitive::Int), &[], )? .i() @@ -1025,8 +976,8 @@ impl<'a: 'b, 'b> JSparseArray<'a, 'b> { .call_method_unchecked( self.internal, self.key_at, - JavaType::Primitive(Primitive::Int), - &[index.into()], + ReturnType::Primitive(Primitive::Int), + &[JValue::from(index).to_jni()], )? .i() } @@ -1036,8 +987,8 @@ impl<'a: 'b, 'b> JSparseArray<'a, 'b> { .call_method_unchecked( self.internal, self.value_at, - JavaType::Object("Ljava/lang/Object;".to_string()), - &[index.into()], + ReturnType::Object, + &[JValue::from(index).to_jni()], )? .l() } @@ -1078,7 +1029,7 @@ impl<'a: 'b, 'b> Iterator for JSparseArrayIter<'a, 'b> { } pub struct JParcelUuid<'a: 'b, 'b> { internal: JObject<'a>, - get_uuid: JMethodID<'a>, + get_uuid: JMethodID, env: &'b JNIEnv<'a>, } @@ -1097,12 +1048,7 @@ impl<'a: 'b, 'b> JParcelUuid<'a, 'b> { 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()), - &[], - )? + .call_method_unchecked(self.internal, self.get_uuid, ReturnType::Object, &[])? .l()?; JUuid::from_env(self.env, obj) } diff --git a/src/droidplug/jni_utils/exceptions.rs b/src/droidplug/jni_utils/exceptions.rs index 6f014d52..6f394376 100644 --- a/src/droidplug/jni_utils/exceptions.rs +++ b/src/droidplug/jni_utils/exceptions.rs @@ -126,16 +126,16 @@ impl<'a: 'b, 'b> JPanicException<'a, 'b> { "(Ljava/lang/String;)V", &[msg.into()], )?; - env.set_rust_field(obj, "any", any)?; + unsafe { env.set_rust_field(obj, "any", any) }?; Self::from_env(env, obj.into()) } pub fn get(&self) -> Result>, Error> { - self.env.get_rust_field(self.internal, "any") + unsafe { self.env.get_rust_field(self.internal, "any") } } pub fn take(&self) -> Result, Error> { - self.env.take_rust_field(self.internal, "any") + unsafe { self.env.take_rust_field(self.internal, "any") } } pub fn resume_unwind(&self) -> Result<(), Error> { @@ -542,7 +542,7 @@ mod test { .l() .unwrap(); assert_eq!( - env.get_array_length(suppressed_list.into_inner()).unwrap(), + env.get_array_length(suppressed_list.into_raw()).unwrap(), 0 ); @@ -579,11 +579,11 @@ mod test { .l() .unwrap(); assert_eq!( - env.get_array_length(suppressed_list.into_inner()).unwrap(), + env.get_array_length(suppressed_list.into_raw()).unwrap(), 1 ); let suppressed_ex = env - .get_object_array_element(suppressed_list.into_inner(), 0) + .get_object_array_element(suppressed_list.into_raw(), 0) .unwrap(); assert!(env.is_same_object(old_ex, suppressed_ex).unwrap()); diff --git a/src/droidplug/jni_utils/future.rs b/src/droidplug/jni_utils/future.rs index 34587a80..ffaf4e0e 100644 --- a/src/droidplug/jni_utils/future.rs +++ b/src/droidplug/jni_utils/future.rs @@ -2,8 +2,8 @@ use super::task::JPollResult; use ::jni::{ JNIEnv, JavaVM, errors::{Error, Result}, - objects::{GlobalRef, JClass, JMethodID, JObject}, - signature::JavaType, + objects::{GlobalRef, JClass, JMethodID, JObject, JValue}, + signature::ReturnType, }; use static_assertions::assert_impl_all; use std::{ @@ -21,7 +21,7 @@ use std::{ /// For a [`Send`] version of this, use [`JSendFuture`]. pub struct JFuture<'a: 'b, 'b> { internal: JObject<'a>, - poll: JMethodID<'a>, + poll: JMethodID, env: &'b JNIEnv<'a>, } @@ -49,8 +49,8 @@ impl<'a: 'b, 'b> JFuture<'a, 'b> { .call_method_unchecked( self.internal, self.poll, - JavaType::Object("io/github/gedgygedgy/rust/task/PollResult".into()), - &[waker.into()], + ReturnType::Object, + &[JValue::from(waker).to_jni()], )? .l()?; JPollResult::from_env(self.env, result) diff --git a/src/droidplug/jni_utils/ops.rs b/src/droidplug/jni_utils/ops.rs index 3b896251..03902913 100644 --- a/src/droidplug/jni_utils/ops.rs +++ b/src/droidplug/jni_utils/ops.rs @@ -282,7 +282,7 @@ fn fn_adapter<'a: 'b, 'b>( "(Z)V", &[local.into()], )?; - env.set_rust_field::<_, _, FnWrapper>(obj, "data", SendSyncWrapper(arc))?; + unsafe { env.set_rust_field::<_, _, FnWrapper>(obj, "data", SendSyncWrapper(arc)) }?; Ok(obj) } @@ -295,7 +295,7 @@ pub(crate) extern "C" fn fn_adapter_call_internal<'a>( ) -> JObject<'a> { use std::panic::AssertUnwindSafe; - let arc = if let Ok(f) = env.get_rust_field::<_, _, FnWrapper>(obj1, "data") { + let arc = if let Ok(f) = unsafe { env.get_rust_field::<_, _, FnWrapper>(obj1, "data") } { AssertUnwindSafe(f.0.clone()) } else { return JObject::null(); @@ -306,6 +306,6 @@ pub(crate) extern "C" fn fn_adapter_call_internal<'a>( 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"); + let _ = unsafe { env.take_rust_field::<_, _, FnWrapper>(obj, "data") }; }); } diff --git a/src/droidplug/jni_utils/stream.rs b/src/droidplug/jni_utils/stream.rs index c108247c..636a4fc5 100644 --- a/src/droidplug/jni_utils/stream.rs +++ b/src/droidplug/jni_utils/stream.rs @@ -2,8 +2,8 @@ use super::task::JPollResult; use ::jni::{ JNIEnv, JavaVM, errors::{Error, Result}, - objects::{GlobalRef, JClass, JMethodID, JObject}, - signature::JavaType, + objects::{GlobalRef, JClass, JMethodID, JObject, JValue}, + signature::ReturnType, }; use futures::stream::Stream; use static_assertions::assert_impl_all; @@ -19,7 +19,7 @@ use std::{ /// For a [`Send`] version of this, use [`JSendStream`]. pub struct JStream<'a: 'b, 'b> { internal: JObject<'a>, - poll_next: JMethodID<'a>, + poll_next: JMethodID, env: &'b JNIEnv<'a>, } @@ -47,8 +47,8 @@ impl<'a: 'b, 'b> JStream<'a, 'b> { .call_method_unchecked( self.internal, self.poll_next, - JavaType::Object("io/github/gedgygedgy/rust/task/PollResult".to_string()), - &[waker.into()], + ReturnType::Object, + &[JValue::from(waker).to_jni()], )? .l()?; let _auto_local = self.env.auto_local(result); @@ -153,7 +153,7 @@ assert_impl_all!(JSendStream: Send); struct JStreamPoll<'a: 'b, 'b> { internal: JObject<'a>, - get: JMethodID<'a>, + get: JMethodID, env: &'b JNIEnv<'a>, } @@ -177,12 +177,7 @@ impl<'a: 'b, 'b> JStreamPoll<'a, 'b> { pub fn get(&self) -> Result> { self.env - .call_method_unchecked( - self.internal, - self.get, - JavaType::Object("java/lang/Object".into()), - &[], - )? + .call_method_unchecked(self.internal, self.get, ReturnType::Object, &[])? .l() } } diff --git a/src/droidplug/jni_utils/task.rs b/src/droidplug/jni_utils/task.rs index c0b3c038..7993282a 100644 --- a/src/droidplug/jni_utils/task.rs +++ b/src/droidplug/jni_utils/task.rs @@ -2,7 +2,7 @@ use ::jni::{ JNIEnv, errors::Result, objects::{JClass, JMethodID, JObject}, - signature::JavaType, + signature::ReturnType, }; use std::task::Waker; @@ -26,7 +26,7 @@ pub fn waker<'a: 'b, 'b>(env: &'b JNIEnv<'a>, waker: Waker) -> Result { internal: JObject<'a>, - get: JMethodID<'a>, + get: JMethodID, env: &'b JNIEnv<'a>, } @@ -50,12 +50,7 @@ impl<'a: 'b, 'b> JPollResult<'a, 'b> { pub fn get(&self) -> Result> { self.env - .call_method_unchecked( - self.internal, - self.get, - JavaType::Object("java/lang/Object".into()), - &[], - )? + .call_method_unchecked(self.internal, self.get, ReturnType::Object, &[])? .l() } } diff --git a/src/droidplug/jni_utils/uuid.rs b/src/droidplug/jni_utils/uuid.rs index d5fdf6fa..2c7b7faf 100644 --- a/src/droidplug/jni_utils/uuid.rs +++ b/src/droidplug/jni_utils/uuid.rs @@ -2,7 +2,7 @@ use jni::{ JNIEnv, errors::Result, objects::{AutoLocal, JMethodID, JObject}, - signature::{JavaType, Primitive}, + signature::{Primitive, ReturnType}, sys::jlong, }; use uuid::Uuid; @@ -11,8 +11,8 @@ use uuid::Uuid; /// 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>, + get_least_significant_bits: JMethodID, + get_most_significant_bits: JMethodID, env: &'b JNIEnv<'a>, } @@ -38,7 +38,7 @@ impl<'a: 'b, 'b> JUuid<'a, 'b> { .call_method_unchecked( self.internal, self.get_least_significant_bits, - JavaType::Primitive(Primitive::Long), + ReturnType::Primitive(Primitive::Long), &[], )? .j()? as u64; @@ -47,7 +47,7 @@ impl<'a: 'b, 'b> JUuid<'a, 'b> { .call_method_unchecked( self.internal, self.get_most_significant_bits, - JavaType::Primitive(Primitive::Long), + ReturnType::Primitive(Primitive::Long), &[], )? .j()? as u64; diff --git a/src/droidplug/peripheral.rs b/src/droidplug/peripheral.rs index 8554376e..40576985 100644 --- a/src/droidplug/peripheral.rs +++ b/src/droidplug/peripheral.rs @@ -390,7 +390,7 @@ impl api::Peripheral for Peripheral { 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())?) + Ok(byte_array_to_vec(env, bytes.into_raw())?) }) } @@ -476,7 +476,7 @@ impl api::Peripheral for Peripheral { 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())?) + Ok(byte_array_to_vec(env, bytes.into_raw())?) }) } diff --git a/tests/android/rust/Cargo.toml b/tests/android/rust/Cargo.toml index 9afc3077..5ec67c7a 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.20" once_cell = "1" tokio = { version = "1", features = ["full"] } uuid = "1" From 8b69a337c3183a253f4db969c3ed0ad37346fdcb Mon Sep 17 00:00:00 2001 From: Kyle Machulis Date: Sat, 25 Apr 2026 18:33:40 -0700 Subject: [PATCH 02/16] build: Upgrade jni from 0.20 to 0.21 Major restructure of the Android JNI backend for jni-rs 0.21 breaking changes. JNIEnv now requires &mut self for all operations and JObject types are no longer Copy/Clone. Key changes: - Remove env field from all JNI wrapper structs, pass &mut JNIEnv per-method-call instead - JSendFuture/JSendStream store JavaVM and obtain env via get_env() on each poll, solving the Future::poll env-passing problem - Replace JList/JMap wrapper usage with direct env.call_method() iteration to avoid &mut borrow conflicts - Extract throw_panic from throw_unwind to enable catch_unwind in ops.rs without double &mut env borrows - Use typed arrays (JByteArray, JObjectArray) per 0.21 API - Wrap call_method_unchecked in unsafe blocks with raw jvalue args - Update test infrastructure for RefCell invariance: explicit RefMut guards, scoped setup before block_on, block-scoped borrows between await points Co-Authored-By: Claude Opus 4.6 --- Cargo.lock | 121 +++- Cargo.toml | 4 +- src/droidplug/adapter.rs | 113 +-- src/droidplug/jni/mod.rs | 20 +- src/droidplug/jni/objects.rs | 990 +++++++++++++------------- src/droidplug/jni_utils/arrays.rs | 28 +- src/droidplug/jni_utils/classcache.rs | 14 +- src/droidplug/jni_utils/exceptions.rs | 399 ++++++----- src/droidplug/jni_utils/future.rs | 337 +++++---- src/droidplug/jni_utils/mod.rs | 30 +- src/droidplug/jni_utils/ops.rs | 161 +++-- src/droidplug/jni_utils/stream.rs | 382 ++++++---- src/droidplug/jni_utils/task.rs | 63 +- src/droidplug/jni_utils/uuid.rs | 96 +-- src/droidplug/mod.rs | 2 +- src/droidplug/peripheral.rs | 324 ++++----- tests/android/rust/Cargo.toml | 2 +- 17 files changed, 1657 insertions(+), 1429 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3800e7a8..986a5db5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -336,6 +336,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" @@ -419,18 +425,31 @@ 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.20.0" +version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "039022cdf4d7b1cf548d31f60ae783138e5fd42013f6271049d7df7afadef96c" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" dependencies = [ "cesu8", + "cfg-if", "combine", + "java-locator", "jni-sys", + "libloading", "log", "thiserror 1.0.69", "walkdir", + "windows-sys 0.45.0", ] [[package]] @@ -476,6 +495,16 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "libloading" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" +dependencies = [ + "cfg-if", + "winapi", +] + [[package]] name = "lock_api" version = "0.4.14" @@ -1097,6 +1126,22 @@ dependencies = [ "semver", ] +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + [[package]] name = "winapi-util" version = "0.1.11" @@ -1106,6 +1151,12 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + [[package]] name = "windows" version = "0.62.2" @@ -1207,6 +1258,15 @@ dependencies = [ "windows-link", ] +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + [[package]] name = "windows-sys" version = "0.59.0" @@ -1234,6 +1294,21 @@ dependencies = [ "windows-link", ] +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + [[package]] name = "windows-targets" version = "0.52.6" @@ -1276,6 +1351,12 @@ dependencies = [ "windows-link", ] +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" @@ -1288,6 +1369,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + [[package]] name = "windows_aarch64_msvc" version = "0.52.6" @@ -1300,6 +1387,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -1324,6 +1417,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + [[package]] name = "windows_i686_msvc" version = "0.52.6" @@ -1336,6 +1435,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + [[package]] name = "windows_x86_64_gnu" version = "0.52.6" @@ -1348,6 +1453,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" @@ -1360,6 +1471,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + [[package]] name = "windows_x86_64_msvc" version = "0.52.6" diff --git a/Cargo.toml b/Cargo.toml index 627bc5eb..48e190ab 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.20.0" +jni = "0.21.0" once_cell = "1.21.3" [target.'cfg(not(target_os = "android"))'.dependencies] -jni = { version = "0.20.0", optional = true } +jni = { version = "0.21.0", 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 1ad9f5f4..ed902ffd 100644 --- a/src/droidplug/adapter.rs +++ b/src/droidplug/adapter.rs @@ -1,4 +1,3 @@ -use super::jni_utils::exceptions::try_block; use super::{ jni::{ global_jvm, @@ -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, + objects::{GlobalRef, JClass, JObject, JString}, sys::jboolean, }; use std::{ @@ -43,30 +40,31 @@ impl Debug for Adapter { impl Adapter { pub(crate) fn new() -> Result { - let env = global_jvm().get_env()?; + let mut 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 internal = env.new_global_ref(&obj)?; let adapter = Self { manager: Arc::new(AdapterManager::default()), internal, }; - unsafe { env.set_rust_field(obj, "handle", adapter.clone()) }?; + unsafe { env.set_rust_field(&obj, "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)?; - - let (addr, properties): (BDAddr, Option) = scan_result.try_into()?; + pub fn report_scan_result( + &self, + env: &mut JNIEnv, + scan_result: JObject, + ) -> Result { + let scan_result = JScanResult::from_env(env, 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 +72,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,8 +86,9 @@ impl Adapter { } fn add(&self, address: BDAddr) -> Result { - let env = global_jvm().get_env()?; - let peripheral = Peripheral::new(&env, self.internal.as_obj(), address)?; + let mut env = global_jvm().get_env()?; + let local_adapter = env.new_local_ref(&self.internal)?; + let peripheral = Peripheral::new(&mut env, local_adapter, address)?; self.manager.add_peripheral(peripheral.clone()); Ok(peripheral) } @@ -130,7 +126,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,39 +134,45 @@ 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( + let mut env = global_jvm().get_env()?; + let filter = JScanFilter::new(&mut env, filter)?; + let filter_obj: JObject = filter.into(); + match env.call_method( + &self.internal, + "startScan", + "(Lcom/nonpolynomial/btleplug/android/impl/ScanFilter;)V", + &[(&filter_obj).into()], + ) { + Ok(_) => Ok(()), + Err(jni::errors::Error::JavaException) => { + let ex = env.exception_occurred()?; + env.exception_clear()?; + + let no_adapter_class = 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))) - }) - .result()? + .unwrap(); + + if env.is_instance_of(&ex, <&JClass>::from(no_adapter_class.as_obj()))? { + Err(Error::NoAdapterAvailable) + } else if env.is_instance_of(&ex, "java/lang/RuntimeException")? { + let msg = env + .call_method(&ex, "getMessage", "()Ljava/lang/String;", &[])? + .l()?; + let jstr: JString = msg.into(); + let msgstr: String = env.get_string(&jstr)?.into(); + Err(Error::RuntimeError(msgstr)) + } else { + env.throw(&ex)?; + Err(jni::errors::Error::JavaException.into()) + } + } + Err(e) => Err(e.into()), + } } async fn stop_scan(&self) -> Result<()> { - let env = global_jvm().get_env()?; + let mut env = global_jvm().get_env()?; env.call_method(&self.internal, "stopScan", "()V", &[])?; Ok(()) } @@ -201,23 +202,25 @@ impl Central for Adapter { } pub(crate) fn adapter_report_scan_result_internal( - env: &JNIEnv, - obj: JObject, + env: &mut JNIEnv, + obj: &JObject, scan_result: JObject, ) -> crate::Result<()> { let adapter = unsafe { env.get_rust_field::<_, _, Adapter>(obj, "handle") }?; - adapter.report_scan_result(scan_result)?; + 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 JNIEnv, + obj: &JObject, addr: JString, connected: jboolean, ) -> crate::Result<()> { let adapter = unsafe { env.get_rust_field::<_, _, Adapter>(obj, "handle") }?; - let addr_str = JavaStr::from_env(env, addr)?; + let addr_str = env.get_string(&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 { diff --git a/src/droidplug/jni/mod.rs b/src/droidplug/jni/mod.rs index cafe87ad..a5368b73 100644 --- a/src/droidplug/jni/mod.rs +++ b/src/droidplug/jni/mod.rs @@ -7,10 +7,12 @@ use std::ffi::c_void; static GLOBAL_JVM: OnceCell = OnceCell::new(); -pub fn init(env: &JNIEnv) -> crate::Result<()> { +pub fn init(env: &mut JNIEnv) -> crate::Result<()> { if let Ok(()) = GLOBAL_JVM.set(env.get_java_vm()?) { + let adapter_class = + env.find_class("com/nonpolynomial/btleplug/android/impl/Adapter")?; env.register_native_methods( - "com/nonpolynomial/btleplug/android/impl/Adapter", + &adapter_class, &[ NativeMethod { name: "reportScanResult".into(), @@ -97,8 +99,7 @@ pub fn init(env: &JNIEnv) -> crate::Result<()> { )?; // FnAdapter native method registration - let fn_adapter_class = - env.auto_local(env.find_class("io/github/gedgygedgy/rust/ops/FnAdapter")?); + let fn_adapter_class = env.find_class("io/github/gedgygedgy/rust/ops/FnAdapter")?; env.register_native_methods( &fn_adapter_class, &[ @@ -132,16 +133,17 @@ 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(mut env: JNIEnv, obj: JObject, scan_result: JObject) { + let _ = super::adapter::adapter_report_scan_result_internal(&mut env, &obj, scan_result); } extern "C" fn adapter_on_connection_state_changed( - env: JNIEnv, + mut env: JNIEnv, obj: JObject, addr: JString, connected: jboolean, ) { - let _ = - super::adapter::adapter_on_connection_state_changed_internal(&env, obj, addr, connected); + let _ = super::adapter::adapter_on_connection_state_changed_internal( + &mut env, &obj, addr, connected, + ); } diff --git a/src/droidplug/jni/objects.rs b/src/droidplug/jni/objects.rs index 839d608e..f1b264a6 100644 --- a/src/droidplug/jni/objects.rs +++ b/src/droidplug/jni/objects.rs @@ -2,17 +2,16 @@ use crate::droidplug::jni_utils::{future::JFuture, stream::JStream, uuid::JUuid} use jni::{ JNIEnv, errors::Result, - objects::{JClass, JList, JMap, JMethodID, JObject, JString, JValue}, + objects::{JClass, JMethodID, JObject, JString}, signature::{Primitive, ReturnType}, - strings::JavaStr, - sys::jint, + sys::{jint, jvalue}, }; -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> { +pub struct JPeripheral<'a> { internal: JObject<'a>, connect: JMethodID, disconnect: JMethodID, @@ -29,10 +28,9 @@ pub struct JPeripheral<'a: 'b, 'b> { get_connection_parameters: JMethodID, request_connection_priority: JMethodID, read_remote_rssi: JMethodID, - env: &'b JNIEnv<'a>, } -impl<'a: 'b, 'b> ::std::ops::Deref for JPeripheral<'a, 'b> { +impl<'a> ::std::ops::Deref for JPeripheral<'a> { type Target = JObject<'a>; fn deref(&self) -> &Self::Target { @@ -40,27 +38,19 @@ impl<'a: 'b, 'b> ::std::ops::Deref for JPeripheral<'a, 'b> { } } -impl<'a: 'b, 'b> From> for JObject<'a> { - fn from(other: JPeripheral<'a, 'b>) -> JObject<'a> { +impl<'a> From> for JObject<'a> { + fn from(other: JPeripheral<'a>) -> JObject<'a> { other.internal } } -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) - } - - fn from_env_impl(env: &'b JNIEnv<'a>, obj: JObject<'a>) -> Result { - //let class = env.auto_local(class); +impl<'a> JPeripheral<'a> { + pub fn from_env(env: &mut JNIEnv<'a>, obj: JObject<'a>) -> Result { 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()); + let class = <&JClass>::from(class_static.as_obj()); let connect = env.get_method_id( class, @@ -140,200 +130,223 @@ impl<'a: 'b, 'b> JPeripheral<'a, 'b> { get_connection_parameters, request_connection_priority, read_remote_rssi, - env, }) } - pub fn new(env: &'b JNIEnv<'a>, adapter: JObject<'a>, addr: BDAddr) -> Result { - // let class = env.find_class("com/nonpolynomial/btleplug/android/impl/Peripheral")?; + pub fn new(env: &mut JNIEnv<'a>, adapter: JObject<'a>, addr: BDAddr) -> Result { let addr_jstr = env.new_string(format!("{:X}", addr))?; + let class_static = crate::droidplug::jni_utils::classcache::get_class( + "com/nonpolynomial/btleplug/android/impl/Peripheral", + ) + .unwrap(); 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(), + <&JClass>::from(class_static.as_obj()), "(Lcom/nonpolynomial/btleplug/android/impl/Adapter;Ljava/lang/String;)V", - &[adapter.into(), addr_jstr.into()], + &[(&adapter).into(), (&addr_jstr).into()], )?; - //Self::from_env_impl(env, obj, class) - Self::from_env_impl(env, obj) + Self::from_env(env, obj) } - pub fn connect(&self) -> Result> { - let future_obj = self - .env - .call_method_unchecked(self.internal, self.connect, ReturnType::Object, &[])? - .l()?; - JFuture::from_env(self.env, future_obj) + pub fn connect(&self, env: &mut JNIEnv<'a>) -> Result> { + let future_obj = unsafe { + env.call_method_unchecked(&self.internal, self.connect, ReturnType::Object, &[]) + }? + .l()?; + JFuture::from_env(env, future_obj) } - pub fn disconnect(&self) -> Result> { - let future_obj = self - .env - .call_method_unchecked(self.internal, self.disconnect, ReturnType::Object, &[])? - .l()?; - JFuture::from_env(self.env, future_obj) + pub fn disconnect(&self, env: &mut JNIEnv<'a>) -> Result> { + let future_obj = unsafe { + env.call_method_unchecked(&self.internal, self.disconnect, ReturnType::Object, &[]) + }? + .l()?; + JFuture::from_env(env, future_obj) } - pub fn is_connected(&self) -> Result { - self.env - .call_method_unchecked( - self.internal, + pub fn is_connected(&self, env: &mut JNIEnv<'a>) -> Result { + unsafe { + env.call_method_unchecked( + &self.internal, self.is_connected, ReturnType::Primitive(Primitive::Boolean), &[], - )? - .z() + ) + }? + .z() } - pub fn discover_services(&self) -> Result> { - let future_obj = self - .env - .call_method_unchecked( - self.internal, + pub fn discover_services(&self, env: &mut JNIEnv<'a>) -> Result> { + let future_obj = unsafe { + env.call_method_unchecked( + &self.internal, self.discover_services, ReturnType::Object, &[], - )? - .l()?; - JFuture::from_env(self.env, future_obj) + ) + }? + .l()?; + JFuture::from_env(env, future_obj) } - pub fn read(&self, uuid: JUuid<'a, 'b>) -> Result> { - let future_obj = self - .env - .call_method_unchecked( - self.internal, + pub fn read(&self, env: &mut JNIEnv<'a>, uuid: &JUuid<'a>) -> Result> { + let future_obj = unsafe { + env.call_method_unchecked( + &self.internal, self.read, ReturnType::Object, - &[JValue::from(uuid).to_jni()], - )? - .l()?; - JFuture::from_env(self.env, future_obj) + &[jvalue { + l: uuid.as_obj().as_raw(), + }], + ) + }? + .l()?; + JFuture::from_env(env, future_obj) } pub fn write( &self, - uuid: JUuid<'a, 'b>, - data: JObject<'a>, + env: &mut JNIEnv<'a>, + uuid: &JUuid<'a>, + data: &JObject<'a>, write_type: jint, - ) -> Result> { - let future_obj = self - .env - .call_method_unchecked( - self.internal, + ) -> Result> { + let future_obj = unsafe { + env.call_method_unchecked( + &self.internal, self.write, ReturnType::Object, &[ - JValue::from(uuid).to_jni(), - JValue::from(data).to_jni(), - JValue::from(write_type).to_jni(), + jvalue { + l: uuid.as_obj().as_raw(), + }, + jvalue { + l: data.as_raw(), + }, + jvalue { i: write_type }, ], - )? - .l()?; - JFuture::from_env(self.env, future_obj) + ) + }? + .l()?; + JFuture::from_env(env, future_obj) } pub fn set_characteristic_notification( &self, - uuid: JUuid<'a, 'b>, + env: &mut JNIEnv<'a>, + uuid: &JUuid<'a>, enable: bool, - ) -> Result> { - let future_obj = self - .env - .call_method_unchecked( - self.internal, + ) -> Result> { + let future_obj = unsafe { + env.call_method_unchecked( + &self.internal, self.set_characteristic_notification, ReturnType::Object, - &[JValue::from(uuid).to_jni(), JValue::from(enable).to_jni()], - )? - .l()?; - JFuture::from_env(self.env, future_obj) + &[ + jvalue { + l: uuid.as_obj().as_raw(), + }, + jvalue { + z: enable as u8, + }, + ], + ) + }? + .l()?; + JFuture::from_env(env, future_obj) } - pub fn get_notifications(&self) -> Result> { - let stream_obj = self - .env - .call_method_unchecked( - self.internal, + pub fn get_notifications(&self, env: &mut JNIEnv<'a>) -> Result> { + let stream_obj = unsafe { + env.call_method_unchecked( + &self.internal, self.get_notifications, ReturnType::Object, &[], - )? - .l()?; - JStream::from_env(self.env, stream_obj) + ) + }? + .l()?; + JStream::from_env(env, stream_obj) } pub fn read_descriptor( &self, - characteristic: JUuid<'a, 'b>, - uuid: JUuid<'a, 'b>, - ) -> Result> { - let future_obj = self - .env - .call_method_unchecked( - self.internal, + env: &mut JNIEnv<'a>, + characteristic: &JUuid<'a>, + uuid: &JUuid<'a>, + ) -> Result> { + let future_obj = unsafe { + env.call_method_unchecked( + &self.internal, self.read_descriptor, ReturnType::Object, &[ - JValue::from(characteristic).to_jni(), - JValue::from(uuid).to_jni(), + jvalue { + l: characteristic.as_obj().as_raw(), + }, + jvalue { + l: uuid.as_obj().as_raw(), + }, ], - )? - .l()?; - JFuture::from_env(self.env, future_obj) + ) + }? + .l()?; + JFuture::from_env(env, future_obj) } - pub fn get_device_name(&self) -> Result> { - let obj = self - .env - .call_method_unchecked(self.internal, self.get_device_name, ReturnType::Object, &[])? - .l()?; + pub fn get_device_name(&self, env: &mut JNIEnv<'a>) -> Result> { + let obj = unsafe { + env.call_method_unchecked( + &self.internal, + self.get_device_name, + ReturnType::Object, + &[], + ) + }? + .l()?; if obj.is_null() { Ok(None) } else { - let name_str = self.env.get_string(obj.into())?; + let jstr: JString = obj.into(); + let name_str = env.get_string(&jstr)?; Ok(Some(name_str.into())) } } - pub fn request_mtu(&self, mtu: jint) -> Result> { - self.env - .call_method_unchecked( - self.internal, + pub fn request_mtu(&self, env: &mut JNIEnv<'a>, mtu: jint) -> Result> { + unsafe { + env.call_method_unchecked( + &self.internal, self.request_mtu, ReturnType::Object, - &[JValue::from(mtu).to_jni()], - )? - .l() + &[jvalue { i: mtu }], + ) + }? + .l() } - pub fn get_connection_parameters(&self) -> Result> { - let obj = self - .env - .call_method_unchecked( - self.internal, + pub fn get_connection_parameters( + &self, + env: &mut JNIEnv<'a>, + ) -> Result> { + let obj = unsafe { + env.call_method_unchecked( + &self.internal, self.get_connection_parameters, ReturnType::Array, &[], - )? - .l()?; + ) + }? + .l()?; if obj.is_null() { return Ok(None); } - let arr = obj.into_raw(); - let len = self.env.get_array_length(arr)?; + let arr = unsafe { jni::objects::JIntArray::from_raw(obj.into_raw()) }; + let len = env.get_array_length(&arr)?; 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 + env.get_int_array_region(&arr, 0, &mut buf)?; Ok(Some(crate::api::ConnectionParameters { interval_us: (buf[0] as u32) * 1250, latency: buf[1] as u16, @@ -341,131 +354,133 @@ impl<'a: 'b, 'b> JPeripheral<'a, 'b> { })) } - pub fn read_remote_rssi(&self) -> Result> { - self.env - .call_method_unchecked( - self.internal, + pub fn read_remote_rssi(&self, env: &mut JNIEnv<'a>) -> Result> { + unsafe { + env.call_method_unchecked( + &self.internal, self.read_remote_rssi, ReturnType::Object, &[], - )? - .l() + ) + }? + .l() } - pub fn request_connection_priority(&self, priority: jint) -> Result { - self.env - .call_method_unchecked( - self.internal, + pub fn request_connection_priority( + &self, + env: &mut JNIEnv<'a>, + priority: jint, + ) -> Result { + unsafe { + env.call_method_unchecked( + &self.internal, self.request_connection_priority, ReturnType::Primitive(Primitive::Boolean), - &[JValue::from(priority).to_jni()], - )? - .z() + &[jvalue { i: priority }], + ) + }? + .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, + env: &mut JNIEnv<'a>, + characteristic: &JUuid<'a>, + uuid: &JUuid<'a>, + data: &JObject<'a>, + ) -> Result> { + let future_obj = unsafe { + env.call_method_unchecked( + &self.internal, self.write_descriptor, ReturnType::Object, &[ - JValue::from(characteristic).to_jni(), - JValue::from(uuid).to_jni(), - JValue::from(data).to_jni(), + jvalue { + l: characteristic.as_obj().as_raw(), + }, + jvalue { + l: uuid.as_obj().as_raw(), + }, + jvalue { + l: data.as_raw(), + }, ], - )? - .l()?; - JFuture::from_env(self.env, future_obj) + ) + }? + .l()?; + JFuture::from_env(env, future_obj) } } -pub struct JBluetoothGattService<'a: 'b, 'b> { +pub struct JBluetoothGattService<'a> { internal: JObject<'a>, get_uuid: JMethodID, - //is_primary: JMethodID, get_characteristics: JMethodID, - 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")?); +impl<'a> JBluetoothGattService<'a> { + pub fn from_env(env: &mut JNIEnv<'a>, obj: JObject<'a>) -> Result { + let class = env.find_class("android/bluetooth/BluetoothGattService")?; 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, }) } pub fn is_primary(&self) -> Result { - /* - self.env - .call_method_unchecked( - self.internal, - self.is_primary, - ReturnType::Primitive(Primitive::Boolean), - &[], - )? - .z() - */ Ok(true) } - pub fn get_uuid(&self) -> Result { - let obj = self - .env - .call_method_unchecked(self.internal, self.get_uuid, ReturnType::Object, &[])? - .l()?; - let uuid_obj = JUuid::from_env(self.env, obj)?; - Ok(uuid_obj.as_uuid()?) + pub fn get_uuid(&self, env: &mut JNIEnv<'a>) -> Result { + let obj = unsafe { + env.call_method_unchecked(&self.internal, self.get_uuid, ReturnType::Object, &[]) + }? + .l()?; + let uuid_obj = JUuid::from_env(env, obj)?; + uuid_obj.as_uuid(env) } - pub fn get_characteristics(&self) -> Result> { - let obj = self - .env - .call_method_unchecked( - self.internal, + pub fn get_characteristics( + &self, + env: &mut JNIEnv<'a>, + ) -> Result>> { + let obj = unsafe { + env.call_method_unchecked( + &self.internal, self.get_characteristics, ReturnType::Object, &[], - )? - .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)?); + ) + }? + .l()?; + let size = env.call_method(&obj, "size", "()I", &[])?.i()?; + let mut chr_vec = Vec::with_capacity(size as usize); + for i in 0..size { + let chr = env + .call_method(&obj, "get", "(I)Ljava/lang/Object;", &[jni::objects::JValue::from(i)])? + .l()?; + chr_vec.push(JBluetoothGattCharacteristic::from_env(env, chr)?); } Ok(chr_vec) } } -pub struct JBluetoothGattCharacteristic<'a: 'b, 'b> { +pub struct JBluetoothGattCharacteristic<'a> { internal: JObject<'a>, get_uuid: JMethodID, get_properties: JMethodID, get_value: JMethodID, get_descriptors: JMethodID, - env: &'b JNIEnv<'a>, } -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")?); +impl<'a> JBluetoothGattCharacteristic<'a> { + pub fn from_env(env: &mut JNIEnv<'a>, obj: JObject<'a>) -> Result { + let class = 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")?; @@ -477,105 +492,112 @@ impl<'a: 'b, 'b> JBluetoothGattCharacteristic<'a, 'b> { 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, ReturnType::Object, &[])? - .l()?; - let uuid_obj = JUuid::from_env(self.env, obj)?; - Ok(uuid_obj.as_uuid()?) + pub fn get_uuid(&self, env: &mut JNIEnv<'a>) -> Result { + let obj = unsafe { + env.call_method_unchecked(&self.internal, self.get_uuid, ReturnType::Object, &[]) + }? + .l()?; + let uuid_obj = JUuid::from_env(env, obj)?; + uuid_obj.as_uuid(env) } - pub fn get_properties(&self) -> Result { - let flags = self - .env - .call_method_unchecked( - self.internal, + pub fn get_properties(&self, env: &mut JNIEnv<'a>) -> Result { + let flags = unsafe { + env.call_method_unchecked( + &self.internal, self.get_properties, ReturnType::Primitive(Primitive::Int), &[], - )? - .i()?; + ) + }? + .i()?; 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, ReturnType::Array, &[])? - .l()?; - crate::droidplug::jni_utils::arrays::byte_array_to_vec(self.env, value.into_raw()) + pub fn get_value(&self, env: &mut JNIEnv<'a>) -> Result> { + let value = unsafe { + env.call_method_unchecked(&self.internal, self.get_value, ReturnType::Array, &[]) + }? + .l()?; + let value_arr = unsafe { jni::objects::JByteArray::from_raw(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, ReturnType::Object, &[])? - .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 JNIEnv<'a>, + ) -> Result>> { + let obj = unsafe { + env.call_method_unchecked( + &self.internal, + self.get_descriptors, + ReturnType::Object, + &[], + ) + }? + .l()?; + let size = env.call_method(&obj, "size", "()I", &[])?.i()?; + let mut desc_vec = Vec::with_capacity(size as usize); + for i in 0..size { + let desc = env + .call_method(&obj, "get", "(I)Ljava/lang/Object;", &[jni::objects::JValue::from(i)])? + .l()?; + desc_vec.push(JBluetoothGattDescriptor::from_env(env, desc)?); } Ok(desc_vec) } } -pub struct JBluetoothGattDescriptor<'a: 'b, 'b> { +pub struct JBluetoothGattDescriptor<'a> { internal: JObject<'a>, get_uuid: JMethodID, - env: &'b JNIEnv<'a>, } -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")?); +impl<'a> JBluetoothGattDescriptor<'a> { + pub fn from_env(env: &mut JNIEnv<'a>, obj: JObject<'a>) -> Result { + let class = 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, ReturnType::Object, &[])? - .l()?; - let uuid_obj = JUuid::from_env(self.env, obj)?; - Ok(uuid_obj.as_uuid()?) + pub fn get_uuid(&self, env: &mut JNIEnv<'a>) -> Result { + let obj = unsafe { + env.call_method_unchecked(&self.internal, self.get_uuid, ReturnType::Object, &[]) + }? + .l()?; + let uuid_obj = JUuid::from_env(env, obj)?; + uuid_obj.as_uuid(env) } } -pub struct JBluetoothDevice<'a: 'b, 'b> { +pub struct JBluetoothDevice<'a> { internal: JObject<'a>, get_address: JMethodID, - 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")?); +impl<'a> JBluetoothDevice<'a> { + pub fn from_env(env: &mut JNIEnv<'a>, obj: JObject<'a>) -> Result { + let class = 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, ReturnType::Object, &[])? - .l()?; + pub fn get_address(&self, env: &mut JNIEnv<'a>) -> Result> { + let obj = unsafe { + env.call_method_unchecked(&self.internal, self.get_address, ReturnType::Object, &[]) + }? + .l()?; Ok(obj.into()) } } @@ -585,27 +607,25 @@ pub struct JScanFilter<'a> { } impl<'a> JScanFilter<'a> { - pub fn new(env: &'a JNIEnv<'a>, filter: ScanFilter) -> Result { + pub fn new(env: &mut JNIEnv<'a>, filter: ScanFilter) -> Result { + let string_class = env.find_class("java/lang/String")?; let uuids = env.new_object_array( filter.services.len() as i32, - env.find_class("java/lang/String")?, - JObject::null(), + &string_class, + &JObject::null(), )?; 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)?; + env.set_object_array_element(&uuids, idx as i32, &uuid_str)?; } + let class_static = crate::droidplug::jni_utils::classcache::get_class( + "com/nonpolynomial/btleplug/android/impl/ScanFilter", + ) + .unwrap(); 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(), + <&JClass>::from(class_static.as_obj()), "([Ljava/lang/String;)V", - &[uuids.into()], + &[(&uuids).into()], )?; Ok(Self { internal: obj }) } @@ -617,18 +637,17 @@ impl<'a> From> for JObject<'a> { } } -pub struct JScanResult<'a: 'b, 'b> { +pub struct JScanResult<'a> { internal: JObject<'a>, get_device: JMethodID, get_scan_record: JMethodID, get_tx_power: JMethodID, get_rssi: JMethodID, - env: &'b JNIEnv<'a>, } -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")?); +impl<'a> JScanResult<'a> { + pub fn from_env(env: &mut JNIEnv<'a>, obj: JObject<'a>) -> Result { + let class = env.find_class("android/bluetooth/le/ScanResult")?; let get_device = env.get_method_id(&class, "getDevice", "()Landroid/bluetooth/BluetoothDevice;")?; @@ -645,86 +664,80 @@ impl<'a: 'b, 'b> JScanResult<'a, 'b> { 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, ReturnType::Object, &[])? - .l()?; - JBluetoothDevice::from_env(self.env, obj) + pub fn get_device(&self, env: &mut JNIEnv<'a>) -> Result> { + let obj = unsafe { + env.call_method_unchecked(&self.internal, self.get_device, ReturnType::Object, &[]) + }? + .l()?; + JBluetoothDevice::from_env(env, obj) } - pub fn get_scan_record(&self) -> Result> { - let obj = self - .env - .call_method_unchecked(self.internal, self.get_scan_record, ReturnType::Object, &[])? - .l()?; - JScanRecord::from_env(self.env, obj) + pub fn get_scan_record(&self, env: &mut JNIEnv<'a>) -> Result> { + let obj = unsafe { + env.call_method_unchecked( + &self.internal, + self.get_scan_record, + ReturnType::Object, + &[], + ) + }? + .l()?; + JScanRecord::from_env(env, obj) } - pub fn get_tx_power(&self) -> Result { - self.env - .call_method_unchecked( - self.internal, + pub fn get_tx_power(&self, env: &mut JNIEnv<'a>) -> Result { + unsafe { + env.call_method_unchecked( + &self.internal, self.get_tx_power, ReturnType::Primitive(Primitive::Int), &[], - )? - .i() + ) + }? + .i() } - pub fn get_rssi(&self) -> Result { - self.env - .call_method_unchecked( - self.internal, + pub fn get_rssi(&self, env: &mut JNIEnv<'a>) -> Result { + unsafe { + env.call_method_unchecked( + &self.internal, self.get_rssi, ReturnType::Primitive(Primitive::Int), &[], - )? - .i() + ) + }? + .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 JNIEnv<'a>, + ) -> 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 device = self.get_device(env)?; + let addr_jstr = device.get_address(env)?; + let addr_str = env.get_string(&addr_jstr)?; let addr = BDAddr::from_str( addr_str .to_str() - .map_err(|e| Self::Error::Other(e.into()))?, + .map_err(|e| crate::Error::Other(e.into()))?, )?; - 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 = self.get_scan_record(env)?; + let record_is_null = env.is_same_object(&*record, JObject::null())?; + let properties = if record_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 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: JString = device_name_obj.into(); + let device_name_str = env.get_string(&device_name_jstr)?; Some( String::from_utf8_lossy(device_name_str.to_bytes()) .chars() @@ -733,65 +746,87 @@ impl<'a: 'b, 'b> TryFrom> for (BDAddr, Option TryFrom> for (BDAddr, Option { +pub struct JScanRecord<'a> { internal: JObject<'a>, get_device_name: JMethodID, get_tx_power_level: JMethodID, get_manufacturer_specific_data: JMethodID, get_service_data: JMethodID, get_service_uuids: JMethodID, - env: &'b JNIEnv<'a>, } -impl<'a: 'b, 'b> From> for JObject<'a> { - fn from(scan_record: JScanRecord<'a, 'b>) -> Self { +impl<'a> From> for JObject<'a> { + fn from(scan_record: JScanRecord<'a>) -> Self { scan_record.internal } } -impl<'a: 'b, 'b> ::std::ops::Deref for JScanRecord<'a, 'b> { +impl<'a> ::std::ops::Deref for JScanRecord<'a> { type Target = JObject<'a>; fn deref(&self) -> &Self::Target { @@ -837,9 +871,9 @@ impl<'a: 'b, 'b> ::std::ops::Deref for JScanRecord<'a, 'b> { } } -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")?); +impl<'a> JScanRecord<'a> { + pub fn from_env(env: &mut JNIEnv<'a>, obj: JObject<'a>) -> Result { + let class = 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")?; @@ -858,85 +892,88 @@ impl<'a: 'b, 'b> JScanRecord<'a, 'b> { get_manufacturer_specific_data, get_service_data, get_service_uuids, - env, }) } - pub fn get_device_name(&self) -> Result> { - let obj = self - .env - .call_method_unchecked(self.internal, self.get_device_name, ReturnType::Object, &[])? - .l()?; - Ok(obj.into()) + pub fn get_device_name(&self, env: &mut JNIEnv<'a>) -> Result> { + unsafe { + env.call_method_unchecked( + &self.internal, + self.get_device_name, + ReturnType::Object, + &[], + ) + }? + .l() } - pub fn get_tx_power_level(&self) -> Result { - self.env - .call_method_unchecked( - self.internal, + pub fn get_tx_power_level(&self, env: &mut JNIEnv<'a>) -> Result { + unsafe { + env.call_method_unchecked( + &self.internal, self.get_tx_power_level, ReturnType::Primitive(Primitive::Int), &[], - )? - .i() + ) + }? + .i() } - pub fn get_manufacturer_specific_data(&self) -> Result> { - let obj = self - .env - .call_method_unchecked( - self.internal, + pub fn get_manufacturer_specific_data( + &self, + env: &mut JNIEnv<'a>, + ) -> Result> { + let obj = unsafe { + env.call_method_unchecked( + &self.internal, self.get_manufacturer_specific_data, ReturnType::Object, &[], - )? - .l()?; - JSparseArray::from_env(self.env, obj) + ) + }? + .l()?; + JSparseArray::from_env(env, obj) } - pub fn get_service_data(&self) -> Result> { - let obj = self - .env - .call_method_unchecked( - self.internal, + pub fn get_service_data(&self, env: &mut JNIEnv<'a>) -> Result> { + unsafe { + env.call_method_unchecked( + &self.internal, self.get_service_data, ReturnType::Object, &[], - )? - .l()?; - JMap::from_env(self.env, obj) + ) + }? + .l() } - pub fn get_service_uuids(&self) -> Result> { - let obj = self - .env - .call_method_unchecked( - self.internal, + pub fn get_service_uuids(&self, env: &mut JNIEnv<'a>) -> Result> { + unsafe { + env.call_method_unchecked( + &self.internal, self.get_service_uuids, ReturnType::Object, &[], - )? - .l()?; - JList::from_env(self.env, obj) + ) + }? + .l() } } -#[derive(Clone)] -pub struct JSparseArray<'a: 'b, 'b> { +pub struct JSparseArray<'a> { internal: JObject<'a>, size: JMethodID, key_at: JMethodID, value_at: JMethodID, - env: &'b JNIEnv<'a>, } -impl<'a: 'b, 'b> From> for JObject<'a> { - fn from(sparse_array: JSparseArray<'a, 'b>) -> Self { +impl<'a> From> for JObject<'a> { + fn from(sparse_array: JSparseArray<'a>) -> Self { sparse_array.internal } } -impl<'a: 'b, 'b> ::std::ops::Deref for JSparseArray<'a, 'b> { +impl<'a> ::std::ops::Deref for JSparseArray<'a> { type Target = JObject<'a>; fn deref(&self) -> &Self::Target { @@ -944,9 +981,9 @@ impl<'a: 'b, 'b> ::std::ops::Deref for JSparseArray<'a, 'b> { } } -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")?); +impl<'a> JSparseArray<'a> { + pub fn from_env(env: &mut JNIEnv<'a>, obj: JObject<'a>) -> Result { + let class = 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")?; @@ -956,100 +993,67 @@ impl<'a: 'b, 'b> JSparseArray<'a, 'b> { size, key_at, value_at, - env, }) } - pub fn size(&self) -> Result { - self.env - .call_method_unchecked( - self.internal, + pub fn size(&self, env: &mut JNIEnv<'a>) -> Result { + unsafe { + env.call_method_unchecked( + &self.internal, self.size, ReturnType::Primitive(Primitive::Int), &[], - )? - .i() + ) + }? + .i() } - pub fn key_at(&self, index: jint) -> Result { - self.env - .call_method_unchecked( - self.internal, + pub fn key_at(&self, env: &mut JNIEnv<'a>, index: jint) -> Result { + unsafe { + env.call_method_unchecked( + &self.internal, self.key_at, ReturnType::Primitive(Primitive::Int), - &[JValue::from(index).to_jni()], - )? - .i() + &[jvalue { i: index }], + ) + }? + .i() } - pub fn value_at(&self, index: jint) -> Result> { - self.env - .call_method_unchecked( - self.internal, + pub fn value_at(&self, env: &mut JNIEnv<'a>, index: jint) -> Result> { + unsafe { + env.call_method_unchecked( + &self.internal, self.value_at, ReturnType::Object, - &[JValue::from(index).to_jni()], - )? - .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)) - }) + &[jvalue { i: index }], + ) + }? + .l() } } -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> { +pub struct JParcelUuid<'a> { internal: JObject<'a>, get_uuid: JMethodID, - 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")?); +impl<'a> JParcelUuid<'a> { + pub fn from_env(env: &mut JNIEnv<'a>, obj: JObject<'a>) -> Result { + let class = 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, ReturnType::Object, &[])? - .l()?; - JUuid::from_env(self.env, obj) + pub fn get_uuid(&self, env: &mut JNIEnv<'a>) -> Result> { + let obj = unsafe { + env.call_method_unchecked(&self.internal, self.get_uuid, ReturnType::Object, &[]) + }? + .l()?; + JUuid::from_env(env, obj) } } diff --git a/src/droidplug/jni_utils/arrays.rs b/src/droidplug/jni_utils/arrays.rs index e471fb9d..435e8777 100644 --- a/src/droidplug/jni_utils/arrays.rs +++ b/src/droidplug/jni_utils/arrays.rs @@ -1,25 +1,26 @@ use jni::{ JNIEnv, errors::Result, - sys::{jbyte, jbyteArray, jint}, + objects::JByteArray, + sys::{jbyte, jint}, }; 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 { +pub fn slice_to_byte_array<'local>(env: &mut JNIEnv<'local>, slice: &[u8]) -> Result> { let obj = env.new_byte_array(slice.len() as jint)?; let slice = unsafe { &*(slice as *const [u8] as *const [jbyte]) }; - env.set_byte_array_region(obj, 0, slice)?; + env.set_byte_array_region(&obj, 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: &JNIEnv, array: &JByteArray) -> Result> { + let size = env.get_array_length(array)? as usize; 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)?; + env.get_byte_array_region(array, 0, result_slice)?; result.set_len(size); } Ok(result) @@ -31,23 +32,26 @@ mod test { #[test] fn test_slice_to_byte_array() { - test_utils::JVM_ENV.with(|env| { + test_utils::JVM_ENV.with(|cell| { + let env = &mut *cell.borrow_mut(); 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!(env.get_array_length(&obj).unwrap(), 5); let mut bytes = [0i8; 5]; - env.get_byte_array_region(obj, 0, &mut bytes).unwrap(); + env.get_byte_array_region(&obj, 0, &mut bytes).unwrap(); assert_eq!(bytes, [1, 2, 3, 4, 5]); }); } #[test] fn test_byte_array_to_vec() { - test_utils::JVM_ENV.with(|env| { + test_utils::JVM_ENV.with(|cell| { + let env = &mut *cell.borrow_mut(); let obj = env.new_byte_array(5).unwrap(); - env.set_byte_array_region(obj, 0, &[1, 2, 3, 4, 5]).unwrap(); + env.set_byte_array_region(&obj, 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]); }); } diff --git a/src/droidplug/jni_utils/classcache.rs b/src/droidplug/jni_utils/classcache.rs index 3b0f63ff..f297e5d8 100644 --- a/src/droidplug/jni_utils/classcache.rs +++ b/src/droidplug/jni_utils/classcache.rs @@ -4,19 +4,15 @@ use once_cell::sync::OnceCell; static CLASSCACHE: OnceCell> = OnceCell::new(); -pub fn find_add_class(env: &JNIEnv, classname: &str) -> Result<()> { +pub fn find_add_class(env: &mut 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(), - ); + let cls = env.find_class(classname)?; + let global = env.new_global_ref(cls)?; + cache.insert(classname.to_owned(), global); 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())) + cache.get(classname).map(|pair| pair.value().clone()) } diff --git a/src/droidplug/jni_utils/exceptions.rs b/src/droidplug/jni_utils/exceptions.rs index 6f394376..c397723b 100644 --- a/src/droidplug/jni_utils/exceptions.rs +++ b/src/droidplug/jni_utils/exceptions.rs @@ -6,15 +6,13 @@ use jni::{ }; 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 +20,61 @@ 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 JNIEnv, + block: impl FnOnce(&mut JNIEnv) -> Result, +) -> TryCatchResult { TryCatchResult { - env, try_result: (|| { 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 JNIEnv<'local>, + class: impl Desc<'local, JClass<'local>>, + block: impl FnOnce(&mut JNIEnv<'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.is_instance_of(&ex, class)? { + return block(env, ex).map(|o| Some(o)); } - env.throw(ex)?; + 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 +93,58 @@ 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 JNIEnv<'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()], + &[(&msg).into()], )?; - unsafe { env.set_rust_field(obj, "any", any) }?; - Self::from_env(env, obj.into()) + unsafe { env.set_rust_field(&obj, "any", any) }?; + Ok(Self { + internal: obj.into(), + }) } - pub fn get(&self) -> Result>, Error> { - unsafe { self.env.get_rust_field(self.internal, "any") } + pub fn get<'b>( + &self, + env: &'b mut JNIEnv, + ) -> Result>, Error> { + unsafe { env.get_rust_field(&self.internal, "any") } } - pub fn take(&self) -> Result, Error> { - unsafe { self.env.take_rust_field(self.internal, "any") } + pub fn take(&self, env: &mut JNIEnv) -> Result, Error> { + unsafe { env.take_rust_field(&self.internal, "any") } } - pub fn resume_unwind(&self) -> Result<(), Error> { - resume_unwind(self.take()?); + pub fn resume_unwind(&self, env: &mut JNIEnv) -> 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,45 +152,53 @@ 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 JNIEnv, + panic: Box, +) -> Result<(), Error> { + let old_ex = if env.exception_check()? { + let ex = env.exception_occurred()?; + env.exception_clear()?; + Some(ex) + } else { + None + }; + let ex = JPanicException::new(env, panic)?; + + if let Some(old_ex) = old_ex { + env.call_method( + &*ex, + "addSuppressed", + "(Ljava/lang/Throwable;)V", + &[(&old_ex).into()], + )?; + } + let ex: JThrowable = ex.into(); + env.throw(&ex)?; + 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 JNIEnv, 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::{JNIEnv, errors::Error, objects::{JObject, JThrowable}}; use super::super::test_utils; use super::try_block; - fn test_catch<'a: 'b, 'b>( - env: &'b JNIEnv<'a>, + fn test_catch( + env: &mut JNIEnv, throw_class: Option<&str>, try_result: Result, rethrow: bool, @@ -218,59 +213,72 @@ mod test { let illegal_argument_exception = env .find_class("java/lang/IllegalArgumentException") .unwrap(); - if let Some(ex) = old_ex { + if let Some(ref ex) = old_ex { env.throw(ex).unwrap(); } let ex = throw_class.map(|c| { - let ex: JThrowable = env.new_object(c, "()V", &[]).unwrap().into(); - ex + let obj = env.new_object(c, "()V", &[]).unwrap(); + JThrowable::from(obj) }); - try_block(env, || { - if let Some(t) = ex { + try_block(env, |env| { + if let Some(ref t) = ex { env.throw(t).unwrap(); } try_result }) - .catch(illegal_argument_exception, |caught| { + .catch(env, illegal_argument_exception, |env, caught| { assert!(!env.exception_check().unwrap()); - assert!(env.is_same_object(ex.unwrap(), caught).unwrap()); + 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, + "java/lang/ArrayIndexOutOfBoundsException", + |env, caught| { + assert!(!env.exception_check().unwrap()); + assert!(env.is_same_object(&caught, ex.as_ref().unwrap()).unwrap()); + if rethrow { + Err(Error::JavaException) + } else { + Ok(2) + } + }, + ) + .catch( + env, + "java/lang/IndexOutOfBoundsException", + |env, caught| { + assert!(!env.exception_check().unwrap()); + assert!(env.is_same_object(&caught, ex.as_ref().unwrap()).unwrap()); + if rethrow { + env.throw(&caught).unwrap(); + Err(Error::JavaException) + } else { + Ok(3) + } + }, + ) + .catch( + env, + "java/lang/StringIndexOutOfBoundsException", + |env, caught| { + assert!(!env.exception_check().unwrap()); + 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::JVM_ENV.with(|cell| { + let env = &mut *cell.borrow_mut(); assert_eq!( test_catch( - &env, + env, Some("java/lang/IllegalArgumentException"), Err(Error::JavaException), false, @@ -284,10 +292,11 @@ mod test { #[test] fn test_catch_second() { - test_utils::JVM_ENV.with(|env| { + test_utils::JVM_ENV.with(|cell| { + let env = &mut *cell.borrow_mut(); assert_eq!( test_catch( - &env, + env, Some("java/lang/ArrayIndexOutOfBoundsException"), Err(Error::JavaException), false, @@ -301,10 +310,11 @@ mod test { #[test] fn test_catch_third() { - test_utils::JVM_ENV.with(|env| { + test_utils::JVM_ENV.with(|cell| { + let env = &mut *cell.borrow_mut(); assert_eq!( test_catch( - &env, + env, Some("java/lang/StringIndexOutOfBoundsException"), Err(Error::JavaException), false, @@ -318,17 +328,19 @@ mod test { #[test] fn test_catch_ok() { - test_utils::JVM_ENV.with(|env| { - assert_eq!(test_catch(&env, None, Ok(0), false).unwrap(), 0); + test_utils::JVM_ENV.with(|cell| { + let env = &mut *cell.borrow_mut(); + assert_eq!(test_catch(env, None, Ok(0), false).unwrap(), 0); assert!(!env.exception_check().unwrap()); }); } #[test] fn test_catch_none() { - test_utils::JVM_ENV.with(|env| { + test_utils::JVM_ENV.with(|cell| { + let env = &mut *cell.borrow_mut(); if let Error::JavaException = test_catch( - &env, + env, Some("java/lang/SecurityException"), Err(Error::JavaException), false, @@ -339,7 +351,7 @@ mod test { let ex = env.exception_occurred().unwrap(); env.exception_clear().unwrap(); assert!( - env.is_instance_of(ex, "java/lang/SecurityException") + env.is_instance_of(&ex, "java/lang/SecurityException") .unwrap() ); } else { @@ -350,7 +362,8 @@ mod test { #[test] fn test_catch_other() { - test_utils::JVM_ENV.with(|env| { + test_utils::JVM_ENV.with(|cell| { + let env = &mut *cell.borrow_mut(); if let Error::InvalidCtorReturn = test_catch(env, None, Err(Error::InvalidCtorReturn), false).unwrap_err() { @@ -363,7 +376,8 @@ mod test { #[test] fn test_catch_bogus_exception() { - test_utils::JVM_ENV.with(|env| { + test_utils::JVM_ENV.with(|cell| { + let env = &mut *cell.borrow_mut(); if let Error::JavaException = test_catch(env, None, Err(Error::JavaException), false).unwrap_err() { @@ -376,18 +390,19 @@ mod test { #[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::JVM_ENV.with(|cell| { + let env = &mut *cell.borrow_mut(); + let ex = JThrowable::from( + env.new_object("java/lang/IllegalArgumentException", "()V", &[]) + .unwrap(), + ); + env.throw(&ex).unwrap(); - if let Error::JavaException = test_catch(&env, None, Ok(0), false).unwrap_err() { + if let Error::JavaException = test_catch(env, None, Ok(0), false).unwrap_err() { assert!(env.exception_check().unwrap()); let actual_ex = env.exception_occurred().unwrap(); env.exception_clear().unwrap(); - assert!(env.is_same_object(actual_ex, ex).unwrap()); + assert!(env.is_same_object(&actual_ex, &ex).unwrap()); } else { panic!("JavaException not found"); } @@ -396,9 +411,10 @@ mod test { #[test] fn test_catch_rethrow() { - test_utils::JVM_ENV.with(|env| { + test_utils::JVM_ENV.with(|cell| { + let env = &mut *cell.borrow_mut(); if let Error::JavaException = test_catch( - &env, + env, Some("java/lang/StringIndexOutOfBoundsException"), Err(Error::JavaException), true, @@ -409,7 +425,7 @@ mod test { let ex = env.exception_occurred().unwrap(); env.exception_clear().unwrap(); assert!( - env.is_instance_of(ex, "java/lang/StringIndexOutOfBoundsException") + env.is_instance_of(&ex, "java/lang/StringIndexOutOfBoundsException") .unwrap() ); } else { @@ -420,9 +436,10 @@ mod test { #[test] fn test_catch_bogus_rethrow() { - test_utils::JVM_ENV.with(|env| { + test_utils::JVM_ENV.with(|cell| { + let env = &mut *cell.borrow_mut(); if let Error::JavaException = test_catch( - &env, + env, Some("java/lang/ArrayIndexOutOfBoundsException"), Err(Error::JavaException), true, @@ -438,84 +455,91 @@ mod test { #[test] fn test_panic_exception_static_str() { - test_utils::JVM_ENV.with(|env| { - use jni::{objects::JString, strings::JavaStr}; + test_utils::JVM_ENV.with(|cell| { + let mut guard = cell.borrow_mut(); + let env = &mut *guard; + 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;", &[]) + .call_method(&*ex, "getMessage", "()Ljava/lang/String;", &[]) .unwrap() .l() .unwrap() .into(); - let str = JavaStr::from_env(env, msg).unwrap(); - assert_eq!(str.to_str().unwrap(), STATIC_MSG); + let str = env.get_string(&msg).unwrap(); + assert_eq!(>::from(str), STATIC_MSG); }); } #[test] fn test_panic_exception_string() { - test_utils::JVM_ENV.with(|env| { - use jni::{objects::JString, strings::JavaStr}; + test_utils::JVM_ENV.with(|cell| { + let mut guard = cell.borrow_mut(); + let env = &mut *guard; + 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;", &[]) + .call_method(&*ex, "getMessage", "()Ljava/lang/String;", &[]) .unwrap() .l() .unwrap() .into(); - let str = JavaStr::from_env(env, msg).unwrap(); - assert_eq!(str.to_str().unwrap(), STRING_MSG); + let str = env.get_string(&msg).unwrap(); + assert_eq!(>::from(str), STRING_MSG); - let any: Box = ex.take().unwrap(); + let any: Box = ex.take(env).unwrap(); assert_eq!(*any.downcast::().unwrap(), STRING_MSG); }); } #[test] fn test_panic_exception_other() { - test_utils::JVM_ENV.with(|env| { + test_utils::JVM_ENV.with(|cell| { + let mut guard = cell.borrow_mut(); + let env = &mut *guard; 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, "getMessage", "()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); }); } #[test] fn test_throw_unwind_ok() { - test_utils::JVM_ENV.with(|env| { + test_utils::JVM_ENV.with(|cell| { + let env = &mut *cell.borrow_mut(); let result = super::throw_unwind(env, || 42).unwrap(); assert_eq!(result, 42); assert!(!env.exception_check().unwrap()); @@ -524,7 +548,8 @@ mod test { #[test] fn test_throw_unwind_panic() { - test_utils::JVM_ENV.with(|env| { + test_utils::JVM_ENV.with(|cell| { + let env = &mut *cell.borrow_mut(); super::throw_unwind(env, || panic!("This is a panic")) .unwrap_err() .unwrap(); @@ -532,22 +557,22 @@ mod test { let ex = env.exception_occurred().unwrap(); env.exception_clear().unwrap(); assert!( - env.is_instance_of(ex, "io/github/gedgygedgy/rust/panic/PanicException") + env.is_instance_of(&ex, "io/github/gedgygedgy/rust/panic/PanicException") .unwrap() ); let suppressed_list = env - .call_method(ex, "getSuppressed", "()[Ljava/lang/Throwable;", &[]) + .call_method(&ex, "getSuppressed", "()[Ljava/lang/Throwable;", &[]) .unwrap() .l() .unwrap(); - assert_eq!( - env.get_array_length(suppressed_list.into_raw()).unwrap(), - 0 - ); + let suppressed_array = + unsafe { jni::objects::JObjectArray::from_raw(suppressed_list.into_raw()) }; + assert_eq!(env.get_array_length(&suppressed_array).unwrap(), 0); - let ex = super::JPanicException::from_env(env, ex).unwrap(); - let any = ex.take().unwrap(); + let ex_throwable = JThrowable::from(JObject::from(ex)); + let ex = super::JPanicException::from_env(ex_throwable); + let any = ex.take(env).unwrap(); let str = any.downcast::<&str>().unwrap(); assert_eq!(*str, "This is a panic"); }); @@ -555,12 +580,11 @@ mod test { #[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::JVM_ENV.with(|cell| { + let env = &mut *cell.borrow_mut(); + let old_ex = + JThrowable::from(env.new_object("java/lang/Exception", "()V", &[]).unwrap()); + env.throw(&old_ex).unwrap(); super::throw_unwind(env, || panic!("This is a panic")) .unwrap_err() @@ -569,26 +593,24 @@ mod test { let ex = env.exception_occurred().unwrap(); env.exception_clear().unwrap(); assert!( - env.is_instance_of(ex, "io/github/gedgygedgy/rust/panic/PanicException") + env.is_instance_of(&ex, "io/github/gedgygedgy/rust/panic/PanicException") .unwrap() ); let suppressed_list = env - .call_method(ex, "getSuppressed", "()[Ljava/lang/Throwable;", &[]) + .call_method(&ex, "getSuppressed", "()[Ljava/lang/Throwable;", &[]) .unwrap() .l() .unwrap(); - assert_eq!( - env.get_array_length(suppressed_list.into_raw()).unwrap(), - 1 - ); - let suppressed_ex = env - .get_object_array_element(suppressed_list.into_raw(), 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(suppressed_list.into_raw()) }; + assert_eq!(env.get_array_length(&suppressed_array).unwrap(), 1); + let suppressed_ex = env.get_object_array_element(&suppressed_array, 0).unwrap(); + assert!(env.is_same_object(&old_ex, &suppressed_ex).unwrap()); + + let ex_throwable = JThrowable::from(JObject::from(ex)); + let ex = super::JPanicException::from_env(ex_throwable); + let any = ex.take(env).unwrap(); let str = any.downcast::<&str>().unwrap(); assert_eq!(*str, "This is a panic"); }); @@ -597,9 +619,10 @@ mod test { #[test] #[should_panic(expected = "This is a panic")] fn test_panic_exception_resume_unwind() { - test_utils::JVM_ENV.with(|env| { + test_utils::JVM_ENV.with(|cell| { + let env = &mut *cell.borrow_mut(); let ex = super::JPanicException::new(env, Box::new("This is a panic")).unwrap(); - ex.resume_unwind().unwrap(); + ex.resume_unwind(env).unwrap(); }); } } diff --git a/src/droidplug/jni_utils/future.rs b/src/droidplug/jni_utils/future.rs index ffaf4e0e..2a42b404 100644 --- a/src/droidplug/jni_utils/future.rs +++ b/src/droidplug/jni_utils/future.rs @@ -1,67 +1,60 @@ -use super::task::JPollResult; use ::jni::{ JNIEnv, JavaVM, - errors::{Error, Result}, - objects::{GlobalRef, JClass, JMethodID, JObject, JValue}, + errors::Result, + objects::{GlobalRef, JClass, JMethodID, JObject}, signature::ReturnType, + sys::jvalue, }; 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. +/// `io.github.gedgygedgy.rust.future.Future`. Provides a typed interface for +/// calling the Java future's `poll` method. /// -/// For a [`Send`] version of this, use [`JSendFuture`]. -pub struct JFuture<'a: 'b, 'b> { +/// For an async [`Future`](std::future::Future) implementation, convert to +/// [`JSendFuture`] via [`JSendFuture::new`]. +pub struct JFuture<'a> { internal: JObject<'a>, - poll: JMethodID, - env: &'b JNIEnv<'a>, + poll_id: JMethodID, } -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(), - ), +impl<'a> JFuture<'a> { + pub fn from_env(env: &mut JNIEnv<'a>, obj: JObject<'a>) -> Result { + let class = + super::classcache::get_class("io/github/gedgygedgy/rust/future/Future").unwrap(); + let poll_id = env.get_method_id( + <&JClass>::from(class.as_obj()), "poll", "(Lio/github/gedgygedgy/rust/task/Waker;)Lio/github/gedgygedgy/rust/task/PollResult;", )?; Ok(Self { internal: obj, - poll, - env, + poll_id, }) } - pub fn poll(&self, waker: JObject<'a>) -> Result> { - let result = self - .env - .call_method_unchecked( - self.internal, - self.poll, + pub fn poll(&self, env: &mut JNIEnv<'a>, waker: &JObject<'_>) -> Result> { + let result = unsafe { + env.call_method_unchecked( + &self.internal, + self.poll_id, ReturnType::Object, - &[JValue::from(waker).to_jni()], - )? - .l()?; - JPollResult::from_env(self.env, result) - } - - pub fn into_future(self) -> JFutureIntoFuture<'a, 'b> { - JFutureIntoFuture(self) + &[jvalue { + l: waker.as_raw(), + }], + ) + }? + .l()?; + Ok(result) } } -impl<'a: 'b, 'b> ::std::ops::Deref for JFuture<'a, 'b> { +impl<'a> ::std::ops::Deref for JFuture<'a> { type Target = JObject<'a>; fn deref(&self) -> &Self::Target { @@ -69,67 +62,62 @@ impl<'a: 'b, 'b> ::std::ops::Deref for JFuture<'a, 'b> { } } -impl<'a: 'b, 'b> From> for JObject<'a> { - fn from(other: JFuture<'a, 'b>) -> JObject<'a> { +impl<'a> From> for JObject<'a> { + fn from(other: JFuture<'a>) -> 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 - } -} - -/// [`Send`] version of [`JFuture`]. +/// [`Send`] version of [`JFuture`]. Implements [`Future`](std::future::Future) +/// by obtaining a [`JNIEnv`] from the stored [`JavaVM`] on each poll. pub struct JSendFuture { internal: GlobalRef, + poll_id: JMethodID, vm: JavaVM, } -impl<'a: 'b, 'b> TryFrom> for JSendFuture { - type Error = Error; +impl JSendFuture { + pub fn new(env: &mut JNIEnv, future: &JFuture) -> Result { + Ok(Self { + internal: env.new_global_ref(&future.internal)?, + poll_id: future.poll_id, + vm: env.get_java_vm()?, + }) + } - fn try_from(future: JFuture<'a, 'b>) -> Result { + pub fn from_env(env: &mut JNIEnv, obj: &JObject) -> Result { + let class = + super::classcache::get_class("io/github/gedgygedgy/rust/future/Future").unwrap(); + let poll_id = env.get_method_id( + <&JClass>::from(class.as_obj()), + "poll", + "(Lio/github/gedgygedgy/rust/task/Waker;)Lio/github/gedgygedgy/rust/task/PollResult;", + )?; Ok(Self { - internal: future.env.new_global_ref(future.internal)?, - vm: future.env.get_java_vm()?, + internal: env.new_global_ref(obj)?, + poll_id, + vm: env.get_java_vm()?, + }) + } + + fn poll_internal(&self, context: &mut Context<'_>) -> Result>> { + let mut env = self.vm.get_env()?; + let jwaker = super::task::waker(&mut env, context.waker().clone())?; + let result = unsafe { + env.call_method_unchecked( + self.internal.as_obj(), + self.poll_id, + ReturnType::Object, + &[jvalue { + l: jwaker.as_raw(), + }], + ) + }? + .l()?; + Ok(if env.is_same_object(&result, JObject::null())? { + Poll::Pending + } else { + Poll::Ready(Ok(env.new_global_ref(result)?)) }) } } @@ -142,16 +130,6 @@ impl ::std::ops::Deref for JSendFuture { } } -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; @@ -167,7 +145,7 @@ 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 std::{ future::Future, @@ -177,9 +155,12 @@ mod test { #[test] fn test_jfuture() { + use super::super::task::JPollResult; use std::sync::Arc; - test_utils::JVM_ENV.with(|env| { + test_utils::JVM_ENV.with(|cell| { + let env = &mut *cell.borrow_mut(); + let data = Arc::new(test_utils::TestWakerData::new()); assert_eq!(Arc::strong_count(&data), 1); assert_eq!(data.value(), false); @@ -191,7 +172,9 @@ mod test { let future_obj = env .new_object("io/github/gedgygedgy/rust/future/SimpleFuture", "()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 = JFuture::from_env(env, 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() @@ -206,17 +189,23 @@ mod test { 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(); + env.call_method( + &future_obj, + "wake", + "(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 = JPollResult::from_env(env, 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,10 +214,11 @@ 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 = JPollResult::from_env(env, 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"); } @@ -239,29 +229,46 @@ mod test { #[test] fn test_jfuture_await() { + use super::super::task::JPollResult; use futures::{executor::block_on, join}; - test_utils::JVM_ENV.with(|env| { - let future_obj = env - .new_object("io/github/gedgygedgy/rust/future/SimpleFuture", "()V", &[]) - .unwrap(); - let future = JFuture::from_env(env, future_obj).unwrap(); - let obj = env.new_object("java/lang/Object", "()V", &[]).unwrap(); + test_utils::JVM_ENV.with(|cell| { + let (future, future_obj_global, obj_global) = { + let env = &mut *cell.borrow_mut(); + let future_obj = env + .new_object("io/github/gedgygedgy/rust/future/SimpleFuture", "()V", &[]) + .unwrap(); + let future_obj_global = env.new_global_ref(&future_obj).unwrap(); + let future_local = env.new_local_ref(&future_obj).unwrap(); + let jfuture = JFuture::from_env(env, future_local).unwrap(); + let future = JSendFuture::new(env, &jfuture).unwrap(); + let obj = env.new_object("java/lang/Object", "()V", &[]).unwrap(); + let obj_global = env.new_global_ref(&obj).unwrap(); + (future, future_obj_global, obj_global) + }; block_on(async { join!( async { - env.call_method(future_obj, "wake", "(Ljava/lang/Object;)V", &[obj.into()]) - .unwrap(); + let env = &mut *cell.borrow_mut(); + 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, + "wake", + "(Ljava/lang/Object;)V", + &[(&obj_local).into()], + ) + .unwrap(); }, async { - assert!( - env.is_same_object( - future.into_future().await.unwrap().get().unwrap(), - obj - ) - .unwrap() - ); + let global = future.await.unwrap(); + let env = &mut *cell.borrow_mut(); + let local = env.new_local_ref(global.as_obj()).unwrap(); + let poll_result = JPollResult::from_env(env, 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()); } ); }); @@ -272,34 +279,53 @@ mod test { fn test_jfuture_await_throw() { use futures::{executor::block_on, join}; - test_utils::JVM_ENV.with(|env| { - let future_obj = env - .new_object("io/github/gedgygedgy/rust/future/SimpleFuture", "()V", &[]) - .unwrap(); - let future = JFuture::from_env(env, future_obj).unwrap(); - let ex = env.new_object("java/lang/Exception", "()V", &[]).unwrap(); + test_utils::JVM_ENV.with(|cell| { + let (future, future_obj_global, ex_global) = { + let env = &mut *cell.borrow_mut(); + let future_obj = env + .new_object("io/github/gedgygedgy/rust/future/SimpleFuture", "()V", &[]) + .unwrap(); + let future_obj_global = env.new_global_ref(&future_obj).unwrap(); + let future_local = env.new_local_ref(&future_obj).unwrap(); + let jfuture = JFuture::from_env(env, future_local).unwrap(); + let future = JSendFuture::new(env, &jfuture).unwrap(); + let ex = env.new_object("java/lang/Exception", "()V", &[]).unwrap(); + let ex_global = env.new_global_ref(&ex).unwrap(); + (future, future_obj_global, ex_global) + }; block_on(async { join!( async { + let env = &mut *cell.borrow_mut(); + 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, + &future_local, "wakeWithThrowable", "(Ljava/lang/Throwable;)V", - &[ex.into()], + &[(&ex_local).into()], ) .unwrap(); }, async { - future.into_future().await.unwrap().get().unwrap_err(); + use super::super::task::JPollResult; + + let global = future.await.unwrap(); + let env = &mut *cell.borrow_mut(); + let local = env.new_local_ref(global.as_obj()).unwrap(); + let poll_result = JPollResult::from_env(env, local).unwrap(); + let _err = poll_result.get(env).unwrap_err(); + let future_ex = env.exception_occurred().unwrap(); env.exception_clear().unwrap(); let actual_ex = env - .call_method(future_ex, "getCause", "()Ljava/lang/Throwable;", &[]) + .call_method(&future_ex, "getCause", "()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()); } ); }); @@ -308,27 +334,44 @@ mod test { #[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_obj = env - .new_object("io/github/gedgygedgy/rust/future/SimpleFuture", "()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(); + test_utils::JVM_ENV.with(|cell| { + let (future, future_obj_global, obj_global) = { + let env = &mut *cell.borrow_mut(); + let future_obj = env + .new_object("io/github/gedgygedgy/rust/future/SimpleFuture", "()V", &[]) + .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("java/lang/Object", "()V", &[]).unwrap(); + let obj_global = env.new_global_ref(&obj).unwrap(); + (future, future_obj_global, obj_global) + }; block_on(async { join!( async { - env.call_method(future_obj, "wake", "(Ljava/lang/Object;)V", &[obj.into()]) - .unwrap(); + let env = &mut *cell.borrow_mut(); + 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, + "wake", + "(Ljava/lang/Object;)V", + &[(&obj_local).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 env = &mut *cell.borrow_mut(); + let local = env.new_local_ref(global_ref.as_obj()).unwrap(); + let jpoll = JPollResult::from_env(env, 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()); } ); }); diff --git a/src/droidplug/jni_utils/mod.rs b/src/droidplug/jni_utils/mod.rs index 84bae6b2..6334cce9 100644 --- a/src/droidplug/jni_utils/mod.rs +++ b/src/droidplug/jni_utils/mod.rs @@ -12,13 +12,14 @@ pub(crate) mod test_utils { use jni::{JNIEnv, JavaVM, objects::GlobalRef}; use lazy_static::lazy_static; use std::{ + cell::RefCell, sync::{Arc, Mutex}, task::{Wake, Waker}, }; use jni::NativeMethod; - fn test_init(env: &JNIEnv) -> jni::errors::Result<()> { + fn test_init(env: &mut 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")?; @@ -31,7 +32,7 @@ pub(crate) mod test_utils { 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")?); + let class = env.find_class("io/github/gedgygedgy/rust/ops/FnAdapter")?; env.register_native_methods( &class, &[ @@ -89,8 +90,8 @@ pub(crate) mod test_utils { } thread_local! { - pub static JVM_ENV: JNIEnv<'static> = { - let env = JVM.jvm.attach_current_thread_permanently().unwrap(); + pub static JVM_ENV: RefCell> = { + let mut env = JVM.jvm.attach_current_thread_permanently().unwrap(); let thread = env .call_static_method( @@ -103,13 +104,13 @@ pub(crate) mod test_utils { .l() .unwrap(); env.call_method( - thread, + &thread, "setContextClassLoader", "(Ljava/lang/ClassLoader;)V", - &[JVM.class_loader.as_obj().into()] + &[(&JVM.class_loader).into()] ).unwrap(); - env + RefCell::new(env) } } @@ -125,17 +126,18 @@ 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 mut env = jvm.attach_current_thread_permanently().unwrap(); + test_init(&mut env).unwrap(); let thread = env .call_static_method( @@ -149,7 +151,7 @@ pub(crate) mod test_utils { .unwrap(); let class_loader = env .call_method( - thread, + &thread, "getContextClassLoader", "()Ljava/lang/ClassLoader;", &[], diff --git a/src/droidplug/jni_utils/ops.rs b/src/droidplug/jni_utils/ops.rs index 03902913..311cd4db 100644 --- a/src/droidplug/jni_utils/ops.rs +++ b/src/droidplug/jni_utils/ops.rs @@ -25,89 +25,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 JNIEnv<'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 = super::classcache::get_class($ic).unwrap(); env.new_object( - JClass::from(super::classcache::get_class($ic).unwrap().as_obj()), + <&JClass>::from(class.as_obj()), "(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 JNIEnv<'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 JNIEnv<'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 JNIEnv<'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 = super::classcache::get_class($ic).unwrap(); env.new_object( - JClass::from(super::classcache::get_class($ic).unwrap().as_obj()), + <&JClass>::from(class.as_obj()), "(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 JNIEnv<'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 JNIEnv<'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 JNIEnv<'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 = super::classcache::get_class($ic).unwrap(); env.new_object( - JClass::from(super::classcache::get_class($ic).unwrap().as_obj()), + <&JClass>::from(class.as_obj()), "(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 JNIEnv<'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 JNIEnv<'local>, f: impl for<'c, 'd> Fn$args -> $ret + 'static, - ) -> Result> { + ) -> Result> { $fi(env, f, true) } }; @@ -129,7 +132,7 @@ define_fn_adapter! { 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 JNIEnv<'c>, JObject<'c>) -> (), closure: move |env, _obj1, obj2, _arg1, _arg2| { f(env, obj2); JObject::null() @@ -152,7 +155,7 @@ define_fn_adapter! { 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 JNIEnv<'c>, JObject<'c>, JObject<'c>, JObject<'c>) -> JObject<'c>, closure: move |env, _obj1, obj2, arg1, arg2| { f(env, obj2, arg1, arg2) }, @@ -174,7 +177,7 @@ define_fn_adapter! { 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 JNIEnv<'c>, JObject<'c>, JObject<'c>) -> JObject<'c>, closure: move |env, _obj1, obj2, arg1, _arg2| { f(env, obj2, arg1) }, @@ -188,7 +191,7 @@ unsafe impl Sync for SendSyncWrapper {} type FnWrapper = SendSyncWrapper< Arc< dyn for<'a, 'b> Fn( - &'b JNIEnv<'a>, + &'b mut JNIEnv<'a>, JObject<'a>, JObject<'a>, JObject<'a>, @@ -198,10 +201,10 @@ type FnWrapper = SendSyncWrapper< >, >; -fn fn_once_adapter<'a: 'b, 'b>( - env: &'b JNIEnv<'a>, +fn fn_once_adapter<'local>( + env: &mut JNIEnv<'local>, f: impl for<'c, 'd> FnOnce( - &'d JNIEnv<'c>, + &'d mut JNIEnv<'c>, JObject<'c>, JObject<'c>, JObject<'c>, @@ -209,7 +212,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 +231,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 JNIEnv<'local>, f: impl for<'c, 'd> FnMut( - &'d JNIEnv<'c>, + &'d mut JNIEnv<'c>, JObject<'c>, JObject<'c>, JObject<'c>, @@ -239,7 +242,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 +254,10 @@ fn fn_mut_adapter<'a: 'b, 'b>( ) } -fn fn_adapter<'a: 'b, 'b>( - env: &'b JNIEnv<'a>, +fn fn_adapter<'local>( + env: &mut JNIEnv<'local>, f: impl for<'c, 'd> Fn( - &'d JNIEnv<'c>, + &'d mut JNIEnv<'c>, JObject<'c>, JObject<'c>, JObject<'c>, @@ -262,10 +265,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 JNIEnv<'c>, JObject<'c>, JObject<'c>, JObject<'c>, @@ -273,39 +276,47 @@ fn fn_adapter<'a: 'b, 'b>( ) -> JObject<'c>, > = Arc::from(f); + let class = super::classcache::get_class("io/github/gedgygedgy/rust/ops/FnAdapter").unwrap(); let obj = env.new_object( - JClass::from( - super::classcache::get_class("io/github/gedgygedgy/rust/ops/FnAdapter") - .unwrap() - .as_obj(), - ), + <&JClass>::from(class.as_obj()), "(Z)V", &[local.into()], )?; - unsafe { env.set_rust_field::<_, _, FnWrapper>(obj, "data", SendSyncWrapper(arc)) }?; + unsafe { env.set_rust_field::<_, _, FnWrapper>(&obj, "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: JNIEnv<'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) = unsafe { 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 arc = + if let Ok(f) = unsafe { env.get_rust_field::<_, _, FnWrapper>(&obj1, "data") } { + AssertUnwindSafe(f.0.clone()) + } else { + return JObject::null(); + }; + match catch_unwind(AssertUnwindSafe(|| arc(&mut env, obj1, obj2, arg1, arg2))) { + Ok(result) => result, + Err(panic) => { + let _ = super::exceptions::throw_panic(&mut env, panic); + JObject::null() + } + } } -pub(crate) extern "C" fn fn_adapter_close_internal(env: JNIEnv, obj: JObject) { - let _ = super::exceptions::throw_unwind(&env, || { - let _ = unsafe { env.take_rust_field::<_, _, FnWrapper>(obj, "data") }; - }); +pub(crate) extern "C" fn fn_adapter_close_internal(mut env: JNIEnv, obj: JObject) { + use std::panic::{AssertUnwindSafe, catch_unwind}; + + let result = catch_unwind(AssertUnwindSafe(|| { + let _ = unsafe { env.take_rust_field::<_, _, FnWrapper>(&obj, "data") }; + })); + if let Err(panic) = result { + let _ = super::exceptions::throw_panic(&mut env, panic); + } } diff --git a/src/droidplug/jni_utils/stream.rs b/src/droidplug/jni_utils/stream.rs index 636a4fc5..e8bb85f0 100644 --- a/src/droidplug/jni_utils/stream.rs +++ b/src/droidplug/jni_utils/stream.rs @@ -1,80 +1,65 @@ use super::task::JPollResult; use ::jni::{ JNIEnv, JavaVM, - errors::{Error, Result}, - objects::{GlobalRef, JClass, JMethodID, JObject, JValue}, + errors::Result, + objects::{GlobalRef, JClass, JMethodID, JObject}, signature::ReturnType, + sys::jvalue, }; 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`. +/// `io.github.gedgygedgy.rust.stream.Stream`. Provides a typed interface for +/// calling the Java stream's `pollNext` method. /// -/// For a [`Send`] version of this, use [`JSendStream`]. -pub struct JStream<'a: 'b, 'b> { +/// For an async [`Stream`] implementation, convert to [`JSendStream`] via +/// [`JSendStream::new`]. +pub struct JStream<'a> { internal: JObject<'a>, - poll_next: JMethodID, - env: &'b JNIEnv<'a>, + poll_next_id: JMethodID, } -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(), - ), +impl<'a> JStream<'a> { + pub fn from_env(env: &mut JNIEnv<'a>, obj: JObject<'a>) -> Result { + let class = + super::classcache::get_class("io/github/gedgygedgy/rust/stream/Stream").unwrap(); + let poll_next_id = env.get_method_id( + <&JClass>::from(class.as_obj()), "pollNext", "(Lio/github/gedgygedgy/rust/task/Waker;)Lio/github/gedgygedgy/rust/task/PollResult;", )?; Ok(Self { internal: obj, - poll_next, - env, + poll_next_id, }) } - fn j_poll_next(&self, waker: JObject<'a>) -> Result>>> { - let result = self - .env - .call_method_unchecked( - self.internal, - self.poll_next, + pub fn poll_next_with_env( + &self, + env: &mut JNIEnv<'a>, + waker: &JObject<'_>, + ) -> Result> { + let result = unsafe { + env.call_method_unchecked( + &self.internal, + self.poll_next_id, ReturnType::Object, - &[JValue::from(waker).to_jni()], - )? - .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())?) + &[jvalue { + l: waker.as_raw(), + }], + ) + }? + .l()?; + Ok(result) } } -impl<'a: 'b, 'b> ::std::ops::Deref for JStream<'a, 'b> { +impl<'a> ::std::ops::Deref for JStream<'a> { type Target = JObject<'a>; fn deref(&self) -> &Self::Target { @@ -82,39 +67,77 @@ impl<'a: 'b, 'b> ::std::ops::Deref for JStream<'a, 'b> { } } -impl<'a: 'b, 'b> From> for JObject<'a> { - fn from(other: JStream<'a, 'b>) -> JObject<'a> { +impl<'a> From> for JObject<'a> { + fn from(other: JStream<'a>) -> JObject<'a> { other.internal } } -impl<'a: 'b, 'b> Stream for JStream<'a, 'b> { - type Item = Result>; - - 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))), - } - } -} - -/// [`Send`] version of [`JStream`]. +/// [`Send`] version of [`JStream`]. Implements [`Stream`] by obtaining a +/// [`JNIEnv`] from the stored [`JavaVM`] on each poll. pub struct JSendStream { internal: GlobalRef, + poll_next_id: JMethodID, vm: JavaVM, } -impl<'a: 'b, 'b> TryFrom> for JSendStream { - type Error = Error; +impl JSendStream { + pub fn new(env: &mut JNIEnv, stream: &JStream) -> Result { + Ok(Self { + internal: env.new_global_ref(&stream.internal)?, + poll_next_id: stream.poll_next_id, + vm: env.get_java_vm()?, + }) + } - fn try_from(stream: JStream<'a, 'b>) -> Result { + pub fn from_env(env: &mut JNIEnv, obj: &JObject) -> Result { + let class = + super::classcache::get_class("io/github/gedgygedgy/rust/stream/Stream").unwrap(); + let poll_next_id = env.get_method_id( + <&JClass>::from(class.as_obj()), + "pollNext", + "(Lio/github/gedgygedgy/rust/task/Waker;)Lio/github/gedgygedgy/rust/task/PollResult;", + )?; Ok(Self { - internal: stream.env.new_global_ref(stream.internal)?, - vm: stream.env.get_java_vm()?, + internal: env.new_global_ref(obj)?, + poll_next_id, + vm: env.get_java_vm()?, }) } + + fn poll_next_internal( + &self, + context: &mut Context<'_>, + ) -> Result>>> { + let mut env = self.vm.get_env()?; + let jwaker = super::task::waker(&mut env, context.waker().clone())?; + let result = unsafe { + env.call_method_unchecked( + self.internal.as_obj(), + self.poll_next_id, + ReturnType::Object, + &[jvalue { + l: jwaker.as_raw(), + }], + ) + }? + .l()?; + + if env.is_same_object(&result, JObject::null())? { + return Ok(Poll::Pending); + } + + let poll_result = JPollResult::from_env(&mut env, result)?; + let stream_poll_obj = poll_result.get(&mut env)?; + + if env.is_same_object(&stream_poll_obj, JObject::null())? { + return Ok(Poll::Ready(None)); + } + + let stream_poll = JStreamPoll::from_env(&mut env, stream_poll_obj)?; + let obj = stream_poll.get(&mut env)?; + Ok(Poll::Ready(Some(Ok(env.new_global_ref(obj)?)))) + } } impl ::std::ops::Deref for JSendStream { @@ -125,19 +148,6 @@ impl ::std::ops::Deref for JSendStream { } } -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; @@ -151,33 +161,25 @@ impl Stream for JSendStream { assert_impl_all!(JSendStream: Send); -struct JStreamPoll<'a: 'b, 'b> { +struct JStreamPoll<'a> { internal: JObject<'a>, get: JMethodID, - env: &'b JNIEnv<'a>, } -impl<'a: 'b, 'b> JStreamPoll<'a, 'b> { - pub fn from_env(env: &'b JNIEnv<'a>, obj: JObject<'a>) -> Result { +impl<'a> JStreamPoll<'a> { + pub fn from_env(env: &mut JNIEnv<'a>, obj: JObject<'a>) -> Result { + let class = + super::classcache::get_class("io/github/gedgygedgy/rust/stream/StreamPoll").unwrap(); let get = env.get_method_id( - JClass::from( - super::classcache::get_class("io/github/gedgygedgy/rust/stream/StreamPoll") - .unwrap() - .as_obj(), - ), + <&JClass>::from(class.as_obj()), "get", "()Ljava/lang/Object;", )?; - Ok(Self { - internal: obj, - get, - env, - }) + Ok(Self { internal: obj, get }) } - pub fn get(&self) -> Result> { - self.env - .call_method_unchecked(self.internal, self.get, ReturnType::Object, &[])? + pub fn get(&self, env: &mut JNIEnv<'a>) -> Result> { + unsafe { env.call_method_unchecked(&self.internal, self.get, ReturnType::Object, &[]) }? .l() } } @@ -185,7 +187,7 @@ impl<'a: 'b, 'b> JStreamPoll<'a, 'b> { #[cfg(test)] mod test { use super::super::test_utils; - use super::JStream; + use super::{JSendStream, JStream}; use futures::stream::Stream; use std::{ pin::Pin, @@ -196,7 +198,9 @@ mod test { fn test_jstream() { use std::sync::Arc; - test_utils::JVM_ENV.with(|env| { + test_utils::JVM_ENV.with(|cell| { + let env = &mut *cell.borrow_mut(); + let data = Arc::new(test_utils::TestWakerData::new()); assert_eq!(Arc::strong_count(&data), 1); assert_eq!(data.value(), false); @@ -208,7 +212,9 @@ mod test { let stream_obj = env .new_object("io/github/gedgygedgy/rust/stream/QueueStream", "()V", &[]) .unwrap(); - let mut stream = JStream::from_env(env, stream_obj).unwrap(); + let stream_local = env.new_local_ref(&stream_obj).unwrap(); + let jstream = JStream::from_env(env, stream_local).unwrap(); + let mut stream = JSendStream::new(env, &jstream).unwrap(); assert!( Pin::new(&mut stream) @@ -219,22 +225,31 @@ mod test { 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(); + env.call_method( + &stream_obj, + "add", + "(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(); + env.call_method( + &stream_obj, + "add", + "(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"); } @@ -243,7 +258,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"); } @@ -258,7 +273,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, "finish", "()V", &[]).unwrap(); assert_eq!(Arc::strong_count(&data), 2); assert_eq!(data.value(), true); data.set_value(false); @@ -277,33 +292,64 @@ mod test { fn test_jstream_await() { use futures::{executor::block_on, join}; - test_utils::JVM_ENV.with(|env| { - let stream_obj = env - .new_object("io/github/gedgygedgy/rust/stream/QueueStream", "()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(); + test_utils::JVM_ENV.with(|cell| { + let (mut stream, stream_obj_global, obj1_global, obj2_global) = { + let env = &mut *cell.borrow_mut(); + let stream_obj = env + .new_object("io/github/gedgygedgy/rust/stream/QueueStream", "()V", &[]) + .unwrap(); + let stream_obj_global = env.new_global_ref(&stream_obj).unwrap(); + let stream_local = env.new_local_ref(&stream_obj).unwrap(); + let jstream = JStream::from_env(env, stream_local).unwrap(); + let stream = JSendStream::new(env, &jstream).unwrap(); + let obj1 = env.new_object("java/lang/Object", "()V", &[]).unwrap(); + let obj1_global = env.new_global_ref(&obj1).unwrap(); + let obj2 = env.new_object("java/lang/Object", "()V", &[]).unwrap(); + let obj2_global = env.new_global_ref(&obj2).unwrap(); + (stream, stream_obj_global, obj1_global, obj2_global) + }; 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(); + let env = &mut *cell.borrow_mut(); + 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, + "add", + "(Ljava/lang/Object;)V", + &[(&o1).into()], + ) + .unwrap(); + env.call_method( + &s, + "add", + "(Ljava/lang/Object;)V", + &[(&o2).into()], + ) + .unwrap(); + env.call_method(&s, "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() - ); + let g1 = stream.next().await.unwrap().unwrap(); + { + let mut guard = cell.borrow_mut(); + let env = &mut *guard; + let o1 = env.new_local_ref(obj1_global.as_obj()).unwrap(); + assert!(env.is_same_object(g1.as_obj(), &o1).unwrap()); + } + + let g2 = stream.next().await.unwrap().unwrap(); + { + let mut guard = cell.borrow_mut(); + let env = &mut *guard; + let o2 = env.new_local_ref(obj2_global.as_obj()).unwrap(); + assert!(env.is_same_object(g2.as_obj(), &o2).unwrap()); + } + assert!(stream.next().await.is_none()); } ); @@ -313,44 +359,64 @@ mod test { #[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 stream_obj = env - .new_object("io/github/gedgygedgy/rust/stream/QueueStream", "()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(); + test_utils::JVM_ENV.with(|cell| { + let (mut stream, stream_obj_global, obj1_global, obj2_global) = { + let env = &mut *cell.borrow_mut(); + let stream_obj = env + .new_object("io/github/gedgygedgy/rust/stream/QueueStream", "()V", &[]) + .unwrap(); + 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("java/lang/Object", "()V", &[]).unwrap(); + let obj1_global = env.new_global_ref(&obj1).unwrap(); + let obj2 = env.new_object("java/lang/Object", "()V", &[]).unwrap(); + let obj2_global = env.new_global_ref(&obj2).unwrap(); + (stream, stream_obj_global, obj1_global, obj2_global) + }; 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(); + let env = &mut *cell.borrow_mut(); + 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, + "add", + "(Ljava/lang/Object;)V", + &[(&o1).into()], + ) + .unwrap(); + env.call_method( + &s, + "add", + "(Ljava/lang/Object;)V", + &[(&o2).into()], + ) + .unwrap(); + env.call_method(&s, "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() - ); + let g1 = stream.next().await.unwrap().unwrap(); + { + let mut guard = cell.borrow_mut(); + let env = &mut *guard; + let o1 = env.new_local_ref(obj1_global.as_obj()).unwrap(); + assert!(env.is_same_object(g1.as_obj(), &o1).unwrap()); + } + + let g2 = stream.next().await.unwrap().unwrap(); + { + let mut guard = cell.borrow_mut(); + let env = &mut *guard; + let o2 = env.new_local_ref(obj2_global.as_obj()).unwrap(); + assert!(env.is_same_object(g2.as_obj(), &o2).unwrap()); + } + assert!(stream.next().await.is_none()); } ); diff --git a/src/droidplug/jni_utils/task.rs b/src/droidplug/jni_utils/task.rs index 7993282a..95096110 100644 --- a/src/droidplug/jni_utils/task.rs +++ b/src/droidplug/jni_utils/task.rs @@ -7,55 +7,40 @@ use ::jni::{ 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> { +pub fn waker<'a>(env: &mut JNIEnv<'a>, waker: Waker) -> Result> { let runnable = super::ops::fn_once_runnable(env, |_e, _o| waker.wake())?; + let class = super::classcache::get_class("io/github/gedgygedgy/rust/task/Waker").unwrap(); let obj = env.new_object( - JClass::from( - super::classcache::get_class("io/github/gedgygedgy/rust/task/Waker") - .unwrap() - .as_obj(), - ), + <&JClass>::from(class.as_obj()), "(Lio/github/gedgygedgy/rust/ops/FnRunnable;)V", - &[runnable.into()], + &[(&runnable).into()], )?; Ok(obj) } /// Wrapper for [`JObject`]s that implement /// `io.github.gedgygedgy.rust.task.PollResult`. -pub struct JPollResult<'a: 'b, 'b> { +pub struct JPollResult<'a> { internal: JObject<'a>, get: JMethodID, - 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, - }) +impl<'a> JPollResult<'a> { + pub fn from_env(env: &mut JNIEnv<'a>, obj: JObject<'a>) -> Result { + let class = + super::classcache::get_class("io/github/gedgygedgy/rust/task/PollResult").unwrap(); + let get = env.get_method_id(<&JClass>::from(class.as_obj()), "get", "()Ljava/lang/Object;")?; + Ok(Self { internal: obj, get }) } - pub fn get(&self) -> Result> { - self.env - .call_method_unchecked(self.internal, self.get, ReturnType::Object, &[])? + pub fn get(&self, env: &mut JNIEnv<'a>) -> Result> { + unsafe { env.call_method_unchecked(&self.internal, self.get, ReturnType::Object, &[]) }? .l() } } -impl<'a: 'b, 'b> ::std::ops::Deref for JPollResult<'a, 'b> { +impl<'a> ::std::ops::Deref for JPollResult<'a> { type Target = JObject<'a>; fn deref(&self) -> &Self::Target { @@ -63,8 +48,8 @@ impl<'a: 'b, 'b> ::std::ops::Deref for JPollResult<'a, 'b> { } } -impl<'a: 'b, 'b> From> for JObject<'a> { - fn from(other: JPollResult<'a, 'b>) -> JObject<'a> { +impl<'a> From> for JObject<'a> { + fn from(other: JPollResult<'a>) -> JObject<'a> { other.internal } } @@ -76,7 +61,9 @@ mod test { #[test] fn test_waker_wake() { - test_utils::JVM_ENV.with(|env| { + test_utils::JVM_ENV.with(|cell| { + let env = &mut *cell.borrow_mut(); + let data = Arc::new(test_utils::TestWakerData::new()); assert_eq!(Arc::strong_count(&data), 1); assert_eq!(data.value(), false); @@ -89,12 +76,12 @@ 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, "wake", "()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, "wake", "()V", &[]).unwrap(); assert_eq!(Arc::strong_count(&data), 1); assert_eq!(data.value(), false); }); @@ -102,7 +89,9 @@ mod test { #[test] fn test_waker_close_wake() { - test_utils::JVM_ENV.with(|env| { + test_utils::JVM_ENV.with(|cell| { + let env = &mut *cell.borrow_mut(); + let data = Arc::new(test_utils::TestWakerData::new()); assert_eq!(Arc::strong_count(&data), 1); assert_eq!(data.value(), false); @@ -115,11 +104,11 @@ 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, "close", "()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, "wake", "()V", &[]).unwrap(); assert_eq!(Arc::strong_count(&data), 1); assert_eq!(data.value(), false); }); diff --git a/src/droidplug/jni_utils/uuid.rs b/src/droidplug/jni_utils/uuid.rs index 2c7b7faf..c330ce7b 100644 --- a/src/droidplug/jni_utils/uuid.rs +++ b/src/droidplug/jni_utils/uuid.rs @@ -1,7 +1,7 @@ use jni::{ JNIEnv, errors::Result, - objects::{AutoLocal, JMethodID, JObject}, + objects::{JMethodID, JObject}, signature::{Primitive, ReturnType}, sys::jlong, }; @@ -9,71 +9,69 @@ 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> { +pub struct JUuid<'a> { internal: JObject<'a>, get_least_significant_bits: JMethodID, get_most_significant_bits: JMethodID, - env: &'b JNIEnv<'a>, } -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) +impl<'a> JUuid<'a> { + pub fn from_env(env: &mut JNIEnv<'a>, obj: JObject<'a>) -> Result { + let class = env.find_class("java/util/UUID")?; + 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, + }) } - pub fn new(env: &'b JNIEnv<'a>, uuid: Uuid) -> Result { + pub fn new(env: &mut JNIEnv<'a>, 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 class = 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) + 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, + }) } - pub fn as_uuid(&self) -> Result { - let least = self - .env - .call_method_unchecked( - self.internal, + pub fn as_uuid(&self, env: &mut JNIEnv<'a>) -> Result { + let least = unsafe { + env.call_method_unchecked( + &self.internal, self.get_least_significant_bits, ReturnType::Primitive(Primitive::Long), &[], - )? - .j()? as u64; - let most = self - .env - .call_method_unchecked( - self.internal, + ) + }? + .j()? as u64; + let most = unsafe { + env.call_method_unchecked( + &self.internal, self.get_most_significant_bits, ReturnType::Primitive(Primitive::Long), &[], - )? - .j()? as u64; + ) + }? + .j()? 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> { +impl<'a> ::std::ops::Deref for JUuid<'a> { type Target = JObject<'a>; fn deref(&self) -> &Self::Target { @@ -81,8 +79,8 @@ impl<'a: 'b, 'b> ::std::ops::Deref for JUuid<'a, 'b> { } } -impl<'a: 'b, 'b> From> for JObject<'a> { - fn from(other: JUuid<'a, 'b>) -> JObject<'a> { +impl<'a> From> for JObject<'a> { + fn from(other: JUuid<'a>) -> JObject<'a> { other.internal } } @@ -115,7 +113,8 @@ mod test { #[test] fn test_uuid_new() { - test_utils::JVM_ENV.with(|env| { + test_utils::JVM_ENV.with(|cell| { + let env = &mut *cell.borrow_mut(); for test in TESTS { let most = test.most as jlong; let least = test.least as jlong; @@ -124,12 +123,12 @@ mod test { let obj: JObject = uuid_obj.into(); let actual_most = env - .call_method(obj, "getMostSignificantBits", "()J", &[]) + .call_method(&obj, "getMostSignificantBits", "()J", &[]) .unwrap() .j() .unwrap(); let actual_least = env - .call_method(obj, "getLeastSignificantBits", "()J", &[]) + .call_method(&obj, "getLeastSignificantBits", "()J", &[]) .unwrap() .j() .unwrap(); @@ -141,7 +140,8 @@ mod test { #[test] fn test_uuid_as_uuid() { - test_utils::JVM_ENV.with(|env| { + test_utils::JVM_ENV.with(|cell| { + let env = &mut *cell.borrow_mut(); for test in TESTS { let most = test.most as jlong; let least = test.least as jlong; @@ -151,7 +151,7 @@ mod test { .unwrap(); let uuid_obj = JUuid::from_env(env, 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)); } }); } diff --git a/src/droidplug/mod.rs b/src/droidplug/mod.rs index eed9db6a..d759835f 100644 --- a/src/droidplug/mod.rs +++ b/src/droidplug/mod.rs @@ -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 JNIEnv) -> 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 40576985..797414a3 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, @@ -17,7 +16,7 @@ use async_trait::async_trait; use futures::stream::Stream; use jni::{ JNIEnv, - objects::{GlobalRef, JList, JObject}, + objects::{GlobalRef, JClass, JObject, JString, JValue}, }; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; @@ -25,7 +24,6 @@ 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}, @@ -37,7 +35,7 @@ use super::jni::{ global_jvm, objects::{JBluetoothGattCharacteristic, JBluetoothGattService, JPeripheral}, }; -use jni::objects::JClass; + #[cfg_attr( feature = "serde", derive(Serialize, Deserialize), @@ -51,107 +49,74 @@ 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 JNIEnv<'a>, + result_ref: &GlobalRef, ) -> 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 = JPollResult::from_env(env, result_obj)?; + + match poll_result.get(env) { + Ok(obj) => Ok(obj), + Err(jni::errors::Error::JavaException) => { + let ex = env.exception_occurred()?; + env.exception_clear()?; + + let future_exception_class = super::jni_utils::classcache::get_class( + "io/github/gedgygedgy/rust/future/FutureException", + ) + .unwrap(); + + if env.is_instance_of(&ex, <&JClass>::from(future_exception_class.as_obj()))? { let cause = env - .call_method(ex, "getCause", "()Ljava/lang/Throwable;", &[])? + .call_method(&ex, "getCause", "()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(), - ), + + let check = |name: &str| -> jni::errors::Result { + let cls = super::jni_utils::classcache::get_class(name).unwrap(); + env.is_instance_of(&cause, <&JClass>::from(cls.as_obj())) + }; + + if check("com/nonpolynomial/btleplug/android/impl/NotConnectedException")? { + Err(Error::NotConnected) + } else if check( + "com/nonpolynomial/btleplug/android/impl/PermissionDeniedException", )? { - 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(), - ), + Err(Error::PermissionDenied) + } else if check( + "com/nonpolynomial/btleplug/android/impl/UnexpectedCallbackException", )? { - 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(), - ), + Err(Error::UnexpectedCallback) + } else if check( + "com/nonpolynomial/btleplug/android/impl/UnexpectedCharacteristicException", )? { - 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(), - ), + Err(Error::UnexpectedCharacteristic) + } else if check( + "com/nonpolynomial/btleplug/android/impl/NoSuchCharacteristicException", )? { - Ok(Err(Error::NoAdapterAvailable)) - } else if env.is_instance_of( - cause, - "java/lang/RuntimeException", + Err(Error::NoSuchCharacteristic) + } else if check( + "com/nonpolynomial/btleplug/android/impl/NoBluetoothAdapterException", )? { + Err(Error::NoAdapterAvailable) + } else if env.is_instance_of(&cause, "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, "getMessage", "()Ljava/lang/String;", &[])? + .l()?; + let jstr: JString = msg.into(); + let msgstr: String = env.get_string(&jstr)?.into(); + Err(Error::RuntimeError(msgstr)) } else { - env.throw(ex)?; - Err(jni::errors::Error::JavaException) + env.throw(&ex)?; + Err(jni::errors::Error::JavaException.into()) } - }, - ) - .result()? + } else { + env.throw(&ex)?; + Err(jni::errors::Error::JavaException.into()) + } + } + Err(e) => Err(e.into()), + } } #[derive(Debug)] @@ -171,11 +136,12 @@ pub struct Peripheral { } impl Peripheral { - pub(crate) fn new(env: &JNIEnv, adapter: JObject, addr: BDAddr) -> Result { + pub(crate) fn new(env: &mut JNIEnv, adapter: JObject, addr: BDAddr) -> Result { let obj = JPeripheral::new(env, adapter, addr)?; + let internal = 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 +154,20 @@ impl Peripheral { pub(crate) fn report_properties(&self, properties: PeripheralProperties) { let mut guard = self.shared.lock().unwrap(); - guard.properties = Some(properties); } fn with_obj( &self, - f: impl FnOnce(&JNIEnv, JPeripheral) -> std::result::Result, + f: impl FnOnce(&mut 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) + let mut env = global_jvm().get_env()?; + let local_obj = env.new_local_ref(&self.internal)?; + let obj = JPeripheral::from_env(&mut env, local_obj)?; + f(&mut env, &obj) } async fn set_characteristic_notification( @@ -211,13 +177,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)?; + 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 +193,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 +216,19 @@ 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)?; + 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); @@ -275,13 +238,14 @@ impl api::Peripheral for Peripheral { })?; // 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_obj = obj.request_mtu(env, 517)?; + let mtu_future = JFuture::from_env(env, mtu_obj)?; + JSendFuture::new(env, &mtu_future) })?; 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()?; + let mtu_obj = get_poll_result(env, &mtu_result_ref)?; + let mtu_val = env.call_method(&mtu_obj, "intValue", "()I", &[])?.i()?; self.mtu.store(mtu_val as u16, Ordering::Relaxed); Ok(()) })?; @@ -289,53 +253,54 @@ impl api::Peripheral for Peripheral { } 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)?; + 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)?; + 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, "size", "()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, "get", "(I)Ljava/lang/Object;", &[JValue::from(i)])? + .l()?; + let service = JBluetoothGattService::from_env(env, 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 +312,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 +337,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)?; + 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)?; + 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_raw())?) + let bytes_obj = get_poll_result(env, &result_ref)?; + let bytes_arr = unsafe { jni::objects::JByteArray::from_raw(bytes_obj.into_raw()) }; + Ok(byte_array_to_vec(env, &bytes_arr)?) }) } @@ -407,15 +371,19 @@ 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)?; + 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 mut env = global_jvm().get_env()?; + let local_obj = env.new_local_ref(item.as_obj())?; + let characteristic = + JBluetoothGattCharacteristic::from_env(&mut env, local_obj)?; + let uuid = characteristic.get_uuid(&mut env)?; + let value = characteristic.get_value(&mut env)?; let service_uuid = shared .lock() .ok() @@ -441,13 +409,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 = JFuture::from_env(env, rssi_obj)?; + 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, "intValue", "()I", &[])?.i()?; Ok(rssi_val as i16) }) } @@ -457,46 +426,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())?; + 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)?; + 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_raw())?) + let bytes_obj = get_poll_result(env, &result_ref)?; + let bytes_arr = unsafe { jni::objects::JByteArray::from_raw(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.toml b/tests/android/rust/Cargo.toml index 5ec67c7a..1aabb571 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.20" +jni = "0.21" once_cell = "1" tokio = { version = "1", features = ["full"] } uuid = "1" From 511b414588095ab7dbc0575e72cdde9ed0140019 Mon Sep 17 00:00:00 2001 From: Kyle Machulis Date: Sun, 26 Apr 2026 18:50:08 -0700 Subject: [PATCH 03/16] fix: Update droidplug and test crate for jni 0.21 lifetime requirements MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit jni 0.21 tightened lifetime variance on JNIEnv, requiring explicit shared lifetimes wherever JObject and JNIEnv are used together. Changes: - `with_obj` closure: use HRTB `for<'env>` so JNIEnv and JPeripheral share the same lifetime, fixing the invariant mutable reference errors - `Peripheral::new`, `report_scan_result`, `adapter_report_scan_result_internal`, `adapter_report_scan_result` JNI callback: add explicit `'a` lifetime annotations tying env and JObject parameters together - `adapter_on_connection_state_changed_internal`: get address string before acquiring the MutexGuard from `get_rust_field` to avoid double-mutable borrow of env - `JUuid::as_obj().as_raw()` → `uuid.as_raw()` via Deref: `JObject::as_obj()` was removed in jni 0.21; JUuid already Derefs to JObject - Test crate `lib.rs`: update `run_test`, `initBtleplug`, and `jni_test!` macro to use `&mut JNIEnv` as required by the updated API Co-Authored-By: Claude Sonnet 4.6 --- src/droidplug/adapter.rs | 17 ++++----- src/droidplug/jni/mod.rs | 2 +- src/droidplug/jni/objects.rs | 14 +++---- src/droidplug/peripheral.rs | 8 ++-- tests/android/rust/Cargo.lock | 72 ++++++++++++++++++++++++++++++++++- tests/android/rust/src/lib.rs | 10 ++--- 6 files changed, 94 insertions(+), 29 deletions(-) diff --git a/src/droidplug/adapter.rs b/src/droidplug/adapter.rs index ed902ffd..70ec8088 100644 --- a/src/droidplug/adapter.rs +++ b/src/droidplug/adapter.rs @@ -57,10 +57,10 @@ impl Adapter { Ok(adapter) } - pub fn report_scan_result( + pub fn report_scan_result<'a>( &self, - env: &mut JNIEnv, - scan_result: JObject, + env: &mut JNIEnv<'a>, + scan_result: JObject<'a>, ) -> Result { let scan_result = JScanResult::from_env(env, scan_result)?; let (addr, properties): (BDAddr, Option) = @@ -201,10 +201,10 @@ impl Central for Adapter { } } -pub(crate) fn adapter_report_scan_result_internal( - env: &mut JNIEnv, +pub(crate) fn adapter_report_scan_result_internal<'a>( + env: &mut JNIEnv<'a>, obj: &JObject, - scan_result: JObject, + scan_result: JObject<'a>, ) -> crate::Result<()> { let adapter = unsafe { env.get_rust_field::<_, _, Adapter>(obj, "handle") }?; let adapter_clone = adapter.clone(); @@ -219,10 +219,9 @@ pub(crate) fn adapter_on_connection_state_changed_internal( addr: JString, connected: jboolean, ) -> crate::Result<()> { + let addr_str: String = env.get_string(&addr)?.into(); + let addr = BDAddr::from_str(&addr_str)?; let adapter = unsafe { env.get_rust_field::<_, _, Adapter>(obj, "handle") }?; - let addr_str = env.get_string(&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 { CentralEvent::DeviceConnected(PeripheralId(addr)) } else { diff --git a/src/droidplug/jni/mod.rs b/src/droidplug/jni/mod.rs index a5368b73..6c94668d 100644 --- a/src/droidplug/jni/mod.rs +++ b/src/droidplug/jni/mod.rs @@ -133,7 +133,7 @@ impl From<::jni::errors::Error> for crate::Error { } } -extern "C" fn adapter_report_scan_result(mut env: JNIEnv, obj: JObject, scan_result: JObject) { +extern "C" fn adapter_report_scan_result<'a>(mut env: JNIEnv<'a>, obj: JObject, scan_result: JObject<'a>) { let _ = super::adapter::adapter_report_scan_result_internal(&mut env, &obj, scan_result); } diff --git a/src/droidplug/jni/objects.rs b/src/droidplug/jni/objects.rs index f1b264a6..62dab6f8 100644 --- a/src/droidplug/jni/objects.rs +++ b/src/droidplug/jni/objects.rs @@ -195,7 +195,7 @@ impl<'a> JPeripheral<'a> { self.read, ReturnType::Object, &[jvalue { - l: uuid.as_obj().as_raw(), + l: uuid.as_raw(), }], ) }? @@ -217,7 +217,7 @@ impl<'a> JPeripheral<'a> { ReturnType::Object, &[ jvalue { - l: uuid.as_obj().as_raw(), + l: uuid.as_raw(), }, jvalue { l: data.as_raw(), @@ -243,7 +243,7 @@ impl<'a> JPeripheral<'a> { ReturnType::Object, &[ jvalue { - l: uuid.as_obj().as_raw(), + l: uuid.as_raw(), }, jvalue { z: enable as u8, @@ -281,10 +281,10 @@ impl<'a> JPeripheral<'a> { ReturnType::Object, &[ jvalue { - l: characteristic.as_obj().as_raw(), + l: characteristic.as_raw(), }, jvalue { - l: uuid.as_obj().as_raw(), + l: uuid.as_raw(), }, ], ) @@ -396,10 +396,10 @@ impl<'a> JPeripheral<'a> { ReturnType::Object, &[ jvalue { - l: characteristic.as_obj().as_raw(), + l: characteristic.as_raw(), }, jvalue { - l: uuid.as_obj().as_raw(), + l: uuid.as_raw(), }, jvalue { l: data.as_raw(), diff --git a/src/droidplug/peripheral.rs b/src/droidplug/peripheral.rs index 797414a3..b7e25052 100644 --- a/src/droidplug/peripheral.rs +++ b/src/droidplug/peripheral.rs @@ -29,8 +29,6 @@ use std::{ sync::atomic::{AtomicU16, Ordering}, sync::{Arc, Mutex}, }; -use uuid::Uuid; - use super::jni::{ global_jvm, objects::{JBluetoothGattCharacteristic, JBluetoothGattService, JPeripheral}, @@ -72,7 +70,7 @@ fn get_poll_result<'a>( .call_method(&ex, "getCause", "()Ljava/lang/Throwable;", &[])? .l()?; - let check = |name: &str| -> jni::errors::Result { + let mut check = |name: &str| -> jni::errors::Result { let cls = super::jni_utils::classcache::get_class(name).unwrap(); env.is_instance_of(&cause, <&JClass>::from(cls.as_obj())) }; @@ -136,7 +134,7 @@ pub struct Peripheral { } impl Peripheral { - pub(crate) fn new(env: &mut JNIEnv, adapter: JObject, addr: BDAddr) -> Result { + pub(crate) fn new<'a>(env: &mut JNIEnv<'a>, adapter: JObject<'a>, addr: BDAddr) -> Result { let obj = JPeripheral::new(env, adapter, addr)?; let internal = env.new_global_ref(&*obj)?; Ok(Self { @@ -159,7 +157,7 @@ impl Peripheral { fn with_obj( &self, - f: impl FnOnce(&mut JNIEnv, &JPeripheral) -> std::result::Result, + f: impl for<'env> FnOnce(&mut JNIEnv<'env>, &JPeripheral<'env>) -> std::result::Result, ) -> std::result::Result where E: From<::jni::errors::Error>, diff --git a/tests/android/rust/Cargo.lock b/tests/android/rust/Cargo.lock index 4d1091fc..ab29b097 100644 --- a/tests/android/rust/Cargo.lock +++ b/tests/android/rust/Cargo.lock @@ -331,16 +331,18 @@ dependencies = [ [[package]] name = "jni" -version = "0.19.0" +version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6df18c2e3db7e453d3c6ac5b3e9d5182664d28788126d39b91f2d1e22b017ec" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" dependencies = [ "cesu8", + "cfg-if", "combine", "jni-sys", "log", "thiserror 1.0.69", "walkdir", + "windows-sys 0.45.0", ] [[package]] @@ -940,6 +942,15 @@ dependencies = [ "windows-link", ] +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + [[package]] name = "windows-sys" version = "0.59.0" @@ -967,6 +978,21 @@ dependencies = [ "windows-link", ] +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + [[package]] name = "windows-targets" version = "0.52.6" @@ -1009,6 +1035,12 @@ dependencies = [ "windows-link", ] +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" @@ -1021,6 +1053,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + [[package]] name = "windows_aarch64_msvc" version = "0.52.6" @@ -1033,6 +1071,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -1057,6 +1101,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + [[package]] name = "windows_i686_msvc" version = "0.52.6" @@ -1069,6 +1119,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + [[package]] name = "windows_x86_64_gnu" version = "0.52.6" @@ -1081,6 +1137,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" @@ -1093,6 +1155,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + [[package]] name = "windows_x86_64_msvc" version = "0.52.6" diff --git a/tests/android/rust/src/lib.rs b/tests/android/rust/src/lib.rs index f56d58aa..b234357f 100644 --- a/tests/android/rust/src/lib.rs +++ b/tests/android/rust/src/lib.rs @@ -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 JNIEnv, 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 { @@ -86,7 +86,7 @@ fn run_test(env: &JNIEnv, test_name: &str, f: impl std::future::Future { #[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: JNIEnv, _class: JClass) { + run_test(&mut env, stringify!($test_fn), $test_fn()); } }; } From a3e3ac34fae4447d689a8226371800de0b56e4c3 Mon Sep 17 00:00:00 2001 From: Kyle Machulis Date: Sun, 26 Apr 2026 19:29:44 -0700 Subject: [PATCH 04/16] build: Bump jni from 0.21 to 0.22 Co-Authored-By: Claude Opus 4.6 --- Cargo.lock | 194 ++++++++++++---------------------- Cargo.toml | 4 +- tests/android/rust/Cargo.toml | 2 +- 3 files changed, 69 insertions(+), 131 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 986a5db5..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" @@ -436,27 +430,54 @@ dependencies = [ [[package]] name = "jni" -version = "0.21.1" +version = "0.22.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" dependencies = [ - "cesu8", "cfg-if", "combine", "java-locator", + "jni-macros", "jni-sys", "libloading", "log", - "thiserror 1.0.69", + "simd_cesu8", + "thiserror", "walkdir", - "windows-sys 0.45.0", + "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 = "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 = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" +checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" +dependencies = [ + "quote", + "syn", +] [[package]] name = "js-sys" @@ -497,12 +518,12 @@ dependencies = [ [[package]] name = "libloading" -version = "0.7.4" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" dependencies = [ "cfg-if", - "winapi", + "windows-link", ] [[package]] @@ -712,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" @@ -757,7 +787,7 @@ checksum = "cc2215ce3e6a77550b80a1c37251b7d294febaf42e36e21b7b411e0bf54d540d" dependencies = [ "log", "serde", - "thiserror 2.0.18", + "thiserror", "xml", ] @@ -813,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" @@ -861,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]] @@ -1126,22 +1152,6 @@ dependencies = [ "semver", ] -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - [[package]] name = "winapi-util" version = "0.1.11" @@ -1151,12 +1161,6 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - [[package]] name = "windows" version = "0.62.2" @@ -1258,15 +1262,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "windows-sys" -version = "0.45.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" -dependencies = [ - "windows-targets 0.42.2", -] - [[package]] name = "windows-sys" version = "0.59.0" @@ -1294,21 +1289,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "windows-targets" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" -dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", -] - [[package]] name = "windows-targets" version = "0.52.6" @@ -1351,12 +1331,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" - [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" @@ -1369,12 +1343,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" -[[package]] -name = "windows_aarch64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" - [[package]] name = "windows_aarch64_msvc" version = "0.52.6" @@ -1387,12 +1355,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" -[[package]] -name = "windows_i686_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" - [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -1417,12 +1379,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" -[[package]] -name = "windows_i686_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" - [[package]] name = "windows_i686_msvc" version = "0.52.6" @@ -1435,12 +1391,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" -[[package]] -name = "windows_x86_64_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" - [[package]] name = "windows_x86_64_gnu" version = "0.52.6" @@ -1453,12 +1403,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" - [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" @@ -1471,12 +1415,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" -[[package]] -name = "windows_x86_64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" - [[package]] name = "windows_x86_64_msvc" version = "0.52.6" diff --git a/Cargo.toml b/Cargo.toml index 48e190ab..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.21.0" +jni = "0.22" once_cell = "1.21.3" [target.'cfg(not(target_os = "android"))'.dependencies] -jni = { version = "0.21.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/tests/android/rust/Cargo.toml b/tests/android/rust/Cargo.toml index 1aabb571..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.21" +jni = "0.22" once_cell = "1" tokio = { version = "1", features = ["full"] } uuid = "1" From 97630988940a43de6fe4ea411c609b6e842b1bdc Mon Sep 17 00:00:00 2001 From: Kyle Machulis Date: Sun, 26 Apr 2026 19:38:24 -0700 Subject: [PATCH 05/16] refactor: Rename JNIEnv to Env and GlobalRef to Global across droidplug Phase 2 of jni 0.22 migration. Non-FFI function signatures now use Env<'a> instead of JNIEnv<'a>. GlobalRef fields in Clone structs are wrapped in Arc>>. Classcache stores Arc>>. extern "C" FFI functions and test_utils are left as-is for later phases. Co-Authored-By: Claude Opus 4.6 --- src/droidplug/adapter.rs | 21 +++--- src/droidplug/jni/mod.rs | 4 +- src/droidplug/jni/objects.rs | 98 +++++++++++++-------------- src/droidplug/jni_utils/arrays.rs | 8 +-- src/droidplug/jni_utils/classcache.rs | 14 ++-- src/droidplug/jni_utils/exceptions.rs | 26 +++---- src/droidplug/jni_utils/future.rs | 28 +++----- src/droidplug/jni_utils/stream.rs | 32 ++++----- src/droidplug/jni_utils/task.rs | 11 ++- src/droidplug/jni_utils/uuid.rs | 10 ++- src/droidplug/mod.rs | 4 +- src/droidplug/peripheral.rs | 18 ++--- 12 files changed, 127 insertions(+), 147 deletions(-) diff --git a/src/droidplug/adapter.rs b/src/droidplug/adapter.rs index 70ec8088..5470c7bd 100644 --- a/src/droidplug/adapter.rs +++ b/src/droidplug/adapter.rs @@ -13,8 +13,8 @@ use crate::{ use async_trait::async_trait; use futures::stream::Stream; use jni::{ - JNIEnv, - objects::{GlobalRef, JClass, JObject, JString}, + Env, + objects::{Global, JClass, JObject, JString}, sys::jboolean, }; use std::{ @@ -27,7 +27,7 @@ use std::{ #[derive(Clone)] pub struct Adapter { manager: Arc>, - internal: GlobalRef, + internal: Arc>>, } impl Debug for Adapter { @@ -47,7 +47,7 @@ impl Adapter { "()V", &[], )?; - let internal = env.new_global_ref(&obj)?; + let internal = Arc::new(env.new_global_ref(&obj)?); let adapter = Self { manager: Arc::new(AdapterManager::default()), internal, @@ -59,8 +59,9 @@ impl Adapter { pub fn report_scan_result<'a>( &self, - env: &mut JNIEnv<'a>, + env: &mut Env<'a>, scan_result: JObject<'a>, + ) -> Result { let scan_result = JScanResult::from_env(env, scan_result)?; let (addr, properties): (BDAddr, Option) = @@ -87,7 +88,7 @@ impl Adapter { fn add(&self, address: BDAddr) -> Result { let mut env = global_jvm().get_env()?; - let local_adapter = env.new_local_ref(&self.internal)?; + let local_adapter = env.new_local_ref(self.internal.as_obj())?; let peripheral = Peripheral::new(&mut env, local_adapter, address)?; self.manager.add_peripheral(peripheral.clone()); Ok(peripheral) @@ -138,7 +139,7 @@ impl Central for Adapter { let filter = JScanFilter::new(&mut env, filter)?; let filter_obj: JObject = filter.into(); match env.call_method( - &self.internal, + self.internal.as_obj(), "startScan", "(Lcom/nonpolynomial/btleplug/android/impl/ScanFilter;)V", &[(&filter_obj).into()], @@ -173,7 +174,7 @@ impl Central for Adapter { async fn stop_scan(&self) -> Result<()> { let mut env = global_jvm().get_env()?; - env.call_method(&self.internal, "stopScan", "()V", &[])?; + env.call_method(self.internal.as_obj(), "stopScan", "()V", &[])?; Ok(()) } @@ -202,7 +203,7 @@ impl Central for Adapter { } pub(crate) fn adapter_report_scan_result_internal<'a>( - env: &mut JNIEnv<'a>, + env: &mut Env<'a>, obj: &JObject, scan_result: JObject<'a>, ) -> crate::Result<()> { @@ -214,7 +215,7 @@ pub(crate) fn adapter_report_scan_result_internal<'a>( } pub(crate) fn adapter_on_connection_state_changed_internal( - env: &mut JNIEnv, + env: &mut Env, obj: &JObject, addr: JString, connected: jboolean, diff --git a/src/droidplug/jni/mod.rs b/src/droidplug/jni/mod.rs index 6c94668d..9453eab7 100644 --- a/src/droidplug/jni/mod.rs +++ b/src/droidplug/jni/mod.rs @@ -1,13 +1,13 @@ pub mod objects; -use ::jni::{JNIEnv, JavaVM, NativeMethod, objects::JObject}; +use ::jni::{Env, JNIEnv, JavaVM, NativeMethod, objects::JObject}; use jni::{objects::JString, sys::jboolean}; use once_cell::sync::OnceCell; use std::ffi::c_void; static GLOBAL_JVM: OnceCell = OnceCell::new(); -pub fn init(env: &mut JNIEnv) -> crate::Result<()> { +pub fn init(env: &mut Env) -> crate::Result<()> { if let Ok(()) = GLOBAL_JVM.set(env.get_java_vm()?) { let adapter_class = env.find_class("com/nonpolynomial/btleplug/android/impl/Adapter")?; diff --git a/src/droidplug/jni/objects.rs b/src/droidplug/jni/objects.rs index 62dab6f8..7e43dbfb 100644 --- a/src/droidplug/jni/objects.rs +++ b/src/droidplug/jni/objects.rs @@ -1,6 +1,6 @@ use crate::droidplug::jni_utils::{future::JFuture, stream::JStream, uuid::JUuid}; use jni::{ - JNIEnv, + Env, errors::Result, objects::{JClass, JMethodID, JObject, JString}, signature::{Primitive, ReturnType}, @@ -45,7 +45,7 @@ impl<'a> From> for JObject<'a> { } impl<'a> JPeripheral<'a> { - pub fn from_env(env: &mut JNIEnv<'a>, obj: JObject<'a>) -> Result { + pub fn from_env(env: &mut Env<'a>, obj: JObject<'a>) -> Result { let class_static = crate::droidplug::jni_utils::classcache::get_class( "com/nonpolynomial/btleplug/android/impl/Peripheral", ) @@ -133,7 +133,7 @@ impl<'a> JPeripheral<'a> { }) } - pub fn new(env: &mut JNIEnv<'a>, adapter: JObject<'a>, addr: BDAddr) -> Result { + pub fn new(env: &mut Env<'a>, adapter: JObject<'a>, addr: BDAddr) -> Result { let addr_jstr = env.new_string(format!("{:X}", addr))?; let class_static = crate::droidplug::jni_utils::classcache::get_class( "com/nonpolynomial/btleplug/android/impl/Peripheral", @@ -147,7 +147,7 @@ impl<'a> JPeripheral<'a> { Self::from_env(env, obj) } - pub fn connect(&self, env: &mut JNIEnv<'a>) -> Result> { + pub fn connect(&self, env: &mut Env<'a>) -> Result> { let future_obj = unsafe { env.call_method_unchecked(&self.internal, self.connect, ReturnType::Object, &[]) }? @@ -155,7 +155,7 @@ impl<'a> JPeripheral<'a> { JFuture::from_env(env, future_obj) } - pub fn disconnect(&self, env: &mut JNIEnv<'a>) -> Result> { + pub fn disconnect(&self, env: &mut Env<'a>) -> Result> { let future_obj = unsafe { env.call_method_unchecked(&self.internal, self.disconnect, ReturnType::Object, &[]) }? @@ -163,7 +163,7 @@ impl<'a> JPeripheral<'a> { JFuture::from_env(env, future_obj) } - pub fn is_connected(&self, env: &mut JNIEnv<'a>) -> Result { + pub fn is_connected(&self, env: &mut Env<'a>) -> Result { unsafe { env.call_method_unchecked( &self.internal, @@ -175,7 +175,7 @@ impl<'a> JPeripheral<'a> { .z() } - pub fn discover_services(&self, env: &mut JNIEnv<'a>) -> Result> { + pub fn discover_services(&self, env: &mut Env<'a>) -> Result> { let future_obj = unsafe { env.call_method_unchecked( &self.internal, @@ -188,7 +188,7 @@ impl<'a> JPeripheral<'a> { JFuture::from_env(env, future_obj) } - pub fn read(&self, env: &mut JNIEnv<'a>, uuid: &JUuid<'a>) -> Result> { + pub fn read(&self, env: &mut Env<'a>, uuid: &JUuid<'a>) -> Result> { let future_obj = unsafe { env.call_method_unchecked( &self.internal, @@ -205,7 +205,7 @@ impl<'a> JPeripheral<'a> { pub fn write( &self, - env: &mut JNIEnv<'a>, + env: &mut Env<'a>, uuid: &JUuid<'a>, data: &JObject<'a>, write_type: jint, @@ -232,7 +232,7 @@ impl<'a> JPeripheral<'a> { pub fn set_characteristic_notification( &self, - env: &mut JNIEnv<'a>, + env: &mut Env<'a>, uuid: &JUuid<'a>, enable: bool, ) -> Result> { @@ -255,7 +255,7 @@ impl<'a> JPeripheral<'a> { JFuture::from_env(env, future_obj) } - pub fn get_notifications(&self, env: &mut JNIEnv<'a>) -> Result> { + pub fn get_notifications(&self, env: &mut Env<'a>) -> Result> { let stream_obj = unsafe { env.call_method_unchecked( &self.internal, @@ -270,7 +270,7 @@ impl<'a> JPeripheral<'a> { pub fn read_descriptor( &self, - env: &mut JNIEnv<'a>, + env: &mut Env<'a>, characteristic: &JUuid<'a>, uuid: &JUuid<'a>, ) -> Result> { @@ -293,7 +293,7 @@ impl<'a> JPeripheral<'a> { JFuture::from_env(env, future_obj) } - pub fn get_device_name(&self, env: &mut JNIEnv<'a>) -> Result> { + pub fn get_device_name(&self, env: &mut Env<'a>) -> Result> { let obj = unsafe { env.call_method_unchecked( &self.internal, @@ -312,7 +312,7 @@ impl<'a> JPeripheral<'a> { } } - pub fn request_mtu(&self, env: &mut JNIEnv<'a>, mtu: jint) -> Result> { + pub fn request_mtu(&self, env: &mut Env<'a>, mtu: jint) -> Result> { unsafe { env.call_method_unchecked( &self.internal, @@ -326,7 +326,7 @@ impl<'a> JPeripheral<'a> { pub fn get_connection_parameters( &self, - env: &mut JNIEnv<'a>, + env: &mut Env<'a>, ) -> Result> { let obj = unsafe { env.call_method_unchecked( @@ -354,7 +354,7 @@ impl<'a> JPeripheral<'a> { })) } - pub fn read_remote_rssi(&self, env: &mut JNIEnv<'a>) -> Result> { + pub fn read_remote_rssi(&self, env: &mut Env<'a>) -> Result> { unsafe { env.call_method_unchecked( &self.internal, @@ -368,7 +368,7 @@ impl<'a> JPeripheral<'a> { pub fn request_connection_priority( &self, - env: &mut JNIEnv<'a>, + env: &mut Env<'a>, priority: jint, ) -> Result { unsafe { @@ -384,7 +384,7 @@ impl<'a> JPeripheral<'a> { pub fn write_descriptor( &self, - env: &mut JNIEnv<'a>, + env: &mut Env<'a>, characteristic: &JUuid<'a>, uuid: &JUuid<'a>, data: &JObject<'a>, @@ -419,7 +419,7 @@ pub struct JBluetoothGattService<'a> { } impl<'a> JBluetoothGattService<'a> { - pub fn from_env(env: &mut JNIEnv<'a>, obj: JObject<'a>) -> Result { + pub fn from_env(env: &mut Env<'a>, obj: JObject<'a>) -> Result { let class = env.find_class("android/bluetooth/BluetoothGattService")?; let get_uuid = env.get_method_id(&class, "getUuid", "()Ljava/util/UUID;")?; @@ -436,7 +436,7 @@ impl<'a> JBluetoothGattService<'a> { Ok(true) } - pub fn get_uuid(&self, env: &mut JNIEnv<'a>) -> Result { + pub fn get_uuid(&self, env: &mut Env<'a>) -> Result { let obj = unsafe { env.call_method_unchecked(&self.internal, self.get_uuid, ReturnType::Object, &[]) }? @@ -447,7 +447,7 @@ impl<'a> JBluetoothGattService<'a> { pub fn get_characteristics( &self, - env: &mut JNIEnv<'a>, + env: &mut Env<'a>, ) -> Result>> { let obj = unsafe { env.call_method_unchecked( @@ -479,7 +479,7 @@ pub struct JBluetoothGattCharacteristic<'a> { } impl<'a> JBluetoothGattCharacteristic<'a> { - pub fn from_env(env: &mut JNIEnv<'a>, obj: JObject<'a>) -> Result { + pub fn from_env(env: &mut Env<'a>, obj: JObject<'a>) -> Result { let class = env.find_class("android/bluetooth/BluetoothGattCharacteristic")?; let get_uuid = env.get_method_id(&class, "getUuid", "()Ljava/util/UUID;")?; @@ -495,7 +495,7 @@ impl<'a> JBluetoothGattCharacteristic<'a> { }) } - pub fn get_uuid(&self, env: &mut JNIEnv<'a>) -> Result { + pub fn get_uuid(&self, env: &mut Env<'a>) -> Result { let obj = unsafe { env.call_method_unchecked(&self.internal, self.get_uuid, ReturnType::Object, &[]) }? @@ -504,7 +504,7 @@ impl<'a> JBluetoothGattCharacteristic<'a> { uuid_obj.as_uuid(env) } - pub fn get_properties(&self, env: &mut JNIEnv<'a>) -> Result { + pub fn get_properties(&self, env: &mut Env<'a>) -> Result { let flags = unsafe { env.call_method_unchecked( &self.internal, @@ -517,7 +517,7 @@ impl<'a> JBluetoothGattCharacteristic<'a> { Ok(CharPropFlags::from_bits_truncate(flags as u8)) } - pub fn get_value(&self, env: &mut JNIEnv<'a>) -> Result> { + pub fn get_value(&self, env: &mut Env<'a>) -> Result> { let value = unsafe { env.call_method_unchecked(&self.internal, self.get_value, ReturnType::Array, &[]) }? @@ -528,7 +528,7 @@ impl<'a> JBluetoothGattCharacteristic<'a> { pub fn get_descriptors( &self, - env: &mut JNIEnv<'a>, + env: &mut Env<'a>, ) -> Result>> { let obj = unsafe { env.call_method_unchecked( @@ -557,7 +557,7 @@ pub struct JBluetoothGattDescriptor<'a> { } impl<'a> JBluetoothGattDescriptor<'a> { - pub fn from_env(env: &mut JNIEnv<'a>, obj: JObject<'a>) -> Result { + pub fn from_env(env: &mut Env<'a>, obj: JObject<'a>) -> Result { let class = env.find_class("android/bluetooth/BluetoothGattDescriptor")?; let get_uuid = env.get_method_id(&class, "getUuid", "()Ljava/util/UUID;")?; @@ -567,7 +567,7 @@ impl<'a> JBluetoothGattDescriptor<'a> { }) } - pub fn get_uuid(&self, env: &mut JNIEnv<'a>) -> Result { + pub fn get_uuid(&self, env: &mut Env<'a>) -> Result { let obj = unsafe { env.call_method_unchecked(&self.internal, self.get_uuid, ReturnType::Object, &[]) }? @@ -583,7 +583,7 @@ pub struct JBluetoothDevice<'a> { } impl<'a> JBluetoothDevice<'a> { - pub fn from_env(env: &mut JNIEnv<'a>, obj: JObject<'a>) -> Result { + pub fn from_env(env: &mut Env<'a>, obj: JObject<'a>) -> Result { let class = env.find_class("android/bluetooth/BluetoothDevice")?; let get_address = env.get_method_id(&class, "getAddress", "()Ljava/lang/String;")?; @@ -593,7 +593,7 @@ impl<'a> JBluetoothDevice<'a> { }) } - pub fn get_address(&self, env: &mut JNIEnv<'a>) -> Result> { + pub fn get_address(&self, env: &mut Env<'a>) -> Result> { let obj = unsafe { env.call_method_unchecked(&self.internal, self.get_address, ReturnType::Object, &[]) }? @@ -607,7 +607,7 @@ pub struct JScanFilter<'a> { } impl<'a> JScanFilter<'a> { - pub fn new(env: &mut JNIEnv<'a>, filter: ScanFilter) -> Result { + pub fn new(env: &mut Env<'a>, filter: ScanFilter) -> Result { let string_class = env.find_class("java/lang/String")?; let uuids = env.new_object_array( filter.services.len() as i32, @@ -646,7 +646,7 @@ pub struct JScanResult<'a> { } impl<'a> JScanResult<'a> { - pub fn from_env(env: &mut JNIEnv<'a>, obj: JObject<'a>) -> Result { + pub fn from_env(env: &mut Env<'a>, obj: JObject<'a>) -> Result { let class = env.find_class("android/bluetooth/le/ScanResult")?; let get_device = @@ -667,7 +667,7 @@ impl<'a> JScanResult<'a> { }) } - pub fn get_device(&self, env: &mut JNIEnv<'a>) -> Result> { + pub fn get_device(&self, env: &mut Env<'a>) -> Result> { let obj = unsafe { env.call_method_unchecked(&self.internal, self.get_device, ReturnType::Object, &[]) }? @@ -675,7 +675,7 @@ impl<'a> JScanResult<'a> { JBluetoothDevice::from_env(env, obj) } - pub fn get_scan_record(&self, env: &mut JNIEnv<'a>) -> Result> { + pub fn get_scan_record(&self, env: &mut Env<'a>) -> Result> { let obj = unsafe { env.call_method_unchecked( &self.internal, @@ -688,7 +688,7 @@ impl<'a> JScanResult<'a> { JScanRecord::from_env(env, obj) } - pub fn get_tx_power(&self, env: &mut JNIEnv<'a>) -> Result { + pub fn get_tx_power(&self, env: &mut Env<'a>) -> Result { unsafe { env.call_method_unchecked( &self.internal, @@ -700,7 +700,7 @@ impl<'a> JScanResult<'a> { .i() } - pub fn get_rssi(&self, env: &mut JNIEnv<'a>) -> Result { + pub fn get_rssi(&self, env: &mut Env<'a>) -> Result { unsafe { env.call_method_unchecked( &self.internal, @@ -714,7 +714,7 @@ impl<'a> JScanResult<'a> { pub fn to_peripheral_properties( &self, - env: &mut JNIEnv<'a>, + env: &mut Env<'a>, ) -> std::result::Result<(BDAddr, Option), crate::Error> { use std::str::FromStr; @@ -872,7 +872,7 @@ impl<'a> ::std::ops::Deref for JScanRecord<'a> { } impl<'a> JScanRecord<'a> { - pub fn from_env(env: &mut JNIEnv<'a>, obj: JObject<'a>) -> Result { + pub fn from_env(env: &mut Env<'a>, obj: JObject<'a>) -> Result { let class = env.find_class("android/bluetooth/le/ScanRecord")?; let get_device_name = env.get_method_id(&class, "getDeviceName", "()Ljava/lang/String;")?; @@ -895,7 +895,7 @@ impl<'a> JScanRecord<'a> { }) } - pub fn get_device_name(&self, env: &mut JNIEnv<'a>) -> Result> { + pub fn get_device_name(&self, env: &mut Env<'a>) -> Result> { unsafe { env.call_method_unchecked( &self.internal, @@ -907,7 +907,7 @@ impl<'a> JScanRecord<'a> { .l() } - pub fn get_tx_power_level(&self, env: &mut JNIEnv<'a>) -> Result { + pub fn get_tx_power_level(&self, env: &mut Env<'a>) -> Result { unsafe { env.call_method_unchecked( &self.internal, @@ -921,7 +921,7 @@ impl<'a> JScanRecord<'a> { pub fn get_manufacturer_specific_data( &self, - env: &mut JNIEnv<'a>, + env: &mut Env<'a>, ) -> Result> { let obj = unsafe { env.call_method_unchecked( @@ -935,7 +935,7 @@ impl<'a> JScanRecord<'a> { JSparseArray::from_env(env, obj) } - pub fn get_service_data(&self, env: &mut JNIEnv<'a>) -> Result> { + pub fn get_service_data(&self, env: &mut Env<'a>) -> Result> { unsafe { env.call_method_unchecked( &self.internal, @@ -947,7 +947,7 @@ impl<'a> JScanRecord<'a> { .l() } - pub fn get_service_uuids(&self, env: &mut JNIEnv<'a>) -> Result> { + pub fn get_service_uuids(&self, env: &mut Env<'a>) -> Result> { unsafe { env.call_method_unchecked( &self.internal, @@ -982,7 +982,7 @@ impl<'a> ::std::ops::Deref for JSparseArray<'a> { } impl<'a> JSparseArray<'a> { - pub fn from_env(env: &mut JNIEnv<'a>, obj: JObject<'a>) -> Result { + pub fn from_env(env: &mut Env<'a>, obj: JObject<'a>) -> Result { let class = env.find_class("android/util/SparseArray")?; let size = env.get_method_id(&class, "size", "()I")?; @@ -996,7 +996,7 @@ impl<'a> JSparseArray<'a> { }) } - pub fn size(&self, env: &mut JNIEnv<'a>) -> Result { + pub fn size(&self, env: &mut Env<'a>) -> Result { unsafe { env.call_method_unchecked( &self.internal, @@ -1008,7 +1008,7 @@ impl<'a> JSparseArray<'a> { .i() } - pub fn key_at(&self, env: &mut JNIEnv<'a>, index: jint) -> Result { + pub fn key_at(&self, env: &mut Env<'a>, index: jint) -> Result { unsafe { env.call_method_unchecked( &self.internal, @@ -1020,7 +1020,7 @@ impl<'a> JSparseArray<'a> { .i() } - pub fn value_at(&self, env: &mut JNIEnv<'a>, index: jint) -> Result> { + pub fn value_at(&self, env: &mut Env<'a>, index: jint) -> Result> { unsafe { env.call_method_unchecked( &self.internal, @@ -1039,7 +1039,7 @@ pub struct JParcelUuid<'a> { } impl<'a> JParcelUuid<'a> { - pub fn from_env(env: &mut JNIEnv<'a>, obj: JObject<'a>) -> Result { + pub fn from_env(env: &mut Env<'a>, obj: JObject<'a>) -> Result { let class = env.find_class("android/os/ParcelUuid")?; let get_uuid = env.get_method_id(&class, "getUuid", "()Ljava/util/UUID;")?; @@ -1049,7 +1049,7 @@ impl<'a> JParcelUuid<'a> { }) } - pub fn get_uuid(&self, env: &mut JNIEnv<'a>) -> Result> { + pub fn get_uuid(&self, env: &mut Env<'a>) -> Result> { let obj = unsafe { env.call_method_unchecked(&self.internal, self.get_uuid, ReturnType::Object, &[]) }? diff --git a/src/droidplug/jni_utils/arrays.rs b/src/droidplug/jni_utils/arrays.rs index 435e8777..71918916 100644 --- a/src/droidplug/jni_utils/arrays.rs +++ b/src/droidplug/jni_utils/arrays.rs @@ -1,21 +1,19 @@ use jni::{ - JNIEnv, + Env, errors::Result, objects::JByteArray, sys::{jbyte, jint}, }; use std::slice; -/// Create a new Java byte array from the given slice. -pub fn slice_to_byte_array<'local>(env: &mut JNIEnv<'local>, slice: &[u8]) -> Result> { +pub fn slice_to_byte_array<'local>(env: &mut Env<'local>, slice: &[u8]) -> Result> { let obj = env.new_byte_array(slice.len() as jint)?; let slice = unsafe { &*(slice as *const [u8] as *const [jbyte]) }; env.set_byte_array_region(&obj, 0, slice)?; Ok(obj) } -/// Get a [`Vec`] of bytes from the given Java byte array. -pub fn byte_array_to_vec(env: &JNIEnv, array: &JByteArray) -> Result> { +pub fn byte_array_to_vec(env: &Env, array: &JByteArray) -> Result> { let size = env.get_array_length(array)? as usize; let mut result = Vec::with_capacity(size); unsafe { diff --git a/src/droidplug/jni_utils/classcache.rs b/src/droidplug/jni_utils/classcache.rs index f297e5d8..39732870 100644 --- a/src/droidplug/jni_utils/classcache.rs +++ b/src/droidplug/jni_utils/classcache.rs @@ -1,18 +1,20 @@ use dashmap::DashMap; -use jni::{JNIEnv, errors::Result, objects::GlobalRef}; +use jni::{Env, errors::Result, objects::{Global, JObject}}; use once_cell::sync::OnceCell; +use std::sync::Arc; -static CLASSCACHE: OnceCell> = OnceCell::new(); +static CLASSCACHE: OnceCell>>>> = OnceCell::new(); -pub fn find_add_class(env: &mut JNIEnv, classname: &str) -> Result<()> { +pub fn find_add_class(env: &mut Env, classname: &str) -> Result<()> { let cache = CLASSCACHE.get_or_init(|| DashMap::new()); let cls = env.find_class(classname)?; - let global = env.new_global_ref(cls)?; - cache.insert(classname.to_owned(), global); + let cls_obj: JObject = cls.into(); + let global = env.new_global_ref(&cls_obj)?; + cache.insert(classname.to_owned(), Arc::new(global)); Ok(()) } -pub fn get_class(classname: &str) -> Option { +pub fn get_class(classname: &str) -> Option>>> { let cache = CLASSCACHE.get_or_init(|| DashMap::new()); cache.get(classname).map(|pair| pair.value().clone()) } diff --git a/src/droidplug/jni_utils/exceptions.rs b/src/droidplug/jni_utils/exceptions.rs index c397723b..f286a209 100644 --- a/src/droidplug/jni_utils/exceptions.rs +++ b/src/droidplug/jni_utils/exceptions.rs @@ -1,5 +1,5 @@ use jni::{ - JNIEnv, + Env, descriptors::Desc, errors::Error, objects::{JClass, JObject, JThrowable}, @@ -21,8 +21,8 @@ pub struct TryCatchResult { /// to be thrown, it will be stored in the resulting [`TryCatchResult`] for /// matching with [`catch`](TryCatchResult::catch). pub fn try_block( - env: &mut JNIEnv, - block: impl FnOnce(&mut JNIEnv) -> Result, + env: &mut Env, + block: impl FnOnce(&mut Env) -> Result, ) -> TryCatchResult { TryCatchResult { try_result: (|| { @@ -39,9 +39,9 @@ pub fn try_block( impl TryCatchResult { pub fn catch<'local>( self, - env: &mut JNIEnv<'local>, + env: &mut Env<'local>, class: impl Desc<'local, JClass<'local>>, - block: impl FnOnce(&mut JNIEnv<'local>, JThrowable<'local>) -> Result, + block: impl FnOnce(&mut Env<'local>, JThrowable<'local>) -> Result, ) -> Self { match (self.try_result, self.catch_result) { (Err(e), _) => Self { @@ -102,7 +102,7 @@ impl<'a> JPanicException<'a> { Self { internal: obj } } - pub fn new(env: &mut 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)?.into() } else if let Some(s) = any.downcast_ref::() { @@ -124,16 +124,16 @@ impl<'a> JPanicException<'a> { pub fn get<'b>( &self, - env: &'b mut JNIEnv, + env: &'b mut Env, ) -> Result>, Error> { unsafe { env.get_rust_field(&self.internal, "any") } } - pub fn take(&self, env: &mut JNIEnv) -> Result, Error> { + pub fn take(&self, env: &mut Env) -> Result, Error> { unsafe { env.take_rust_field(&self.internal, "any") } } - pub fn resume_unwind(&self, env: &mut JNIEnv) -> Result<(), Error> { + pub fn resume_unwind(&self, env: &mut Env) -> Result<(), Error> { resume_unwind(self.take(env)?); } } @@ -156,7 +156,7 @@ impl<'a> ::std::ops::Deref for JPanicException<'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 JNIEnv, + env: &mut Env, panic: Box, ) -> Result<(), Error> { let old_ex = if env.exception_check()? { @@ -184,7 +184,7 @@ pub fn throw_panic( /// 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( - env: &mut JNIEnv, + env: &mut Env, f: impl FnOnce() -> R + UnwindSafe, ) -> Result> { catch_unwind(f).map_err(|e| throw_panic(env, e)) @@ -192,13 +192,13 @@ pub fn throw_unwind( #[cfg(test)] mod test { - use jni::{JNIEnv, errors::Error, objects::{JObject, JThrowable}}; + use jni::{Env, errors::Error, objects::{JObject, JThrowable}}; use super::super::test_utils; use super::try_block; fn test_catch( - env: &mut JNIEnv, + env: &mut Env, throw_class: Option<&str>, try_result: Result, rethrow: bool, diff --git a/src/droidplug/jni_utils/future.rs b/src/droidplug/jni_utils/future.rs index 2a42b404..c1b854dd 100644 --- a/src/droidplug/jni_utils/future.rs +++ b/src/droidplug/jni_utils/future.rs @@ -1,7 +1,7 @@ use ::jni::{ - JNIEnv, JavaVM, + Env, JavaVM, errors::Result, - objects::{GlobalRef, JClass, JMethodID, JObject}, + objects::{Global, JClass, JMethodID, JObject}, signature::ReturnType, sys::jvalue, }; @@ -12,19 +12,13 @@ use std::{ task::{Context, Poll}, }; -/// Wrapper for [`JObject`]s that implement -/// `io.github.gedgygedgy.rust.future.Future`. Provides a typed interface for -/// calling the Java future's `poll` method. -/// -/// For an async [`Future`](std::future::Future) implementation, convert to -/// [`JSendFuture`] via [`JSendFuture::new`]. pub struct JFuture<'a> { internal: JObject<'a>, poll_id: JMethodID, } impl<'a> JFuture<'a> { - pub fn from_env(env: &mut JNIEnv<'a>, obj: JObject<'a>) -> Result { + pub fn from_env(env: &mut Env<'a>, obj: JObject<'a>) -> Result { let class = super::classcache::get_class("io/github/gedgygedgy/rust/future/Future").unwrap(); let poll_id = env.get_method_id( @@ -38,7 +32,7 @@ impl<'a> JFuture<'a> { }) } - pub fn poll(&self, env: &mut JNIEnv<'a>, waker: &JObject<'_>) -> Result> { + pub fn poll(&self, env: &mut Env<'a>, waker: &JObject<'_>) -> Result> { let result = unsafe { env.call_method_unchecked( &self.internal, @@ -68,16 +62,14 @@ impl<'a> From> for JObject<'a> { } } -/// [`Send`] version of [`JFuture`]. Implements [`Future`](std::future::Future) -/// by obtaining a [`JNIEnv`] from the stored [`JavaVM`] on each poll. pub struct JSendFuture { - internal: GlobalRef, + internal: Global>, poll_id: JMethodID, vm: JavaVM, } impl JSendFuture { - pub fn new(env: &mut JNIEnv, future: &JFuture) -> Result { + pub fn new(env: &mut Env, future: &JFuture) -> Result { Ok(Self { internal: env.new_global_ref(&future.internal)?, poll_id: future.poll_id, @@ -85,7 +77,7 @@ impl JSendFuture { }) } - pub fn from_env(env: &mut JNIEnv, obj: &JObject) -> Result { + pub fn from_env(env: &mut Env, obj: &JObject) -> Result { let class = super::classcache::get_class("io/github/gedgygedgy/rust/future/Future").unwrap(); let poll_id = env.get_method_id( @@ -100,7 +92,7 @@ impl JSendFuture { }) } - fn poll_internal(&self, context: &mut Context<'_>) -> Result>> { + fn poll_internal(&self, context: &mut Context<'_>) -> Result>>>> { let mut env = self.vm.get_env()?; let jwaker = super::task::waker(&mut env, context.waker().clone())?; let result = unsafe { @@ -123,7 +115,7 @@ impl JSendFuture { } impl ::std::ops::Deref for JSendFuture { - type Target = GlobalRef; + type Target = Global>; fn deref(&self) -> &Self::Target { &self.internal @@ -131,7 +123,7 @@ impl ::std::ops::Deref for JSendFuture { } 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) { diff --git a/src/droidplug/jni_utils/stream.rs b/src/droidplug/jni_utils/stream.rs index e8bb85f0..751a95e5 100644 --- a/src/droidplug/jni_utils/stream.rs +++ b/src/droidplug/jni_utils/stream.rs @@ -1,8 +1,8 @@ use super::task::JPollResult; use ::jni::{ - JNIEnv, JavaVM, + Env, JavaVM, errors::Result, - objects::{GlobalRef, JClass, JMethodID, JObject}, + objects::{Global, JClass, JMethodID, JObject}, signature::ReturnType, sys::jvalue, }; @@ -13,19 +13,13 @@ use std::{ task::{Context, Poll}, }; -/// Wrapper for [`JObject`]s that implement -/// `io.github.gedgygedgy.rust.stream.Stream`. Provides a typed interface for -/// calling the Java stream's `pollNext` method. -/// -/// For an async [`Stream`] implementation, convert to [`JSendStream`] via -/// [`JSendStream::new`]. pub struct JStream<'a> { internal: JObject<'a>, poll_next_id: JMethodID, } impl<'a> JStream<'a> { - pub fn from_env(env: &mut JNIEnv<'a>, obj: JObject<'a>) -> Result { + pub fn from_env(env: &mut Env<'a>, obj: JObject<'a>) -> Result { let class = super::classcache::get_class("io/github/gedgygedgy/rust/stream/Stream").unwrap(); let poll_next_id = env.get_method_id( @@ -41,7 +35,7 @@ impl<'a> JStream<'a> { pub fn poll_next_with_env( &self, - env: &mut JNIEnv<'a>, + env: &mut Env<'a>, waker: &JObject<'_>, ) -> Result> { let result = unsafe { @@ -73,16 +67,14 @@ impl<'a> From> for JObject<'a> { } } -/// [`Send`] version of [`JStream`]. Implements [`Stream`] by obtaining a -/// [`JNIEnv`] from the stored [`JavaVM`] on each poll. pub struct JSendStream { - internal: GlobalRef, + internal: Global>, poll_next_id: JMethodID, vm: JavaVM, } impl JSendStream { - pub fn new(env: &mut JNIEnv, stream: &JStream) -> Result { + pub fn new(env: &mut Env, stream: &JStream) -> Result { Ok(Self { internal: env.new_global_ref(&stream.internal)?, poll_next_id: stream.poll_next_id, @@ -90,7 +82,7 @@ impl JSendStream { }) } - pub fn from_env(env: &mut JNIEnv, obj: &JObject) -> Result { + pub fn from_env(env: &mut Env, obj: &JObject) -> Result { let class = super::classcache::get_class("io/github/gedgygedgy/rust/stream/Stream").unwrap(); let poll_next_id = env.get_method_id( @@ -108,7 +100,7 @@ impl JSendStream { fn poll_next_internal( &self, context: &mut Context<'_>, - ) -> Result>>> { + ) -> Result>>>>> { let mut env = self.vm.get_env()?; let jwaker = super::task::waker(&mut env, context.waker().clone())?; let result = unsafe { @@ -141,7 +133,7 @@ impl JSendStream { } impl ::std::ops::Deref for JSendStream { - type Target = GlobalRef; + type Target = Global>; fn deref(&self) -> &Self::Target { &self.internal @@ -149,7 +141,7 @@ impl ::std::ops::Deref for JSendStream { } 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) { @@ -167,7 +159,7 @@ struct JStreamPoll<'a> { } impl<'a> JStreamPoll<'a> { - pub fn from_env(env: &mut JNIEnv<'a>, obj: JObject<'a>) -> Result { + pub fn from_env(env: &mut Env<'a>, obj: JObject<'a>) -> Result { let class = super::classcache::get_class("io/github/gedgygedgy/rust/stream/StreamPoll").unwrap(); let get = env.get_method_id( @@ -178,7 +170,7 @@ impl<'a> JStreamPoll<'a> { Ok(Self { internal: obj, get }) } - pub fn get(&self, env: &mut JNIEnv<'a>) -> Result> { + pub fn get(&self, env: &mut Env<'a>) -> Result> { unsafe { env.call_method_unchecked(&self.internal, self.get, ReturnType::Object, &[]) }? .l() } diff --git a/src/droidplug/jni_utils/task.rs b/src/droidplug/jni_utils/task.rs index 95096110..32339b6e 100644 --- a/src/droidplug/jni_utils/task.rs +++ b/src/droidplug/jni_utils/task.rs @@ -1,13 +1,12 @@ use ::jni::{ - JNIEnv, + Env, errors::Result, objects::{JClass, JMethodID, JObject}, signature::ReturnType, }; use std::task::Waker; -/// Wraps the given waker in a `io.github.gedgygedgy.rust.task.Waker` object. -pub fn waker<'a>(env: &mut JNIEnv<'a>, waker: Waker) -> Result> { +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 = super::classcache::get_class("io/github/gedgygedgy/rust/task/Waker").unwrap(); @@ -19,22 +18,20 @@ pub fn waker<'a>(env: &mut JNIEnv<'a>, waker: Waker) -> Result> { Ok(obj) } -/// Wrapper for [`JObject`]s that implement -/// `io.github.gedgygedgy.rust.task.PollResult`. pub struct JPollResult<'a> { internal: JObject<'a>, get: JMethodID, } impl<'a> JPollResult<'a> { - pub fn from_env(env: &mut JNIEnv<'a>, obj: JObject<'a>) -> Result { + pub fn from_env(env: &mut Env<'a>, obj: JObject<'a>) -> Result { let class = super::classcache::get_class("io/github/gedgygedgy/rust/task/PollResult").unwrap(); let get = env.get_method_id(<&JClass>::from(class.as_obj()), "get", "()Ljava/lang/Object;")?; Ok(Self { internal: obj, get }) } - pub fn get(&self, env: &mut JNIEnv<'a>) -> Result> { + pub fn get(&self, env: &mut Env<'a>) -> Result> { unsafe { env.call_method_unchecked(&self.internal, self.get, ReturnType::Object, &[]) }? .l() } diff --git a/src/droidplug/jni_utils/uuid.rs b/src/droidplug/jni_utils/uuid.rs index c330ce7b..caa05ff2 100644 --- a/src/droidplug/jni_utils/uuid.rs +++ b/src/droidplug/jni_utils/uuid.rs @@ -1,5 +1,5 @@ use jni::{ - JNIEnv, + Env, errors::Result, objects::{JMethodID, JObject}, signature::{Primitive, ReturnType}, @@ -7,8 +7,6 @@ use jni::{ }; 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> { internal: JObject<'a>, get_least_significant_bits: JMethodID, @@ -16,7 +14,7 @@ pub struct JUuid<'a> { } impl<'a> JUuid<'a> { - pub fn from_env(env: &mut JNIEnv<'a>, obj: JObject<'a>) -> Result { + pub fn from_env(env: &mut Env<'a>, obj: JObject<'a>) -> Result { let class = env.find_class("java/util/UUID")?; let get_least_significant_bits = env.get_method_id(&class, "getLeastSignificantBits", "()J")?; @@ -29,7 +27,7 @@ impl<'a> JUuid<'a> { }) } - pub fn new(env: &mut JNIEnv<'a>, uuid: Uuid) -> Result { + pub fn new(env: &mut Env<'a>, uuid: Uuid) -> Result { let val = uuid.as_u128(); let least = (val & 0xFFFFFFFFFFFFFFFF) as jlong; let most = ((val >> 64) & 0xFFFFFFFFFFFFFFFF) as jlong; @@ -47,7 +45,7 @@ impl<'a> JUuid<'a> { }) } - pub fn as_uuid(&self, env: &mut JNIEnv<'a>) -> Result { + pub fn as_uuid(&self, env: &mut Env<'a>) -> Result { let least = unsafe { env.call_method_unchecked( &self.internal, diff --git a/src/droidplug/mod.rs b/src/droidplug/mod.rs index d759835f..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: &mut 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 b7e25052..dc81c2fc 100644 --- a/src/droidplug/peripheral.rs +++ b/src/droidplug/peripheral.rs @@ -15,8 +15,8 @@ use crate::{ use async_trait::async_trait; use futures::stream::Stream; use jni::{ - JNIEnv, - objects::{GlobalRef, JClass, JObject, JString, JValue}, + Env, + objects::{Global, JClass, JObject, JString, JValue}, }; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; @@ -48,8 +48,8 @@ impl Display for PeripheralId { } fn get_poll_result<'a>( - env: &mut JNIEnv<'a>, - result_ref: &GlobalRef, + env: &mut Env<'a>, + result_ref: &Global>, ) -> Result> { let result_obj = env.new_local_ref(result_ref)?; let poll_result = JPollResult::from_env(env, result_obj)?; @@ -128,15 +128,15 @@ struct PeripheralShared { #[derive(Clone)] pub struct Peripheral { addr: BDAddr, - internal: GlobalRef, + internal: Arc>>, shared: Arc>, mtu: Arc, } impl Peripheral { - pub(crate) fn new<'a>(env: &mut JNIEnv<'a>, adapter: JObject<'a>, addr: BDAddr) -> Result { + pub(crate) fn new<'a>(env: &mut Env<'a>, adapter: JObject<'a>, addr: BDAddr) -> Result { let obj = JPeripheral::new(env, adapter, addr)?; - let internal = env.new_global_ref(&*obj)?; + let internal = Arc::new(env.new_global_ref(&*obj)?); Ok(Self { addr, internal, @@ -157,13 +157,13 @@ impl Peripheral { fn with_obj( &self, - f: impl for<'env> FnOnce(&mut JNIEnv<'env>, &JPeripheral<'env>) -> std::result::Result, + f: impl for<'env> FnOnce(&mut Env<'env>, &JPeripheral<'env>) -> std::result::Result, ) -> std::result::Result where E: From<::jni::errors::Error>, { let mut env = global_jvm().get_env()?; - let local_obj = env.new_local_ref(&self.internal)?; + let local_obj = env.new_local_ref(self.internal.as_obj())?; let obj = JPeripheral::from_env(&mut env, local_obj)?; f(&mut env, &obj) } From d73d17f7b9cf4795734153f99f6e9f947cb20786 Mon Sep 17 00:00:00 2001 From: Kyle Machulis Date: Sun, 26 Apr 2026 19:47:12 -0700 Subject: [PATCH 06/16] refactor: Wrap JNI strings with jni_str! and signatures with jni_sig! Phase 3 of jni 0.22 migration. All string literals passed to JNI API methods (find_class, get_method_id, call_method, new_object, is_instance_of, set/get/take_rust_field, call_static_method) are now wrapped with jni_str!() for names and jni_sig!() for type signatures. classcache::find_add_class uses JNIString for runtime &str conversion. register_native_methods calls wrapped in unsafe blocks (required by 0.22). Co-Authored-By: Claude Opus 4.6 --- src/droidplug/adapter.rs | 22 ++-- src/droidplug/jni/mod.rs | 31 +++--- src/droidplug/jni/objects.rs | 149 +++++++++++++------------- src/droidplug/jni_utils/classcache.rs | 5 +- src/droidplug/jni_utils/exceptions.rs | 49 ++++----- src/droidplug/jni_utils/future.rs | 44 ++++---- src/droidplug/jni_utils/mod.rs | 36 +++---- src/droidplug/jni_utils/ops.rs | 15 +-- src/droidplug/jni_utils/stream.rs | 62 +++++------ src/droidplug/jni_utils/task.rs | 14 +-- src/droidplug/jni_utils/uuid.rs | 23 ++-- src/droidplug/peripheral.rs | 16 +-- 12 files changed, 239 insertions(+), 227 deletions(-) diff --git a/src/droidplug/adapter.rs b/src/droidplug/adapter.rs index 5470c7bd..2befcf2c 100644 --- a/src/droidplug/adapter.rs +++ b/src/droidplug/adapter.rs @@ -13,7 +13,7 @@ use crate::{ use async_trait::async_trait; use futures::stream::Stream; use jni::{ - Env, + Env, jni_sig, jni_str, objects::{Global, JClass, JObject, JString}, sys::jboolean, }; @@ -43,8 +43,8 @@ impl Adapter { let mut env = global_jvm().get_env()?; let obj = env.new_object( - "com/nonpolynomial/btleplug/android/impl/Adapter", - "()V", + jni_str!("com/nonpolynomial/btleplug/android/impl/Adapter"), + jni_sig!("()V"), &[], )?; let internal = Arc::new(env.new_global_ref(&obj)?); @@ -52,7 +52,7 @@ impl Adapter { manager: Arc::new(AdapterManager::default()), internal, }; - unsafe { env.set_rust_field(&obj, "handle", adapter.clone()) }?; + unsafe { env.set_rust_field(&obj, jni_str!("handle"), adapter.clone()) }?; Ok(adapter) } @@ -140,8 +140,8 @@ impl Central for Adapter { let filter_obj: JObject = filter.into(); match env.call_method( self.internal.as_obj(), - "startScan", - "(Lcom/nonpolynomial/btleplug/android/impl/ScanFilter;)V", + jni_str!("startScan"), + jni_sig!("(Lcom/nonpolynomial/btleplug/android/impl/ScanFilter;)V"), &[(&filter_obj).into()], ) { Ok(_) => Ok(()), @@ -156,9 +156,9 @@ impl Central for Adapter { if env.is_instance_of(&ex, <&JClass>::from(no_adapter_class.as_obj()))? { Err(Error::NoAdapterAvailable) - } else if env.is_instance_of(&ex, "java/lang/RuntimeException")? { + } else if env.is_instance_of(&ex, jni_str!("java/lang/RuntimeException"))? { let msg = env - .call_method(&ex, "getMessage", "()Ljava/lang/String;", &[])? + .call_method(&ex, jni_str!("getMessage"), jni_sig!("()Ljava/lang/String;"), &[])? .l()?; let jstr: JString = msg.into(); let msgstr: String = env.get_string(&jstr)?.into(); @@ -174,7 +174,7 @@ impl Central for Adapter { async fn stop_scan(&self) -> Result<()> { let mut env = global_jvm().get_env()?; - env.call_method(self.internal.as_obj(), "stopScan", "()V", &[])?; + env.call_method(self.internal.as_obj(), jni_str!("stopScan"), jni_sig!("()V"), &[])?; Ok(()) } @@ -207,7 +207,7 @@ pub(crate) fn adapter_report_scan_result_internal<'a>( obj: &JObject, scan_result: JObject<'a>, ) -> crate::Result<()> { - let adapter = unsafe { env.get_rust_field::<_, _, Adapter>(obj, "handle") }?; + 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)?; @@ -222,7 +222,7 @@ pub(crate) fn adapter_on_connection_state_changed_internal( ) -> crate::Result<()> { let addr_str: String = env.get_string(&addr)?.into(); let addr = BDAddr::from_str(&addr_str)?; - let adapter = unsafe { env.get_rust_field::<_, _, Adapter>(obj, "handle") }?; + let adapter = unsafe { env.get_rust_field::<_, _, Adapter>(obj, jni_str!("handle")) }?; adapter.manager.emit(if connected != 0 { CentralEvent::DeviceConnected(PeripheralId(addr)) } else { diff --git a/src/droidplug/jni/mod.rs b/src/droidplug/jni/mod.rs index 9453eab7..80ddf8e7 100644 --- a/src/droidplug/jni/mod.rs +++ b/src/droidplug/jni/mod.rs @@ -1,6 +1,6 @@ pub mod objects; -use ::jni::{Env, JNIEnv, JavaVM, NativeMethod, objects::JObject}; +use ::jni::{Env, JNIEnv, JavaVM, NativeMethod, jni_str, jni_sig, objects::JObject}; use jni::{objects::JString, sys::jboolean}; use once_cell::sync::OnceCell; use std::ffi::c_void; @@ -10,22 +10,22 @@ static GLOBAL_JVM: OnceCell = OnceCell::new(); pub fn init(env: &mut Env) -> crate::Result<()> { if let Ok(()) = GLOBAL_JVM.set(env.get_java_vm()?) { let adapter_class = - env.find_class("com/nonpolynomial/btleplug/android/impl/Adapter")?; - env.register_native_methods( + 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(), + name: jni_str!("reportScanResult").into(), + sig: jni_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(), + name: jni_str!("onConnectionStateChanged").into(), + sig: jni_sig!("(Ljava/lang/String;Z)V").into(), fn_ptr: adapter_on_connection_state_changed as *mut c_void, }, ], - )?; + )? }; super::jni_utils::classcache::find_add_class( env, "com/nonpolynomial/btleplug/android/impl/Peripheral", @@ -99,24 +99,25 @@ pub fn init(env: &mut Env) -> crate::Result<()> { )?; // FnAdapter native method registration - let fn_adapter_class = env.find_class("io/github/gedgygedgy/rust/ops/FnAdapter")?; - env.register_native_methods( + let fn_adapter_class = env.find_class(jni_str!("io/github/gedgygedgy/rust/ops/FnAdapter"))?; + unsafe { env.register_native_methods( &fn_adapter_class, &[ NativeMethod { - name: "callInternal".into(), + name: jni_str!("callInternal").into(), sig: - "(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;" + jni_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(), + name: jni_str!("closeInternal").into(), + sig: jni_sig!("()V").into(), fn_ptr: super::jni_utils::ops::fn_adapter_close_internal as *mut c_void, }, ], - )?; + )? }; + } Ok(()) } diff --git a/src/droidplug/jni/objects.rs b/src/droidplug/jni/objects.rs index 7e43dbfb..5563b755 100644 --- a/src/droidplug/jni/objects.rs +++ b/src/droidplug/jni/objects.rs @@ -2,6 +2,7 @@ use crate::droidplug::jni_utils::{future::JFuture, stream::JStream, uuid::JUuid} use jni::{ Env, errors::Result, + jni_sig, jni_str, objects::{JClass, JMethodID, JObject, JString}, signature::{Primitive, ReturnType}, sys::{jint, jvalue}, @@ -54,64 +55,64 @@ impl<'a> JPeripheral<'a> { let connect = env.get_method_id( class, - "connect", - "()Lio/github/gedgygedgy/rust/future/Future;", + jni_str!("connect"), + jni_sig!("()Lio/github/gedgygedgy/rust/future/Future;"), )?; let disconnect = env.get_method_id( class, - "disconnect", - "()Lio/github/gedgygedgy/rust/future/Future;", + jni_str!("disconnect"), + jni_sig!("()Lio/github/gedgygedgy/rust/future/Future;"), )?; - let is_connected = env.get_method_id(class, "isConnected", "()Z")?; + let is_connected = env.get_method_id(class, jni_str!("isConnected"), jni_sig!("()Z"))?; let discover_services = env.get_method_id( class, - "discoverServices", - "()Lio/github/gedgygedgy/rust/future/Future;", + jni_str!("discoverServices"), + jni_sig!("()Lio/github/gedgygedgy/rust/future/Future;"), )?; let read = env.get_method_id( class, - "read", - "(Ljava/util/UUID;)Lio/github/gedgygedgy/rust/future/Future;", + jni_str!("read"), + jni_sig!("(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;", + jni_str!("write"), + jni_sig!("(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;", + jni_str!("setCharacteristicNotification"), + jni_sig!("(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;", + jni_str!("getNotifications"), + jni_sig!("()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;", + jni_str!("readDescriptor"), + jni_sig!("(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;", + jni_str!("writeDescriptor"), + jni_sig!("(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 get_device_name = env.get_method_id(class, jni_str!("getDeviceName"), jni_sig!("()Ljava/lang/String;"))?; let request_mtu = env.get_method_id( class, - "requestMtu", - "(I)Lio/github/gedgygedgy/rust/future/Future;", + jni_str!("requestMtu"), + jni_sig!("(I)Lio/github/gedgygedgy/rust/future/Future;"), )?; let get_connection_parameters = - env.get_method_id(class, "getConnectionParameters", "()[I")?; + env.get_method_id(class, jni_str!("getConnectionParameters"), jni_sig!("()[I"))?; let request_connection_priority = - env.get_method_id(class, "requestConnectionPriority", "(I)Z")?; + env.get_method_id(class, jni_str!("requestConnectionPriority"), jni_sig!("(I)Z"))?; let read_remote_rssi = env.get_method_id( class, - "readRemoteRssi", - "()Lio/github/gedgygedgy/rust/future/Future;", + jni_str!("readRemoteRssi"), + jni_sig!("()Lio/github/gedgygedgy/rust/future/Future;"), )?; Ok(Self { internal: obj, @@ -141,7 +142,7 @@ impl<'a> JPeripheral<'a> { .unwrap(); let obj = env.new_object( <&JClass>::from(class_static.as_obj()), - "(Lcom/nonpolynomial/btleplug/android/impl/Adapter;Ljava/lang/String;)V", + jni_sig!("(Lcom/nonpolynomial/btleplug/android/impl/Adapter;Ljava/lang/String;)V"), &[(&adapter).into(), (&addr_jstr).into()], )?; Self::from_env(env, obj) @@ -420,11 +421,11 @@ pub struct JBluetoothGattService<'a> { impl<'a> JBluetoothGattService<'a> { pub fn from_env(env: &mut Env<'a>, obj: JObject<'a>) -> Result { - let class = env.find_class("android/bluetooth/BluetoothGattService")?; + let class = env.find_class(jni_str!("android/bluetooth/BluetoothGattService"))?; - let get_uuid = env.get_method_id(&class, "getUuid", "()Ljava/util/UUID;")?; + let get_uuid = env.get_method_id(&class, jni_str!("getUuid"), jni_sig!("()Ljava/util/UUID;"))?; let get_characteristics = - env.get_method_id(&class, "getCharacteristics", "()Ljava/util/List;")?; + env.get_method_id(&class, jni_str!("getCharacteristics"), jni_sig!("()Ljava/util/List;"))?; Ok(Self { internal: obj, get_uuid, @@ -458,11 +459,11 @@ impl<'a> JBluetoothGattService<'a> { ) }? .l()?; - let size = env.call_method(&obj, "size", "()I", &[])?.i()?; + 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, "get", "(I)Ljava/lang/Object;", &[jni::objects::JValue::from(i)])? + .call_method(&obj, jni_str!("get"), jni_sig!("(I)Ljava/lang/Object;"), &[jni::objects::JValue::from(i)])? .l()?; chr_vec.push(JBluetoothGattCharacteristic::from_env(env, chr)?); } @@ -480,12 +481,12 @@ pub struct JBluetoothGattCharacteristic<'a> { impl<'a> JBluetoothGattCharacteristic<'a> { pub fn from_env(env: &mut Env<'a>, obj: JObject<'a>) -> Result { - let class = env.find_class("android/bluetooth/BluetoothGattCharacteristic")?; + let class = env.find_class(jni_str!("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")?; + let get_uuid = env.get_method_id(&class, jni_str!("getUuid"), jni_sig!("()Ljava/util/UUID;"))?; + let get_properties = env.get_method_id(&class, jni_str!("getProperties"), jni_sig!("()I"))?; + let get_descriptors = env.get_method_id(&class, jni_str!("getDescriptors"), jni_sig!("()Ljava/util/List;"))?; + let get_value = env.get_method_id(&class, jni_str!("getValue"), jni_sig!("()[B"))?; Ok(Self { internal: obj, get_uuid, @@ -539,11 +540,11 @@ impl<'a> JBluetoothGattCharacteristic<'a> { ) }? .l()?; - let size = env.call_method(&obj, "size", "()I", &[])?.i()?; + 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, "get", "(I)Ljava/lang/Object;", &[jni::objects::JValue::from(i)])? + .call_method(&obj, jni_str!("get"), jni_sig!("(I)Ljava/lang/Object;"), &[jni::objects::JValue::from(i)])? .l()?; desc_vec.push(JBluetoothGattDescriptor::from_env(env, desc)?); } @@ -558,9 +559,9 @@ pub struct JBluetoothGattDescriptor<'a> { impl<'a> JBluetoothGattDescriptor<'a> { pub fn from_env(env: &mut Env<'a>, obj: JObject<'a>) -> Result { - let class = env.find_class("android/bluetooth/BluetoothGattDescriptor")?; + let class = env.find_class(jni_str!("android/bluetooth/BluetoothGattDescriptor"))?; - let get_uuid = env.get_method_id(&class, "getUuid", "()Ljava/util/UUID;")?; + let get_uuid = env.get_method_id(&class, jni_str!("getUuid"), jni_sig!("()Ljava/util/UUID;"))?; Ok(Self { internal: obj, get_uuid, @@ -584,9 +585,9 @@ pub struct JBluetoothDevice<'a> { impl<'a> JBluetoothDevice<'a> { pub fn from_env(env: &mut Env<'a>, obj: JObject<'a>) -> Result { - let class = env.find_class("android/bluetooth/BluetoothDevice")?; + let class = env.find_class(jni_str!("android/bluetooth/BluetoothDevice"))?; - let get_address = env.get_method_id(&class, "getAddress", "()Ljava/lang/String;")?; + let get_address = env.get_method_id(&class, jni_str!("getAddress"), jni_sig!("()Ljava/lang/String;"))?; Ok(Self { internal: obj, get_address, @@ -608,7 +609,7 @@ pub struct JScanFilter<'a> { impl<'a> JScanFilter<'a> { pub fn new(env: &mut Env<'a>, filter: ScanFilter) -> Result { - let string_class = env.find_class("java/lang/String")?; + let string_class = env.find_class(jni_str!("java/lang/String"))?; let uuids = env.new_object_array( filter.services.len() as i32, &string_class, @@ -624,7 +625,7 @@ impl<'a> JScanFilter<'a> { .unwrap(); let obj = env.new_object( <&JClass>::from(class_static.as_obj()), - "([Ljava/lang/String;)V", + jni_sig!("([Ljava/lang/String;)V"), &[(&uuids).into()], )?; Ok(Self { internal: obj }) @@ -647,17 +648,17 @@ pub struct JScanResult<'a> { impl<'a> JScanResult<'a> { pub fn from_env(env: &mut Env<'a>, obj: JObject<'a>) -> Result { - let class = env.find_class("android/bluetooth/le/ScanResult")?; + let class = env.find_class(jni_str!("android/bluetooth/le/ScanResult"))?; let get_device = - env.get_method_id(&class, "getDevice", "()Landroid/bluetooth/BluetoothDevice;")?; + env.get_method_id(&class, jni_str!("getDevice"), jni_sig!("()Landroid/bluetooth/BluetoothDevice;"))?; let get_scan_record = env.get_method_id( &class, - "getScanRecord", - "()Landroid/bluetooth/le/ScanRecord;", + jni_str!("getScanRecord"), + jni_sig!("()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")?; + let get_tx_power = env.get_method_id(&class, jni_str!("getTxPower"), jni_sig!("()I"))?; + let get_rssi = env.get_method_id(&class, jni_str!("getRssi"), jni_sig!("()I"))?; Ok(Self { internal: obj, get_device, @@ -775,28 +776,28 @@ impl<'a> JScanResult<'a> { let mut service_data = HashMap::new(); if !env.is_same_object(&service_data_obj, JObject::null())? { let entry_set = env - .call_method(&service_data_obj, "entrySet", "()Ljava/util/Set;", &[])? + .call_method(&service_data_obj, jni_str!("entrySet"), jni_sig!("()Ljava/util/Set;"), &[])? .l()?; let iter_obj = env .call_method( &entry_set, - "iterator", - "()Ljava/util/Iterator;", + jni_str!("iterator"), + jni_sig!("()Ljava/util/Iterator;"), &[], )? .l()?; while env - .call_method(&iter_obj, "hasNext", "()Z", &[])? + .call_method(&iter_obj, jni_str!("hasNext"), jni_sig!("()Z"), &[])? .z()? { let entry = env - .call_method(&iter_obj, "next", "()Ljava/lang/Object;", &[])? + .call_method(&iter_obj, jni_str!("next"), jni_sig!("()Ljava/lang/Object;"), &[])? .l()?; let key = env - .call_method(&entry, "getKey", "()Ljava/lang/Object;", &[])? + .call_method(&entry, jni_str!("getKey"), jni_sig!("()Ljava/lang/Object;"), &[])? .l()?; let value = env - .call_method(&entry, "getValue", "()Ljava/lang/Object;", &[])? + .call_method(&entry, jni_str!("getValue"), jni_sig!("()Ljava/lang/Object;"), &[])? .l()?; let parcel_uuid = JParcelUuid::from_env(env, key)?; let juuid = parcel_uuid.get_uuid(env)?; @@ -813,14 +814,14 @@ impl<'a> JScanResult<'a> { let mut services = Vec::new(); if !env.is_same_object(&services_obj, JObject::null())? { let size = env - .call_method(&services_obj, "size", "()I", &[])? + .call_method(&services_obj, jni_str!("size"), jni_sig!("()I"), &[])? .i()?; for i in 0..size { let obj = env .call_method( &services_obj, - "get", - "(I)Ljava/lang/Object;", + jni_str!("get"), + jni_sig!("(I)Ljava/lang/Object;"), &[jni::objects::JValue::from(i)], )? .l()?; @@ -873,18 +874,18 @@ impl<'a> ::std::ops::Deref for JScanRecord<'a> { impl<'a> JScanRecord<'a> { pub fn from_env(env: &mut Env<'a>, obj: JObject<'a>) -> Result { - let class = env.find_class("android/bluetooth/le/ScanRecord")?; + let class = env.find_class(jni_str!("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_device_name = env.get_method_id(&class, jni_str!("getDeviceName"), jni_sig!("()Ljava/lang/String;"))?; + let get_tx_power_level = env.get_method_id(&class, jni_str!("getTxPowerLevel"), jni_sig!("()I"))?; let get_manufacturer_specific_data = env.get_method_id( &class, - "getManufacturerSpecificData", - "()Landroid/util/SparseArray;", + jni_str!("getManufacturerSpecificData"), + jni_sig!("()Landroid/util/SparseArray;"), )?; - let get_service_data = env.get_method_id(&class, "getServiceData", "()Ljava/util/Map;")?; + let get_service_data = env.get_method_id(&class, jni_str!("getServiceData"), jni_sig!("()Ljava/util/Map;"))?; let get_service_uuids = - env.get_method_id(&class, "getServiceUuids", "()Ljava/util/List;")?; + env.get_method_id(&class, jni_str!("getServiceUuids"), jni_sig!("()Ljava/util/List;"))?; Ok(Self { internal: obj, get_device_name, @@ -983,11 +984,11 @@ impl<'a> ::std::ops::Deref for JSparseArray<'a> { impl<'a> JSparseArray<'a> { pub fn from_env(env: &mut Env<'a>, obj: JObject<'a>) -> Result { - let class = env.find_class("android/util/SparseArray")?; + let class = env.find_class(jni_str!("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;")?; + let size = env.get_method_id(&class, jni_str!("size"), jni_sig!("()I"))?; + let key_at = env.get_method_id(&class, jni_str!("keyAt"), jni_sig!("(I)I"))?; + let value_at = env.get_method_id(&class, jni_str!("valueAt"), jni_sig!("(I)Ljava/lang/Object;"))?; Ok(Self { internal: obj, size, @@ -1040,9 +1041,9 @@ pub struct JParcelUuid<'a> { impl<'a> JParcelUuid<'a> { pub fn from_env(env: &mut Env<'a>, obj: JObject<'a>) -> Result { - let class = env.find_class("android/os/ParcelUuid")?; + let class = env.find_class(jni_str!("android/os/ParcelUuid"))?; - let get_uuid = env.get_method_id(&class, "getUuid", "()Ljava/util/UUID;")?; + let get_uuid = env.get_method_id(&class, jni_str!("getUuid"), jni_sig!("()Ljava/util/UUID;"))?; Ok(Self { internal: obj, get_uuid, diff --git a/src/droidplug/jni_utils/classcache.rs b/src/droidplug/jni_utils/classcache.rs index 39732870..9aa35eba 100644 --- a/src/droidplug/jni_utils/classcache.rs +++ b/src/droidplug/jni_utils/classcache.rs @@ -1,5 +1,5 @@ use dashmap::DashMap; -use jni::{Env, errors::Result, objects::{Global, JObject}}; +use jni::{Env, errors::Result, objects::{Global, JObject}, strings::JNIString}; use once_cell::sync::OnceCell; use std::sync::Arc; @@ -7,7 +7,8 @@ static CLASSCACHE: OnceCell>>>> = On pub fn find_add_class(env: &mut Env, classname: &str) -> Result<()> { let cache = CLASSCACHE.get_or_init(|| DashMap::new()); - let cls = env.find_class(classname)?; + let jni_name = JNIString::from(classname); + let cls = env.find_class(&jni_name)?; let cls_obj: JObject = cls.into(); let global = env.new_global_ref(&cls_obj)?; cache.insert(classname.to_owned(), Arc::new(global)); diff --git a/src/droidplug/jni_utils/exceptions.rs b/src/droidplug/jni_utils/exceptions.rs index f286a209..9391d648 100644 --- a/src/droidplug/jni_utils/exceptions.rs +++ b/src/droidplug/jni_utils/exceptions.rs @@ -2,6 +2,7 @@ use jni::{ Env, descriptors::Desc, errors::Error, + jni_str, jni_sig, objects::{JClass, JObject, JThrowable}, }; use std::{ @@ -112,11 +113,11 @@ impl<'a> JPanicException<'a> { }; let obj = env.new_object( - "io/github/gedgygedgy/rust/panic/PanicException", - "(Ljava/lang/String;)V", + jni_str!("io/github/gedgygedgy/rust/panic/PanicException"), + jni_sig!("(Ljava/lang/String;)V"), &[(&msg).into()], )?; - unsafe { env.set_rust_field(&obj, "any", any) }?; + unsafe { env.set_rust_field(&obj, jni_str!("any"), any) }?; Ok(Self { internal: obj.into(), }) @@ -126,11 +127,11 @@ impl<'a> JPanicException<'a> { &self, env: &'b mut Env, ) -> Result>, Error> { - unsafe { env.get_rust_field(&self.internal, "any") } + unsafe { env.get_rust_field(&self.internal, jni_str!("any")) } } pub fn take(&self, env: &mut Env) -> Result, Error> { - unsafe { env.take_rust_field(&self.internal, "any") } + unsafe { env.take_rust_field(&self.internal, jni_str!("any")) } } pub fn resume_unwind(&self, env: &mut Env) -> Result<(), Error> { @@ -171,8 +172,8 @@ pub fn throw_panic( if let Some(old_ex) = old_ex { env.call_method( &*ex, - "addSuppressed", - "(Ljava/lang/Throwable;)V", + jni_str!("addSuppressed"), + jni_sig!("(Ljava/lang/Throwable;)V"), &[(&old_ex).into()], )?; } @@ -192,7 +193,7 @@ pub fn throw_unwind( #[cfg(test)] mod test { - use jni::{Env, errors::Error, objects::{JObject, JThrowable}}; + use jni::{Env, errors::Error, jni_str, jni_sig, objects::{JObject, JThrowable}, strings::JNIString}; use super::super::test_utils; use super::try_block; @@ -211,14 +212,14 @@ mod test { None }; let illegal_argument_exception = env - .find_class("java/lang/IllegalArgumentException") + .find_class(jni_str!("java/lang/IllegalArgumentException")) .unwrap(); if let Some(ref ex) = old_ex { env.throw(ex).unwrap(); } let ex = throw_class.map(|c| { - let obj = env.new_object(c, "()V", &[]).unwrap(); + let obj = env.new_object(JNIString::from(c), jni_sig!("()V"), &[]).unwrap(); JThrowable::from(obj) }); @@ -235,7 +236,7 @@ mod test { }) .catch( env, - "java/lang/ArrayIndexOutOfBoundsException", + jni_str!("java/lang/ArrayIndexOutOfBoundsException"), |env, caught| { assert!(!env.exception_check().unwrap()); assert!(env.is_same_object(&caught, ex.as_ref().unwrap()).unwrap()); @@ -248,7 +249,7 @@ mod test { ) .catch( env, - "java/lang/IndexOutOfBoundsException", + jni_str!("java/lang/IndexOutOfBoundsException"), |env, caught| { assert!(!env.exception_check().unwrap()); assert!(env.is_same_object(&caught, ex.as_ref().unwrap()).unwrap()); @@ -262,7 +263,7 @@ mod test { ) .catch( env, - "java/lang/StringIndexOutOfBoundsException", + jni_str!("java/lang/StringIndexOutOfBoundsException"), |env, caught| { assert!(!env.exception_check().unwrap()); assert!(env.is_same_object(&caught, ex.as_ref().unwrap()).unwrap()); @@ -351,7 +352,7 @@ mod test { let ex = env.exception_occurred().unwrap(); env.exception_clear().unwrap(); assert!( - env.is_instance_of(&ex, "java/lang/SecurityException") + env.is_instance_of(&ex, jni_str!("java/lang/SecurityException")) .unwrap() ); } else { @@ -393,7 +394,7 @@ mod test { test_utils::JVM_ENV.with(|cell| { let env = &mut *cell.borrow_mut(); let ex = JThrowable::from( - env.new_object("java/lang/IllegalArgumentException", "()V", &[]) + env.new_object(jni_str!("java/lang/IllegalArgumentException"), jni_sig!("()V"), &[]) .unwrap(), ); env.throw(&ex).unwrap(); @@ -425,7 +426,7 @@ mod test { let ex = env.exception_occurred().unwrap(); env.exception_clear().unwrap(); assert!( - env.is_instance_of(&ex, "java/lang/StringIndexOutOfBoundsException") + env.is_instance_of(&ex, jni_str!("java/lang/StringIndexOutOfBoundsException")) .unwrap() ); } else { @@ -469,7 +470,7 @@ mod test { } let msg: JString = env - .call_method(&*ex, "getMessage", "()Ljava/lang/String;", &[]) + .call_method(&*ex, jni_str!("getMessage"), jni_sig!("()Ljava/lang/String;"), &[]) .unwrap() .l() .unwrap() @@ -496,7 +497,7 @@ mod test { } let msg: JString = env - .call_method(&*ex, "getMessage", "()Ljava/lang/String;", &[]) + .call_method(&*ex, jni_str!("getMessage"), jni_sig!("()Ljava/lang/String;"), &[]) .unwrap() .l() .unwrap() @@ -525,7 +526,7 @@ mod test { } let msg = env - .call_method(&*ex, "getMessage", "()Ljava/lang/String;", &[]) + .call_method(&*ex, jni_str!("getMessage"), jni_sig!("()Ljava/lang/String;"), &[]) .unwrap() .l() .unwrap(); @@ -557,12 +558,12 @@ mod test { let ex = env.exception_occurred().unwrap(); env.exception_clear().unwrap(); 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(); @@ -583,7 +584,7 @@ mod test { test_utils::JVM_ENV.with(|cell| { let env = &mut *cell.borrow_mut(); let old_ex = - JThrowable::from(env.new_object("java/lang/Exception", "()V", &[]).unwrap()); + JThrowable::from(env.new_object(jni_str!("java/lang/Exception"), jni_sig!("()V"), &[]).unwrap()); env.throw(&old_ex).unwrap(); super::throw_unwind(env, || panic!("This is a panic")) @@ -593,12 +594,12 @@ mod test { let ex = env.exception_occurred().unwrap(); env.exception_clear().unwrap(); 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(); diff --git a/src/droidplug/jni_utils/future.rs b/src/droidplug/jni_utils/future.rs index c1b854dd..5dfb93ea 100644 --- a/src/droidplug/jni_utils/future.rs +++ b/src/droidplug/jni_utils/future.rs @@ -1,6 +1,7 @@ use ::jni::{ Env, JavaVM, errors::Result, + jni_sig, jni_str, objects::{Global, JClass, JMethodID, JObject}, signature::ReturnType, sys::jvalue, @@ -23,8 +24,8 @@ impl<'a> JFuture<'a> { super::classcache::get_class("io/github/gedgygedgy/rust/future/Future").unwrap(); let poll_id = env.get_method_id( <&JClass>::from(class.as_obj()), - "poll", - "(Lio/github/gedgygedgy/rust/task/Waker;)Lio/github/gedgygedgy/rust/task/PollResult;", + jni_str!("poll"), + jni_sig!("(Lio/github/gedgygedgy/rust/task/Waker;)Lio/github/gedgygedgy/rust/task/PollResult;"), )?; Ok(Self { internal: obj, @@ -82,8 +83,8 @@ impl JSendFuture { super::classcache::get_class("io/github/gedgygedgy/rust/future/Future").unwrap(); let poll_id = env.get_method_id( <&JClass>::from(class.as_obj()), - "poll", - "(Lio/github/gedgygedgy/rust/task/Waker;)Lio/github/gedgygedgy/rust/task/PollResult;", + jni_str!("poll"), + jni_sig!("(Lio/github/gedgygedgy/rust/task/Waker;)Lio/github/gedgygedgy/rust/task/PollResult;"), )?; Ok(Self { internal: env.new_global_ref(obj)?, @@ -139,6 +140,7 @@ assert_impl_all!(JSendFuture: Send); mod test { use super::super::test_utils; use super::{JFuture, JSendFuture}; + use jni::{jni_sig, jni_str}; use std::{ future::Future, pin::Pin, @@ -162,7 +164,7 @@ 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 future_local = env.new_local_ref(&future_obj).unwrap(); let jfuture = JFuture::from_env(env, future_local).unwrap(); @@ -180,11 +182,11 @@ mod test { assert_eq!(Arc::strong_count(&data), 3); assert_eq!(data.value(), false); - let obj = env.new_object("java/lang/Object", "()V", &[]).unwrap(); + let obj = env.new_object(jni_str!("java/lang/Object"), jni_sig!("()V"), &[]).unwrap(); env.call_method( &future_obj, - "wake", - "(Ljava/lang/Object;)V", + jni_str!("wake"), + jni_sig!("(Ljava/lang/Object;)V"), &[(&obj).into()], ) .unwrap(); @@ -228,13 +230,13 @@ mod test { let (future, future_obj_global, obj_global) = { let env = &mut *cell.borrow_mut(); 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_obj_global = env.new_global_ref(&future_obj).unwrap(); let future_local = env.new_local_ref(&future_obj).unwrap(); let jfuture = JFuture::from_env(env, future_local).unwrap(); let future = JSendFuture::new(env, &jfuture).unwrap(); - let obj = env.new_object("java/lang/Object", "()V", &[]).unwrap(); + let obj = env.new_object(jni_str!("java/lang/Object"), jni_sig!("()V"), &[]).unwrap(); let obj_global = env.new_global_ref(&obj).unwrap(); (future, future_obj_global, obj_global) }; @@ -247,8 +249,8 @@ mod test { let obj_local = env.new_local_ref(obj_global.as_obj()).unwrap(); env.call_method( &future_local, - "wake", - "(Ljava/lang/Object;)V", + jni_str!("wake"), + jni_sig!("(Ljava/lang/Object;)V"), &[(&obj_local).into()], ) .unwrap(); @@ -275,13 +277,13 @@ mod test { let (future, future_obj_global, ex_global) = { let env = &mut *cell.borrow_mut(); 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_obj_global = env.new_global_ref(&future_obj).unwrap(); let future_local = env.new_local_ref(&future_obj).unwrap(); let jfuture = JFuture::from_env(env, future_local).unwrap(); let future = JSendFuture::new(env, &jfuture).unwrap(); - let ex = env.new_object("java/lang/Exception", "()V", &[]).unwrap(); + let ex = env.new_object(jni_str!("java/lang/Exception"), jni_sig!("()V"), &[]).unwrap(); let ex_global = env.new_global_ref(&ex).unwrap(); (future, future_obj_global, ex_global) }; @@ -294,8 +296,8 @@ mod test { let ex_local = env.new_local_ref(ex_global.as_obj()).unwrap(); env.call_method( &future_local, - "wakeWithThrowable", - "(Ljava/lang/Throwable;)V", + jni_str!("wakeWithThrowable"), + jni_sig!("(Ljava/lang/Throwable;)V"), &[(&ex_local).into()], ) .unwrap(); @@ -312,7 +314,7 @@ mod test { let future_ex = env.exception_occurred().unwrap(); env.exception_clear().unwrap(); 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(); @@ -333,11 +335,11 @@ mod test { let (future, future_obj_global, obj_global) = { let env = &mut *cell.borrow_mut(); 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_obj_global = env.new_global_ref(&future_obj).unwrap(); let future = JSendFuture::from_env(env, &future_obj).unwrap(); - let obj = env.new_object("java/lang/Object", "()V", &[]).unwrap(); + let obj = env.new_object(jni_str!("java/lang/Object"), jni_sig!("()V"), &[]).unwrap(); let obj_global = env.new_global_ref(&obj).unwrap(); (future, future_obj_global, obj_global) }; @@ -350,8 +352,8 @@ mod test { let obj_local = env.new_local_ref(obj_global.as_obj()).unwrap(); env.call_method( &future_local, - "wake", - "(Ljava/lang/Object;)V", + jni_str!("wake"), + jni_sig!("(Ljava/lang/Object;)V"), &[(&obj_local).into()], ) .unwrap(); diff --git a/src/droidplug/jni_utils/mod.rs b/src/droidplug/jni_utils/mod.rs index 6334cce9..cbe02d2a 100644 --- a/src/droidplug/jni_utils/mod.rs +++ b/src/droidplug/jni_utils/mod.rs @@ -9,7 +9,7 @@ pub mod uuid; #[cfg(test)] pub(crate) mod test_utils { - use jni::{JNIEnv, JavaVM, objects::GlobalRef}; + use jni::{JNIEnv, JavaVM, jni_str, jni_sig, objects::GlobalRef}; use lazy_static::lazy_static; use std::{ cell::RefCell, @@ -32,24 +32,24 @@ pub(crate) mod test_utils { 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.find_class("io/github/gedgygedgy/rust/ops/FnAdapter")?; - env.register_native_methods( + let class = env.find_class(jni_str!("io/github/gedgygedgy/rust/ops/FnAdapter"))?; + unsafe { env.register_native_methods( &class, &[ NativeMethod { - name: "callInternal".into(), + name: jni_str!("callInternal").into(), sig: - "(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;" + jni_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(), + name: jni_str!("closeInternal").into(), + sig: jni_sig!("()V").into(), fn_ptr: super::ops::fn_adapter_close_internal as *mut c_void, }, ], - )?; + )? }; Ok(()) } @@ -95,9 +95,9 @@ pub(crate) mod test_utils { let thread = env .call_static_method( - "java/lang/Thread", - "currentThread", - "()Ljava/lang/Thread;", + jni_str!("java/lang/Thread"), + jni_str!("currentThread"), + jni_sig!("()Ljava/lang/Thread;"), &[], ) .unwrap() @@ -105,8 +105,8 @@ pub(crate) mod test_utils { .unwrap(); env.call_method( &thread, - "setContextClassLoader", - "(Ljava/lang/ClassLoader;)V", + jni_str!("setContextClassLoader"), + jni_sig!("(Ljava/lang/ClassLoader;)V"), &[(&JVM.class_loader).into()] ).unwrap(); @@ -141,9 +141,9 @@ pub(crate) mod test_utils { let thread = env .call_static_method( - "java/lang/Thread", - "currentThread", - "()Ljava/lang/Thread;", + jni_str!("java/lang/Thread"), + jni_str!("currentThread"), + jni_sig!("()Ljava/lang/Thread;"), &[], ) .unwrap() @@ -152,8 +152,8 @@ pub(crate) mod test_utils { let class_loader = env .call_method( &thread, - "getContextClassLoader", - "()Ljava/lang/ClassLoader;", + jni_str!("getContextClassLoader"), + jni_sig!("()Ljava/lang/ClassLoader;"), &[], ) .unwrap() diff --git a/src/droidplug/jni_utils/ops.rs b/src/droidplug/jni_utils/ops.rs index 311cd4db..4f5aa8b8 100644 --- a/src/droidplug/jni_utils/ops.rs +++ b/src/droidplug/jni_utils/ops.rs @@ -1,6 +1,7 @@ use ::jni::{ JNIEnv, errors::Result, + jni_sig, jni_str, objects::{JClass, JObject}, }; use std::sync::{Arc, Mutex}; @@ -34,7 +35,7 @@ macro_rules! define_fn_adapter { let class = super::classcache::get_class($ic).unwrap(); env.new_object( <&JClass>::from(class.as_obj()), - "(Lio/github/gedgygedgy/rust/ops/FnAdapter;)V", + jni_sig!("(Lio/github/gedgygedgy/rust/ops/FnAdapter;)V"), &[(&adapter).into()], ) } @@ -63,7 +64,7 @@ macro_rules! define_fn_adapter { let class = super::classcache::get_class($ic).unwrap(); env.new_object( <&JClass>::from(class.as_obj()), - "(Lio/github/gedgygedgy/rust/ops/FnAdapter;)V", + jni_sig!("(Lio/github/gedgygedgy/rust/ops/FnAdapter;)V"), &[(&adapter).into()], ) } @@ -93,7 +94,7 @@ macro_rules! define_fn_adapter { let class = super::classcache::get_class($ic).unwrap(); env.new_object( <&JClass>::from(class.as_obj()), - "(Lio/github/gedgygedgy/rust/ops/FnAdapter;)V", + jni_sig!("(Lio/github/gedgygedgy/rust/ops/FnAdapter;)V"), &[(&adapter).into()], ) } @@ -279,10 +280,10 @@ fn fn_adapter<'local>( let class = super::classcache::get_class("io/github/gedgygedgy/rust/ops/FnAdapter").unwrap(); let obj = env.new_object( <&JClass>::from(class.as_obj()), - "(Z)V", + jni_sig!("(Z)V"), &[local.into()], )?; - unsafe { env.set_rust_field::<_, _, FnWrapper>(&obj, "data", SendSyncWrapper(arc)) }?; + unsafe { env.set_rust_field::<_, _, FnWrapper>(&obj, jni_str!("data"), SendSyncWrapper(arc)) }?; Ok(obj) } @@ -296,7 +297,7 @@ pub(crate) extern "C" fn fn_adapter_call_internal<'local>( use std::panic::{AssertUnwindSafe, catch_unwind}; let arc = - if let Ok(f) = unsafe { env.get_rust_field::<_, _, FnWrapper>(&obj1, "data") } { + if let Ok(f) = unsafe { env.get_rust_field::<_, _, FnWrapper>(&obj1, jni_str!("data")) } { AssertUnwindSafe(f.0.clone()) } else { return JObject::null(); @@ -314,7 +315,7 @@ pub(crate) extern "C" fn fn_adapter_close_internal(mut env: JNIEnv, obj: JObject use std::panic::{AssertUnwindSafe, catch_unwind}; let result = catch_unwind(AssertUnwindSafe(|| { - let _ = unsafe { env.take_rust_field::<_, _, FnWrapper>(&obj, "data") }; + let _ = unsafe { env.take_rust_field::<_, _, FnWrapper>(&obj, jni_str!("data")) }; })); if let Err(panic) = result { let _ = super::exceptions::throw_panic(&mut env, panic); diff --git a/src/droidplug/jni_utils/stream.rs b/src/droidplug/jni_utils/stream.rs index 751a95e5..70399ccf 100644 --- a/src/droidplug/jni_utils/stream.rs +++ b/src/droidplug/jni_utils/stream.rs @@ -2,6 +2,7 @@ use super::task::JPollResult; use ::jni::{ Env, JavaVM, errors::Result, + jni_sig, jni_str, objects::{Global, JClass, JMethodID, JObject}, signature::ReturnType, sys::jvalue, @@ -24,8 +25,8 @@ impl<'a> JStream<'a> { super::classcache::get_class("io/github/gedgygedgy/rust/stream/Stream").unwrap(); let poll_next_id = env.get_method_id( <&JClass>::from(class.as_obj()), - "pollNext", - "(Lio/github/gedgygedgy/rust/task/Waker;)Lio/github/gedgygedgy/rust/task/PollResult;", + jni_str!("pollNext"), + jni_sig!("(Lio/github/gedgygedgy/rust/task/Waker;)Lio/github/gedgygedgy/rust/task/PollResult;"), )?; Ok(Self { internal: obj, @@ -87,8 +88,8 @@ impl JSendStream { super::classcache::get_class("io/github/gedgygedgy/rust/stream/Stream").unwrap(); let poll_next_id = env.get_method_id( <&JClass>::from(class.as_obj()), - "pollNext", - "(Lio/github/gedgygedgy/rust/task/Waker;)Lio/github/gedgygedgy/rust/task/PollResult;", + jni_str!("pollNext"), + jni_sig!("(Lio/github/gedgygedgy/rust/task/Waker;)Lio/github/gedgygedgy/rust/task/PollResult;"), )?; Ok(Self { internal: env.new_global_ref(obj)?, @@ -164,8 +165,8 @@ impl<'a> JStreamPoll<'a> { super::classcache::get_class("io/github/gedgygedgy/rust/stream/StreamPoll").unwrap(); let get = env.get_method_id( <&JClass>::from(class.as_obj()), - "get", - "()Ljava/lang/Object;", + jni_str!("get"), + jni_sig!("()Ljava/lang/Object;"), )?; Ok(Self { internal: obj, get }) } @@ -181,6 +182,7 @@ mod test { use super::super::test_utils; use super::{JSendStream, JStream}; use futures::stream::Stream; + use jni::{jni_sig, jni_str}; use std::{ pin::Pin, task::{Context, Poll}, @@ -202,7 +204,7 @@ 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 stream_local = env.new_local_ref(&stream_obj).unwrap(); let jstream = JStream::from_env(env, stream_local).unwrap(); @@ -216,11 +218,11 @@ mod test { assert_eq!(Arc::strong_count(&data), 3); assert_eq!(data.value(), false); - let obj1 = env.new_object("java/lang/Object", "()V", &[]).unwrap(); + let obj1 = env.new_object(jni_str!("java/lang/Object"), jni_sig!("()V"), &[]).unwrap(); env.call_method( &stream_obj, - "add", - "(Ljava/lang/Object;)V", + jni_str!("add"), + jni_sig!("(Ljava/lang/Object;)V"), &[(&obj1).into()], ) .unwrap(); @@ -228,11 +230,11 @@ mod test { assert_eq!(data.value(), true); data.set_value(false); - let obj2 = env.new_object("java/lang/Object", "()V", &[]).unwrap(); + let obj2 = env.new_object(jni_str!("java/lang/Object"), jni_sig!("()V"), &[]).unwrap(); env.call_method( &stream_obj, - "add", - "(Ljava/lang/Object;)V", + jni_str!("add"), + jni_sig!("(Ljava/lang/Object;)V"), &[(&obj2).into()], ) .unwrap(); @@ -265,7 +267,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); @@ -288,15 +290,15 @@ mod test { let (mut stream, stream_obj_global, obj1_global, obj2_global) = { let env = &mut *cell.borrow_mut(); 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_obj_global = env.new_global_ref(&stream_obj).unwrap(); let stream_local = env.new_local_ref(&stream_obj).unwrap(); let jstream = JStream::from_env(env, stream_local).unwrap(); let stream = JSendStream::new(env, &jstream).unwrap(); - let obj1 = env.new_object("java/lang/Object", "()V", &[]).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("java/lang/Object", "()V", &[]).unwrap(); + let obj2 = env.new_object(jni_str!("java/lang/Object"), jni_sig!("()V"), &[]).unwrap(); let obj2_global = env.new_global_ref(&obj2).unwrap(); (stream, stream_obj_global, obj1_global, obj2_global) }; @@ -310,19 +312,19 @@ mod test { let o2 = env.new_local_ref(obj2_global.as_obj()).unwrap(); env.call_method( &s, - "add", - "(Ljava/lang/Object;)V", + jni_str!("add"), + jni_sig!("(Ljava/lang/Object;)V"), &[(&o1).into()], ) .unwrap(); env.call_method( &s, - "add", - "(Ljava/lang/Object;)V", + jni_str!("add"), + jni_sig!("(Ljava/lang/Object;)V"), &[(&o2).into()], ) .unwrap(); - env.call_method(&s, "finish", "()V", &[]).unwrap(); + env.call_method(&s, jni_str!("finish"), jni_sig!("()V"), &[]).unwrap(); }, async { use futures::StreamExt; @@ -357,13 +359,13 @@ mod test { let (mut stream, stream_obj_global, obj1_global, obj2_global) = { let env = &mut *cell.borrow_mut(); 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_obj_global = env.new_global_ref(&stream_obj).unwrap(); let stream = JSendStream::from_env(env, &stream_obj).unwrap(); - let obj1 = env.new_object("java/lang/Object", "()V", &[]).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("java/lang/Object", "()V", &[]).unwrap(); + let obj2 = env.new_object(jni_str!("java/lang/Object"), jni_sig!("()V"), &[]).unwrap(); let obj2_global = env.new_global_ref(&obj2).unwrap(); (stream, stream_obj_global, obj1_global, obj2_global) }; @@ -377,19 +379,19 @@ mod test { let o2 = env.new_local_ref(obj2_global.as_obj()).unwrap(); env.call_method( &s, - "add", - "(Ljava/lang/Object;)V", + jni_str!("add"), + jni_sig!("(Ljava/lang/Object;)V"), &[(&o1).into()], ) .unwrap(); env.call_method( &s, - "add", - "(Ljava/lang/Object;)V", + jni_str!("add"), + jni_sig!("(Ljava/lang/Object;)V"), &[(&o2).into()], ) .unwrap(); - env.call_method(&s, "finish", "()V", &[]).unwrap(); + env.call_method(&s, jni_str!("finish"), jni_sig!("()V"), &[]).unwrap(); }, async { use futures::StreamExt; diff --git a/src/droidplug/jni_utils/task.rs b/src/droidplug/jni_utils/task.rs index 32339b6e..bda74312 100644 --- a/src/droidplug/jni_utils/task.rs +++ b/src/droidplug/jni_utils/task.rs @@ -1,6 +1,7 @@ use ::jni::{ Env, errors::Result, + jni_sig, jni_str, objects::{JClass, JMethodID, JObject}, signature::ReturnType, }; @@ -12,7 +13,7 @@ pub fn waker<'a>(env: &mut Env<'a>, waker: Waker) -> Result> { let class = super::classcache::get_class("io/github/gedgygedgy/rust/task/Waker").unwrap(); let obj = env.new_object( <&JClass>::from(class.as_obj()), - "(Lio/github/gedgygedgy/rust/ops/FnRunnable;)V", + jni_sig!("(Lio/github/gedgygedgy/rust/ops/FnRunnable;)V"), &[(&runnable).into()], )?; Ok(obj) @@ -27,7 +28,7 @@ impl<'a> JPollResult<'a> { pub fn from_env(env: &mut Env<'a>, obj: JObject<'a>) -> Result { let class = super::classcache::get_class("io/github/gedgygedgy/rust/task/PollResult").unwrap(); - let get = env.get_method_id(<&JClass>::from(class.as_obj()), "get", "()Ljava/lang/Object;")?; + let get = env.get_method_id(<&JClass>::from(class.as_obj()), jni_str!("get"), jni_sig!("()Ljava/lang/Object;"))?; Ok(Self { internal: obj, get }) } @@ -54,6 +55,7 @@ impl<'a> From> for JObject<'a> { #[cfg(test)] mod test { use super::super::test_utils; + use jni::{jni_sig, jni_str}; use std::sync::Arc; #[test] @@ -73,12 +75,12 @@ 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); }); @@ -101,11 +103,11 @@ 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); }); diff --git a/src/droidplug/jni_utils/uuid.rs b/src/droidplug/jni_utils/uuid.rs index caa05ff2..fd7dbaa0 100644 --- a/src/droidplug/jni_utils/uuid.rs +++ b/src/droidplug/jni_utils/uuid.rs @@ -1,6 +1,7 @@ use jni::{ Env, errors::Result, + jni_str, jni_sig, objects::{JMethodID, JObject}, signature::{Primitive, ReturnType}, sys::jlong, @@ -15,11 +16,11 @@ pub struct JUuid<'a> { impl<'a> JUuid<'a> { pub fn from_env(env: &mut Env<'a>, obj: JObject<'a>) -> Result { - let class = env.find_class("java/util/UUID")?; + let class = env.find_class(jni_str!("java/util/UUID"))?; let get_least_significant_bits = - env.get_method_id(&class, "getLeastSignificantBits", "()J")?; + env.get_method_id(&class, jni_str!("getLeastSignificantBits"), jni_sig!("()J"))?; let get_most_significant_bits = - env.get_method_id(&class, "getMostSignificantBits", "()J")?; + env.get_method_id(&class, jni_str!("getMostSignificantBits"), jni_sig!("()J"))?; Ok(Self { internal: obj, get_least_significant_bits, @@ -32,12 +33,12 @@ impl<'a> JUuid<'a> { let least = (val & 0xFFFFFFFFFFFFFFFF) as jlong; let most = ((val >> 64) & 0xFFFFFFFFFFFFFFFF) as jlong; - let class = env.find_class("java/util/UUID")?; - let obj = env.new_object(&class, "(JJ)V", &[most.into(), least.into()])?; + let class = env.find_class(jni_str!("java/util/UUID"))?; + let obj = env.new_object(&class, jni_sig!("(JJ)V"), &[most.into(), least.into()])?; let get_least_significant_bits = - env.get_method_id(&class, "getLeastSignificantBits", "()J")?; + env.get_method_id(&class, jni_str!("getLeastSignificantBits"), jni_sig!("()J"))?; let get_most_significant_bits = - env.get_method_id(&class, "getMostSignificantBits", "()J")?; + env.get_method_id(&class, jni_str!("getMostSignificantBits"), jni_sig!("()J"))?; Ok(Self { internal: obj, get_least_significant_bits, @@ -87,7 +88,7 @@ impl<'a> From> for JObject<'a> { 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 { @@ -121,12 +122,12 @@ 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(); @@ -145,7 +146,7 @@ mod test { 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(); diff --git a/src/droidplug/peripheral.rs b/src/droidplug/peripheral.rs index dc81c2fc..f8d6dc5f 100644 --- a/src/droidplug/peripheral.rs +++ b/src/droidplug/peripheral.rs @@ -15,7 +15,7 @@ use crate::{ use async_trait::async_trait; use futures::stream::Stream; use jni::{ - Env, + Env, jni_sig, jni_str, objects::{Global, JClass, JObject, JString, JValue}, }; #[cfg(feature = "serde")] @@ -67,7 +67,7 @@ fn get_poll_result<'a>( if env.is_instance_of(&ex, <&JClass>::from(future_exception_class.as_obj()))? { let cause = env - .call_method(&ex, "getCause", "()Ljava/lang/Throwable;", &[])? + .call_method(&ex, jni_str!("getCause"), jni_sig!("()Ljava/lang/Throwable;"), &[])? .l()?; let mut check = |name: &str| -> jni::errors::Result { @@ -97,9 +97,9 @@ fn get_poll_result<'a>( "com/nonpolynomial/btleplug/android/impl/NoBluetoothAdapterException", )? { Err(Error::NoAdapterAvailable) - } else if env.is_instance_of(&cause, "java/lang/RuntimeException")? { + } else if env.is_instance_of(&cause, jni_str!("java/lang/RuntimeException"))? { let msg = env - .call_method(&cause, "getMessage", "()Ljava/lang/String;", &[])? + .call_method(&cause, jni_str!("getMessage"), jni_sig!("()Ljava/lang/String;"), &[])? .l()?; let jstr: JString = msg.into(); let msgstr: String = env.get_string(&jstr)?.into(); @@ -243,7 +243,7 @@ impl api::Peripheral for Peripheral { 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, "intValue", "()I", &[])?.i()?; + 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(()) })?; @@ -274,13 +274,13 @@ impl api::Peripheral for Peripheral { use std::iter::FromIterator; let obj = get_poll_result(env, &result_ref)?; - let size = env.call_method(&obj, "size", "()I", &[])?.i()?; + 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 i in 0..size { let svc_obj = env - .call_method(&obj, "get", "(I)Ljava/lang/Object;", &[JValue::from(i)])? + .call_method(&obj, jni_str!("get"), jni_sig!("(I)Ljava/lang/Object;"), &[JValue::from(i)])? .l()?; let service = JBluetoothGattService::from_env(env, svc_obj)?; let mut characteristics = BTreeSet::::new(); @@ -414,7 +414,7 @@ impl api::Peripheral for Peripheral { let result_ref = future.await?; self.with_obj(|env, _obj| { let rssi_obj = get_poll_result(env, &result_ref)?; - let rssi_val = env.call_method(&rssi_obj, "intValue", "()I", &[])?.i()?; + let rssi_val = env.call_method(&rssi_obj, jni_str!("intValue"), jni_sig!("()I"), &[])?.i()?; Ok(rssi_val as i16) }) } From 9ed09ce9eb27498f555c605e7a70d23ef613e9e5 Mon Sep 17 00:00:00 2001 From: Kyle Machulis Date: Sun, 26 Apr 2026 20:04:11 -0700 Subject: [PATCH 07/16] refactor: Migrate to callback-based attach_current_thread MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace all deprecated get_env() and attach_current_thread_permanently() calls with jni 0.22's callback-based attach_current_thread(|env| {...}). Production code: adapter.rs (4 sites), peripheral.rs with_obj + notifications closure (2 sites), future.rs poll_internal (1 site), stream.rs poll_next_internal (1 site). Test infrastructure: Replace RefCell> thread-local with with_env(f) helper that wraps attach_current_thread. This is a fundamental rethink — 0.22's callback model means Env can't escape the closure, so the old pattern of storing env in a RefCell is dead. All 25 test call sites migrated from JVM_ENV.with(|cell|) to with_env(|env|). Block_on/join tests use nested with_env calls for independent env access. Co-Authored-By: Claude Opus 4.6 --- src/droidplug/adapter.rs | 53 +++--- src/droidplug/jni_utils/arrays.rs | 12 +- src/droidplug/jni_utils/exceptions.rs | 105 ++++++------ src/droidplug/jni_utils/future.rs | 202 +++++++++++------------ src/droidplug/jni_utils/mod.rs | 106 ++++++------ src/droidplug/jni_utils/stream.rs | 225 +++++++++++++------------- src/droidplug/jni_utils/task.rs | 14 +- src/droidplug/jni_utils/uuid.rs | 12 +- src/droidplug/peripheral.rs | 50 +++--- 9 files changed, 392 insertions(+), 387 deletions(-) diff --git a/src/droidplug/adapter.rs b/src/droidplug/adapter.rs index 2befcf2c..0bbf4f7d 100644 --- a/src/droidplug/adapter.rs +++ b/src/droidplug/adapter.rs @@ -40,21 +40,21 @@ impl Debug for Adapter { impl Adapter { pub(crate) fn new() -> Result { - let mut env = global_jvm().get_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) + global_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<'a>( @@ -87,11 +87,12 @@ impl Adapter { } fn add(&self, address: BDAddr) -> Result { - let mut env = global_jvm().get_env()?; - let local_adapter = env.new_local_ref(self.internal.as_obj())?; - let peripheral = Peripheral::new(&mut env, local_adapter, address)?; - self.manager.add_peripheral(peripheral.clone()); - Ok(peripheral) + global_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( @@ -135,8 +136,8 @@ impl Central for Adapter { } async fn start_scan(&self, filter: ScanFilter) -> Result<()> { - let mut env = global_jvm().get_env()?; - let filter = JScanFilter::new(&mut env, filter)?; + global_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(), @@ -170,12 +171,14 @@ impl Central for Adapter { } Err(e) => Err(e.into()), } + }) } async fn stop_scan(&self) -> Result<()> { - let mut env = global_jvm().get_env()?; - env.call_method(self.internal.as_obj(), jni_str!("stopScan"), jni_sig!("()V"), &[])?; - Ok(()) + global_jvm().attach_current_thread(|env| { + env.call_method(self.internal.as_obj(), jni_str!("stopScan"), jni_sig!("()V"), &[])?; + Ok(()) + }) } async fn peripherals(&self) -> Result> { diff --git a/src/droidplug/jni_utils/arrays.rs b/src/droidplug/jni_utils/arrays.rs index 71918916..a523c9a4 100644 --- a/src/droidplug/jni_utils/arrays.rs +++ b/src/droidplug/jni_utils/arrays.rs @@ -30,27 +30,27 @@ mod test { #[test] fn test_slice_to_byte_array() { - test_utils::JVM_ENV.with(|cell| { - let env = &mut *cell.borrow_mut(); + 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); let mut bytes = [0i8; 5]; env.get_byte_array_region(&obj, 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(|cell| { - let env = &mut *cell.borrow_mut(); + 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(); 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/exceptions.rs b/src/droidplug/jni_utils/exceptions.rs index 9391d648..9f1393ef 100644 --- a/src/droidplug/jni_utils/exceptions.rs +++ b/src/droidplug/jni_utils/exceptions.rs @@ -275,8 +275,7 @@ mod test { #[test] fn test_catch_first() { - test_utils::JVM_ENV.with(|cell| { - let env = &mut *cell.borrow_mut(); + test_utils::with_env(|env| { assert_eq!( test_catch( env, @@ -288,13 +287,13 @@ mod test { 1 ); assert!(!env.exception_check().unwrap()); - }); + Ok(()) + }).unwrap(); } #[test] fn test_catch_second() { - test_utils::JVM_ENV.with(|cell| { - let env = &mut *cell.borrow_mut(); + test_utils::with_env(|env| { assert_eq!( test_catch( env, @@ -306,13 +305,13 @@ mod test { 2 ); assert!(!env.exception_check().unwrap()); - }); + Ok(()) + }).unwrap(); } #[test] fn test_catch_third() { - test_utils::JVM_ENV.with(|cell| { - let env = &mut *cell.borrow_mut(); + test_utils::with_env(|env| { assert_eq!( test_catch( env, @@ -324,22 +323,22 @@ mod test { 3 ); assert!(!env.exception_check().unwrap()); - }); + Ok(()) + }).unwrap(); } #[test] fn test_catch_ok() { - test_utils::JVM_ENV.with(|cell| { - let env = &mut *cell.borrow_mut(); + test_utils::with_env(|env| { assert_eq!(test_catch(env, None, Ok(0), false).unwrap(), 0); assert!(!env.exception_check().unwrap()); - }); + Ok(()) + }).unwrap(); } #[test] fn test_catch_none() { - test_utils::JVM_ENV.with(|cell| { - let env = &mut *cell.borrow_mut(); + test_utils::with_env(|env| { if let Error::JavaException = test_catch( env, Some("java/lang/SecurityException"), @@ -358,13 +357,13 @@ mod test { } else { panic!("No JavaException"); } - }); + Ok(()) + }).unwrap(); } #[test] fn test_catch_other() { - test_utils::JVM_ENV.with(|cell| { - let env = &mut *cell.borrow_mut(); + test_utils::with_env(|env| { if let Error::InvalidCtorReturn = test_catch(env, None, Err(Error::InvalidCtorReturn), false).unwrap_err() { @@ -372,13 +371,13 @@ mod test { } else { panic!("InvalidCtorReturn not found"); } - }); + Ok(()) + }).unwrap(); } #[test] fn test_catch_bogus_exception() { - test_utils::JVM_ENV.with(|cell| { - let env = &mut *cell.borrow_mut(); + test_utils::with_env(|env| { if let Error::JavaException = test_catch(env, None, Err(Error::JavaException), false).unwrap_err() { @@ -386,13 +385,13 @@ mod test { } else { panic!("JavaException not found"); } - }); + Ok(()) + }).unwrap(); } #[test] fn test_catch_prior_exception() { - test_utils::JVM_ENV.with(|cell| { - let env = &mut *cell.borrow_mut(); + test_utils::with_env(|env| { let ex = JThrowable::from( env.new_object(jni_str!("java/lang/IllegalArgumentException"), jni_sig!("()V"), &[]) .unwrap(), @@ -407,13 +406,13 @@ mod test { } else { panic!("JavaException not found"); } - }); + Ok(()) + }).unwrap(); } #[test] fn test_catch_rethrow() { - test_utils::JVM_ENV.with(|cell| { - let env = &mut *cell.borrow_mut(); + test_utils::with_env(|env| { if let Error::JavaException = test_catch( env, Some("java/lang/StringIndexOutOfBoundsException"), @@ -432,13 +431,13 @@ mod test { } else { panic!("JavaException not found"); } - }); + Ok(()) + }).unwrap(); } #[test] fn test_catch_bogus_rethrow() { - test_utils::JVM_ENV.with(|cell| { - let env = &mut *cell.borrow_mut(); + test_utils::with_env(|env| { if let Error::JavaException = test_catch( env, Some("java/lang/ArrayIndexOutOfBoundsException"), @@ -451,14 +450,13 @@ mod test { } else { panic!("JavaException not found"); } - }); + Ok(()) + }).unwrap(); } #[test] fn test_panic_exception_static_str() { - test_utils::JVM_ENV.with(|cell| { - let mut guard = cell.borrow_mut(); - let env = &mut *guard; + test_utils::with_env(|env| { use jni::objects::JString; const STATIC_MSG: &str = "This is a &'static str"; @@ -477,14 +475,13 @@ mod test { .into(); let str = env.get_string(&msg).unwrap(); assert_eq!(>::from(str), STATIC_MSG); - }); + Ok(()) + }).unwrap(); } #[test] fn test_panic_exception_string() { - test_utils::JVM_ENV.with(|cell| { - let mut guard = cell.borrow_mut(); - let env = &mut *guard; + test_utils::with_env(|env| { use jni::objects::JString; use std::any::Any; @@ -507,14 +504,13 @@ mod test { 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(|cell| { - let mut guard = cell.borrow_mut(); - let env = &mut *guard; + test_utils::with_env(|env| { use jni::objects::JObject; use std::any::Any; @@ -534,23 +530,23 @@ mod test { 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(|cell| { - let env = &mut *cell.borrow_mut(); + test_utils::with_env(|env| { let result = super::throw_unwind(env, || 42).unwrap(); assert_eq!(result, 42); assert!(!env.exception_check().unwrap()); - }); + Ok(()) + }).unwrap(); } #[test] fn test_throw_unwind_panic() { - test_utils::JVM_ENV.with(|cell| { - let env = &mut *cell.borrow_mut(); + test_utils::with_env(|env| { super::throw_unwind(env, || panic!("This is a panic")) .unwrap_err() .unwrap(); @@ -576,13 +572,13 @@ mod test { 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(|cell| { - let env = &mut *cell.borrow_mut(); + test_utils::with_env(|env| { let old_ex = JThrowable::from(env.new_object(jni_str!("java/lang/Exception"), jni_sig!("()V"), &[]).unwrap()); env.throw(&old_ex).unwrap(); @@ -614,16 +610,17 @@ mod test { 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(|cell| { - let env = &mut *cell.borrow_mut(); + test_utils::with_env(|env| { let ex = super::JPanicException::new(env, Box::new("This is a panic")).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 5dfb93ea..f3960576 100644 --- a/src/droidplug/jni_utils/future.rs +++ b/src/droidplug/jni_utils/future.rs @@ -94,23 +94,24 @@ impl JSendFuture { } fn poll_internal(&self, context: &mut Context<'_>) -> Result>>>> { - let mut env = self.vm.get_env()?; - let jwaker = super::task::waker(&mut env, context.waker().clone())?; - let result = unsafe { - env.call_method_unchecked( - self.internal.as_obj(), - self.poll_id, - ReturnType::Object, - &[jvalue { - l: jwaker.as_raw(), - }], - ) - }? - .l()?; - Ok(if env.is_same_object(&result, JObject::null())? { - Poll::Pending - } else { - Poll::Ready(Ok(env.new_global_ref(result)?)) + self.vm.attach_current_thread(|env| { + let jwaker = super::task::waker(env, context.waker().clone())?; + let result = unsafe { + env.call_method_unchecked( + self.internal.as_obj(), + self.poll_id, + ReturnType::Object, + &[jvalue { + l: jwaker.as_raw(), + }], + ) + }? + .l()?; + Ok(if env.is_same_object(&result, JObject::null())? { + Poll::Pending + } else { + Poll::Ready(Ok(env.new_global_ref(result)?)) + }) }) } } @@ -152,9 +153,7 @@ mod test { use super::super::task::JPollResult; use std::sync::Arc; - test_utils::JVM_ENV.with(|cell| { - let env = &mut *cell.borrow_mut(); - + 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); @@ -218,7 +217,9 @@ mod test { } assert_eq!(Arc::strong_count(&data), 2); assert_eq!(data.value(), true); - }); + + Ok(()) + }).unwrap(); } #[test] @@ -226,25 +227,23 @@ mod test { use super::super::task::JPollResult; use futures::{executor::block_on, join}; - test_utils::JVM_ENV.with(|cell| { - let (future, future_obj_global, obj_global) = { - let env = &mut *cell.borrow_mut(); - let future_obj = env - .new_object(jni_str!("io/github/gedgygedgy/rust/future/SimpleFuture"), jni_sig!("()V"), &[]) - .unwrap(); - let future_obj_global = env.new_global_ref(&future_obj).unwrap(); - let future_local = env.new_local_ref(&future_obj).unwrap(); - let jfuture = JFuture::from_env(env, 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(); - (future, future_obj_global, obj_global) - }; - - block_on(async { - join!( - async { - let env = &mut *cell.borrow_mut(); + let (future, future_obj_global, obj_global) = test_utils::with_env(|env| { + let future_obj = env + .new_object(jni_str!("io/github/gedgygedgy/rust/future/SimpleFuture"), jni_sig!("()V"), &[]) + .unwrap(); + let future_obj_global = env.new_global_ref(&future_obj).unwrap(); + let future_local = env.new_local_ref(&future_obj).unwrap(); + let jfuture = JFuture::from_env(env, 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( @@ -254,18 +253,21 @@ mod test { &[(&obj_local).into()], ) .unwrap(); - }, - async { - let global = future.await.unwrap(); - let env = &mut *cell.borrow_mut(); + 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 = JPollResult::from_env(env, 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(); + } + ); }); } @@ -273,25 +275,23 @@ mod test { fn test_jfuture_await_throw() { use futures::{executor::block_on, join}; - test_utils::JVM_ENV.with(|cell| { - let (future, future_obj_global, ex_global) = { - let env = &mut *cell.borrow_mut(); - let future_obj = env - .new_object(jni_str!("io/github/gedgygedgy/rust/future/SimpleFuture"), jni_sig!("()V"), &[]) - .unwrap(); - let future_obj_global = env.new_global_ref(&future_obj).unwrap(); - let future_local = env.new_local_ref(&future_obj).unwrap(); - let jfuture = JFuture::from_env(env, 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(); - (future, future_obj_global, ex_global) - }; - - block_on(async { - join!( - async { - let env = &mut *cell.borrow_mut(); + let (future, future_obj_global, ex_global) = test_utils::with_env(|env| { + let future_obj = env + .new_object(jni_str!("io/github/gedgygedgy/rust/future/SimpleFuture"), jni_sig!("()V"), &[]) + .unwrap(); + let future_obj_global = env.new_global_ref(&future_obj).unwrap(); + let future_local = env.new_local_ref(&future_obj).unwrap(); + let jfuture = JFuture::from_env(env, 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( @@ -301,12 +301,14 @@ mod test { &[(&ex_local).into()], ) .unwrap(); - }, - async { - use super::super::task::JPollResult; - - let global = future.await.unwrap(); - let env = &mut *cell.borrow_mut(); + 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 = JPollResult::from_env(env, local).unwrap(); let _err = poll_result.get(env).unwrap_err(); @@ -320,9 +322,10 @@ mod test { .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(); + } + ); }); } @@ -331,23 +334,21 @@ mod test { use super::super::task::JPollResult; use futures::{executor::block_on, join}; - test_utils::JVM_ENV.with(|cell| { - let (future, future_obj_global, obj_global) = { - let env = &mut *cell.borrow_mut(); - let future_obj = env - .new_object(jni_str!("io/github/gedgygedgy/rust/future/SimpleFuture"), jni_sig!("()V"), &[]) - .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(); - (future, future_obj_global, obj_global) - }; - - block_on(async { - join!( - async { - let env = &mut *cell.borrow_mut(); + let (future, future_obj_global, obj_global) = test_utils::with_env(|env| { + let future_obj = env + .new_object(jni_str!("io/github/gedgygedgy/rust/future/SimpleFuture"), jni_sig!("()V"), &[]) + .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( @@ -357,18 +358,21 @@ mod test { &[(&obj_local).into()], ) .unwrap(); - }, - async { - let global_ref = future.await.unwrap(); - let env = &mut *cell.borrow_mut(); + 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 = JPollResult::from_env(env, 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 cbe02d2a..e7641a35 100644 --- a/src/droidplug/jni_utils/mod.rs +++ b/src/droidplug/jni_utils/mod.rs @@ -9,17 +9,17 @@ pub mod uuid; #[cfg(test)] pub(crate) mod test_utils { - use jni::{JNIEnv, JavaVM, jni_str, jni_sig, objects::GlobalRef}; + use jni::{Env, JavaVM, jni_str, jni_sig, objects::Global, objects::JObject}; use lazy_static::lazy_static; use std::{ - cell::RefCell, + cell::Cell, sync::{Arc, Mutex}, task::{Wake, Waker}, }; use jni::NativeMethod; - fn test_init(env: &mut JNIEnv) -> jni::errors::Result<()> { + fn test_init(env: &mut Env) -> 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")?; @@ -86,32 +86,37 @@ pub(crate) mod test_utils { struct GlobalJVM { jvm: JavaVM, - class_loader: GlobalRef, + class_loader: Global>, } thread_local! { - pub static JVM_ENV: RefCell> = { - let mut env = JVM.jvm.attach_current_thread_permanently().unwrap(); - - let thread = env - .call_static_method( - jni_str!("java/lang/Thread"), - jni_str!("currentThread"), - jni_sig!("()Ljava/lang/Thread;"), - &[], - ) - .unwrap() - .l() - .unwrap(); - env.call_method( - &thread, - jni_str!("setContextClassLoader"), - jni_sig!("(Ljava/lang/ClassLoader;)V"), - &[(&JVM.class_loader).into()] - ).unwrap(); - - RefCell::new(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! { @@ -136,30 +141,31 @@ pub(crate) mod test_utils { .unwrap(); let jvm = JavaVM::new(jvm_args).unwrap(); - let mut env = jvm.attach_current_thread_permanently().unwrap(); - test_init(&mut 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(); - 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/stream.rs b/src/droidplug/jni_utils/stream.rs index 70399ccf..0352bc85 100644 --- a/src/droidplug/jni_utils/stream.rs +++ b/src/droidplug/jni_utils/stream.rs @@ -102,34 +102,35 @@ impl JSendStream { &self, context: &mut Context<'_>, ) -> Result>>>>> { - let mut env = self.vm.get_env()?; - let jwaker = super::task::waker(&mut env, context.waker().clone())?; - let result = unsafe { - env.call_method_unchecked( - self.internal.as_obj(), - self.poll_next_id, - ReturnType::Object, - &[jvalue { - l: jwaker.as_raw(), - }], - ) - }? - .l()?; - - if env.is_same_object(&result, JObject::null())? { - return Ok(Poll::Pending); - } + self.vm.attach_current_thread(|env| { + let jwaker = super::task::waker(env, context.waker().clone())?; + let result = unsafe { + env.call_method_unchecked( + self.internal.as_obj(), + self.poll_next_id, + ReturnType::Object, + &[jvalue { + l: jwaker.as_raw(), + }], + ) + }? + .l()?; + + if env.is_same_object(&result, JObject::null())? { + return Ok(Poll::Pending); + } - let poll_result = JPollResult::from_env(&mut env, result)?; - let stream_poll_obj = poll_result.get(&mut env)?; + let poll_result = JPollResult::from_env(env, result)?; + let stream_poll_obj = poll_result.get(env)?; - if env.is_same_object(&stream_poll_obj, JObject::null())? { - return Ok(Poll::Ready(None)); - } + if env.is_same_object(&stream_poll_obj, JObject::null())? { + return Ok(Poll::Ready(None)); + } - let stream_poll = JStreamPoll::from_env(&mut env, stream_poll_obj)?; - let obj = stream_poll.get(&mut env)?; - Ok(Poll::Ready(Some(Ok(env.new_global_ref(obj)?)))) + let stream_poll = JStreamPoll::from_env(env, stream_poll_obj)?; + let obj = stream_poll.get(env)?; + Ok(Poll::Ready(Some(Ok(env.new_global_ref(obj)?)))) + }) } } @@ -192,9 +193,7 @@ mod test { fn test_jstream() { use std::sync::Arc; - test_utils::JVM_ENV.with(|cell| { - let env = &mut *cell.borrow_mut(); - + 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); @@ -279,34 +278,34 @@ 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(|cell| { - let (mut stream, stream_obj_global, obj1_global, obj2_global) = { - let env = &mut *cell.borrow_mut(); - let stream_obj = env - .new_object(jni_str!("io/github/gedgygedgy/rust/stream/QueueStream"), jni_sig!("()V"), &[]) - .unwrap(); - let stream_obj_global = env.new_global_ref(&stream_obj).unwrap(); - let stream_local = env.new_local_ref(&stream_obj).unwrap(); - let jstream = JStream::from_env(env, 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(); - (stream, stream_obj_global, obj1_global, obj2_global) - }; - - block_on(async { - join!( - async { - let env = &mut *cell.borrow_mut(); + let (mut stream, stream_obj_global, obj1_global, obj2_global) = test_utils::with_env(|env| { + let stream_obj = env + .new_object(jni_str!("io/github/gedgygedgy/rust/stream/QueueStream"), jni_sig!("()V"), &[]) + .unwrap(); + let stream_obj_global = env.new_global_ref(&stream_obj).unwrap(); + let stream_local = env.new_local_ref(&stream_obj).unwrap(); + let jstream = JStream::from_env(env, 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(); @@ -325,29 +324,28 @@ mod test { ) .unwrap(); env.call_method(&s, jni_str!("finish"), jni_sig!("()V"), &[]).unwrap(); - }, - async { - use futures::StreamExt; - let g1 = stream.next().await.unwrap().unwrap(); - { - let mut guard = cell.borrow_mut(); - let env = &mut *guard; - let o1 = env.new_local_ref(obj1_global.as_obj()).unwrap(); - assert!(env.is_same_object(g1.as_obj(), &o1).unwrap()); - } - - let g2 = stream.next().await.unwrap().unwrap(); - { - let mut guard = cell.borrow_mut(); - let env = &mut *guard; - let o2 = env.new_local_ref(obj2_global.as_obj()).unwrap(); - assert!(env.is_same_object(g2.as_obj(), &o2).unwrap()); - } - - assert!(stream.next().await.is_none()); - } - ); - }); + 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()); + } + ); }); } @@ -355,25 +353,23 @@ mod test { fn test_jsendstream_await() { use futures::{executor::block_on, join}; - test_utils::JVM_ENV.with(|cell| { - let (mut stream, stream_obj_global, obj1_global, obj2_global) = { - let env = &mut *cell.borrow_mut(); - let stream_obj = env - .new_object(jni_str!("io/github/gedgygedgy/rust/stream/QueueStream"), jni_sig!("()V"), &[]) - .unwrap(); - 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(); - (stream, stream_obj_global, obj1_global, obj2_global) - }; - - block_on(async { - join!( - async { - let env = &mut *cell.borrow_mut(); + let (mut stream, stream_obj_global, obj1_global, obj2_global) = test_utils::with_env(|env| { + let stream_obj = env + .new_object(jni_str!("io/github/gedgygedgy/rust/stream/QueueStream"), jni_sig!("()V"), &[]) + .unwrap(); + 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(); @@ -392,29 +388,28 @@ mod test { ) .unwrap(); env.call_method(&s, jni_str!("finish"), jni_sig!("()V"), &[]).unwrap(); - }, - async { - use futures::StreamExt; - let g1 = stream.next().await.unwrap().unwrap(); - { - let mut guard = cell.borrow_mut(); - let env = &mut *guard; - let o1 = env.new_local_ref(obj1_global.as_obj()).unwrap(); - assert!(env.is_same_object(g1.as_obj(), &o1).unwrap()); - } - - let g2 = stream.next().await.unwrap().unwrap(); - { - let mut guard = cell.borrow_mut(); - let env = &mut *guard; - let o2 = env.new_local_ref(obj2_global.as_obj()).unwrap(); - assert!(env.is_same_object(g2.as_obj(), &o2).unwrap()); - } - - assert!(stream.next().await.is_none()); - } - ); - }); + 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 bda74312..f2393434 100644 --- a/src/droidplug/jni_utils/task.rs +++ b/src/droidplug/jni_utils/task.rs @@ -60,9 +60,7 @@ mod test { #[test] fn test_waker_wake() { - test_utils::JVM_ENV.with(|cell| { - let env = &mut *cell.borrow_mut(); - + 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); @@ -83,14 +81,13 @@ mod test { 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(|cell| { - let env = &mut *cell.borrow_mut(); - + 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); @@ -110,6 +107,7 @@ mod test { 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 fd7dbaa0..48618684 100644 --- a/src/droidplug/jni_utils/uuid.rs +++ b/src/droidplug/jni_utils/uuid.rs @@ -112,8 +112,7 @@ mod test { #[test] fn test_uuid_new() { - test_utils::JVM_ENV.with(|cell| { - let env = &mut *cell.borrow_mut(); + test_utils::with_env(|env| { for test in TESTS { let most = test.most as jlong; let least = test.least as jlong; @@ -134,13 +133,13 @@ mod test { assert_eq!(actual_most, most); assert_eq!(actual_least, least); } - }); + Ok(()) + }).unwrap(); } #[test] fn test_uuid_as_uuid() { - test_utils::JVM_ENV.with(|cell| { - let env = &mut *cell.borrow_mut(); + test_utils::with_env(|env| { for test in TESTS { let most = test.most as jlong; let least = test.least as jlong; @@ -152,6 +151,7 @@ mod test { assert_eq!(uuid_obj.as_uuid(env).unwrap(), Uuid::from_u128(test.uuid)); } - }); + Ok(()) + }).unwrap(); } } diff --git a/src/droidplug/peripheral.rs b/src/droidplug/peripheral.rs index f8d6dc5f..50bedcea 100644 --- a/src/droidplug/peripheral.rs +++ b/src/droidplug/peripheral.rs @@ -162,10 +162,11 @@ impl Peripheral { where E: From<::jni::errors::Error>, { - let mut env = global_jvm().get_env()?; - let local_obj = env.new_local_ref(self.internal.as_obj())?; - let obj = JPeripheral::from_env(&mut env, local_obj)?; - f(&mut env, &obj) + global_jvm().attach_current_thread(|env| { + let local_obj = env.new_local_ref(self.internal.as_obj())?; + let obj = JPeripheral::from_env(env, local_obj)?; + f(env, &obj) + }) } async fn set_characteristic_notification( @@ -376,27 +377,28 @@ impl api::Peripheral for Peripheral { let stream = stream .map(move |item| match item { Ok(item) => { - let mut env = global_jvm().get_env()?; - let local_obj = env.new_local_ref(item.as_obj())?; - let characteristic = - JBluetoothGattCharacteristic::from_env(&mut env, local_obj)?; - let uuid = characteristic.get_uuid(&mut env)?; - let value = characteristic.get_value(&mut 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) + global_jvm().attach_current_thread(|env| { + let local_obj = env.new_local_ref(item.as_obj())?; + let characteristic = + JBluetoothGattCharacteristic::from_env(env, 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, }) } Err(err) => Err(err), From f1487caef69da8132f041a2cb08236cbddb4e934 Mon Sep 17 00:00:00 2001 From: Kyle Machulis Date: Sun, 26 Apr 2026 20:25:03 -0700 Subject: [PATCH 08/16] refactor: Migrate extern C functions to EnvUnowned + with_env pattern MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Completes the jni 0.22 FFI migration: - ops.rs: JNIEnv → Env for non-FFI code, EnvUnowned + with_env() for extern "C" fn_adapter_call_internal and fn_adapter_close_internal - jni/mod.rs: extern "C" callbacks migrated to EnvUnowned + with_env() - classcache: Store Global> instead of Global> so &Global satisfies Desc for new_object/get_method_id - Fix <&JClass>::from(class.as_obj()) → class.as_ref() across all files - arrays.rs: new_byte_array now takes usize, remove jint cast Co-Authored-By: Claude Opus 4.6 --- src/droidplug/jni/mod.rs | 22 ++++-- src/droidplug/jni_utils/arrays.rs | 4 +- src/droidplug/jni_utils/classcache.rs | 9 ++- src/droidplug/jni_utils/future.rs | 6 +- src/droidplug/jni_utils/ops.rs | 100 ++++++++++++++------------ src/droidplug/jni_utils/stream.rs | 8 +-- src/droidplug/jni_utils/task.rs | 6 +- 7 files changed, 86 insertions(+), 69 deletions(-) diff --git a/src/droidplug/jni/mod.rs b/src/droidplug/jni/mod.rs index 80ddf8e7..0c128e1c 100644 --- a/src/droidplug/jni/mod.rs +++ b/src/droidplug/jni/mod.rs @@ -1,6 +1,6 @@ pub mod objects; -use ::jni::{Env, JNIEnv, JavaVM, NativeMethod, jni_str, jni_sig, objects::JObject}; +use ::jni::{Env, EnvUnowned, JavaVM, NativeMethod, jni_str, jni_sig, objects::JObject}; use jni::{objects::JString, sys::jboolean}; use once_cell::sync::OnceCell; use std::ffi::c_void; @@ -134,17 +134,25 @@ impl From<::jni::errors::Error> for crate::Error { } } -extern "C" fn adapter_report_scan_result<'a>(mut env: JNIEnv<'a>, obj: JObject, scan_result: JObject<'a>) { - let _ = super::adapter::adapter_report_scan_result_internal(&mut env, &obj, scan_result); +extern "C" fn adapter_report_scan_result<'a>(mut env: EnvUnowned<'a>, obj: JObject, scan_result: JObject<'a>) { + 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( - mut env: JNIEnv, + mut env: EnvUnowned, obj: JObject, addr: JString, connected: jboolean, ) { - let _ = super::adapter::adapter_on_connection_state_changed_internal( - &mut env, &obj, addr, connected, - ); + let outcome = env.with_env(|env| { + let _ = super::adapter::adapter_on_connection_state_changed_internal( + env, &obj, addr, connected, + ); + Ok::<(), jni::errors::Error>(()) + }); + let _ = outcome.into_outcome(); } diff --git a/src/droidplug/jni_utils/arrays.rs b/src/droidplug/jni_utils/arrays.rs index a523c9a4..cd39c37c 100644 --- a/src/droidplug/jni_utils/arrays.rs +++ b/src/droidplug/jni_utils/arrays.rs @@ -2,12 +2,12 @@ use jni::{ Env, errors::Result, objects::JByteArray, - sys::{jbyte, jint}, + sys::jbyte, }; use std::slice; pub fn slice_to_byte_array<'local>(env: &mut Env<'local>, slice: &[u8]) -> Result> { - let obj = env.new_byte_array(slice.len() as jint)?; + 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)?; Ok(obj) diff --git a/src/droidplug/jni_utils/classcache.rs b/src/droidplug/jni_utils/classcache.rs index 9aa35eba..a7db0ee5 100644 --- a/src/droidplug/jni_utils/classcache.rs +++ b/src/droidplug/jni_utils/classcache.rs @@ -1,21 +1,20 @@ use dashmap::DashMap; -use jni::{Env, errors::Result, objects::{Global, JObject}, strings::JNIString}; +use jni::{Env, errors::Result, objects::{Global, JClass}, strings::JNIString}; use once_cell::sync::OnceCell; use std::sync::Arc; -static CLASSCACHE: OnceCell>>>> = OnceCell::new(); +static CLASSCACHE: OnceCell>>>> = OnceCell::new(); pub fn find_add_class(env: &mut Env, classname: &str) -> Result<()> { let cache = CLASSCACHE.get_or_init(|| DashMap::new()); let jni_name = JNIString::from(classname); let cls = env.find_class(&jni_name)?; - let cls_obj: JObject = cls.into(); - let global = env.new_global_ref(&cls_obj)?; + let global = env.new_global_ref(&cls)?; cache.insert(classname.to_owned(), Arc::new(global)); Ok(()) } -pub fn get_class(classname: &str) -> Option>>> { +pub fn get_class(classname: &str) -> Option>>> { let cache = CLASSCACHE.get_or_init(|| DashMap::new()); cache.get(classname).map(|pair| pair.value().clone()) } diff --git a/src/droidplug/jni_utils/future.rs b/src/droidplug/jni_utils/future.rs index f3960576..5b5f4785 100644 --- a/src/droidplug/jni_utils/future.rs +++ b/src/droidplug/jni_utils/future.rs @@ -2,7 +2,7 @@ use ::jni::{ Env, JavaVM, errors::Result, jni_sig, jni_str, - objects::{Global, JClass, JMethodID, JObject}, + objects::{Global, JMethodID, JObject}, signature::ReturnType, sys::jvalue, }; @@ -23,7 +23,7 @@ impl<'a> JFuture<'a> { let class = super::classcache::get_class("io/github/gedgygedgy/rust/future/Future").unwrap(); let poll_id = env.get_method_id( - <&JClass>::from(class.as_obj()), + class.as_ref(), jni_str!("poll"), jni_sig!("(Lio/github/gedgygedgy/rust/task/Waker;)Lio/github/gedgygedgy/rust/task/PollResult;"), )?; @@ -82,7 +82,7 @@ impl JSendFuture { let class = super::classcache::get_class("io/github/gedgygedgy/rust/future/Future").unwrap(); let poll_id = env.get_method_id( - <&JClass>::from(class.as_obj()), + class.as_ref(), jni_str!("poll"), jni_sig!("(Lio/github/gedgygedgy/rust/task/Waker;)Lio/github/gedgygedgy/rust/task/PollResult;"), )?; diff --git a/src/droidplug/jni_utils/ops.rs b/src/droidplug/jni_utils/ops.rs index 4f5aa8b8..a09cb6b0 100644 --- a/src/droidplug/jni_utils/ops.rs +++ b/src/droidplug/jni_utils/ops.rs @@ -1,8 +1,8 @@ use ::jni::{ - JNIEnv, + Env, EnvUnowned, errors::Result, jni_sig, jni_str, - objects::{JClass, JObject}, + objects::JObject, }; use std::sync::{Arc, Mutex}; @@ -27,21 +27,21 @@ macro_rules! define_fn_adapter { closure: $closure:expr, ) => { fn $foi<'local>( - env: &mut JNIEnv<'local>, + env: &mut Env<'local>, $closure_name: impl for<'c, 'd> FnOnce$args -> $ret + 'static, local: bool, ) -> Result> { let adapter = fn_once_adapter(env, $closure, local)?; let class = super::classcache::get_class($ic).unwrap(); env.new_object( - <&JClass>::from(class.as_obj()), + class.as_ref(), jni_sig!("(Lio/github/gedgygedgy/rust/ops/FnAdapter;)V"), &[(&adapter).into()], ) } pub fn $fo<'local>( - env: &mut JNIEnv<'local>, + env: &mut Env<'local>, f: impl for<'c, 'd> FnOnce$args -> $ret + Send + 'static, ) -> Result> { $foi(env, f, false) @@ -49,21 +49,21 @@ macro_rules! define_fn_adapter { #[allow(dead_code)] pub fn $fol<'local>( - env: &mut JNIEnv<'local>, + env: &mut Env<'local>, f: impl for<'c, 'd> FnOnce$args -> $ret + 'static, ) -> Result> { $foi(env, f, true) } fn $fmi<'local>( - env: &mut JNIEnv<'local>, + env: &mut Env<'local>, mut $closure_name: impl for<'c, 'd> FnMut$args -> $ret + 'static, local: bool, ) -> Result> { let adapter = fn_mut_adapter(env, $closure, local)?; let class = super::classcache::get_class($ic).unwrap(); env.new_object( - <&JClass>::from(class.as_obj()), + class.as_ref(), jni_sig!("(Lio/github/gedgygedgy/rust/ops/FnAdapter;)V"), &[(&adapter).into()], ) @@ -71,7 +71,7 @@ macro_rules! define_fn_adapter { #[allow(dead_code)] pub fn $fm<'local>( - env: &mut JNIEnv<'local>, + env: &mut Env<'local>, f: impl for<'c, 'd> FnMut$args -> $ret + Send + 'static, ) -> Result> { $fmi(env, f, false) @@ -79,21 +79,21 @@ macro_rules! define_fn_adapter { #[allow(dead_code)] pub fn $fml<'local>( - env: &mut JNIEnv<'local>, + env: &mut Env<'local>, f: impl for<'c, 'd> FnMut$args -> $ret + 'static, ) -> Result> { $fmi(env, f, true) } fn $fi<'local>( - env: &mut JNIEnv<'local>, + env: &mut Env<'local>, $closure_name: impl for<'c, 'd> Fn$args -> $ret + 'static, local: bool, ) -> Result> { let adapter = fn_adapter(env, $closure, local)?; let class = super::classcache::get_class($ic).unwrap(); env.new_object( - <&JClass>::from(class.as_obj()), + class.as_ref(), jni_sig!("(Lio/github/gedgygedgy/rust/ops/FnAdapter;)V"), &[(&adapter).into()], ) @@ -101,7 +101,7 @@ macro_rules! define_fn_adapter { #[allow(dead_code)] pub fn $f<'local>( - env: &mut JNIEnv<'local>, + env: &mut Env<'local>, f: impl for<'c, 'd> Fn$args -> $ret + Send + Sync + 'static, ) -> Result> { $fi(env, f, false) @@ -109,7 +109,7 @@ macro_rules! define_fn_adapter { #[allow(dead_code)] pub fn $fl<'local>( - env: &mut JNIEnv<'local>, + env: &mut Env<'local>, f: impl for<'c, 'd> Fn$args -> $ret + 'static, ) -> Result> { $fi(env, f, true) @@ -133,7 +133,7 @@ define_fn_adapter! { doc_fn_once: "fn_once_runnable", doc_fn: "fn_runnable", doc_noop: "be a no-op", - signature: f: impl for<'c, 'd> Fn(&'d mut 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() @@ -156,7 +156,7 @@ define_fn_adapter! { doc_fn_once: "fn_once_bi_function", doc_fn: "fn_bi_funciton", doc_noop: "return `null`", - signature: f: impl for<'c, 'd> Fn(&'d mut 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) }, @@ -178,7 +178,7 @@ define_fn_adapter! { doc_fn_once: "fn_once_function", doc_fn: "fn_function", doc_noop: "return `null`", - signature: f: impl for<'c, 'd> Fn(&'d mut 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) }, @@ -192,7 +192,7 @@ unsafe impl Sync for SendSyncWrapper {} type FnWrapper = SendSyncWrapper< Arc< dyn for<'a, 'b> Fn( - &'b mut JNIEnv<'a>, + &'b mut Env<'a>, JObject<'a>, JObject<'a>, JObject<'a>, @@ -203,9 +203,9 @@ type FnWrapper = SendSyncWrapper< >; fn fn_once_adapter<'local>( - env: &mut JNIEnv<'local>, + env: &mut Env<'local>, f: impl for<'c, 'd> FnOnce( - &'d mut JNIEnv<'c>, + &'d mut Env<'c>, JObject<'c>, JObject<'c>, JObject<'c>, @@ -233,9 +233,9 @@ fn fn_once_adapter<'local>( } fn fn_mut_adapter<'local>( - env: &mut JNIEnv<'local>, + env: &mut Env<'local>, f: impl for<'c, 'd> FnMut( - &'d mut JNIEnv<'c>, + &'d mut Env<'c>, JObject<'c>, JObject<'c>, JObject<'c>, @@ -256,9 +256,9 @@ fn fn_mut_adapter<'local>( } fn fn_adapter<'local>( - env: &mut JNIEnv<'local>, + env: &mut Env<'local>, f: impl for<'c, 'd> Fn( - &'d mut JNIEnv<'c>, + &'d mut Env<'c>, JObject<'c>, JObject<'c>, JObject<'c>, @@ -269,7 +269,7 @@ fn fn_adapter<'local>( ) -> Result> { let arc: Arc< dyn for<'c, 'd> Fn( - &'d mut JNIEnv<'c>, + &'d mut Env<'c>, JObject<'c>, JObject<'c>, JObject<'c>, @@ -279,7 +279,7 @@ fn fn_adapter<'local>( let class = super::classcache::get_class("io/github/gedgygedgy/rust/ops/FnAdapter").unwrap(); let obj = env.new_object( - <&JClass>::from(class.as_obj()), + class.as_ref(), jni_sig!("(Z)V"), &[local.into()], )?; @@ -288,7 +288,7 @@ fn fn_adapter<'local>( } pub(crate) extern "C" fn fn_adapter_call_internal<'local>( - mut env: JNIEnv<'local>, + mut env: EnvUnowned<'local>, obj1: JObject<'local>, obj2: JObject<'local>, arg1: JObject<'local>, @@ -296,28 +296,38 @@ pub(crate) extern "C" fn fn_adapter_call_internal<'local>( ) -> JObject<'local> { use std::panic::{AssertUnwindSafe, catch_unwind}; - let arc = - if let Ok(f) = unsafe { env.get_rust_field::<_, _, FnWrapper>(&obj1, jni_str!("data")) } { - AssertUnwindSafe(f.0.clone()) - } else { - return JObject::null(); - }; - match catch_unwind(AssertUnwindSafe(|| arc(&mut env, obj1, obj2, arg1, arg2))) { - Ok(result) => result, - Err(panic) => { - let _ = super::exceptions::throw_panic(&mut env, panic); - 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(mut env: JNIEnv, obj: JObject) { +pub(crate) extern "C" fn fn_adapter_close_internal(mut env: EnvUnowned, obj: JObject) { use std::panic::{AssertUnwindSafe, catch_unwind}; - 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(&mut env, panic); - } + 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 0352bc85..af1d4704 100644 --- a/src/droidplug/jni_utils/stream.rs +++ b/src/droidplug/jni_utils/stream.rs @@ -3,7 +3,7 @@ use ::jni::{ Env, JavaVM, errors::Result, jni_sig, jni_str, - objects::{Global, JClass, JMethodID, JObject}, + objects::{Global, JMethodID, JObject}, signature::ReturnType, sys::jvalue, }; @@ -24,7 +24,7 @@ impl<'a> JStream<'a> { let class = super::classcache::get_class("io/github/gedgygedgy/rust/stream/Stream").unwrap(); let poll_next_id = env.get_method_id( - <&JClass>::from(class.as_obj()), + class.as_ref(), jni_str!("pollNext"), jni_sig!("(Lio/github/gedgygedgy/rust/task/Waker;)Lio/github/gedgygedgy/rust/task/PollResult;"), )?; @@ -87,7 +87,7 @@ impl JSendStream { let class = super::classcache::get_class("io/github/gedgygedgy/rust/stream/Stream").unwrap(); let poll_next_id = env.get_method_id( - <&JClass>::from(class.as_obj()), + class.as_ref(), jni_str!("pollNext"), jni_sig!("(Lio/github/gedgygedgy/rust/task/Waker;)Lio/github/gedgygedgy/rust/task/PollResult;"), )?; @@ -165,7 +165,7 @@ impl<'a> JStreamPoll<'a> { let class = super::classcache::get_class("io/github/gedgygedgy/rust/stream/StreamPoll").unwrap(); let get = env.get_method_id( - <&JClass>::from(class.as_obj()), + class.as_ref(), jni_str!("get"), jni_sig!("()Ljava/lang/Object;"), )?; diff --git a/src/droidplug/jni_utils/task.rs b/src/droidplug/jni_utils/task.rs index f2393434..e412f55b 100644 --- a/src/droidplug/jni_utils/task.rs +++ b/src/droidplug/jni_utils/task.rs @@ -2,7 +2,7 @@ use ::jni::{ Env, errors::Result, jni_sig, jni_str, - objects::{JClass, JMethodID, JObject}, + objects::{JMethodID, JObject}, signature::ReturnType, }; use std::task::Waker; @@ -12,7 +12,7 @@ pub fn waker<'a>(env: &mut Env<'a>, waker: Waker) -> Result> { let class = super::classcache::get_class("io/github/gedgygedgy/rust/task/Waker").unwrap(); let obj = env.new_object( - <&JClass>::from(class.as_obj()), + class.as_ref(), jni_sig!("(Lio/github/gedgygedgy/rust/ops/FnRunnable;)V"), &[(&runnable).into()], )?; @@ -28,7 +28,7 @@ impl<'a> JPollResult<'a> { pub fn from_env(env: &mut Env<'a>, obj: JObject<'a>) -> Result { let class = super::classcache::get_class("io/github/gedgygedgy/rust/task/PollResult").unwrap(); - let get = env.get_method_id(<&JClass>::from(class.as_obj()), jni_str!("get"), jni_sig!("()Ljava/lang/Object;"))?; + let get = env.get_method_id(class.as_ref(), jni_str!("get"), jni_sig!("()Ljava/lang/Object;"))?; Ok(Self { internal: obj, get }) } From b6e0139495616e6c1c2a4a52f986317e5e2384d4 Mon Sep 17 00:00:00 2001 From: Kyle Machulis Date: Sun, 26 Apr 2026 20:40:45 -0700 Subject: [PATCH 09/16] fix: Update exception handling and NativeMethod for jni 0.22 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - exception_check() now returns bool (was Result), remove ? - exception_occurred() now returns Option (was Result), use .unwrap() - exception_clear() now returns () (was Result<()>), remove ?/.unwrap() - throw() now returns Err(JavaException) on success; use match or let _ = instead of .unwrap()/.? to preserve original control flow semantics - JObject → JThrowable/JString: use env.cast_local::() (From impls removed) - NativeMethod struct fields replaced by unsafe from_raw_parts constructor - JObjectArray::from_raw now requires env param and element type param - JavaStr → MUTF8Chars in test string assertions (String::from(chars)) Co-Authored-By: Claude Opus 4.6 --- src/droidplug/adapter.rs | 10 +- src/droidplug/jni/mod.rs | 42 ++++---- src/droidplug/jni_utils/exceptions.rs | 143 ++++++++++++++------------ src/droidplug/jni_utils/future.rs | 2 +- src/droidplug/jni_utils/mod.rs | 22 ++-- src/droidplug/peripheral.rs | 14 +-- 6 files changed, 118 insertions(+), 115 deletions(-) diff --git a/src/droidplug/adapter.rs b/src/droidplug/adapter.rs index 0bbf4f7d..ef01d690 100644 --- a/src/droidplug/adapter.rs +++ b/src/droidplug/adapter.rs @@ -14,7 +14,7 @@ use async_trait::async_trait; use futures::stream::Stream; use jni::{ Env, jni_sig, jni_str, - objects::{Global, JClass, JObject, JString}, + objects::{Global, JObject, JString}, sys::jboolean, }; use std::{ @@ -147,15 +147,15 @@ impl Central for Adapter { ) { Ok(_) => Ok(()), Err(jni::errors::Error::JavaException) => { - let ex = env.exception_occurred()?; - env.exception_clear()?; + let ex = env.exception_occurred().unwrap(); + env.exception_clear(); let no_adapter_class = super::jni_utils::classcache::get_class( "com/nonpolynomial/btleplug/android/impl/NoBluetoothAdapterException", ) .unwrap(); - if env.is_instance_of(&ex, <&JClass>::from(no_adapter_class.as_obj()))? { + if env.is_instance_of(&ex, no_adapter_class.as_ref())? { Err(Error::NoAdapterAvailable) } else if env.is_instance_of(&ex, jni_str!("java/lang/RuntimeException"))? { let msg = env @@ -165,7 +165,7 @@ impl Central for Adapter { let msgstr: String = env.get_string(&jstr)?.into(); Err(Error::RuntimeError(msgstr)) } else { - env.throw(&ex)?; + let _ = env.throw(&ex); Err(jni::errors::Error::JavaException.into()) } } diff --git a/src/droidplug/jni/mod.rs b/src/droidplug/jni/mod.rs index 0c128e1c..b2f3b7f5 100644 --- a/src/droidplug/jni/mod.rs +++ b/src/droidplug/jni/mod.rs @@ -14,16 +14,16 @@ pub fn init(env: &mut Env) -> crate::Result<()> { unsafe { env.register_native_methods( &adapter_class, &[ - NativeMethod { - name: jni_str!("reportScanResult").into(), - sig: jni_sig!("(Landroid/bluetooth/le/ScanResult;)V").into(), - fn_ptr: adapter_report_scan_result as *mut c_void, - }, - NativeMethod { - name: jni_str!("onConnectionStateChanged").into(), - sig: jni_sig!("(Ljava/lang/String;Z)V").into(), - fn_ptr: adapter_on_connection_state_changed as *mut c_void, - }, + NativeMethod::from_raw_parts( + jni_str!("reportScanResult"), + jni_str!("(Landroid/bluetooth/le/ScanResult;)V"), + adapter_report_scan_result as *mut c_void, + ), + NativeMethod::from_raw_parts( + jni_str!("onConnectionStateChanged"), + jni_str!("(Ljava/lang/String;Z)V"), + adapter_on_connection_state_changed as *mut c_void, + ), ], )? }; super::jni_utils::classcache::find_add_class( @@ -103,18 +103,16 @@ pub fn init(env: &mut Env) -> crate::Result<()> { unsafe { env.register_native_methods( &fn_adapter_class, &[ - NativeMethod { - name: jni_str!("callInternal").into(), - sig: - jni_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: jni_str!("closeInternal").into(), - sig: jni_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, + ), ], )? }; diff --git a/src/droidplug/jni_utils/exceptions.rs b/src/droidplug/jni_utils/exceptions.rs index 9f1393ef..c9668f5d 100644 --- a/src/droidplug/jni_utils/exceptions.rs +++ b/src/droidplug/jni_utils/exceptions.rs @@ -27,7 +27,7 @@ pub fn try_block( ) -> TryCatchResult { TryCatchResult { try_result: (|| { - if env.exception_check()? { + if env.exception_check() { Err(Error::JavaException) } else { Ok(block(env)) @@ -59,13 +59,19 @@ impl TryCatchResult { }, (Ok(Err(Error::JavaException)), None) => { let catch_result = (|| { - if env.exception_check()? { - let ex = env.exception_occurred()?; - env.exception_clear()?; - if env.is_instance_of(&ex, class)? { - return block(env, 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) })() @@ -118,8 +124,9 @@ impl<'a> JPanicException<'a> { &[(&msg).into()], )?; unsafe { env.set_rust_field(&obj, jni_str!("any"), any) }?; + let throwable = env.cast_local::(obj)?; Ok(Self { - internal: obj.into(), + internal: throwable, }) } @@ -160,10 +167,10 @@ 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()?; - Some(ex) + let old_ex = if env.exception_check() { + let ex = env.exception_occurred(); + env.exception_clear(); + ex } else { None }; @@ -178,8 +185,12 @@ pub fn throw_panic( )?; } let ex: JThrowable = ex.into(); - env.throw(&ex)?; - Ok(()) + // 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 @@ -204,10 +215,10 @@ mod test { 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 }; @@ -215,22 +226,22 @@ mod test { .find_class(jni_str!("java/lang/IllegalArgumentException")) .unwrap(); if let Some(ref ex) = old_ex { - env.throw(ex).unwrap(); + let _ = env.throw(ex); } let ex = throw_class.map(|c| { let obj = env.new_object(JNIString::from(c), jni_sig!("()V"), &[]).unwrap(); - JThrowable::from(obj) + env.cast_local::(obj).unwrap() }); try_block(env, |env| { if let Some(ref t) = ex { - env.throw(t).unwrap(); + let _ = env.throw(t); } try_result }) .catch(env, illegal_argument_exception, |env, caught| { - assert!(!env.exception_check().unwrap()); + assert!(!env.exception_check()); assert!(env.is_same_object(&caught, ex.as_ref().unwrap()).unwrap()); Ok(1) }) @@ -238,7 +249,7 @@ mod test { env, jni_str!("java/lang/ArrayIndexOutOfBoundsException"), |env, caught| { - assert!(!env.exception_check().unwrap()); + assert!(!env.exception_check()); assert!(env.is_same_object(&caught, ex.as_ref().unwrap()).unwrap()); if rethrow { Err(Error::JavaException) @@ -251,10 +262,10 @@ mod test { env, jni_str!("java/lang/IndexOutOfBoundsException"), |env, caught| { - assert!(!env.exception_check().unwrap()); + assert!(!env.exception_check()); assert!(env.is_same_object(&caught, ex.as_ref().unwrap()).unwrap()); if rethrow { - env.throw(&caught).unwrap(); + let _ = env.throw(&caught); Err(Error::JavaException) } else { Ok(3) @@ -265,7 +276,7 @@ mod test { env, jni_str!("java/lang/StringIndexOutOfBoundsException"), |env, caught| { - assert!(!env.exception_check().unwrap()); + assert!(!env.exception_check()); assert!(env.is_same_object(&caught, ex.as_ref().unwrap()).unwrap()); Ok(4) }, @@ -286,7 +297,7 @@ mod test { .unwrap(), 1 ); - assert!(!env.exception_check().unwrap()); + assert!(!env.exception_check()); Ok(()) }).unwrap(); } @@ -304,7 +315,7 @@ mod test { .unwrap(), 2 ); - assert!(!env.exception_check().unwrap()); + assert!(!env.exception_check()); Ok(()) }).unwrap(); } @@ -322,7 +333,7 @@ mod test { .unwrap(), 3 ); - assert!(!env.exception_check().unwrap()); + assert!(!env.exception_check()); Ok(()) }).unwrap(); } @@ -331,7 +342,7 @@ mod test { fn test_catch_ok() { test_utils::with_env(|env| { assert_eq!(test_catch(env, None, Ok(0), false).unwrap(), 0); - assert!(!env.exception_check().unwrap()); + assert!(!env.exception_check()); Ok(()) }).unwrap(); } @@ -347,9 +358,9 @@ mod test { ) .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, jni_str!("java/lang/SecurityException")) .unwrap() @@ -367,7 +378,7 @@ mod test { 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"); } @@ -381,7 +392,7 @@ mod test { 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"); } @@ -392,16 +403,14 @@ mod test { #[test] fn test_catch_prior_exception() { test_utils::with_env(|env| { - let ex = JThrowable::from( - env.new_object(jni_str!("java/lang/IllegalArgumentException"), jni_sig!("()V"), &[]) - .unwrap(), - ); - env.throw(&ex).unwrap(); + 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()); + assert!(env.exception_check()); let actual_ex = env.exception_occurred().unwrap(); - env.exception_clear().unwrap(); + env.exception_clear(); assert!(env.is_same_object(&actual_ex, &ex).unwrap()); } else { panic!("JavaException not found"); @@ -421,9 +430,9 @@ mod test { ) .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, jni_str!("java/lang/StringIndexOutOfBoundsException")) .unwrap() @@ -446,7 +455,7 @@ mod test { ) .unwrap_err() { - assert!(!env.exception_check().unwrap()); + assert!(!env.exception_check()); } else { panic!("JavaException not found"); } @@ -467,14 +476,14 @@ mod test { assert_eq!(*any.downcast_ref::<&str>().unwrap(), STATIC_MSG); } - let msg: JString = env + let msg_obj = env .call_method(&*ex, jni_str!("getMessage"), jni_sig!("()Ljava/lang/String;"), &[]) .unwrap() .l() - .unwrap() - .into(); - let str = env.get_string(&msg).unwrap(); - assert_eq!(>::from(str), STATIC_MSG); + .unwrap(); + let msg = env.cast_local::(msg_obj).unwrap(); + let chars = env.get_string(&msg).unwrap(); + assert_eq!(String::from(chars), STATIC_MSG); Ok(()) }).unwrap(); } @@ -493,14 +502,14 @@ mod test { assert_eq!(*any.downcast_ref::().unwrap(), STRING_MSG); } - let msg: JString = env + let msg_obj = env .call_method(&*ex, jni_str!("getMessage"), jni_sig!("()Ljava/lang/String;"), &[]) .unwrap() .l() - .unwrap() - .into(); - let str = env.get_string(&msg).unwrap(); - assert_eq!(>::from(str), STRING_MSG); + .unwrap(); + let msg = env.cast_local::(msg_obj).unwrap(); + let chars = env.get_string(&msg).unwrap(); + assert_eq!(String::from(chars), STRING_MSG); let any: Box = ex.take(env).unwrap(); assert_eq!(*any.downcast::().unwrap(), STRING_MSG); @@ -539,7 +548,7 @@ mod test { 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(); } @@ -550,9 +559,9 @@ mod test { 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, jni_str!("io/github/gedgygedgy/rust/panic/PanicException")) .unwrap() @@ -564,11 +573,10 @@ mod test { .l() .unwrap(); let suppressed_array = - unsafe { jni::objects::JObjectArray::from_raw(suppressed_list.into_raw()) }; + unsafe { jni::objects::JObjectArray::::from_raw(env, suppressed_list.into_raw()) }; assert_eq!(env.get_array_length(&suppressed_array).unwrap(), 0); - let ex_throwable = JThrowable::from(JObject::from(ex)); - let ex = super::JPanicException::from_env(ex_throwable); + 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"); @@ -579,16 +587,16 @@ mod test { #[test] fn test_throw_unwind_panic_suppress() { test_utils::with_env(|env| { - let old_ex = - JThrowable::from(env.new_object(jni_str!("java/lang/Exception"), jni_sig!("()V"), &[]).unwrap()); - env.throw(&old_ex).unwrap(); + 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, jni_str!("io/github/gedgygedgy/rust/panic/PanicException")) .unwrap() @@ -600,13 +608,12 @@ mod test { .l() .unwrap(); let suppressed_array = - unsafe { jni::objects::JObjectArray::from_raw(suppressed_list.into_raw()) }; + unsafe { jni::objects::JObjectArray::::from_raw(env, suppressed_list.into_raw()) }; assert_eq!(env.get_array_length(&suppressed_array).unwrap(), 1); let suppressed_ex = env.get_object_array_element(&suppressed_array, 0).unwrap(); assert!(env.is_same_object(&old_ex, &suppressed_ex).unwrap()); - let ex_throwable = JThrowable::from(JObject::from(ex)); - let ex = super::JPanicException::from_env(ex_throwable); + 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"); diff --git a/src/droidplug/jni_utils/future.rs b/src/droidplug/jni_utils/future.rs index 5b5f4785..2564fcd1 100644 --- a/src/droidplug/jni_utils/future.rs +++ b/src/droidplug/jni_utils/future.rs @@ -314,7 +314,7 @@ mod test { 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, jni_str!("getCause"), jni_sig!("()Ljava/lang/Throwable;"), &[]) .unwrap() diff --git a/src/droidplug/jni_utils/mod.rs b/src/droidplug/jni_utils/mod.rs index e7641a35..437f8907 100644 --- a/src/droidplug/jni_utils/mod.rs +++ b/src/droidplug/jni_utils/mod.rs @@ -36,18 +36,16 @@ pub(crate) mod test_utils { unsafe { env.register_native_methods( &class, &[ - NativeMethod { - name: jni_str!("callInternal").into(), - sig: - jni_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: jni_str!("closeInternal").into(), - sig: jni_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(()) diff --git a/src/droidplug/peripheral.rs b/src/droidplug/peripheral.rs index 50bedcea..10b896cd 100644 --- a/src/droidplug/peripheral.rs +++ b/src/droidplug/peripheral.rs @@ -16,7 +16,7 @@ use async_trait::async_trait; use futures::stream::Stream; use jni::{ Env, jni_sig, jni_str, - objects::{Global, JClass, JObject, JString, JValue}, + objects::{Global, JObject, JString, JValue}, }; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; @@ -57,22 +57,22 @@ fn get_poll_result<'a>( match poll_result.get(env) { Ok(obj) => Ok(obj), Err(jni::errors::Error::JavaException) => { - let ex = env.exception_occurred()?; - env.exception_clear()?; + let ex = env.exception_occurred().unwrap(); + env.exception_clear(); let future_exception_class = super::jni_utils::classcache::get_class( "io/github/gedgygedgy/rust/future/FutureException", ) .unwrap(); - if env.is_instance_of(&ex, <&JClass>::from(future_exception_class.as_obj()))? { + if env.is_instance_of(&ex, future_exception_class.as_ref())? { let cause = env .call_method(&ex, jni_str!("getCause"), jni_sig!("()Ljava/lang/Throwable;"), &[])? .l()?; let mut check = |name: &str| -> jni::errors::Result { let cls = super::jni_utils::classcache::get_class(name).unwrap(); - env.is_instance_of(&cause, <&JClass>::from(cls.as_obj())) + env.is_instance_of(&cause, cls.as_ref()) }; if check("com/nonpolynomial/btleplug/android/impl/NotConnectedException")? { @@ -105,11 +105,11 @@ fn get_poll_result<'a>( let msgstr: String = env.get_string(&jstr)?.into(); Err(Error::RuntimeError(msgstr)) } else { - env.throw(&ex)?; + let _ = env.throw(&ex); Err(jni::errors::Error::JavaException.into()) } } else { - env.throw(&ex)?; + let _ = env.throw(&ex); Err(jni::errors::Error::JavaException.into()) } } From 21f96d579965827d55824c83eab23efae9a9e11f Mon Sep 17 00:00:00 2001 From: Kyle Machulis Date: Sun, 26 Apr 2026 21:36:50 -0700 Subject: [PATCH 10/16] fix: Modernize deprecated jni 0.22 APIs and fix Android-only build errors Migrate deprecated array methods to type methods (JByteArray::set_region, JPrimitiveArray::len, JObjectArray::get_element), replace removed From for JString with cast_local, update from_raw calls to pass env, fix jboolean (now bool) comparisons, resolve JObject Send lifetime issue in async connect by scoping Global references, and replace env.get_string with JString::mutf8_chars throughout. Co-Authored-By: Claude Opus 4.6 --- src/droidplug/adapter.rs | 8 ++-- src/droidplug/jni/mod.rs | 2 +- src/droidplug/jni/objects.rs | 55 ++++++++++++--------------- src/droidplug/jni_utils/arrays.rs | 12 +++--- src/droidplug/jni_utils/exceptions.rs | 10 ++--- src/droidplug/peripheral.rs | 48 ++++++++++++----------- 6 files changed, 67 insertions(+), 68 deletions(-) diff --git a/src/droidplug/adapter.rs b/src/droidplug/adapter.rs index ef01d690..98d0c79c 100644 --- a/src/droidplug/adapter.rs +++ b/src/droidplug/adapter.rs @@ -161,8 +161,8 @@ impl Central for Adapter { let msg = env .call_method(&ex, jni_str!("getMessage"), jni_sig!("()Ljava/lang/String;"), &[])? .l()?; - let jstr: JString = msg.into(); - let msgstr: String = env.get_string(&jstr)?.into(); + let jstr = env.cast_local::(msg)?; + let msgstr = String::from(jstr.mutf8_chars(env)?); Err(Error::RuntimeError(msgstr)) } else { let _ = env.throw(&ex); @@ -223,10 +223,10 @@ pub(crate) fn adapter_on_connection_state_changed_internal( addr: JString, connected: jboolean, ) -> crate::Result<()> { - let addr_str: String = env.get_string(&addr)?.into(); + 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 != 0 { + 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 b2f3b7f5..be039f70 100644 --- a/src/droidplug/jni/mod.rs +++ b/src/droidplug/jni/mod.rs @@ -1,6 +1,6 @@ pub mod objects; -use ::jni::{Env, EnvUnowned, JavaVM, NativeMethod, jni_str, jni_sig, objects::JObject}; +use ::jni::{Env, EnvUnowned, JavaVM, NativeMethod, jni_str, objects::JObject}; use jni::{objects::JString, sys::jboolean}; use once_cell::sync::OnceCell; use std::ffi::c_void; diff --git a/src/droidplug/jni/objects.rs b/src/droidplug/jni/objects.rs index 5563b755..c512bfde 100644 --- a/src/droidplug/jni/objects.rs +++ b/src/droidplug/jni/objects.rs @@ -3,7 +3,7 @@ use jni::{ Env, errors::Result, jni_sig, jni_str, - objects::{JClass, JMethodID, JObject, JString}, + objects::{JMethodID, JObject, JString}, signature::{Primitive, ReturnType}, sys::{jint, jvalue}, }; @@ -51,7 +51,7 @@ impl<'a> JPeripheral<'a> { "com/nonpolynomial/btleplug/android/impl/Peripheral", ) .unwrap(); - let class = <&JClass>::from(class_static.as_obj()); + let class = &**class_static; let connect = env.get_method_id( class, @@ -141,7 +141,7 @@ impl<'a> JPeripheral<'a> { ) .unwrap(); let obj = env.new_object( - <&JClass>::from(class_static.as_obj()), + &**class_static, jni_sig!("(Lcom/nonpolynomial/btleplug/android/impl/Adapter;Ljava/lang/String;)V"), &[(&adapter).into(), (&addr_jstr).into()], )?; @@ -247,7 +247,7 @@ impl<'a> JPeripheral<'a> { l: uuid.as_raw(), }, jvalue { - z: enable as u8, + z: enable, }, ], ) @@ -307,9 +307,9 @@ impl<'a> JPeripheral<'a> { if obj.is_null() { Ok(None) } else { - let jstr: JString = obj.into(); - let name_str = env.get_string(&jstr)?; - Ok(Some(name_str.into())) + let jstr = env.cast_local::(obj)?; + let name_str = jstr.mutf8_chars(env)?; + Ok(Some(String::from(name_str))) } } @@ -341,13 +341,13 @@ impl<'a> JPeripheral<'a> { if obj.is_null() { return Ok(None); } - let arr = unsafe { jni::objects::JIntArray::from_raw(obj.into_raw()) }; - let len = 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]; - env.get_int_array_region(&arr, 0, &mut buf)?; + arr.get_region(env, 0, &mut buf)?; Ok(Some(crate::api::ConnectionParameters { interval_us: (buf[0] as u32) * 1250, latency: buf[1] as u16, @@ -523,7 +523,7 @@ impl<'a> JBluetoothGattCharacteristic<'a> { env.call_method_unchecked(&self.internal, self.get_value, ReturnType::Array, &[]) }? .l()?; - let value_arr = unsafe { jni::objects::JByteArray::from_raw(value.into_raw()) }; + 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) } @@ -599,7 +599,7 @@ impl<'a> JBluetoothDevice<'a> { env.call_method_unchecked(&self.internal, self.get_address, ReturnType::Object, &[]) }? .l()?; - Ok(obj.into()) + env.cast_local::(obj) } } @@ -609,22 +609,21 @@ pub struct JScanFilter<'a> { impl<'a> JScanFilter<'a> { pub fn new(env: &mut Env<'a>, filter: ScanFilter) -> Result { - let string_class = env.find_class(jni_str!("java/lang/String"))?; - let uuids = env.new_object_array( - filter.services.len() as i32, - &string_class, - &JObject::null(), + 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_static = crate::droidplug::jni_utils::classcache::get_class( "com/nonpolynomial/btleplug/android/impl/ScanFilter", ) .unwrap(); let obj = env.new_object( - <&JClass>::from(class_static.as_obj()), + &**class_static, jni_sig!("([Ljava/lang/String;)V"), &[(&uuids).into()], )?; @@ -721,12 +720,8 @@ impl<'a> JScanResult<'a> { let device = self.get_device(env)?; let addr_jstr = device.get_address(env)?; - let addr_str = env.get_string(&addr_jstr)?; - let addr = BDAddr::from_str( - addr_str - .to_str() - .map_err(|e| crate::Error::Other(e.into()))?, - )?; + let addr_str = String::from(addr_jstr.mutf8_chars(env)?); + let addr = BDAddr::from_str(&addr_str)?; let record = self.get_scan_record(env)?; let record_is_null = env.is_same_object(&*record, JObject::null())?; @@ -737,10 +732,10 @@ impl<'a> JScanResult<'a> { let device_name = if env.is_same_object(&device_name_obj, JObject::null())? { None } else { - let device_name_jstr: JString = device_name_obj.into(); - let device_name_str = env.get_string(&device_name_jstr)?; + 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(), @@ -765,7 +760,7 @@ impl<'a> JScanResult<'a> { let key = manufacturer_specific_data_obj.key_at(env, i)?; let value = manufacturer_specific_data_obj.value_at(env, i)?; let value_arr = - unsafe { jni::objects::JByteArray::from_raw(value.into_raw()) }; + 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); @@ -803,7 +798,7 @@ impl<'a> JScanResult<'a> { let juuid = parcel_uuid.get_uuid(env)?; let uuid = juuid.as_uuid(env)?; let value_arr = - unsafe { jni::objects::JByteArray::from_raw(value.into_raw()) }; + 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); diff --git a/src/droidplug/jni_utils/arrays.rs b/src/droidplug/jni_utils/arrays.rs index cd39c37c..eb208be1 100644 --- a/src/droidplug/jni_utils/arrays.rs +++ b/src/droidplug/jni_utils/arrays.rs @@ -9,16 +9,16 @@ use std::slice; 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) } pub fn byte_array_to_vec(env: &Env, array: &JByteArray) -> Result> { - let size = env.get_array_length(array)? as usize; + 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(array, 0, result_slice)?; + array.get_region(env, 0, result_slice)?; result.set_len(size); } Ok(result) @@ -32,10 +32,10 @@ mod test { fn test_slice_to_byte_array() { 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(); @@ -45,7 +45,7 @@ mod test { fn test_byte_array_to_vec() { 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]) + obj.set_region(env, 0, &[1, 2, 3, 4, 5]) .unwrap(); let vec = super::byte_array_to_vec(env, &obj).unwrap(); diff --git a/src/droidplug/jni_utils/exceptions.rs b/src/droidplug/jni_utils/exceptions.rs index c9668f5d..28afbc88 100644 --- a/src/droidplug/jni_utils/exceptions.rs +++ b/src/droidplug/jni_utils/exceptions.rs @@ -482,7 +482,7 @@ mod test { .l() .unwrap(); let msg = env.cast_local::(msg_obj).unwrap(); - let chars = env.get_string(&msg).unwrap(); + let chars = msg.mutf8_chars(env).unwrap(); assert_eq!(String::from(chars), STATIC_MSG); Ok(()) }).unwrap(); @@ -508,7 +508,7 @@ mod test { .l() .unwrap(); let msg = env.cast_local::(msg_obj).unwrap(); - let chars = env.get_string(&msg).unwrap(); + let chars = msg.mutf8_chars(env).unwrap(); assert_eq!(String::from(chars), STRING_MSG); let any: Box = ex.take(env).unwrap(); @@ -574,7 +574,7 @@ mod test { .unwrap(); let suppressed_array = unsafe { jni::objects::JObjectArray::::from_raw(env, suppressed_list.into_raw()) }; - assert_eq!(env.get_array_length(&suppressed_array).unwrap(), 0); + assert_eq!(suppressed_array.len(env).unwrap(), 0); let ex = super::JPanicException::from_env(ex); let any = ex.take(env).unwrap(); @@ -609,8 +609,8 @@ mod test { .unwrap(); let suppressed_array = unsafe { jni::objects::JObjectArray::::from_raw(env, suppressed_list.into_raw()) }; - assert_eq!(env.get_array_length(&suppressed_array).unwrap(), 1); - let suppressed_ex = env.get_object_array_element(&suppressed_array, 0).unwrap(); + 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); diff --git a/src/droidplug/peripheral.rs b/src/droidplug/peripheral.rs index 10b896cd..81cd54fb 100644 --- a/src/droidplug/peripheral.rs +++ b/src/droidplug/peripheral.rs @@ -101,8 +101,8 @@ fn get_poll_result<'a>( let msg = env .call_method(&cause, jni_str!("getMessage"), jni_sig!("()Ljava/lang/String;"), &[])? .l()?; - let jstr: JString = msg.into(); - let msgstr: String = env.get_string(&jstr)?.into(); + let jstr = env.cast_local::(msg)?; + let msgstr = String::from(jstr.mutf8_chars(env)?); Err(Error::RuntimeError(msgstr)) } else { let _ = env.throw(&ex); @@ -219,12 +219,14 @@ impl api::Peripheral for Peripheral { } async fn connect(&self) -> Result<()> { - let future = self.with_obj(|env, obj| { - let future = obj.connect(env)?; - JSendFuture::new(env, &future) - })?; - let result_ref = future.await?; - self.with_obj(|env, _obj| get_poll_result(env, &result_ref).map(|_| {}))?; + { + let future = self.with_obj(|env, obj| { + let future = obj.connect(env)?; + 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(env) { @@ -236,18 +238,20 @@ impl api::Peripheral for Peripheral { Ok(()) })?; // Auto-negotiate maximum MTU (517) after connection - let mtu_future = self.with_obj(|env, obj| { - let mtu_obj = obj.request_mtu(env, 517)?; - let mtu_future = JFuture::from_env(env, mtu_obj)?; - 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(()) - })?; + { + let mtu_future = self.with_obj(|env, obj| { + let mtu_obj = obj.request_mtu(env, 517)?; + let mtu_future = JFuture::from_env(env, mtu_obj)?; + 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(()) } @@ -352,7 +356,7 @@ impl api::Peripheral for Peripheral { let result_ref = future.await?; self.with_obj(|env, _obj| { let bytes_obj = get_poll_result(env, &result_ref)?; - let bytes_arr = unsafe { jni::objects::JByteArray::from_raw(bytes_obj.into_raw()) }; + let bytes_arr = unsafe { jni::objects::JByteArray::from_raw(env, bytes_obj.into_raw()) }; Ok(byte_array_to_vec(env, &bytes_arr)?) }) } @@ -443,7 +447,7 @@ impl api::Peripheral for Peripheral { let result_ref = future.await?; self.with_obj(|env, _obj| { let bytes_obj = get_poll_result(env, &result_ref)?; - let bytes_arr = unsafe { jni::objects::JByteArray::from_raw(bytes_obj.into_raw()) }; + let bytes_arr = unsafe { jni::objects::JByteArray::from_raw(env, bytes_obj.into_raw()) }; Ok(byte_array_to_vec(env, &bytes_arr)?) }) } From 866db2b810409b4cd08e71ffec4338ebdf262122 Mon Sep 17 00:00:00 2001 From: Kyle Machulis Date: Sun, 26 Apr 2026 21:36:55 -0700 Subject: [PATCH 11/16] fix: Migrate Android test crate to jni 0.22 EnvUnowned + with_env pattern Replace deprecated JNIEnv with EnvUnowned in extern "system" FFI entry points, use with_env/into_outcome for env access, and update throw_new to use jni_str! and JNIString for 0.22 string type requirements. Co-Authored-By: Claude Opus 4.6 --- tests/android/rust/Cargo.lock | 172 ++++++++++++++-------------------- tests/android/rust/src/lib.rs | 23 +++-- 2 files changed, 85 insertions(+), 110 deletions(-) diff --git a/tests/android/rust/Cargo.lock b/tests/android/rust/Cargo.lock index ab29b097..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,25 +325,52 @@ dependencies = [ [[package]] name = "jni" -version = "0.21.1" +version = "0.22.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" dependencies = [ - "cesu8", "cfg-if", "combine", + "jni-macros", "jni-sys", "log", - "thiserror 1.0.69", + "simd_cesu8", + "thiserror", "walkdir", - "windows-sys 0.45.0", + "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 = "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 = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" +checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" +dependencies = [ + "quote", + "syn", +] [[package]] name = "js-sys" @@ -550,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" @@ -571,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" @@ -589,7 +625,7 @@ checksum = "cc2215ce3e6a77550b80a1c37251b7d294febaf42e36e21b7b411e0bf54d540d" dependencies = [ "log", "serde", - "thiserror 2.0.18", + "thiserror", "xml", ] @@ -623,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" @@ -662,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]] @@ -942,15 +974,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "windows-sys" -version = "0.45.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" -dependencies = [ - "windows-targets 0.42.2", -] - [[package]] name = "windows-sys" version = "0.59.0" @@ -978,21 +1001,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "windows-targets" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" -dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", -] - [[package]] name = "windows-targets" version = "0.52.6" @@ -1035,12 +1043,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" - [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" @@ -1053,12 +1055,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" -[[package]] -name = "windows_aarch64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" - [[package]] name = "windows_aarch64_msvc" version = "0.52.6" @@ -1071,12 +1067,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" -[[package]] -name = "windows_i686_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" - [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -1101,12 +1091,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" -[[package]] -name = "windows_i686_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" - [[package]] name = "windows_i686_msvc" version = "0.52.6" @@ -1119,12 +1103,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" -[[package]] -name = "windows_x86_64_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" - [[package]] name = "windows_x86_64_gnu" version = "0.52.6" @@ -1137,12 +1115,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" - [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" @@ -1155,12 +1127,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" -[[package]] -name = "windows_x86_64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" - [[package]] name = "windows_x86_64_msvc" version = "0.52.6" diff --git a/tests/android/rust/src/lib.rs b/tests/android/rust/src/lib.rs index b234357f..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: &mut 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: &mut 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(mut env: JNIEnv, _class: JClass) { - run_test(&mut 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(); } }; } From 3480907ea02737b8f2df29bc3fbf8f84bb88b899 Mon Sep 17 00:00:00 2001 From: Kyle Machulis Date: Sun, 26 Apr 2026 22:19:34 -0700 Subject: [PATCH 12/16] refactor: Replace JNI boilerplate with bind_java_type! macros and JavaVM::singleton() Convert all hand-rolled JNI wrapper types (struct + from_env + Deref + From impls) to jni 0.22's bind_java_type! macro. Replace GLOBAL_JVM OnceCell with JavaVM::singleton(). Simplify JSendFuture/JSendStream by dropping cached JMethodID fields in favour of cast_local per poll call. Net reduction of ~780 lines of boilerplate. Co-Authored-By: Claude Opus 4.6 --- src/droidplug/adapter.rs | 12 +- src/droidplug/jni/mod.rs | 24 +- src/droidplug/jni/objects.rs | 952 ++++++------------------------ src/droidplug/jni_utils/future.rs | 101 +--- src/droidplug/jni_utils/stream.rs | 123 +--- src/droidplug/jni_utils/task.rs | 42 +- src/droidplug/jni_utils/uuid.rs | 87 +-- src/droidplug/peripheral.rs | 20 +- 8 files changed, 289 insertions(+), 1072 deletions(-) diff --git a/src/droidplug/adapter.rs b/src/droidplug/adapter.rs index 98d0c79c..ec424397 100644 --- a/src/droidplug/adapter.rs +++ b/src/droidplug/adapter.rs @@ -1,6 +1,6 @@ use super::{ jni::{ - global_jvm, + jvm, objects::{JScanFilter, JScanResult}, }, peripheral::{Peripheral, PeripheralId}, @@ -40,7 +40,7 @@ impl Debug for Adapter { impl Adapter { pub(crate) fn new() -> Result { - global_jvm().attach_current_thread(|env| { + jvm()?.attach_current_thread(|env| { let obj = env.new_object( jni_str!("com/nonpolynomial/btleplug/android/impl/Adapter"), jni_sig!("()V"), @@ -63,7 +63,7 @@ impl Adapter { scan_result: JObject<'a>, ) -> Result { - let scan_result = JScanResult::from_env(env, scan_result)?; + let scan_result = env.cast_local::(scan_result)?; let (addr, properties): (BDAddr, Option) = scan_result.to_peripheral_properties(env)?; @@ -87,7 +87,7 @@ impl Adapter { } fn add(&self, address: BDAddr) -> Result { - global_jvm().attach_current_thread(|env| { + 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()); @@ -136,7 +136,7 @@ impl Central for Adapter { } async fn start_scan(&self, filter: ScanFilter) -> Result<()> { - global_jvm().attach_current_thread(|env| { + jvm()?.attach_current_thread(|env| { let filter = JScanFilter::new(env, filter)?; let filter_obj: JObject = filter.into(); match env.call_method( @@ -175,7 +175,7 @@ impl Central for Adapter { } async fn stop_scan(&self) -> Result<()> { - global_jvm().attach_current_thread(|env| { + jvm()?.attach_current_thread(|env| { env.call_method(self.internal.as_obj(), jni_str!("stopScan"), jni_sig!("()V"), &[])?; Ok(()) }) diff --git a/src/droidplug/jni/mod.rs b/src/droidplug/jni/mod.rs index be039f70..989c6795 100644 --- a/src/droidplug/jni/mod.rs +++ b/src/droidplug/jni/mod.rs @@ -1,14 +1,24 @@ pub mod objects; -use ::jni::{Env, EnvUnowned, JavaVM, NativeMethod, jni_str, objects::JObject}; +use ::jni::{Env, EnvUnowned, NativeMethod, jni_str, objects::JObject}; 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: &mut Env) -> crate::Result<()> { - if let Ok(()) = GLOBAL_JVM.set(env.get_java_vm()?) { + 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<()> { + { let adapter_class = env.find_class(jni_str!("com/nonpolynomial/btleplug/android/impl/Adapter"))?; unsafe { env.register_native_methods( @@ -120,10 +130,8 @@ pub fn init(env: &mut Env) -> crate::Result<()> { 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 { diff --git a/src/droidplug/jni/objects.rs b/src/droidplug/jni/objects.rs index c512bfde..da319d8f 100644 --- a/src/droidplug/jni/objects.rs +++ b/src/droidplug/jni/objects.rs @@ -1,309 +1,113 @@ use crate::droidplug::jni_utils::{future::JFuture, stream::JStream, uuid::JUuid}; use jni::{ Env, + bind_java_type, errors::Result, jni_sig, jni_str, - objects::{JMethodID, JObject, JString}, - signature::{Primitive, ReturnType}, - sys::{jint, jvalue}, + objects::{JObject, JString}, + sys::jint, }; use std::{collections::HashMap, iter::Iterator}; use uuid::Uuid; use crate::api::{BDAddr, CharPropFlags, PeripheralProperties, ScanFilter}; -pub struct JPeripheral<'a> { - internal: JObject<'a>, - connect: JMethodID, - disconnect: JMethodID, - is_connected: JMethodID, - discover_services: JMethodID, - read: JMethodID, - write: JMethodID, - set_characteristic_notification: JMethodID, - get_notifications: JMethodID, - read_descriptor: JMethodID, - write_descriptor: JMethodID, - get_device_name: JMethodID, - request_mtu: JMethodID, - get_connection_parameters: JMethodID, - request_connection_priority: JMethodID, - read_remote_rssi: JMethodID, -} - -impl<'a> ::std::ops::Deref for JPeripheral<'a> { - type Target = JObject<'a>; - - fn deref(&self) -> &Self::Target { - &self.internal - } -} - -impl<'a> From> for JObject<'a> { - fn from(other: JPeripheral<'a>) -> JObject<'a> { - other.internal - } +bind_java_type! { + pub JPeripheral => com.nonpolynomial.btleplug.android.impl.Peripheral, + constructors { + fn with_adapter(adapter: JObject, address: JString), + }, + methods { + priv fn connect_raw() -> JObject { name = "connect" }, + priv fn disconnect_raw() -> JObject { name = "disconnect" }, + fn is_connected() -> jboolean, + priv fn discover_services_raw() -> JObject { name = "discoverServices" }, + priv fn read_raw(uuid: JObject) -> JObject { name = "read" }, + priv fn write_raw(uuid: JObject, data: JObject, write_type: jint) -> JObject { name = "write" }, + priv fn set_characteristic_notification_raw(uuid: JObject, enable: jboolean) -> JObject { + name = "setCharacteristicNotification", + }, + priv fn get_notifications_raw() -> JObject { name = "getNotifications" }, + priv fn read_descriptor_raw(characteristic: JObject, uuid: JObject) -> JObject { name = "readDescriptor" }, + priv fn write_descriptor_raw(characteristic: JObject, uuid: JObject, data: JObject) -> JObject { + name = "writeDescriptor", + }, + priv fn get_device_name_raw() -> JObject { name = "getDeviceName" }, + fn request_mtu(mtu: jint) -> JObject, + priv fn get_connection_parameters_raw() -> JObject { name = "getConnectionParameters" }, + fn request_connection_priority(priority: jint) -> jboolean, + fn read_remote_rssi() -> JObject, + }, } -impl<'a> JPeripheral<'a> { - pub fn from_env(env: &mut Env<'a>, obj: JObject<'a>) -> Result { - let class_static = crate::droidplug::jni_utils::classcache::get_class( - "com/nonpolynomial/btleplug/android/impl/Peripheral", - ) - .unwrap(); - let class = &**class_static; - - let connect = env.get_method_id( - class, - jni_str!("connect"), - jni_sig!("()Lio/github/gedgygedgy/rust/future/Future;"), - )?; - let disconnect = env.get_method_id( - class, - jni_str!("disconnect"), - jni_sig!("()Lio/github/gedgygedgy/rust/future/Future;"), - )?; - let is_connected = env.get_method_id(class, jni_str!("isConnected"), jni_sig!("()Z"))?; - let discover_services = env.get_method_id( - class, - jni_str!("discoverServices"), - jni_sig!("()Lio/github/gedgygedgy/rust/future/Future;"), - )?; - let read = env.get_method_id( - class, - jni_str!("read"), - jni_sig!("(Ljava/util/UUID;)Lio/github/gedgygedgy/rust/future/Future;"), - )?; - let write = env.get_method_id( - class, - jni_str!("write"), - jni_sig!("(Ljava/util/UUID;[BI)Lio/github/gedgygedgy/rust/future/Future;"), - )?; - let set_characteristic_notification = env.get_method_id( - class, - jni_str!("setCharacteristicNotification"), - jni_sig!("(Ljava/util/UUID;Z)Lio/github/gedgygedgy/rust/future/Future;"), - )?; - let get_notifications = env.get_method_id( - class, - jni_str!("getNotifications"), - jni_sig!("()Lio/github/gedgygedgy/rust/stream/Stream;"), - )?; - let read_descriptor = env.get_method_id( - class, - jni_str!("readDescriptor"), - jni_sig!("(Ljava/util/UUID;Ljava/util/UUID;)Lio/github/gedgygedgy/rust/future/Future;"), - )?; - let write_descriptor = env.get_method_id( - class, - jni_str!("writeDescriptor"), - jni_sig!("(Ljava/util/UUID;Ljava/util/UUID;[B)Lio/github/gedgygedgy/rust/future/Future;"), - )?; - let get_device_name = env.get_method_id(class, jni_str!("getDeviceName"), jni_sig!("()Ljava/lang/String;"))?; - let request_mtu = env.get_method_id( - class, - jni_str!("requestMtu"), - jni_sig!("(I)Lio/github/gedgygedgy/rust/future/Future;"), - )?; - let get_connection_parameters = - env.get_method_id(class, jni_str!("getConnectionParameters"), jni_sig!("()[I"))?; - let request_connection_priority = - env.get_method_id(class, jni_str!("requestConnectionPriority"), jni_sig!("(I)Z"))?; - let read_remote_rssi = env.get_method_id( - class, - jni_str!("readRemoteRssi"), - jni_sig!("()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, - }) - } - - pub fn new(env: &mut Env<'a>, adapter: JObject<'a>, addr: BDAddr) -> Result { +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_static = crate::droidplug::jni_utils::classcache::get_class( - "com/nonpolynomial/btleplug/android/impl/Peripheral", - ) - .unwrap(); - let obj = env.new_object( - &**class_static, - jni_sig!("(Lcom/nonpolynomial/btleplug/android/impl/Adapter;Ljava/lang/String;)V"), - &[(&adapter).into(), (&addr_jstr).into()], - )?; - Self::from_env(env, obj) - } - - pub fn connect(&self, env: &mut Env<'a>) -> Result> { - let future_obj = unsafe { - env.call_method_unchecked(&self.internal, self.connect, ReturnType::Object, &[]) - }? - .l()?; - JFuture::from_env(env, future_obj) + JPeripheral::with_adapter(env, &adapter, &addr_jstr) } +} - pub fn disconnect(&self, env: &mut Env<'a>) -> Result> { - let future_obj = unsafe { - env.call_method_unchecked(&self.internal, self.disconnect, ReturnType::Object, &[]) - }? - .l()?; - JFuture::from_env(env, future_obj) +impl<'local> JPeripheral<'local> { + pub fn connect(&self, env: &mut Env<'local>) -> Result> { + env.cast_local::(self.connect_raw(env)?) } - pub fn is_connected(&self, env: &mut Env<'a>) -> Result { - unsafe { - env.call_method_unchecked( - &self.internal, - self.is_connected, - ReturnType::Primitive(Primitive::Boolean), - &[], - ) - }? - .z() + pub fn disconnect(&self, env: &mut Env<'local>) -> Result> { + env.cast_local::(self.disconnect_raw(env)?) } - pub fn discover_services(&self, env: &mut Env<'a>) -> Result> { - let future_obj = unsafe { - env.call_method_unchecked( - &self.internal, - self.discover_services, - ReturnType::Object, - &[], - ) - }? - .l()?; - JFuture::from_env(env, future_obj) + pub fn discover_services(&self, env: &mut Env<'local>) -> Result> { + env.cast_local::(self.discover_services_raw(env)?) } - pub fn read(&self, env: &mut Env<'a>, uuid: &JUuid<'a>) -> Result> { - let future_obj = unsafe { - env.call_method_unchecked( - &self.internal, - self.read, - ReturnType::Object, - &[jvalue { - l: uuid.as_raw(), - }], - ) - }? - .l()?; - JFuture::from_env(env, future_obj) + pub fn read(&self, env: &mut Env<'local>, uuid: &JUuid<'local>) -> Result> { + env.cast_local::(self.read_raw(env, uuid)?) } pub fn write( &self, - env: &mut Env<'a>, - uuid: &JUuid<'a>, - data: &JObject<'a>, + env: &mut Env<'local>, + uuid: &JUuid<'local>, + data: &JObject<'local>, write_type: jint, - ) -> Result> { - let future_obj = unsafe { - env.call_method_unchecked( - &self.internal, - self.write, - ReturnType::Object, - &[ - jvalue { - l: uuid.as_raw(), - }, - jvalue { - l: data.as_raw(), - }, - jvalue { i: write_type }, - ], - ) - }? - .l()?; - JFuture::from_env(env, future_obj) + ) -> Result> { + env.cast_local::(self.write_raw(env, uuid, data, write_type)?) } pub fn set_characteristic_notification( &self, - env: &mut Env<'a>, - uuid: &JUuid<'a>, + env: &mut Env<'local>, + uuid: &JUuid<'local>, enable: bool, - ) -> Result> { - let future_obj = unsafe { - env.call_method_unchecked( - &self.internal, - self.set_characteristic_notification, - ReturnType::Object, - &[ - jvalue { - l: uuid.as_raw(), - }, - jvalue { - z: enable, - }, - ], - ) - }? - .l()?; - JFuture::from_env(env, future_obj) + ) -> Result> { + env.cast_local::(self.set_characteristic_notification_raw(env, uuid, enable)?) } - pub fn get_notifications(&self, env: &mut Env<'a>) -> Result> { - let stream_obj = unsafe { - env.call_method_unchecked( - &self.internal, - self.get_notifications, - ReturnType::Object, - &[], - ) - }? - .l()?; - JStream::from_env(env, stream_obj) + pub fn get_notifications(&self, env: &mut Env<'local>) -> Result> { + env.cast_local::(self.get_notifications_raw(env)?) } pub fn read_descriptor( &self, - env: &mut Env<'a>, - characteristic: &JUuid<'a>, - uuid: &JUuid<'a>, - ) -> Result> { - let future_obj = unsafe { - env.call_method_unchecked( - &self.internal, - self.read_descriptor, - ReturnType::Object, - &[ - jvalue { - l: characteristic.as_raw(), - }, - jvalue { - l: uuid.as_raw(), - }, - ], - ) - }? - .l()?; - JFuture::from_env(env, future_obj) + env: &mut Env<'local>, + characteristic: &JUuid<'local>, + uuid: &JUuid<'local>, + ) -> Result> { + env.cast_local::(self.read_descriptor_raw(env, characteristic, uuid)?) + } + + pub fn write_descriptor( + &self, + env: &mut Env<'local>, + characteristic: &JUuid<'local>, + uuid: &JUuid<'local>, + data: &JObject<'local>, + ) -> Result> { + env.cast_local::(self.write_descriptor_raw(env, characteristic, uuid, data)?) } - pub fn get_device_name(&self, env: &mut Env<'a>) -> Result> { - let obj = unsafe { - env.call_method_unchecked( - &self.internal, - self.get_device_name, - ReturnType::Object, - &[], - ) - }? - .l()?; + pub fn get_device_name(&self, env: &mut Env<'local>) -> Result> { + let obj = self.get_device_name_raw(env)?; if obj.is_null() { Ok(None) } else { @@ -313,31 +117,11 @@ impl<'a> JPeripheral<'a> { } } - pub fn request_mtu(&self, env: &mut Env<'a>, mtu: jint) -> Result> { - unsafe { - env.call_method_unchecked( - &self.internal, - self.request_mtu, - ReturnType::Object, - &[jvalue { i: mtu }], - ) - }? - .l() - } - pub fn get_connection_parameters( &self, - env: &mut Env<'a>, + env: &mut Env<'local>, ) -> Result> { - let obj = unsafe { - env.call_method_unchecked( - &self.internal, - self.get_connection_parameters, - ReturnType::Array, - &[], - ) - }? - .l()?; + let obj = self.get_connection_parameters_raw(env)?; if obj.is_null() { return Ok(None); } @@ -354,253 +138,123 @@ impl<'a> JPeripheral<'a> { supervision_timeout_us: (buf[2] as u32) * 10_000, })) } - - pub fn read_remote_rssi(&self, env: &mut Env<'a>) -> Result> { - unsafe { - env.call_method_unchecked( - &self.internal, - self.read_remote_rssi, - ReturnType::Object, - &[], - ) - }? - .l() - } - - pub fn request_connection_priority( - &self, - env: &mut Env<'a>, - priority: jint, - ) -> Result { - unsafe { - env.call_method_unchecked( - &self.internal, - self.request_connection_priority, - ReturnType::Primitive(Primitive::Boolean), - &[jvalue { i: priority }], - ) - }? - .z() - } - - pub fn write_descriptor( - &self, - env: &mut Env<'a>, - characteristic: &JUuid<'a>, - uuid: &JUuid<'a>, - data: &JObject<'a>, - ) -> Result> { - let future_obj = unsafe { - env.call_method_unchecked( - &self.internal, - self.write_descriptor, - ReturnType::Object, - &[ - jvalue { - l: characteristic.as_raw(), - }, - jvalue { - l: uuid.as_raw(), - }, - jvalue { - l: data.as_raw(), - }, - ], - ) - }? - .l()?; - JFuture::from_env(env, future_obj) - } } -pub struct JBluetoothGattService<'a> { - internal: JObject<'a>, - get_uuid: JMethodID, - get_characteristics: JMethodID, +bind_java_type! { + pub JBluetoothGattService => android.bluetooth.BluetoothGattService, + methods { + fn get_uuid_obj() -> JObject { + name = "getUuid", + }, + fn get_characteristics_obj() -> JObject { + name = "getCharacteristics", + }, + }, } -impl<'a> JBluetoothGattService<'a> { - pub fn from_env(env: &mut Env<'a>, obj: JObject<'a>) -> Result { - let class = env.find_class(jni_str!("android/bluetooth/BluetoothGattService"))?; - - let get_uuid = env.get_method_id(&class, jni_str!("getUuid"), jni_sig!("()Ljava/util/UUID;"))?; - let get_characteristics = - env.get_method_id(&class, jni_str!("getCharacteristics"), jni_sig!("()Ljava/util/List;"))?; - Ok(Self { - internal: obj, - get_uuid, - get_characteristics, - }) - } - +impl<'local> JBluetoothGattService<'local> { pub fn is_primary(&self) -> Result { Ok(true) } - pub fn get_uuid(&self, env: &mut Env<'a>) -> Result { - let obj = unsafe { - env.call_method_unchecked(&self.internal, self.get_uuid, ReturnType::Object, &[]) - }? - .l()?; - let uuid_obj = JUuid::from_env(env, obj)?; + pub fn get_uuid(&self, env: &mut Env<'local>) -> Result { + let obj = self.get_uuid_obj(env)?; + let uuid_obj = env.cast_local::(obj)?; uuid_obj.as_uuid(env) } pub fn get_characteristics( &self, - env: &mut Env<'a>, - ) -> Result>> { - let obj = unsafe { - env.call_method_unchecked( - &self.internal, - self.get_characteristics, - ReturnType::Object, - &[], - ) - }? - .l()?; + env: &mut Env<'local>, + ) -> Result>> { + let obj = self.get_characteristics_obj(env)?; 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(JBluetoothGattCharacteristic::from_env(env, chr)?); + chr_vec.push(env.cast_local::(chr)?); } Ok(chr_vec) } } -pub struct JBluetoothGattCharacteristic<'a> { - internal: JObject<'a>, - get_uuid: JMethodID, - get_properties: JMethodID, - get_value: JMethodID, - get_descriptors: JMethodID, +bind_java_type! { + pub JBluetoothGattCharacteristic => android.bluetooth.BluetoothGattCharacteristic, + methods { + fn get_uuid_obj() -> JObject { + name = "getUuid", + }, + fn get_properties_raw() -> jint { + name = "getProperties", + }, + fn get_value_obj() -> JObject { + name = "getValue", + }, + fn get_descriptors_obj() -> JObject { + name = "getDescriptors", + }, + }, } -impl<'a> JBluetoothGattCharacteristic<'a> { - pub fn from_env(env: &mut Env<'a>, obj: JObject<'a>) -> Result { - let class = env.find_class(jni_str!("android/bluetooth/BluetoothGattCharacteristic"))?; - - let get_uuid = env.get_method_id(&class, jni_str!("getUuid"), jni_sig!("()Ljava/util/UUID;"))?; - let get_properties = env.get_method_id(&class, jni_str!("getProperties"), jni_sig!("()I"))?; - let get_descriptors = env.get_method_id(&class, jni_str!("getDescriptors"), jni_sig!("()Ljava/util/List;"))?; - let get_value = env.get_method_id(&class, jni_str!("getValue"), jni_sig!("()[B"))?; - Ok(Self { - internal: obj, - get_uuid, - get_properties, - get_value, - get_descriptors, - }) - } - - pub fn get_uuid(&self, env: &mut Env<'a>) -> Result { - let obj = unsafe { - env.call_method_unchecked(&self.internal, self.get_uuid, ReturnType::Object, &[]) - }? - .l()?; - let uuid_obj = JUuid::from_env(env, obj)?; +impl<'local> JBluetoothGattCharacteristic<'local> { + pub fn get_uuid(&self, env: &mut Env<'local>) -> Result { + let obj = self.get_uuid_obj(env)?; + let uuid_obj = env.cast_local::(obj)?; uuid_obj.as_uuid(env) } - pub fn get_properties(&self, env: &mut Env<'a>) -> Result { - let flags = unsafe { - env.call_method_unchecked( - &self.internal, - self.get_properties, - ReturnType::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, env: &mut Env<'a>) -> Result> { - let value = unsafe { - env.call_method_unchecked(&self.internal, self.get_value, ReturnType::Array, &[]) - }? - .l()?; + pub fn get_value(&self, env: &mut Env<'local>) -> Result> { + let value = self.get_value_obj(env)?; 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, - env: &mut Env<'a>, - ) -> Result>> { - let obj = unsafe { - env.call_method_unchecked( - &self.internal, - self.get_descriptors, - ReturnType::Object, - &[], - ) - }? - .l()?; + env: &mut Env<'local>, + ) -> Result>> { + let obj = self.get_descriptors_obj(env)?; 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(JBluetoothGattDescriptor::from_env(env, desc)?); + desc_vec.push(env.cast_local::(desc)?); } Ok(desc_vec) } } -pub struct JBluetoothGattDescriptor<'a> { - internal: JObject<'a>, - get_uuid: JMethodID, +bind_java_type! { + pub JBluetoothGattDescriptor => android.bluetooth.BluetoothGattDescriptor, + methods { + fn get_uuid_obj() -> JObject { + name = "getUuid", + }, + }, } -impl<'a> JBluetoothGattDescriptor<'a> { - pub fn from_env(env: &mut Env<'a>, obj: JObject<'a>) -> Result { - let class = env.find_class(jni_str!("android/bluetooth/BluetoothGattDescriptor"))?; - - let get_uuid = env.get_method_id(&class, jni_str!("getUuid"), jni_sig!("()Ljava/util/UUID;"))?; - Ok(Self { - internal: obj, - get_uuid, - }) - } - - pub fn get_uuid(&self, env: &mut Env<'a>) -> Result { - let obj = unsafe { - env.call_method_unchecked(&self.internal, self.get_uuid, ReturnType::Object, &[]) - }? - .l()?; - let uuid_obj = JUuid::from_env(env, obj)?; +impl<'local> JBluetoothGattDescriptor<'local> { + pub fn get_uuid(&self, env: &mut Env<'local>) -> Result { + let obj = self.get_uuid_obj(env)?; + let uuid_obj = env.cast_local::(obj)?; uuid_obj.as_uuid(env) } } -pub struct JBluetoothDevice<'a> { - internal: JObject<'a>, - get_address: JMethodID, -} - -impl<'a> JBluetoothDevice<'a> { - pub fn from_env(env: &mut Env<'a>, obj: JObject<'a>) -> Result { - let class = env.find_class(jni_str!("android/bluetooth/BluetoothDevice"))?; - - let get_address = env.get_method_id(&class, jni_str!("getAddress"), jni_sig!("()Ljava/lang/String;"))?; - Ok(Self { - internal: obj, - get_address, - }) - } - - pub fn get_address(&self, env: &mut Env<'a>) -> Result> { - let obj = unsafe { - env.call_method_unchecked(&self.internal, self.get_address, ReturnType::Object, &[]) - }? - .l()?; - env.cast_local::(obj) - } +bind_java_type! { + pub JBluetoothDevice => android.bluetooth.BluetoothDevice, + methods { + fn get_address() -> JString, + }, } pub struct JScanFilter<'a> { @@ -637,84 +291,33 @@ impl<'a> From> for JObject<'a> { } } -pub struct JScanResult<'a> { - internal: JObject<'a>, - get_device: JMethodID, - get_scan_record: JMethodID, - get_tx_power: JMethodID, - get_rssi: JMethodID, +bind_java_type! { + pub JScanResult => android.bluetooth.le.ScanResult, + methods { + fn get_device_obj() -> JObject { + name = "getDevice", + }, + fn get_scan_record_obj() -> JObject { + name = "getScanRecord", + }, + fn get_tx_power() -> jint, + fn get_rssi() -> jint, + }, } -impl<'a> JScanResult<'a> { - pub fn from_env(env: &mut Env<'a>, obj: JObject<'a>) -> Result { - let class = env.find_class(jni_str!("android/bluetooth/le/ScanResult"))?; - - let get_device = - env.get_method_id(&class, jni_str!("getDevice"), jni_sig!("()Landroid/bluetooth/BluetoothDevice;"))?; - let get_scan_record = env.get_method_id( - &class, - jni_str!("getScanRecord"), - jni_sig!("()Landroid/bluetooth/le/ScanRecord;"), - )?; - let get_tx_power = env.get_method_id(&class, jni_str!("getTxPower"), jni_sig!("()I"))?; - let get_rssi = env.get_method_id(&class, jni_str!("getRssi"), jni_sig!("()I"))?; - Ok(Self { - internal: obj, - get_device, - get_scan_record, - get_tx_power, - get_rssi, - }) - } - - pub fn get_device(&self, env: &mut Env<'a>) -> Result> { - let obj = unsafe { - env.call_method_unchecked(&self.internal, self.get_device, ReturnType::Object, &[]) - }? - .l()?; - JBluetoothDevice::from_env(env, obj) - } - - pub fn get_scan_record(&self, env: &mut Env<'a>) -> Result> { - let obj = unsafe { - env.call_method_unchecked( - &self.internal, - self.get_scan_record, - ReturnType::Object, - &[], - ) - }? - .l()?; - JScanRecord::from_env(env, obj) - } - - pub fn get_tx_power(&self, env: &mut Env<'a>) -> Result { - unsafe { - env.call_method_unchecked( - &self.internal, - self.get_tx_power, - ReturnType::Primitive(Primitive::Int), - &[], - ) - }? - .i() +impl<'local> JScanResult<'local> { + pub fn get_device(&self, env: &mut Env<'local>) -> Result> { + let obj = self.get_device_obj(env)?; + env.cast_local::(obj) } - pub fn get_rssi(&self, env: &mut Env<'a>) -> Result { - unsafe { - env.call_method_unchecked( - &self.internal, - self.get_rssi, - ReturnType::Primitive(Primitive::Int), - &[], - ) - }? - .i() + pub fn get_scan_record(&self, env: &mut Env<'local>) -> Result> { + self.get_scan_record_obj(env) } pub fn to_peripheral_properties( &self, - env: &mut Env<'a>, + env: &mut Env<'local>, ) -> std::result::Result<(BDAddr, Option), crate::Error> { use std::str::FromStr; @@ -723,11 +326,11 @@ impl<'a> JScanResult<'a> { let addr_str = String::from(addr_jstr.mutf8_chars(env)?); let addr = BDAddr::from_str(&addr_str)?; - let record = self.get_scan_record(env)?; - let record_is_null = env.is_same_object(&*record, JObject::null())?; - let properties = if record_is_null { + let record_obj = self.get_scan_record(env)?; + let properties = if record_obj.is_null() { None } else { + 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 @@ -752,13 +355,14 @@ impl<'a> JScanResult<'a> { let rssi = Some(self.get_rssi(env)? as i16); - let manufacturer_specific_data_obj = record.get_manufacturer_specific_data(env)?; + let mfr_data_obj = record.get_manufacturer_specific_data(env)?; let mut manufacturer_data = HashMap::new(); - if !env.is_same_object(&*manufacturer_specific_data_obj, JObject::null())? { - let size = manufacturer_specific_data_obj.size(env)?; + 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 = manufacturer_specific_data_obj.key_at(env, i)?; - let value = manufacturer_specific_data_obj.value_at(env, i)?; + 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 = @@ -794,7 +398,7 @@ impl<'a> JScanResult<'a> { let value = env .call_method(&entry, jni_str!("getValue"), jni_sig!("()Ljava/lang/Object;"), &[])? .l()?; - let parcel_uuid = JParcelUuid::from_env(env, key)?; + let parcel_uuid = env.cast_local::(key)?; let juuid = parcel_uuid.get_uuid(env)?; let uuid = juuid.as_uuid(env)?; let value_arr = @@ -820,7 +424,7 @@ impl<'a> JScanResult<'a> { &[jni::objects::JValue::from(i)], )? .l()?; - let parcel_uuid = JParcelUuid::from_env(env, obj)?; + let parcel_uuid = env.cast_local::(obj)?; let juuid = parcel_uuid.get_uuid(env)?; let uuid = juuid.as_uuid(env)?; services.push(uuid); @@ -844,212 +448,38 @@ impl<'a> JScanResult<'a> { } } -pub struct JScanRecord<'a> { - internal: JObject<'a>, - get_device_name: JMethodID, - get_tx_power_level: JMethodID, - get_manufacturer_specific_data: JMethodID, - get_service_data: JMethodID, - get_service_uuids: JMethodID, +bind_java_type! { + pub JScanRecord => android.bluetooth.le.ScanRecord, + methods { + fn get_device_name() -> JObject, + fn get_tx_power_level() -> jint, + fn get_manufacturer_specific_data() -> JObject, + fn get_service_data() -> JObject, + fn get_service_uuids() -> JObject, + }, } -impl<'a> From> for JObject<'a> { - fn from(scan_record: JScanRecord<'a>) -> Self { - scan_record.internal - } +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> ::std::ops::Deref for JScanRecord<'a> { - type Target = JObject<'a>; - - fn deref(&self) -> &Self::Target { - &self.internal - } +bind_java_type! { + pub JParcelUuid => android.os.ParcelUuid, + methods { + fn get_uuid_obj() -> JObject { + name = "getUuid", + }, + }, } -impl<'a> JScanRecord<'a> { - pub fn from_env(env: &mut Env<'a>, obj: JObject<'a>) -> Result { - let class = env.find_class(jni_str!("android/bluetooth/le/ScanRecord"))?; - - let get_device_name = env.get_method_id(&class, jni_str!("getDeviceName"), jni_sig!("()Ljava/lang/String;"))?; - let get_tx_power_level = env.get_method_id(&class, jni_str!("getTxPowerLevel"), jni_sig!("()I"))?; - let get_manufacturer_specific_data = env.get_method_id( - &class, - jni_str!("getManufacturerSpecificData"), - jni_sig!("()Landroid/util/SparseArray;"), - )?; - let get_service_data = env.get_method_id(&class, jni_str!("getServiceData"), jni_sig!("()Ljava/util/Map;"))?; - let get_service_uuids = - env.get_method_id(&class, jni_str!("getServiceUuids"), jni_sig!("()Ljava/util/List;"))?; - Ok(Self { - internal: obj, - get_device_name, - get_tx_power_level, - get_manufacturer_specific_data, - get_service_data, - get_service_uuids, - }) - } - - pub fn get_device_name(&self, env: &mut Env<'a>) -> Result> { - unsafe { - env.call_method_unchecked( - &self.internal, - self.get_device_name, - ReturnType::Object, - &[], - ) - }? - .l() - } - - pub fn get_tx_power_level(&self, env: &mut Env<'a>) -> Result { - unsafe { - env.call_method_unchecked( - &self.internal, - self.get_tx_power_level, - ReturnType::Primitive(Primitive::Int), - &[], - ) - }? - .i() - } - - pub fn get_manufacturer_specific_data( - &self, - env: &mut Env<'a>, - ) -> Result> { - let obj = unsafe { - env.call_method_unchecked( - &self.internal, - self.get_manufacturer_specific_data, - ReturnType::Object, - &[], - ) - }? - .l()?; - JSparseArray::from_env(env, obj) - } - - pub fn get_service_data(&self, env: &mut Env<'a>) -> Result> { - unsafe { - env.call_method_unchecked( - &self.internal, - self.get_service_data, - ReturnType::Object, - &[], - ) - }? - .l() - } - - pub fn get_service_uuids(&self, env: &mut Env<'a>) -> Result> { - unsafe { - env.call_method_unchecked( - &self.internal, - self.get_service_uuids, - ReturnType::Object, - &[], - ) - }? - .l() - } -} - -pub struct JSparseArray<'a> { - internal: JObject<'a>, - size: JMethodID, - key_at: JMethodID, - value_at: JMethodID, -} - -impl<'a> From> for JObject<'a> { - fn from(sparse_array: JSparseArray<'a>) -> Self { - sparse_array.internal - } -} - -impl<'a> ::std::ops::Deref for JSparseArray<'a> { - type Target = JObject<'a>; - - fn deref(&self) -> &Self::Target { - &self.internal - } -} - -impl<'a> JSparseArray<'a> { - pub fn from_env(env: &mut Env<'a>, obj: JObject<'a>) -> Result { - let class = env.find_class(jni_str!("android/util/SparseArray"))?; - - let size = env.get_method_id(&class, jni_str!("size"), jni_sig!("()I"))?; - let key_at = env.get_method_id(&class, jni_str!("keyAt"), jni_sig!("(I)I"))?; - let value_at = env.get_method_id(&class, jni_str!("valueAt"), jni_sig!("(I)Ljava/lang/Object;"))?; - Ok(Self { - internal: obj, - size, - key_at, - value_at, - }) - } - - pub fn size(&self, env: &mut Env<'a>) -> Result { - unsafe { - env.call_method_unchecked( - &self.internal, - self.size, - ReturnType::Primitive(Primitive::Int), - &[], - ) - }? - .i() - } - - pub fn key_at(&self, env: &mut Env<'a>, index: jint) -> Result { - unsafe { - env.call_method_unchecked( - &self.internal, - self.key_at, - ReturnType::Primitive(Primitive::Int), - &[jvalue { i: index }], - ) - }? - .i() - } - - pub fn value_at(&self, env: &mut Env<'a>, index: jint) -> Result> { - unsafe { - env.call_method_unchecked( - &self.internal, - self.value_at, - ReturnType::Object, - &[jvalue { i: index }], - ) - }? - .l() - } -} - -pub struct JParcelUuid<'a> { - internal: JObject<'a>, - get_uuid: JMethodID, -} - -impl<'a> JParcelUuid<'a> { - pub fn from_env(env: &mut Env<'a>, obj: JObject<'a>) -> Result { - let class = env.find_class(jni_str!("android/os/ParcelUuid"))?; - - let get_uuid = env.get_method_id(&class, jni_str!("getUuid"), jni_sig!("()Ljava/util/UUID;"))?; - Ok(Self { - internal: obj, - get_uuid, - }) - } - - pub fn get_uuid(&self, env: &mut Env<'a>) -> Result> { - let obj = unsafe { - env.call_method_unchecked(&self.internal, self.get_uuid, ReturnType::Object, &[]) - }? - .l()?; - JUuid::from_env(env, obj) +impl<'local> JParcelUuid<'local> { + pub fn get_uuid(&self, env: &mut Env<'local>) -> Result> { + let obj = self.get_uuid_obj(env)?; + env.cast_local::(obj) } } diff --git a/src/droidplug/jni_utils/future.rs b/src/droidplug/jni_utils/future.rs index 2564fcd1..62b6766c 100644 --- a/src/droidplug/jni_utils/future.rs +++ b/src/droidplug/jni_utils/future.rs @@ -1,10 +1,8 @@ use ::jni::{ Env, JavaVM, + bind_java_type, errors::Result, - jni_sig, jni_str, - objects::{Global, JMethodID, JObject}, - signature::ReturnType, - sys::jvalue, + objects::{Global, JObject}, }; use static_assertions::assert_impl_all; use std::{ @@ -13,82 +11,29 @@ use std::{ task::{Context, Poll}, }; -pub struct JFuture<'a> { - internal: JObject<'a>, - poll_id: JMethodID, -} - -impl<'a> JFuture<'a> { - pub fn from_env(env: &mut Env<'a>, obj: JObject<'a>) -> Result { - let class = - super::classcache::get_class("io/github/gedgygedgy/rust/future/Future").unwrap(); - let poll_id = env.get_method_id( - class.as_ref(), - jni_str!("poll"), - jni_sig!("(Lio/github/gedgygedgy/rust/task/Waker;)Lio/github/gedgygedgy/rust/task/PollResult;"), - )?; - Ok(Self { - internal: obj, - poll_id, - }) - } - - pub fn poll(&self, env: &mut Env<'a>, waker: &JObject<'_>) -> Result> { - let result = unsafe { - env.call_method_unchecked( - &self.internal, - self.poll_id, - ReturnType::Object, - &[jvalue { - l: waker.as_raw(), - }], - ) - }? - .l()?; - Ok(result) - } -} - -impl<'a> ::std::ops::Deref for JFuture<'a> { - type Target = JObject<'a>; - - fn deref(&self) -> &Self::Target { - &self.internal - } -} - -impl<'a> From> for JObject<'a> { - fn from(other: JFuture<'a>) -> JObject<'a> { - other.internal - } +bind_java_type! { + pub JFuture => io.github.gedgygedgy.rust.future.Future, + methods { + fn poll(waker: JObject) -> JObject, + }, } pub struct JSendFuture { internal: Global>, - poll_id: JMethodID, vm: JavaVM, } impl JSendFuture { pub fn new(env: &mut Env, future: &JFuture) -> Result { Ok(Self { - internal: env.new_global_ref(&future.internal)?, - poll_id: future.poll_id, + internal: env.new_global_ref(&**future)?, vm: env.get_java_vm()?, }) } pub fn from_env(env: &mut Env, obj: &JObject) -> Result { - let class = - super::classcache::get_class("io/github/gedgygedgy/rust/future/Future").unwrap(); - let poll_id = env.get_method_id( - class.as_ref(), - jni_str!("poll"), - jni_sig!("(Lio/github/gedgygedgy/rust/task/Waker;)Lio/github/gedgygedgy/rust/task/PollResult;"), - )?; Ok(Self { internal: env.new_global_ref(obj)?, - poll_id, vm: env.get_java_vm()?, }) } @@ -96,17 +41,9 @@ impl JSendFuture { fn poll_internal(&self, context: &mut Context<'_>) -> Result>>>> { self.vm.attach_current_thread(|env| { let jwaker = super::task::waker(env, context.waker().clone())?; - let result = unsafe { - env.call_method_unchecked( - self.internal.as_obj(), - self.poll_id, - ReturnType::Object, - &[jvalue { - l: jwaker.as_raw(), - }], - ) - }? - .l()?; + 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 { @@ -166,7 +103,7 @@ mod test { .new_object(jni_str!("io/github/gedgygedgy/rust/future/SimpleFuture"), jni_sig!("()V"), &[]) .unwrap(); let future_local = env.new_local_ref(&future_obj).unwrap(); - let jfuture = JFuture::from_env(env, future_local).unwrap(); + let jfuture = env.cast_local::(future_local).unwrap(); let mut future = JSendFuture::new(env, &jfuture).unwrap(); assert!( @@ -196,7 +133,7 @@ mod test { if let Poll::Ready(result) = poll { let global = result.unwrap(); let local = env.new_local_ref(global.as_obj()).unwrap(); - let poll_result = JPollResult::from_env(env, local).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 { @@ -209,7 +146,7 @@ mod test { if let Poll::Ready(result) = poll { let global = result.unwrap(); let local = env.new_local_ref(global.as_obj()).unwrap(); - let poll_result = JPollResult::from_env(env, local).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 { @@ -233,7 +170,7 @@ mod test { .unwrap(); let future_obj_global = env.new_global_ref(&future_obj).unwrap(); let future_local = env.new_local_ref(&future_obj).unwrap(); - let jfuture = JFuture::from_env(env, future_local).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(); @@ -260,7 +197,7 @@ mod test { let global = future.await.unwrap(); test_utils::with_env(|env| { let local = env.new_local_ref(global.as_obj()).unwrap(); - let poll_result = JPollResult::from_env(env, local).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()); @@ -281,7 +218,7 @@ mod test { .unwrap(); let future_obj_global = env.new_global_ref(&future_obj).unwrap(); let future_local = env.new_local_ref(&future_obj).unwrap(); - let jfuture = JFuture::from_env(env, future_local).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(); @@ -310,7 +247,7 @@ mod test { let global = future.await.unwrap(); test_utils::with_env(|env| { let local = env.new_local_ref(global.as_obj()).unwrap(); - let poll_result = JPollResult::from_env(env, local).unwrap(); + let poll_result = env.cast_local::(local).unwrap(); let _err = poll_result.get(env).unwrap_err(); let future_ex = env.exception_occurred().unwrap(); @@ -365,7 +302,7 @@ mod test { let global_ref = future.await.unwrap(); test_utils::with_env(|env| { let local = env.new_local_ref(global_ref.as_obj()).unwrap(); - let jpoll = JPollResult::from_env(env, local).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()); diff --git a/src/droidplug/jni_utils/stream.rs b/src/droidplug/jni_utils/stream.rs index af1d4704..644ff7c4 100644 --- a/src/droidplug/jni_utils/stream.rs +++ b/src/droidplug/jni_utils/stream.rs @@ -1,11 +1,9 @@ use super::task::JPollResult; use ::jni::{ Env, JavaVM, + bind_java_type, errors::Result, - jni_sig, jni_str, - objects::{Global, JMethodID, JObject}, - signature::ReturnType, - sys::jvalue, + objects::{Global, JObject}, }; use futures::stream::Stream; use static_assertions::assert_impl_all; @@ -14,86 +12,36 @@ use std::{ task::{Context, Poll}, }; -pub struct JStream<'a> { - internal: JObject<'a>, - poll_next_id: JMethodID, +bind_java_type! { + pub JStream => io.github.gedgygedgy.rust.stream.Stream, + methods { + fn poll_next(waker: JObject) -> JObject, + }, } -impl<'a> JStream<'a> { - pub fn from_env(env: &mut Env<'a>, obj: JObject<'a>) -> Result { - let class = - super::classcache::get_class("io/github/gedgygedgy/rust/stream/Stream").unwrap(); - let poll_next_id = env.get_method_id( - class.as_ref(), - jni_str!("pollNext"), - jni_sig!("(Lio/github/gedgygedgy/rust/task/Waker;)Lio/github/gedgygedgy/rust/task/PollResult;"), - )?; - Ok(Self { - internal: obj, - poll_next_id, - }) - } - - pub fn poll_next_with_env( - &self, - env: &mut Env<'a>, - waker: &JObject<'_>, - ) -> Result> { - let result = unsafe { - env.call_method_unchecked( - &self.internal, - self.poll_next_id, - ReturnType::Object, - &[jvalue { - l: waker.as_raw(), - }], - ) - }? - .l()?; - Ok(result) - } -} - -impl<'a> ::std::ops::Deref for JStream<'a> { - type Target = JObject<'a>; - - fn deref(&self) -> &Self::Target { - &self.internal - } -} - -impl<'a> From> for JObject<'a> { - fn from(other: JStream<'a>) -> JObject<'a> { - other.internal - } +bind_java_type! { + JStreamPoll => io.github.gedgygedgy.rust.stream.StreamPoll, + methods { + fn get() -> JObject, + }, } pub struct JSendStream { internal: Global>, - poll_next_id: JMethodID, vm: JavaVM, } impl JSendStream { pub fn new(env: &mut Env, stream: &JStream) -> Result { Ok(Self { - internal: env.new_global_ref(&stream.internal)?, - poll_next_id: stream.poll_next_id, + internal: env.new_global_ref(&**stream)?, vm: env.get_java_vm()?, }) } pub fn from_env(env: &mut Env, obj: &JObject) -> Result { - let class = - super::classcache::get_class("io/github/gedgygedgy/rust/stream/Stream").unwrap(); - let poll_next_id = env.get_method_id( - class.as_ref(), - jni_str!("pollNext"), - jni_sig!("(Lio/github/gedgygedgy/rust/task/Waker;)Lio/github/gedgygedgy/rust/task/PollResult;"), - )?; Ok(Self { internal: env.new_global_ref(obj)?, - poll_next_id, vm: env.get_java_vm()?, }) } @@ -104,30 +52,22 @@ impl JSendStream { ) -> Result>>>>> { self.vm.attach_current_thread(|env| { let jwaker = super::task::waker(env, context.waker().clone())?; - let result = unsafe { - env.call_method_unchecked( - self.internal.as_obj(), - self.poll_next_id, - ReturnType::Object, - &[jvalue { - l: jwaker.as_raw(), - }], - ) - }? - .l()?; + 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); } - let poll_result = JPollResult::from_env(env, result)?; + let poll_result = env.cast_local::(result)?; let stream_poll_obj = poll_result.get(env)?; if env.is_same_object(&stream_poll_obj, JObject::null())? { return Ok(Poll::Ready(None)); } - let stream_poll = JStreamPoll::from_env(env, stream_poll_obj)?; + 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)?)))) }) @@ -155,29 +95,6 @@ impl Stream for JSendStream { assert_impl_all!(JSendStream: Send); -struct JStreamPoll<'a> { - internal: JObject<'a>, - get: JMethodID, -} - -impl<'a> JStreamPoll<'a> { - pub fn from_env(env: &mut Env<'a>, obj: JObject<'a>) -> Result { - let class = - super::classcache::get_class("io/github/gedgygedgy/rust/stream/StreamPoll").unwrap(); - let get = env.get_method_id( - class.as_ref(), - jni_str!("get"), - jni_sig!("()Ljava/lang/Object;"), - )?; - Ok(Self { internal: obj, get }) - } - - pub fn get(&self, env: &mut Env<'a>) -> Result> { - unsafe { env.call_method_unchecked(&self.internal, self.get, ReturnType::Object, &[]) }? - .l() - } -} - #[cfg(test)] mod test { use super::super::test_utils; @@ -206,7 +123,7 @@ mod test { .new_object(jni_str!("io/github/gedgygedgy/rust/stream/QueueStream"), jni_sig!("()V"), &[]) .unwrap(); let stream_local = env.new_local_ref(&stream_obj).unwrap(); - let jstream = JStream::from_env(env, stream_local).unwrap(); + let jstream = env.cast_local::(stream_local).unwrap(); let mut stream = JSendStream::new(env, &jstream).unwrap(); assert!( @@ -293,7 +210,7 @@ mod test { .unwrap(); let stream_obj_global = env.new_global_ref(&stream_obj).unwrap(); let stream_local = env.new_local_ref(&stream_obj).unwrap(); - let jstream = JStream::from_env(env, stream_local).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(); diff --git a/src/droidplug/jni_utils/task.rs b/src/droidplug/jni_utils/task.rs index e412f55b..92a777c4 100644 --- a/src/droidplug/jni_utils/task.rs +++ b/src/droidplug/jni_utils/task.rs @@ -1,9 +1,9 @@ use ::jni::{ Env, + bind_java_type, errors::Result, - jni_sig, jni_str, - objects::{JMethodID, JObject}, - signature::ReturnType, + jni_sig, + objects::JObject, }; use std::task::Waker; @@ -19,37 +19,11 @@ pub fn waker<'a>(env: &mut Env<'a>, waker: Waker) -> Result> { Ok(obj) } -pub struct JPollResult<'a> { - internal: JObject<'a>, - get: JMethodID, -} - -impl<'a> JPollResult<'a> { - pub fn from_env(env: &mut Env<'a>, obj: JObject<'a>) -> Result { - let class = - super::classcache::get_class("io/github/gedgygedgy/rust/task/PollResult").unwrap(); - let get = env.get_method_id(class.as_ref(), jni_str!("get"), jni_sig!("()Ljava/lang/Object;"))?; - Ok(Self { internal: obj, get }) - } - - pub fn get(&self, env: &mut Env<'a>) -> Result> { - unsafe { env.call_method_unchecked(&self.internal, self.get, ReturnType::Object, &[]) }? - .l() - } -} - -impl<'a> ::std::ops::Deref for JPollResult<'a> { - type Target = JObject<'a>; - - fn deref(&self) -> &Self::Target { - &self.internal - } -} - -impl<'a> From> for JObject<'a> { - fn from(other: JPollResult<'a>) -> JObject<'a> { - other.internal - } +bind_java_type! { + pub JPollResult => io.github.gedgygedgy.rust.task.PollResult, + methods { + fn get() -> JObject, + }, } #[cfg(test)] diff --git a/src/droidplug/jni_utils/uuid.rs b/src/droidplug/jni_utils/uuid.rs index 48618684..840c1538 100644 --- a/src/droidplug/jni_utils/uuid.rs +++ b/src/droidplug/jni_utils/uuid.rs @@ -1,89 +1,40 @@ use jni::{ Env, + bind_java_type, errors::Result, - jni_str, jni_sig, - objects::{JMethodID, JObject}, - signature::{Primitive, ReturnType}, sys::jlong, }; use uuid::Uuid; -pub struct JUuid<'a> { - internal: JObject<'a>, - get_least_significant_bits: JMethodID, - get_most_significant_bits: JMethodID, +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> JUuid<'a> { - pub fn from_env(env: &mut Env<'a>, obj: JObject<'a>) -> Result { - let class = env.find_class(jni_str!("java/util/UUID"))?; - let get_least_significant_bits = - env.get_method_id(&class, jni_str!("getLeastSignificantBits"), jni_sig!("()J"))?; - let get_most_significant_bits = - env.get_method_id(&class, jni_str!("getMostSignificantBits"), jni_sig!("()J"))?; - Ok(Self { - internal: obj, - get_least_significant_bits, - get_most_significant_bits, - }) - } - - pub fn new(env: &mut Env<'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.find_class(jni_str!("java/util/UUID"))?; - let obj = env.new_object(&class, jni_sig!("(JJ)V"), &[most.into(), least.into()])?; - let get_least_significant_bits = - env.get_method_id(&class, jni_str!("getLeastSignificantBits"), jni_sig!("()J"))?; - let get_most_significant_bits = - env.get_method_id(&class, jni_str!("getMostSignificantBits"), jni_sig!("()J"))?; - Ok(Self { - internal: obj, - get_least_significant_bits, - get_most_significant_bits, - }) + JUuid::with_bits(env, most, least) } +} - pub fn as_uuid(&self, env: &mut Env<'a>) -> Result { - let least = unsafe { - env.call_method_unchecked( - &self.internal, - self.get_least_significant_bits, - ReturnType::Primitive(Primitive::Long), - &[], - ) - }? - .j()? as u64; - let most = unsafe { - env.call_method_unchecked( - &self.internal, - self.get_most_significant_bits, - ReturnType::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)) } } -impl<'a> ::std::ops::Deref for JUuid<'a> { - type Target = JObject<'a>; - - fn deref(&self) -> &Self::Target { - &self.internal - } -} - -impl<'a> From> for JObject<'a> { - fn from(other: JUuid<'a>) -> JObject<'a> { - other.internal - } -} - #[cfg(test)] mod test { use super::super::test_utils; @@ -147,7 +98,7 @@ mod test { let obj = env .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(env).unwrap(), Uuid::from_u128(test.uuid)); } diff --git a/src/droidplug/peripheral.rs b/src/droidplug/peripheral.rs index 81cd54fb..20547a7a 100644 --- a/src/droidplug/peripheral.rs +++ b/src/droidplug/peripheral.rs @@ -30,7 +30,7 @@ use std::{ sync::{Arc, Mutex}, }; use super::jni::{ - global_jvm, + jvm, objects::{JBluetoothGattCharacteristic, JBluetoothGattService, JPeripheral}, }; @@ -52,7 +52,7 @@ fn get_poll_result<'a>( result_ref: &Global>, ) -> Result> { let result_obj = env.new_local_ref(result_ref)?; - let poll_result = JPollResult::from_env(env, result_obj)?; + let poll_result = env.cast_local::(result_obj)?; match poll_result.get(env) { Ok(obj) => Ok(obj), @@ -135,7 +135,7 @@ pub struct Peripheral { impl Peripheral { pub(crate) fn new<'a>(env: &mut Env<'a>, adapter: JObject<'a>, addr: BDAddr) -> Result { - let obj = JPeripheral::new(env, adapter, addr)?; + let obj = JPeripheral::create(env, adapter, addr)?; let internal = Arc::new(env.new_global_ref(&*obj)?); Ok(Self { addr, @@ -162,9 +162,9 @@ impl Peripheral { where E: From<::jni::errors::Error>, { - global_jvm().attach_current_thread(|env| { + jvm()?.attach_current_thread(|env| { let local_obj = env.new_local_ref(self.internal.as_obj())?; - let obj = JPeripheral::from_env(env, local_obj)?; + let obj = env.cast_local::(local_obj)?; f(env, &obj) }) } @@ -241,7 +241,7 @@ impl api::Peripheral for Peripheral { { let mtu_future = self.with_obj(|env, obj| { let mtu_obj = obj.request_mtu(env, 517)?; - let mtu_future = JFuture::from_env(env, mtu_obj)?; + let mtu_future = env.cast_local::(mtu_obj)?; JSendFuture::new(env, &mtu_future) })?; let mtu_result_ref = mtu_future.await?; @@ -287,7 +287,7 @@ impl api::Peripheral for Peripheral { let svc_obj = env .call_method(&obj, jni_str!("get"), jni_sig!("(I)Ljava/lang/Object;"), &[JValue::from(i)])? .l()?; - let service = JBluetoothGattService::from_env(env, svc_obj)?; + let service = env.cast_local::(svc_obj)?; let mut characteristics = BTreeSet::::new(); for characteristic in service.get_characteristics(env)? { let mut descriptors = BTreeSet::new(); @@ -381,10 +381,10 @@ impl api::Peripheral for Peripheral { let stream = stream .map(move |item| match item { Ok(item) => { - global_jvm().attach_current_thread(|env| { + jvm()?.attach_current_thread(|env| { let local_obj = env.new_local_ref(item.as_obj())?; let characteristic = - JBluetoothGattCharacteristic::from_env(env, local_obj)?; + env.cast_local::(local_obj)?; let uuid = characteristic.get_uuid(env)?; let value = characteristic.get_value(env)?; let service_uuid = shared @@ -414,7 +414,7 @@ impl api::Peripheral for Peripheral { async fn read_rssi(&self) -> Result { let future = self.with_obj(|env, obj| { let rssi_obj = obj.read_remote_rssi(env)?; - let rssi_future = JFuture::from_env(env, rssi_obj)?; + let rssi_future = env.cast_local::(rssi_obj)?; JSendFuture::new(env, &rssi_future) })?; let result_ref = future.await?; From 1067a0fc59608be0898fae40d2816b384d3e2479 Mon Sep 17 00:00:00 2001 From: Kyle Machulis Date: Sun, 26 Apr 2026 22:22:37 -0700 Subject: [PATCH 13/16] refactor: Replace adapter extern C callbacks with native_method! macro The macro generates extern "system" trampolines with automatic catch_unwind and error-to-Java-exception propagation, replacing the manual EnvUnowned pattern that silently swallowed errors. Co-Authored-By: Claude Opus 4.6 --- src/droidplug/jni/mod.rs | 54 +++++++++++++++++++--------------------- 1 file changed, 25 insertions(+), 29 deletions(-) diff --git a/src/droidplug/jni/mod.rs b/src/droidplug/jni/mod.rs index 989c6795..21adee1d 100644 --- a/src/droidplug/jni/mod.rs +++ b/src/droidplug/jni/mod.rs @@ -1,6 +1,6 @@ pub mod objects; -use ::jni::{Env, EnvUnowned, NativeMethod, jni_str, objects::JObject}; +use ::jni::{Env, NativeMethod, jni_str, native_method, objects::JObject}; use jni::{objects::JString, sys::jboolean}; use std::ffi::c_void; use std::sync::Once; @@ -24,16 +24,16 @@ fn init_inner(env: &mut Env) -> crate::Result<()> { unsafe { env.register_native_methods( &adapter_class, &[ - NativeMethod::from_raw_parts( - jni_str!("reportScanResult"), - jni_str!("(Landroid/bluetooth/le/ScanResult;)V"), - adapter_report_scan_result as *mut c_void, - ), - NativeMethod::from_raw_parts( - jni_str!("onConnectionStateChanged"), - jni_str!("(Ljava/lang/String;Z)V"), - adapter_on_connection_state_changed as *mut c_void, - ), + native_method! { + name = "reportScanResult", + sig = (scan_result: JObject) -> (), + fn = adapter_report_scan_result, + }, + native_method! { + name = "onConnectionStateChanged", + sig = (addr: JString, connected: jboolean) -> (), + fn = adapter_on_connection_state_changed, + }, ], )? }; super::jni_utils::classcache::find_add_class( @@ -140,25 +140,21 @@ impl From<::jni::errors::Error> for crate::Error { } } -extern "C" fn adapter_report_scan_result<'a>(mut env: EnvUnowned<'a>, obj: JObject, scan_result: JObject<'a>) { - 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(); +fn adapter_report_scan_result<'local>( + env: &mut Env<'local>, + obj: JObject<'local>, + scan_result: JObject<'local>, +) -> jni::errors::Result<()> { + super::adapter::adapter_report_scan_result_internal(env, &obj, scan_result) + .map_err(|e| jni::errors::Error::Other(Box::new(e))) } -extern "C" fn adapter_on_connection_state_changed( - mut env: EnvUnowned, - obj: JObject, - addr: JString, +fn adapter_on_connection_state_changed<'local>( + env: &mut Env<'local>, + obj: JObject<'local>, + addr: JString<'local>, connected: jboolean, -) { - let outcome = env.with_env(|env| { - let _ = super::adapter::adapter_on_connection_state_changed_internal( - env, &obj, addr, connected, - ); - Ok::<(), jni::errors::Error>(()) - }); - let _ = outcome.into_outcome(); +) -> jni::errors::Result<()> { + super::adapter::adapter_on_connection_state_changed_internal(env, &obj, addr, connected) + .map_err(|e| jni::errors::Error::Other(Box::new(e))) } From 2b946633a59c80ae004117fed080fd81160fd981 Mon Sep 17 00:00:00 2001 From: Kyle Machulis Date: Sun, 26 Apr 2026 22:30:01 -0700 Subject: [PATCH 14/16] refactor: Eliminate classcache module, use bind_java_type! class caching Replace the hand-rolled DashMap-based class cache with bind_java_type!'s built-in global class caching via Reference::lookup_class(). Add bare bind_java_type! declarations for exception types, FnAdapter, and other utility classes previously only tracked in classcache. Delete classcache.rs entirely. Co-Authored-By: Claude Opus 4.6 --- src/droidplug/adapter.rs | 10 +-- src/droidplug/jni/mod.rs | 102 ++++++++------------------ src/droidplug/jni/objects.rs | 40 ++++++++-- src/droidplug/jni_utils/classcache.rs | 20 ----- src/droidplug/jni_utils/future.rs | 4 + src/droidplug/jni_utils/mod.rs | 41 ++++++----- src/droidplug/jni_utils/ops.rs | 43 +++++++---- src/droidplug/jni_utils/stream.rs | 2 +- src/droidplug/jni_utils/task.rs | 10 ++- src/droidplug/peripheral.rs | 46 ++++++------ 10 files changed, 154 insertions(+), 164 deletions(-) delete mode 100644 src/droidplug/jni_utils/classcache.rs diff --git a/src/droidplug/adapter.rs b/src/droidplug/adapter.rs index ec424397..af98ce09 100644 --- a/src/droidplug/adapter.rs +++ b/src/droidplug/adapter.rs @@ -150,12 +150,12 @@ impl Central for Adapter { let ex = env.exception_occurred().unwrap(); env.exception_clear(); - let no_adapter_class = super::jni_utils::classcache::get_class( - "com/nonpolynomial/btleplug/android/impl/NoBluetoothAdapterException", - ) - .unwrap(); + let no_adapter_class = ::lookup_class( + env, + &Default::default(), + )?; - if env.is_instance_of(&ex, no_adapter_class.as_ref())? { + 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 diff --git a/src/droidplug/jni/mod.rs b/src/droidplug/jni/mod.rs index 21adee1d..27531fc4 100644 --- a/src/droidplug/jni/mod.rs +++ b/src/droidplug/jni/mod.rs @@ -1,6 +1,6 @@ pub mod objects; -use ::jni::{Env, NativeMethod, jni_str, native_method, objects::JObject}; +use ::jni::{Env, NativeMethod, jni_str, native_method, objects::{JObject, Reference}}; use jni::{objects::JString, sys::jboolean}; use std::ffi::c_void; use std::sync::Once; @@ -36,82 +36,38 @@ fn init_inner(env: &mut Env) -> crate::Result<()> { }, ], )? }; - 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.find_class(jni_str!("io/github/gedgygedgy/rust/ops/FnAdapter"))?; + let fn_adapter_class = ::lookup_class(env, &loader)?; unsafe { env.register_native_methods( - &fn_adapter_class, + &*fn_adapter_class, &[ NativeMethod::from_raw_parts( jni_str!("callInternal"), diff --git a/src/droidplug/jni/objects.rs b/src/droidplug/jni/objects.rs index da319d8f..74509fa3 100644 --- a/src/droidplug/jni/objects.rs +++ b/src/droidplug/jni/objects.rs @@ -3,7 +3,7 @@ use jni::{ Env, bind_java_type, errors::Result, - jni_sig, jni_str, + jni_sig, objects::{JObject, JString}, sys::jint, }; @@ -12,6 +12,34 @@ use uuid::Uuid; use crate::api::{BDAddr, CharPropFlags, PeripheralProperties, ScanFilter}; +bind_java_type! { + pub JNotConnectedException => com.nonpolynomial.btleplug.android.impl.NotConnectedException, +} + +bind_java_type! { + pub JPermissionDeniedException => com.nonpolynomial.btleplug.android.impl.PermissionDeniedException, +} + +bind_java_type! { + pub JUnexpectedCallbackException => com.nonpolynomial.btleplug.android.impl.UnexpectedCallbackException, +} + +bind_java_type! { + pub JUnexpectedCharacteristicException => com.nonpolynomial.btleplug.android.impl.UnexpectedCharacteristicException, +} + +bind_java_type! { + pub JNoSuchCharacteristicException => com.nonpolynomial.btleplug.android.impl.NoSuchCharacteristicException, +} + +bind_java_type! { + pub JNoBluetoothAdapterException => com.nonpolynomial.btleplug.android.impl.NoBluetoothAdapterException, +} + +bind_java_type! { + pub JScanFilterClass => com.nonpolynomial.btleplug.android.impl.ScanFilter, +} + bind_java_type! { pub JPeripheral => com.nonpolynomial.btleplug.android.impl.Peripheral, constructors { @@ -272,12 +300,12 @@ impl<'a> JScanFilter<'a> { let uuid_str = env.new_string(uuid.to_string())?; uuids.set_element(env, idx, &uuid_str)?; } - let class_static = crate::droidplug::jni_utils::classcache::get_class( - "com/nonpolynomial/btleplug/android/impl/ScanFilter", - ) - .unwrap(); + let class = ::lookup_class( + env, + &Default::default(), + )?; let obj = env.new_object( - &**class_static, + &*class, jni_sig!("([Ljava/lang/String;)V"), &[(&uuids).into()], )?; diff --git a/src/droidplug/jni_utils/classcache.rs b/src/droidplug/jni_utils/classcache.rs deleted file mode 100644 index a7db0ee5..00000000 --- a/src/droidplug/jni_utils/classcache.rs +++ /dev/null @@ -1,20 +0,0 @@ -use dashmap::DashMap; -use jni::{Env, errors::Result, objects::{Global, JClass}, strings::JNIString}; -use once_cell::sync::OnceCell; -use std::sync::Arc; - -static CLASSCACHE: OnceCell>>>> = OnceCell::new(); - -pub fn find_add_class(env: &mut Env, classname: &str) -> Result<()> { - let cache = CLASSCACHE.get_or_init(|| DashMap::new()); - let jni_name = JNIString::from(classname); - let cls = env.find_class(&jni_name)?; - let global = env.new_global_ref(&cls)?; - cache.insert(classname.to_owned(), Arc::new(global)); - Ok(()) -} - -pub fn get_class(classname: &str) -> Option>>> { - let cache = CLASSCACHE.get_or_init(|| DashMap::new()); - cache.get(classname).map(|pair| pair.value().clone()) -} diff --git a/src/droidplug/jni_utils/future.rs b/src/droidplug/jni_utils/future.rs index 62b6766c..3bba3720 100644 --- a/src/droidplug/jni_utils/future.rs +++ b/src/droidplug/jni_utils/future.rs @@ -18,6 +18,10 @@ bind_java_type! { }, } +bind_java_type! { + pub JFutureException => io.github.gedgygedgy.rust.future.FutureException, +} + pub struct JSendFuture { internal: Global>, vm: JavaVM, diff --git a/src/droidplug/jni_utils/mod.rs b/src/droidplug/jni_utils/mod.rs index 437f8907..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,32 +8,38 @@ pub mod uuid; #[cfg(test)] pub(crate) mod test_utils { - use jni::{Env, JavaVM, jni_str, jni_sig, objects::Global, objects::JObject}; + 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: &mut Env) -> 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.find_class(jni_str!("io/github/gedgygedgy/rust/ops/FnAdapter"))?; + 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( - &class, + &*fn_adapter_class, &[ NativeMethod::from_raw_parts( jni_str!("callInternal"), diff --git a/src/droidplug/jni_utils/ops.rs b/src/droidplug/jni_utils/ops.rs index a09cb6b0..7ed0db1a 100644 --- a/src/droidplug/jni_utils/ops.rs +++ b/src/droidplug/jni_utils/ops.rs @@ -1,11 +1,28 @@ use ::jni::{ Env, EnvUnowned, + bind_java_type, errors::Result, jni_sig, jni_str, - objects::JObject, + 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, @@ -17,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, @@ -32,9 +49,9 @@ macro_rules! define_fn_adapter { local: bool, ) -> Result> { let adapter = fn_once_adapter(env, $closure, local)?; - let class = super::classcache::get_class($ic).unwrap(); + let class = <$it as Reference>::lookup_class(env, &Default::default())?; env.new_object( - class.as_ref(), + &*class, jni_sig!("(Lio/github/gedgygedgy/rust/ops/FnAdapter;)V"), &[(&adapter).into()], ) @@ -61,9 +78,9 @@ macro_rules! define_fn_adapter { local: bool, ) -> Result> { let adapter = fn_mut_adapter(env, $closure, local)?; - let class = super::classcache::get_class($ic).unwrap(); + let class = <$it as Reference>::lookup_class(env, &Default::default())?; env.new_object( - class.as_ref(), + &*class, jni_sig!("(Lio/github/gedgygedgy/rust/ops/FnAdapter;)V"), &[(&adapter).into()], ) @@ -91,9 +108,9 @@ macro_rules! define_fn_adapter { local: bool, ) -> Result> { let adapter = fn_adapter(env, $closure, local)?; - let class = super::classcache::get_class($ic).unwrap(); + let class = <$it as Reference>::lookup_class(env, &Default::default())?; env.new_object( - class.as_ref(), + &*class, jni_sig!("(Lio/github/gedgygedgy/rust/ops/FnAdapter;)V"), &[(&adapter).into()], ) @@ -127,7 +144,7 @@ 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", @@ -150,7 +167,7 @@ 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", @@ -172,7 +189,7 @@ 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", @@ -277,9 +294,9 @@ fn fn_adapter<'local>( ) -> JObject<'c>, > = Arc::from(f); - let class = super::classcache::get_class("io/github/gedgygedgy/rust/ops/FnAdapter").unwrap(); + let class = ::lookup_class(env, &Default::default())?; let obj = env.new_object( - class.as_ref(), + &*class, jni_sig!("(Z)V"), &[local.into()], )?; diff --git a/src/droidplug/jni_utils/stream.rs b/src/droidplug/jni_utils/stream.rs index 644ff7c4..fa424bbc 100644 --- a/src/droidplug/jni_utils/stream.rs +++ b/src/droidplug/jni_utils/stream.rs @@ -20,7 +20,7 @@ bind_java_type! { } bind_java_type! { - JStreamPoll => io.github.gedgygedgy.rust.stream.StreamPoll, + pub JStreamPoll => io.github.gedgygedgy.rust.stream.StreamPoll, methods { fn get() -> JObject, }, diff --git a/src/droidplug/jni_utils/task.rs b/src/droidplug/jni_utils/task.rs index 92a777c4..587dba93 100644 --- a/src/droidplug/jni_utils/task.rs +++ b/src/droidplug/jni_utils/task.rs @@ -3,16 +3,20 @@ use ::jni::{ bind_java_type, errors::Result, jni_sig, - objects::JObject, + objects::{JObject, Reference}, }; use std::task::Waker; +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 = super::classcache::get_class("io/github/gedgygedgy/rust/task/Waker").unwrap(); + let class = ::lookup_class(env, &Default::default())?; let obj = env.new_object( - class.as_ref(), + &*class, jni_sig!("(Lio/github/gedgygedgy/rust/ops/FnRunnable;)V"), &[(&runnable).into()], )?; diff --git a/src/droidplug/peripheral.rs b/src/droidplug/peripheral.rs index 20547a7a..10806bda 100644 --- a/src/droidplug/peripheral.rs +++ b/src/droidplug/peripheral.rs @@ -60,42 +60,38 @@ fn get_poll_result<'a>( let ex = env.exception_occurred().unwrap(); env.exception_clear(); - let future_exception_class = super::jni_utils::classcache::get_class( - "io/github/gedgygedgy/rust/future/FutureException", - ) - .unwrap(); + use jni::objects::Reference; + use super::jni::objects::*; - if env.is_instance_of(&ex, future_exception_class.as_ref())? { + let future_ex_class = ::lookup_class( + env, &Default::default(), + )?; + + if env.is_instance_of(&ex, &*future_ex_class)? { let cause = env .call_method(&ex, jni_str!("getCause"), jni_sig!("()Ljava/lang/Throwable;"), &[])? .l()?; - let mut check = |name: &str| -> jni::errors::Result { - let cls = super::jni_utils::classcache::get_class(name).unwrap(); - env.is_instance_of(&cause, cls.as_ref()) - }; + 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("com/nonpolynomial/btleplug/android/impl/NotConnectedException")? { + if check_exception!(JNotConnectedException, env, &cause) { Err(Error::NotConnected) - } else if check( - "com/nonpolynomial/btleplug/android/impl/PermissionDeniedException", - )? { + } else if check_exception!(JPermissionDeniedException, env, &cause) { Err(Error::PermissionDenied) - } else if check( - "com/nonpolynomial/btleplug/android/impl/UnexpectedCallbackException", - )? { + } else if check_exception!(JUnexpectedCallbackException, env, &cause) { Err(Error::UnexpectedCallback) - } else if check( - "com/nonpolynomial/btleplug/android/impl/UnexpectedCharacteristicException", - )? { + } else if check_exception!(JUnexpectedCharacteristicException, env, &cause) { Err(Error::UnexpectedCharacteristic) - } else if check( - "com/nonpolynomial/btleplug/android/impl/NoSuchCharacteristicException", - )? { + } else if check_exception!(JNoSuchCharacteristicException, env, &cause) { Err(Error::NoSuchCharacteristic) - } else if check( - "com/nonpolynomial/btleplug/android/impl/NoBluetoothAdapterException", - )? { + } 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 From c3ac72b0265742e22a78dbf1701dfcd7517150ca Mon Sep 17 00:00:00 2001 From: Kyle Machulis Date: Sun, 26 Apr 2026 23:03:32 -0700 Subject: [PATCH 15/16] fix: Fix Android cross-compilation issues with bind_java_type! macros - Use string literal syntax for Java class paths containing "impl" keyword (bind_java_type! parses dot-separated idents, and "impl" is reserved in Rust) - Use block syntax for methods with name overrides (inline sig + block is not supported, must use { name = "...", sig = (...) -> T } form) - Fix borrow checker issues in JPeripheral wrapper methods by splitting raw call and cast_local into separate statements - Fix error type mismatches in with_obj and notification stream by unifying on crate::Result Co-Authored-By: Claude Opus 4.6 --- src/droidplug/jni/mod.rs | 8 +-- src/droidplug/jni/objects.rs | 109 ++++++++++++++++------------------- src/droidplug/peripheral.rs | 39 ++++++------- 3 files changed, 73 insertions(+), 83 deletions(-) diff --git a/src/droidplug/jni/mod.rs b/src/droidplug/jni/mod.rs index 27531fc4..32a10f08 100644 --- a/src/droidplug/jni/mod.rs +++ b/src/droidplug/jni/mod.rs @@ -101,8 +101,8 @@ fn adapter_report_scan_result<'local>( obj: JObject<'local>, scan_result: JObject<'local>, ) -> jni::errors::Result<()> { - super::adapter::adapter_report_scan_result_internal(env, &obj, scan_result) - .map_err(|e| jni::errors::Error::Other(Box::new(e))) + let _ = super::adapter::adapter_report_scan_result_internal(env, &obj, scan_result); + Ok(()) } fn adapter_on_connection_state_changed<'local>( @@ -111,6 +111,6 @@ fn adapter_on_connection_state_changed<'local>( addr: JString<'local>, connected: jboolean, ) -> jni::errors::Result<()> { - super::adapter::adapter_on_connection_state_changed_internal(env, &obj, addr, connected) - .map_err(|e| jni::errors::Error::Other(Box::new(e))) + 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 74509fa3..6dcc3368 100644 --- a/src/droidplug/jni/objects.rs +++ b/src/droidplug/jni/objects.rs @@ -3,7 +3,7 @@ use jni::{ Env, bind_java_type, errors::Result, - jni_sig, + jni_sig, jni_str, objects::{JObject, JString}, sys::jint, }; @@ -13,56 +13,58 @@ use uuid::Uuid; use crate::api::{BDAddr, CharPropFlags, PeripheralProperties, ScanFilter}; bind_java_type! { - pub JNotConnectedException => com.nonpolynomial.btleplug.android.impl.NotConnectedException, + pub JNotConnectedException => "com.nonpolynomial.btleplug.android.impl.NotConnectedException", } bind_java_type! { - pub JPermissionDeniedException => com.nonpolynomial.btleplug.android.impl.PermissionDeniedException, + pub JPermissionDeniedException => "com.nonpolynomial.btleplug.android.impl.PermissionDeniedException", } bind_java_type! { - pub JUnexpectedCallbackException => com.nonpolynomial.btleplug.android.impl.UnexpectedCallbackException, + pub JUnexpectedCallbackException => "com.nonpolynomial.btleplug.android.impl.UnexpectedCallbackException", } bind_java_type! { - pub JUnexpectedCharacteristicException => com.nonpolynomial.btleplug.android.impl.UnexpectedCharacteristicException, + pub JUnexpectedCharacteristicException => "com.nonpolynomial.btleplug.android.impl.UnexpectedCharacteristicException", } bind_java_type! { - pub JNoSuchCharacteristicException => com.nonpolynomial.btleplug.android.impl.NoSuchCharacteristicException, + pub JNoSuchCharacteristicException => "com.nonpolynomial.btleplug.android.impl.NoSuchCharacteristicException", } bind_java_type! { - pub JNoBluetoothAdapterException => com.nonpolynomial.btleplug.android.impl.NoBluetoothAdapterException, + pub JNoBluetoothAdapterException => "com.nonpolynomial.btleplug.android.impl.NoBluetoothAdapterException", } bind_java_type! { - pub JScanFilterClass => com.nonpolynomial.btleplug.android.impl.ScanFilter, + pub JScanFilterClass => "com.nonpolynomial.btleplug.android.impl.ScanFilter", } bind_java_type! { - pub JPeripheral => com.nonpolynomial.btleplug.android.impl.Peripheral, + pub JPeripheral => "com.nonpolynomial.btleplug.android.impl.Peripheral", constructors { fn with_adapter(adapter: JObject, address: JString), }, methods { - priv fn connect_raw() -> JObject { name = "connect" }, - priv fn disconnect_raw() -> JObject { name = "disconnect" }, + priv fn connect_raw { name = "connect", sig = () -> JObject }, + priv fn disconnect_raw { name = "disconnect", sig = () -> JObject }, fn is_connected() -> jboolean, - priv fn discover_services_raw() -> JObject { name = "discoverServices" }, - priv fn read_raw(uuid: JObject) -> JObject { name = "read" }, - priv fn write_raw(uuid: JObject, data: JObject, write_type: jint) -> JObject { name = "write" }, - priv fn set_characteristic_notification_raw(uuid: JObject, enable: jboolean) -> JObject { + priv fn discover_services_raw { name = "discoverServices", sig = () -> JObject }, + priv fn read_raw { name = "read", sig = (uuid: JObject) -> JObject }, + priv fn write_raw { name = "write", sig = (uuid: JObject, data: JObject, write_type: jint) -> JObject }, + priv fn set_characteristic_notification_raw { name = "setCharacteristicNotification", + sig = (uuid: JObject, enable: jboolean) -> JObject, }, - priv fn get_notifications_raw() -> JObject { name = "getNotifications" }, - priv fn read_descriptor_raw(characteristic: JObject, uuid: JObject) -> JObject { name = "readDescriptor" }, - priv fn write_descriptor_raw(characteristic: JObject, uuid: JObject, data: JObject) -> JObject { + priv fn get_notifications_raw { name = "getNotifications", sig = () -> JObject }, + priv fn read_descriptor_raw { name = "readDescriptor", sig = (characteristic: JObject, uuid: JObject) -> JObject }, + priv fn write_descriptor_raw { name = "writeDescriptor", + sig = (characteristic: JObject, uuid: JObject, data: JObject) -> JObject, }, - priv fn get_device_name_raw() -> JObject { name = "getDeviceName" }, + priv fn get_device_name_raw { name = "getDeviceName", sig = () -> JObject }, fn request_mtu(mtu: jint) -> JObject, - priv fn get_connection_parameters_raw() -> JObject { name = "getConnectionParameters" }, + priv fn get_connection_parameters_raw { name = "getConnectionParameters", sig = () -> JObject }, fn request_connection_priority(priority: jint) -> jboolean, fn read_remote_rssi() -> JObject, }, @@ -77,19 +79,23 @@ impl JPeripheral<'_> { impl<'local> JPeripheral<'local> { pub fn connect(&self, env: &mut Env<'local>) -> Result> { - env.cast_local::(self.connect_raw(env)?) + let raw = self.connect_raw(env)?; + env.cast_local::(raw) } pub fn disconnect(&self, env: &mut Env<'local>) -> Result> { - env.cast_local::(self.disconnect_raw(env)?) + let raw = self.disconnect_raw(env)?; + env.cast_local::(raw) } pub fn discover_services(&self, env: &mut Env<'local>) -> Result> { - env.cast_local::(self.discover_services_raw(env)?) + let raw = self.discover_services_raw(env)?; + env.cast_local::(raw) } pub fn read(&self, env: &mut Env<'local>, uuid: &JUuid<'local>) -> Result> { - env.cast_local::(self.read_raw(env, uuid)?) + let raw = self.read_raw(env, uuid)?; + env.cast_local::(raw) } pub fn write( @@ -99,7 +105,8 @@ impl<'local> JPeripheral<'local> { data: &JObject<'local>, write_type: jint, ) -> Result> { - env.cast_local::(self.write_raw(env, uuid, data, write_type)?) + let raw = self.write_raw(env, uuid, data, write_type)?; + env.cast_local::(raw) } pub fn set_characteristic_notification( @@ -108,11 +115,13 @@ impl<'local> JPeripheral<'local> { uuid: &JUuid<'local>, enable: bool, ) -> Result> { - env.cast_local::(self.set_characteristic_notification_raw(env, uuid, enable)?) + let raw = self.set_characteristic_notification_raw(env, uuid, enable)?; + env.cast_local::(raw) } pub fn get_notifications(&self, env: &mut Env<'local>) -> Result> { - env.cast_local::(self.get_notifications_raw(env)?) + let raw = self.get_notifications_raw(env)?; + env.cast_local::(raw) } pub fn read_descriptor( @@ -121,7 +130,8 @@ impl<'local> JPeripheral<'local> { characteristic: &JUuid<'local>, uuid: &JUuid<'local>, ) -> Result> { - env.cast_local::(self.read_descriptor_raw(env, characteristic, uuid)?) + let raw = self.read_descriptor_raw(env, characteristic, uuid)?; + env.cast_local::(raw) } pub fn write_descriptor( @@ -131,7 +141,8 @@ impl<'local> JPeripheral<'local> { uuid: &JUuid<'local>, data: &JObject<'local>, ) -> Result> { - env.cast_local::(self.write_descriptor_raw(env, characteristic, uuid, data)?) + let raw = self.write_descriptor_raw(env, characteristic, uuid, data)?; + env.cast_local::(raw) } pub fn get_device_name(&self, env: &mut Env<'local>) -> Result> { @@ -171,12 +182,8 @@ impl<'local> JPeripheral<'local> { bind_java_type! { pub JBluetoothGattService => android.bluetooth.BluetoothGattService, methods { - fn get_uuid_obj() -> JObject { - name = "getUuid", - }, - fn get_characteristics_obj() -> JObject { - name = "getCharacteristics", - }, + fn get_uuid_obj { name = "getUuid", sig = () -> JObject }, + fn get_characteristics_obj { name = "getCharacteristics", sig = () -> JObject }, }, } @@ -211,18 +218,10 @@ impl<'local> JBluetoothGattService<'local> { bind_java_type! { pub JBluetoothGattCharacteristic => android.bluetooth.BluetoothGattCharacteristic, methods { - fn get_uuid_obj() -> JObject { - name = "getUuid", - }, - fn get_properties_raw() -> jint { - name = "getProperties", - }, - fn get_value_obj() -> JObject { - name = "getValue", - }, - fn get_descriptors_obj() -> JObject { - name = "getDescriptors", - }, + fn get_uuid_obj { name = "getUuid", sig = () -> JObject }, + fn get_properties_raw { name = "getProperties", sig = () -> jint }, + fn get_value_obj { name = "getValue", sig = () -> JObject }, + fn get_descriptors_obj { name = "getDescriptors", sig = () -> JObject }, }, } @@ -264,9 +263,7 @@ impl<'local> JBluetoothGattCharacteristic<'local> { bind_java_type! { pub JBluetoothGattDescriptor => android.bluetooth.BluetoothGattDescriptor, methods { - fn get_uuid_obj() -> JObject { - name = "getUuid", - }, + fn get_uuid_obj { name = "getUuid", sig = () -> JObject }, }, } @@ -322,12 +319,8 @@ impl<'a> From> for JObject<'a> { bind_java_type! { pub JScanResult => android.bluetooth.le.ScanResult, methods { - fn get_device_obj() -> JObject { - name = "getDevice", - }, - fn get_scan_record_obj() -> JObject { - name = "getScanRecord", - }, + fn get_device_obj { name = "getDevice", sig = () -> JObject }, + fn get_scan_record_obj { name = "getScanRecord", sig = () -> JObject }, fn get_tx_power() -> jint, fn get_rssi() -> jint, }, @@ -499,9 +492,7 @@ bind_java_type! { bind_java_type! { pub JParcelUuid => android.os.ParcelUuid, methods { - fn get_uuid_obj() -> JObject { - name = "getUuid", - }, + fn get_uuid_obj { name = "getUuid", sig = () -> JObject }, }, } diff --git a/src/droidplug/peripheral.rs b/src/droidplug/peripheral.rs index 10806bda..13361b78 100644 --- a/src/droidplug/peripheral.rs +++ b/src/droidplug/peripheral.rs @@ -151,13 +151,10 @@ impl Peripheral { guard.properties = Some(properties); } - fn with_obj( + fn with_obj( &self, - f: impl for<'env> FnOnce(&mut Env<'env>, &JPeripheral<'env>) -> std::result::Result, - ) -> std::result::Result - where - E: From<::jni::errors::Error>, - { + 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)?; @@ -173,7 +170,7 @@ impl Peripheral { let future = self.with_obj(|env, obj| { let uuid_obj = JUuid::new(env, characteristic.uuid)?; let future = obj.set_characteristic_notification(env, &uuid_obj, enable)?; - JSendFuture::new(env, &future) + Ok(JSendFuture::new(env, &future)?) })?; let result_ref = future.await?; self.with_obj(|env, _obj| get_poll_result(env, &result_ref).map(|_| {})) @@ -218,7 +215,7 @@ impl api::Peripheral for Peripheral { { let future = self.with_obj(|env, obj| { let future = obj.connect(env)?; - JSendFuture::new(env, &future) + Ok(JSendFuture::new(env, &future)?) })?; let result_ref = future.await?; self.with_obj(|env, _obj| get_poll_result(env, &result_ref).map(|_| {}))?; @@ -238,7 +235,7 @@ impl api::Peripheral for Peripheral { let mtu_future = self.with_obj(|env, obj| { let mtu_obj = obj.request_mtu(env, 517)?; let mtu_future = env.cast_local::(mtu_obj)?; - JSendFuture::new(env, &mtu_future) + Ok(JSendFuture::new(env, &mtu_future)?) })?; let mtu_result_ref = mtu_future.await?; self.with_obj(|env, _obj| -> Result<()> { @@ -254,7 +251,7 @@ impl api::Peripheral for Peripheral { async fn disconnect(&self) -> Result<()> { let future = self.with_obj(|env, obj| { let future = obj.disconnect(env)?; - JSendFuture::new(env, &future) + Ok(JSendFuture::new(env, &future)?) })?; let result_ref = future.await?; self.with_obj(|env, _obj| get_poll_result(env, &result_ref).map(|_| {})) @@ -268,7 +265,7 @@ impl api::Peripheral for Peripheral { async fn discover_services(&self) -> Result<()> { let future = self.with_obj(|env, obj| { let future = obj.discover_services(env)?; - JSendFuture::new(env, &future) + Ok(JSendFuture::new(env, &future)?) })?; let result_ref = future.await?; self.with_obj(|env, _obj| { @@ -337,7 +334,7 @@ impl api::Peripheral for Peripheral { WriteType::WithoutResponse => 1, }; let future = obj.write(env, &uuid, &data_obj.into(), write_type)?; - JSendFuture::new(env, &future) + Ok(JSendFuture::new(env, &future)?) })?; let result_ref = future.await?; self.with_obj(|env, _obj| get_poll_result(env, &result_ref).map(|_| {})) @@ -347,7 +344,7 @@ impl api::Peripheral for Peripheral { let future = self.with_obj(|env, obj| { let uuid = JUuid::new(env, characteristic.uuid)?; let future = obj.read(env, &uuid)?; - JSendFuture::new(env, &future) + Ok(JSendFuture::new(env, &future)?) })?; let result_ref = future.await?; self.with_obj(|env, _obj| { @@ -372,12 +369,13 @@ impl api::Peripheral for Peripheral { let shared = self.shared.clone(); let stream = self.with_obj(|env, obj| { let stream = obj.get_notifications(env)?; - JSendStream::new(env, &stream) + Ok(JSendStream::new(env, &stream)?) })?; let stream = stream .map(move |item| match item { Ok(item) => { - jvm()?.attach_current_thread(|env| { + 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)?; @@ -399,9 +397,10 @@ impl api::Peripheral for Peripheral { 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)) @@ -411,7 +410,7 @@ impl api::Peripheral for Peripheral { let future = self.with_obj(|env, obj| { let rssi_obj = obj.read_remote_rssi(env)?; let rssi_future = env.cast_local::(rssi_obj)?; - JSendFuture::new(env, &rssi_future) + Ok(JSendFuture::new(env, &rssi_future)?) })?; let result_ref = future.await?; self.with_obj(|env, _obj| { @@ -427,7 +426,7 @@ impl api::Peripheral for Peripheral { let uuid = JUuid::new(env, descriptor.uuid)?; let data_obj = super::jni_utils::arrays::slice_to_byte_array(env, data)?; let future = obj.write_descriptor(env, &characteristic, &uuid, &data_obj.into())?; - JSendFuture::new(env, &future) + Ok(JSendFuture::new(env, &future)?) })?; let result_ref = future.await?; self.with_obj(|env, _obj| get_poll_result(env, &result_ref).map(|_| {})) @@ -438,7 +437,7 @@ impl api::Peripheral for Peripheral { let characteristic = JUuid::new(env, descriptor.characteristic_uuid)?; let uuid = JUuid::new(env, descriptor.uuid)?; let future = obj.read_descriptor(env, &characteristic, &uuid)?; - JSendFuture::new(env, &future) + Ok(JSendFuture::new(env, &future)?) })?; let result_ref = future.await?; self.with_obj(|env, _obj| { From d81b3b4b2a04279523a27b0318413987530c3df1 Mon Sep 17 00:00:00 2001 From: Kyle Machulis Date: Sun, 26 Apr 2026 23:47:08 -0700 Subject: [PATCH 16/16] fix: Fix JNI signature mismatches caused by bind_java_type! JObject mapping bind_java_type! maps JObject to Ljava/lang/Object; in JNI signatures, but Java methods using domain-specific types (UUID, Future, Stream, ScanResult, byte[], List, Map, etc.) require exact signature matches. This caused runtime "Method not found" errors and a SIGSEGV in the scan callback. Changes: - objects.rs: Move all methods with domain-typed params/returns out of bind_java_type! into manual env.call_method() with correct JNI signatures. Keep bind_java_type! for class definitions and primitive-only methods. - future.rs: Move JFuture::poll to manual impl with correct Waker/PollResult sigs - stream.rs: Move JStream::poll_next to manual impl with correct Waker/PollResult sigs - mod.rs: Fix reportScanResult to use extern "C" + EnvUnowned + with_env for raw JNI ABI compatibility (from_raw_parts requires raw C calling convention). Add env.get_java_vm() to seed JavaVM singleton during init. - peripheral_finder.rs: Add catch_unwind and logging to adapter background thread for Android debugging (silent thread death caused confusing RecvError) Verified: 28/29 Android integration tests pass on Pixel 9a. The single failure (testPropertiesContainPeripheralInfo TX power) is a test-peripheral issue. Co-Authored-By: Claude Opus 4.6 --- src/droidplug/jni/mod.rs | 29 +++--- src/droidplug/jni/objects.rs | 159 ++++++++++++++++++------------ src/droidplug/jni_utils/future.rs | 15 ++- src/droidplug/jni_utils/stream.rs | 15 ++- tests/common/peripheral_finder.rs | 48 +++++---- 5 files changed, 167 insertions(+), 99 deletions(-) diff --git a/src/droidplug/jni/mod.rs b/src/droidplug/jni/mod.rs index 32a10f08..3046b39c 100644 --- a/src/droidplug/jni/mod.rs +++ b/src/droidplug/jni/mod.rs @@ -1,6 +1,6 @@ pub mod objects; -use ::jni::{Env, NativeMethod, jni_str, native_method, objects::{JObject, Reference}}; +use ::jni::{Env, EnvUnowned, NativeMethod, jni_str, native_method, objects::{JObject, Reference}}; use jni::{objects::JString, sys::jboolean}; use std::ffi::c_void; use std::sync::Once; @@ -18,17 +18,21 @@ pub fn init(env: &mut Env) -> crate::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, &[ - native_method! { - name = "reportScanResult", - sig = (scan_result: JObject) -> (), - fn = adapter_report_scan_result, - }, + // 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) -> (), @@ -96,13 +100,16 @@ impl From<::jni::errors::Error> for crate::Error { } } -fn adapter_report_scan_result<'local>( - env: &mut Env<'local>, +extern "C" fn adapter_report_scan_result<'local>( + mut env: EnvUnowned<'local>, obj: JObject<'local>, scan_result: JObject<'local>, -) -> jni::errors::Result<()> { - let _ = super::adapter::adapter_report_scan_result_internal(env, &obj, scan_result); - Ok(()) +) { + 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(); } fn adapter_on_connection_state_changed<'local>( diff --git a/src/droidplug/jni/objects.rs b/src/droidplug/jni/objects.rs index 6dcc3368..3b53cfbe 100644 --- a/src/droidplug/jni/objects.rs +++ b/src/droidplug/jni/objects.rs @@ -4,7 +4,7 @@ use jni::{ bind_java_type, errors::Result, jni_sig, jni_str, - objects::{JObject, JString}, + objects::{JObject, JString, Reference}, sys::jint, }; use std::{collections::HashMap, iter::Iterator}; @@ -40,61 +40,53 @@ bind_java_type! { pub JScanFilterClass => "com.nonpolynomial.btleplug.android.impl.ScanFilter", } +// 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", - constructors { - fn with_adapter(adapter: JObject, address: JString), - }, methods { - priv fn connect_raw { name = "connect", sig = () -> JObject }, - priv fn disconnect_raw { name = "disconnect", sig = () -> JObject }, fn is_connected() -> jboolean, - priv fn discover_services_raw { name = "discoverServices", sig = () -> JObject }, - priv fn read_raw { name = "read", sig = (uuid: JObject) -> JObject }, - priv fn write_raw { name = "write", sig = (uuid: JObject, data: JObject, write_type: jint) -> JObject }, - priv fn set_characteristic_notification_raw { - name = "setCharacteristicNotification", - sig = (uuid: JObject, enable: jboolean) -> JObject, - }, - priv fn get_notifications_raw { name = "getNotifications", sig = () -> JObject }, - priv fn read_descriptor_raw { name = "readDescriptor", sig = (characteristic: JObject, uuid: JObject) -> JObject }, - priv fn write_descriptor_raw { - name = "writeDescriptor", - sig = (characteristic: JObject, uuid: JObject, data: JObject) -> JObject, - }, - priv fn get_device_name_raw { name = "getDeviceName", sig = () -> JObject }, - fn request_mtu(mtu: jint) -> JObject, - priv fn get_connection_parameters_raw { name = "getConnectionParameters", sig = () -> JObject }, fn request_connection_priority(priority: jint) -> jboolean, - fn read_remote_rssi() -> JObject, }, } 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))?; - JPeripheral::with_adapter(env, &adapter, &addr_jstr) + let class = JPeripheral::lookup_class(env, &Default::default())?; + let obj = env.new_object( + &*class, + jni_sig!("(Lcom/nonpolynomial/btleplug/android/impl/Adapter;Ljava/lang/String;)V"), + &[(&adapter).into(), (&addr_jstr).into()], + )?; + env.cast_local::(obj) } } impl<'local> JPeripheral<'local> { pub fn connect(&self, env: &mut Env<'local>) -> Result> { - let raw = self.connect_raw(env)?; + let raw = env.call_method(self, jni_str!("connect"), + jni_sig!("()Lio/github/gedgygedgy/rust/future/Future;"), &[])?.l()?; env.cast_local::(raw) } pub fn disconnect(&self, env: &mut Env<'local>) -> Result> { - let raw = self.disconnect_raw(env)?; + 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, env: &mut Env<'local>) -> Result> { - let raw = self.discover_services_raw(env)?; + 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, env: &mut Env<'local>, uuid: &JUuid<'local>) -> Result> { - let raw = self.read_raw(env, uuid)?; + 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) } @@ -105,7 +97,9 @@ impl<'local> JPeripheral<'local> { data: &JObject<'local>, write_type: jint, ) -> Result> { - let raw = self.write_raw(env, uuid, data, write_type)?; + 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) } @@ -115,12 +109,15 @@ impl<'local> JPeripheral<'local> { uuid: &JUuid<'local>, enable: bool, ) -> Result> { - let raw = self.set_characteristic_notification_raw(env, uuid, enable)?; + 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, env: &mut Env<'local>) -> Result> { - let raw = self.get_notifications_raw(env)?; + let raw = env.call_method(self, jni_str!("getNotifications"), + jni_sig!("()Lio/github/gedgygedgy/rust/stream/Stream;"), &[])?.l()?; env.cast_local::(raw) } @@ -130,7 +127,9 @@ impl<'local> JPeripheral<'local> { characteristic: &JUuid<'local>, uuid: &JUuid<'local>, ) -> Result> { - let raw = self.read_descriptor_raw(env, characteristic, uuid)?; + 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) } @@ -141,12 +140,15 @@ impl<'local> JPeripheral<'local> { uuid: &JUuid<'local>, data: &JObject<'local>, ) -> Result> { - let raw = self.write_descriptor_raw(env, characteristic, uuid, data)?; + 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 = self.get_device_name_raw(env)?; + let obj = env.call_method(self, jni_str!("getDeviceName"), + jni_sig!("()Ljava/lang/String;"), &[])?.l()?; if obj.is_null() { Ok(None) } else { @@ -156,11 +158,19 @@ impl<'local> JPeripheral<'local> { } } + 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, env: &mut Env<'local>, ) -> Result> { - let obj = self.get_connection_parameters_raw(env)?; + let obj = env.call_method(self, jni_str!("getConnectionParameters"), + jni_sig!("()[I"), &[])?.l()?; if obj.is_null() { return Ok(None); } @@ -177,14 +187,19 @@ impl<'local> JPeripheral<'local> { supervision_timeout_us: (buf[2] as u32) * 10_000, })) } + + 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) + } } +// Android SDK types: class definition only, methods use manual JNI signatures +// because return types (UUID, List, byte[]) don't map to JObject. + bind_java_type! { pub JBluetoothGattService => android.bluetooth.BluetoothGattService, - methods { - fn get_uuid_obj { name = "getUuid", sig = () -> JObject }, - fn get_characteristics_obj { name = "getCharacteristics", sig = () -> JObject }, - }, } impl<'local> JBluetoothGattService<'local> { @@ -193,7 +208,8 @@ impl<'local> JBluetoothGattService<'local> { } pub fn get_uuid(&self, env: &mut Env<'local>) -> Result { - let obj = self.get_uuid_obj(env)?; + 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) } @@ -202,7 +218,8 @@ impl<'local> JBluetoothGattService<'local> { &self, env: &mut Env<'local>, ) -> Result>> { - let obj = self.get_characteristics_obj(env)?; + 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 { @@ -218,16 +235,14 @@ impl<'local> JBluetoothGattService<'local> { bind_java_type! { pub JBluetoothGattCharacteristic => android.bluetooth.BluetoothGattCharacteristic, methods { - fn get_uuid_obj { name = "getUuid", sig = () -> JObject }, fn get_properties_raw { name = "getProperties", sig = () -> jint }, - fn get_value_obj { name = "getValue", sig = () -> JObject }, - fn get_descriptors_obj { name = "getDescriptors", sig = () -> JObject }, }, } impl<'local> JBluetoothGattCharacteristic<'local> { pub fn get_uuid(&self, env: &mut Env<'local>) -> Result { - let obj = self.get_uuid_obj(env)?; + 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) } @@ -238,7 +253,8 @@ impl<'local> JBluetoothGattCharacteristic<'local> { } pub fn get_value(&self, env: &mut Env<'local>) -> Result> { - let value = self.get_value_obj(env)?; + 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) } @@ -247,7 +263,8 @@ impl<'local> JBluetoothGattCharacteristic<'local> { &self, env: &mut Env<'local>, ) -> Result>> { - let obj = self.get_descriptors_obj(env)?; + 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 { @@ -262,14 +279,12 @@ impl<'local> JBluetoothGattCharacteristic<'local> { bind_java_type! { pub JBluetoothGattDescriptor => android.bluetooth.BluetoothGattDescriptor, - methods { - fn get_uuid_obj { name = "getUuid", sig = () -> JObject }, - }, } impl<'local> JBluetoothGattDescriptor<'local> { pub fn get_uuid(&self, env: &mut Env<'local>) -> Result { - let obj = self.get_uuid_obj(env)?; + 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) } @@ -297,7 +312,7 @@ impl<'a> JScanFilter<'a> { let uuid_str = env.new_string(uuid.to_string())?; uuids.set_element(env, idx, &uuid_str)?; } - let class = ::lookup_class( + let class = ::lookup_class( env, &Default::default(), )?; @@ -319,8 +334,6 @@ impl<'a> From> for JObject<'a> { bind_java_type! { pub JScanResult => android.bluetooth.le.ScanResult, methods { - fn get_device_obj { name = "getDevice", sig = () -> JObject }, - fn get_scan_record_obj { name = "getScanRecord", sig = () -> JObject }, fn get_tx_power() -> jint, fn get_rssi() -> jint, }, @@ -328,12 +341,14 @@ bind_java_type! { impl<'local> JScanResult<'local> { pub fn get_device(&self, env: &mut Env<'local>) -> Result> { - let obj = self.get_device_obj(env)?; + 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, env: &mut Env<'local>) -> Result> { - self.get_scan_record_obj(env) + env.call_method(self, jni_str!("getScanRecord"), + jni_sig!("()Landroid/bluetooth/le/ScanRecord;"), &[])?.l() } pub fn to_peripheral_properties( @@ -472,14 +487,32 @@ impl<'local> JScanResult<'local> { bind_java_type! { pub JScanRecord => android.bluetooth.le.ScanRecord, methods { - fn get_device_name() -> JObject, fn get_tx_power_level() -> jint, - fn get_manufacturer_specific_data() -> JObject, - fn get_service_data() -> JObject, - fn get_service_uuids() -> JObject, }, } +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_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_service_data(&self, env: &mut Env<'local>) -> Result> { + env.call_method(self, jni_str!("getServiceData"), + jni_sig!("()Ljava/util/Map;"), &[])?.l() + } + + pub fn get_service_uuids(&self, env: &mut Env<'local>) -> Result> { + env.call_method(self, jni_str!("getServiceUuids"), + jni_sig!("()Ljava/util/List;"), &[])?.l() + } +} + bind_java_type! { pub JSparseArray => android.util.SparseArray, methods { @@ -491,14 +524,12 @@ bind_java_type! { bind_java_type! { pub JParcelUuid => android.os.ParcelUuid, - methods { - fn get_uuid_obj { name = "getUuid", sig = () -> JObject }, - }, } impl<'local> JParcelUuid<'local> { pub fn get_uuid(&self, env: &mut Env<'local>) -> Result> { - let obj = self.get_uuid_obj(env)?; + 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/future.rs b/src/droidplug/jni_utils/future.rs index 3bba3720..5abcd5f8 100644 --- a/src/droidplug/jni_utils/future.rs +++ b/src/droidplug/jni_utils/future.rs @@ -2,6 +2,7 @@ use ::jni::{ Env, JavaVM, bind_java_type, errors::Result, + jni_sig, jni_str, objects::{Global, JObject}, }; use static_assertions::assert_impl_all; @@ -13,9 +14,17 @@ use std::{ bind_java_type! { pub JFuture => io.github.gedgygedgy.rust.future.Future, - methods { - fn poll(waker: JObject) -> JObject, - }, +} + +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() + } } bind_java_type! { diff --git a/src/droidplug/jni_utils/stream.rs b/src/droidplug/jni_utils/stream.rs index fa424bbc..512918d9 100644 --- a/src/droidplug/jni_utils/stream.rs +++ b/src/droidplug/jni_utils/stream.rs @@ -3,6 +3,7 @@ use ::jni::{ Env, JavaVM, bind_java_type, errors::Result, + jni_sig, jni_str, objects::{Global, JObject}, }; use futures::stream::Stream; @@ -14,9 +15,17 @@ use std::{ bind_java_type! { pub JStream => io.github.gedgygedgy.rust.stream.Stream, - methods { - fn poll_next(waker: JObject) -> JObject, - }, +} + +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() + } } bind_java_type! { 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