From cbffca916d0b96278c1f1f8c4a6b4462a602686f Mon Sep 17 00:00:00 2001 From: Robert Bragg Date: Mon, 23 Mar 2026 23:12:50 +0000 Subject: [PATCH 1/2] Clippy lint fixes --- android-activity/src/game_activity/mod.rs | 19 ++++++++++++++----- android-activity/src/init.rs | 4 ++-- android-activity/src/input/sdk.rs | 15 ++++++--------- android-activity/src/lib.rs | 8 ++++---- android-activity/src/native_activity/mod.rs | 2 +- android-activity/src/util.rs | 7 ++----- 6 files changed, 29 insertions(+), 26 deletions(-) diff --git a/android-activity/src/game_activity/mod.rs b/android-activity/src/game_activity/mod.rs index c2532746..6233335a 100644 --- a/android-activity/src/game_activity/mod.rs +++ b/android-activity/src/game_activity/mod.rs @@ -174,19 +174,28 @@ impl AndroidApp { } } +// Wrapper around the raw android_app pointer that can be safely sent across threads. +// SAFETY: The android_app pointer is managed by the GameActivity glue code and protected +// by a Mutex. Access is synchronized and the pointer is cleared on APP_CMD_DESTROY. +// The Mutex wrapper provides Sync, so we only need to implement Send. +#[derive(Debug)] +struct SendAndroidApp(*mut ffi::android_app); + +unsafe impl Send for SendAndroidApp {} + #[derive(Debug, Clone)] struct GameActivityGlue { - game_activity_app: Arc>, + game_activity_app: Arc>, } impl GameActivityGlue { fn new(game_activity_app: *mut ffi::android_app) -> Self { Self { - game_activity_app: Arc::new(Mutex::new(game_activity_app)), + game_activity_app: Arc::new(Mutex::new(SendAndroidApp(game_activity_app))), } } - fn locked_app(&self) -> std::sync::MutexGuard<'_, *mut ffi::android_app> { + fn locked_app(&self) -> std::sync::MutexGuard<'_, SendAndroidApp> { self.game_activity_app.lock().unwrap() } @@ -203,7 +212,7 @@ impl GameActivityGlue { F: FnOnce(*mut ffi::android_app) -> R, { let app = self.locked_app(); - f(*app) + f(app.0) } /// Called when handling the `APP_CMD_DESTROY` event to clear our retained @@ -211,7 +220,7 @@ impl GameActivityGlue { /// accidentally access it after it's been freed. fn clear_app(&self) { let mut app = self.locked_app(); - *app = ptr::null_mut(); + app.0 = ptr::null_mut(); } } diff --git a/android-activity/src/init.rs b/android-activity/src/init.rs index 3995ee0a..508e622f 100644 --- a/android-activity/src/init.rs +++ b/android-activity/src/init.rs @@ -33,7 +33,7 @@ fn forward_stdio_to_logcat() -> std::thread::JoinHandle> { std::thread::Builder::new() .name("stdio-to-logcat".to_string()) .spawn(move || -> std::io::Result<()> { - let tag = CStr::from_bytes_with_nul(b"RustStdoutStderr\0").unwrap(); + let tag = c"RustStdoutStderr"; let mut reader = BufReader::new(file); let mut buffer = String::new(); loop { @@ -258,7 +258,7 @@ fn try_init_current_thread(env: &mut jni::Env, activity: &JObject) -> jni::error // Also name native thread - this needs to happen here after attaching to a JVM thread, // since that changes the thread name to something like "Thread-2". unsafe { - let thread_name = std::ffi::CStr::from_bytes_with_nul(b"android_main\0").unwrap(); + let thread_name = c"android_main"; let _ = libc::pthread_setname_np(libc::pthread_self(), thread_name.as_ptr()); } Ok(()) diff --git a/android-activity/src/input/sdk.rs b/android-activity/src/input/sdk.rs index dbe10f9e..0bea028a 100644 --- a/android-activity/src/input/sdk.rs +++ b/android-activity/src/input/sdk.rs @@ -78,9 +78,9 @@ jni::bind_java_type! { } impl AKeyCharacterMap<'_> { - pub(crate) fn get<'local>( + pub(crate) fn get( &self, - env: &'local mut jni::Env, + env: &mut jni::Env, key_code: jint, meta_state: jint, ) -> Result { @@ -97,10 +97,7 @@ impl AKeyCharacterMap<'_> { .map_err(|err| jni_utils::clear_and_map_exception_to_err(env, err)) } - pub(crate) fn get_keyboard_type<'local>( - &self, - env: &'local mut jni::Env, - ) -> Result { + pub(crate) fn get_keyboard_type(&self, env: &mut jni::Env) -> Result { self._get_keyboard_type(env) .map_err(|err| jni_utils::clear_and_map_exception_to_err(env, err)) } @@ -183,7 +180,7 @@ impl KeyCharacterMap { } }) .map_err(|err| { - let err: InternalAppError = err.into(); + let err: InternalAppError = err; err.into() }) } @@ -217,7 +214,7 @@ impl KeyCharacterMap { }) }) .map_err(|err| { - let err: InternalAppError = err.into(); + let err: InternalAppError = err; err.into() }) } @@ -239,7 +236,7 @@ impl KeyCharacterMap { Ok(keyboard_type.into()) }) .map_err(|err| { - let err: InternalAppError = err.into(); + let err: InternalAppError = err; err.into() }) } diff --git a/android-activity/src/lib.rs b/android-activity/src/lib.rs index 589454de..64fac0d5 100644 --- a/android-activity/src/lib.rs +++ b/android-activity/src/lib.rs @@ -206,9 +206,9 @@ //! //! Before `android_main()` is called: //! - A `JavaVM` and -//! [`android.content.Context`](https://developer.android.com/reference/android/content/Context) -//! instance will be associated with the [`ndk_context`] crate so that other, -//! independent, Rust crates are able to find a JavaVM for making JNI calls. +//! [`android.content.Context`](https://developer.android.com/reference/android/content/Context) +//! instance will be associated with the [`ndk_context`] crate so that other, +//! independent, Rust crates are able to find a JavaVM for making JNI calls. //! - The `JavaVM` will be attached to the native thread (for JNI) //! - A [Looper] is attached to the Rust native thread. //! @@ -1358,7 +1358,7 @@ impl<'a> OnCreateState<'a> { /// - Don't wrap the reference in an `Auto` which would treat the reference /// like a local reference and try to delete it when dropped. pub fn activity_as_ptr(&self) -> *mut c_void { - self.java_activity as *mut c_void + self.java_activity } /// Returns the saved state of the `Activity` as a byte slice, which may be diff --git a/android-activity/src/native_activity/mod.rs b/android-activity/src/native_activity/mod.rs index 6ab89d57..5634f117 100644 --- a/android-activity/src/native_activity/mod.rs +++ b/android-activity/src/native_activity/mod.rs @@ -351,7 +351,7 @@ impl AndroidAppInner { return; } - let na_mut = na as *mut ndk_sys::ANativeActivity; + let na_mut = na; unsafe { ndk_sys::ANativeActivity_setWindowFlags( na_mut.cast(), diff --git a/android-activity/src/util.rs b/android-activity/src/util.rs index 6e047584..1345d4f6 100644 --- a/android-activity/src/util.rs +++ b/android-activity/src/util.rs @@ -32,7 +32,7 @@ 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") }; + let rust_panic = c"RustPanic"; if let Some(panic) = panic.downcast_ref::() { if let Ok(msg) = CString::new(panic.clone()) { @@ -43,10 +43,7 @@ pub(crate) fn log_panic(panic: Box) { 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") - }); + android_log(Level::Error, rust_panic, c"UnknownPanic"); } } From 8b8cf2ba5cda6d6a8f1643e5f086102869f7cf30 Mon Sep 17 00:00:00 2001 From: Robert Bragg Date: Mon, 23 Mar 2026 15:58:59 +0000 Subject: [PATCH 2/2] Release 0.6.1 --- android-activity/CHANGELOG.md | 35 ++++++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/android-activity/CHANGELOG.md b/android-activity/CHANGELOG.md index 2a1a8ede..5d8795d1 100644 --- a/android-activity/CHANGELOG.md +++ b/android-activity/CHANGELOG.md @@ -6,6 +6,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.6.1] - 2026-03-30 + ### Added - input: `TextInputAction` enum representing action button types on soft keyboards. ([#216](https://github.com/rust-mobile/android-activity/pull/216)) @@ -57,6 +59,10 @@ fn android_on_create(state: &OnCreateState) { - rust-version bumped to 1.85.0 ([#193](https://github.com/rust-mobile/android-activity/pull/193), [#219](https://github.com/rust-mobile/android-activity/pull/219)) - GameActivity updated to 4.4.0 ([#191](https://github.com/rust-mobile/android-activity/pull/191), [#240](https://github.com/rust-mobile/android-activity/pull/240)) +- `ndk-context` is initialized with an `Application` context instead of an `Activity` context ([#229](https://github.com/rust-mobile/android-activity/pull/229)) + + +#### GameActivity 4.4.0 Update **Important:** This release is no longer compatible with GameActivity 2.0.2 @@ -82,6 +88,30 @@ Note: there is no guarantee that later 4.x.x releases of GameActivity will be co `android-activity`, so please refer to the `android-activity` release notes for any future updates regarding GameActivity compatibility. +#### Initializing `ndk-context` with an Application Context + +`ndk-context` is a separate, framework-independent crate that provides a way for library crates to access a Java VM pointer and an `android.content.Context` JNI reference without needing to depend on `android-activity` directly. + +`ndk-context` may be initialized by various framework crates, including `android-activity`, on behalf of library crates. + +Historically `android-activity` has initialized `ndk-context` with an `Activity` context since that was the simplest choice considering that the entrypoint for `android-activity` comes from an `Activity` `onCreate` callback. + +However, in retrospect it was realized that this was a short-sighted mistake when considering that: +1. `ndk-context` only provides a single, global context reference for the entire application that can't be updated +2. An Android application can have multiple `Activity` instances over its lifetime (and at times could have no `Activity` instances at all, e.g. if the app is running a background `Service`) +3. Whatever is put into `ndk-context` needs to leak a corresponding global reference to ensure it remains valid to access safely. This is inappropriate for an `Activity` reference since it can be destroyed and recreated multiple times over the lifetime of the application. + +A far better choice, that aligns with the global nature of the `ndk-context` API is to initialize it with an `Application` context which is valid for the entire lifetime of the application. + +**Note:** Although the `ndk-context` API only promises to provide an `android.content.Context` _and_ specifically warns that user's should not assume the context is an `Activity`, there is still some risk that some users of `ndk-context` could be affected by the change made in [#229](https://github.com/rust-mobile/android-activity/pull/229). + +For example, until recently the `webbrowser` crate (for opening URLs) was assuming it could access an `Activity` context via `ndk-context`. In preparation for making this change, `webbrowser` was updated to ensure it is agnostic to the type of context provided by `ndk-context`, see: + +Other notable library crates that support Android (such as `app_dirs2`) are expected to be unaffected by this, since they already operate in terms of the `android.content.Context` API, not the `Activity` API. + +_**Note:**: if some crate really needs an `Activity` reference then they should ideally be able to get one via the +`AndroidApp::activity_as_ptr()` API. They may need to add some Android-specific initialization API, similar to how Winit has a dedicated `.with_android_app()` API. An Android-specific init API that could accept a JNI reference to an `Activity` could avoid needing to specifically depend on `android-activity`. + ### Fixed - *Safety* `AndroidApp::asset_manager()` returns an `AssetManager` that has a safe `'static` lifetime that's not invalidated when `android_main()` returns ([#233](https://github.com/rust-mobile/android-activity/pull/233)) @@ -90,6 +120,8 @@ GameActivity compatibility. - *Safety* The `game-activity` backend clears its `android_app` ptr after `onDestroy` and `AndroidApp` remains safe to access after `android_main()` returns ([#236](https://github.com/rust-mobile/android-activity/pull/236)) - Support for `AndroidApp::show/hide_soft_input()` APIs in the `native-activity` backend ([#178](https://github.com/rust-mobile/android-activity/pull/178)) +Overall, some effort was made to ensure that `android-activity` can gracefully and safely handle cases where the `Activity` gets repeatedly created, destroyed and recreated (e.g due to configuration changes). Theoretically it should even be possible to run multiple `Activity` instances (although that's not really something that `NativeActivity` or `GameActivity` are designed for). + ## [0.6.0] - 2024-04-26 ### Changed @@ -303,7 +335,8 @@ GameActivity compatibility. ### Added - Initial release -[unreleased]: https://github.com/rust-mobile/android-activity/compare/v0.6.0...HEAD +[unreleased]: https://github.com/rust-mobile/android-activity/compare/v0.6.1...HEAD +[0.6.1]: https://github.com/rust-mobile/android-activity/compare/v0.6.0...v0.6.1 [0.6.0]: https://github.com/rust-mobile/android-activity/compare/v0.5.2...v0.6.0 [0.5.2]: https://github.com/rust-mobile/android-activity/compare/v0.5.1...v0.5.2 [0.5.1]: https://github.com/rust-mobile/android-activity/compare/v0.5.0...v0.5.1