use crate::{convert::*, jni_clear_ex_ignore, jni_with_env, AutoLocalGlobalize, JObjectAutoLocal};
use jni::{errors::Error, objects::*};
#[allow(unused)]
use std::sync::OnceLock;
#[cfg(feature = "proxy")]
#[cfg(not(target_os = "android"))]
const CLASS_DATA: &[u8] = include_bytes!(concat!(
env!("OUT_DIR"),
"/rust/jniminhelper/InvocHdl.class"
));
#[cfg(feature = "proxy")]
#[cfg(target_os = "android")]
const DEX_DATA: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/classes.dex"));
#[cfg(feature = "proxy")]
pub(crate) fn get_helper_class_loader() -> Result<&'static JniClassLoader, Error> {
static CLASS_LOADER: OnceLock<JniClassLoader> = OnceLock::new();
#[cfg(not(target_os = "android"))]
if CLASS_LOADER.get().is_none() {
let loader = JniClassLoader::app_loader()?;
loader.define_class("rust/jniminhelper/InvocHdl", CLASS_DATA)?;
let _ = CLASS_LOADER.set(loader);
}
#[cfg(target_os = "android")]
if CLASS_LOADER.get().is_none() {
let loader = JniClassLoader::load_dex(DEX_DATA)?;
let _ = CLASS_LOADER.set(loader);
}
Ok(CLASS_LOADER.get().unwrap())
}
#[derive(Clone, Debug)]
pub struct JniClassLoader {
inner: GlobalRef,
}
impl TryFrom<&JObject<'_>> for JniClassLoader {
type Error = Error;
fn try_from(value: &JObject<'_>) -> Result<Self, Self::Error> {
jni_with_env(|env| {
let cls_loader = env.find_class("java/lang/ClassLoader").auto_local(env)?;
value
.class_check(cls_loader.as_class(), env)
.and_then(|l| env.new_global_ref(l))
.map(|inner| Self { inner })
})
}
}
impl AsRef<JObject<'static>> for JniClassLoader {
fn as_ref(&self) -> &JObject<'static> {
self.inner.as_obj()
}
}
impl std::ops::Deref for JniClassLoader {
type Target = JObject<'static>;
fn deref(&self) -> &Self::Target {
self.inner.as_obj()
}
}
impl JniClassLoader {
#[cfg(not(target_os = "android"))]
pub fn app_loader() -> Result<Self, Error> {
jni_with_env(|env| {
env.call_static_method(
"java/lang/ClassLoader",
"getSystemClassLoader",
"()Ljava/lang/ClassLoader;",
&[],
)
.get_object(env)
.globalize(env)
.map(|inner| Self { inner })
})
}
#[cfg(target_os = "android")]
pub fn app_loader() -> Result<Self, Error> {
jni_with_env(|env| {
let context = android_context();
env.call_method(context, "getClassLoader", "()Ljava/lang/ClassLoader;", &[])
.get_object(env)
.globalize(env)
.map(|inner| Self { inner })
})
}
#[cfg(all(feature = "proxy", target_os = "android"))]
pub fn helper_loader() -> Result<&'static Self, Error> {
get_helper_class_loader()
}
pub fn load_class(&self, name: &str) -> Result<GlobalRef, Error> {
jni_with_env(|env| {
if let Ok(cls) = env
.find_class(class_name_to_internal(name))
.map_err(jni_clear_ex_ignore)
.global_ref(env)
{
return Ok(cls);
}
let class_name = class_name_to_java(name).new_jobject(env)?;
env.call_method(
self,
"loadClass",
"(Ljava/lang/String;)Ljava/lang/Class;",
&[(&class_name).into()],
)
.get_object(env)
.and_then(|cls| cls.null_check_owned("ClassLoader.findClass() returned null"))
.globalize(env)
})
}
#[cfg(not(target_os = "android"))]
pub fn define_class(&self, name: &str, data: &[u8]) -> Result<GlobalRef, Error> {
jni_with_env(|env| {
env.define_class(name, self, data)
.global_ref(env)
.and_then(|cls| cls.null_check_owned("JNIEnv::define_class() returned null"))
})
}
}
#[cfg(target_os = "android")]
impl JniClassLoader {
pub fn load_dex(dex_data: &'static [u8]) -> Result<Self, Error> {
let parent_class_loader = Self::app_loader()?;
parent_class_loader.append_dex(dex_data)
}
pub fn append_dex(&self, dex_data: &'static [u8]) -> Result<Self, Error> {
jni_with_env(|env| {
let context = android_context();
if android_api_level() >= 26 {
let dex_buffer = unsafe {
env.new_direct_byte_buffer(dex_data.as_ptr() as *mut _, dex_data.len())
.auto_local(env)?
};
env.new_object(
"dalvik/system/InMemoryDexClassLoader",
"(Ljava/nio/ByteBuffer;Ljava/lang/ClassLoader;)V",
&[(&dex_buffer).into(), self.into()],
)
} else {
let code_cache_path = if android_api_level() >= 21 {
env.call_method(context, "getCodeCacheDir", "()Ljava/io/File;", &[])
} else {
let dir_name = "code_cache".new_jobject(env)?;
env.call_method(
context,
"getDir",
"(Ljava/lang/String;I)Ljava/io/File;",
&[(&dir_name).into(), 0.into()],
)
}
.get_object(env)
.and_then(|p| env.call_method(&p, "getAbsolutePath", "()Ljava/lang/String;", &[]))
.get_object(env)?
.get_string(env)
.map(std::path::PathBuf::from)?;
let dex_hash = {
use std::hash::{DefaultHasher, Hasher};
let mut hasher = DefaultHasher::new();
hasher.write(dex_data);
hasher.finish()
};
let dex_name = format!("{dex_hash:016x}.dex");
let dex_file_path = code_cache_path.join(dex_name);
std::fs::write(&dex_file_path, dex_data).unwrap(); let dex_file_path = dex_file_path.to_string_lossy().new_jobject(env)?;
let oats_dir_path = code_cache_path.join("oats");
let _ = std::fs::create_dir(&oats_dir_path);
let oats_dir_path = oats_dir_path.to_string_lossy().new_jobject(env)?;
env.new_object(
"dalvik/system/DexClassLoader",
"(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/ClassLoader;)V",
&[
(&dex_file_path).into(),
(&oats_dir_path).into(),
(&JObject::null()).into(),
self.into(),
],
)
}
.global_ref(env)
.map(|inner| Self { inner })
})
}
#[cfg(feature = "proxy")]
pub fn replace_app_loader(&self) -> Result<(), Error> {
use crate::jni_clear_ex;
jni_with_env(|env| {
let th = get_activity_thread(env)?;
let packages = env
.get_field(&th, "mPackages", "Landroid/util/ArrayMap;")
.get_object(env)?;
let pkg_name = android_app_package_name().new_jobject(env)?;
let loaded_apk_weak = env
.call_method(
&packages,
"get",
"(Ljava/lang/Object;)Ljava/lang/Object;",
&[(&pkg_name).into()],
)
.get_object(env)?;
let loaded_apk = env
.call_method(&loaded_apk_weak, "get", "()Ljava/lang/Object;", &[])
.get_object(env)?;
env.set_field(
&loaded_apk,
"mClassLoader",
"Ljava/lang/ClassLoader;",
self.inner.as_obj().into(),
)
.map_err(jni_clear_ex)
})
}
}
#[cfg(target_os = "android")]
#[inline(always)]
pub fn android_context() -> &'static JObject<'static> {
static ANDROID_CONTEXT: OnceLock<(GlobalRef, bool)> = OnceLock::new();
let (ctx, from_glue_crate) = ANDROID_CONTEXT.get_or_init(|| {
jni_with_env(|env| {
let ctx = ndk_context::android_context();
let obj = unsafe { JObject::from_raw(ctx.context().cast()) };
if !obj.is_null() {
Ok((env.new_global_ref(obj)?, true))
} else {
let th = get_activity_thread(env)?;
env.call_method(&th, "getApplication", "()Landroid/app/Application;", &[])
.get_object(env)
.globalize(env)
.map(|ctx| (ctx, false))
}
})
.unwrap()
});
if !from_glue_crate {
warn!("`ndk_context::android_context().context()` is null. Check the Android glue crate.");
warn!("Using `Application` (No `Activity` and UI availability); other crates may fail.");
}
ctx.as_obj()
}
#[cfg(target_os = "android")]
fn get_activity_thread<'a>(env: &mut jni::JNIEnv<'a>) -> Result<AutoLocal<'a, JObject<'a>>, Error> {
env.call_static_method(
"android/app/ActivityThread",
"currentActivityThread",
"()Landroid/app/ActivityThread;",
&[],
)
.get_object(env)
}
#[cfg(target_os = "android")]
pub fn android_api_level() -> i32 {
static API_LEVEL: OnceLock<i32> = OnceLock::new();
*API_LEVEL.get_or_init(|| {
jni_with_env(|env| {
let os_build_class = env.find_class("android/os/Build$VERSION").unwrap();
env.get_static_field(&os_build_class, "SDK_INT", "I")
.get_int()
})
.unwrap()
})
}
#[cfg(target_os = "android")]
pub fn android_app_name() -> &'static str {
static APP_NAME: OnceLock<String> = OnceLock::new();
APP_NAME.get_or_init(|| {
android_app_package_name()
.split('.')
.next_back()
.unwrap()
.to_string()
})
}
#[cfg(target_os = "android")]
pub fn android_app_package_name() -> &'static str {
static PACKAGE_NAME: OnceLock<String> = OnceLock::new();
PACKAGE_NAME.get_or_init(|| {
jni_with_env(|env| {
let ctx = android_context();
env.call_method(ctx, "getPackageName", "()Ljava/lang/String;", &[])
.get_object(env)?
.get_string(env)
})
.unwrap()
})
}