mirror of
https://github.com/rust-mobile/android-activity.git
synced 2026-07-09 16:27:28 +00:00
af331e3bff
With the way events are delivered via an `InputQueue` with `NativeActivity` there is no direct access to the underlying KeyEvent and MotionEvent Java objects and no `ndk` API that supports the equivalent of `KeyEvent.getUnicodeChar()` What `getUnicodeChar` does under the hood though is to do lookups into a `KeyCharacterMap` for the corresponding `InputDevice` based on the event's `key_code` and `meta_state` - which are things we can do via some JNI bindings for `KeyCharacterMap`. Although it's still awkward to expose an API like `key_event.get_unicode_char()` we can instead provide an API that lets you look up a `KeyCharacterMap` for any `device_id` and applications can then use that for character mapping. This approach is also more general than the `getUnicodeChar` utility since it exposes other useful state, such as being able to check what kind of keyboard input events are coming from (such as a full physical keyboard vs a virtual / 'predictive' keyboard) For consistency this exposes the same API through the game-activity backend, even though the game-activity backend is technically able to support unicode lookups via `getUnicodeChar` (since it has access to the Java `KeyEvent` object). This highlighted a need to be able to use other `AndroidApp` APIs while processing input, which wasn't possible with the `.input_events()` API design because the `AndroidApp` held a lock over the backend while iterating events. This changes `input_events()` to `input_events_iter()` which now returns a form of lending iterator and instead of taking a callback that gets called repeatedly by `input_events()` a similar callback is now passed to `iter.next(callback)`. The API isn't as ergonomic as I would have liked, considering that lending iterators aren't a standard feature for Rust yet but also since we still want to have the handling for each individual event go via a callback that can report whether an event was "handled". I think the slightly awkward ergonomics are acceptable though considering that the API will generally be used as an implementation detail within middleware frameworks like Winit. Since this is the first example where we're creating non-trivial Java bindings for an Android SDK API this adds some JNI utilities and establishes a pattern for how we can implement a class binding. It's an implementation detail but with how I wrote the binding I tried to keep in mind the possibility of creating a procmacro later that would generate some of the JNI boilerplate involved.
This is a minimal test application based on NativeActivity that just
runs a mainloop based on android_activity::poll_events() and traces
the events received without doing any rendering. It also saves and
restores some minimal application state.
Since this test doesn't require a custom Activity subclass it's
optionally possible to build this example with cargo apk.
Gradle Build
export ANDROID_NDK_HOME="path/to/ndk"
export ANDROID_HOME="path/to/sdk"
rustup target add aarch64-linux-android
cargo install cargo-ndk
cargo ndk -t arm64-v8a -o app/src/main/jniLibs/ build
./gradlew build
./gradlew installDebug
Cargo APK Build
export ANDROID_NDK_HOME="path/to/ndk"
export ANDROID_SDK_HOME="path/to/sdk"
rustup target add aarch64-linux-android
cargo install cargo-apk
cargo apk build
cargo apk run