#[cfg(target_os = "android")]
use jni::objects::{GlobalRef, JClass, JObject, JString, JValue};
#[cfg(target_os = "android")]
use jni::sys::jstring;
#[cfg(target_os = "android")]
use jni::{JNIEnv, JavaVM};
#[cfg(target_os = "android")]
use std::sync::Arc;
#[cfg(target_os = "android")]
use blinc_core::native_bridge::{
parse_native_result_json, NativeBridgeError, NativeBridgeState, NativeResult, NativeValue,
PlatformAdapter,
};
#[cfg(target_os = "android")]
use tracing::{debug, error, warn};
#[cfg(target_os = "android")]
pub struct AndroidNativeBridgeAdapter {
vm: JavaVM,
bridge_class: GlobalRef,
}
#[cfg(target_os = "android")]
impl AndroidNativeBridgeAdapter {
pub fn new(vm: JavaVM, env: &mut JNIEnv) -> Result<Self, jni::errors::Error> {
let class = env.find_class("com/blinc/BlincNativeBridge")?;
let bridge_class = env.new_global_ref(class)?;
debug!("AndroidNativeBridgeAdapter initialized");
Ok(Self { vm, bridge_class })
}
pub fn from_android_app(app: &android_activity::AndroidApp) -> Result<Self, NativeBridgeError> {
let vm = unsafe { JavaVM::from_raw(app.vm_as_ptr() as *mut _) }.map_err(|e| {
NativeBridgeError::PlatformError(format!("Failed to get JavaVM: {}", e))
})?;
let activity_jobject = app.activity_as_ptr() as jni::sys::jobject;
if activity_jobject.is_null() {
return Err(NativeBridgeError::PlatformError(
"Activity jobject is null".into(),
));
}
let bridge_class = {
let mut env = vm.attach_current_thread().map_err(|e| {
NativeBridgeError::PlatformError(format!("Failed to attach thread: {}", e))
})?;
let activity_obj = unsafe { JObject::from_raw(activity_jobject) };
let activity_class = env.get_object_class(&activity_obj).map_err(|e| {
NativeBridgeError::PlatformError(format!("Failed to get Activity class: {}", e))
})?;
let loader_obj = env
.call_method(
&activity_class,
"getClassLoader",
"()Ljava/lang/ClassLoader;",
&[],
)
.and_then(|v| v.l())
.map_err(|e| {
NativeBridgeError::PlatformError(format!("Failed to get ClassLoader: {}", e))
})?;
let class_name = env.new_string("com.blinc.BlincNativeBridge").map_err(|e| {
NativeBridgeError::PlatformError(format!(
"Failed to allocate class name string: {}",
e
))
})?;
let bridge_obj = env
.call_method(
&loader_obj,
"loadClass",
"(Ljava/lang/String;)Ljava/lang/Class;",
&[JValue::Object(&class_name)],
)
.and_then(|v| v.l())
.map_err(|e| {
NativeBridgeError::PlatformError(format!(
"Failed to load BlincNativeBridge via Activity ClassLoader: {}",
e
))
})?;
let bridge_jclass: JClass = bridge_obj.into();
env.new_global_ref(bridge_jclass).map_err(|e| {
NativeBridgeError::PlatformError(format!("Failed to create global ref: {}", e))
})?
};
debug!("AndroidNativeBridgeAdapter initialized from AndroidApp");
Ok(Self { vm, bridge_class })
}
fn args_to_json(args: &[NativeValue]) -> String {
let mut parts = Vec::with_capacity(args.len());
for arg in args {
let json = match arg {
NativeValue::Void => "null".to_string(),
NativeValue::Bool(v) => v.to_string(),
NativeValue::Int32(v) => v.to_string(),
NativeValue::Int64(v) => v.to_string(),
NativeValue::Float32(v) => v.to_string(),
NativeValue::Float64(v) => v.to_string(),
NativeValue::String(s) => {
format!("\"{}\"", s.replace('\\', "\\\\").replace('"', "\\\""))
}
NativeValue::Bytes(b) => {
use base64::{engine::general_purpose::STANDARD, Engine};
format!("\"{}\"", STANDARD.encode(b))
}
NativeValue::Json(j) => j.clone(),
};
parts.push(json);
}
format!("[{}]", parts.join(","))
}
}
#[cfg(target_os = "android")]
impl PlatformAdapter for AndroidNativeBridgeAdapter {
fn call(
&self,
namespace: &str,
name: &str,
args: Vec<NativeValue>,
) -> NativeResult<NativeValue> {
debug!("Android native call: {}.{}", namespace, name);
let mut env = self
.vm
.attach_current_thread()
.map_err(|e| NativeBridgeError::PlatformError(format!("JNI attach failed: {}", e)))?;
let ns_jstring = env.new_string(namespace).map_err(|e| {
NativeBridgeError::PlatformError(format!("Failed to create namespace string: {}", e))
})?;
let name_jstring = env.new_string(name).map_err(|e| {
NativeBridgeError::PlatformError(format!("Failed to create name string: {}", e))
})?;
let args_json = Self::args_to_json(&args);
let args_jstring = env.new_string(&args_json).map_err(|e| {
NativeBridgeError::PlatformError(format!("Failed to create args string: {}", e))
})?;
let result = env
.call_static_method(
&self.bridge_class,
"callNative",
"(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;",
&[
JValue::Object(&ns_jstring),
JValue::Object(&name_jstring),
JValue::Object(&args_jstring),
],
)
.map_err(|e| {
error!("JNI call failed: {}", e);
NativeBridgeError::PlatformError(format!("JNI call failed: {}", e))
})?;
let result_obj = result.l().map_err(|e| {
NativeBridgeError::PlatformError(format!("Failed to get result object: {}", e))
})?;
let result_jstring = JString::from(result_obj);
let result_str: String = env
.get_string(&result_jstring)
.map_err(|e| {
NativeBridgeError::PlatformError(format!("Failed to get result string: {}", e))
})?
.into();
debug!("Android native result: {}", result_str);
parse_native_result_json(&result_str)
}
}
#[cfg(target_os = "android")]
pub fn init_android_native_bridge(
app: &android_activity::AndroidApp,
) -> Result<(), NativeBridgeError> {
if !NativeBridgeState::is_initialized() {
NativeBridgeState::init();
}
let adapter = AndroidNativeBridgeAdapter::from_android_app(app)?;
NativeBridgeState::get().set_platform_adapter(Arc::new(adapter));
debug!("Android native bridge initialized");
Ok(())
}
#[cfg(not(target_os = "android"))]
pub struct AndroidNativeBridgeAdapter;
#[cfg(not(target_os = "android"))]
impl Default for AndroidNativeBridgeAdapter {
fn default() -> Self {
Self
}
}
#[cfg(not(target_os = "android"))]
impl AndroidNativeBridgeAdapter {
pub fn new() -> Self {
Self
}
}
#[cfg(not(target_os = "android"))]
pub fn init_android_native_bridge() -> Result<(), String> {
Err("Android native bridge only available on Android".to_string())
}