use jni::{
jni_sig, jni_str,
objects::{JObject, JString, JThread},
vm::JavaVM,
};
use log::Level;
use ndk::asset::AssetManager;
use std::{
ffi::{c_void, CStr, CString},
fs::File,
io::{BufRead as _, BufReader},
os::fd::{FromRawFd as _, RawFd},
sync::OnceLock,
};
use crate::{
main_callbacks::MainCallbacks, util::android_log, OnCreateState, ANDROID_ACTIVITY_TAG,
};
fn forward_stdio_to_logcat() -> std::thread::JoinHandle<std::io::Result<()>> {
let file = unsafe {
let mut logpipe: [RawFd; 2] = Default::default();
libc::pipe2(logpipe.as_mut_ptr(), libc::O_CLOEXEC);
libc::dup2(logpipe[1], libc::STDOUT_FILENO);
libc::dup2(logpipe[1], libc::STDERR_FILENO);
libc::close(logpipe[1]);
File::from_raw_fd(logpipe[0])
};
std::thread::Builder::new()
.name("stdio-to-logcat".to_string())
.spawn(move || -> std::io::Result<()> {
let tag = c"RustStdoutStderr";
let mut reader = BufReader::new(file);
let mut buffer = String::new();
loop {
buffer.clear();
let len = match reader.read_line(&mut buffer) {
Ok(len) => len,
Err(e) => {
android_log(
Level::Error,
ANDROID_ACTIVITY_TAG,
&CString::new(format!(
"Logcat forwarder failed to read stdin/stderr: {e:?}"
))
.unwrap(),
);
break Err(e);
}
};
if len == 0 {
break Ok(());
} else if let Ok(msg) = CString::new(buffer.clone()) {
android_log(Level::Info, tag, &msg);
}
}
})
.expect("Failed to start stdout/stderr to logcat forwarder thread")
}
unsafe extern "C" fn _android_activity_anchor() {}
fn dlopen_self() -> Result<*mut c_void, String> {
unsafe {
let mut info: libc::Dl_info = std::mem::zeroed();
if libc::dladdr(
_android_activity_anchor as *const () as *const c_void,
&mut info,
) == 0
{
return Err("dladdr failed".into());
}
if info.dli_fname.is_null() {
return Err("dladdr returned null dli_fname".into());
}
libc::dlerror();
let handle = libc::dlopen(info.dli_fname, libc::RTLD_NOW | libc::RTLD_NOLOAD);
if handle.is_null() {
let err = CStr::from_ptr(libc::dlerror())
.to_string_lossy()
.into_owned();
let path = CStr::from_ptr(info.dli_fname)
.to_string_lossy()
.into_owned();
return Err(format!("dlopen({path}) failed: {err}"));
}
Ok(handle)
}
}
fn lookup_self_symbol(symbol: &CStr) -> Option<*mut c_void> {
unsafe {
let handle = match dlopen_self() {
Ok(h) => h,
Err(err) => {
let msg = format!(
"Warning: failed to dlopen self, looking for symbol {}: {err}",
symbol.to_string_lossy()
);
android_log(
Level::Warn,
ANDROID_ACTIVITY_TAG,
&CString::new(msg).unwrap(),
);
return None;
}
};
libc::dlerror();
let sym = libc::dlsym(handle, symbol.as_ptr());
if libc::dlclose(handle) != 0 {
let err = CStr::from_ptr(libc::dlerror())
.to_string_lossy()
.into_owned();
let msg = format!("dlclose failed for self handle: {err}");
android_log(
Level::Warn,
ANDROID_ACTIVITY_TAG,
&CString::new(msg).unwrap(),
);
}
if sym.is_null() {
None
} else {
Some(sym)
}
}
}
pub(crate) unsafe fn init_java_main_thread_on_create(
jvm: JavaVM,
jni_activity: *mut c_void,
saved_state: &[u8],
) {
let _join_log_forwarder = forward_stdio_to_logcat();
let msg = CString::new(format!(
"Creating: Activity = {:p}, saved state size = {}",
jni_activity,
saved_state.len()
))
.unwrap();
android_log(Level::Info, ANDROID_ACTIVITY_TAG, &msg);
let android_on_create: extern "Rust" fn(state: &OnCreateState) = unsafe {
let Some(symbol) = lookup_self_symbol(c"android_on_create") else {
return;
};
std::mem::transmute(symbol)
};
let state = OnCreateState::new(jvm.clone(), jni_activity, saved_state);
let res = jvm.attach_current_thread(|_env| -> jni::errors::Result<()> {
android_on_create(&state);
Ok(())
});
if let Err(err) = res {
let msg = CString::new(format!(
"JNI error while running android_on_create: {:?}",
err
))
.unwrap();
android_log(Level::Error, ANDROID_ACTIVITY_TAG, &msg);
}
}
struct AppState {
main_callbacks: MainCallbacks,
app_asset_manager: AssetManager,
}
static APP_ONCE: OnceLock<AppState> = OnceLock::new();
fn get_application<'local, 'any>(
env: &mut jni::Env<'local>,
activity: &JObject<'any>,
) -> jni::errors::Result<JObject<'local>> {
let app = env
.call_method(
activity,
jni_str!("getApplication"),
jni_sig!(() -> android.app.Application),
&[],
)?
.l()?;
Ok(app)
}
fn get_assets<'local, 'any>(
env: &mut jni::Env<'local>,
application: &JObject<'any>,
) -> jni::errors::Result<JObject<'local>> {
let assets_manager = env
.call_method(
application,
jni_str!("getAssets"),
jni_sig!(() -> android.content.res.AssetManager),
&[],
)?
.l()?;
Ok(assets_manager)
}
fn try_init_current_thread(env: &mut jni::Env, activity: &JObject) -> jni::errors::Result<()> {
let activity_class = env.get_object_class(activity)?;
let class_loader = activity_class.get_class_loader(env)?;
let thread = JThread::current_thread(env)?;
thread.set_context_class_loader(env, &class_loader)?;
let thread_name = JString::from_jni_str(env, jni_str!("android_main"))?;
thread.set_name(env, &thread_name)?;
unsafe {
let thread_name = c"android_main";
let _ = libc::pthread_setname_np(libc::pthread_self(), thread_name.as_ptr());
}
Ok(())
}
pub(crate) fn init_android_main_thread(
vm: &JavaVM,
jni_activity: &JObject,
java_main_looper: &ndk::looper::ForeignLooper,
) -> jni::errors::Result<(AssetManager, MainCallbacks)> {
vm.with_local_frame(10, |env| -> jni::errors::Result<_> {
let app_state = APP_ONCE.get_or_init(|| unsafe {
let application =
get_application(env, jni_activity).expect("Failed to get Application instance");
let app_asset_manager =
get_assets(env, &application).expect("Failed to get AssetManager");
let app_global = env
.new_global_ref(application)
.expect("Failed to create global ref for Application");
let app_global = app_global.into_raw();
ndk_context::initialize_android_context(vm.get_raw().cast(), app_global.cast());
let asset_manager_global = env
.new_global_ref(app_asset_manager)
.expect("Failed to create global ref for AssetManager");
let asset_manager_global = asset_manager_global.into_raw();
let asset_manager_ptr =
ndk_sys::AAssetManager_fromJava(env.get_raw() as _, asset_manager_global as _);
assert_ne!(
asset_manager_ptr,
std::ptr::null_mut(),
"Failed to get Application AAssetManager"
);
let app_asset_manager =
AssetManager::from_ptr(std::ptr::NonNull::new(asset_manager_ptr).unwrap());
let main_callbacks = MainCallbacks::new(java_main_looper);
AppState {
main_callbacks,
app_asset_manager,
}
});
if let Err(err) = try_init_current_thread(env, jni_activity) {
let msg =
CString::new(format!("Failed to initialize Java thread state: {:?}", err)).unwrap();
android_log(Level::Error, ANDROID_ACTIVITY_TAG, &msg);
}
let asset_manager = unsafe { AssetManager::from_ptr(app_state.app_asset_manager.ptr()) };
let main_callbacks = app_state.main_callbacks.clone();
Ok((asset_manager, main_callbacks))
})
}