diff --git a/Cargo.toml b/Cargo.toml index 5673fe4..a953b6f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,3 +3,6 @@ resolver = "2" members = ["android-activity"] exclude = ["examples"] + +[patch.crates-io] +jni = { path = "/home/rib/src/jni-rs/jni-git-dev0" } \ No newline at end of file diff --git a/android-activity/Cargo.toml b/android-activity/Cargo.toml index ec46358..e370948 100644 --- a/android-activity/Cargo.toml +++ b/android-activity/Cargo.toml @@ -35,7 +35,6 @@ api-level-30 = ["ndk/api-level-30"] [dependencies] log = "0.4" -jni-sys = "0.3" cesu8 = "1" jni = "0.21" ndk-sys = "0.6.0" diff --git a/android-activity/src/game_activity/ffi.rs b/android-activity/src/game_activity/ffi.rs index 51063ff..576d489 100644 --- a/android-activity/src/game_activity/ffi.rs +++ b/android-activity/src/game_activity/ffi.rs @@ -12,7 +12,7 @@ #![allow(deref_nullptr)] #![allow(dead_code)] -use jni_sys::*; +use jni::sys::*; use libc::{pthread_cond_t, pthread_mutex_t, pthread_t}; use ndk_sys::{AAssetManager, AConfiguration, ALooper, ALooper_callbackFunc, ANativeWindow, ARect}; diff --git a/android-activity/src/game_activity/mod.rs b/android-activity/src/game_activity/mod.rs index f430daa..75eb1b5 100644 --- a/android-activity/src/game_activity/mod.rs +++ b/android-activity/src/game_activity/mod.rs @@ -13,7 +13,7 @@ use std::time::Duration; use libc::c_void; use log::{error, trace}; -use jni_sys::*; +use jni::sys::*; use ndk_sys::ALooper_wake; use ndk_sys::{ALooper, ALooper_pollAll}; @@ -24,7 +24,7 @@ use ndk::native_window::NativeWindow; use crate::error::InternalResult; use crate::input::{Axis, KeyCharacterMap, KeyCharacterMapBinding}; -use crate::jni_utils::{self, CloneJavaVM}; +use crate::jni_utils; use crate::util::{abort_on_panic, forward_stdio_to_logcat, log_panic, try_get_path_from_ptr}; use crate::{ AndroidApp, ConfigurationRef, InputStatus, MainEvent, PollEvent, Rect, WindowManagerFlags, @@ -121,32 +121,35 @@ impl AndroidAppWaker { } impl AndroidApp { - pub(crate) unsafe fn from_ptr(ptr: NonNull, jvm: CloneJavaVM) -> Self { - let mut env = jvm.get_env().unwrap(); // We attach to the thread before creating the AndroidApp - - let key_map_binding = match KeyCharacterMapBinding::new(&mut env) { - Ok(b) => b, - Err(err) => { - panic!("Failed to create KeyCharacterMap JNI bindings: {err:?}"); - } - }; - - // Note: we don't use from_ptr since we don't own the android_app.config - // and need to keep in mind that the Drop handler is going to call - // AConfiguration_delete() - let config = Configuration::clone_from_ptr(NonNull::new_unchecked((*ptr.as_ptr()).config)); + pub(crate) unsafe fn from_ptr(ptr: NonNull, jvm: jni::JavaVM) -> Self { + // We attach to the thread before creating the AndroidApp + jni::JavaVM::with_env::<_, _, jni::errors::Error>(|env| { + let key_map_binding = match KeyCharacterMapBinding::new(env) { + Ok(b) => b, + Err(err) => { + panic!("Failed to create KeyCharacterMap JNI bindings: {err:?}"); + } + }; - Self { - inner: Arc::new(RwLock::new(AndroidAppInner { - jvm, - native_app: NativeAppGlue { ptr }, - config: ConfigurationRef::new(config), - native_window: Default::default(), - key_map_binding: Arc::new(key_map_binding), - key_maps: Mutex::new(HashMap::new()), - input_receiver: Mutex::new(None), - })), - } + // Note: we don't use from_ptr since we don't own the android_app.config + // and need to keep in mind that the Drop handler is going to call + // AConfiguration_delete() + let config = + Configuration::clone_from_ptr(NonNull::new_unchecked((*ptr.as_ptr()).config)); + + Ok(Self { + inner: Arc::new(RwLock::new(AndroidAppInner { + jvm, + native_app: NativeAppGlue { ptr }, + config: ConfigurationRef::new(config), + native_window: Default::default(), + key_map_binding: Arc::new(key_map_binding), + key_maps: Mutex::new(HashMap::new()), + input_receiver: Mutex::new(None), + })), + }) + }) + .expect("Failed to create AndroidApp instance") } } @@ -253,7 +256,7 @@ impl NativeAppGlue { #[derive(Debug)] pub struct AndroidAppInner { - pub(crate) jvm: CloneJavaVM, + pub(crate) jvm: jni::JavaVM, native_app: NativeAppGlue, config: ConfigurationRef, native_window: RwLock>, @@ -875,7 +878,7 @@ pub unsafe extern "C" fn Java_com_google_androidgamesdk_GameActivity_initializeN jasset_mgr: jobject, saved_state: jbyteArray, java_config: jobject, -) -> jni_sys::jlong { +) -> jlong { Java_com_google_androidgamesdk_GameActivity_initializeNativeCode_C( env, java_game_activity, @@ -914,11 +917,17 @@ pub unsafe extern "C" fn _rust_glue_entry(native_app: *mut ffi::android_app) { let activity: jobject = (*(*native_app).activity).javaGameActivity; ndk_context::initialize_android_context(jvm.cast(), activity.cast()); - let jvm = CloneJavaVM::from_raw(jvm).unwrap(); - // Since this is a newly spawned thread then the JVM hasn't been attached - // to the thread yet. Attach before calling the applications main function - // so they can safely make JNI calls - jvm.attach_current_thread_permanently().unwrap(); + let jvm = jni::JavaVM::from_raw(jvm); + // Since this is a newly spawned thread then the JVM hasn't been attached to the + // thread yet. + // + // For compatibility we attach before calling the applications main function to + // allow it to assume the thread is attached before making JNI calls. + jvm.attach_current_thread::<_, _, jni::errors::Error>( + jni::JNIVersion::V1_4, + |_| Ok(()), + ) + .expect("Failed to attach thread to JVM"); jvm }; @@ -953,7 +962,11 @@ pub unsafe extern "C" fn _rust_glue_entry(native_app: *mut ffi::android_app) { // This should detach automatically but lets detach explicitly to avoid depending // on the TLS trickery in `jni-rs` - jvm.detach_current_thread(); + if let Err(err) = jvm.detach_current_thread() { + log::error!("Failed to detach thread from JVM: {}", err); + } else { + log::debug!("Detached thread from JVM"); + } ndk_context::release_android_context(); } diff --git a/android-activity/src/input/sdk.rs b/android-activity/src/input/sdk.rs index b40a7df..b2c638d 100644 --- a/android-activity/src/input/sdk.rs +++ b/android-activity/src/input/sdk.rs @@ -1,16 +1,13 @@ use std::sync::Arc; +use jni::sys::jint; use jni::{ objects::{GlobalRef, JClass, JMethodID, JObject, JStaticMethodID, JValue}, signature::{Primitive, ReturnType}, - JNIEnv, + JNIEnv, JNIVersion, JavaVM, }; -use jni_sys::jint; -use crate::{ - input::{Keycode, MetaState}, - jni_utils::CloneJavaVM, -}; +use crate::input::{Keycode, MetaState}; use crate::{ error::{AppError, InternalAppError}, @@ -94,7 +91,7 @@ pub enum KeyMapChar { #[derive(Debug)] pub(crate) struct KeyCharacterMapBinding { //vm: JavaVM, - klass: GlobalRef, + klass: GlobalRef>, get_method_id: JMethodID, get_dead_char_method_id: JStaticMethodID, get_keyboard_type_method_id: JMethodID, @@ -213,18 +210,31 @@ impl KeyCharacterMapBinding { } /// Describes the keys provided by a keyboard device and their associated labels. -#[derive(Clone, Debug)] +#[derive(Debug)] pub struct KeyCharacterMap { - jvm: CloneJavaVM, + jvm: JavaVM, binding: Arc, - key_map: GlobalRef, + key_map: GlobalRef>, +} +impl Clone for KeyCharacterMap { + fn clone(&self) -> Self { + let jvm = self.jvm.clone(); + jvm.attach_current_thread::<_, _, jni::errors::Error>(JNIVersion::V1_4, |env| { + Ok(Self { + jvm: jvm.clone(), + binding: Arc::clone(&self.binding), + key_map: env.new_global_ref(&self.key_map)?, + }) + }) + .expect("Failed to attach thread to JVM and clone key map") + } } impl KeyCharacterMap { pub(crate) fn new( - jvm: CloneJavaVM, + jvm: JavaVM, binding: Arc, - key_map: GlobalRef, + key_map: GlobalRef>, ) -> Self { Self { jvm, @@ -247,41 +257,39 @@ impl KeyCharacterMap { /// is caught. pub fn get(&self, key_code: Keycode, meta_state: MetaState) -> Result { let key_code: u32 = key_code.into(); - let key_code = key_code as jni_sys::jint; + let key_code = key_code as jni::sys::jint; let meta_state: u32 = meta_state.0; - let meta_state = meta_state as jni_sys::jint; + let meta_state = meta_state as jni::sys::jint; - // Since we expect this API to be called from the `main` thread then we expect to already be - // attached to the JVM - // - // Safety: there's no other JNIEnv in scope so this env can't be used to subvert the mutable - // borrow rules that ensure we can only add local references to the top JNI frame. - let mut env = self.jvm.get_env().map_err(|err| { - let err: InternalAppError = err.into(); - err - })?; - let unicode = self - .binding - .get(&mut env, self.key_map.as_obj(), key_code, meta_state)?; - let unicode = unicode as u32; + let vm = self.jvm.clone(); + vm.attach_current_thread::<_, _, InternalAppError>(JNIVersion::V1_4, |env| { + let unicode = self + .binding + .get(env, self.key_map.as_obj(), key_code, meta_state)?; + let unicode = unicode as u32; - const COMBINING_ACCENT: u32 = 0x80000000; - const COMBINING_ACCENT_MASK: u32 = !COMBINING_ACCENT; + const COMBINING_ACCENT: u32 = 0x80000000; + const COMBINING_ACCENT_MASK: u32 = !COMBINING_ACCENT; - if unicode == 0 { - Ok(KeyMapChar::None) - } else if unicode & COMBINING_ACCENT == COMBINING_ACCENT { - let accent = unicode & COMBINING_ACCENT_MASK; - // Safety: assumes Android key maps don't contain invalid unicode characters - Ok(KeyMapChar::CombiningAccent(unsafe { - char::from_u32_unchecked(accent) - })) - } else { - // Safety: assumes Android key maps don't contain invalid unicode characters - Ok(KeyMapChar::Unicode(unsafe { - char::from_u32_unchecked(unicode) - })) - } + if unicode == 0 { + Ok(KeyMapChar::None) + } else if unicode & COMBINING_ACCENT == COMBINING_ACCENT { + let accent = unicode & COMBINING_ACCENT_MASK; + // Safety: assumes Android key maps don't contain invalid unicode characters + Ok(KeyMapChar::CombiningAccent(unsafe { + char::from_u32_unchecked(accent) + })) + } else { + // Safety: assumes Android key maps don't contain invalid unicode characters + Ok(KeyMapChar::Unicode(unsafe { + char::from_u32_unchecked(unicode) + })) + } + }) + .map_err(|err| { + let err: InternalAppError = err.into(); + err.into() + }) } /// Get the character that is produced by combining the dead key producing accent with the key producing character c. @@ -297,28 +305,24 @@ impl KeyCharacterMap { accent_char: char, base_char: char, ) -> Result, AppError> { - let accent_char = accent_char as jni_sys::jint; - let base_char = base_char as jni_sys::jint; + let accent_char = accent_char as jni::sys::jint; + let base_char = base_char as jni::sys::jint; - // Since we expect this API to be called from the `main` thread then we expect to already be - // attached to the JVM - // - // Safety: there's no other JNIEnv in scope so this env can't be used to subvert the mutable - // borrow rules that ensure we can only add local references to the top JNI frame. - let mut env = self.jvm.get_env().map_err(|err| { - let err: InternalAppError = err.into(); - err - })?; - let unicode = self - .binding - .get_dead_char(&mut env, accent_char, base_char)?; - let unicode = unicode as u32; + let vm = self.jvm.clone(); + vm.attach_current_thread::<_, _, InternalAppError>(JNIVersion::V1_4, |env| { + let unicode = self.binding.get_dead_char(env, accent_char, base_char)?; + let unicode = unicode as u32; - // Safety: assumes Android key maps don't contain invalid unicode characters - Ok(if unicode == 0 { - None - } else { - Some(unsafe { char::from_u32_unchecked(unicode) }) + // Safety: assumes Android key maps don't contain invalid unicode characters + Ok(if unicode == 0 { + None + } else { + Some(unsafe { char::from_u32_unchecked(unicode) }) + }) + }) + .map_err(|err| { + let err: InternalAppError = err.into(); + err.into() }) } @@ -332,19 +336,15 @@ impl KeyCharacterMap { /// a [`AppError::JavaError`] in case there is a spurious JNI error or an exception /// is caught. pub fn get_keyboard_type(&self) -> Result { - // Since we expect this API to be called from the `main` thread then we expect to already be - // attached to the JVM - // - // Safety: there's no other JNIEnv in scope so this env can't be used to subvert the mutable - // borrow rules that ensure we can only add local references to the top JNI frame. - let mut env = self.jvm.get_env().map_err(|err| { + let vm = self.jvm.clone(); + vm.attach_current_thread::<_, _, InternalAppError>(JNIVersion::V1_4, |env| { + let keyboard_type = self.binding.get_keyboard_type(env, self.key_map.as_obj())?; + let keyboard_type = keyboard_type as u32; + Ok(keyboard_type.into()) + }) + .map_err(|err| { let err: InternalAppError = err.into(); - err - })?; - let keyboard_type = self - .binding - .get_keyboard_type(&mut env, self.key_map.as_obj())?; - let keyboard_type = keyboard_type as u32; - Ok(keyboard_type.into()) + err.into() + }) } } diff --git a/android-activity/src/jni_utils.rs b/android-activity/src/jni_utils.rs index dcf6083..763a49d 100644 --- a/android-activity/src/jni_utils.rs +++ b/android-activity/src/jni_utils.rs @@ -5,11 +5,11 @@ //! //! These utilities help us check + clear exceptions and map them into Rust Errors. -use std::{ops::Deref, sync::Arc}; +use std::sync::Arc; use jni::{ objects::{JObject, JString}, - JavaVM, + JNIVersion, JavaVM, }; use crate::{ @@ -17,36 +17,6 @@ use crate::{ input::{KeyCharacterMap, KeyCharacterMapBinding}, }; -// TODO: JavaVM should implement Clone -#[derive(Debug)] -pub(crate) struct CloneJavaVM { - pub jvm: JavaVM, -} -impl Clone for CloneJavaVM { - fn clone(&self) -> Self { - Self { - jvm: unsafe { JavaVM::from_raw(self.jvm.get_java_vm_pointer()).unwrap() }, - } - } -} -impl CloneJavaVM { - pub unsafe fn from_raw(jvm: *mut jni_sys::JavaVM) -> InternalResult { - Ok(Self { - jvm: JavaVM::from_raw(jvm)?, - }) - } -} -unsafe impl Send for CloneJavaVM {} -unsafe impl Sync for CloneJavaVM {} - -impl Deref for CloneJavaVM { - type Target = JavaVM; - - fn deref(&self) -> &Self::Target { - &self.jvm - } -} - /// Use with `.map_err()` to map `jni::errors::Error::JavaException` into a /// richer error based on the actual contents of the `JThrowable` /// @@ -60,9 +30,11 @@ pub(crate) fn clear_and_map_exception_to_err( ) -> InternalAppError { if matches!(err, jni::errors::Error::JavaException) { let result = env.with_local_frame::<_, _, InternalAppError>(5, |env| { - let e = env.exception_occurred()?; - assert!(!e.is_null()); // should only be called after receiving a JavaException Result - env.exception_clear()?; + let Some(e) = env.exception_occurred() else { + // should only be called after receiving a JavaException Result + unreachable!("JNI Error was JavaException but no exception was set"); + }; + env.exception_clear(); let class = env.get_object_class(&e)?; //let get_stack_trace_method = env.get_method_id(&class, "getStackTrace", "()[Ljava/lang/StackTraceElement;")?; @@ -105,47 +77,45 @@ pub(crate) fn clear_and_map_exception_to_err( } } -pub(crate) fn device_key_character_map( - jvm: CloneJavaVM, +pub(crate) fn device_key_character_map_with_env( + env: &mut jni::JNIEnv<'_>, key_map_binding: Arc, device_id: i32, -) -> InternalResult { - // Don't really need to 'attach' since this should be called from the app's main thread that - // should already be attached, but the redundancy should be fine - // - // Attach 'permanently' to avoid any chance of detaching the thread from the VM - let mut env = jvm.attach_current_thread_permanently()?; - - // We don't want to accidentally leak any local references while we - // aren't going to be returning from here back to the JVM, to unwind, so - // we make a local frame - let character_map = env.with_local_frame::<_, _, jni::errors::Error>(10, |env| { - let input_device_class = env.find_class("android/view/InputDevice")?; // Creates a local ref - let device = env - .call_static_method( - input_device_class, - "getDevice", - "(I)Landroid/view/InputDevice;", - &[device_id.into()], - )? - .l()?; // Creates a local ref - - let character_map = env - .call_method( - &device, - "getKeyCharacterMap", - "()Landroid/view/KeyCharacterMap;", - &[], - )? - .l()?; - let character_map = env.new_global_ref(character_map)?; - - Ok(character_map) - })?; +) -> jni::errors::Result { + let input_device_class = env.find_class("android/view/InputDevice")?; // Creates a local ref + let device = env + .call_static_method( + input_device_class, + "getDevice", + "(I)Landroid/view/InputDevice;", + &[device_id.into()], + )? + .l()?; // Creates a local ref + + let character_map = env + .call_method( + &device, + "getKeyCharacterMap", + "()Landroid/view/KeyCharacterMap;", + &[], + )? + .l()?; + let character_map = env.new_global_ref(character_map)?; Ok(KeyCharacterMap::new( - jvm.clone(), + env.get_java_vm().clone(), key_map_binding, character_map, )) } + +pub(crate) fn device_key_character_map( + jvm: JavaVM, + key_map_binding: Arc, + device_id: i32, +) -> InternalResult { + jvm.attach_current_thread(JNIVersion::V1_4, |env| { + device_key_character_map_with_env(env, key_map_binding, device_id) + .map_err(|err| clear_and_map_exception_to_err(env, err)) + }) +} diff --git a/android-activity/src/native_activity/glue.rs b/android-activity/src/native_activity/glue.rs index dab3e29..13da40b 100644 --- a/android-activity/src/native_activity/glue.rs +++ b/android-activity/src/native_activity/glue.rs @@ -9,10 +9,10 @@ use std::{ sync::{Arc, Condvar, Mutex, Weak}, }; +use jni::JNIVersion; use ndk::{configuration::Configuration, input_queue::InputQueue, native_window::NativeWindow}; use crate::{ - jni_utils::CloneJavaVM, util::{abort_on_panic, forward_stdio_to_logcat, log_panic}, ConfigurationRef, }; @@ -855,15 +855,18 @@ extern "C" fn ANativeActivity_onCreate( let jvm = abort_on_panic(|| unsafe { let na = activity; - let jvm: *mut jni_sys::JavaVM = (*na).vm; + let jvm: *mut jni::sys::JavaVM = (*na).vm.cast(); let activity = (*na).clazz; // Completely bogus name; this is the _instance_ not class pointer ndk_context::initialize_android_context(jvm.cast(), activity.cast()); - let jvm = CloneJavaVM::from_raw(jvm).unwrap(); - // Since this is a newly spawned thread then the JVM hasn't been attached - // to the thread yet. Attach before calling the applications main function - // so they can safely make JNI calls - jvm.attach_current_thread_permanently().unwrap(); + let jvm = jni::JavaVM::from_raw(jvm); + // Since this is a newly spawned thread then the JVM hasn't been attached to the + // thread yet. + // + // For compatibility we attach before calling the applications main function to + // allow it to assume the thread is attached before making JNI calls. + jvm.attach_current_thread::<_, _, jni::errors::Error>(JNIVersion::V1_4, |_| Ok(())) + .expect("Failed to attach thread to JVM"); jvm }); @@ -900,7 +903,11 @@ extern "C" fn ANativeActivity_onCreate( // This should detach automatically but lets detach explicitly to avoid depending // on the TLS trickery in `jni-rs` - jvm.detach_current_thread(); + if let Err(err) = jvm.detach_current_thread() { + log::error!("Failed to detach thread from JVM: {}", err); + } else { + log::debug!("Detached thread from JVM"); + } ndk_context::release_android_context(); } diff --git a/android-activity/src/native_activity/mod.rs b/android-activity/src/native_activity/mod.rs index 88a9fe8..8ac7d05 100644 --- a/android-activity/src/native_activity/mod.rs +++ b/android-activity/src/native_activity/mod.rs @@ -8,6 +8,7 @@ use std::ptr::NonNull; use std::sync::{Arc, Mutex, RwLock, Weak}; use std::time::Duration; +use jni::JavaVM; use libc::c_void; use log::{error, trace}; use ndk::input_queue::InputQueue; @@ -16,7 +17,7 @@ use ndk::{asset::AssetManager, native_window::NativeWindow}; use crate::error::InternalResult; use crate::input::{Axis, KeyCharacterMap, KeyCharacterMapBinding}; use crate::input::{TextInputState, TextSpan}; -use crate::jni_utils::{self, CloneJavaVM}; +use crate::jni_utils; use crate::{ util, AndroidApp, ConfigurationRef, InputStatus, MainEvent, PollEvent, Rect, WindowManagerFlags, }; @@ -86,50 +87,51 @@ impl AndroidAppWaker { } impl AndroidApp { - pub(crate) fn new(native_activity: NativeActivityGlue, jvm: CloneJavaVM) -> Self { - let mut env = jvm.get_env().unwrap(); // We attach to the thread before creating the AndroidApp - - let key_map_binding = match KeyCharacterMapBinding::new(&mut env) { - Ok(b) => b, - Err(err) => { - panic!("Failed to create KeyCharacterMap JNI bindings: {err:?}"); - } - }; + pub(crate) fn new(native_activity: NativeActivityGlue, jvm: JavaVM) -> Self { + JavaVM::with_env::<_, _, jni::errors::Error>(|env| { + let key_map_binding = match KeyCharacterMapBinding::new(env) { + Ok(b) => b, + Err(err) => { + panic!("Failed to create KeyCharacterMap JNI bindings: {err:?}"); + } + }; - let app = Self { - inner: Arc::new(RwLock::new(AndroidAppInner { - jvm, - native_activity, - looper: Looper { - ptr: ptr::null_mut(), - }, - key_map_binding: Arc::new(key_map_binding), - key_maps: Mutex::new(HashMap::new()), - input_receiver: Mutex::new(None), - })), - }; + let app = Self { + inner: Arc::new(RwLock::new(AndroidAppInner { + jvm, + native_activity, + looper: Looper { + ptr: ptr::null_mut(), + }, + key_map_binding: Arc::new(key_map_binding), + key_maps: Mutex::new(HashMap::new()), + input_receiver: Mutex::new(None), + })), + }; - { - let mut guard = app.inner.write().unwrap(); - - let main_fd = guard.native_activity.cmd_read_fd(); - unsafe { - guard.looper.ptr = ndk_sys::ALooper_prepare( - ndk_sys::ALOOPER_PREPARE_ALLOW_NON_CALLBACKS as libc::c_int, - ); - ndk_sys::ALooper_addFd( - guard.looper.ptr, - main_fd, - LOOPER_ID_MAIN, - ndk_sys::ALOOPER_EVENT_INPUT as libc::c_int, - None, - //&mut guard.cmd_poll_source as *mut _ as *mut _); - ptr::null_mut(), - ); + { + let mut guard = app.inner.write().unwrap(); + + let main_fd = guard.native_activity.cmd_read_fd(); + unsafe { + guard.looper.ptr = ndk_sys::ALooper_prepare( + ndk_sys::ALOOPER_PREPARE_ALLOW_NON_CALLBACKS as libc::c_int, + ); + ndk_sys::ALooper_addFd( + guard.looper.ptr, + main_fd, + LOOPER_ID_MAIN, + ndk_sys::ALOOPER_EVENT_INPUT as libc::c_int, + None, + //&mut guard.cmd_poll_source as *mut _ as *mut _); + ptr::null_mut(), + ); + } } - } - app + Ok(app) + }) + .expect("Failed to create AndroidApp instance") } } @@ -142,7 +144,7 @@ unsafe impl Sync for Looper {} #[derive(Debug)] pub(crate) struct AndroidAppInner { - pub(crate) jvm: CloneJavaVM, + pub(crate) jvm: JavaVM, pub(crate) native_activity: NativeActivityGlue, looper: Looper,