Robert Bragg 5c091cd7bb Import android-games-sdk changes for 4.4.0
This imports the Android Games SDK from:

- repo: https://github.com/rust-mobile/android-games-sdk
- branch: android-activity-4.4.0
- commit: 78daa4adfc4a619daeab9f96181190b145f1e544

This is based on the GameActivity 4.4.0 release from:

- repo: https://android.googlesource.com/platform/frameworks/opt/gamesdk
- branch: android-games-sdk-game-activity-release
- commit: 541587a073871a9d2659f90335dcae345007eeed

Our integration branch includes the following patches:

- 78daa4ad Add mainLooper to android_app
- 179eaa92 notify android_main of editor actions
- 5102e14c Don't send (unused) APP_CMD_EDITOR_ACTION
- 223936cf Don't send (unused) APP_CMD_KEY/TOUCH_EVENTs
- a24b21a4 android-activity: don't read unicode via getUnicodeChar
- 9e3926b1 android-activity: rename C symbols that need export
- 32ac1c73 glue: support InputAvailable events
- 69a1868c glue: fix deadlocks in java callbacks after app destroyed
- b5a2df04 glue: remove unused variable

This re-runs `generate-bindings.sh`

Notes:

Reviewing the upstream changes, it doesn't look like much has changed
since the 4.0.0 release (at least in the glue layer).

It was quite painful rebasing on the latest upstream release due to
upstream running clang-format across their whole repo

In this case I ended up using `git filter-branch` to reformat our
patches before rebasing:

```
git filter-branch -f --tree-filter '
find game-activity/prefab-src/modules/game-activity \
  \( -iname "*.c" -o -iname "*.h" -o -iname "*.cpp" \) \
    -print0 |
    xargs -0 clang-format -i --style="{BasedOnStyle: Google,
    AccessModifierOffset: -4, AlignOperands: false,
    AllowShortFunctionsOnASingleLine: Empty,
    AlwaysBreakBeforeMultilineStrings: false, ColumnLimit: 100,
    CommentPragmas: \"NOLINT:.*\", ConstructorInitializerIndentWidth: 6,
    ContinuationIndentWidth: 8, IndentWidth: 4,
    PenaltyBreakBeforeFirstCallParameter: 100000,
    SpacesBeforeTrailingComments: 1}"
    ' cdf4eee808130cc007a6203904d1d6c9acbf53a3^..HEAD
```

Previously, we were apparently based on Google's
`android-games-sdk-game-text-input-release` branch instead of their
`android-games-sdk-game-activity-release` branch, which made it awkward
to diff changes because both branches include the game-activity SDK but
all of the same patches have different commit hashes between branches.

Our previous base commit for GameActivity 4.0.0 was:
7f54c13ee549e4511dcdc15a8ca73864e87be605
which corresponds to:
65ee0100ead8cf73c851f150bffad2779dfa8704
on the game-activity-release branch

Note: The upstream release notes are also confusing because where they
list what commits are included in each release, then for the 4.0.0
release those commits only exist on the `game-text` release branch but
for the 4.4.0 release the commits exist on the `game-activity` release
branch.

These are the upstream patches to the game-activity glue since 4.0.0:

- c28257b2 Push new version of GameActivity 4.4.0
    - no functional change
- e32db80f Fixed formatting of gamesdk repo
    - no functional change
- a7cdb8c6 Migrate from deprecate ALooper_pollAll to ALooper_pollOnce
    - only affects examples
- 163d7fcb Improve android_app_set_activity_state ANR protection
    - this adds a timeout for how long android_app_set_activity_state
      will block waiting for the android_main thread to handle
      synchronous callbacks (such as onStart, onResume)
    - this is backwards compatible
- d3fbe82a Improve version revision macro updating
    - no functional change
- 2ae5d1f4 Release a new alpha version for AGDK components.
    - no functional change
- 3e5fc4cd Add JNI_OnLoad function
    - an (optional) alternative means to call `GameActivity_register`
      which won't affect us since the `JNI_OnLoad` exported from C++ won't
      be exported when compiling with the Rust toolchain.
- 044fd03c Release a new alpha version for AGDK components.
    - no functional change
- 1198bb06 Fix GameActivity getLocale* functions.
    - this is a backwards compatible fix that doesn't interact without
      integration changes
- 07eff729 Change GameActivity and GameTextInput to 4.1 alpha.
    - no functional change

Based on this audit, our integration should be backwards compatible with
GameActivity 4.0.0
2026-03-21 15:37:05 +00:00
2026-01-07 16:51:08 +00:00

android-activity

ci crates.io Docs MSRV

Overview

android-activity provides a "glue" layer for building native Rust applications on Android, supporting multiple Activity base classes. It's comparable to android_native_app_glue.c for C/C++ applications and is an alternative to the ndk-glue crate.

android-activity provides a way to load your crate as a cdylib library via the onCreate method of your Android Activity class; run an android_main() function in a separate thread from the Java main thread and marshal events (such as lifecycle events and input events) between Java and your native thread.

So far it supports NativeActivity or GameActivity (from the Android Game Development Kit) and there's also interest in supporting a first-party RustActivity base class that could be better tailored to the needs of Rust applications.

