Skip to content

Update to jni 0.22 and jni-sys 0.4 #202

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: release-0.6
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,6 @@ resolver = "2"
members = ["android-activity"]

exclude = ["examples"]

[patch.crates-io]
jni = { path = "/home/rib/src/jni-rs/jni-git-dev0" }
1 change: 0 additions & 1 deletion android-activity/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
2 changes: 1 addition & 1 deletion android-activity/src/game_activity/ffi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};

Expand Down
83 changes: 48 additions & 35 deletions android-activity/src/game_activity/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand All @@ -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,
Expand Down Expand Up @@ -121,32 +121,35 @@ impl AndroidAppWaker {
}

impl AndroidApp {
pub(crate) unsafe fn from_ptr(ptr: NonNull<ffi::android_app>, 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<ffi::android_app>, 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")
}
}

Expand Down Expand Up @@ -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<Option<NativeWindow>>,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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
};

Expand Down Expand Up @@ -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();
}
Expand Down
152 changes: 76 additions & 76 deletions android-activity/src/input/sdk.rs
Original file line number Diff line number Diff line change
@@ -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},
Expand Down Expand Up @@ -94,7 +91,7 @@ pub enum KeyMapChar {
#[derive(Debug)]
pub(crate) struct KeyCharacterMapBinding {
//vm: JavaVM,
klass: GlobalRef,
klass: GlobalRef<JClass<'static>>,
get_method_id: JMethodID,
get_dead_char_method_id: JStaticMethodID,
get_keyboard_type_method_id: JMethodID,
Expand Down Expand Up @@ -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<KeyCharacterMapBinding>,
key_map: GlobalRef,
key_map: GlobalRef<JObject<'static>>,
}
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<KeyCharacterMapBinding>,
key_map: GlobalRef,
key_map: GlobalRef<JObject<'static>>,
) -> Self {
Self {
jvm,
Expand All @@ -247,41 +257,39 @@ impl KeyCharacterMap {
/// is caught.
pub fn get(&self, key_code: Keycode, meta_state: MetaState) -> Result<KeyMapChar, AppError> {
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.
Expand All @@ -297,28 +305,24 @@ impl KeyCharacterMap {
accent_char: char,
base_char: char,
) -> Result<Option<char>, 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()
})
}

Expand All @@ -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<KeyboardType, AppError> {
// 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()
})
}
}
Loading
Loading