use jni::{
objects::{GlobalRef, JClass, JObject, JValue},
sys::jlong,
JNIEnv, NativeMethod,
};
use crate::android::java::Result;
pub struct CallbackSystem {
bytecode: &'static [u8],
class_name: &'static str,
callback_name: &'static str,
callback_signature: &'static str,
}
impl CallbackSystem {
pub fn new(
bytecode: &'static [u8],
class_name: &'static str,
callback_name: &'static str,
callback_signature: &'static str,
) -> Self {
Self {
bytecode,
class_name,
callback_name,
callback_signature,
}
}
pub fn load_and_register(&self, env: &mut JNIEnv<'_>) -> Result<GlobalRef> {
let callback_class = self.load_dex_class(env)?;
self.register_native_callback(env, &callback_class)?;
let global = env.new_global_ref(callback_class)?;
Ok(global)
}
fn load_dex_class<'a>(&self, env: &mut JNIEnv<'a>) -> Result<JClass<'a>> {
const IN_MEMORY_LOADER: &str = "dalvik/system/InMemoryDexClassLoader";
let byte_buffer = unsafe {
env.new_direct_byte_buffer(self.bytecode.as_ptr() as *mut u8, self.bytecode.len())
}?;
let dex_class_loader = env.new_object(
IN_MEMORY_LOADER,
"(Ljava/nio/ByteBuffer;Ljava/lang/ClassLoader;)V",
&[
JValue::Object(&byte_buffer),
JValue::Object(&JObject::null()),
],
)?;
let class_name = env.new_string(self.class_name)?;
let class = env
.call_method(
&dex_class_loader,
"loadClass",
"(Ljava/lang/String;)Ljava/lang/Class;",
&[JValue::Object(&class_name)],
)?
.l()?;
Ok(class.into())
}
fn register_native_callback<'a>(
&self,
env: &mut JNIEnv<'a>,
callback_class: &JClass<'a>,
) -> Result<()> {
env.register_native_methods(
callback_class,
&[NativeMethod {
name: self.callback_name.into(),
sig: self.callback_signature.into(),
fn_ptr: rust_callback as *mut _,
}],
)?;
Ok(())
}
}
#[no_mangle]
unsafe extern "C" fn rust_callback<'a>(
mut env: JNIEnv<'a>,
_class: JObject<'a>,
handler_ptr_high: jlong,
handler_ptr_low: jlong,
location: JObject<'a>,
) {
#[cfg(not(target_pointer_width = "64"))]
compile_error!("Only 64-bit Android targets are supported");
let handler_ptr_raw: usize =
((handler_ptr_high as u64) << 32 | handler_ptr_low as u64) as usize;
let callback: fn(&mut JNIEnv, JObject) = unsafe { std::mem::transmute(handler_ptr_raw) };
callback(&mut env, location);
}