diff --git a/android-activity/src/game_activity/ffi.rs b/android-activity/src/game_activity/ffi.rs index 40ecc1d..3196865 100644 --- a/android-activity/src/game_activity/ffi.rs +++ b/android-activity/src/game_activity/ffi.rs @@ -13,8 +13,8 @@ #![allow(dead_code)] use jni_sys::*; -use ndk_sys::{ARect, AConfiguration, ALooper, ALooper_callbackFunc, AAssetManager, ANativeWindow}; -use libc::{size_t, pthread_t, pthread_mutex_t, pthread_cond_t}; +use libc::{pthread_cond_t, pthread_mutex_t, pthread_t, size_t}; +use ndk_sys::{AAssetManager, AConfiguration, ALooper, ALooper_callbackFunc, ANativeWindow, ARect}; #[cfg(all( any(target_os = "android", feature = "test"), diff --git a/android-activity/src/game_activity/mod.rs b/android-activity/src/game_activity/mod.rs index 21755ef..54beb45 100644 --- a/android-activity/src/game_activity/mod.rs +++ b/android-activity/src/game_activity/mod.rs @@ -71,7 +71,7 @@ impl<'a> StateSaver<'a> { } (*app_ptr).savedState = buf; - (*app_ptr).savedStateSize = state.len() as ffi::size_t; + (*app_ptr).savedStateSize = state.len() as _; } } } @@ -549,7 +549,7 @@ extern "C" { pub fn GameActivity_onCreate_C( activity: *mut ffi::GameActivity, savedState: *mut ::std::os::raw::c_void, - savedStateSize: ffi::size_t, + savedStateSize: libc::size_t, ); } @@ -582,7 +582,7 @@ pub unsafe extern "C" fn Java_com_google_androidgamesdk_GameActivity_loadNativeC pub unsafe extern "C" fn GameActivity_onCreate( activity: *mut ffi::GameActivity, saved_state: *mut ::std::os::raw::c_void, - saved_state_size: ffi::size_t, + saved_state_size: libc::size_t, ) { GameActivity_onCreate_C(activity, saved_state, saved_state_size); } diff --git a/android-activity/src/lib.rs b/android-activity/src/lib.rs index 787be40..ed10481 100644 --- a/android-activity/src/lib.rs +++ b/android-activity/src/lib.rs @@ -72,19 +72,34 @@ pub struct Rect { impl Rect { /// An empty `Rect` with all components set to zero. pub fn empty() -> Self { - Self { left: 0, top: 0, right: 0, bottom: 0 } + Self { + left: 0, + top: 0, + right: 0, + bottom: 0, + } } } impl Into for Rect { fn into(self) -> ndk_sys::ARect { - ndk_sys::ARect { left: self.left, right: self.right, top: self.top, bottom: self.bottom } + ndk_sys::ARect { + left: self.left, + right: self.right, + top: self.top, + bottom: self.bottom, + } } } impl From for Rect { fn from(arect: ndk_sys::ARect) -> Self { - Self { left: arect.left, right: arect.right, top: arect.top, bottom: arect.bottom } + Self { + left: arect.left, + right: arect.right, + top: arect.top, + bottom: arect.bottom, + } } } @@ -384,12 +399,6 @@ impl Hash for AndroidApp { } impl AndroidApp { - #[cfg_attr(docsrs, doc(cfg(feature = "native-activity")))] - #[cfg(feature = "native-activity")] - pub(crate) fn native_activity(&self) -> *const ndk_sys::ANativeActivity { - self.inner.read().unwrap().native_activity() - } - /// Queries the current [`NativeWindow`] for the application. /// /// This will only return `Some(window)` between diff --git a/android-activity/src/native_activity/glue.rs b/android-activity/src/native_activity/glue.rs new file mode 100644 index 0000000..9cb529f --- /dev/null +++ b/android-activity/src/native_activity/glue.rs @@ -0,0 +1,876 @@ +//! This 'glue' layer acts as an IPC shim between the JVM main thread and the Rust +//! main thread. Notifying Rust of lifecycle events from the JVM and handling +//! synchronization between the two threads. + +use std::{ + ffi::{CStr, CString}, + fs::File, + io::{BufRead, BufReader}, + ops::Deref, + os::unix::prelude::{FromRawFd, RawFd}, + ptr::{self, NonNull}, + sync::{Arc, Condvar, Mutex, Weak}, +}; + +use libc; + +use log::Level; +use ndk::{configuration::Configuration, input_queue::InputQueue, native_window::NativeWindow}; +use ndk_sys::ANativeActivity; + +use crate::ConfigurationRef; + +use super::{AndroidApp, Rect}; + +#[derive(Clone, Copy, Eq, PartialEq, Debug)] +pub enum AppCmd { + InputQueueChanged = 0, + InitWindow = 1, + TermWindow = 2, + WindowResized = 3, + WindowRedrawNeeded = 4, + ContentRectChanged = 5, + GainedFocus = 6, + LostFocus = 7, + ConfigChanged = 8, + LowMemory = 9, + Start = 10, + Resume = 11, + SaveState = 12, + Pause = 13, + Stop = 14, + Destroy = 15, +} +impl TryFrom for AppCmd { + type Error = (); + + fn try_from(value: i8) -> Result { + match value { + 0 => Ok(AppCmd::InputQueueChanged), + 1 => Ok(AppCmd::InitWindow), + 2 => Ok(AppCmd::TermWindow), + 3 => Ok(AppCmd::WindowResized), + 4 => Ok(AppCmd::WindowRedrawNeeded), + 5 => Ok(AppCmd::ContentRectChanged), + 6 => Ok(AppCmd::GainedFocus), + 7 => Ok(AppCmd::LostFocus), + 8 => Ok(AppCmd::ConfigChanged), + 9 => Ok(AppCmd::LowMemory), + 10 => Ok(AppCmd::Start), + 11 => Ok(AppCmd::Resume), + 12 => Ok(AppCmd::SaveState), + 13 => Ok(AppCmd::Pause), + 14 => Ok(AppCmd::Stop), + 15 => Ok(AppCmd::Destroy), + _ => Err(()), + } + } +} + +#[derive(Clone, Copy, Eq, PartialEq, Default, Debug)] +pub enum State { + #[default] + Init, + Start, + Resume, + Pause, + Stop, +} + +#[derive(Debug)] +pub struct WaitableNativeActivityState { + pub activity: *mut ndk_sys::ANativeActivity, + + pub mutex: Mutex, + pub cond: Condvar, +} + +#[derive(Debug, Clone)] +pub struct NativeActivityGlue { + pub inner: Arc, +} +unsafe impl Send for NativeActivityGlue {} +unsafe impl Sync for NativeActivityGlue {} + +impl Deref for NativeActivityGlue { + type Target = WaitableNativeActivityState; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +impl NativeActivityGlue { + pub fn new( + activity: *mut ANativeActivity, + saved_state: *const libc::c_void, + saved_state_size: libc::size_t, + ) -> Self { + let glue = Self { + inner: Arc::new(WaitableNativeActivityState::new( + activity, + saved_state, + saved_state_size, + )), + }; + + let weak_ref = Arc::downgrade(&glue.inner); + let weak_ptr = Weak::into_raw(weak_ref); + unsafe { + (*activity).instance = weak_ptr as *mut _; + + (*(*activity).callbacks).onDestroy = Some(on_destroy); + (*(*activity).callbacks).onStart = Some(on_start); + (*(*activity).callbacks).onResume = Some(on_resume); + (*(*activity).callbacks).onSaveInstanceState = Some(on_save_instance_state); + (*(*activity).callbacks).onPause = Some(on_pause); + (*(*activity).callbacks).onStop = Some(on_stop); + (*(*activity).callbacks).onConfigurationChanged = Some(on_configuration_changed); + (*(*activity).callbacks).onLowMemory = Some(on_low_memory); + (*(*activity).callbacks).onWindowFocusChanged = Some(on_window_focus_changed); + (*(*activity).callbacks).onNativeWindowCreated = Some(on_native_window_created); + (*(*activity).callbacks).onNativeWindowDestroyed = Some(on_native_window_destroyed); + (*(*activity).callbacks).onInputQueueCreated = Some(on_input_queue_created); + (*(*activity).callbacks).onInputQueueDestroyed = Some(on_input_queue_destroyed); + } + + glue + } + + /// Returns the file descriptor that needs to be polled by the Rust main thread + /// for events/commands from the JVM thread + pub fn cmd_read_fd(&self) -> libc::c_int { + self.mutex.lock().unwrap().msg_read + } + + /// For the Rust main thread to read a single pending command sent from the JVM main thread + pub fn read_cmd(&self) -> Option { + self.inner.mutex.lock().unwrap().read_cmd() + } + + /// For the Rust main thread to get an ndk::InputQueue that wraps the AInputQueue pointer + /// we have and at the same time ensure that the input queue is attached to the given looper. + /// + /// NB: it's expected that the input queue is detached as soon as we know there is new + /// input (knowing the app will be notified) and only re-attached when the application + /// reads the input (to avoid lots of redundant wake ups) + pub fn looper_attached_input_queue( + &self, + looper: *mut ndk_sys::ALooper, + ident: libc::c_int, + ) -> Option { + let mut guard = self.mutex.lock().unwrap(); + + if guard.input_queue == ptr::null_mut() { + return None; + } + + unsafe { + // Reattach the input queue to the looper so future input will again deliver an + // `InputAvailable` event. + guard.attach_input_queue_to_looper(looper, ident); + Some(InputQueue::from_ptr(NonNull::new_unchecked( + guard.input_queue, + ))) + } + } + + pub fn detach_input_queue_from_looper(&self) { + unsafe { + self.inner + .mutex + .lock() + .unwrap() + .detach_input_queue_from_looper(); + } + } + + pub fn config(&self) -> ConfigurationRef { + self.mutex.lock().unwrap().config.clone() + } + + pub fn content_rect(&self) -> Rect { + self.mutex.lock().unwrap().content_rect.into() + } +} + +#[derive(Debug)] +pub struct NativeActivityState { + pub msg_read: libc::c_int, + pub msg_write: libc::c_int, + pub config: super::ConfigurationRef, + pub saved_state: *mut libc::c_void, + pub saved_state_size: libc::size_t, + pub input_queue: *mut ndk_sys::AInputQueue, + pub window: Option, + pub content_rect: ndk_sys::ARect, + pub activity_state: State, + pub destroy_requested: bool, + pub running: bool, + pub state_saved: bool, + pub destroyed: bool, + pub redraw_needed: bool, + pub pending_input_queue: *mut ndk_sys::AInputQueue, + pub pending_window: Option, + pub pending_content_rect: ndk_sys::ARect, +} + +impl NativeActivityState { + pub fn read_cmd(&mut self) -> Option { + let mut cmd_i: i8 = 0; + loop { + match unsafe { libc::read(self.msg_read, &mut cmd_i as *mut _ as *mut _, 1) } { + 1 => { + let cmd = AppCmd::try_from(cmd_i); + return match cmd { + Ok(AppCmd::SaveState) => { + self.free_saved_state(); + Some(AppCmd::SaveState) + } + Ok(cmd) => Some(cmd), + Err(_) => { + log::error!("Spurious, unknown NativeActivityGlue cmd: {}", cmd_i); + None + } + }; + } + -1 => { + let err = std::io::Error::last_os_error(); + if err.kind() != std::io::ErrorKind::Interrupted { + log::error!("Failure reading NativeActivityGlue cmd: {}", err); + return None; + } + } + count => { + log::error!( + "Spurious read of {count} bytes while reading NativeActivityGlue cmd" + ); + return None; + } + } + } + } + + fn write_cmd(&mut self, cmd: AppCmd) { + let cmd = cmd as i8; + loop { + match unsafe { libc::write(self.msg_write, &cmd as *const _ as *const _, 1) } { + 1 => break, + -1 => { + let err = std::io::Error::last_os_error(); + if err.kind() != std::io::ErrorKind::Interrupted { + log::error!("Failure writing NativeActivityGlue cmd: {}", err); + return; + } + } + count => { + log::error!( + "Spurious write of {count} bytes while writing NativeActivityGlue cmd" + ); + return; + } + } + } + } + + fn free_saved_state(&mut self) { + if self.saved_state != ptr::null_mut() { + unsafe { libc::free(self.saved_state) }; + self.saved_state = ptr::null_mut(); + self.saved_state_size = 0; + } + } + + pub unsafe fn attach_input_queue_to_looper( + &mut self, + looper: *mut ndk_sys::ALooper, + ident: libc::c_int, + ) { + if self.input_queue != ptr::null_mut() { + log::trace!("Attaching input queue to looper"); + ndk_sys::AInputQueue_attachLooper( + self.input_queue, + looper, + ident, + None, + ptr::null_mut(), + ); + } + } + + pub unsafe fn detach_input_queue_from_looper(&mut self) { + if self.input_queue != ptr::null_mut() { + log::trace!("Detaching input queue from looper"); + ndk_sys::AInputQueue_detachLooper(self.input_queue); + } + } +} + +impl Drop for WaitableNativeActivityState { + fn drop(&mut self) { + log::debug!("WaitableNativeActivityState::drop!"); + unsafe { + let mut guard = self.mutex.lock().unwrap(); + guard.free_saved_state(); + guard.detach_input_queue_from_looper(); + guard.destroyed = true; + self.cond.notify_one(); + } + } +} + +impl WaitableNativeActivityState { + /////////////////////////////// + // Java-side callback handling + /////////////////////////////// + + pub fn new( + activity: *mut ndk_sys::ANativeActivity, + saved_state_in: *const libc::c_void, + saved_state_size: libc::size_t, + ) -> Self { + let mut msgpipe: [libc::c_int; 2] = [-1, -1]; + unsafe { + if libc::pipe(msgpipe.as_mut_ptr()) != 0 { + panic!( + "could not create Rust <-> Java IPC pipe: {}", + std::io::Error::last_os_error() + ); + } + } + + // NB: The implementation for `ANativeActivity` explicitly documents that save_state must + // be tracked via `malloc()` and `free()`, since `ANativeActivity` may need to free state + // that it is given via its `onSaveInstanceState` callback. + let mut saved_state = ptr::null_mut(); + unsafe { + if saved_state_in != ptr::null() && saved_state_size > 0 { + saved_state = libc::malloc(saved_state_size); + assert!( + saved_state != ptr::null_mut(), + "Failed to allocate {} bytes for restoring saved application state", + saved_state_size + ); + libc::memcpy(saved_state, saved_state_in, saved_state_size); + } + } + + let config = unsafe { + let config = ndk_sys::AConfiguration_new(); + ndk_sys::AConfiguration_fromAssetManager(config, (*activity).assetManager); + + let config = super::ConfigurationRef::new(Configuration::from_ptr( + NonNull::new_unchecked(config), + )); + log::debug!("Config: {:#?}", config); + config + }; + + Self { + activity, + mutex: Mutex::new(NativeActivityState { + msg_read: msgpipe[0], + msg_write: msgpipe[1], + config, + saved_state, + saved_state_size, + input_queue: ptr::null_mut(), + window: None, + content_rect: Rect::empty().into(), + activity_state: State::Init, + destroy_requested: false, + running: false, + state_saved: false, + destroyed: false, + redraw_needed: false, + pending_input_queue: ptr::null_mut(), + pending_window: None, + pending_content_rect: Rect::empty().into(), + }), + cond: Condvar::new(), + } + } + + pub fn notify_destroyed(&self) { + let mut guard = self.mutex.lock().unwrap(); + + unsafe { + guard.write_cmd(AppCmd::Destroy); + while !guard.destroyed { + guard = self.cond.wait(guard).unwrap(); + } + + libc::close(guard.msg_read); + guard.msg_read = -1; + libc::close(guard.msg_write); + guard.msg_write = -1; + } + } + + pub fn notify_config_changed(&self) { + let mut guard = self.mutex.lock().unwrap(); + guard.write_cmd(AppCmd::ConfigChanged); + } + + pub fn notify_low_memory(&self) { + let mut guard = self.mutex.lock().unwrap(); + guard.write_cmd(AppCmd::LowMemory); + } + + pub fn notify_focus_changed(&self, focused: bool) { + let mut guard = self.mutex.lock().unwrap(); + guard.write_cmd(if focused { + AppCmd::GainedFocus + } else { + AppCmd::LostFocus + }); + } + + unsafe fn set_input(&self, input_queue: *mut ndk_sys::AInputQueue) { + let mut guard = self.mutex.lock().unwrap(); + + // The pending_input_queue state should only be set while in this method, and since + // it doesn't allow re-entrance and is cleared before returning then we expect + // this to be null + debug_assert!( + guard.pending_input_queue.is_null(), + "InputQueue update clash" + ); + + guard.pending_input_queue = input_queue; + guard.write_cmd(AppCmd::InputQueueChanged); + while guard.input_queue != guard.pending_input_queue { + guard = self.cond.wait(guard).unwrap(); + } + guard.pending_input_queue = ptr::null_mut(); + } + + unsafe fn set_window(&self, window: Option) { + let mut guard = self.mutex.lock().unwrap(); + + // The pending_window state should only be set while in this method, and since + // it doesn't allow re-entrance and is cleared before returning then we expect + // this to be None + debug_assert!(guard.pending_window.is_none(), "NativeWindow update clash"); + + if guard.window.is_some() { + guard.write_cmd(AppCmd::TermWindow); + } + guard.pending_window = window; + if guard.pending_window.is_some() { + guard.write_cmd(AppCmd::InitWindow); + } + while guard.window != guard.pending_window { + guard = self.cond.wait(guard).unwrap(); + } + guard.pending_window = None; + } + + unsafe fn set_activity_state(&self, state: State) { + let mut guard = self.mutex.lock().unwrap(); + + let cmd = match state { + State::Init => panic!("Can't explicitly transition into 'init' state"), + State::Start => AppCmd::Start, + State::Resume => AppCmd::Resume, + State::Pause => AppCmd::Pause, + State::Stop => AppCmd::Stop, + }; + guard.write_cmd(cmd); + + while guard.activity_state != state { + guard = self.cond.wait(guard).unwrap(); + } + } + + unsafe fn request_save_state(&self) -> (*mut libc::c_void, libc::size_t) { + let mut guard = self.mutex.lock().unwrap(); + + guard.state_saved = false; + guard.write_cmd(AppCmd::SaveState); + while guard.state_saved == false { + guard = self.cond.wait(guard).unwrap(); + } + + let saved_state = std::mem::replace(&mut guard.saved_state, ptr::null_mut()); + let saved_state_size = std::mem::take(&mut guard.saved_state_size); + if saved_state != ptr::null_mut() && saved_state_size > 0 { + (saved_state, saved_state_size) + } else { + (ptr::null_mut(), 0) + } + } + + pub fn saved_state(&self) -> Option> { + let guard = self.mutex.lock().unwrap(); + + unsafe { + if guard.saved_state != ptr::null_mut() && guard.saved_state_size > 0 { + let buf: &mut [u8] = std::slice::from_raw_parts_mut( + guard.saved_state.cast(), + guard.saved_state_size as usize, + ); + let state = buf.to_vec(); + Some(state) + } else { + None + } + } + } + + pub fn set_saved_state(&self, state: &[u8]) { + let mut guard = self.mutex.lock().unwrap(); + + // ANativeActivity specifically expects the state to have been allocated + // via libc::malloc since it will automatically handle freeing the data. + + unsafe { + // In case the application calls store() multiple times for some reason we + // make sure to free any pre-existing state... + if guard.saved_state != ptr::null_mut() { + libc::free(guard.saved_state); + guard.saved_state = ptr::null_mut(); + guard.saved_state_size = 0; + } + + let buf = libc::malloc(state.len()); + if buf == ptr::null_mut() { + panic!("Failed to allocate save_state buffer"); + } + + // Since it's a byte array there's no special alignment requirement here. + // + // Since we re-define `buf` we ensure it's not possible to access the buffer + // via its original pointer for the lifetime of the slice. + { + let buf: &mut [u8] = std::slice::from_raw_parts_mut(buf.cast(), state.len()); + buf.copy_from_slice(state); + } + + guard.saved_state = buf; + guard.saved_state_size = state.len() as _; + } + } + + //////////////////////////// + // Rust-side event loop + //////////////////////////// + + pub fn notify_main_thread_running(&self) { + let mut guard = self.mutex.lock().unwrap(); + guard.running = true; + self.cond.notify_one(); + } + + pub unsafe fn pre_exec_cmd( + &self, + cmd: AppCmd, + looper: *mut ndk_sys::ALooper, + input_queue_ident: libc::c_int, + ) { + log::trace!("Pre: AppCmd::{:#?}", cmd); + match cmd { + AppCmd::InputQueueChanged => { + let mut guard = self.mutex.lock().unwrap(); + guard.detach_input_queue_from_looper(); + guard.input_queue = guard.pending_input_queue; + if guard.input_queue != ptr::null_mut() { + guard.attach_input_queue_to_looper(looper, input_queue_ident); + } + self.cond.notify_one(); + } + AppCmd::InitWindow => { + let mut guard = self.mutex.lock().unwrap(); + guard.window = guard.pending_window.clone(); + self.cond.notify_one(); + } + AppCmd::Resume | AppCmd::Start | AppCmd::Pause | AppCmd::Stop => { + let mut guard = self.mutex.lock().unwrap(); + guard.activity_state = match cmd { + AppCmd::Start => State::Start, + AppCmd::Pause => State::Pause, + AppCmd::Resume => State::Resume, + AppCmd::Stop => State::Stop, + _ => unreachable!(), + }; + self.cond.notify_one(); + } + AppCmd::ConfigChanged => { + let guard = self.mutex.lock().unwrap(); + let config = ndk_sys::AConfiguration_new(); + ndk_sys::AConfiguration_fromAssetManager(config, (*self.activity).assetManager); + let config = Configuration::from_ptr(NonNull::new_unchecked(config)); + guard.config.replace(config); + log::debug!("Config: {:#?}", guard.config); + } + AppCmd::Destroy => { + let mut guard = self.mutex.lock().unwrap(); + guard.destroy_requested = true; + } + _ => {} + } + } + + pub unsafe fn post_exec_cmd(&self, cmd: AppCmd) { + log::trace!("Post: AppCmd::{:#?}", cmd); + match cmd { + AppCmd::TermWindow => { + let mut guard = self.mutex.lock().unwrap(); + guard.window = None; + self.cond.notify_one(); + } + AppCmd::SaveState => { + let mut guard = self.mutex.lock().unwrap(); + guard.state_saved = true; + self.cond.notify_one(); + } + AppCmd::Resume => { + let mut guard = self.mutex.lock().unwrap(); + guard.free_saved_state(); + } + _ => {} + } + } +} + +extern "Rust" { + pub fn android_main(app: AndroidApp); +} + +fn android_log(level: Level, tag: &CStr, msg: &CStr) { + let prio = match level { + Level::Error => ndk_sys::android_LogPriority::ANDROID_LOG_ERROR, + Level::Warn => ndk_sys::android_LogPriority::ANDROID_LOG_WARN, + Level::Info => ndk_sys::android_LogPriority::ANDROID_LOG_INFO, + Level::Debug => ndk_sys::android_LogPriority::ANDROID_LOG_DEBUG, + Level::Trace => ndk_sys::android_LogPriority::ANDROID_LOG_VERBOSE, + }; + unsafe { + ndk_sys::__android_log_write(prio.0 as libc::c_int, tag.as_ptr(), msg.as_ptr()); + } +} + +unsafe extern "C" fn on_destroy(activity: *mut ndk_sys::ANativeActivity) { + log::debug!("Destroy: {:p}\n", activity); + let weak_ptr: *const WaitableNativeActivityState = (*activity).instance.cast(); + if let Some(waitable_activity) = Weak::from_raw(weak_ptr).upgrade() { + waitable_activity.notify_destroyed() + } +} + +unsafe extern "C" fn on_start(activity: *mut ndk_sys::ANativeActivity) { + log::debug!("Start: {:p}\n", activity); + let weak_ptr: *const WaitableNativeActivityState = (*activity).instance.cast(); + if let Some(waitable_activity) = Weak::from_raw(weak_ptr).upgrade() { + waitable_activity.set_activity_state(State::Start); + } +} + +unsafe extern "C" fn on_resume(activity: *mut ndk_sys::ANativeActivity) { + log::debug!("Resume: {:p}\n", activity); + let weak_ptr: *const WaitableNativeActivityState = (*activity).instance.cast(); + if let Some(waitable_activity) = Weak::from_raw(weak_ptr).upgrade() { + waitable_activity.set_activity_state(State::Resume); + } +} + +unsafe extern "C" fn on_save_instance_state( + activity: *mut ndk_sys::ANativeActivity, + out_len: *mut ndk_sys::size_t, +) -> *mut libc::c_void { + log::debug!("SaveInstanceState: {:p}\n", activity); + let weak_ptr: *const WaitableNativeActivityState = (*activity).instance.cast(); + if let Some(waitable_activity) = Weak::from_raw(weak_ptr).upgrade() { + let (state, len) = waitable_activity.request_save_state(); + *out_len = len as ndk_sys::size_t; + state + } else { + *out_len = 0; + ptr::null_mut() + } +} + +unsafe extern "C" fn on_pause(activity: *mut ndk_sys::ANativeActivity) { + log::debug!("Pause: {:p}\n", activity); + let weak_ptr: *const WaitableNativeActivityState = (*activity).instance.cast(); + if let Some(waitable_activity) = Weak::from_raw(weak_ptr).upgrade() { + waitable_activity.set_activity_state(State::Pause); + } +} + +unsafe extern "C" fn on_stop(activity: *mut ndk_sys::ANativeActivity) { + log::debug!("Stop: {:p}\n", activity); + let weak_ptr: *const WaitableNativeActivityState = (*activity).instance.cast(); + if let Some(waitable_activity) = Weak::from_raw(weak_ptr).upgrade() { + waitable_activity.set_activity_state(State::Stop); + } +} + +unsafe extern "C" fn on_configuration_changed(activity: *mut ndk_sys::ANativeActivity) { + log::debug!("ConfigurationChanged: {:p}\n", activity); + let weak_ptr: *const WaitableNativeActivityState = (*activity).instance.cast(); + if let Some(waitable_activity) = Weak::from_raw(weak_ptr).upgrade() { + waitable_activity.notify_config_changed(); + } +} + +unsafe extern "C" fn on_low_memory(activity: *mut ndk_sys::ANativeActivity) { + log::debug!("LowMemory: {:p}\n", activity); + let weak_ptr: *const WaitableNativeActivityState = (*activity).instance.cast(); + if let Some(waitable_activity) = Weak::from_raw(weak_ptr).upgrade() { + waitable_activity.notify_low_memory(); + } +} + +unsafe extern "C" fn on_window_focus_changed( + activity: *mut ndk_sys::ANativeActivity, + focused: libc::c_int, +) { + log::debug!("WindowFocusChanged: {:p} -- {}\n", activity, focused); + let weak_ptr: *const WaitableNativeActivityState = (*activity).instance.cast(); + if let Some(waitable_activity) = Weak::from_raw(weak_ptr).upgrade() { + waitable_activity.notify_focus_changed(focused != 0); + } +} + +unsafe extern "C" fn on_native_window_created( + activity: *mut ndk_sys::ANativeActivity, + window: *mut ndk_sys::ANativeWindow, +) { + log::debug!("NativeWindowCreated: {:p} -- {:p}\n", activity, window); + let weak_ptr: *const WaitableNativeActivityState = (*activity).instance.cast(); + if let Some(waitable_activity) = Weak::from_raw(weak_ptr).upgrade() { + // It's important that we use ::clone_from_ptr() here because NativeWindow + // has a Drop implementation that will unconditionally _release() the native window + let window = NativeWindow::clone_from_ptr(NonNull::new_unchecked(window)); + waitable_activity.set_window(Some(window)); + } +} + +unsafe extern "C" fn on_native_window_destroyed( + activity: *mut ndk_sys::ANativeActivity, + window: *mut ndk_sys::ANativeWindow, +) { + log::debug!("NativeWindowDestroyed: {:p} -- {:p}\n", activity, window); + let weak_ptr: *const WaitableNativeActivityState = (*activity).instance.cast(); + if let Some(waitable_activity) = Weak::from_raw(weak_ptr).upgrade() { + waitable_activity.set_window(None); + } +} + +unsafe extern "C" fn on_input_queue_created( + activity: *mut ndk_sys::ANativeActivity, + queue: *mut ndk_sys::AInputQueue, +) { + log::debug!("InputQueueCreated: {:p} -- {:p}\n", activity, queue); + let weak_ptr: *const WaitableNativeActivityState = (*activity).instance.cast(); + if let Some(waitable_activity) = Weak::from_raw(weak_ptr).upgrade() { + waitable_activity.set_input(queue); + } +} + +unsafe extern "C" fn on_input_queue_destroyed( + activity: *mut ndk_sys::ANativeActivity, + queue: *mut ndk_sys::AInputQueue, +) { + log::debug!("InputQueueDestroyed: {:p} -- {:p}\n", activity, queue); + let weak_ptr: *const WaitableNativeActivityState = (*activity).instance.cast(); + if let Some(waitable_activity) = Weak::from_raw(weak_ptr).upgrade() { + waitable_activity.set_input(ptr::null_mut()); + } +} + +/// This is the native entrypoint for our cdylib library that `ANativeActivity` will look for via `dlsym` +#[no_mangle] +extern "C" fn ANativeActivity_onCreate( + activity: *mut ndk_sys::ANativeActivity, + saved_state: *const libc::c_void, + saved_state_size: libc::size_t, +) { + log::debug!("Creating: {:p}", activity); + + // Maybe make this stdout/stderr redirection an optional / opt-in feature?... + unsafe { + let mut logpipe: [RawFd; 2] = Default::default(); + libc::pipe(logpipe.as_mut_ptr()); + libc::dup2(logpipe[1], libc::STDOUT_FILENO); + libc::dup2(logpipe[1], libc::STDERR_FILENO); + std::thread::spawn(move || { + let tag = CStr::from_bytes_with_nul(b"RustStdoutStderr\0").unwrap(); + let file = File::from_raw_fd(logpipe[0]); + let mut reader = BufReader::new(file); + let mut buffer = String::new(); + loop { + buffer.clear(); + if let Ok(len) = reader.read_line(&mut buffer) { + if len == 0 { + break; + } else if let Ok(msg) = CString::new(buffer.clone()) { + android_log(Level::Info, tag, &msg); + } + } + } + }); + } + + // Conceptually we associate a glue reference with the JVM main thread, and another + // reference with the Rust main thread + let jvm_glue = NativeActivityGlue::new(activity, saved_state, saved_state_size); + + let rust_glue = jvm_glue.clone(); + // Let us Send the NativeActivity pointer to the Rust main() thread without a wrapper type + let activity_ptr: libc::intptr_t = activity as _; + + // Note: we drop the thread handle which will detach the thread + std::thread::spawn(move || { + let activity: *mut ANativeActivity = activity_ptr as *mut _; + + let jvm = unsafe { + let na = activity; + let jvm = (*na).vm; + let activity = (*na).clazz; // Completely bogus name; this is the _instance_ not class pointer + ndk_context::initialize_android_context(jvm.cast(), activity.cast()); + + // Since this is a newly spawned thread then the JVM hasn't been attached + // to the thread yet. Attach before calling the applications main function + // so they can safely make JNI calls + let mut jenv_out: *mut core::ffi::c_void = std::ptr::null_mut(); + if let Some(attach_current_thread) = (*(*jvm)).AttachCurrentThread { + attach_current_thread(jvm, &mut jenv_out, std::ptr::null_mut()); + } + + jvm + }; + + let app = AndroidApp::new(rust_glue.clone()); + + rust_glue.notify_main_thread_running(); + + unsafe { + // XXX: If we were in control of the Java Activity subclass then + // we could potentially run the android_main function via a Java native method + // springboard (e.g. call an Activity subclass method that calls a jni native + // method that then just calls android_main()) that would make sure there was + // a Java frame at the base of our call stack which would then be recognised + // when calling FindClass to lookup a suitable classLoader, instead of + // defaulting to the system loader. Without this then it's difficult for native + // code to look up non-standard Java classes. + android_main(app); + + // Since this is a newly spawned thread then the JVM hasn't been attached + // to the thread yet. Attach before calling the applications main function + // so they can safely make JNI calls + if let Some(detach_current_thread) = (*(*jvm)).DetachCurrentThread { + detach_current_thread(jvm); + } + + ndk_context::release_android_context(); + } + }); + + // Wait for thread to start. + let mut guard = jvm_glue.mutex.lock().unwrap(); + while !guard.running { + guard = jvm_glue.cond.wait(guard).unwrap(); + } +} diff --git a/android-activity/src/native_activity/mod.rs b/android-activity/src/native_activity/mod.rs index 3753362..0712d68 100644 --- a/android-activity/src/native_activity/mod.rs +++ b/android-activity/src/native_activity/mod.rs @@ -1,30 +1,24 @@ #![cfg(any(feature = "native-activity", doc))] -use std::ffi::{CStr, CString}; -use std::fs::File; -use std::io::{BufRead, BufReader}; -use std::ops::Deref; -use std::os::raw; -use std::os::unix::prelude::*; +use std::ptr; use std::ptr::NonNull; use std::sync::{Arc, RwLock}; use std::time::Duration; -use std::{ptr, thread}; -use log::{error, info, trace, Level}; +use log::{error, info, trace}; use ndk_sys::ALooper_wake; use ndk_sys::{ALooper, ALooper_pollAll}; use ndk::asset::AssetManager; -use ndk::configuration::Configuration; -use ndk::input_queue::InputQueue; use ndk::native_window::NativeWindow; use crate::{ util, AndroidApp, ConfigurationRef, InputStatus, MainEvent, PollEvent, Rect, WindowManagerFlags, }; +use self::glue::NativeActivityGlue; + pub mod input { pub use ndk::event::{ Axis, ButtonState, EdgeFlags, KeyAction, KeyEvent, KeyEventFlags, Keycode, MetaState, @@ -42,9 +36,15 @@ pub mod input { } } -// The only time it's safe to update the android_app->savedState pointer is +mod glue; + +pub const LOOPER_ID_MAIN: libc::c_int = 1; +pub const LOOPER_ID_INPUT: libc::c_int = 2; +//pub const LOOPER_ID_USER: ::std::os::raw::c_uint = 3; + +// The only time it's safe to update the saved_state pointer is // while handling a SaveState event, so this API is only exposed for those -// events... +// events #[derive(Debug)] pub struct StateSaver<'a> { app: &'a AndroidAppInner, @@ -52,37 +52,7 @@ pub struct StateSaver<'a> { impl<'a> StateSaver<'a> { pub fn store(&self, state: &'a [u8]) { - // android_native_app_glue specifically expects savedState to have been allocated - // via libc::malloc since it will automatically handle freeing the data once it - // has been handed over to the Java Activity / main thread. - unsafe { - let app_ptr = self.app.native_app.as_ptr(); - - // In case the application calls store() multiple times for some reason we - // make sure to free any pre-existing state... - if (*app_ptr).saved_state != ptr::null_mut() { - libc::free((*app_ptr).saved_state); - (*app_ptr).saved_state = ptr::null_mut(); - (*app_ptr).saved_state_size = 0; - } - - let buf = libc::malloc(state.len()); - if buf == ptr::null_mut() { - panic!("Failed to allocate save_state buffer"); - } - - // Since it's a byte array there's no special alignment requirement here. - // - // Since we re-define `buf` we ensure it's not possible to access the buffer - // via its original pointer for the lifetime of the slice. - { - let buf: &mut [u8] = std::slice::from_raw_parts_mut(buf.cast(), state.len()); - buf.copy_from_slice(state); - } - - (*app_ptr).saved_state = buf; - (*app_ptr).saved_state_size = state.len() as _; - } + self.app.native_activity.set_saved_state(state); } } @@ -92,19 +62,7 @@ pub struct StateLoader<'a> { } impl<'a> StateLoader<'a> { pub fn load(&self) -> Option> { - unsafe { - let app_ptr = self.app.native_app.as_ptr(); - if (*app_ptr).saved_state != ptr::null_mut() && (*app_ptr).saved_state_size > 0 { - let buf: &mut [u8] = std::slice::from_raw_parts_mut( - (*app_ptr).saved_state.cast(), - (*app_ptr).saved_state_size as usize, - ); - let state = buf.to_vec(); - Some(state) - } else { - None - } - } + self.app.native_activity.saved_state() } } @@ -126,144 +84,65 @@ impl AndroidAppWaker { } } -/// These are the original C structs / constants from android_native_app_glue.c naively -/// ported to Rust. -/// -/// TODO: start integrating all this state directly into `AndroidApp`/`NativeAppGlue` -mod ffi { - pub const LOOPER_ID_MAIN: libc::c_uint = 1; - pub const LOOPER_ID_INPUT: libc::c_uint = 2; - //pub const LOOPER_ID_USER: ::std::os::raw::c_uint = 3; +impl AndroidApp { + pub(crate) fn new(native_activity: NativeActivityGlue) -> Self { + let app = Self { + inner: Arc::new(RwLock::new(AndroidAppInner { + native_activity, + looper: Looper { + ptr: ptr::null_mut(), + }, + })), + }; - #[derive(Clone, Copy, Eq, PartialEq, Debug)] - pub enum AppCmd { - InputChanged = 0, - InitWindow = 1, - TermWindow = 2, - WindowResized = 3, - WindowRedrawNeeded = 4, - ContentRectChanged = 5, - GainedFocus = 6, - LostFocus = 7, - ConfigChanged = 8, - LowMemory = 9, - Start = 10, - Resume = 11, - SaveState = 12, - Pause = 13, - Stop = 14, - Destroy = 15, - } - impl TryFrom for AppCmd { - type Error = (); + { + let mut guard = app.inner.write().unwrap(); - fn try_from(value: i8) -> Result { - match value { - 0 => Ok(AppCmd::InputChanged), - 1 => Ok(AppCmd::InitWindow), - 2 => Ok(AppCmd::TermWindow), - 3 => Ok(AppCmd::WindowResized), - 4 => Ok(AppCmd::WindowRedrawNeeded), - 5 => Ok(AppCmd::ContentRectChanged), - 6 => Ok(AppCmd::GainedFocus), - 7 => Ok(AppCmd::LostFocus), - 8 => Ok(AppCmd::ConfigChanged), - 9 => Ok(AppCmd::LowMemory), - 10 => Ok(AppCmd::Start), - 11 => Ok(AppCmd::Resume), - 12 => Ok(AppCmd::SaveState), - 13 => Ok(AppCmd::Pause), - 14 => Ok(AppCmd::Stop), - 15 => Ok(AppCmd::Destroy), - _ => Err(()) + let main_fd = guard.native_activity.cmd_read_fd(); + unsafe { + guard.looper.ptr = ndk_sys::ALooper_prepare( + ndk_sys::ALOOPER_PREPARE_ALLOW_NON_CALLBACKS as libc::c_int, + ); + ndk_sys::ALooper_addFd( + guard.looper.ptr, + main_fd, + LOOPER_ID_MAIN, + ndk_sys::ALOOPER_EVENT_INPUT as libc::c_int, + None, + //&mut guard.cmd_poll_source as *mut _ as *mut _); + ptr::null_mut(), + ); } } - } - pub struct NativeActivityPollSource { - pub id: i32, - pub app: *mut NativeActivityGlue, - pub process: ::std::option::Option< - unsafe extern "C" fn(app: *mut NativeActivityGlue, source: *mut NativeActivityPollSource), - >, - } - - pub struct NativeActivityGlue { - pub activity: *mut ndk_sys::ANativeActivity, - pub config: *mut ndk_sys::AConfiguration, - pub saved_state: *mut libc::c_void, - pub saved_state_size: libc::size_t, - pub looper: *mut ndk_sys::ALooper, - pub input_queue: *mut ndk_sys::AInputQueue, - pub window: *mut ndk_sys::ANativeWindow, - pub content_rect: ndk_sys::ARect, - pub activity_state: libc::c_int, - pub destroy_requested: bool, - pub mutex: libc::pthread_mutex_t, - pub cond: libc::pthread_cond_t, - pub msg_read: libc::c_int, - pub msg_write: libc::c_int, - pub thread: libc::pthread_t, - pub cmd_poll_source: NativeActivityPollSource, - pub input_poll_source: NativeActivityPollSource, - pub running: bool, - pub state_saved: bool, - pub destroyed: bool, - pub redraw_needed: bool, - pub pending_input_queue: *mut ndk_sys::AInputQueue, - pub pending_window: *mut ndk_sys::ANativeWindow, - pub pending_content_rect: ndk_sys::ARect, - } -} - -impl AndroidApp { - pub(crate) unsafe fn from_ptr(ptr: NonNull) -> AndroidApp { - // Note: we don't use from_ptr since we don't own the android_app.config - // and need to keep in mind that the Drop handler is going to call - // AConfiguration_delete() - let config = Configuration::clone_from_ptr(NonNull::new_unchecked((*ptr.as_ptr()).config)); - - AndroidApp { - inner: Arc::new(RwLock::new(AndroidAppInner { - native_app: NativeAppGlue { ptr }, - config: ConfigurationRef::new(config), - native_window: Default::default(), - })), - } + app } } #[derive(Debug)] -struct NativeAppGlue { - ptr: NonNull, +struct Looper { + pub ptr: *mut ALooper, } -impl Deref for NativeAppGlue { - type Target = NonNull; - - fn deref(&self) -> &Self::Target { - &self.ptr - } -} -unsafe impl Send for NativeAppGlue {} -unsafe impl Sync for NativeAppGlue {} +unsafe impl Send for Looper {} +unsafe impl Sync for Looper {} #[derive(Debug)] pub(crate) struct AndroidAppInner { - native_app: NativeAppGlue, - config: ConfigurationRef, - native_window: RwLock>, + pub(crate) native_activity: NativeActivityGlue, + looper: Looper, } impl AndroidAppInner { pub(crate) fn native_activity(&self) -> *const ndk_sys::ANativeActivity { - unsafe { - let app_ptr = self.native_app.as_ptr(); - (*app_ptr).activity.cast() - } + self.native_activity.activity + } + + pub(crate) fn looper(&self) -> *mut ndk_sys::ALooper { + self.looper.ptr } pub fn native_window<'a>(&self) -> Option { - self.native_window.read().unwrap().clone() + self.native_activity.mutex.lock().unwrap().window.clone() } pub fn poll_events(&self, timeout: Option, mut callback: F) @@ -273,8 +152,6 @@ impl AndroidAppInner { trace!("poll_events"); unsafe { - let native_app = &self.native_app; - let mut fd: i32 = 0; let mut events: i32 = 0; let mut source: *mut core::ffi::c_void = ptr::null_mut(); @@ -284,7 +161,12 @@ impl AndroidAppInner { } else { -1 }; + info!("Calling ALooper_pollAll, timeout = {timeout_milliseconds}"); + assert!( + ndk_sys::ALooper_forThread() != ptr::null_mut(), + "Application tried to poll events from non-main thread" + ); let id = ALooper_pollAll( timeout_milliseconds, &mut fd, @@ -312,95 +194,69 @@ impl AndroidAppInner { panic!("ALooper_pollAll returned POLL_ERROR"); } id if id >= 0 => { - match id as u32 { - ffi::LOOPER_ID_MAIN => { + match id { + LOOPER_ID_MAIN => { trace!("ALooper_pollAll returned ID_MAIN"); - let source: *mut ffi::NativeActivityPollSource = source.cast(); - if source != ptr::null_mut() { - if let Some(ipc_cmd) = android_app_read_cmd(native_app.as_ptr()) { - let main_cmd = match ipc_cmd { - // We don't forward info about the AInputQueue to apps since it's - // an implementation details that's also not compatible with - // GameActivity - ffi::AppCmd::InputChanged => None, + if let Some(ipc_cmd) = self.native_activity.read_cmd() { + let main_cmd = match ipc_cmd { + // We don't forward info about the AInputQueue to apps since it's + // an implementation details that's also not compatible with + // GameActivity + glue::AppCmd::InputQueueChanged => None, - ffi::AppCmd::InitWindow => Some(MainEvent::InitWindow {}), - ffi::AppCmd::TermWindow => Some(MainEvent::TerminateWindow {}), - ffi::AppCmd::WindowResized => { - Some(MainEvent::WindowResized {}) - } - ffi::AppCmd::WindowRedrawNeeded => { - Some(MainEvent::RedrawNeeded {}) - } - ffi::AppCmd::ContentRectChanged => { - Some(MainEvent::ContentRectChanged {}) - } - ffi::AppCmd::GainedFocus => Some(MainEvent::GainedFocus), - ffi::AppCmd::LostFocus => Some(MainEvent::LostFocus), - ffi::AppCmd::ConfigChanged => { - Some(MainEvent::ConfigChanged {}) - } - ffi::AppCmd::LowMemory => Some(MainEvent::LowMemory), - ffi::AppCmd::Start => Some(MainEvent::Start), - ffi::AppCmd::Resume => Some(MainEvent::Resume { - loader: StateLoader { app: &self }, - }), - ffi::AppCmd::SaveState => Some(MainEvent::SaveState { - saver: StateSaver { app: &self }, - }), - ffi::AppCmd::Pause => Some(MainEvent::Pause), - ffi::AppCmd::Stop => Some(MainEvent::Stop), - ffi::AppCmd::Destroy => Some(MainEvent::Destroy), - }; - - trace!("Calling android_app_pre_exec_cmd({ipc_cmd:#?})"); - android_app_pre_exec_cmd(native_app.as_ptr(), ipc_cmd); - - if let Some(main_cmd) = main_cmd { - trace!("Read ID_MAIN command {ipc_cmd:#?} = {main_cmd:#?}"); - match main_cmd { - MainEvent::ConfigChanged { .. } => { - self.config.replace(Configuration::clone_from_ptr( - NonNull::new_unchecked( - (*native_app.as_ptr()).config, - ), - )); - } - MainEvent::InitWindow { .. } => { - let win_ptr = (*native_app.as_ptr()).window; - // It's important that we use ::clone_from_ptr() here - // because NativeWindow has a Drop implementation that - // will unconditionally _release() the native window - *self.native_window.write().unwrap() = - Some(NativeWindow::clone_from_ptr( - NonNull::new(win_ptr).unwrap(), - )); - } - MainEvent::TerminateWindow { .. } => { - *self.native_window.write().unwrap() = None; - } - _ => {} - } - - trace!("Invoking callback for ID_MAIN command = {main_cmd:?}"); - callback(PollEvent::Main(main_cmd)); + glue::AppCmd::InitWindow => Some(MainEvent::InitWindow {}), + glue::AppCmd::TermWindow => Some(MainEvent::TerminateWindow {}), + glue::AppCmd::WindowResized => { + Some(MainEvent::WindowResized {}) } + glue::AppCmd::WindowRedrawNeeded => { + Some(MainEvent::RedrawNeeded {}) + } + glue::AppCmd::ContentRectChanged => { + Some(MainEvent::ContentRectChanged {}) + } + glue::AppCmd::GainedFocus => Some(MainEvent::GainedFocus), + glue::AppCmd::LostFocus => Some(MainEvent::LostFocus), + glue::AppCmd::ConfigChanged => { + Some(MainEvent::ConfigChanged {}) + } + glue::AppCmd::LowMemory => Some(MainEvent::LowMemory), + glue::AppCmd::Start => Some(MainEvent::Start), + glue::AppCmd::Resume => Some(MainEvent::Resume { + loader: StateLoader { app: &self }, + }), + glue::AppCmd::SaveState => Some(MainEvent::SaveState { + saver: StateSaver { app: &self }, + }), + glue::AppCmd::Pause => Some(MainEvent::Pause), + glue::AppCmd::Stop => Some(MainEvent::Stop), + glue::AppCmd::Destroy => Some(MainEvent::Destroy), + }; - trace!("Calling android_app_post_exec_cmd({ipc_cmd:#?})"); - android_app_post_exec_cmd(native_app.as_ptr(), ipc_cmd); + trace!("Calling pre_exec_cmd({ipc_cmd:#?})"); + self.native_activity.pre_exec_cmd( + ipc_cmd, + self.looper(), + LOOPER_ID_INPUT, + ); + + if let Some(main_cmd) = main_cmd { + trace!("Invoking callback for ID_MAIN command = {main_cmd:?}"); + callback(PollEvent::Main(main_cmd)); } - } else { - panic!("ALooper_pollAll returned ID_MAIN event with NULL android_poll_source!"); + + trace!("Calling post_exec_cmd({ipc_cmd:#?})"); + self.native_activity.post_exec_cmd(ipc_cmd); } } - ffi::LOOPER_ID_INPUT => { + LOOPER_ID_INPUT => { trace!("ALooper_pollAll returned ID_INPUT"); // To avoid spamming the application with event loop iterations notifying them of // input events then we only send one `InputAvailable` per iteration of input // handling. We re-attach the looper when the application calls // `AndroidApp::input_events()` - android_app_detach_input_queue_looper(native_app.as_ptr()); + self.native_activity.detach_input_queue_from_looper(); callback(PollEvent::Main(MainEvent::InputAvailable)) } _ => { @@ -417,35 +273,26 @@ impl AndroidAppInner { pub fn create_waker(&self) -> AndroidAppWaker { unsafe { - // From the application's pov we assume the app_ptr and looper pointer - // have static lifetimes and we can safely assume they are never NULL. - let app_ptr = self.native_app.as_ptr(); + // From the application's pov we assume the looper pointer has a static + // lifetimes and we can safely assume it is never NULL. AndroidAppWaker { - looper: NonNull::new_unchecked((*app_ptr).looper), + looper: NonNull::new_unchecked(self.looper.ptr), } } } pub fn config(&self) -> ConfigurationRef { - self.config.clone() + self.native_activity.config() } pub fn content_rect(&self) -> Rect { - unsafe { - let app_ptr = self.native_app.as_ptr(); - Rect { - left: (*app_ptr).content_rect.left, - right: (*app_ptr).content_rect.right, - top: (*app_ptr).content_rect.top, - bottom: (*app_ptr).content_rect.bottom, - } - } + self.native_activity.content_rect() } pub fn asset_manager(&self) -> AssetManager { unsafe { - let app_ptr = self.native_app.as_ptr(); - let am_ptr = NonNull::new_unchecked((*(*app_ptr).activity).assetManager); + let activity_ptr = self.native_activity.activity; + let am_ptr = NonNull::new_unchecked((*activity_ptr).assetManager); AssetManager::from_ptr(am_ptr) } } @@ -504,18 +351,15 @@ impl AndroidAppInner { where F: FnMut(&input::InputEvent) -> InputStatus, { - let queue = unsafe { - let app_ptr = self.native_app.as_ptr(); - if (*app_ptr).input_queue == ptr::null_mut() { - return; - } - - // Reattach the input queue to the looper so future input will again deliver an - // `InputAvailable` event. - android_app_attach_input_queue_looper(app_ptr); - - let queue = NonNull::new_unchecked((*app_ptr).input_queue); - InputQueue::from_ptr(queue) + // Get the InputQueue for the NativeActivity (if there is one) and also ensure + // the queue is re-attached to our event Looper (so new input events will again + // trigger a wake up) + let queue = self + .native_activity + .looper_attached_input_queue(self.looper(), LOOPER_ID_INPUT); + let queue = match queue { + Some(queue) => queue, + None => return, }; // Note: we basically ignore errors from get_event() currently. Looking @@ -563,558 +407,3 @@ impl AndroidAppInner { unsafe { util::try_get_path_from_ptr((*na).obbPath) } } } - - -//////////////////////////// -// Rust-side event loop -//////////////////////////// - -unsafe fn free_saved_state(android_app: *mut ffi::NativeActivityGlue) { - libc::pthread_mutex_lock(&mut (*android_app).mutex as *mut _); - if (*android_app).saved_state != ptr::null_mut() { - libc::free((*android_app).saved_state); - (*android_app).saved_state = ptr::null_mut(); - (*android_app).saved_state_size = 0; - } - libc::pthread_mutex_unlock(&mut (*android_app).mutex as *mut _); -} - -unsafe fn android_app_read_cmd(android_app: *mut ffi::NativeActivityGlue) -> Option { - let mut cmd_i: i8 = 0; - loop { - match libc::read((*android_app).msg_read, &mut cmd_i as *mut _ as *mut _, 1) { - 1 => { - let cmd = ffi::AppCmd::try_from(cmd_i); - return match cmd { - Ok(ffi::AppCmd::SaveState) => { - free_saved_state(android_app); - Some(ffi::AppCmd::SaveState) - } - Ok(cmd) => Some(cmd), - Err(_) => { - log::error!("Spurious, unknown NativeActivityGlue cmd: {}", cmd_i); - None - } - }; - } - -1 => { - let err = std::io::Error::last_os_error(); - if err.kind() != std::io::ErrorKind::Interrupted { - log::error!("Failure reading NativeActivityGlue cmd: {}", err); - return None; - } - } - count => { - log::error!("Spurious read of {count} bytes while reading NativeActivityGlue cmd"); - return None; - } - } - } -} - -unsafe fn print_cur_config(android_app: *mut ffi::NativeActivityGlue) { - let mut lang = [0u8; 2]; - ndk_sys::AConfiguration_getLanguage((*android_app).config, lang[..].as_mut_ptr()); - let lang = if lang[0] == 0 { - " ".to_owned() - } else { - std::str::from_utf8(&lang[..]).unwrap().to_owned() - }; - let mut country = " ".to_owned(); - ndk_sys::AConfiguration_getCountry((*android_app).config, country.as_mut_ptr() as *mut _); - - ndk_sys::AConfiguration_getCountry((*android_app).config, country[..].as_mut_ptr()); - - log::debug!("Config: mcc={} mnc={} lang={} cnt={} orien={} touch={} dens={} keys={} nav={} keysHid={} navHid={} sdk={} size={} long={} modetype={} modenight={}", - ndk_sys::AConfiguration_getMcc((*android_app).config), - ndk_sys::AConfiguration_getMnc((*android_app).config), - lang, - country, - ndk_sys::AConfiguration_getOrientation((*android_app).config), - ndk_sys::AConfiguration_getTouchscreen((*android_app).config), - ndk_sys::AConfiguration_getDensity((*android_app).config), - ndk_sys::AConfiguration_getKeyboard((*android_app).config), - ndk_sys::AConfiguration_getNavigation((*android_app).config), - ndk_sys::AConfiguration_getKeysHidden((*android_app).config), - ndk_sys::AConfiguration_getNavHidden((*android_app).config), - ndk_sys::AConfiguration_getSdkVersion((*android_app).config), - ndk_sys::AConfiguration_getScreenSize((*android_app).config), - ndk_sys::AConfiguration_getScreenLong((*android_app).config), - ndk_sys::AConfiguration_getUiModeType((*android_app).config), - ndk_sys::AConfiguration_getUiModeNight((*android_app).config)); -} - -unsafe fn android_app_attach_input_queue_looper(android_app: *mut ffi::NativeActivityGlue) { - if (*android_app).input_queue != ptr::null_mut() { - log::debug!("Attaching input queue to looper"); - ndk_sys::AInputQueue_attachLooper((*android_app).input_queue, - (*android_app).looper, ffi::LOOPER_ID_INPUT as libc::c_int, None, - &mut (*android_app).input_poll_source as *mut _ as *mut _); - } -} - -unsafe fn android_app_detach_input_queue_looper(android_app: *mut ffi::NativeActivityGlue) { - if (*android_app).input_queue != ptr::null_mut() { - log::debug!("Detaching input queue from looper"); - ndk_sys::AInputQueue_detachLooper((*android_app).input_queue); - } -} - -unsafe fn android_app_pre_exec_cmd(android_app: *mut ffi::NativeActivityGlue, cmd: ffi::AppCmd) { - match cmd { - ffi::AppCmd::InputChanged => { - log::debug!("AppCmd::INPUT_CHANGED\n"); - libc::pthread_mutex_lock(&mut (*android_app).mutex as *mut _); - if (*android_app).input_queue != ptr::null_mut() { - android_app_detach_input_queue_looper(android_app); - } - (*android_app).input_queue = (*android_app).pending_input_queue; - if (*android_app).input_queue != ptr::null_mut() { - android_app_attach_input_queue_looper(android_app); - } - libc::pthread_cond_broadcast(&mut (*android_app).cond as *mut _); - libc::pthread_mutex_unlock(&mut (*android_app).mutex as *mut _); - } - ffi::AppCmd::InitWindow => { - log::debug!("AppCmd::INIT_WINDOW"); - libc::pthread_mutex_lock(&mut (*android_app).mutex as *mut _); - (*android_app).window = (*android_app).pending_window; - libc::pthread_cond_broadcast(&mut (*android_app).cond as *mut _); - libc::pthread_mutex_unlock(&mut (*android_app).mutex as *mut _); - } - ffi::AppCmd::TermWindow => { - log::debug!("AppCmd::TERM_WINDOW"); - libc::pthread_cond_broadcast(&mut (*android_app).cond as *mut _); - } - ffi::AppCmd::Resume | ffi::AppCmd::Start | ffi::AppCmd::Pause | ffi::AppCmd::Stop => { - log::debug!("activityState={:#?}", cmd); - libc::pthread_mutex_lock(&mut (*android_app).mutex as *mut _); - (*android_app).activity_state = cmd as i32; - libc::pthread_cond_broadcast(&mut (*android_app).cond as *mut _); - libc::pthread_mutex_unlock(&mut (*android_app).mutex as *mut _); - } - ffi::AppCmd::ConfigChanged => { - log::debug!("AppCmd::CONFIG_CHANGED"); - ndk_sys::AConfiguration_fromAssetManager((*android_app).config, - (*(*android_app).activity).assetManager); - print_cur_config(android_app); - } - ffi::AppCmd::Destroy => { - log::debug!("AppCmd::DESTROY"); - (*android_app).destroy_requested = true; - } - _ => { } - } -} - -unsafe fn android_app_post_exec_cmd(android_app: *mut ffi::NativeActivityGlue, cmd: ffi::AppCmd) { - match cmd { - ffi::AppCmd::TermWindow => { - log::debug!("AppCmd::TERM_WINDOW"); - libc::pthread_mutex_lock(&mut (*android_app).mutex as *mut _); - (*android_app).window = ptr::null_mut(); - libc::pthread_cond_broadcast(&mut (*android_app).cond as *mut _); - libc::pthread_mutex_unlock(&mut (*android_app).mutex as *mut _); - } - ffi::AppCmd::SaveState => { - log::debug!("AppCmd::SAVE_STATE"); - libc::pthread_mutex_lock(&mut (*android_app).mutex as *mut _); - (*android_app).state_saved = true; - libc::pthread_cond_broadcast(&mut (*android_app).cond as *mut _); - libc::pthread_mutex_unlock(&mut (*android_app).mutex as *mut _); - } - ffi::AppCmd::Resume => { - free_saved_state(android_app); - } - _ => { } - } -} - -unsafe fn android_app_destroy(android_app: *mut ffi::NativeActivityGlue) { - log::debug!("android_app_destroy!"); - free_saved_state(android_app); - libc::pthread_mutex_lock(&mut (*android_app).mutex as *mut _); - if (*android_app).input_queue != ptr::null_mut() { - ndk_sys::AInputQueue_detachLooper((*android_app).input_queue); - } - ndk_sys::AConfiguration_delete((*android_app).config); - (*android_app).destroyed = true; - libc::pthread_cond_broadcast(&mut (*android_app).cond as *mut _); - libc::pthread_mutex_unlock(&mut (*android_app).mutex as *mut _); - // Can't touch android_app object after this. -} - -extern "C" fn android_app_main(arg: *mut libc::c_void) -> *mut libc::c_void { - unsafe { - let android_app: *mut ffi::NativeActivityGlue = arg.cast(); - - (*android_app).config = ndk_sys::AConfiguration_new(); - ndk_sys::AConfiguration_fromAssetManager((*android_app).config, (*(*android_app).activity).assetManager); - - print_cur_config(android_app); - - (*android_app).cmd_poll_source.id = ffi::LOOPER_ID_MAIN as i32; - (*android_app).cmd_poll_source.app = android_app; - (*android_app).cmd_poll_source.process = None; - - let looper = ndk_sys::ALooper_prepare(ndk_sys::ALOOPER_PREPARE_ALLOW_NON_CALLBACKS as libc::c_int); - ndk_sys::ALooper_addFd(looper, (*android_app).msg_read, ffi::LOOPER_ID_MAIN as libc::c_int, ndk_sys::ALOOPER_EVENT_INPUT as libc::c_int, None, - &mut (*android_app).cmd_poll_source as *mut _ as *mut _); - (*android_app).looper = looper; - - libc::pthread_mutex_lock(&mut (*android_app).mutex as *mut _); - (*android_app).running = true; - libc::pthread_cond_broadcast(&mut (*android_app).cond as *mut _); - libc::pthread_mutex_unlock(&mut (*android_app).mutex as *mut _); - - _rust_glue_entry(android_app); - - android_app_destroy(android_app); - - ptr::null_mut() - } -} - - -/////////////////////////////// -// Java-side callback handling -/////////////////////////////// - - -unsafe fn android_app_create(activity: *mut ndk_sys::ANativeActivity, - saved_state_in: *const libc::c_void, saved_state_size: libc::size_t) -> *mut ffi::NativeActivityGlue -{ - let mut msgpipe: [libc::c_int; 2] = [ -1, -1 ]; - if libc::pipe(msgpipe.as_mut_ptr()) != 0 { - panic!("could not create Rust <-> Java IPC pipe: {}", std::io::Error::last_os_error()); - } - - // For now we need to use malloc to track saved state, while the android_native_app_glue - // code will use free() to free the memory. - let mut saved_state = ptr::null_mut(); - if saved_state_in != ptr::null() && saved_state_size > 0 { - saved_state = libc::malloc(saved_state_size); - assert!(saved_state != ptr::null_mut(), "Failed to allocate {} bytes for restoring saved application state", saved_state_size); - libc::memcpy(saved_state, saved_state_in, saved_state_size); - } - - let android_app = Box::into_raw(Box::new(ffi::NativeActivityGlue { - activity, - config: ptr::null_mut(), - saved_state, - saved_state_size, - looper: ptr::null_mut(), - input_queue: ptr::null_mut(), - window: ptr::null_mut(), - content_rect: Rect::empty().into(), - activity_state: 0, - destroy_requested: false, - mutex: libc::PTHREAD_MUTEX_INITIALIZER, - cond: libc::PTHREAD_COND_INITIALIZER, - msg_read: msgpipe[0], - msg_write: msgpipe[1], - thread: 0, - cmd_poll_source: ffi::NativeActivityPollSource { id: 0, app: ptr::null_mut(), process: None }, - input_poll_source: ffi::NativeActivityPollSource { id: 0, app: ptr::null_mut(), process: None }, - running: false, - state_saved: false, - destroyed: false, - redraw_needed: false, - pending_input_queue: ptr::null_mut(), - pending_window: ptr::null_mut(), - pending_content_rect: Rect::empty().into(), - })); - - // TODO: use std::os::spawn and drop the handle to detach instead of directly - // using pthread_create - let mut attr = std::mem::MaybeUninit::::zeroed(); - libc::pthread_attr_init(attr.as_mut_ptr()); - libc::pthread_attr_setdetachstate(attr.as_mut_ptr(), libc::PTHREAD_CREATE_DETACHED); - let mut thread = std::mem::MaybeUninit::::zeroed(); - libc::pthread_create(thread.as_mut_ptr(), attr.as_mut_ptr(), android_app_main, android_app as *mut _); - let thread = thread.assume_init(); - (*android_app).thread = thread; - - // TODO: switch to std::sync::Condvar - // Wait for thread to start. - libc::pthread_mutex_lock(&mut (*android_app).mutex as *mut _); - while (*android_app).running == false { - libc::pthread_cond_wait(&mut (*android_app).cond as *mut _, &mut (*android_app).mutex as *mut _); - } - libc::pthread_mutex_unlock(&mut (*android_app).mutex as *mut _); - - android_app -} - -unsafe fn android_app_drop(android_app: *mut ffi::NativeActivityGlue) { - libc::pthread_mutex_lock(&mut (*android_app).mutex as *mut _); - android_app_write_cmd(android_app, ffi::AppCmd::Destroy as i8); - while !(*android_app).destroyed == false { - libc::pthread_cond_wait(&mut (*android_app).cond as *mut _, &mut (*android_app).mutex as *mut _); - } - libc::pthread_mutex_unlock(&mut (*android_app).mutex as *mut _); - - libc::close((*android_app).msg_read); - libc::close((*android_app).msg_write); - libc::pthread_cond_destroy(&mut (*android_app).cond as *mut _); - libc::pthread_mutex_destroy(&mut (*android_app).mutex as *mut _); - - let _android_app = Box::from_raw(android_app); - // Box dropped here -} - -unsafe fn android_app_write_cmd(android_app: *mut ffi::NativeActivityGlue, cmd: i8) { - loop { - match libc::write((*android_app).msg_write, &cmd as *const _ as *const _, 1) { - 1 => break, - -1 => { - let err = std::io::Error::last_os_error(); - if err.kind() != std::io::ErrorKind::Interrupted { - log::error!("Failure writing NativeActivityGlue cmd: {}", err); - return; - } - } - count => { - log::error!("Spurious write of {count} bytes while writing NativeActivityGlue cmd"); - return; - } - } - } -} - -unsafe fn android_app_set_input(android_app: *mut ffi::NativeActivityGlue, input_queue: *mut ndk_sys::AInputQueue) { - libc::pthread_mutex_lock(&mut (*android_app).mutex as *mut _); - (*android_app).pending_input_queue = input_queue; - android_app_write_cmd(android_app, ffi::AppCmd::InputChanged as i8); - while (*android_app).input_queue != (*android_app).pending_input_queue { - libc::pthread_cond_wait(&mut (*android_app).cond as *mut _, &mut (*android_app).mutex as *mut _); - } - libc::pthread_mutex_unlock(&mut (*android_app).mutex as *mut _); -} - -unsafe fn android_app_set_window(android_app: *mut ffi::NativeActivityGlue, window: *mut ndk_sys::ANativeWindow) { - libc::pthread_mutex_lock(&mut (*android_app).mutex as *mut _); - if (*android_app).pending_window != ptr::null_mut() { - android_app_write_cmd(android_app, ffi::AppCmd::TermWindow as i8); - } - (*android_app).pending_window = window; - if window != ptr::null_mut() { - android_app_write_cmd(android_app, ffi::AppCmd::InitWindow as i8); - } - while (*android_app).window != (*android_app).pending_window { - libc::pthread_cond_wait(&mut (*android_app).cond as *mut _, &mut (*android_app).mutex as *mut _); - } - libc::pthread_mutex_unlock(&mut (*android_app).mutex as *mut _); -} - -unsafe fn android_app_set_activity_state(android_app: *mut ffi::NativeActivityGlue, cmd: i8) { - libc::pthread_mutex_lock(&mut (*android_app).mutex as *mut _); - android_app_write_cmd(android_app, cmd); - while (*android_app).activity_state as i8 != cmd { - libc::pthread_cond_wait(&mut (*android_app).cond as *mut _, &mut (*android_app).mutex as *mut _); - } - libc::pthread_mutex_unlock(&mut (*android_app).mutex as *mut _); -} - -unsafe extern "C" fn on_destroy(activity: *mut ndk_sys::ANativeActivity) { - log::debug!("Destroy: {:p}\n", activity); - - let android_app: *mut ffi::NativeActivityGlue = (*activity).instance.cast(); - (*activity).instance = ptr::null_mut(); - android_app_drop(android_app); -} - -unsafe extern "C" fn on_start(activity: *mut ndk_sys::ANativeActivity) { - log::debug!("Start: {:p}\n", activity); - - let android_app: *mut ffi::NativeActivityGlue = (*activity).instance.cast(); - android_app_set_activity_state(android_app, ffi::AppCmd::Start as i8); -} - -unsafe extern "C" fn on_resume(activity: *mut ndk_sys::ANativeActivity) { - log::debug!("Resume: {:p}\n", activity); - let android_app: *mut ffi::NativeActivityGlue = (*activity).instance.cast(); - android_app_set_activity_state(android_app, ffi::AppCmd::Resume as i8); -} - -unsafe extern "C" fn on_save_instance_state(activity: *mut ndk_sys::ANativeActivity, out_len: *mut ndk_sys::size_t) -> *mut libc::c_void { - let android_app: *mut ffi::NativeActivityGlue = (*activity).instance.cast(); - let mut saved_state: *mut libc::c_void = ptr::null_mut(); - - log::debug!("SaveInstanceState: {:p}\n", activity); - libc::pthread_mutex_lock(&mut (*android_app).mutex as *mut _); - (*android_app).state_saved = false; - android_app_write_cmd(android_app, ffi::AppCmd::SaveState as i8); - while (*android_app).state_saved == false { - libc::pthread_cond_wait(&mut (*android_app).cond as *mut _, &mut (*android_app).mutex as *mut _); - } - - if (*android_app).saved_state != ptr::null_mut() { - saved_state = (*android_app).saved_state; - *out_len = (*android_app).saved_state_size as _; - (*android_app).saved_state = ptr::null_mut(); - (*android_app).saved_state_size = 0; - } - - libc::pthread_mutex_unlock(&mut (*android_app).mutex as *mut _); - - return saved_state; -} - -unsafe extern "C" fn on_pause(activity: *mut ndk_sys::ANativeActivity) { - log::debug!("Pause: {:p}\n", activity); - let android_app: *mut ffi::NativeActivityGlue = (*activity).instance.cast(); - android_app_set_activity_state(android_app, ffi::AppCmd::Pause as i8); -} - -unsafe extern "C" fn on_stop(activity: *mut ndk_sys::ANativeActivity) { - log::debug!("Stop: {:p}\n", activity); - let android_app: *mut ffi::NativeActivityGlue = (*activity).instance.cast(); - android_app_set_activity_state(android_app, ffi::AppCmd::Stop as i8); -} - -unsafe extern "C" fn on_configuration_changed(activity: *mut ndk_sys::ANativeActivity) { - log::debug!("ConfigurationChanged: {:p}\n", activity); - let android_app: *mut ffi::NativeActivityGlue = (*activity).instance.cast(); - android_app_write_cmd(android_app, ffi::AppCmd::ConfigChanged as i8); -} - -unsafe extern "C" fn on_low_memory(activity: *mut ndk_sys::ANativeActivity) { - log::debug!("LowMemory: {:p}\n", activity); - let android_app: *mut ffi::NativeActivityGlue = (*activity).instance.cast(); - android_app_write_cmd(android_app, ffi::AppCmd::LowMemory as i8); -} - -unsafe extern "C" fn on_window_focus_changed(activity: *mut ndk_sys::ANativeActivity, focused: libc::c_int) { - log::debug!("WindowFocusChanged: {:p} -- {}\n", activity, focused); - let android_app: *mut ffi::NativeActivityGlue = (*activity).instance.cast(); - android_app_write_cmd(android_app, - if focused != 0 { ffi::AppCmd::GainedFocus as i8 } else { ffi::AppCmd::LostFocus as i8}); -} - -unsafe extern "C" fn on_native_window_created(activity: *mut ndk_sys::ANativeActivity, window: *mut ndk_sys::ANativeWindow) { - log::debug!("NativeWindowCreated: {:p} -- {:p}\n", activity, window); - let android_app: *mut ffi::NativeActivityGlue = (*activity).instance.cast(); - android_app_set_window(android_app, window); -} - -unsafe extern "C" fn on_native_window_destroyed(activity: *mut ndk_sys::ANativeActivity, window: *mut ndk_sys::ANativeWindow) { - log::debug!("NativeWindowDestroyed: {:p} -- {:p}\n", activity, window); - let android_app: *mut ffi::NativeActivityGlue = (*activity).instance.cast(); - android_app_set_window(android_app, ptr::null_mut()); -} - -unsafe extern "C" fn on_input_queue_created(activity: *mut ndk_sys::ANativeActivity, queue: *mut ndk_sys::AInputQueue) { - log::debug!("InputQueueCreated: {:p} -- {:p}\n", activity, queue); - let android_app: *mut ffi::NativeActivityGlue = (*activity).instance.cast(); - android_app_set_input(android_app, queue); -} - -unsafe extern "C" fn on_input_queue_destroyed(activity: *mut ndk_sys::ANativeActivity, queue: *mut ndk_sys::AInputQueue) { - log::debug!("InputQueueDestroyed: {:p} -- {:p}\n", activity, queue); - let android_app: *mut ffi::NativeActivityGlue = (*activity).instance.cast(); - android_app_set_input(android_app, ptr::null_mut()); -} - -#[no_mangle] -unsafe extern "C" fn ANativeActivity_onCreate( - activity: *mut ndk_sys::ANativeActivity, - saved_state: *const libc::c_void, - saved_state_size: libc::size_t, -) { - log::debug!("Creating: {:p}", activity); - - (*(*activity).callbacks).onDestroy = Some(on_destroy); - (*(*activity).callbacks).onStart = Some(on_start); - (*(*activity).callbacks).onResume = Some(on_resume); - (*(*activity).callbacks).onSaveInstanceState = Some(on_save_instance_state); - (*(*activity).callbacks).onPause = Some(on_pause); - (*(*activity).callbacks).onStop = Some(on_stop); - (*(*activity).callbacks).onConfigurationChanged = Some(on_configuration_changed); - (*(*activity).callbacks).onLowMemory = Some(on_low_memory); - (*(*activity).callbacks).onWindowFocusChanged = Some(on_window_focus_changed); - (*(*activity).callbacks).onNativeWindowCreated = Some(on_native_window_created); - (*(*activity).callbacks).onNativeWindowDestroyed = Some(on_native_window_destroyed); - (*(*activity).callbacks).onInputQueueCreated = Some(on_input_queue_created); - (*(*activity).callbacks).onInputQueueDestroyed = Some(on_input_queue_destroyed); - - (*activity).instance = android_app_create(activity, saved_state, saved_state_size) as *mut _; -} - -fn android_log(level: Level, tag: &CStr, msg: &CStr) { - let prio = match level { - Level::Error => ndk_sys::android_LogPriority::ANDROID_LOG_ERROR, - Level::Warn => ndk_sys::android_LogPriority::ANDROID_LOG_WARN, - Level::Info => ndk_sys::android_LogPriority::ANDROID_LOG_INFO, - Level::Debug => ndk_sys::android_LogPriority::ANDROID_LOG_DEBUG, - Level::Trace => ndk_sys::android_LogPriority::ANDROID_LOG_VERBOSE, - }; - unsafe { - ndk_sys::__android_log_write(prio.0 as raw::c_int, tag.as_ptr(), msg.as_ptr()); - } -} - -extern "Rust" { - pub fn android_main(app: AndroidApp); -} - -// This is a spring board between android_native_app_glue and the user's -// `app_main` function. This is run on a dedicated thread spawned -// by android_native_app_glue. -pub unsafe fn _rust_glue_entry(app: *mut ffi::NativeActivityGlue) { - // Maybe make this stdout/stderr redirection an optional / opt-in feature?... - let mut logpipe: [RawFd; 2] = Default::default(); - libc::pipe(logpipe.as_mut_ptr()); - libc::dup2(logpipe[1], libc::STDOUT_FILENO); - libc::dup2(logpipe[1], libc::STDERR_FILENO); - thread::spawn(move || { - let tag = CStr::from_bytes_with_nul(b"RustStdoutStderr\0").unwrap(); - let file = File::from_raw_fd(logpipe[0]); - let mut reader = BufReader::new(file); - let mut buffer = String::new(); - loop { - buffer.clear(); - if let Ok(len) = reader.read_line(&mut buffer) { - if len == 0 { - break; - } else if let Ok(msg) = CString::new(buffer.clone()) { - android_log(Level::Info, tag, &msg); - } - } - } - }); - - let app = AndroidApp::from_ptr(NonNull::new(app).unwrap()); - - let na = app.native_activity(); - let jvm = (*na).vm; - let activity = (*na).clazz; // Completely bogus name; this is the _instance_ not class pointer - ndk_context::initialize_android_context(jvm.cast(), activity.cast()); - - // Since this is a newly spawned thread then the JVM hasn't been attached - // to the thread yet. Attach before calling the applications main function - // so they can safely make JNI calls - let mut jenv_out: *mut core::ffi::c_void = std::ptr::null_mut(); - if let Some(attach_current_thread) = (*(*jvm)).AttachCurrentThread { - attach_current_thread(jvm, &mut jenv_out, std::ptr::null_mut()); - } - - // XXX: If we were in control of the Java Activity subclass then - // we could potentially run the android_main function via a Java native method - // springboard (e.g. call an Activity subclass method that calls a jni native - // method that then just calls android_main()) that would make sure there was - // a Java frame at the base of our call stack which would then be recognised - // when calling FindClass to lookup a suitable classLoader, instead of - // defaulting to the system loader. Without this then it's difficult for native - // code to look up non-standard Java classes. - android_main(app); - - // Since this is a newly spawned thread then the JVM hasn't been attached - // to the thread yet. Attach before calling the applications main function - // so they can safely make JNI calls - if let Some(detach_current_thread) = (*(*jvm)).DetachCurrentThread { - detach_current_thread(jvm); - } - - ndk_context::release_android_context(); -}