pub use jni;
pub use {convert::*, loader::*};
#[cfg(feature = "proxy")]
pub use proxy::*;
#[cfg(feature = "proxy")]
#[cfg(target_os = "android")]
pub use {permission::*, receiver::*};
#[cfg(not(target_os = "android"))]
macro_rules! warn {
($($arg:tt)+) => (eprintln!($($arg)+))
}
#[cfg(target_os = "android")]
macro_rules! warn {
($($arg:tt)+) => (log::warn!($($arg)+))
}
mod convert;
mod loader;
#[cfg(feature = "proxy")]
mod proxy;
#[cfg(feature = "proxy")]
#[cfg(target_os = "android")]
mod receiver;
#[cfg(feature = "proxy")]
#[cfg(target_os = "android")]
mod permission;
use jni::{
errors::Error,
objects::{GlobalRef, JObject},
JNIEnv, JavaVM,
};
use std::{cell::Cell, sync::OnceLock};
type AutoLocal<'a> = jni::objects::AutoLocal<'a, JObject<'a>>;
static JAVA_VM: OnceLock<JavaVM> = OnceLock::new();
thread_local! {
static LAST_CLEARED_EX: Cell<Option<GlobalRef>> = const { Cell::new(None) };
}
#[inline(always)]
pub fn jni_with_env<R>(f: impl FnOnce(&mut JNIEnv) -> Result<R, Error>) -> Result<R, Error> {
let vm = unsafe { jni_get_vm() };
let mut guarded_env = vm.attach_current_thread()?;
f(&mut guarded_env).map_err(jni_clear_ex)
}
pub fn jni_attach_permanently() -> bool {
let vm = unsafe { jni_get_vm() };
if vm.get_env().is_ok() {
return false;
}
vm.attach_current_thread_permanently().is_ok()
}
#[cfg(not(target_os = "android"))]
pub unsafe fn jni_set_vm(vm: &JavaVM) -> bool {
if JAVA_VM.get().is_some() {
false
} else {
let vm = unsafe { JavaVM::from_raw(vm.get_java_vm_pointer()).unwrap() };
JAVA_VM.set(vm).unwrap();
true
}
}
#[cfg(not(target_os = "android"))]
#[inline(always)]
pub unsafe fn jni_get_vm() -> JavaVM {
let raw_vm = JAVA_VM
.get_or_init(|| {
let args = jni::InitArgsBuilder::new().build().unwrap();
JavaVM::new(args).unwrap()
})
.get_java_vm_pointer();
jni::JavaVM::from_raw(raw_vm.cast()).unwrap()
}
#[cfg(target_os = "android")]
#[inline(always)]
pub unsafe fn jni_get_vm() -> JavaVM {
let raw_vm = JAVA_VM
.get_or_init(|| {
let ctx = ndk_context::android_context();
unsafe { jni::JavaVM::from_raw(ctx.vm().cast()) }.unwrap()
})
.get_java_vm_pointer();
jni::JavaVM::from_raw(raw_vm.cast()).unwrap()
}
#[inline]
pub fn jni_clear_ex(err: Error) -> Error {
jni_clear_ex_inner(err, true, true)
}
#[inline]
pub fn jni_clear_ex_silent(err: Error) -> Error {
jni_clear_ex_inner(err, false, true)
}
#[inline]
pub fn jni_clear_ex_ignore(err: Error) -> Error {
jni_clear_ex_inner(err, false, false)
}
#[inline(always)]
pub fn jni_last_cleared_ex() -> Option<GlobalRef> {
LAST_CLEARED_EX.take()
}
#[inline(always)]
fn jni_clear_ex_inner(err: Error, print_err: bool, store_ex: bool) -> Error {
let thread_id = std::thread::current().id();
if let Error::JavaException = err {
let _ = jni_with_env(|env| {
if env.exception_check().unwrap_or(true) {
if !print_err && !store_ex {
env.exception_clear().unwrap();
return Ok(());
}
let ex = env.exception_occurred();
#[cfg(not(target_os = "android"))]
if print_err {
let _ = env.exception_describe();
}
env.exception_clear().unwrap();
if print_err {
#[cfg(target_os = "android")]
if let Ok(ex) = ex.as_ref() {
if let Ok(ex_msg) = ex.get_throwable_msg(env) {
let ex_type = class_name_to_java(&ex.get_class_name(env).unwrap());
warn!("Exception in thread \"{thread_id:?}\" {ex_type}: {ex_msg}");
} else {
warn!("Unknown Java exception in thread \"{thread_id:?}\"");
}
}
print_rust_stack();
}
if store_ex {
if let Ok(ex) = ex.global_ref(env) {
LAST_CLEARED_EX.set(Some(ex));
}
} else {
let _ = ex.auto_local(env);
}
}
Ok(())
});
} else if print_err {
warn!("JNI Error in thread \"{thread_id:?}\": {err:?}");
print_rust_stack();
}
err
}
fn print_rust_stack() {
use std::backtrace::*;
#[cfg(not(target_os = "android"))]
{
let backtrace = Backtrace::capture();
if let BacktraceStatus::Captured = backtrace.status() {
warn!("{}", backtrace);
}
}
#[cfg(target_os = "android")]
warn!("\n{}", Backtrace::force_capture());
}
pub trait JObjectAutoLocal<'a> {
fn auto_local(self, env: &JNIEnv<'a>) -> Result<AutoLocal<'a>, Error>;
fn global_ref(self, env: &JNIEnv<'a>) -> Result<GlobalRef, Error>;
}
impl<'a, T> JObjectAutoLocal<'a> for Result<T, Error>
where
T: Into<JObject<'a>>,
{
#[inline(always)]
fn auto_local(self, env: &JNIEnv<'a>) -> Result<AutoLocal<'a>, Error> {
self.map(|o| env.auto_local(o.into())).map_err(jni_clear_ex)
}
#[inline(always)]
fn global_ref(self, env: &JNIEnv<'a>) -> Result<GlobalRef, Error> {
let local = self.auto_local(env);
local.globalize(env)
}
}
pub trait AutoLocalGlobalize<'a> {
fn globalize(self, env: &JNIEnv<'a>) -> Result<GlobalRef, Error>;
}
impl<'a> AutoLocalGlobalize<'a> for Result<AutoLocal<'a>, Error> {
#[inline(always)]
fn globalize(self, env: &JNIEnv<'a>) -> Result<GlobalRef, Error> {
let local = self?;
let global = env.new_global_ref(&local)?;
if !local.is_null() {
global.null_check("new_global_ref")?;
}
Ok(global)
}
}