Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 34 additions & 1 deletion android-activity/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down Expand Up @@ -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

Expand All @@ -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: <https://github.com/amodm/webbrowser-rs/pull/111>

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))
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down
19 changes: 14 additions & 5 deletions android-activity/src/game_activity/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Mutex<*mut ffi::android_app>>,
game_activity_app: Arc<Mutex<SendAndroidApp>>,
}

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()
}

Expand All @@ -203,15 +212,15 @@ 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
/// pointer to the GameActivity `android_app` glue so that we don't
/// 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();
}
}

Expand Down
4 changes: 2 additions & 2 deletions android-activity/src/init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ fn forward_stdio_to_logcat() -> std::thread::JoinHandle<std::io::Result<()>> {
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 {
Expand Down Expand Up @@ -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(())
Expand Down
15 changes: 6 additions & 9 deletions android-activity/src/input/sdk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<jint, InternalAppError> {
Expand All @@ -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<jint, InternalAppError> {
pub(crate) fn get_keyboard_type(&self, env: &mut jni::Env) -> Result<jint, InternalAppError> {
self._get_keyboard_type(env)
.map_err(|err| jni_utils::clear_and_map_exception_to_err(env, err))
}
Expand Down Expand Up @@ -183,7 +180,7 @@ impl KeyCharacterMap {
}
})
.map_err(|err| {
let err: InternalAppError = err.into();
let err: InternalAppError = err;
err.into()
})
}
Expand Down Expand Up @@ -217,7 +214,7 @@ impl KeyCharacterMap {
})
})
.map_err(|err| {
let err: InternalAppError = err.into();
let err: InternalAppError = err;
err.into()
})
}
Expand All @@ -239,7 +236,7 @@ impl KeyCharacterMap {
Ok(keyboard_type.into())
})
.map_err(|err| {
let err: InternalAppError = err.into();
let err: InternalAppError = err;
err.into()
})
}
Expand Down
8 changes: 4 additions & 4 deletions android-activity/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
//!
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion android-activity/src/native_activity/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down
7 changes: 2 additions & 5 deletions android-activity/src/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ pub(crate) fn android_log(level: Level, tag: &CStr, msg: &CStr) {
}

pub(crate) fn log_panic(panic: Box<dyn std::any::Any + Send>) {
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::<String>() {
if let Ok(msg) = CString::new(panic.clone()) {
Expand All @@ -43,10 +43,7 @@ pub(crate) fn log_panic(panic: Box<dyn std::any::Any + Send>) {
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");
}
}

Expand Down
Loading