Example

Cargo.toml

[dependencies]
log = "0.4"
android_logger = "0.13"
android-activity = { version = "0.6", features = [ "native-activity" ] }

[lib]
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

use android_activity::{AndroidApp, InputStatus, MainEvent, PollEvent};

#[unsafe(no_mangle)]
fn android_main(app: AndroidApp) {

    // `android_main` is tied to your `Activity` lifecycle, not your application lifecycle
    // and so it may be called multiple times if your activity is destroyed and recreated.
    //
    // Use a `OnceLock` or similar to ensure that you don't attempt to initialize global state
    // multiple times.
    static APP_ONCE: OnceLock<()> = OnceLock::new();
    APP_ONCE.get_or_init(|| {
        android_logger::init_once(android_logger::Config::default().with_min_level(log::Level::Info));
    });

    loop {
        app.poll_events(Some(std::time::Duration::from_millis(500)) /* timeout */, |event| {
            match event {
                PollEvent::Wake => { log::info!("Early wake up"); },
                PollEvent::Timeout => { log::info!("Hello, World!"); },
                PollEvent::Main(main_event) => {
                    log::info!("Main event: {:?}", main_event);
                    match main_event {
                        // Once you receive a `Destroy` event, your `AndroidApp` will no longer
                        // be associated with any `Activity` and it's methods will effectively be no-ops.
                        //
                        // You should return from `android_main` and if your `Activity` gets recreated then
                        // a new `AndroidApp` will be passsed to a new invocation of `android_main`.
                        MainEvent::Destroy => { return; }
                        _ => {}
                    }
                },
                _ => {}
            }

            app.input_events(|event| {
                log::info!("Input Event: {event:?}");
                InputStatus::Unhandled
            });
        });
    }
}
rustup target add aarch64-linux-android
cargo install cargo-apk
cargo apk run
adb logcat example:V *:S

Optional android_on_create entry point

android-activity also supports an optional android_on_create entry point that gets called from the Activity.onCreate() callback before android_main() is called, allowing for doing some setup work on the Java main thread before the main Rust code starts running.

For example:

use std::sync::OnceLock;
use jni::{JavaVM, objects::JObject};

#[unsafe(no_mangle)]
fn android_on_create(state: &android_activity::OnCreateState) {

    // `android_on_create` is tied to your `Activity` lifecycle, not your application lifecycle
    // and so it may be called multiple times if your activity is destroyed and recreated.
    //
    // Use a `OnceLock` or similar to ensure that you don't attempt to initialize global state
    // multiple times.
    static APP_ONCE: OnceLock<()> = OnceLock::new();
    APP_ONCE.get_or_init(|| {
        // Initialize logging...
    });
    let vm = unsafe { JavaVM::from_raw(state.vm_as_ptr().cast()) };
    let activity = state.activity_as_ptr() as jni::sys::jobject;
    // Do some other setup work on the Java main thread before `android_main` starts running
}

Full Examples

See this collection of examples (based on both GameActivity and NativeActivity).

Each example is a standalone project that may also be a convenient templates for starting a new project.

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?

To learn more about the NativeActivity class that's shipped with Android see here.

To learn more about the GameActivity class that's part of the Android Game Developer's Kit and also see a comparison with NativeActivity see here

Generally speaking, if unsure, NativeActivity may be more convenient to start with since you may not need to compile/link any Java or Kotlin code.

It's expected that the GameActivity backend will gain more sophisticated input handling features over time (such as for supporting input via onscreen keyboards or game controllers) and only GameActivity is based on the AppCompatActivity subclass which you may want in some situations to help with compatibility across devices.

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

Winit-based applications

Firstly; if you have a 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 documentation 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 #[unsafe(no_mangle)]fn android_main(app: AndroidApp) entry point.

See the Android documentation for more details and also see the Winit-based examples here.

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 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

The steps to switch a simple standalone application over from ndk-glue to android-activity (still based on NativeActivity) should be:

  1. Remove ndk-glue from your Cargo.toml
  2. Add a dependency on android-activity, like android-activity = { version="0.6", features = [ "native-activity" ] }
  3. Optionally add a dependency on android_logger = "0.13.0"
  4. Update the main entry point to look like this:
use android_activity::AndroidApp;

#[no_mangle]
fn android_main(app: AndroidApp) {
    android_logger::init_once(android_logger::Config::default().with_min_level(log::Level::Info));
}

See this minimal NativeActivity Mainloop for more details about how to poll for events.

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

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:

  1. Support alternative Activity classes: Prior glue crates were based on NativeActivity and their API precluded supporting alternatives. In particular there was an interest in the GameActivity class in conjunction with it's GameTextInput library that can facilitate onscreen keyboard support. This also allows building applications based on the standard AppCompatActivity base class which isn't possible with NativeActivity. Finally there was interest in paving the way towards supporting a first-party RustActivity that could be best tailored towards the needs of Rust applications on Android.
  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.

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.

S
Description
No description provided
Readme 2.1 MiB
Languages
Rust 86.9%
C 8.1%
C++ 4.8%
Shell 0.2%