native-activity: re-work ported glue code into idiomatic Rust

This is a fairly thorough re-working of all the code that was initially
naively ported from android_native_app_glue.c to be more idiomatic Rust
code (though by it's nature it still involves a lot of unsafe code)

Most of the glue code now lives in src/native_activity/glue.rs as part
of a NativeActivityGlue API

The design as far as the threading model, IPC and synchronization goes
is unchanged.
This commit is contained in:
Robert Bragg
2022-10-07 02:21:02 +01:00
parent 2870a84fbe
commit cd32b4a064
5 changed files with 1023 additions and 849 deletions
+2 -2
View File
@@ -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"),
+3 -3
View File
@@ -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);
}
+18 -9
View File
@@ -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<ndk_sys::ARect> 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<ndk_sys::ARect> 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
@@ -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<i8> for AppCmd {
type Error = ();
fn try_from(value: i8) -> Result<Self, Self::Error> {
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<NativeActivityState>,
pub cond: Condvar,
}
#[derive(Debug, Clone)]
pub struct NativeActivityGlue {
pub inner: Arc<WaitableNativeActivityState>,
}
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<AppCmd> {
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<InputQueue> {
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<NativeWindow>,
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<NativeWindow>,
pub pending_content_rect: ndk_sys::ARect,
}
impl NativeActivityState {
pub fn read_cmd(&mut self) -> Option<AppCmd> {
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<NativeWindow>) {
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<Vec<u8>> {
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();
}
}
File diff suppressed because it is too large Load Diff