From 507cfe072e7138980dd50607e6fb3500d59d404d Mon Sep 17 00:00:00 2001 From: jtnunley Date: Mon, 10 Apr 2023 09:45:05 -0700 Subject: [PATCH 01/19] Add dependabot support --- .github/dependabot.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..c7ecf5e --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,10 @@ +version: 2 +updates: + - package-ecosystem: cargo + directory: / + schedule: + interval: weekly + - package-ecosystem: github-actions + directory: / + schedule: + interval: weekly From a04c483d79d0d4614448cebad996a0c88ecf8f91 Mon Sep 17 00:00:00 2001 From: Marijn Suijten Date: Mon, 20 Mar 2023 21:26:18 +0100 Subject: [PATCH 02/19] native-activity: Propagate `onNativeWindowResized` callback to main loop --- android-activity/src/native_activity/glue.rs | 25 ++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/android-activity/src/native_activity/glue.rs b/android-activity/src/native_activity/glue.rs index 131878d..4562030 100644 --- a/android-activity/src/native_activity/glue.rs +++ b/android-activity/src/native_activity/glue.rs @@ -126,6 +126,7 @@ impl NativeActivityGlue { (*(*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).onNativeWindowResized = Some(on_native_window_resized); (*(*activity).callbacks).onNativeWindowDestroyed = Some(on_native_window_destroyed); (*(*activity).callbacks).onInputQueueCreated = Some(on_input_queue_created); (*(*activity).callbacks).onInputQueueDestroyed = Some(on_input_queue_destroyed); @@ -396,6 +397,16 @@ impl WaitableNativeActivityState { }); } + pub fn notify_window_resized(&self, native_window: *mut ndk_sys::ANativeWindow) { + let mut guard = self.mutex.lock().unwrap(); + // set_window always syncs .pending_window back to .window before returning. This callback + // from Android can never arrive at an interim state, and validates that Android: + // 1. Only provides resizes in between onNativeWindowCreated and onNativeWindowDestroyed; + // 2. Doesn't call it on a bogus window pointer that we don't know about. + debug_assert_eq!(guard.window.as_ref().unwrap().ptr().as_ptr(), native_window); + guard.write_cmd(AppCmd::WindowResized); + } + unsafe fn set_input(&self, input_queue: *mut ndk_sys::AInputQueue) { let mut guard = self.mutex.lock().unwrap(); @@ -694,13 +705,23 @@ unsafe extern "C" fn on_native_window_created( ) { log::debug!("NativeWindowCreated: {:p} -- {:p}\n", activity, window); try_with_waitable_activity_ref(activity, |waitable_activity| { - // It's important that we use ::clone_from_ptr() here because NativeWindow - // has a Drop implementation that will unconditionally _release() the native window + // Use clone_from_ptr to acquire additional ownership on the NativeWindow, + // which will unconditionally be _release()'d on Drop. let window = NativeWindow::clone_from_ptr(NonNull::new_unchecked(window)); waitable_activity.set_window(Some(window)); }); } +unsafe extern "C" fn on_native_window_resized( + activity: *mut ndk_sys::ANativeActivity, + window: *mut ndk_sys::ANativeWindow, +) { + log::debug!("NativeWindowResized: {:p} -- {:p}\n", activity, window); + try_with_waitable_activity_ref(activity, |waitable_activity| { + waitable_activity.notify_window_resized(window); + }); +} + unsafe extern "C" fn on_native_window_destroyed( activity: *mut ndk_sys::ANativeActivity, window: *mut ndk_sys::ANativeWindow, From 87fe6a84653716f2c41f0b2da66e0d3e32e6b496 Mon Sep 17 00:00:00 2001 From: Marijn Suijten Date: Sat, 1 Apr 2023 14:41:33 +0200 Subject: [PATCH 03/19] native-activity: Propagate `onNativeWindowRedrawNeeded` callback to main loop --- android-activity/Cargo.toml | 4 ++-- android-activity/src/native_activity/glue.rs | 22 ++++++++++++++++++++ 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/android-activity/Cargo.toml b/android-activity/Cargo.toml index 70223dd..f0de17e 100644 --- a/android-activity/Cargo.toml +++ b/android-activity/Cargo.toml @@ -17,7 +17,7 @@ license = "MIT OR Apache-2.0" # # In general it's only the final application crate that needs # to decide on a backend. -default=[] +default = [] game-activity = [] native-activity = [] @@ -43,4 +43,4 @@ targets = [ "x86_64-linux-android", ] -rustdoc-args = ["--cfg", "docsrs" ] \ No newline at end of file +rustdoc-args = ["--cfg", "docsrs"] diff --git a/android-activity/src/native_activity/glue.rs b/android-activity/src/native_activity/glue.rs index 4562030..8e47e8a 100644 --- a/android-activity/src/native_activity/glue.rs +++ b/android-activity/src/native_activity/glue.rs @@ -127,6 +127,8 @@ impl NativeActivityGlue { (*(*activity).callbacks).onWindowFocusChanged = Some(on_window_focus_changed); (*(*activity).callbacks).onNativeWindowCreated = Some(on_native_window_created); (*(*activity).callbacks).onNativeWindowResized = Some(on_native_window_resized); + (*(*activity).callbacks).onNativeWindowRedrawNeeded = + Some(on_native_window_redraw_needed); (*(*activity).callbacks).onNativeWindowDestroyed = Some(on_native_window_destroyed); (*(*activity).callbacks).onInputQueueCreated = Some(on_input_queue_created); (*(*activity).callbacks).onInputQueueDestroyed = Some(on_input_queue_destroyed); @@ -407,6 +409,16 @@ impl WaitableNativeActivityState { guard.write_cmd(AppCmd::WindowResized); } + pub fn notify_window_redraw_needed(&self, native_window: *mut ndk_sys::ANativeWindow) { + let mut guard = self.mutex.lock().unwrap(); + // set_window always syncs .pending_window back to .window before returning. This callback + // from Android can never arrive at an interim state, and validates that Android: + // 1. Only provides resizes in between onNativeWindowCreated and onNativeWindowDestroyed; + // 2. Doesn't call it on a bogus window pointer that we don't know about. + debug_assert_eq!(guard.window.as_ref().unwrap().ptr().as_ptr(), native_window); + guard.write_cmd(AppCmd::WindowRedrawNeeded); + } + unsafe fn set_input(&self, input_queue: *mut ndk_sys::AInputQueue) { let mut guard = self.mutex.lock().unwrap(); @@ -722,6 +734,16 @@ unsafe extern "C" fn on_native_window_resized( }); } +unsafe extern "C" fn on_native_window_redraw_needed( + activity: *mut ndk_sys::ANativeActivity, + window: *mut ndk_sys::ANativeWindow, +) { + log::debug!("NativeWindowRedrawNeeded: {:p} -- {:p}\n", activity, window); + try_with_waitable_activity_ref(activity, |waitable_activity| { + waitable_activity.notify_window_redraw_needed(window) + }); +} + unsafe extern "C" fn on_native_window_destroyed( activity: *mut ndk_sys::ANativeActivity, window: *mut ndk_sys::ANativeWindow, From 0c3f16c9ba77185c059f93ec6307116ae0bce142 Mon Sep 17 00:00:00 2001 From: Marijn Suijten Date: Sat, 1 Apr 2023 15:00:35 +0200 Subject: [PATCH 04/19] native-activity: Propagate `onContentRectChanged` callback to main loop --- android-activity/src/native_activity/glue.rs | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/android-activity/src/native_activity/glue.rs b/android-activity/src/native_activity/glue.rs index 8e47e8a..908e63f 100644 --- a/android-activity/src/native_activity/glue.rs +++ b/android-activity/src/native_activity/glue.rs @@ -132,6 +132,7 @@ impl NativeActivityGlue { (*(*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).callbacks).onContentRectChanged = Some(on_content_rect_changed); } glue @@ -211,7 +212,6 @@ pub struct NativeActivityState { 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 { @@ -358,7 +358,6 @@ impl WaitableNativeActivityState { redraw_needed: false, pending_input_queue: ptr::null_mut(), pending_window: None, - pending_content_rect: Rect::empty().into(), }), cond: Condvar::new(), } @@ -459,6 +458,12 @@ impl WaitableNativeActivityState { guard.pending_window = None; } + unsafe fn set_content_rect(&self, rect: *const ndk_sys::ARect) { + let mut guard = self.mutex.lock().unwrap(); + guard.content_rect = *rect; + guard.write_cmd(AppCmd::ContentRectChanged); + } + unsafe fn set_activity_state(&self, state: State) { let mut guard = self.mutex.lock().unwrap(); @@ -774,6 +779,16 @@ unsafe extern "C" fn on_input_queue_destroyed( }); } +unsafe extern "C" fn on_content_rect_changed( + activity: *mut ndk_sys::ANativeActivity, + rect: *const ndk_sys::ARect, +) { + log::debug!("ContentRectChanged: {:p} -- {:p}\n", activity, rect); + try_with_waitable_activity_ref(activity, |waitable_activity| { + waitable_activity.set_content_rect(rect) + }); +} + /// This is the native entrypoint for our cdylib library that `ANativeActivity` will look for via `dlsym` #[no_mangle] extern "C" fn ANativeActivity_onCreate( From fe9d68c99e07e44b58a2280d5f5be0a163893302 Mon Sep 17 00:00:00 2001 From: Marijn Suijten Date: Sat, 1 Apr 2023 15:11:48 +0200 Subject: [PATCH 05/19] Clean up partial module imports Some items were imported in scope while most of the code uses fully qualified names. --- android-activity/src/native_activity/glue.rs | 7 +++---- android-activity/src/native_activity/mod.rs | 15 +++++---------- 2 files changed, 8 insertions(+), 14 deletions(-) diff --git a/android-activity/src/native_activity/glue.rs b/android-activity/src/native_activity/glue.rs index 908e63f..4e2b863 100644 --- a/android-activity/src/native_activity/glue.rs +++ b/android-activity/src/native_activity/glue.rs @@ -14,7 +14,6 @@ use std::{ use log::Level; use ndk::{configuration::Configuration, input_queue::InputQueue, native_window::NativeWindow}; -use ndk_sys::ANativeActivity; use crate::ConfigurationRef; @@ -99,7 +98,7 @@ impl Deref for NativeActivityGlue { impl NativeActivityGlue { pub fn new( - activity: *mut ANativeActivity, + activity: *mut ndk_sys::ANativeActivity, saved_state: *const libc::c_void, saved_state_size: libc::size_t, ) -> Self { @@ -149,7 +148,7 @@ impl NativeActivityGlue { self.inner.mutex.lock().unwrap().read_cmd() } - /// For the Rust main thread to get an ndk::InputQueue that wraps the AInputQueue pointer + /// For the Rust main thread to get an [`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 @@ -837,7 +836,7 @@ extern "C" fn ANativeActivity_onCreate( // Note: we drop the thread handle which will detach the thread std::thread::spawn(move || { - let activity: *mut ANativeActivity = activity_ptr as *mut _; + let activity: *mut ndk_sys::ANativeActivity = activity_ptr as *mut _; let jvm = unsafe { let na = activity; diff --git a/android-activity/src/native_activity/mod.rs b/android-activity/src/native_activity/mod.rs index a67f56a..e0bb330 100644 --- a/android-activity/src/native_activity/mod.rs +++ b/android-activity/src/native_activity/mod.rs @@ -7,12 +7,7 @@ use std::time::Duration; use libc::c_void; use log::{error, trace}; - -use ndk_sys::ALooper_wake; -use ndk_sys::{ALooper, ALooper_pollAll}; - -use ndk::asset::AssetManager; -use ndk::native_window::NativeWindow; +use ndk::{asset::AssetManager, native_window::NativeWindow}; use crate::{ util, AndroidApp, ConfigurationRef, InputStatus, MainEvent, PollEvent, Rect, WindowManagerFlags, @@ -63,7 +58,7 @@ pub struct AndroidAppWaker { // The looper pointer is owned by the android_app and effectively // has a 'static lifetime, and the ALooper_wake C API is thread // safe, so this can be cloned safely and is send + sync safe - looper: NonNull, + looper: NonNull, } unsafe impl Send for AndroidAppWaker {} unsafe impl Sync for AndroidAppWaker {} @@ -77,7 +72,7 @@ impl AndroidAppWaker { /// [wake_event]: crate::PollEvent::Wake pub fn wake(&self) { unsafe { - ALooper_wake(self.looper.as_ptr()); + ndk_sys::ALooper_wake(self.looper.as_ptr()); } } } @@ -119,7 +114,7 @@ impl AndroidApp { #[derive(Debug)] struct Looper { - pub ptr: *mut ALooper, + pub ptr: *mut ndk_sys::ALooper, } unsafe impl Send for Looper {} unsafe impl Sync for Looper {} @@ -174,7 +169,7 @@ impl AndroidAppInner { !ndk_sys::ALooper_forThread().is_null(), "Application tried to poll events from non-main thread" ); - let id = ALooper_pollAll( + let id = ndk_sys::ALooper_pollAll( timeout_milliseconds, &mut fd, &mut events, From 9ac7891664b6efe38d47dccba42a5fbf20aa43b9 Mon Sep 17 00:00:00 2001 From: Robert Bragg Date: Sat, 22 Apr 2023 17:35:46 +0100 Subject: [PATCH 06/19] CI: test with cargo-ndk 2, which compiles with rustc 1.60 --- .github/workflows/ci.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d9f7191..25cee7f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -36,7 +36,9 @@ jobs: i686-linux-android - name: Install cargo-ndk - run: cargo install cargo-ndk + # We're currently sticking with cargo-ndk 2, until we bump our + # MSRV to 1.68+ + run: cargo install cargo-ndk --version "^2" - name: Setup Java uses: actions/setup-java@v3 From 1b44d38822a430c5d898c490d8f3623cf9fb477d Mon Sep 17 00:00:00 2001 From: Robert Bragg Date: Sun, 23 Apr 2023 15:47:15 +0100 Subject: [PATCH 07/19] Bump MSRV to 0.64 and and MSRV policy to README --- .github/workflows/ci.yml | 2 +- README.md | 8 ++++++++ android-activity/Cargo.toml | 1 + 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 25cee7f..bbed8aa 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,7 +19,7 @@ jobs: # We need to support the same MSRV as Winit which we are currently # assuming will be bumped to 1.60.0 soon: # https://github.com/rust-windowing/winit/pull/2453 - rust_version: [1.60.0, stable] + rust_version: [1.64.0, stable] steps: - uses: actions/checkout@v2 diff --git a/README.md b/README.md index 780474b..b2d0dc0 100644 --- a/README.md +++ b/README.md @@ -147,3 +147,11 @@ Prior to working on android-activity, the existing glue crates available for bui [`AppCompatActivity`]: https://developer.android.com/reference/androidx/appcompat/app/AppCompatActivity +# MSRV + +We aim to (at least) support stable releases of Rust from the last three months. Rust has a 6 week release cycle which means we will support the last three stable releases. +For example, when Rust 1.69 is released we would limit our `rust_version` to 1.67. + +We will only bump the `rust_version` at the point where we either depend on a new features or a dependency has increased its MSRV, and we won't be greedy. In other words we will only set the MSRV to the lowest version that's _needed_. + +MSRV updates are not considered to be inherently semver breaking (unless a new feature is exposed in the public API) and so a `rust_version` change may happen in patch releases. diff --git a/android-activity/Cargo.toml b/android-activity/Cargo.toml index f0de17e..5c13802 100644 --- a/android-activity/Cargo.toml +++ b/android-activity/Cargo.toml @@ -9,6 +9,7 @@ repository = "https://github.com/rust-mobile/android-activity" documentation = "https://docs.rs/android-activity" description = "Glue for building Rust applications on Android with NativeActivity or GameActivity" license = "MIT OR Apache-2.0" +rust_version = "0.64" [features] # Note: we don't enable any backend by default since features From d4a3d5845d38bf1621d628e79c1535d49305aa3b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 23 Apr 2023 15:11:09 +0000 Subject: [PATCH 08/19] build(deps): bump actions/checkout from 2 to 3 Bumps [actions/checkout](https://github.com/actions/checkout) from 2 to 3. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v2...v3) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bbed8aa..58dfe30 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,7 +21,7 @@ jobs: # https://github.com/rust-windowing/winit/pull/2453 rust_version: [1.64.0, stable] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: hecrj/setup-rust-action@v1 with: @@ -98,7 +98,7 @@ jobs: format: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: actions-rs/toolchain@v1 with: From 0fa6888484b5a7fb8c314b33b7b4ac7cb64d9a80 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 23 Apr 2023 15:14:30 +0000 Subject: [PATCH 09/19] build(deps): update num_enum requirement from 0.5 to 0.6 Updates the requirements on [num_enum](https://github.com/illicitonion/num_enum) to permit the latest version. - [Release notes](https://github.com/illicitonion/num_enum/releases) - [Commits](https://github.com/illicitonion/num_enum/compare/0.5.0...0.6.1) --- updated-dependencies: - dependency-name: num_enum dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- android-activity/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android-activity/Cargo.toml b/android-activity/Cargo.toml index 5c13802..3724c68 100644 --- a/android-activity/Cargo.toml +++ b/android-activity/Cargo.toml @@ -29,7 +29,7 @@ ndk = "0.7" ndk-sys = "0.4" ndk-context = "0.1" android-properties = "0.2" -num_enum = "0.5" +num_enum = "0.6" bitflags = "1.3" libc = "0.2" From e0c96ad6b478140a4ce77b7db185b345fac5fe96 Mon Sep 17 00:00:00 2001 From: jtnunley Date: Wed, 29 Mar 2023 09:19:07 -0700 Subject: [PATCH 10/19] Dedup android_log --- android-activity/src/game_activity/mod.rs | 15 +-------------- android-activity/src/native_activity/glue.rs | 14 +------------- android-activity/src/util.rs | 15 +++++++++++++++ 3 files changed, 17 insertions(+), 27 deletions(-) diff --git a/android-activity/src/game_activity/mod.rs b/android-activity/src/game_activity/mod.rs index 842865f..4c920ab 100644 --- a/android-activity/src/game_activity/mod.rs +++ b/android-activity/src/game_activity/mod.rs @@ -5,7 +5,6 @@ use std::fs::File; use std::io::{BufRead, BufReader}; use std::marker::PhantomData; use std::ops::Deref; -use std::os::raw; use std::os::unix::prelude::*; use std::ptr::NonNull; use std::sync::{Arc, RwLock}; @@ -24,6 +23,7 @@ use ndk::asset::AssetManager; use ndk::configuration::Configuration; use ndk::native_window::NativeWindow; +use crate::util::android_log; use crate::{ util, AndroidApp, ConfigurationRef, InputStatus, MainEvent, PollEvent, Rect, WindowManagerFlags, }; @@ -595,19 +595,6 @@ pub unsafe extern "C" fn GameActivity_onCreate( GameActivity_onCreate_C(activity, saved_state, saved_state_size); } -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); } diff --git a/android-activity/src/native_activity/glue.rs b/android-activity/src/native_activity/glue.rs index 4e2b863..9c33ce4 100644 --- a/android-activity/src/native_activity/glue.rs +++ b/android-activity/src/native_activity/glue.rs @@ -15,6 +15,7 @@ use std::{ use log::Level; use ndk::{configuration::Configuration, input_queue::InputQueue, native_window::NativeWindow}; +use crate::util::android_log; use crate::ConfigurationRef; use super::{AndroidApp, Rect}; @@ -611,19 +612,6 @@ 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 fn try_with_waitable_activity_ref( activity: *mut ndk_sys::ANativeActivity, closure: impl FnOnce(Arc), diff --git a/android-activity/src/util.rs b/android-activity/src/util.rs index 6069d7e..bd5b6b1 100644 --- a/android-activity/src/util.rs +++ b/android-activity/src/util.rs @@ -1,5 +1,7 @@ use std::{ffi::CStr, os::raw::c_char}; +use log::Level; + pub fn try_get_path_from_ptr(path: *const c_char) -> Option { if path.is_null() { return None; @@ -13,3 +15,16 @@ pub fn try_get_path_from_ptr(path: *const c_char) -> Option } Some(std::path::PathBuf::from(cstr)) } + +pub(crate) 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()); + } +} From 4976cbad443246e6588084076b34017c591ce705 Mon Sep 17 00:00:00 2001 From: Robert Bragg Date: Mon, 24 Apr 2023 11:42:13 +0100 Subject: [PATCH 11/19] Fix rust_version typo s/0.64/1.64/ Also removes a stale comment about MSVR policy in ci.yml --- .github/workflows/ci.yml | 4 +--- android-activity/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 58dfe30..c4d7c13 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,9 +16,7 @@ jobs: strategy: fail-fast: false matrix: - # We need to support the same MSRV as Winit which we are currently - # assuming will be bumped to 1.60.0 soon: - # https://github.com/rust-windowing/winit/pull/2453 + # See top README for MSRV policy rust_version: [1.64.0, stable] steps: - uses: actions/checkout@v3 diff --git a/android-activity/Cargo.toml b/android-activity/Cargo.toml index 3724c68..764c8e6 100644 --- a/android-activity/Cargo.toml +++ b/android-activity/Cargo.toml @@ -9,7 +9,7 @@ repository = "https://github.com/rust-mobile/android-activity" documentation = "https://docs.rs/android-activity" description = "Glue for building Rust applications on Android with NativeActivity or GameActivity" license = "MIT OR Apache-2.0" -rust_version = "0.64" +rust_version = "1.64" [features] # Note: we don't enable any backend by default since features From d6ccefaf77a030d78eae723f8776f7daf27015b3 Mon Sep 17 00:00:00 2001 From: jtnunley Date: Mon, 20 Mar 2023 09:46:03 -0700 Subject: [PATCH 12/19] Add catch unwind wrappers to extern "C" functions Co-authored-by: Robert Bragg --- android-activity/src/game_activity/mod.rs | 94 +++--- android-activity/src/native_activity/glue.rs | 317 ++++++++++--------- android-activity/src/util.rs | 44 ++- 3 files changed, 262 insertions(+), 193 deletions(-) diff --git a/android-activity/src/game_activity/mod.rs b/android-activity/src/game_activity/mod.rs index 4c920ab..82bc3e3 100644 --- a/android-activity/src/game_activity/mod.rs +++ b/android-activity/src/game_activity/mod.rs @@ -23,7 +23,7 @@ use ndk::asset::AssetManager; use ndk::configuration::Configuration; use ndk::native_window::NativeWindow; -use crate::util::android_log; +use crate::util::{abort_on_panic, android_log}; use crate::{ util, AndroidApp, ConfigurationRef, InputStatus, MainEvent, PollEvent, Rect, WindowManagerFlags, }; @@ -604,55 +604,57 @@ extern "Rust" { // by android_native_app_glue. #[no_mangle] pub unsafe extern "C" fn _rust_glue_entry(app: *mut ffi::android_app) { - // 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); + abort_on_panic(|| { + // 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 jvm: *mut JavaVM = (*(*app).activity).vm; + let activity: jobject = (*(*app).activity).javaGameActivity; + ndk_context::initialize_android_context(jvm.cast(), activity.cast()); + + let app = AndroidApp::from_ptr(NonNull::new(app).unwrap()); + + // 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()); } - }); - let jvm: *mut JavaVM = (*(*app).activity).vm; - let activity: jobject = (*(*app).activity).javaGameActivity; - ndk_context::initialize_android_context(jvm.cast(), activity.cast()); + // 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); - let app = AndroidApp::from_ptr(NonNull::new(app).unwrap()); + if let Some(detach_current_thread) = (*(*jvm)).DetachCurrentThread { + detach_current_thread(jvm); + } - // 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); - - if let Some(detach_current_thread) = (*(*jvm)).DetachCurrentThread { - detach_current_thread(jvm); - } - - ndk_context::release_android_context(); + ndk_context::release_android_context(); + }) } diff --git a/android-activity/src/native_activity/glue.rs b/android-activity/src/native_activity/glue.rs index 9c33ce4..6af66af 100644 --- a/android-activity/src/native_activity/glue.rs +++ b/android-activity/src/native_activity/glue.rs @@ -15,8 +15,7 @@ use std::{ use log::Level; use ndk::{configuration::Configuration, input_queue::InputQueue, native_window::NativeWindow}; -use crate::util::android_log; -use crate::ConfigurationRef; +use crate::{util::abort_on_panic, util::android_log, ConfigurationRef}; use super::{AndroidApp, Rect}; @@ -628,92 +627,112 @@ unsafe fn try_with_waitable_activity_ref( } unsafe extern "C" fn on_destroy(activity: *mut ndk_sys::ANativeActivity) { - log::debug!("Destroy: {:p}\n", activity); - try_with_waitable_activity_ref(activity, |waitable_activity| { - waitable_activity.notify_destroyed() - }); + abort_on_panic(|| { + log::debug!("Destroy: {:p}\n", activity); + try_with_waitable_activity_ref(activity, |waitable_activity| { + waitable_activity.notify_destroyed() + }); + }) } unsafe extern "C" fn on_start(activity: *mut ndk_sys::ANativeActivity) { - log::debug!("Start: {:p}\n", activity); - try_with_waitable_activity_ref(activity, |waitable_activity| { - waitable_activity.set_activity_state(State::Start); - }); + abort_on_panic(|| { + log::debug!("Start: {:p}\n", activity); + try_with_waitable_activity_ref(activity, |waitable_activity| { + waitable_activity.set_activity_state(State::Start); + }); + }) } unsafe extern "C" fn on_resume(activity: *mut ndk_sys::ANativeActivity) { - log::debug!("Resume: {:p}\n", activity); - try_with_waitable_activity_ref(activity, |waitable_activity| { - waitable_activity.set_activity_state(State::Resume); - }); + abort_on_panic(|| { + log::debug!("Resume: {:p}\n", activity); + try_with_waitable_activity_ref(activity, |waitable_activity| { + 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); - *out_len = 0; - let mut ret = ptr::null_mut(); - try_with_waitable_activity_ref(activity, |waitable_activity| { - let (state, len) = waitable_activity.request_save_state(); - *out_len = len as ndk_sys::size_t; - ret = state - }); + abort_on_panic(|| { + log::debug!("SaveInstanceState: {:p}\n", activity); + *out_len = 0; + let mut ret = ptr::null_mut(); + try_with_waitable_activity_ref(activity, |waitable_activity| { + let (state, len) = waitable_activity.request_save_state(); + *out_len = len as ndk_sys::size_t; + ret = state + }); - log::debug!("Saved state = {:p}, len = {}", ret, *out_len); - ret + log::debug!("Saved state = {:p}, len = {}", ret, *out_len); + ret + }) } unsafe extern "C" fn on_pause(activity: *mut ndk_sys::ANativeActivity) { - log::debug!("Pause: {:p}\n", activity); - try_with_waitable_activity_ref(activity, |waitable_activity| { - waitable_activity.set_activity_state(State::Pause); - }); + abort_on_panic(|| { + log::debug!("Pause: {:p}\n", activity); + try_with_waitable_activity_ref(activity, |waitable_activity| { + waitable_activity.set_activity_state(State::Pause); + }); + }) } unsafe extern "C" fn on_stop(activity: *mut ndk_sys::ANativeActivity) { - log::debug!("Stop: {:p}\n", activity); - try_with_waitable_activity_ref(activity, |waitable_activity| { - waitable_activity.set_activity_state(State::Stop); - }); + abort_on_panic(|| { + log::debug!("Stop: {:p}\n", activity); + try_with_waitable_activity_ref(activity, |waitable_activity| { + 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); - try_with_waitable_activity_ref(activity, |waitable_activity| { - waitable_activity.notify_config_changed(); - }); + abort_on_panic(|| { + log::debug!("ConfigurationChanged: {:p}\n", activity); + try_with_waitable_activity_ref(activity, |waitable_activity| { + waitable_activity.notify_config_changed(); + }); + }) } unsafe extern "C" fn on_low_memory(activity: *mut ndk_sys::ANativeActivity) { - log::debug!("LowMemory: {:p}\n", activity); - try_with_waitable_activity_ref(activity, |waitable_activity| { - waitable_activity.notify_low_memory(); - }); + abort_on_panic(|| { + log::debug!("LowMemory: {:p}\n", activity); + try_with_waitable_activity_ref(activity, |waitable_activity| { + 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); - try_with_waitable_activity_ref(activity, |waitable_activity| { - waitable_activity.notify_focus_changed(focused != 0); - }); + abort_on_panic(|| { + log::debug!("WindowFocusChanged: {:p} -- {}\n", activity, focused); + try_with_waitable_activity_ref(activity, |waitable_activity| { + 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); - try_with_waitable_activity_ref(activity, |waitable_activity| { - // Use clone_from_ptr to acquire additional ownership on the NativeWindow, - // which will unconditionally be _release()'d on Drop. - let window = NativeWindow::clone_from_ptr(NonNull::new_unchecked(window)); - waitable_activity.set_window(Some(window)); - }); + abort_on_panic(|| { + log::debug!("NativeWindowCreated: {:p} -- {:p}\n", activity, window); + try_with_waitable_activity_ref(activity, |waitable_activity| { + // Use clone_from_ptr to acquire additional ownership on the NativeWindow, + // which will unconditionally be _release()'d on Drop. + let window = NativeWindow::clone_from_ptr(NonNull::new_unchecked(window)); + waitable_activity.set_window(Some(window)); + }); + }) } unsafe extern "C" fn on_native_window_resized( @@ -740,30 +759,36 @@ 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); - try_with_waitable_activity_ref(activity, |waitable_activity| { - waitable_activity.set_window(None); - }); + abort_on_panic(|| { + log::debug!("NativeWindowDestroyed: {:p} -- {:p}\n", activity, window); + try_with_waitable_activity_ref(activity, |waitable_activity| { + 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); - try_with_waitable_activity_ref(activity, |waitable_activity| { - waitable_activity.set_input(queue); - }); + abort_on_panic(|| { + log::debug!("InputQueueCreated: {:p} -- {:p}\n", activity, queue); + try_with_waitable_activity_ref(activity, |waitable_activity| { + 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); - try_with_waitable_activity_ref(activity, |waitable_activity| { - waitable_activity.set_input(ptr::null_mut()); - }); + abort_on_panic(|| { + log::debug!("InputQueueDestroyed: {:p} -- {:p}\n", activity, queue); + try_with_waitable_activity_ref(activity, |waitable_activity| { + waitable_activity.set_input(ptr::null_mut()); + }); + }) } unsafe extern "C" fn on_content_rect_changed( @@ -783,92 +808,94 @@ extern "C" fn ANativeActivity_onCreate( saved_state: *const libc::c_void, saved_state_size: libc::size_t, ) { - // 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); + abort_on_panic(|| { + // 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); + } } } + }); + } + + log::trace!( + "Creating: {:p}, saved_state = {:p}, save_state_size = {}", + activity, + saved_state, + saved_state_size + ); + + // 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 ndk_sys::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); + + if let Some(detach_current_thread) = (*(*jvm)).DetachCurrentThread { + detach_current_thread(jvm); + } + + ndk_context::release_android_context(); } }); - } - log::trace!( - "Creating: {:p}, saved_state = {:p}, save_state_size = {}", - activity, - saved_state, - saved_state_size - ); - - // 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 ndk_sys::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); - - 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(); } - }); - - // 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/util.rs b/android-activity/src/util.rs index bd5b6b1..c115328 100644 --- a/android-activity/src/util.rs +++ b/android-activity/src/util.rs @@ -1,6 +1,8 @@ -use std::{ffi::CStr, os::raw::c_char}; - use log::Level; +use std::{ + ffi::{CStr, CString}, + os::raw::c_char, +}; pub fn try_get_path_from_ptr(path: *const c_char) -> Option { if path.is_null() { @@ -28,3 +30,41 @@ pub(crate) fn android_log(level: Level, tag: &CStr, msg: &CStr) { ndk_sys::__android_log_write(prio.0 as libc::c_int, tag.as_ptr(), msg.as_ptr()); } } + +/// Run a closure and abort the program if it panics. +/// +/// This is generally used to ensure Rust callbacks won't unwind past the JNI boundary, which leads +/// to undefined behaviour. +/// +/// TODO(rib): throw a Java exception instead of aborting. An Android Activity does not necessarily +/// own the entire process because other application Services (or even Activities) may run in +/// threads within the same process, and so we're tearing down too much by aborting the process. +pub(crate) fn abort_on_panic(f: impl FnOnce() -> R) -> R { + std::panic::catch_unwind(std::panic::AssertUnwindSafe(f)).unwrap_or_else(|panic| { + // Try logging the panic before aborting + // + // Just in case our attempt to log a panic could itself cause a panic we use a + // second catch_unwind here. + let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { + // Try logging the panic, but abort if that fails. + let rust_panic = unsafe { CStr::from_bytes_with_nul_unchecked(b"RustPanic\0") }; + + if let Some(panic) = panic.downcast_ref::() { + if let Ok(msg) = CString::new(panic.clone()) { + android_log(Level::Error, rust_panic, &msg); + } + } else if let Ok(panic) = panic.downcast::<&str>() { + if let Ok(msg) = CString::new(*panic) { + android_log(Level::Error, rust_panic, &msg); + } + } else { + let unknown_panic = + unsafe { CStr::from_bytes_with_nul_unchecked(b"UnknownPanic\0") }; + android_log(Level::Error, unknown_panic, unsafe { + CStr::from_bytes_with_nul_unchecked(b"\0") + }); + } + })); + std::process::abort(); + }) +} From 049e6602199e27a81bddf28c209a3e028efee907 Mon Sep 17 00:00:00 2001 From: kukie Date: Tue, 23 May 2023 15:43:03 +0300 Subject: [PATCH 13/19] fix: pointer_index always returns 0 --- android-activity/src/game_activity/input.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android-activity/src/game_activity/input.rs b/android-activity/src/game_activity/input.rs index ac951be..0f29a42 100644 --- a/android-activity/src/game_activity/input.rs +++ b/android-activity/src/game_activity/input.rs @@ -332,7 +332,7 @@ impl<'a> MotionEvent<'a> { /// or [`PointerDown`](MotionAction::PointerDown). #[inline] pub fn pointer_index(&self) -> usize { - let action = self.action as u32 & ndk_sys::AMOTION_EVENT_ACTION_MASK; + let action = self.action as u32; let index = (action & ndk_sys::AMOTION_EVENT_ACTION_POINTER_INDEX_MASK) >> ndk_sys::AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT; index as usize From 3843a7cfaa04c3981c1e655db6bfa6e603924ebd Mon Sep 17 00:00:00 2001 From: Robert Bragg Date: Thu, 27 Apr 2023 22:00:43 +0100 Subject: [PATCH 14/19] Call Activity.finish() when android_main returns Calling Activity.finish() is what ensures the Activity will get gracefully destroyed, including calling the Activity's onDestroy method. Fixes: #67 --- android-activity/CHANGELOG.md | 2 + android-activity/src/game_activity/mod.rs | 75 ++++++++++++-------- android-activity/src/native_activity/glue.rs | 37 +++++++--- android-activity/src/util.rs | 40 +++++------ 4 files changed, 96 insertions(+), 58 deletions(-) diff --git a/android-activity/CHANGELOG.md b/android-activity/CHANGELOG.md index 6ddb0e1..6124643 100644 --- a/android-activity/CHANGELOG.md +++ b/android-activity/CHANGELOG.md @@ -3,6 +3,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +### Changed +- The `Activity.finish()` method is now called when `android_main` returns so the `Activity` will be destroyed ([#67](https://github.com/rust-mobile/android-activity/issues/67)) ## [0.4.1] - 2022-02-16 ### Added diff --git a/android-activity/src/game_activity/mod.rs b/android-activity/src/game_activity/mod.rs index 82bc3e3..d43dc6e 100644 --- a/android-activity/src/game_activity/mod.rs +++ b/android-activity/src/game_activity/mod.rs @@ -6,6 +6,7 @@ use std::io::{BufRead, BufReader}; use std::marker::PhantomData; use std::ops::Deref; use std::os::unix::prelude::*; +use std::panic::catch_unwind; use std::ptr::NonNull; use std::sync::{Arc, RwLock}; use std::time::Duration; @@ -23,7 +24,7 @@ use ndk::asset::AssetManager; use ndk::configuration::Configuration; use ndk::native_window::NativeWindow; -use crate::util::{abort_on_panic, android_log}; +use crate::util::{abort_on_panic, android_log, log_panic}; use crate::{ util, AndroidApp, ConfigurationRef, InputStatus, MainEvent, PollEvent, Rect, WindowManagerFlags, }; @@ -603,7 +604,8 @@ extern "Rust" { // `app_main` function. This is run on a dedicated thread spawned // by android_native_app_glue. #[no_mangle] -pub unsafe extern "C" fn _rust_glue_entry(app: *mut ffi::android_app) { +#[allow(unused_unsafe)] // Otherwise rust 1.64 moans about using unsafe{} in unsafe functions +pub unsafe extern "C" fn _rust_glue_entry(native_app: *mut ffi::android_app) { abort_on_panic(|| { // Maybe make this stdout/stderr redirection an optional / opt-in feature?... let mut logpipe: [RawFd; 2] = Default::default(); @@ -627,34 +629,51 @@ pub unsafe extern "C" fn _rust_glue_entry(app: *mut ffi::android_app) { } }); - let jvm: *mut JavaVM = (*(*app).activity).vm; - let activity: jobject = (*(*app).activity).javaGameActivity; - ndk_context::initialize_android_context(jvm.cast(), activity.cast()); + let jvm = unsafe { + let jvm = (*(*native_app).activity).vm; + let activity: jobject = (*(*native_app).activity).javaGameActivity; + ndk_context::initialize_android_context(jvm.cast(), activity.cast()); - let app = AndroidApp::from_ptr(NonNull::new(app).unwrap()); + // 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()); + } - // 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 + }; + + unsafe { + let app = AndroidApp::from_ptr(NonNull::new(native_app).unwrap()); + + // We want to specifically catch any panic from the application's android_main + // so we can finish + destroy the Activity gracefully via the JVM + catch_unwind(|| { + // 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); + }) + .unwrap_or_else(|panic| log_panic(panic)); + + // Let JVM know that our Activity can be destroyed before detaching from the JVM + // + // "Note that this method can be called from any thread; it will send a message + // to the main thread of the process where the Java finish call will take place" + ffi::GameActivity_finish((*native_app).activity); + + if let Some(detach_current_thread) = (*(*jvm)).DetachCurrentThread { + detach_current_thread(jvm); + } + + ndk_context::release_android_context(); } - - // 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); - - if let Some(detach_current_thread) = (*(*jvm)).DetachCurrentThread { - detach_current_thread(jvm); - } - - ndk_context::release_android_context(); }) } diff --git a/android-activity/src/native_activity/glue.rs b/android-activity/src/native_activity/glue.rs index 6af66af..64df108 100644 --- a/android-activity/src/native_activity/glue.rs +++ b/android-activity/src/native_activity/glue.rs @@ -8,6 +8,7 @@ use std::{ io::{BufRead, BufReader}, ops::Deref, os::unix::prelude::{FromRawFd, RawFd}, + panic::catch_unwind, ptr::{self, NonNull}, sync::{Arc, Condvar, Mutex, Weak}, }; @@ -15,7 +16,11 @@ use std::{ use log::Level; use ndk::{configuration::Configuration, input_queue::InputQueue, native_window::NativeWindow}; -use crate::{util::abort_on_panic, util::android_log, ConfigurationRef}; +use crate::{ + util::android_log, + util::{abort_on_panic, log_panic}, + ConfigurationRef, +}; use super::{AndroidApp, Rect}; @@ -803,6 +808,7 @@ unsafe extern "C" fn on_content_rect_changed( /// This is the native entrypoint for our cdylib library that `ANativeActivity` will look for via `dlsym` #[no_mangle] +#[allow(unused_unsafe)] // Otherwise rust 1.64 moans about using unsafe{} in unsafe functions extern "C" fn ANativeActivity_onCreate( activity: *mut ndk_sys::ANativeActivity, saved_state: *const libc::c_void, @@ -874,15 +880,26 @@ extern "C" fn ANativeActivity_onCreate( 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); + // We want to specifically catch any panic from the application's android_main + // so we can finish + destroy the Activity gracefully via the JVM + catch_unwind(|| { + // 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); + }) + .unwrap_or_else(|panic| log_panic(panic)); + + // Let JVM know that our Activity can be destroyed before detaching from the JVM + // + // "Note that this method can be called from any thread; it will send a message + // to the main thread of the process where the Java finish call will take place" + ndk_sys::ANativeActivity_finish(activity); if let Some(detach_current_thread) = (*(*jvm)).DetachCurrentThread { detach_current_thread(jvm); diff --git a/android-activity/src/util.rs b/android-activity/src/util.rs index c115328..6e04758 100644 --- a/android-activity/src/util.rs +++ b/android-activity/src/util.rs @@ -31,6 +31,25 @@ pub(crate) fn android_log(level: Level, tag: &CStr, msg: &CStr) { } } +pub(crate) fn log_panic(panic: Box) { + let rust_panic = unsafe { CStr::from_bytes_with_nul_unchecked(b"RustPanic\0") }; + + if let Some(panic) = panic.downcast_ref::() { + if let Ok(msg) = CString::new(panic.clone()) { + android_log(Level::Error, rust_panic, &msg); + } + } else if let Ok(panic) = panic.downcast::<&str>() { + if let Ok(msg) = CString::new(*panic) { + android_log(Level::Error, rust_panic, &msg); + } + } else { + let unknown_panic = unsafe { CStr::from_bytes_with_nul_unchecked(b"UnknownPanic\0") }; + android_log(Level::Error, unknown_panic, unsafe { + CStr::from_bytes_with_nul_unchecked(b"\0") + }); + } +} + /// Run a closure and abort the program if it panics. /// /// This is generally used to ensure Rust callbacks won't unwind past the JNI boundary, which leads @@ -45,26 +64,7 @@ pub(crate) fn abort_on_panic(f: impl FnOnce() -> R) -> R { // // Just in case our attempt to log a panic could itself cause a panic we use a // second catch_unwind here. - let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { - // Try logging the panic, but abort if that fails. - let rust_panic = unsafe { CStr::from_bytes_with_nul_unchecked(b"RustPanic\0") }; - - if let Some(panic) = panic.downcast_ref::() { - if let Ok(msg) = CString::new(panic.clone()) { - android_log(Level::Error, rust_panic, &msg); - } - } else if let Ok(panic) = panic.downcast::<&str>() { - if let Ok(msg) = CString::new(*panic) { - android_log(Level::Error, rust_panic, &msg); - } - } else { - let unknown_panic = - unsafe { CStr::from_bytes_with_nul_unchecked(b"UnknownPanic\0") }; - android_log(Level::Error, unknown_panic, unsafe { - CStr::from_bytes_with_nul_unchecked(b"\0") - }); - } - })); + let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| log_panic(panic))); std::process::abort(); }) } From 4a4efd871a62a1519ea628c599a116a321b0babc Mon Sep 17 00:00:00 2001 From: Marijn Suijten Date: Fri, 16 Jun 2023 17:35:53 +0200 Subject: [PATCH 15/19] README: Add badges to CI, crates.io, docs.rs and show the MSRV I was trying to quickly get to the documentation of this crate and had the GitHub page open... but there was no link on the front-page: let's fix that. --- README.md | 39 +++++++++++++++++++++-------------- android-activity/src/input.rs | 2 +- 2 files changed, 24 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index b2d0dc0..7c49732 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,11 @@ -# Overview +# `android-activity` + +[![ci](https://github.com/rust-mobile/android-activity/actions/workflows/ci.yml/badge.svg)](https://github.com/rust-mobile/android-activity/actions/workflows/ci.yml) +[![crates.io](https://img.shields.io/crates/v/android-activity.svg)](https://crates.io/crates/android-activity) +[![Docs](https://docs.rs/android-activity/badge.svg)](https://docs.rs/android-activity) +[![MSRV](https://img.shields.io/badge/rustc-1.64.0+-ab6000.svg)](https://blog.rust-lang.org/2022/09/22/Rust-1.64.0.html) + +## Overview `android-activity` provides a "glue" layer for building native Rust applications on Android, supporting multiple [`Activity`] base classes. @@ -22,10 +29,11 @@ applications. [ndk-glue]: https://crates.io/crates/ndk-glue [agdk]: https://developer.android.com/games/agdk -# Example +## Example Cargo.toml -``` + +```toml [dependencies] log = "0.4" android_logger = "0.11" @@ -38,6 +46,7 @@ crate_type = ["cdylib"] _Note: that you will need to either specify the **"native-activity"** feature or **"game-activity"** feature to identify which `Activity` base class your application is based on_ lib.rs + ```rust use android_activity::{AndroidApp, InputStatus, MainEvent, PollEvent}; @@ -69,14 +78,14 @@ fn android_main(app: AndroidApp) { } ``` -``` +```sh rustup target add aarch64-linux-android cargo install cargo-apk cargo apk run adb logcat example:V *:S ``` -# Full Examples +## Full Examples See [this collection of examples](https://github.com/rust-mobile/rust-android-examples) (based on both `GameActivity` and `NativeActivity`). @@ -84,7 +93,7 @@ Each example is a standalone project that may also be a convenient templates for For the examples based on middleware frameworks (Winit and or Egui) they also aim to demonstrate how it's possible to write portable code that will run on Android and other systems. -# Should I use NativeActivity or GameActivity? +## Should I use NativeActivity or GameActivity? To learn more about the `NativeActivity` class that's shipped with Android see [here](https://developer.android.com/ndk/guides/concepts#naa). @@ -96,22 +105,23 @@ It's expected that the `GameActivity` backend will gain more sophisticated input Even if you start out using `NativeActivity` for the convenience, it's likely that most moderately complex applications will eventually need to define their own `Activity` subclass (either subclassing `NativeActivity` or `GameActivity`) which will require compiling at least a small amount of Java or Kotlin code. This is generally due to Android's design which directs numerous events via the `Activity` class which can only be processed by overloading some associated Activity method. -# Switching from ndk-glue to android-activity +## Switching from ndk-glue to android-activity + +### Winit-based applications -## Winit-based applications Firstly; if you have a [Winit](https://crates.io/crates/winit) based application and also have an explicit dependency on `ndk-glue` your application will need to remove its dependency on `ndk-glue` for the 0.28 release of Winit which will be based on android-activity (Since glue crates, due to their nature, can't be compatible with alternative glue crates). Winit-based applications can follow the [Android README](https://github.com/rust-windowing/winit#android) guidance for advice on how to switch over. Most Winit-based applications should aim to remove any explicit dependency on a specific glue crate (so not depend directly on `ndk-glue` or `android-activity` and instead rely on Winit to pull in the right glue crate). The main practical change will then be to add a `#[no_mangle]fn android_main(app: AndroidApp)` entry point. See the [Android README](https://github.com/rust-windowing/winit#android) for more details and also see the [Winit-based examples here](https://github.com/rust-mobile/rust-android-examples). -## Middleware crates (i.e. not applications) +### Middleware crates (i.e. not applications) If you have a crate that would be considered a middleware library (for example using JNI to support access to Bluetooth, or Android's Camera APIs) then the crate should almost certainly remove any dependence on a specific glue crate because this imposes a strict compatibility constraint that means the crate can only be used by applications that use that exact same glue crate version. Middleware libraries can instead look at using the [ndk-context](https://crates.io/crates/ndk-context) crate as a means for being able to use JNI without making any assumptions about the applications overall architecture. This way a middleware crate can work with alternative glue crates (including `ndk-glue` and `android-activity`) as well as work with embedded use cases (i.e. non-native applications that may just embed a dynamic library written in Rust to implement certain native functions). -## Other, non-Winit-based applications +### Other, non-Winit-based applications The steps to switch a simple standalone application over from `ndk-glue` to `android-activity` (still based on `NativeActivity`) should be: @@ -120,7 +130,7 @@ The steps to switch a simple standalone application over from `ndk-glue` to `and 3. Optionally add a dependency on `android_logger = "0.11.0"` 4. Update the `main` entry point to look like this: -``` +```rust use android_activity::AndroidApp; #[no_mangle] @@ -133,8 +143,7 @@ See this minimal [NativeActivity Mainloop](https://github.com/rust-mobile/androi There is is no `#[ndk_glue::main]` replacement considering that `android_main()` entry point needs to be passed an `AndroidApp` argument which isn't compatible with a traditional `main()` function. Having an Android specific entry point also gives a place to initialize Android logging and handle other Android specific details (such as building an event loop based on the `app` argument) - -## Design Summary / Motivation behind android-activity +### Design Summary / Motivation behind android-activity Prior to working on android-activity, the existing glue crates available for building standalone Rust applications on Android were found to have a number of technical limitations that this crate aimed to solve: @@ -142,12 +151,10 @@ Prior to working on android-activity, the existing glue crates available for bui 2. **Encapsulate IPC + synchronization between the native thread and the JVM thread**: For example with `ndk-glue` the application itself needs to avoid race conditions between the native and Java thread by following a locking convention) and it wasn't clear how this would extend to support other requests (like state saving) that also require synchronization. 3. **Avoid static global state**: Keeping in mind the possibility of supporting applications with multiple native activities there was interest in having an API that didn't rely on global statics to track top-level state. Instead of having global getters for state then `android-activity` passes an explicit `app: AndroidApp` argument to the entry point that encapsulates the state connected with a single `Activity`. - [`GameTextInput`]: https://developer.android.com/games/agdk/add-support-for-text-input [`AppCompatActivity`]: https://developer.android.com/reference/androidx/appcompat/app/AppCompatActivity - -# MSRV +## MSRV We aim to (at least) support stable releases of Rust from the last three months. Rust has a 6 week release cycle which means we will support the last three stable releases. For example, when Rust 1.69 is released we would limit our `rust_version` to 1.67. diff --git a/android-activity/src/input.rs b/android-activity/src/input.rs index 3653260..f28d1a9 100644 --- a/android-activity/src/input.rs +++ b/android-activity/src/input.rs @@ -63,7 +63,7 @@ pub enum Class { impl From for Class { fn from(source: u32) -> Self { - let class = SourceFlags::from_bits_truncate(source as u32); + let class = SourceFlags::from_bits_truncate(source); match class { SourceFlags::BUTTON => Class::Button, SourceFlags::POINTER => Class::Pointer, From ca0d2eb3aa6e91116b00b5a8dd9be3fb92cfade0 Mon Sep 17 00:00:00 2001 From: Marijn Suijten Date: Sat, 24 Jun 2023 00:43:14 +0200 Subject: [PATCH 16/19] cargo: Fix `rust_version` -> `rust-version` property typo Cargo complains: warning: android-activity/Cargo.toml: unused manifest key: package.rust_version Solve this by replacing the underscore with a hyphen. --- android-activity/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android-activity/Cargo.toml b/android-activity/Cargo.toml index 764c8e6..db3f4ff 100644 --- a/android-activity/Cargo.toml +++ b/android-activity/Cargo.toml @@ -9,7 +9,7 @@ repository = "https://github.com/rust-mobile/android-activity" documentation = "https://docs.rs/android-activity" description = "Glue for building Rust applications on Android with NativeActivity or GameActivity" license = "MIT OR Apache-2.0" -rust_version = "1.64" +rust-version = "1.64" [features] # Note: we don't enable any backend by default since features From cd814206385639e433ed1d43ec0f67e28d434139 Mon Sep 17 00:00:00 2001 From: Robert Bragg Date: Sun, 25 Jun 2023 23:45:32 +0100 Subject: [PATCH 17/19] Update agdk-mainloop --- examples/agdk-mainloop/Cargo.toml | 2 ++ examples/agdk-mainloop/app/build.gradle | 2 ++ .../app/src/main/AndroidManifest.xml | 3 +- examples/agdk-mainloop/build.gradle | 4 +-- examples/agdk-mainloop/gradle.properties | 4 ++- .../gradle/wrapper/gradle-wrapper.properties | 2 +- examples/agdk-mainloop/src/lib.rs | 31 ++++++++++++++++--- 7 files changed, 38 insertions(+), 10 deletions(-) diff --git a/examples/agdk-mainloop/Cargo.toml b/examples/agdk-mainloop/Cargo.toml index 47d30a3..6971468 100644 --- a/examples/agdk-mainloop/Cargo.toml +++ b/examples/agdk-mainloop/Cargo.toml @@ -9,6 +9,8 @@ edition = "2021" log = "0.4" android_logger = "0.11.0" android-activity = { path="../../android-activity", features = ["game-activity"] } +ndk-sys = "0.4" +ndk = "0.7" [lib] name="main" diff --git a/examples/agdk-mainloop/app/build.gradle b/examples/agdk-mainloop/app/build.gradle index 77c1a85..cce660e 100644 --- a/examples/agdk-mainloop/app/build.gradle +++ b/examples/agdk-mainloop/app/build.gradle @@ -3,6 +3,7 @@ plugins { } android { + ndkVersion "25.2.9519653" compileSdk 31 defaultConfig { @@ -32,6 +33,7 @@ android { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } + namespace 'co.realfit.agdkmainloop' } dependencies { diff --git a/examples/agdk-mainloop/app/src/main/AndroidManifest.xml b/examples/agdk-mainloop/app/src/main/AndroidManifest.xml index 7b99a0b..b9d7563 100644 --- a/examples/agdk-mainloop/app/src/main/AndroidManifest.xml +++ b/examples/agdk-mainloop/app/src/main/AndroidManifest.xml @@ -1,6 +1,5 @@ - + = Default::default(); + let mut native_window: Option = None; while !quit { app.poll_events( @@ -37,11 +37,11 @@ fn android_main(app: AndroidApp) { } } MainEvent::InitWindow { .. } => { - render_state = Some(()); + native_window = app.native_window(); redraw_pending = true; } MainEvent::TerminateWindow { .. } => { - render_state = None; + native_window = None; } MainEvent::WindowResized { .. } => { redraw_pending = true; @@ -65,7 +65,7 @@ fn android_main(app: AndroidApp) { } if redraw_pending { - if let Some(_rs) = render_state { + if let Some(native_window) = &native_window { redraw_pending = false; // Handle input @@ -75,9 +75,32 @@ fn android_main(app: AndroidApp) { }); info!("Render..."); + dummy_render(native_window); } } }, ); } } + +/// Post a NOP frame to the window +/// +/// Since this is a bare minimum test app we don't depend +/// on any GPU graphics APIs but we do need to at least +/// convince Android that we're drawing something and are +/// responsive, otherwise it will stop delivering input +/// events to us. +fn dummy_render(native_window: &ndk::native_window::NativeWindow) { + unsafe { + let mut buf: ndk_sys::ANativeWindow_Buffer = std::mem::zeroed(); + let mut rect: ndk_sys::ARect = std::mem::zeroed(); + ndk_sys::ANativeWindow_lock( + native_window.ptr().as_ptr() as _, + &mut buf as _, + &mut rect as _, + ); + // Note: we don't try and touch the buffer since that + // also requires us to handle various buffer formats + ndk_sys::ANativeWindow_unlockAndPost(native_window.ptr().as_ptr() as _); + } +} From 230035526b5127b93c2d120c5b7f5aa8418852b3 Mon Sep 17 00:00:00 2001 From: Robert Bragg Date: Sun, 25 Jun 2023 23:45:42 +0100 Subject: [PATCH 18/19] Update na-mainloop --- examples/na-mainloop/Cargo.toml | 2 ++ examples/na-mainloop/app/build.gradle | 2 ++ .../app/src/main/AndroidManifest.xml | 3 +- examples/na-mainloop/build.gradle | 4 +-- examples/na-mainloop/gradle.properties | 4 ++- .../gradle/wrapper/gradle-wrapper.properties | 2 +- examples/na-mainloop/src/lib.rs | 31 ++++++++++++++++--- 7 files changed, 38 insertions(+), 10 deletions(-) diff --git a/examples/na-mainloop/Cargo.toml b/examples/na-mainloop/Cargo.toml index 6280913..93da1d9 100644 --- a/examples/na-mainloop/Cargo.toml +++ b/examples/na-mainloop/Cargo.toml @@ -9,6 +9,8 @@ edition = "2021" log = "0.4" android_logger = "0.11.0" android-activity = { path="../../android-activity", features = [ "native-activity" ] } +ndk-sys = "0.4" +ndk = "0.7" [lib] #name="na_mainloop" diff --git a/examples/na-mainloop/app/build.gradle b/examples/na-mainloop/app/build.gradle index c17a5d3..dd102cc 100644 --- a/examples/na-mainloop/app/build.gradle +++ b/examples/na-mainloop/app/build.gradle @@ -3,6 +3,7 @@ plugins { } android { + ndkVersion "25.2.9519653" compileSdk 31 defaultConfig { @@ -32,6 +33,7 @@ android { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } + namespace 'co.realfit.namainloop' } dependencies { diff --git a/examples/na-mainloop/app/src/main/AndroidManifest.xml b/examples/na-mainloop/app/src/main/AndroidManifest.xml index 56fe639..d104641 100644 --- a/examples/na-mainloop/app/src/main/AndroidManifest.xml +++ b/examples/na-mainloop/app/src/main/AndroidManifest.xml @@ -1,6 +1,5 @@ - + = Default::default(); + let mut native_window: Option = None; while !quit { app.poll_events( @@ -37,11 +37,11 @@ fn android_main(app: AndroidApp) { } } MainEvent::InitWindow { .. } => { - render_state = Some(()); + native_window = app.native_window(); redraw_pending = true; } MainEvent::TerminateWindow { .. } => { - render_state = None; + native_window = None; } MainEvent::WindowResized { .. } => { redraw_pending = true; @@ -65,7 +65,7 @@ fn android_main(app: AndroidApp) { } if redraw_pending { - if let Some(_rs) = render_state { + if let Some(native_window) = &native_window { redraw_pending = false; // Handle input @@ -75,9 +75,32 @@ fn android_main(app: AndroidApp) { }); info!("Render..."); + dummy_render(native_window); } } }, ); } } + +/// Post a NOP frame to the window +/// +/// Since this is a bare minimum test app we don't depend +/// on any GPU graphics APIs but we do need to at least +/// convince Android that we're drawing something and are +/// responsive, otherwise it will stop delivering input +/// events to us. +fn dummy_render(native_window: &ndk::native_window::NativeWindow) { + unsafe { + let mut buf: ndk_sys::ANativeWindow_Buffer = std::mem::zeroed(); + let mut rect: ndk_sys::ARect = std::mem::zeroed(); + ndk_sys::ANativeWindow_lock( + native_window.ptr().as_ptr() as _, + &mut buf as _, + &mut rect as _, + ); + // Note: we don't try and touch the buffer since that + // also requires us to handle various buffer formats + ndk_sys::ANativeWindow_unlockAndPost(native_window.ptr().as_ptr() as _); + } +} From 9a713c823d0ff8fc70cee722ffed909f65543ad6 Mon Sep 17 00:00:00 2001 From: Robert Bragg Date: Sun, 25 Jun 2023 23:46:32 +0100 Subject: [PATCH 19/19] Release 0.4.2 --- android-activity/CHANGELOG.md | 6 ++++++ android-activity/Cargo.toml | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/android-activity/CHANGELOG.md b/android-activity/CHANGELOG.md index 6124643..ca4be1f 100644 --- a/android-activity/CHANGELOG.md +++ b/android-activity/CHANGELOG.md @@ -3,8 +3,14 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] + + +## [0.4.2] - 2022-02-16 ### Changed - The `Activity.finish()` method is now called when `android_main` returns so the `Activity` will be destroyed ([#67](https://github.com/rust-mobile/android-activity/issues/67)) +- The `native-activity` backend now propagates `NativeWindow` redraw/resize and `ContentRectChanged` callbacks to main loop ([#70](https://github.com/rust-mobile/android-activity/pull/70)) +- The `game-activity` implementation of `pointer_index()` was fixed to not always return `0` ([#80](https://github.com/rust-mobile/android-activity/pull/84)) +- Added `panic` guards around application's `android_main()` and native code that could potentially unwind across a Java FFI boundary ([#68](https://github.com/rust-mobile/android-activity/pull/68)) ## [0.4.1] - 2022-02-16 ### Added diff --git a/android-activity/Cargo.toml b/android-activity/Cargo.toml index db3f4ff..ed6a1b0 100644 --- a/android-activity/Cargo.toml +++ b/android-activity/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "android-activity" -version = "0.4.1" +version = "0.4.2" edition = "2021" keywords = ["android", "ndk"] readme = "../README.md"