Compare commits
243 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b4ddf059b7 | |||
| 11a5a54483 | |||
| 8de2b6dbaf | |||
| 57b5192366 | |||
| dd66428b14 | |||
| f17b25b673 | |||
| 5c091cd7bb | |||
| 8124bc786d | |||
| c3ed6ba77d | |||
| c1d00b9191 | |||
| 4acfd2d59c | |||
| b042af60f2 | |||
| 9163368955 | |||
| 91cf9d7229 | |||
| ae5553288c | |||
| 0c32e9d8fa | |||
| 43de2770b9 | |||
| 2a05cd2763 | |||
| 0062cfc7a0 | |||
| 0f49d96fa0 | |||
| 2b20da72bd | |||
| f44d837bf7 | |||
| 4ff35807fb | |||
| ae24c96dcc | |||
| 42e0f88287 | |||
| fdcf4ce28d | |||
| 483164c333 | |||
| 279d73889f | |||
| 7577299c84 | |||
| 4e93184d8b | |||
| 31feb32f07 | |||
| 25f4220fef | |||
| 7e8990fd92 | |||
| fe2c50ccc6 | |||
| a20a7e4ee4 | |||
| 0b0e19ed44 | |||
| e686e80112 | |||
| b9e883866e | |||
| 9e8c85c647 | |||
| a97cf1ceae | |||
| 1652ebb229 | |||
| b943f58863 | |||
| 019ad634a2 | |||
| 87cda3c560 | |||
| bde1cb3436 | |||
| 69f3642499 | |||
| c0f3fa6754 | |||
| 42af0cccfa | |||
| 5d7616e30e | |||
| 3755ed7e7a | |||
| 5367c865e3 | |||
| eacddd744a | |||
| 36832feacf | |||
| 88714f0b6a | |||
| 85eb7274f4 | |||
| 49f2b86424 | |||
| 976e9d06af | |||
| ac2e17e977 | |||
| db3ea3386f | |||
| 51d05d48c8 | |||
| fe171bc532 | |||
| 0d299300f4 | |||
| 0a87a84c57 | |||
| 7bd3ba6dde | |||
| 6a0197c28f | |||
| e5b8242ff2 | |||
| c9faa9c44e | |||
| 4b9b8d754b | |||
| 526d39c1f3 | |||
| 4ffa3ac2e1 | |||
| 967882f3d9 | |||
| 35e080baf0 | |||
| 5cb67a2b89 | |||
| 672360c5e6 | |||
| 9fce890219 | |||
| 2deec162b5 | |||
| eeeb80209f | |||
| 6c3583dc24 | |||
| bfd8bfd04c | |||
| af341897a2 | |||
| a84722ff23 | |||
| d9af67008a | |||
| c2f467c174 | |||
| e14d2c1deb | |||
| 100d5bc1d4 | |||
| 98aef99419 | |||
| a7114c807f | |||
| a7dc90d9bb | |||
| 6af4d61227 | |||
| 6e036c99e4 | |||
| 2a917ca5c4 | |||
| add58dbb2e | |||
| d16cb79350 | |||
| b590ec5484 | |||
| 74d9669854 | |||
| a92237fab4 | |||
| 969ba5adf9 | |||
| ce4413b2c6 | |||
| a291e378ee | |||
| 2ecaab9f15 | |||
| 3d5e479a4e | |||
| 219a14bda1 | |||
| 733fabffd3 | |||
| f2132c4dab | |||
| 9930b9bf90 | |||
| 0eefd623ed | |||
| 83cdb56e24 | |||
| 942053d88e | |||
| 865cc6a780 | |||
| 4f6d7d68de | |||
| 7ea440d6c1 | |||
| 75e9e8672d | |||
| 47a073f702 | |||
| 499d09595b | |||
| 23a8570d48 | |||
| c9f57a734f | |||
| e91176cb08 | |||
| 2a61f84c70 | |||
| 2654c9659b | |||
| 35fe600235 | |||
| 242285b205 | |||
| 379f064170 | |||
| e2f69421a0 | |||
| 1b3334178b | |||
| 535994f4a2 | |||
| 6b3307410e | |||
| b4cf0eeabf | |||
| af331e3bff | |||
| 6f72dde55d | |||
| d0f10a0dd9 | |||
| 3464ba20bc | |||
| 1abb02c820 | |||
| c0a9e20c5a | |||
| 66cfc68dac | |||
| ed2dc53ee4 | |||
| 74f510a99a | |||
| 741e633ea8 | |||
| 41f30c39ad | |||
| 96497f9da9 | |||
| c22a5453df | |||
| 9bb5f9c9cf | |||
| a604c0aa9f | |||
| b09526a4a9 | |||
| cc3983ca21 | |||
| 2a2f27637f | |||
| d2d18154d9 | |||
| 3e3fb84c03 | |||
| 202ab4c1e9 | |||
| d6345abb2a | |||
| c471fdf903 | |||
| 1a8a92b3fb | |||
| bb97af154f | |||
| 8a21219695 | |||
| c10a2fb67a | |||
| ab2606a73d | |||
| a84a7b54cd | |||
| a9e91f4308 | |||
| 0f00a58a41 | |||
| 9a713c823d | |||
| 230035526b | |||
| cd81420638 | |||
| 1ad3abd934 | |||
| ca0d2eb3aa | |||
| 79e03e08fb | |||
| 120d2f66c7 | |||
| 4a4efd871a | |||
| 3843a7cfaa | |||
| 924e5405c2 | |||
| 049e660219 | |||
| 6559dc8133 | |||
| d6ccefaf77 | |||
| 9229bb20c1 | |||
| 4976cbad44 | |||
| e0c96ad6b4 | |||
| c70e5d852f | |||
| f28d6adc55 | |||
| 0fa6888484 | |||
| d4a3d5845d | |||
| c91745a39d | |||
| 1b44d38822 | |||
| 9ac7891664 | |||
| 03b06b8c5e | |||
| caf2a624b2 | |||
| fe9d68c99e | |||
| 0c3f16c9ba | |||
| 87fe6a8465 | |||
| a04c483d79 | |||
| 507cfe072e | |||
| 36ddfaa9ce | |||
| b2467cb028 | |||
| 6dd3c0c02a | |||
| 6ae2e427d6 | |||
| dc9ca832c5 | |||
| 9d06f62a4e | |||
| 6942637c3c | |||
| 47b2e279e0 | |||
| 40fb012000 | |||
| 4155fbc9db | |||
| 435e183a58 | |||
| 2e279210a3 | |||
| 14eb2f0a17 | |||
| 8f68cb5db9 | |||
| b217ad546c | |||
| bf251c63cd | |||
| 1633f62d0d | |||
| 6ca4ea2e30 | |||
| 9640893477 | |||
| fab31d3408 | |||
| 38554d98db | |||
| aa5fcc53bb | |||
| 4669508823 | |||
| 649b65c0c0 | |||
| c75f33a81e | |||
| bd8cc86ec7 | |||
| e8ae198653 | |||
| 1ece2ad87d | |||
| 8077a4b0da | |||
| 364dffb2f7 | |||
| 0590bf601a | |||
| c3d115fd7b | |||
| 1ed7d383e0 | |||
| 2e25e2ebed | |||
| 949386ea4e | |||
| 879d7cac5a | |||
| cd32b4a064 | |||
| 2870a84fbe | |||
| 3de1433f82 | |||
| 1507b37425 | |||
| 1314210be4 | |||
| a65729400b | |||
| 2d44950d14 | |||
| b13a53f182 | |||
| b717c03dda | |||
| 42d6a7247e | |||
| 47afecda36 | |||
| 17f0c674c0 | |||
| 8ae1059aec | |||
| 7cdb77eca4 | |||
| 6245866228 | |||
| 5c876308c1 | |||
| 351d0e9ddb | |||
| e689461580 | |||
| ac46815956 |
@@ -0,0 +1,10 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: cargo
|
||||
directory: /
|
||||
schedule:
|
||||
interval: weekly
|
||||
- package-ecosystem: github-actions
|
||||
directory: /
|
||||
schedule:
|
||||
interval: weekly
|
||||
@@ -2,7 +2,7 @@ name: ci
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
branches: "*"
|
||||
pull_request:
|
||||
|
||||
env:
|
||||
@@ -12,20 +12,32 @@ env:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-22.04
|
||||
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
|
||||
rust_version: [1.60.0, stable]
|
||||
# See top README for MSRV policy
|
||||
rust-version: [1.85.0, stable]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- uses: hecrj/setup-rust-action@v1
|
||||
# Downgrade all dependencies to their minimum version, both to ensure our
|
||||
# minimum version bounds are correct and buildable, as well as to satisfy
|
||||
# our MSRV check when arbitrary dependencies bump their MSRV beyond our
|
||||
# MSRV in a patch-release.
|
||||
# This implies that downstream consumers can only rely on our MSRV when
|
||||
# downgrading various (transitive) dependencies.
|
||||
- uses: hecrj/setup-rust-action@v2
|
||||
with:
|
||||
rust-version: ${{ matrix.rust_version }}
|
||||
rust-version: nightly
|
||||
if: ${{ matrix.rust-version != 'stable' }}
|
||||
- name: Downgrade dependencies
|
||||
run: cargo +nightly generate-lockfile -Zminimal-versions
|
||||
if: ${{ matrix.rust-version != 'stable' }}
|
||||
|
||||
- uses: hecrj/setup-rust-action@v2
|
||||
with:
|
||||
rust-version: ${{ matrix.rust-version }}
|
||||
|
||||
- name: Install Rust targets
|
||||
run: >
|
||||
@@ -36,16 +48,7 @@ jobs:
|
||||
i686-linux-android
|
||||
|
||||
- name: Install cargo-ndk
|
||||
run: cargo install cargo-ndk
|
||||
|
||||
- name: Setup Java
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: '17'
|
||||
|
||||
- name: Setup Android SDK
|
||||
uses: android-actions/setup-android@v2
|
||||
run: cargo +stable install cargo-ndk
|
||||
|
||||
- name: Build game-activity
|
||||
working-directory: android-activity
|
||||
@@ -68,7 +71,7 @@ jobs:
|
||||
build --features native-activity
|
||||
|
||||
- name: Build agdk-mainloop example
|
||||
if: matrix.rust_version == 'stable'
|
||||
if: matrix.rust-version == 'stable'
|
||||
working-directory: examples/agdk-mainloop
|
||||
run: >
|
||||
cargo ndk
|
||||
@@ -79,7 +82,7 @@ jobs:
|
||||
-o app/src/main/jniLibs/ -- build
|
||||
|
||||
- name: Build na-mainloop example
|
||||
if: matrix.rust_version == 'stable'
|
||||
if: matrix.rust-version == 'stable'
|
||||
working-directory: examples/na-mainloop
|
||||
run: >
|
||||
cargo ndk
|
||||
@@ -89,48 +92,33 @@ jobs:
|
||||
-t x86
|
||||
-o app/src/main/jniLibs/ -- build
|
||||
|
||||
- name: Build agdk-egui example
|
||||
if: matrix.rust_version == 'stable'
|
||||
working-directory: examples/agdk-egui
|
||||
run: >
|
||||
cargo ndk
|
||||
-t arm64-v8a
|
||||
-t armeabi-v7a
|
||||
-t x86_64
|
||||
-t x86
|
||||
-o app/src/main/jniLibs/ -- build
|
||||
|
||||
- name: Build agdk-eframe example
|
||||
if: matrix.rust_version == 'stable'
|
||||
working-directory: examples/agdk-eframe
|
||||
run: >
|
||||
cargo ndk
|
||||
-t arm64-v8a
|
||||
-t armeabi-v7a
|
||||
-t x86_64
|
||||
-t x86
|
||||
-o app/src/main/jniLibs/ -- build
|
||||
|
||||
- name: Documentation
|
||||
run: >
|
||||
cargo ndk -t arm64-v8a doc --no-deps
|
||||
|
||||
- name: Build doctests
|
||||
# All doctests are set to no_run, because they require running in the
|
||||
# context of an Android app.
|
||||
# Only run on stable because cross-compiling doctests is only supported
|
||||
# since Rust 1.89.
|
||||
if: ${{ matrix.rust-version == 'stable' }}
|
||||
run: |
|
||||
cargo test --doc -F native-activity --target aarch64-linux-android
|
||||
cargo ndk -t arm64-v8a -- test --doc -F game-activity
|
||||
|
||||
format:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: stable
|
||||
override: true
|
||||
components: rustfmt
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Format
|
||||
run: cargo fmt --all -- --check
|
||||
working-directory: android-activity
|
||||
|
||||
- name: Format agdk-egui example
|
||||
- name: Format na-mainloop example
|
||||
run: cargo fmt --all -- --check
|
||||
working-directory: examples/agdk-egui
|
||||
working-directory: examples/na-mainloop
|
||||
|
||||
- name: Format agdk-mainloop example
|
||||
run: cargo fmt --all -- --check
|
||||
working-directory: examples/agdk-mainloop
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
target
|
||||
/target
|
||||
/Cargo.lock
|
||||
|
||||
@@ -1,18 +1,5 @@
|
||||
[workspace]
|
||||
members = [
|
||||
"android-activity"
|
||||
]
|
||||
resolver = "2"
|
||||
members = ["android-activity"]
|
||||
|
||||
exclude = [
|
||||
"examples/agdk-mainloop",
|
||||
"examples/agdk-winit-wgpu",
|
||||
"examples/agdk-eframe",
|
||||
"examples/agdk-egui",
|
||||
"examples/agdk-oboe",
|
||||
"examples/agdk-cpal",
|
||||
"examples/na-mainloop",
|
||||
"examples/na-winit-wgpu",
|
||||
"examples/na-subclass-jni",
|
||||
"examples/na-openxr-info",
|
||||
"examples/na-openxr-wgpu"
|
||||
]
|
||||
exclude = ["examples"]
|
||||
|
||||
@@ -1,60 +1,87 @@
|
||||
# Overview
|
||||
# `android-activity`
|
||||
|
||||
[](https://github.com/rust-mobile/android-activity/actions/workflows/ci.yml)
|
||||
[](https://crates.io/crates/android-activity)
|
||||
[](https://docs.rs/android-activity)
|
||||
[](https://blog.rust-lang.org/2025/02/20/Rust-1.85.0/)
|
||||
|
||||
## 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`][ndk_concepts]
|
||||
for C/C++ applications.
|
||||
for C/C++ applications and is an alternative to the [ndk-glue] crate.
|
||||
|
||||
`android-activity` supports [`NativeActivity`] or [`GameActivity`] from the
|
||||
Android Game Development Kit and can be extended to support additional base
|
||||
classes.
|
||||
`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.
|
||||
|
||||
`android-activity` provides a way to load a `cdylib` via the `onCreate` method of
|
||||
your `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][agdk]) and there's also interest in supporting a first-party
|
||||
`RustActivity` base class that could be better tailored to the needs of Rust
|
||||
applications.
|
||||
|
||||
[`Activity`]: https://developer.android.com/reference/android/app/Activity
|
||||
[`NativeActivity`]: https://developer.android.com/reference/android/app/NativeActivity
|
||||
[ndk_concepts]: https://developer.android.com/ndk/guides/concepts#naa
|
||||
[`GameActivity`]: https://developer.android.com/games/agdk/integrate-game-activity
|
||||
[`GameActivity`]: https://developer.android.com/games/agdk/game-activity
|
||||
[ndk-glue]: https://crates.io/crates/ndk-glue
|
||||
[agdk]: https://developer.android.com/games/agdk/overview
|
||||
|
||||
### Example
|
||||
## Quick Start
|
||||
|
||||
```
|
||||
cargo init --lib --name=example
|
||||
```
|
||||
**Cargo.toml:**
|
||||
|
||||
Cargo.toml
|
||||
```
|
||||
```toml
|
||||
[dependencies]
|
||||
log = "0.4"
|
||||
android_logger = "0.11"
|
||||
android-activity = { git = "https://github.com/rib/android-activity/", features = [ "native-activity" ] }
|
||||
android_logger = "0.13"
|
||||
android-activity = { version = "0.6", features = [ "native-activity" ] }
|
||||
|
||||
[lib]
|
||||
crate_type = ["cdylib"]
|
||||
crate-type = ["cdylib"]
|
||||
```
|
||||
|
||||
lib.rs
|
||||
```rust
|
||||
use log::info;
|
||||
use android_activity::{PollEvent, MainEvent};
|
||||
_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_
|
||||
|
||||
#[no_mangle]
|
||||
**lib.rs:**
|
||||
|
||||
```rust
|
||||
use std::sync::OnceLock;
|
||||
use android_activity::{AndroidApp, InputStatus, MainEvent, PollEvent};
|
||||
|
||||
// - Called on a dedicated Activity main loop thread, spawned after `android_on_create` returns
|
||||
// - May be called multiple times if your Activity is destroyed and recreated.
|
||||
// - Note: this symbol has a "Rust" ABI (default), not "C" ABI.
|
||||
#[unsafe(no_mangle)]
|
||||
fn android_main(app: AndroidApp) {
|
||||
android_logger::init_once(
|
||||
android_logger::Config::default().with_min_level(log::Level::Info)
|
||||
);
|
||||
|
||||
// `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 => { info!("Early wake up"); },
|
||||
PollEvent::Timeout => { info!("Hello, World!"); },
|
||||
PollEvent::Wake => { log::info!("Early wake up"); },
|
||||
PollEvent::Timeout => { log::info!("Hello, World!"); },
|
||||
PollEvent::Main(main_event) => {
|
||||
info!("Main event: {:?}", 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 passed to a new invocation of `android_main`.
|
||||
MainEvent::Destroy => { return; }
|
||||
_ => {}
|
||||
}
|
||||
@@ -63,188 +90,262 @@ fn android_main(app: AndroidApp) {
|
||||
}
|
||||
|
||||
app.input_events(|event| {
|
||||
info!("Input Event: {event:?}");
|
||||
log::info!("Input Event: {event:?}");
|
||||
InputStatus::Unhandled
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```
|
||||
```sh
|
||||
rustup target add aarch64-linux-android
|
||||
cargo install cargo-apk
|
||||
cargo apk run
|
||||
adb logcat example:V *:S
|
||||
```
|
||||
|
||||
# Game Activity
|
||||
_Note: although `cargo apk` is convenient for this quick start example, it's
|
||||
generally recommended that you should use a more-standard, Gradle-based build
|
||||
system for your Android application and use something like `cargo ndk` for
|
||||
building your Rust code into a `cdylib` that is then packaged via Gradle._
|
||||
|
||||
Originally the aim was to enable support for building Rust applications based on the
|
||||
[GameActivity] class provided by [Google's Android Game Development Kit][agdk]
|
||||
which can also facilitate integration with additional AGDK libraries including:
|
||||
1. [Game Text Input](https://developer.android.com/games/agdk/add-support-for-text-input): a library
|
||||
to help fullscreen native applications utilize the Android soft keyboard.
|
||||
2. [Game Controller Library, aka 'Paddleboat'](https://developer.android.com/games/sdk/game-controller):
|
||||
a native library designed to help support access to game controller inputs.
|
||||
3.[Frame Pacing Library, aka ' Swappy'](https://developer.android.com/games/sdk/frame-pacing): a library
|
||||
that helps OpenGL and Vulkan games achieve smooth rendering and correct frame pacing on Android.
|
||||
3. [Memory Advice API](https://developer.android.com/games/sdk/memory-advice/overview): an API to
|
||||
help applications monitor their own memory usage to stay within safe limits for the system.
|
||||
4. [Oboe audio library](https://developer.android.com/games/sdk/oboe): a low-latency audio API for native
|
||||
applications.
|
||||
## Full Examples
|
||||
|
||||
Since `GameActivity` is based on the widely used [AppCompatActivity] base class, it also
|
||||
provides a variety of back ported Activity APIs which can make it more practical to
|
||||
support a wider range of devices and Android versions.
|
||||
See [this collection of
|
||||
examples](https://github.com/rust-mobile/rust-android-examples) (based on both
|
||||
`GameActivity` and `NativeActivity`).
|
||||
|
||||
[GameActivity]: https://developer.android.com/games/agdk/integrate-game-activity
|
||||
[agdk]: https://developer.android.com/games/agdk
|
||||
[AppCompatActivity]: https://developer.android.com/reference/androidx/appcompat/app/AppCompatActivity
|
||||
Each example is a standalone Android Studio project that can serve as a
|
||||
convenient template for starting a new project.
|
||||
|
||||
# Native Activity
|
||||
For the examples based on middleware frameworks (Winit or Egui) they also
|
||||
aim to demonstrate how it's possible to write portable code that will run on
|
||||
Android and other systems.
|
||||
|
||||
This project also supports [`NativeActivity`][NativeActivity] based applications. Although
|
||||
NativeActivity is more limited than `GameActivity` and does not derive from `AppCompatActivity` it
|
||||
can sometimes still be convenient to build on `NativeActivity` in situations where you are using a
|
||||
limited/minimal build system that is not able to compile Java or Kotlin code or fetch from Maven
|
||||
repositories - this is because `NativeActivity` is included as part of the Android platform.
|
||||
## Optional `android_on_create` entry point
|
||||
|
||||
[NativeActivity]: https://developer.android.com/reference/android/app/NativeActivity
|
||||
`android-activity` also supports an optional `android_on_create` entry point
|
||||
that gets called from the `Activity.onCreate()` callback before `android_main()`
|
||||
is called.
|
||||
|
||||
# Design
|
||||
`android_on_create` is called from the Java main / UI thread before the
|
||||
`android_main` thread is spawned.
|
||||
|
||||
## Compatibility
|
||||
|
||||
All `Activity` classes are supported via a common API that enables you to write
|
||||
`Activity` subclass agnostic code wherever you don't depend on features that are
|
||||
specific to a particular subclass.
|
||||
|
||||
For example, it makes it possible to have a [Winit backend](https://github.com/rib/winit/tree/agdk-game-activity)
|
||||
that supports Android applications running with different `Activity` classes.
|
||||
|
||||
## API Summary
|
||||
|
||||
|
||||
### `android_main` entrypoint
|
||||
The glue crates define a standard entrypoint ABI for your `cdylib` that looks like:
|
||||
|
||||
```rust
|
||||
use android_activity::AndroidApp;
|
||||
|
||||
#[no_mangle]
|
||||
fn android_main(app: AndroidApp) {
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
There's currently no high-level macro provided for things like initializing
|
||||
logging or allowing the main function to return a `Result<>` since it's expected
|
||||
that different downstream frameworks may each have differing opinions on the
|
||||
details and may want to provide their own macros.
|
||||
|
||||
|
||||
### `AndroidApp`
|
||||
|
||||
Your `android_main()` function is passed an `AndroidApp` struct to access state
|
||||
about your running application and handle synchronized interaction between your
|
||||
native Rust application and the `Activity` running on the Java main thread.
|
||||
|
||||
For example, the `AndroidApp` API enables:
|
||||
1. Access to Android lifecycle events
|
||||
2. Notifications of SurfaceView lifecycle events
|
||||
3. Access to input events
|
||||
4. Ability to save and restore state each time your process stops and starts
|
||||
5. Access application [`Configuration`] state
|
||||
6. internal/external/obb filesystem paths
|
||||
|
||||
_Note: that some of the `AndroidApp` APIs (such as for polling events) are only
|
||||
deemed safe to use from the application's main thread_
|
||||
|
||||
[`Configuration`]: https://developer.android.com/reference/android/content/res/Configuration
|
||||
|
||||
### Synchronized event callbacks
|
||||
|
||||
The `AndroidApp::poll_events()` API is similar to the Winit `EventLoop::run` API in that it
|
||||
takes a `FnMut` closure that is called for each outstanding event (such as for lifecycle events).
|
||||
This design ensures the glue layer can transparently handle any required synchronization with
|
||||
Java before and after each callback.
|
||||
|
||||
For example, when the Java main thread notifies the glue layer that its `SurfaceView` is being
|
||||
destroyed the Java thread will then block until it gets an explicit acknowledgement that the
|
||||
native application has had an opportunity to react to this notification. The glue layer will
|
||||
automatically release the blocked Java thread once it has delivered the corresponding event.
|
||||
Considering that many Android SDK APIs (such as `android.view.View`) must be
|
||||
accessed from the main thread, `android_on_create` can be a good place to do any
|
||||
setup work that needs to be done on the Java main thread.
|
||||
|
||||
For example:
|
||||
|
||||
```rust
|
||||
use native_activity::{PollEvent, MainEvent};
|
||||
use log::info;
|
||||
use std::sync::OnceLock;
|
||||
use jni::{JavaVM, objects::JObject};
|
||||
|
||||
#[no_mangle]
|
||||
extern "C" fn android_main() {
|
||||
android_logger::init_once(
|
||||
android_logger::Config::default().with_min_level(log::Level::Info)
|
||||
);
|
||||
#[unsafe(no_mangle)]
|
||||
fn android_on_create(state: &android_activity::OnCreateState) {
|
||||
|
||||
let mut quit = false;
|
||||
let mut redraw_pending = true;
|
||||
let mut render_state: Option<()> = Default::default();
|
||||
|
||||
let app = native_activity::android_app();
|
||||
while !quit {
|
||||
app.poll_events(Some(std::time::Duration::from_millis(500)) /* timeout */, |event| {
|
||||
match event {
|
||||
PollEvent::Wake => { info!("Early wake up"); },
|
||||
PollEvent::Timeout => {
|
||||
info!("Timed out");
|
||||
// Real app would probably rely on vblank sync via graphics API...
|
||||
redraw_pending = true;
|
||||
},
|
||||
PollEvent::Main(main_event) => {
|
||||
info!("Main event: {:?}", main_event);
|
||||
match main_event {
|
||||
MainEvent::SaveState { saver, .. } => {
|
||||
saver.store("foo://bar".as_bytes());
|
||||
},
|
||||
MainEvent::Pause => {},
|
||||
MainEvent::Resume { loader, .. } => {
|
||||
if let Some(state) = loader.load() {
|
||||
if let Ok(uri) = String::from_utf8(state) {
|
||||
info!("Resumed with saved state = {uri:#?}");
|
||||
}
|
||||
}
|
||||
},
|
||||
MainEvent::InitWindow { .. } => {
|
||||
render_state = Some(());
|
||||
redraw_pending = true;
|
||||
},
|
||||
MainEvent::TerminateWindow { .. } => {
|
||||
render_state = None;
|
||||
}
|
||||
MainEvent::WindowResized { .. } => { redraw_pending = true; },
|
||||
MainEvent::RedrawNeeded { ..} => { redraw_pending = true; },
|
||||
MainEvent::LowMemory => {},
|
||||
|
||||
MainEvent::Destroy => { quit = true },
|
||||
_ => { /* ... */}
|
||||
}
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
|
||||
if redraw_pending {
|
||||
if let Some(_rs) = render_state {
|
||||
redraw_pending = false;
|
||||
|
||||
// Handle input
|
||||
app.input_events(|event| {
|
||||
info!("Input Event: {event:?}");
|
||||
|
||||
});
|
||||
|
||||
info!("Render...");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
// `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
|
||||
}
|
||||
```
|
||||
|
||||
_(Note: there is also an `AndroidApp::run_on_java_main_thread()` method that
|
||||
gives another way to run code on the Java main thread for some use cases)_
|
||||
|
||||
## 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).
|
||||
|
||||
To learn more about the `GameActivity` class that's part of the [Android Game
|
||||
Developer's Kit][agdk] and also see a comparison with `NativeActivity` see
|
||||
[here](https://developer.android.com/games/agdk/game-activity)
|
||||
|
||||
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, but
|
||||
GameActivity is likely to be the better longer-term choice, due to being based
|
||||
on `AppCompatActivity` and having built in support for input methods (such as
|
||||
onscreen keyboards).
|
||||
|
||||
### NativeActivity
|
||||
|
||||
- Good for: Simple apps, quick prototyping, limited text input support
|
||||
- Setup: Just add the feature flag
|
||||
- Limitations: No built-in input method support (can only receive physical key
|
||||
events from soft keyboards that typically only allows basic ascii input)
|
||||
|
||||
The unique advantage of the `NativeActivity` class is that it's shipped as part
|
||||
of the Android OS and so you can use it without needing to compile or link any
|
||||
Java or Kotlin code.
|
||||
|
||||
`NativeActivity` is technically the only way to build a native Android
|
||||
application purely in Rust without any Java or Kotlin code at all.
|
||||
|
||||
The most significant limitation of `NativeActivity` is that it doesn't have
|
||||
built-in support for input methods (such as onscreen keyboards) and so it's
|
||||
often not a good choice for applications that need to support text input.
|
||||
|
||||
Since some soft keyboards will deliver physical key events for basic ascii input
|
||||
then `NativeActivity` can enable basic text input for prototyping but this is
|
||||
unlikely to be sufficient for production applications.
|
||||
|
||||
For advanced use cases, it would be possible to provide custom `InputConnection`
|
||||
support in conjunction with `NativeActivity` but this isn't something that
|
||||
`android-activity` provides out of the box currently.
|
||||
|
||||
### GameActivity
|
||||
|
||||
- Good for: Apps needing text input, modern AndroidX features
|
||||
- Setup requirements:
|
||||
- Add gradle dependency: `androidx.games:games-activity:4.4.0`
|
||||
- Enable the `game-activity` feature in Cargo.toml
|
||||
- **Important**: Do NOT enable prefab support [details here](#don't-compile-and-link-the-upstream-gameactivity-prefab-c-glue-layer)
|
||||
- Provides: IME support, AppCompatActivity features
|
||||
|
||||
`GameActivity` has built in support for input methods via the `GameTextInput`
|
||||
library and so is a better choice for applications that need to support text
|
||||
input.
|
||||
|
||||
`GameActivity` allows you to update the `ImeOptions` and actions associated with
|
||||
the soft keyboard as well as receive IME span updates for tracking the user's
|
||||
text input state.
|
||||
|
||||
`GameActivity` is based on the [`AppCompatActivity`] class, which is a standard
|
||||
Jetpack / AndroidX class that offers a lot of built-in functionality to help
|
||||
with compatibility across different Android versions and devices.
|
||||
|
||||
### Game Activity Library Version
|
||||
|
||||
`android-activity` currently supports the [`GameActivity` 4.4.0 Jetpack
|
||||
library](https://developer.android.com/jetpack/androidx/releases/games) and is
|
||||
backwards compatible with the previous `4.0.0` stable release. We can't
|
||||
guarantee that the next 4.x stable release will be compatible, but it's fairly
|
||||
likely that it will be.
|
||||
|
||||
Your Android package should depend on `androidx.games:games-activity:4.4.0` from
|
||||
Google's Maven repository.
|
||||
|
||||
Read the upstream [GameActivity getting
|
||||
started](https://developer.android.com/games/agdk/game-activity/get-started)
|
||||
guide for more details on how to add the GameActivity library to your project.
|
||||
|
||||
#### Don't compile and link the upstream GameActivity 'prefab' (C++ glue) layer
|
||||
|
||||
**Important**: Do _not_ follow upstream instructions to enable native prefab
|
||||
support for `GameActivity` that will compile and link the upstream C++ glue
|
||||
layer as part of your build. The upstream glue layer is not directly compatible
|
||||
with `android-activity` which provides its own native glue layer that integrates
|
||||
with Rust.
|
||||
|
||||
I.e. you do _not_ need to enable prefabs via your `build.gradle` file:
|
||||
|
||||
```gradle
|
||||
buildFeatures {
|
||||
prefab true
|
||||
}
|
||||
```
|
||||
|
||||
and do _not_ add a snippet like this to your `CMakeLists.txt` file:
|
||||
|
||||
```cmake
|
||||
find_package(game-activity REQUIRED CONFIG)
|
||||
target_link_libraries(${PROJECT_NAME} PUBLIC log android
|
||||
game-activity::game-activity_static)
|
||||
```
|
||||
|
||||
### Planning to Implement an Activity Subclass
|
||||
|
||||
It's not possible to subclass an Activity from Rust / JNI code alone.
|
||||
|
||||
Keep in mind that Android's design directs many events via the `Activity` class
|
||||
which can only be processed by overloading some associated `Activity` method, so
|
||||
if you want to handle those events then you will need to implement an `Activity`
|
||||
subclass and overload the relevant methods.
|
||||
|
||||
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.
|
||||
|
||||
_At the end of the day, Android's application programming model is fundamentally
|
||||
based around a Java VM running Java/Kotlin code that can optionally call into
|
||||
native code (not the other way around)._
|
||||
|
||||
## 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 its [`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`.
|
||||
|
||||
It's possible to write an application with `android-activity` that can
|
||||
gracefully handle repeated create -> run -> destroy cycles of the `Activity`
|
||||
due to its avoidance of global state. Theoretically you could even run
|
||||
multiple `Activity` instances at the same (though since `NativeActivity` and
|
||||
`GameActivity` were designed for fullscreen games, that only need a single
|
||||
Activity, this is not a common use case).
|
||||
|
||||
[`GameTextInput`]: https://developer.android.com/games/agdk/add-support-for-text-input
|
||||
[`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.
|
||||
|
||||
## Game Activity Library Versioning Policy
|
||||
|
||||
Any single release of `android-activity` will support a specific version of the
|
||||
Game Activity Jetpack / AndroidX library (documented above).
|
||||
|
||||
The required version of the Game Activity library does not form part of our Rust
|
||||
semver contract, since it doesn't affect the public Rust API of
|
||||
`android-activity`.
|
||||
|
||||
This means that a new patch release of `android-activity` may update the
|
||||
required version of `GameActivity`, which may require users to update how they
|
||||
package their application.
|
||||
|
||||
This is similar to how MSRV updates work, where new toolchain requirements can
|
||||
affect how you build your application but that change is orthogonal to the
|
||||
public API of the crate.
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
/target
|
||||
Cargo.lock
|
||||
|
||||
|
||||
# Added by cargo
|
||||
#
|
||||
# already existing elements were commented out
|
||||
|
||||
#/target
|
||||
#Cargo.lock
|
||||
@@ -1,11 +1,320 @@
|
||||
<!-- markdownlint-disable MD022 MD024 MD032 MD033 -->
|
||||
|
||||
# Changelog
|
||||
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.6.1] - 2026-03-30
|
||||
|
||||
## [0.2] - 2022-08-25
|
||||
### Added
|
||||
|
||||
- input: `TextInputAction` enum representing action button types on soft keyboards. ([#216](https://github.com/rust-mobile/android-activity/pull/216))
|
||||
- input: `InputEvent::TextAction` event for handling action button presses from soft keyboards. ([#216](https://github.com/rust-mobile/android-activity/pull/216))
|
||||
- The `ndk` and `ndk-sys` crates are now re-exported under `android_activity::ndk` and `android_activity::ndk_sys` ([#194](https://github.com/rust-mobile/android-activity/pull/194))
|
||||
- `AndroidApp::java_main_looper()` gives access to the `ALooper` for the Java main / UI thread ([#198](https://github.com/rust-mobile/android-activity/pull/198))
|
||||
- `AndroidApp::run_on_java_main_thread()` can be used to run boxed closures on the Java main / UI thread ([#232](https://github.com/rust-mobile/android-activity/pull/232))
|
||||
- Support for 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 / UI thread before the `android_main` Rust code starts running.
|
||||
|
||||
For example:
|
||||
|
||||
```rust
|
||||
use std::sync::OnceLock;
|
||||
use android_activity::OnCreateState;
|
||||
use jni::{JavaVM, refs::Global, objects::JObject};
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
fn android_on_create(state: &OnCreateState) {
|
||||
static APP_ONCE: OnceLock<()> = OnceLock::new();
|
||||
APP_ONCE.get_or_init(|| {
|
||||
// Initialize logging...
|
||||
//
|
||||
// Remember, `android_on_create` may be called multiple times but some
|
||||
// logger crates will panic if initialized multiple times.
|
||||
});
|
||||
let vm = unsafe { JavaVM::from_raw(state.vm_as_ptr().cast()) };
|
||||
let activity = state.activity_as_ptr() as jni::sys::jobject;
|
||||
// Although the thread is implicitly already attached (we are inside an onCreate native method)
|
||||
// using `vm.attach_current_thread` here will use the existing attachment, give us an `&Env`
|
||||
// reference and also catch Java exceptions.
|
||||
if let Err(err) = vm.attach_current_thread(|env| -> jni::errors::Result<()> {
|
||||
// SAFETY:
|
||||
// - The `Activity` reference / pointer is at least valid until we return
|
||||
// - By creating a `Cast` we ensure we can't accidentally delete the reference
|
||||
let activity = unsafe { env.as_cast_raw::<JObject>(&activity)? };
|
||||
|
||||
// Do something with the activity on the Java main thread...
|
||||
Ok(())
|
||||
}) {
|
||||
eprintln!("Failed to interact with Android SDK on Java main thread: {err:?}");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- Support for `MotionEvent` history, providing higher fidelity input data for things like stylus input (`native-activity` + `game-activity` backends). ([#218](https://github.com/rust-mobile/android-activity/pull/218))
|
||||
|
||||
|
||||
### Changed
|
||||
|
||||
- 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
|
||||
|
||||
**Android Packaging:** Your Android application must be packaged with the corresponding androidX, GameActivity 4.x.x library from Google.
|
||||
|
||||
This release has been tested with the [`androidx.games:games-activity:4.4.0` stable
|
||||
release](https://developer.android.com/jetpack/androidx/releases/games#games-activity-4.4.0), and is backwards
|
||||
compatible with the 4.0.0 stable release.
|
||||
|
||||
If you use Gradle to build your Android application, you can depend on the 4.4.0 release of the GameActivity library via:
|
||||
|
||||
```gradle
|
||||
dependencies {
|
||||
implementation 'androidx.appcompat:appcompat:1.7.1'
|
||||
|
||||
// To use the Games Activity library
|
||||
implementation "androidx.games:games-activity:4.4.0"
|
||||
// Note: don't include game-text-input separately, since it's integrated into game-activity
|
||||
}
|
||||
```
|
||||
|
||||
Note: there is no guarantee that later 4.x.x releases of GameActivity will be compatible with this release of
|
||||
`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))
|
||||
- *Safety* The `native-activity` backend clears its `ANativeActivity` ptr after `onDestroy` and `AndroidApp` remains safe to access after `android_main()` returns ([#234](https://github.com/rust-mobile/android-activity/pull/234))
|
||||
- *Safety* `AndroidApp::activity_as_ptr()` returns a pointer to a global reference that remains valid until `AndroidApp` is dropped, instead of the `ANativeActivity`'s `clazz` pointer which is only guaranteed to be valid until `onDestroy` returns (`native-activity` backend) ([#234](https://github.com/rust-mobile/android-activity/pull/234))
|
||||
- *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
|
||||
- rust-version bumped to 1.69.0 ([#156](https://github.com/rust-mobile/android-activity/pull/156))
|
||||
- Upgrade to `ndk-sys 0.6.0` and `ndk 0.9.0` ([#155](https://github.com/rust-mobile/android-activity/pull/155))
|
||||
|
||||
### Fixed
|
||||
- Check for null `saved_state_in` pointer from `NativeActivity`
|
||||
|
||||
## [0.5.2] - 2024-01-30
|
||||
|
||||
### Fixed
|
||||
- NativeActivity: OR with `EVENT_ACTION_MASK` when extracting action from `MotionEvent` - fixing multi-touch input ([#146](https://github.com/rust-mobile/android-activity/issues/146), [#147](https://github.com/rust-mobile/android-activity/pull/147))
|
||||
|
||||
## [0.5.1] - 2023-12-20
|
||||
|
||||
### Changed
|
||||
- Avoids depending on default features for `ndk` crate to avoid pulling in any `raw-window-handle` dependencies ([#142](https://github.com/rust-mobile/android-activity/pull/142))
|
||||
|
||||
**Note:** Technically, this could be observed as a breaking change in case you
|
||||
were depending on the `rwh_06` feature that was enabled by default in the
|
||||
`ndk` crate. This could be observed via the `NativeWindow` type (exposed via
|
||||
`AndroidApp::native_window()`) no longer implementing `rwh_06::HasWindowHandle`.
|
||||
|
||||
In the unlikely case that you were depending on the `ndk`'s `rwh_06` API
|
||||
being enabled by default via `android-activity`'s `ndk` dependency, your crate
|
||||
should explicitly enable the `rwh_06` feature for the `ndk` crate.
|
||||
|
||||
As far as could be seen though, it's not expected that anything was
|
||||
depending on this (e.g. anything based on Winit enables the `ndk` feature
|
||||
based on an equivalent `winit` feature).
|
||||
|
||||
The benefit of the change is that it can help avoid a redundant
|
||||
`raw-window-handle 0.6` dependency in projects that still need to use older
|
||||
(non-default) `raw-window-handle` versions. (Though note that this may be
|
||||
awkward to achieve in practice since other crates that depend on the `ndk`
|
||||
are still likely to use default features and also pull in
|
||||
`raw-window-handles 0.6`)
|
||||
|
||||
- The IO thread now gets named `stdio-to-logcat` and main thread is named `android_main` ([#145](https://github.com/rust-mobile/android-activity/pull/145))
|
||||
- Improved IO error handling in `stdio-to-logcat` IO loop. ([#133](https://github.com/rust-mobile/android-activity/pull/133))
|
||||
|
||||
## [0.5.0] - 2023-10-16
|
||||
### Added
|
||||
- Added `MotionEvent::action_button()` exposing the button associated with button press/release actions ([#138](https://github.com/rust-mobile/android-activity/pull/138))
|
||||
|
||||
### Changed
|
||||
- rust-version bumped to 0.68 ([#123](https://github.com/rust-mobile/android-activity/pull/123))
|
||||
- *Breaking*: updates to `ndk 0.8` and `ndk-sys 0.5` ([#128](https://github.com/rust-mobile/android-activity/pull/128))
|
||||
- The `Pointer` and `PointerIter` types from the `ndk` crate are no longer directly exposed in the public API ([#122](https://github.com/rust-mobile/android-activity/pull/122))
|
||||
- All input API enums based on Android SDK enums have been made runtime extensible via hidden `__Unknown(u32)` variants ([#131](https://github.com/rust-mobile/android-activity/pull/131))
|
||||
|
||||
## [0.5.0-beta.1] - 2023-08-15
|
||||
### Changed
|
||||
- Pulled in `ndk-sys 0.5.0-beta.0` and `ndk 0.8.0-beta.0` ([#113](https://github.com/rust-mobile/android-activity/pull/113))
|
||||
|
||||
## [0.5.0-beta.0] - 2023-08-15
|
||||
|
||||
### Added
|
||||
- Added `KeyEvent::meta_state()` for being able to query the state of meta keys, needed for character mapping ([#102](https://github.com/rust-mobile/android-activity/pull/102))
|
||||
- Added `KeyCharacterMap` JNI bindings to the corresponding Android SDK API ([#102](https://github.com/rust-mobile/android-activity/pull/102))
|
||||
- Added `AndroidApp::device_key_character_map()` for being able to get a `KeyCharacterMap` for a given `device_id` for unicode character mapping ([#102](https://github.com/rust-mobile/android-activity/pull/102))
|
||||
|
||||
<details>
|
||||
<summary>Click here for an example of how to handle unicode character mapping:</summary>
|
||||
|
||||
```rust
|
||||
let mut combining_accent = None;
|
||||
// Snip
|
||||
|
||||
|
||||
let combined_key_char = if let Ok(map) = app.device_key_character_map(device_id) {
|
||||
match map.get(key_event.key_code(), key_event.meta_state()) {
|
||||
Ok(KeyMapChar::Unicode(unicode)) => {
|
||||
let combined_unicode = if let Some(accent) = combining_accent {
|
||||
match map.get_dead_char(accent, unicode) {
|
||||
Ok(Some(key)) => {
|
||||
info!("KeyEvent: Combined '{unicode}' with accent '{accent}' to give '{key}'");
|
||||
Some(key)
|
||||
}
|
||||
Ok(None) => None,
|
||||
Err(err) => {
|
||||
log::error!("KeyEvent: Failed to combine 'dead key' accent '{accent}' with '{unicode}': {err:?}");
|
||||
None
|
||||
}
|
||||
}
|
||||
} else {
|
||||
info!("KeyEvent: Pressed '{unicode}'");
|
||||
Some(unicode)
|
||||
};
|
||||
combining_accent = None;
|
||||
combined_unicode.map(|unicode| KeyMapChar::Unicode(unicode))
|
||||
}
|
||||
Ok(KeyMapChar::CombiningAccent(accent)) => {
|
||||
info!("KeyEvent: Pressed 'dead key' combining accent '{accent}'");
|
||||
combining_accent = Some(accent);
|
||||
Some(KeyMapChar::CombiningAccent(accent))
|
||||
}
|
||||
Ok(KeyMapChar::None) => {
|
||||
info!("KeyEvent: Pressed non-unicode key");
|
||||
combining_accent = None;
|
||||
None
|
||||
}
|
||||
Err(err) => {
|
||||
log::error!("KeyEvent: Failed to get key map character: {err:?}");
|
||||
combining_accent = None;
|
||||
None
|
||||
}
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
```
|
||||
|
||||
</details>
|
||||
- Added `TextEvent` Input Method event for supporting text editing via virtual keyboards ([#24](https://github.com/rust-mobile/android-activity/pull/24))
|
||||
|
||||
### Changed
|
||||
- GameActivity updated to 2.0.2 (requires the corresponding 2.0.2 `.aar` release from Google) ([#88](https://github.com/rust-mobile/android-activity/pull/88))
|
||||
- `AndroidApp::input_events()` is replaced by `AndroidApp::input_events_iter()` ([#102](https://github.com/rust-mobile/android-activity/pull/102))
|
||||
|
||||
<details>
|
||||
<summary>Click here for an example of how to use `input_events_iter()`:</summary>
|
||||
|
||||
```rust
|
||||
match app.input_events_iter() {
|
||||
Ok(mut iter) => {
|
||||
loop {
|
||||
let read_input = iter.next(|event| {
|
||||
let handled = match event {
|
||||
InputEvent::KeyEvent(key_event) => {
|
||||
// Snip
|
||||
}
|
||||
InputEvent::MotionEvent(motion_event) => {
|
||||
// Snip
|
||||
}
|
||||
event => {
|
||||
// Snip
|
||||
}
|
||||
};
|
||||
|
||||
handled
|
||||
});
|
||||
|
||||
if !read_input {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
log::error!("Failed to get input events iterator: {err:?}");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
## [0.4.3] - 2023-07-30
|
||||
### Fixed
|
||||
- Fixed a deadlock in the `native-activity` backend while waiting for the native thread after getting an `onDestroy` callback from Java ([#94](https://github.com/rust-mobile/android-activity/pull/94))
|
||||
- Fixed numerous deadlocks in the `game-activity` backend with how it would wait for the native thread in various Java callbacks, after the app has returned from `android_main` ([#98](https://github.com/rust-mobile/android-activity/pull/98))
|
||||
|
||||
## [0.4.2] - 2023-06-17
|
||||
### 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] - 2023-02-16
|
||||
### Added
|
||||
- Added `AndroidApp::vm_as_ptr()` to expose JNI `JavaVM` pointer ([#60](https://github.com/rust-mobile/android-activity/issues/60))
|
||||
- Added `AndroidApp::activity_as_ptr()` to expose Android `Activity` JNI reference as pointer ([#60](https://github.com/rust-mobile/android-activity/issues/60))
|
||||
### Changed
|
||||
- Removed some overly-verbose logging in the `native-activity` backend ([#49](https://github.com/rust-mobile/android-activity/pull/49))
|
||||
### Removed
|
||||
- Most of the examples were moved to <https://github.com/rust-mobile/rust-android-examples> ([#50](https://github.com/rust-mobile/android-activity/pull/50))
|
||||
|
||||
## [0.4.0] - 2022-11-10
|
||||
### Changed
|
||||
- *Breaking*: `input_events` callback now return whether an event was handled or not to allow for fallback handling ([#31](https://github.com/rust-mobile/android-activity/issues/31))
|
||||
- The native-activity backend is now implemented in Rust only, without building on `android_native_app_glue.c` ([#35](https://github.com/rust-mobile/android-activity/pull/35))
|
||||
### Added
|
||||
- Added `Pointer::tool_type()` API to `GameActivity` backend for compatibility with `ndk` events API ([#38](https://github.com/rust-mobile/android-activity/pull/38))
|
||||
|
||||
## [0.3.0] - 2022-09-15
|
||||
### Added
|
||||
- `show/hide_sot_input` API for being able to show/hide a soft keyboard (other IME still pending)
|
||||
- `set_window_flags()` API for setting WindowManager params
|
||||
### Changed
|
||||
- *Breaking*: Created extensible, `#[non_exhaustive]` `InputEvent` wrapper enum instead of exposing `ndk` type directly
|
||||
|
||||
## [0.2.0] - 2022-08-25
|
||||
### Added
|
||||
- Emit an `InputAvailable` event for new input with `NativeActivity` and `GameActivity`
|
||||
enabling gui apps that don't render continuously
|
||||
@@ -22,6 +331,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
### Changed
|
||||
- Documentation fixes
|
||||
|
||||
## [0.1] - 2022-07-04
|
||||
## [0.1.0] - 2022-07-04
|
||||
### Added
|
||||
- Initial release
|
||||
- Initial release
|
||||
|
||||
[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
|
||||
[0.5.0]: https://github.com/rust-mobile/android-activity/compare/v0.4.3...v0.5.0
|
||||
[0.4.3]: https://github.com/rust-mobile/android-activity/compare/v0.4.2...v0.4.3
|
||||
[0.4.2]: https://github.com/rust-mobile/android-activity/compare/v0.4.1...v0.4.2
|
||||
[0.4.1]: https://github.com/rust-mobile/android-activity/compare/v0.4.0...v0.4.1
|
||||
[0.4.0]: https://github.com/rust-mobile/android-activity/compare/v0.3.0...v0.4.0
|
||||
[0.3.0]: https://github.com/rust-mobile/android-activity/compare/v0.2.0...v0.3.0
|
||||
[0.2.0]: https://github.com/rust-mobile/android-activity/compare/v0.1.1...v0.2.0
|
||||
[0.1.1]: https://github.com/rust-mobile/android-activity/compare/v0.1.0...v0.1.1
|
||||
[0.1.0]: https://github.com/rust-mobile/android-activity/releases/tag/v0.1.0
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
[package]
|
||||
name = "android-activity"
|
||||
version = "0.3.0"
|
||||
version = "0.6.1"
|
||||
edition = "2021"
|
||||
keywords = ["android", "ndk"]
|
||||
readme = "../README.md"
|
||||
homepage = "https://github.com/rib/android-activity"
|
||||
repository = "https://github.com/rib/android-activity"
|
||||
homepage = "https://github.com/rust-mobile/android-activity"
|
||||
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"
|
||||
include = ["/build.rs", "/android-games-sdk", "/LICENSE*", "/src"]
|
||||
|
||||
rust-version = "1.85.0"
|
||||
|
||||
[features]
|
||||
# Note: we don't enable any backend by default since features
|
||||
@@ -17,23 +20,29 @@ license = "MIT OR Apache-2.0"
|
||||
#
|
||||
# In general it's only the final application crate that needs
|
||||
# to decide on a backend.
|
||||
default=[]
|
||||
game-activity = []
|
||||
default = []
|
||||
game-activity = ["simd_cesu8"]
|
||||
native-activity = []
|
||||
api-level-30 = ["ndk/api-level-30"]
|
||||
|
||||
[dependencies]
|
||||
log = "0.4"
|
||||
jni-sys = "0.3"
|
||||
ndk = "0.7"
|
||||
ndk-sys = "0.4"
|
||||
ndk-context = "0.1"
|
||||
simd_cesu8 = { version = "1.0.1", optional = true }
|
||||
jni = "0.22.4"
|
||||
ndk-sys = "0.6.0"
|
||||
ndk = { version = "0.9.0", default-features = false }
|
||||
ndk-context = "0.1.1"
|
||||
android-properties = "0.2"
|
||||
num_enum = "0.5"
|
||||
bitflags = "1.3"
|
||||
libc = "0.2"
|
||||
num_enum = "0.7"
|
||||
bitflags = "2.0"
|
||||
libc = "0.2.139"
|
||||
thiserror = "2"
|
||||
|
||||
[build-dependencies]
|
||||
cc = { version = "1.0", features = ["parallel"] }
|
||||
cc = { version = "1.0.42", features = ["parallel"] }
|
||||
|
||||
[dev-dependencies]
|
||||
jni = "0.22.4"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = [
|
||||
@@ -43,4 +52,4 @@ targets = [
|
||||
"x86_64-linux-android",
|
||||
]
|
||||
|
||||
rustdoc-args = ["--cfg", "docsrs" ]
|
||||
rustdoc-args = ["--cfg", "docsrs"]
|
||||
|
||||
@@ -1,12 +1,24 @@
|
||||
The third-party glue code, under the native-activity-csrc/ and game-activity-csrc/ directories
|
||||
is covered by the Apache 2.0 license only:
|
||||
# License
|
||||
|
||||
Apache License, Version 2.0 (docs/LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
|
||||
## GameActivity
|
||||
|
||||
The third-party glue code, under the game-activity-csrc/ directory is covered by
|
||||
the Apache 2.0 license only:
|
||||
|
||||
Apache License, Version 2.0 (LICENSE-APACHE or <http://www.apache.org/licenses/LICENSE-2.0>)
|
||||
|
||||
## SDK Documentation
|
||||
|
||||
Documentation for APIs that are direct bindings of Android platform APIs are covered
|
||||
by the Apache 2.0 license only:
|
||||
|
||||
Apache License, Version 2.0 (LICENSE-APACHE or <http://www.apache.org/licenses/LICENSE-2.0>)
|
||||
|
||||
## android-activity
|
||||
|
||||
All other code is dual-licensed under either
|
||||
|
||||
* MIT License (docs/LICENSE-MIT or http://opensource.org/licenses/MIT)
|
||||
* Apache License, Version 2.0 (docs/LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
|
||||
- MIT License (LICENSE-MIT or <http://opensource.org/licenses/MIT>)
|
||||
- Apache License, Version 2.0 (LICENSE-APACHE or <http://www.apache.org/licenses/LICENSE-2.0>)
|
||||
|
||||
at your option.
|
||||
at your option.
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
# android-games-sdk
|
||||
|
||||
This is an imported copy of the native "prefab" source for `GameActivity` and
|
||||
`GameTextInput`, from our fork of Google's
|
||||
[android-games-sdk](https://github.com/rust-mobile/android-games-sdk).
|
||||
|
||||
We use an external fork to track our integration patches on top of the Android
|
||||
Game Development Kit (AGDK) in a way that it is easier to update to new upstream
|
||||
versions. It also makes it easier to try and upstream changes when we fix bugs.
|
||||
|
||||
## Updating to new agdk version checklist
|
||||
|
||||
This is a basic checklist for things that need to be done when updating to a new
|
||||
agdk version:
|
||||
|
||||
- [ ] Create a new integration branch based on our last integrated branch and
|
||||
rebase that on the latest *release* branch from Google:
|
||||
|
||||
```bash
|
||||
git clone git@github.com:rust-mobile/android-games-sdk.git
|
||||
cd android-games-sdk
|
||||
git remote add google https://android.googlesource.com/platform/frameworks/opt/gamesdk
|
||||
git fetch google
|
||||
git checkout -b android-activity-5.0.0 origin/android-activity-4.0.0
|
||||
git rebase --onto google/android-games-sdk-game-activity-release <base>
|
||||
# (where <base> is the upstream commit ID below our stack of integration patches)
|
||||
```
|
||||
|
||||
- [ ] Set the `ANDROID_GAMES_SDK` environment variable so you can build
|
||||
android-activity against your external games-sdk branch while updating.
|
||||
- [ ] Re-generate the `GameActivity` FFI bindings with `./generate-bindings.sh`
|
||||
(this can be done with `ANDROID_GAMES_SDK` set in your environment and also
|
||||
repeated after importing)
|
||||
- [ ] Update [build.rs](../build.rs) with any new includes and src files
|
||||
- [ ] Update the `src/game-activity` backend as needed
|
||||
- [ ] Push a new `android-games-sdk` branch like `android-activity-5.0.0` that
|
||||
can be referenced when importing a copy into `android-activity`
|
||||
- [ ] Review and run `./import-games-sdk.sh` when ready to copy external AGDK
|
||||
code into this repo
|
||||
- [ ] Clearly reference the branch name and commit hash from the
|
||||
`android-games-sdk` repo in the `android-activity` commit that imports new
|
||||
games-sdk source.
|
||||
- [ ] Update CHANGELOG.md as required
|
||||
@@ -31,17 +31,44 @@
|
||||
#include <android/input.h>
|
||||
#include <android/native_window.h>
|
||||
#include <android/rect.h>
|
||||
#include <common/gamesdk_common.h>
|
||||
#include <game-activity/GameActivityEvents.h>
|
||||
#include <game-text-input/gametextinput.h>
|
||||
#include <jni.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include "game-text-input/gametextinput.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define GAMEACTIVITY_VERSION_REVISION a0e943c3a84fd7f344c3d36cdf4e88fd595f81b8
|
||||
#define GAMEACTIVITY_MAJOR_VERSION 4
|
||||
#define GAMEACTIVITY_MINOR_VERSION 4
|
||||
#define GAMEACTIVITY_BUGFIX_VERSION 0
|
||||
#define GAMEACTIVITY_PACKED_VERSION \
|
||||
ANDROID_GAMESDK_PACKED_VERSION(GAMEACTIVITY_MAJOR_VERSION, GAMEACTIVITY_MINOR_VERSION, \
|
||||
GAMEACTIVITY_BUGFIX_VERSION)
|
||||
|
||||
/**
|
||||
* The type of a component for which to retrieve insets. See
|
||||
* https://developer.android.com/reference/androidx/core/view/WindowInsetsCompat.Type
|
||||
*/
|
||||
typedef enum GameCommonInsetsType : uint8_t {
|
||||
GAMECOMMON_INSETS_TYPE_CAPTION_BAR = 0,
|
||||
GAMECOMMON_INSETS_TYPE_DISPLAY_CUTOUT,
|
||||
GAMECOMMON_INSETS_TYPE_IME,
|
||||
GAMECOMMON_INSETS_TYPE_MANDATORY_SYSTEM_GESTURES,
|
||||
GAMECOMMON_INSETS_TYPE_NAVIGATION_BARS,
|
||||
GAMECOMMON_INSETS_TYPE_STATUS_BARS,
|
||||
GAMECOMMON_INSETS_TYPE_SYSTEM_BARS,
|
||||
GAMECOMMON_INSETS_TYPE_SYSTEM_GESTURES,
|
||||
GAMECOMMON_INSETS_TYPE_TAPABLE_ELEMENT,
|
||||
GAMECOMMON_INSETS_TYPE_WATERFALL,
|
||||
GAMECOMMON_INSETS_TYPE_COUNT
|
||||
} GameCommonInsetsType;
|
||||
|
||||
/**
|
||||
* {@link GameActivityCallbacks}
|
||||
*/
|
||||
@@ -115,204 +142,11 @@ typedef struct GameActivity {
|
||||
const char* obbPath;
|
||||
} GameActivity;
|
||||
|
||||
/**
|
||||
* The maximum number of axes supported in an Android MotionEvent.
|
||||
* See https://developer.android.com/ndk/reference/group/input.
|
||||
*/
|
||||
#define GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT 48
|
||||
|
||||
/**
|
||||
* \brief Describe information about a pointer, found in a
|
||||
* GameActivityMotionEvent.
|
||||
*
|
||||
* You can read values directly from this structure, or use helper functions
|
||||
* (`GameActivityPointerAxes_getX`, `GameActivityPointerAxes_getY` and
|
||||
* `GameActivityPointerAxes_getAxisValue`).
|
||||
*
|
||||
* The X axis and Y axis are enabled by default but any other axis that you want
|
||||
* to read **must** be enabled first, using
|
||||
* `GameActivityPointerAxes_enableAxis`.
|
||||
*
|
||||
* \see GameActivityMotionEvent
|
||||
*/
|
||||
typedef struct GameActivityPointerAxes {
|
||||
int32_t id;
|
||||
float axisValues[GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT];
|
||||
float rawX;
|
||||
float rawY;
|
||||
} GameActivityPointerAxes;
|
||||
|
||||
typedef struct GameActivityHistoricalPointerAxes {
|
||||
int64_t eventTime;
|
||||
float axisValues[GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT];
|
||||
} GameActivityHistoricalPointerAxes;
|
||||
|
||||
/** \brief Get the current X coordinate of the pointer. */
|
||||
inline float GameActivityPointerAxes_getX(
|
||||
const GameActivityPointerAxes* pointerInfo) {
|
||||
return pointerInfo->axisValues[AMOTION_EVENT_AXIS_X];
|
||||
}
|
||||
|
||||
/** \brief Get the current Y coordinate of the pointer. */
|
||||
inline float GameActivityPointerAxes_getY(
|
||||
const GameActivityPointerAxes* pointerInfo) {
|
||||
return pointerInfo->axisValues[AMOTION_EVENT_AXIS_Y];
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Enable the specified axis, so that its value is reported in the
|
||||
* GameActivityPointerAxes structures stored in a motion event.
|
||||
*
|
||||
* You must enable any axis that you want to read, apart from
|
||||
* `AMOTION_EVENT_AXIS_X` and `AMOTION_EVENT_AXIS_Y` that are enabled by
|
||||
* default.
|
||||
*
|
||||
* If the axis index is out of range, nothing is done.
|
||||
*/
|
||||
void GameActivityPointerAxes_enableAxis(int32_t axis);
|
||||
|
||||
/**
|
||||
* \brief Disable the specified axis. Its value won't be reported in the
|
||||
* GameActivityPointerAxes structures stored in a motion event anymore.
|
||||
*
|
||||
* Apart from X and Y, any axis that you want to read **must** be enabled first,
|
||||
* using `GameActivityPointerAxes_enableAxis`.
|
||||
*
|
||||
* If the axis index is out of range, nothing is done.
|
||||
*/
|
||||
void GameActivityPointerAxes_disableAxis(int32_t axis);
|
||||
|
||||
/**
|
||||
* \brief Enable the specified axis, so that its value is reported in the
|
||||
* GameActivityHistoricalPointerAxes structures associated with a motion event.
|
||||
*
|
||||
* You must enable any axis that you want to read (no axes are enabled by
|
||||
* default).
|
||||
*
|
||||
* If the axis index is out of range, nothing is done.
|
||||
*/
|
||||
void GameActivityHistoricalPointerAxes_enableAxis(int32_t axis);
|
||||
|
||||
/**
|
||||
* \brief Disable the specified axis. Its value won't be reported in the
|
||||
* GameActivityHistoricalPointerAxes structures associated with motion events
|
||||
* anymore.
|
||||
*
|
||||
* If the axis index is out of range, nothing is done.
|
||||
*/
|
||||
void GameActivityHistoricalPointerAxes_disableAxis(int32_t axis);
|
||||
|
||||
/**
|
||||
* \brief Get the value of the requested axis.
|
||||
*
|
||||
* Apart from X and Y, any axis that you want to read **must** be enabled first,
|
||||
* using `GameActivityPointerAxes_enableAxis`.
|
||||
*
|
||||
* Find the valid enums for the axis (`AMOTION_EVENT_AXIS_X`,
|
||||
* `AMOTION_EVENT_AXIS_Y`, `AMOTION_EVENT_AXIS_PRESSURE`...)
|
||||
* in https://developer.android.com/ndk/reference/group/input.
|
||||
*
|
||||
* @param pointerInfo The structure containing information about the pointer,
|
||||
* obtained from GameActivityMotionEvent.
|
||||
* @param axis The axis to get the value from
|
||||
* @return The value of the axis, or 0 if the axis is invalid or was not
|
||||
* enabled.
|
||||
*/
|
||||
inline float GameActivityPointerAxes_getAxisValue(
|
||||
GameActivityPointerAxes* pointerInfo, int32_t axis) {
|
||||
if (axis < 0 || axis >= GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return pointerInfo->axisValues[axis];
|
||||
}
|
||||
|
||||
/**
|
||||
* The maximum number of pointers returned inside a motion event.
|
||||
*/
|
||||
#if (defined GAMEACTIVITY_MAX_NUM_POINTERS_IN_MOTION_EVENT_OVERRIDE)
|
||||
#define GAMEACTIVITY_MAX_NUM_POINTERS_IN_MOTION_EVENT \
|
||||
GAMEACTIVITY_MAX_NUM_POINTERS_IN_MOTION_EVENT_OVERRIDE
|
||||
#else
|
||||
#define GAMEACTIVITY_MAX_NUM_POINTERS_IN_MOTION_EVENT 8
|
||||
#endif
|
||||
|
||||
/**
|
||||
* The maximum number of historic samples associated with a single motion event.
|
||||
*/
|
||||
#if (defined GAMEACTIVITY_MAX_NUM_HISTORICAL_IN_MOTION_EVENT_OVERRIDE)
|
||||
#define GAMEACTIVITY_MAX_NUM_HISTORICAL_IN_MOTION_EVENT \
|
||||
GAMEACTIVITY_MAX_NUM_HISTORICAL_IN_MOTION_EVENT_OVERRIDE
|
||||
#else
|
||||
#define GAMEACTIVITY_MAX_NUM_HISTORICAL_IN_MOTION_EVENT 8
|
||||
#endif
|
||||
|
||||
/**
|
||||
* \brief Describe a motion event that happened on the GameActivity SurfaceView.
|
||||
*
|
||||
* This is 1:1 mapping to the information contained in a Java `MotionEvent`
|
||||
* (see https://developer.android.com/reference/android/view/MotionEvent).
|
||||
*/
|
||||
typedef struct GameActivityMotionEvent {
|
||||
int32_t deviceId;
|
||||
int32_t source;
|
||||
int32_t action;
|
||||
|
||||
int64_t eventTime;
|
||||
int64_t downTime;
|
||||
|
||||
int32_t flags;
|
||||
int32_t metaState;
|
||||
|
||||
int32_t actionButton;
|
||||
int32_t buttonState;
|
||||
int32_t classification;
|
||||
int32_t edgeFlags;
|
||||
|
||||
uint32_t pointerCount;
|
||||
GameActivityPointerAxes
|
||||
pointers[GAMEACTIVITY_MAX_NUM_POINTERS_IN_MOTION_EVENT];
|
||||
|
||||
float precisionX;
|
||||
float precisionY;
|
||||
|
||||
int16_t historicalStart;
|
||||
|
||||
// Note the actual buffer of historical data has a length of
|
||||
// pointerCount * historicalCount, since the historical axis
|
||||
// data is per-pointer.
|
||||
int16_t historicalCount;
|
||||
} GameActivityMotionEvent;
|
||||
|
||||
/**
|
||||
* \brief Describe a key event that happened on the GameActivity SurfaceView.
|
||||
*
|
||||
* This is 1:1 mapping to the information contained in a Java `KeyEvent`
|
||||
* (see https://developer.android.com/reference/android/view/KeyEvent).
|
||||
*/
|
||||
typedef struct GameActivityKeyEvent {
|
||||
int32_t deviceId;
|
||||
int32_t source;
|
||||
int32_t action;
|
||||
|
||||
int64_t eventTime;
|
||||
int64_t downTime;
|
||||
|
||||
int32_t flags;
|
||||
int32_t metaState;
|
||||
|
||||
int32_t modifiers;
|
||||
int32_t repeatCount;
|
||||
int32_t keyCode;
|
||||
int32_t scanCode;
|
||||
} GameActivityKeyEvent;
|
||||
|
||||
/**
|
||||
* A function the user should call from their callback with the data, its length
|
||||
* and the library- supplied context.
|
||||
*/
|
||||
typedef void (*SaveInstanceStateRecallback)(const char* bytes, int len,
|
||||
void* context);
|
||||
typedef void (*SaveInstanceStateRecallback)(const char* bytes, int len, void* context);
|
||||
|
||||
/**
|
||||
* These are the callbacks the framework makes into a native application.
|
||||
@@ -341,8 +175,7 @@ typedef struct GameActivityCallbacks {
|
||||
* that the saved state will be persisted, so it can not contain any active
|
||||
* entities (pointers to memory, file descriptors, etc).
|
||||
*/
|
||||
void (*onSaveInstanceState)(GameActivity* activity,
|
||||
SaveInstanceStateRecallback recallback,
|
||||
void (*onSaveInstanceState)(GameActivity* activity, SaveInstanceStateRecallback recallback,
|
||||
void* context);
|
||||
|
||||
/**
|
||||
@@ -373,16 +206,15 @@ typedef struct GameActivityCallbacks {
|
||||
* The drawing window for this native activity has been created. You
|
||||
* can use the given native window object to start drawing.
|
||||
*/
|
||||
void (*onNativeWindowCreated)(GameActivity* activity,
|
||||
ANativeWindow* window);
|
||||
void (*onNativeWindowCreated)(GameActivity* activity, ANativeWindow* window);
|
||||
|
||||
/**
|
||||
* The drawing window for this native activity has been resized. You should
|
||||
* retrieve the new size from the window and ensure that your rendering in
|
||||
* it now matches.
|
||||
*/
|
||||
void (*onNativeWindowResized)(GameActivity* activity, ANativeWindow* window,
|
||||
int32_t newWidth, int32_t newHeight);
|
||||
void (*onNativeWindowResized)(GameActivity* activity, ANativeWindow* window, int32_t newWidth,
|
||||
int32_t newHeight);
|
||||
|
||||
/**
|
||||
* The drawing window for this native activity needs to be redrawn. To
|
||||
@@ -390,8 +222,7 @@ typedef struct GameActivityCallbacks {
|
||||
* rotation), applications should not return from this function until they
|
||||
* have finished drawing their window in its current state.
|
||||
*/
|
||||
void (*onNativeWindowRedrawNeeded)(GameActivity* activity,
|
||||
ANativeWindow* window);
|
||||
void (*onNativeWindowRedrawNeeded)(GameActivity* activity, ANativeWindow* window);
|
||||
|
||||
/**
|
||||
* The drawing window for this native activity is going to be destroyed.
|
||||
@@ -401,11 +232,10 @@ typedef struct GameActivityCallbacks {
|
||||
* properly synchronize with the other thread to stop its drawing before
|
||||
* returning from here.
|
||||
*/
|
||||
void (*onNativeWindowDestroyed)(GameActivity* activity,
|
||||
ANativeWindow* window);
|
||||
void (*onNativeWindowDestroyed)(GameActivity* activity, ANativeWindow* window);
|
||||
|
||||
/**
|
||||
* The current device AConfiguration has changed. The new configuration can
|
||||
* The current device AConfiguration has changed. The new configuration can
|
||||
* be retrieved from assetManager.
|
||||
*/
|
||||
void (*onConfigurationChanged)(GameActivity* activity);
|
||||
@@ -422,18 +252,14 @@ typedef struct GameActivityCallbacks {
|
||||
* SurfaceView. Ownership of `event` is maintained by the library and it is
|
||||
* only valid during the callback.
|
||||
*/
|
||||
bool (*onTouchEvent)(GameActivity* activity,
|
||||
const GameActivityMotionEvent* event,
|
||||
const GameActivityHistoricalPointerAxes* historical,
|
||||
int historicalLen);
|
||||
bool (*onTouchEvent)(GameActivity* activity, const GameActivityMotionEvent* event);
|
||||
|
||||
/**
|
||||
* Callback called for every key down event on the GameActivity SurfaceView.
|
||||
* Ownership of `event` is maintained by the library and it is only valid
|
||||
* during the callback.
|
||||
*/
|
||||
bool (*onKeyDown)(GameActivity* activity,
|
||||
const GameActivityKeyEvent* event);
|
||||
bool (*onKeyDown)(GameActivity* activity, const GameActivityKeyEvent* event);
|
||||
|
||||
/**
|
||||
* Callback called for every key up event on the GameActivity SurfaceView.
|
||||
@@ -447,45 +273,31 @@ typedef struct GameActivityCallbacks {
|
||||
* Ownership of `state` is maintained by the library and it is only valid
|
||||
* during the callback.
|
||||
*/
|
||||
void (*onTextInputEvent)(GameActivity* activity,
|
||||
const GameTextInputState* state);
|
||||
void (*onTextInputEvent)(GameActivity* activity, const GameTextInputState* state);
|
||||
|
||||
/**
|
||||
* Callback called when WindowInsets of the main app window have changed.
|
||||
* Call GameActivity_getWindowInsets to retrieve the insets themselves.
|
||||
*/
|
||||
void (*onWindowInsetsChanged)(GameActivity* activity);
|
||||
|
||||
/**
|
||||
* Callback called when the rectangle in the window where the content
|
||||
* should be placed has changed.
|
||||
*/
|
||||
void (*onContentRectChanged)(GameActivity* activity, const ARect* rect);
|
||||
|
||||
/**
|
||||
* Callback called when the software keyboard is shown or hidden.
|
||||
*/
|
||||
void (*onSoftwareKeyboardVisibilityChanged)(GameActivity* activity, bool visible);
|
||||
|
||||
/**
|
||||
* Callback called when the software keyboard is shown or hidden.
|
||||
*/
|
||||
bool (*onEditorAction)(GameActivity* activity, int action);
|
||||
} GameActivityCallbacks;
|
||||
|
||||
/**
|
||||
* \brief Convert a Java `MotionEvent` to a `GameActivityMotionEvent`.
|
||||
*
|
||||
* This is done automatically by the GameActivity: see `onTouchEvent` to set
|
||||
* a callback to consume the received events.
|
||||
* This function can be used if you re-implement events handling in your own
|
||||
* activity. On return, the out_event->historicalStart will be zero, and should
|
||||
* be updated to index into whatever buffer out_historical is copied.
|
||||
* On return the length of out_historical is
|
||||
* (out_event->pointerCount x out_event->historicalCount) and is in a
|
||||
* pointer-major order (i.e. all axis for a pointer are contiguous)
|
||||
* Ownership of out_event is maintained by the caller.
|
||||
*/
|
||||
int GameActivityMotionEvent_fromJava(JNIEnv* env, jobject motionEvent,
|
||||
GameActivityMotionEvent* out_event,
|
||||
GameActivityHistoricalPointerAxes *out_historical);
|
||||
|
||||
/**
|
||||
* \brief Convert a Java `KeyEvent` to a `GameActivityKeyEvent`.
|
||||
*
|
||||
* This is done automatically by the GameActivity: see `onKeyUp` and `onKeyDown`
|
||||
* to set a callback to consume the received events.
|
||||
* This function can be used if you re-implement events handling in your own
|
||||
* activity.
|
||||
* Ownership of out_event is maintained by the caller.
|
||||
*/
|
||||
void GameActivityKeyEvent_fromJava(JNIEnv* env, jobject motionEvent,
|
||||
GameActivityKeyEvent* out_event);
|
||||
|
||||
/**
|
||||
* This is the function that must be in the native code to instantiate the
|
||||
* application's native activity. It is called with the activity instance (see
|
||||
@@ -517,7 +329,7 @@ void GameActivity_finish(GameActivity* activity);
|
||||
* Flags for GameActivity_setWindowFlags,
|
||||
* as per the Java API at android.view.WindowManager.LayoutParams.
|
||||
*/
|
||||
enum GameActivitySetWindowFlags {
|
||||
enum GameActivitySetWindowFlags : uint32_t {
|
||||
/**
|
||||
* As long as this window is visible to the user, allow the lock
|
||||
* screen to activate while the screen is on. This can be used
|
||||
@@ -710,14 +522,13 @@ enum GameActivitySetWindowFlags {
|
||||
* *any* thread; it will send a message to the main thread of the process
|
||||
* where the Java finish call will take place.
|
||||
*/
|
||||
void GameActivity_setWindowFlags(GameActivity* activity, uint32_t addFlags,
|
||||
uint32_t removeFlags);
|
||||
void GameActivity_setWindowFlags(GameActivity* activity, uint32_t addFlags, uint32_t removeFlags);
|
||||
|
||||
/**
|
||||
* Flags for GameActivity_showSoftInput; see the Java InputMethodManager
|
||||
* API for documentation.
|
||||
*/
|
||||
enum GameActivityShowSoftInputFlags {
|
||||
enum GameActivityShowSoftInputFlags : uint8_t {
|
||||
/**
|
||||
* Implicit request to show the input window, not as the result
|
||||
* of a direct request by the user.
|
||||
@@ -740,22 +551,27 @@ enum GameActivityShowSoftInputFlags {
|
||||
*/
|
||||
void GameActivity_showSoftInput(GameActivity* activity, uint32_t flags);
|
||||
|
||||
/**
|
||||
* Restarts the input method. Calls InputMethodManager.restartInput().
|
||||
* 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 call will take place.
|
||||
*/
|
||||
void GameActivity_restartInput(GameActivity* activity);
|
||||
|
||||
/**
|
||||
* Set the text entry state (see documentation of the GameTextInputState struct
|
||||
* in the Game Text Input library reference).
|
||||
*
|
||||
* Ownership of the state is maintained by the caller.
|
||||
*/
|
||||
void GameActivity_setTextInputState(GameActivity* activity,
|
||||
const GameTextInputState* state);
|
||||
void GameActivity_setTextInputState(GameActivity* activity, const GameTextInputState* state);
|
||||
|
||||
/**
|
||||
* Get the last-received text entry state (see documentation of the
|
||||
* GameTextInputState struct in the Game Text Input library reference).
|
||||
*
|
||||
*/
|
||||
void GameActivity_getTextInputState(GameActivity* activity,
|
||||
GameTextInputGetStateCallback callback,
|
||||
void GameActivity_getTextInputState(GameActivity* activity, GameTextInputGetStateCallback callback,
|
||||
void* context);
|
||||
|
||||
/**
|
||||
@@ -767,7 +583,7 @@ GameTextInput* GameActivity_getTextInput(const GameActivity* activity);
|
||||
* Flags for GameActivity_hideSoftInput; see the Java InputMethodManager
|
||||
* API for documentation.
|
||||
*/
|
||||
enum GameActivityHideSoftInputFlags {
|
||||
enum GameActivityHideSoftInputFlags : uint16_t {
|
||||
/**
|
||||
* The soft input window should only be hidden if it was not
|
||||
* explicitly shown by the user.
|
||||
@@ -794,8 +610,12 @@ void GameActivity_hideSoftInput(GameActivity* activity, uint32_t flags);
|
||||
* for more details.
|
||||
* You can use these insets to influence what you show on the screen.
|
||||
*/
|
||||
void GameActivity_getWindowInsets(GameActivity* activity,
|
||||
GameCommonInsetsType type, ARect* insets);
|
||||
void GameActivity_getWindowInsets(GameActivity* activity, GameCommonInsetsType type, ARect* insets);
|
||||
|
||||
/**
|
||||
* Tells whether the software keyboard is visible or not.
|
||||
*/
|
||||
bool GameActivity_isSoftwareKeyboardVisible(GameActivity* activity);
|
||||
|
||||
/**
|
||||
* Set options on how the IME behaves when it is requested for text input.
|
||||
@@ -803,12 +623,72 @@ void GameActivity_getWindowInsets(GameActivity* activity,
|
||||
* https://developer.android.com/reference/android/view/inputmethod/EditorInfo
|
||||
* for the meaning of inputType, actionId and imeOptions.
|
||||
*
|
||||
* Note that this function will attach the current thread to the JVM if it is
|
||||
* not already attached, so the caller must detach the thread from the JVM
|
||||
* before the thread is destroyed using DetachCurrentThread.
|
||||
* <b>Note:</b> currently only TYPE_NULL AND TYPE_CLASS_NUMBER are supported.
|
||||
*/
|
||||
void GameActivity_setImeEditorInfo(GameActivity* activity, int inputType,
|
||||
int actionId, int imeOptions);
|
||||
void GameActivity_setImeEditorInfo(GameActivity* activity, enum GameTextInputType inputType,
|
||||
enum GameTextInputActionType actionId,
|
||||
enum GameTextInputImeOptions imeOptions);
|
||||
|
||||
/**
|
||||
* These are getters for Configuration class members. They may be called from
|
||||
* any thread.
|
||||
*/
|
||||
int GameActivity_getOrientation(GameActivity* activity);
|
||||
int GameActivity_getColorMode(GameActivity* activity);
|
||||
int GameActivity_getDensityDpi(GameActivity* activity);
|
||||
float GameActivity_getFontScale(GameActivity* activity);
|
||||
int GameActivity_getFontWeightAdjustment(GameActivity* activity);
|
||||
int GameActivity_getHardKeyboardHidden(GameActivity* activity);
|
||||
int GameActivity_getKeyboard(GameActivity* activity);
|
||||
int GameActivity_getKeyboardHidden(GameActivity* activity);
|
||||
int GameActivity_getLocalesCount(GameActivity* activity);
|
||||
int GameActivity_getMcc(GameActivity* activity);
|
||||
int GameActivity_getMnc(GameActivity* activity);
|
||||
int GameActivity_getNavigation(GameActivity* activity);
|
||||
int GameActivity_getNavigationHidden(GameActivity* activity);
|
||||
int GameActivity_getOrientation(GameActivity* activity);
|
||||
int GameActivity_getScreenHeightDp(GameActivity* activity);
|
||||
int GameActivity_getScreenLayout(GameActivity* activity);
|
||||
int GameActivity_getScreenWidthDp(GameActivity* activity);
|
||||
int GameActivity_getSmallestScreenWidthDp(GameActivity* activity);
|
||||
int GameActivity_getTouchscreen(GameActivity* activity);
|
||||
int GameActivity_getUIMode(GameActivity* activity);
|
||||
|
||||
/**
|
||||
* The functions below return Java locale information.
|
||||
*
|
||||
* In simple cases there will be just one locale, but it's possible tha
|
||||
* there are more than one locale objects. Users are encouraged to write code
|
||||
* that handles all locales and not just the first one.
|
||||
*
|
||||
* The functions in the block below return string values in the provided buffer.
|
||||
* Return value is zero if there were no errors, otherwise it's non-zero.
|
||||
* If the return value is zero, `dst` will contain a null-terminated string:
|
||||
* strlen(dst) <= dst_size - 1.
|
||||
* If the return value is non-zero, the content of dst is undefined.
|
||||
*
|
||||
* Parameters:
|
||||
*
|
||||
* dst, dst_size: define a receiver buffer. Locale string can be something
|
||||
* short like "EN/EN", but it may be longer. You should be safe with a buffer
|
||||
* size of 256 bytes.
|
||||
*
|
||||
* If the buffer is too small, ENOBUFS is returned. Try allocating a larger
|
||||
* buffer in this case.
|
||||
*
|
||||
* localeIdx must be between 0 and the value of GameActivity_getLocalesCount().
|
||||
* If localeIdx is out of range, EINVAL is returned.
|
||||
*
|
||||
* Refer to Java documentation of locales for more information.
|
||||
*/
|
||||
int GameActivity_getLocaleLanguage(char* dst, size_t dst_size, GameActivity* activity,
|
||||
size_t localeIdx);
|
||||
int GameActivity_getLocaleScript(char* dst, size_t dst_size, GameActivity* activity,
|
||||
size_t localeIdx);
|
||||
int GameActivity_getLocaleCountry(char* dst, size_t dst_size, GameActivity* activity,
|
||||
size_t localeIdx);
|
||||
int GameActivity_getLocaleVariant(char* dst, size_t dst_size, GameActivity* activity,
|
||||
size_t localeIdx);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
@@ -816,4 +696,4 @@ void GameActivity_setImeEditorInfo(GameActivity* activity, int inputType,
|
||||
|
||||
/** @} */
|
||||
|
||||
#endif // ANDROID_GAME_SDK_GAME_ACTIVITY_H
|
||||
#endif // ANDROID_GAME_SDK_GAME_ACTIVITY_H
|
||||
@@ -0,0 +1,292 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @addtogroup GameActivity Game Activity Events
|
||||
* The interface to use Game Activity Events.
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file GameActivityEvents.h
|
||||
*/
|
||||
#ifndef ANDROID_GAME_SDK_GAME_ACTIVITY_EVENTS_H
|
||||
#define ANDROID_GAME_SDK_GAME_ACTIVITY_EVENTS_H
|
||||
|
||||
#include <android/input.h>
|
||||
#include <jni.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* The maximum number of axes supported in an Android MotionEvent.
|
||||
* See https://developer.android.com/ndk/reference/group/input.
|
||||
*/
|
||||
#define GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT 48
|
||||
|
||||
/**
|
||||
* \brief Describe information about a pointer, found in a
|
||||
* GameActivityMotionEvent.
|
||||
*
|
||||
* You can read values directly from this structure, or use helper functions
|
||||
* (`GameActivityPointerAxes_getX`, `GameActivityPointerAxes_getY` and
|
||||
* `GameActivityPointerAxes_getAxisValue`).
|
||||
*
|
||||
* The X axis and Y axis are enabled by default but any other axis that you want
|
||||
* to read **must** be enabled first, using
|
||||
* `GameActivityPointerAxes_enableAxis`.
|
||||
*
|
||||
* \see GameActivityMotionEvent
|
||||
*/
|
||||
typedef struct GameActivityPointerAxes {
|
||||
int32_t id;
|
||||
int32_t toolType;
|
||||
float axisValues[GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT];
|
||||
float rawX;
|
||||
float rawY;
|
||||
} GameActivityPointerAxes;
|
||||
|
||||
/** \brief Get the toolType of the pointer. */
|
||||
inline int32_t GameActivityPointerAxes_getToolType(const GameActivityPointerAxes* pointerInfo) {
|
||||
return pointerInfo->toolType;
|
||||
}
|
||||
|
||||
/** \brief Get the current X coordinate of the pointer. */
|
||||
inline float GameActivityPointerAxes_getX(const GameActivityPointerAxes* pointerInfo) {
|
||||
return pointerInfo->axisValues[AMOTION_EVENT_AXIS_X];
|
||||
}
|
||||
|
||||
/** \brief Get the current Y coordinate of the pointer. */
|
||||
inline float GameActivityPointerAxes_getY(const GameActivityPointerAxes* pointerInfo) {
|
||||
return pointerInfo->axisValues[AMOTION_EVENT_AXIS_Y];
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Enable the specified axis, so that its value is reported in the
|
||||
* GameActivityPointerAxes structures stored in a motion event.
|
||||
*
|
||||
* You must enable any axis that you want to read, apart from
|
||||
* `AMOTION_EVENT_AXIS_X` and `AMOTION_EVENT_AXIS_Y` that are enabled by
|
||||
* default.
|
||||
*
|
||||
* If the axis index is out of range, nothing is done.
|
||||
*/
|
||||
void GameActivityPointerAxes_enableAxis(int32_t axis);
|
||||
|
||||
/**
|
||||
* \brief Disable the specified axis. Its value won't be reported in the
|
||||
* GameActivityPointerAxes structures stored in a motion event anymore.
|
||||
*
|
||||
* Apart from X and Y, any axis that you want to read **must** be enabled first,
|
||||
* using `GameActivityPointerAxes_enableAxis`.
|
||||
*
|
||||
* If the axis index is out of range, nothing is done.
|
||||
*/
|
||||
void GameActivityPointerAxes_disableAxis(int32_t axis);
|
||||
|
||||
/**
|
||||
* \brief Get the value of the requested axis.
|
||||
*
|
||||
* Apart from X and Y, any axis that you want to read **must** be enabled first,
|
||||
* using `GameActivityPointerAxes_enableAxis`.
|
||||
*
|
||||
* Find the valid enums for the axis (`AMOTION_EVENT_AXIS_X`,
|
||||
* `AMOTION_EVENT_AXIS_Y`, `AMOTION_EVENT_AXIS_PRESSURE`...)
|
||||
* in https://developer.android.com/ndk/reference/group/input.
|
||||
*
|
||||
* @param pointerInfo The structure containing information about the pointer,
|
||||
* obtained from GameActivityMotionEvent.
|
||||
* @param axis The axis to get the value from
|
||||
* @return The value of the axis, or 0 if the axis is invalid or was not
|
||||
* enabled.
|
||||
*/
|
||||
float GameActivityPointerAxes_getAxisValue(const GameActivityPointerAxes* pointerInfo,
|
||||
int32_t axis);
|
||||
|
||||
inline float GameActivityPointerAxes_getPressure(const GameActivityPointerAxes* pointerInfo) {
|
||||
return GameActivityPointerAxes_getAxisValue(pointerInfo, AMOTION_EVENT_AXIS_PRESSURE);
|
||||
}
|
||||
|
||||
inline float GameActivityPointerAxes_getSize(const GameActivityPointerAxes* pointerInfo) {
|
||||
return GameActivityPointerAxes_getAxisValue(pointerInfo, AMOTION_EVENT_AXIS_SIZE);
|
||||
}
|
||||
|
||||
inline float GameActivityPointerAxes_getTouchMajor(const GameActivityPointerAxes* pointerInfo) {
|
||||
return GameActivityPointerAxes_getAxisValue(pointerInfo, AMOTION_EVENT_AXIS_TOUCH_MAJOR);
|
||||
}
|
||||
|
||||
inline float GameActivityPointerAxes_getTouchMinor(const GameActivityPointerAxes* pointerInfo) {
|
||||
return GameActivityPointerAxes_getAxisValue(pointerInfo, AMOTION_EVENT_AXIS_TOUCH_MINOR);
|
||||
}
|
||||
|
||||
inline float GameActivityPointerAxes_getToolMajor(const GameActivityPointerAxes* pointerInfo) {
|
||||
return GameActivityPointerAxes_getAxisValue(pointerInfo, AMOTION_EVENT_AXIS_TOOL_MAJOR);
|
||||
}
|
||||
|
||||
inline float GameActivityPointerAxes_getToolMinor(const GameActivityPointerAxes* pointerInfo) {
|
||||
return GameActivityPointerAxes_getAxisValue(pointerInfo, AMOTION_EVENT_AXIS_TOOL_MINOR);
|
||||
}
|
||||
|
||||
inline float GameActivityPointerAxes_getOrientation(const GameActivityPointerAxes* pointerInfo) {
|
||||
return GameActivityPointerAxes_getAxisValue(pointerInfo, AMOTION_EVENT_AXIS_ORIENTATION);
|
||||
}
|
||||
|
||||
/**
|
||||
* The maximum number of pointers returned inside a motion event.
|
||||
*/
|
||||
#if (defined GAMEACTIVITY_MAX_NUM_POINTERS_IN_MOTION_EVENT_OVERRIDE)
|
||||
#define GAMEACTIVITY_MAX_NUM_POINTERS_IN_MOTION_EVENT \
|
||||
GAMEACTIVITY_MAX_NUM_POINTERS_IN_MOTION_EVENT_OVERRIDE
|
||||
#else
|
||||
#define GAMEACTIVITY_MAX_NUM_POINTERS_IN_MOTION_EVENT 8
|
||||
#endif
|
||||
|
||||
/**
|
||||
* \brief Describe a motion event that happened on the GameActivity SurfaceView.
|
||||
*
|
||||
* This is 1:1 mapping to the information contained in a Java `MotionEvent`
|
||||
* (see https://developer.android.com/reference/android/view/MotionEvent).
|
||||
*/
|
||||
typedef struct GameActivityMotionEvent {
|
||||
int32_t deviceId;
|
||||
int32_t source;
|
||||
int32_t action;
|
||||
|
||||
int64_t eventTime;
|
||||
int64_t downTime;
|
||||
|
||||
int32_t flags;
|
||||
int32_t metaState;
|
||||
|
||||
int32_t actionButton;
|
||||
int32_t buttonState;
|
||||
int32_t classification;
|
||||
int32_t edgeFlags;
|
||||
|
||||
uint32_t pointerCount;
|
||||
GameActivityPointerAxes pointers[GAMEACTIVITY_MAX_NUM_POINTERS_IN_MOTION_EVENT];
|
||||
|
||||
int historySize;
|
||||
int64_t* historicalEventTimesMillis;
|
||||
int64_t* historicalEventTimesNanos;
|
||||
float* historicalAxisValues;
|
||||
|
||||
float precisionX;
|
||||
float precisionY;
|
||||
} GameActivityMotionEvent;
|
||||
|
||||
float GameActivityMotionEvent_getHistoricalAxisValue(const GameActivityMotionEvent* event, int axis,
|
||||
int pointerIndex, int historyPos);
|
||||
|
||||
inline int GameActivityMotionEvent_getHistorySize(const GameActivityMotionEvent* event) {
|
||||
return event->historySize;
|
||||
}
|
||||
|
||||
inline float GameActivityMotionEvent_getHistoricalX(const GameActivityMotionEvent* event,
|
||||
int pointerIndex, int historyPos) {
|
||||
return GameActivityMotionEvent_getHistoricalAxisValue(event, AMOTION_EVENT_AXIS_X, pointerIndex,
|
||||
historyPos);
|
||||
}
|
||||
|
||||
inline float GameActivityMotionEvent_getHistoricalY(const GameActivityMotionEvent* event,
|
||||
int pointerIndex, int historyPos) {
|
||||
return GameActivityMotionEvent_getHistoricalAxisValue(event, AMOTION_EVENT_AXIS_Y, pointerIndex,
|
||||
historyPos);
|
||||
}
|
||||
|
||||
inline float GameActivityMotionEvent_getHistoricalPressure(const GameActivityMotionEvent* event,
|
||||
int pointerIndex, int historyPos) {
|
||||
return GameActivityMotionEvent_getHistoricalAxisValue(event, AMOTION_EVENT_AXIS_PRESSURE,
|
||||
pointerIndex, historyPos);
|
||||
}
|
||||
|
||||
inline float GameActivityMotionEvent_getHistoricalSize(const GameActivityMotionEvent* event,
|
||||
int pointerIndex, int historyPos) {
|
||||
return GameActivityMotionEvent_getHistoricalAxisValue(event, AMOTION_EVENT_AXIS_SIZE,
|
||||
pointerIndex, historyPos);
|
||||
}
|
||||
|
||||
inline float GameActivityMotionEvent_getHistoricalTouchMajor(const GameActivityMotionEvent* event,
|
||||
int pointerIndex, int historyPos) {
|
||||
return GameActivityMotionEvent_getHistoricalAxisValue(event, AMOTION_EVENT_AXIS_TOUCH_MAJOR,
|
||||
pointerIndex, historyPos);
|
||||
}
|
||||
|
||||
inline float GameActivityMotionEvent_getHistoricalTouchMinor(const GameActivityMotionEvent* event,
|
||||
int pointerIndex, int historyPos) {
|
||||
return GameActivityMotionEvent_getHistoricalAxisValue(event, AMOTION_EVENT_AXIS_TOUCH_MINOR,
|
||||
pointerIndex, historyPos);
|
||||
}
|
||||
|
||||
inline float GameActivityMotionEvent_getHistoricalToolMajor(const GameActivityMotionEvent* event,
|
||||
int pointerIndex, int historyPos) {
|
||||
return GameActivityMotionEvent_getHistoricalAxisValue(event, AMOTION_EVENT_AXIS_TOOL_MAJOR,
|
||||
pointerIndex, historyPos);
|
||||
}
|
||||
|
||||
inline float GameActivityMotionEvent_getHistoricalToolMinor(const GameActivityMotionEvent* event,
|
||||
int pointerIndex, int historyPos) {
|
||||
return GameActivityMotionEvent_getHistoricalAxisValue(event, AMOTION_EVENT_AXIS_TOOL_MINOR,
|
||||
pointerIndex, historyPos);
|
||||
}
|
||||
|
||||
inline float GameActivityMotionEvent_getHistoricalOrientation(const GameActivityMotionEvent* event,
|
||||
int pointerIndex, int historyPos) {
|
||||
return GameActivityMotionEvent_getHistoricalAxisValue(event, AMOTION_EVENT_AXIS_ORIENTATION,
|
||||
pointerIndex, historyPos);
|
||||
}
|
||||
|
||||
/** \brief Handle the freeing of the GameActivityMotionEvent struct. */
|
||||
void GameActivityMotionEvent_destroy(GameActivityMotionEvent* c_event);
|
||||
|
||||
/**
|
||||
* \brief Describe a key event that happened on the GameActivity SurfaceView.
|
||||
*
|
||||
* This is 1:1 mapping to the information contained in a Java `KeyEvent`
|
||||
* (see https://developer.android.com/reference/android/view/KeyEvent).
|
||||
* The only exception is the event times, which are reported as
|
||||
* nanoseconds in this struct.
|
||||
*/
|
||||
typedef struct GameActivityKeyEvent {
|
||||
int32_t deviceId;
|
||||
int32_t source;
|
||||
int32_t action;
|
||||
|
||||
int64_t eventTime;
|
||||
int64_t downTime;
|
||||
|
||||
int32_t flags;
|
||||
int32_t metaState;
|
||||
|
||||
int32_t modifiers;
|
||||
int32_t repeatCount;
|
||||
int32_t keyCode;
|
||||
int32_t scanCode;
|
||||
// int32_t unicodeChar;
|
||||
} GameActivityKeyEvent;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
/** @} */
|
||||
|
||||
#endif // ANDROID_GAME_SDK_GAME_ACTIVITY_EVENTS_H
|
||||
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
#ifndef ANDROID_GAME_SDK_GAME_ACTIVITY_LOG_H_
|
||||
#define ANDROID_GAME_SDK_GAME_ACTIVITY_LOG_H_
|
||||
|
||||
#define LOG_TAG "GameActivity"
|
||||
#include <android/log.h>
|
||||
|
||||
#define ALOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__);
|
||||
#define ALOGW(...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__);
|
||||
#define ALOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__);
|
||||
#ifdef NDEBUG
|
||||
#define ALOGV(...)
|
||||
#else
|
||||
#define ALOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__);
|
||||
#endif
|
||||
|
||||
/* Returns 2nd arg. Used to substitute default value if caller's vararg list
|
||||
* is empty.
|
||||
*/
|
||||
#define __android_second(first, second, ...) second
|
||||
|
||||
/* If passed multiple args, returns ',' followed by all but 1st arg, otherwise
|
||||
* returns nothing.
|
||||
*/
|
||||
#define __android_rest(first, ...) , ##__VA_ARGS__
|
||||
|
||||
#define android_printAssert(cond, tag, fmt...) \
|
||||
__android_log_assert(cond, tag, __android_second(0, ##fmt, NULL) __android_rest(fmt))
|
||||
|
||||
#define CONDITION(cond) (__builtin_expect((cond) != 0, 0))
|
||||
|
||||
#ifndef LOG_ALWAYS_FATAL_IF
|
||||
#define LOG_ALWAYS_FATAL_IF(cond, ...) \
|
||||
((CONDITION(cond)) ? ((void)android_printAssert(#cond, LOG_TAG, ##__VA_ARGS__)) : (void)0)
|
||||
#endif
|
||||
|
||||
#ifndef LOG_ALWAYS_FATAL
|
||||
#define LOG_ALWAYS_FATAL(...) (((void)android_printAssert(NULL, LOG_TAG, ##__VA_ARGS__)))
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Simplified macro to send a warning system log message using current LOG_TAG.
|
||||
*/
|
||||
#ifndef SLOGW
|
||||
#define SLOGW(...) ((void)__android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__))
|
||||
#endif
|
||||
|
||||
#ifndef SLOGW_IF
|
||||
#define SLOGW_IF(cond, ...) \
|
||||
((__predict_false(cond)) ? ((void)__android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__)) \
|
||||
: (void)0)
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Versions of LOG_ALWAYS_FATAL_IF and LOG_ALWAYS_FATAL that
|
||||
* are stripped out of release builds.
|
||||
*/
|
||||
#if LOG_NDEBUG
|
||||
|
||||
#ifndef LOG_FATAL_IF
|
||||
#define LOG_FATAL_IF(cond, ...) ((void)0)
|
||||
#endif
|
||||
#ifndef LOG_FATAL
|
||||
#define LOG_FATAL(...) ((void)0)
|
||||
#endif
|
||||
|
||||
#else
|
||||
|
||||
#ifndef LOG_FATAL_IF
|
||||
#define LOG_FATAL_IF(cond, ...) LOG_ALWAYS_FATAL_IF(cond, ##__VA_ARGS__)
|
||||
#endif
|
||||
#ifndef LOG_FATAL
|
||||
#define LOG_FATAL(...) LOG_ALWAYS_FATAL(__VA_ARGS__)
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Assertion that generates a log message when the assertion fails.
|
||||
* Stripped out of release builds. Uses the current LOG_TAG.
|
||||
*/
|
||||
#ifndef ALOG_ASSERT
|
||||
#define ALOG_ASSERT(cond, ...) LOG_FATAL_IF(!(cond), ##__VA_ARGS__)
|
||||
#endif
|
||||
|
||||
#define LOG_TRACE(...)
|
||||
|
||||
#endif // ANDROID_GAME_SDK_GAME_ACTIVITY_LOG_H_
|
||||
@@ -27,30 +27,10 @@
|
||||
#include <poll.h>
|
||||
#include <pthread.h>
|
||||
#include <sched.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "game-activity/GameActivity.h"
|
||||
|
||||
#if (defined NATIVE_APP_GLUE_MAX_NUM_MOTION_EVENTS_OVERRIDE)
|
||||
#define NATIVE_APP_GLUE_MAX_NUM_MOTION_EVENTS \
|
||||
NATIVE_APP_GLUE_MAX_NUM_MOTION_EVENTS_OVERRIDE
|
||||
#else
|
||||
#define NATIVE_APP_GLUE_MAX_NUM_MOTION_EVENTS 16
|
||||
#endif
|
||||
|
||||
#if (defined NATIVE_APP_GLUE_MAX_HISTORICAL_POINTER_SAMPLES_OVERRIDE)
|
||||
#define NATIVE_APP_GLUE_MAX_HISTORICAL_POINTER_SAMPLES \
|
||||
NATIVE_APP_GLUE_MAX_HISTORICAL_POINTER_SAMPLES_OVERRIDE
|
||||
#else
|
||||
#define NATIVE_APP_GLUE_MAX_HISTORICAL_POINTER_SAMPLES 64
|
||||
#endif
|
||||
|
||||
#if (defined NATIVE_APP_GLUE_MAX_NUM_KEY_EVENTS_OVERRIDE)
|
||||
#define NATIVE_APP_GLUE_MAX_NUM_KEY_EVENTS \
|
||||
NATIVE_APP_GLUE_MAX_NUM_KEY_EVENTS_OVERRIDE
|
||||
#else
|
||||
#define NATIVE_APP_GLUE_MAX_NUM_KEY_EVENTS 4
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
@@ -120,16 +100,15 @@ struct android_poll_source {
|
||||
* Function to call to perform the standard processing of data from
|
||||
* this source.
|
||||
*/
|
||||
void (*process)(struct android_app* app,
|
||||
struct android_poll_source* source);
|
||||
void (*process)(struct android_app* app, struct android_poll_source* source);
|
||||
};
|
||||
|
||||
struct android_input_buffer {
|
||||
/**
|
||||
* Pointer to a read-only array of pointers to GameActivityMotionEvent.
|
||||
* Pointer to a read-only array of GameActivityMotionEvent.
|
||||
* Only the first motionEventsCount events are valid.
|
||||
*/
|
||||
GameActivityMotionEvent motionEvents[NATIVE_APP_GLUE_MAX_NUM_MOTION_EVENTS];
|
||||
GameActivityMotionEvent* motionEvents;
|
||||
|
||||
/**
|
||||
* The number of valid motion events in `motionEvents`.
|
||||
@@ -137,36 +116,25 @@ struct android_input_buffer {
|
||||
uint64_t motionEventsCount;
|
||||
|
||||
/**
|
||||
* Pointer to a read-only array of pointers to GameActivityHistoricalPointerAxes.
|
||||
*
|
||||
* Only the first historicalSamplesCount samples are valid.
|
||||
* Refer to event->historicalStart, event->pointerCount and event->historicalCount
|
||||
* to access the specific samples that relate to an event.
|
||||
*
|
||||
* Each slice of samples for one event has a length of
|
||||
* (event->pointerCount and event->historicalCount) and is in pointer-major
|
||||
* order so the historic samples for each pointer are contiguous.
|
||||
* E.g. you would access historic sample index 3 for pointer 2 of an event with:
|
||||
*
|
||||
* historicalAxisSamples[event->historicalStart + (event->historicalCount * 2) + 3];
|
||||
* The size of the `motionEvents` buffer.
|
||||
*/
|
||||
GameActivityHistoricalPointerAxes historicalAxisSamples[NATIVE_APP_GLUE_MAX_HISTORICAL_POINTER_SAMPLES];
|
||||
uint64_t motionEventsBufferSize;
|
||||
|
||||
/**
|
||||
* The number of valid historical samples in `historicalAxisSamples`.
|
||||
*/
|
||||
uint64_t historicalSamplesCount;
|
||||
|
||||
/**
|
||||
* Pointer to a read-only array of pointers to GameActivityKeyEvent.
|
||||
* Pointer to a read-only array of GameActivityKeyEvent.
|
||||
* Only the first keyEventsCount events are valid.
|
||||
*/
|
||||
GameActivityKeyEvent keyEvents[NATIVE_APP_GLUE_MAX_NUM_KEY_EVENTS];
|
||||
GameActivityKeyEvent* keyEvents;
|
||||
|
||||
/**
|
||||
* The number of valid "Key" events in `keyEvents`.
|
||||
*/
|
||||
uint64_t keyEventsCount;
|
||||
|
||||
/**
|
||||
* The size of the `keyEvents` buffer.
|
||||
*/
|
||||
uint64_t keyEventsBufferSize;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -233,6 +201,9 @@ struct android_app {
|
||||
/** The ALooper associated with the app's thread. */
|
||||
ALooper* looper;
|
||||
|
||||
/** The ALooper associated with the app's Java main/UI thread. */
|
||||
ALooper* mainLooper;
|
||||
|
||||
/** When non-NULL, this is the window surface that the app can draw in. */
|
||||
ANativeWindow* window;
|
||||
|
||||
@@ -242,6 +213,27 @@ struct android_app {
|
||||
*/
|
||||
ARect contentRect;
|
||||
|
||||
/**
|
||||
* Whether the software keyboard is visible or not.
|
||||
*/
|
||||
bool softwareKeyboardVisible;
|
||||
|
||||
/**
|
||||
* Last editor action. Valid within APP_CMD_SOFTWARE_KB_VIS_CHANGED handler.
|
||||
*
|
||||
* Note: the upstream comment above isn't accurate.
|
||||
* - `APP_CMD_SOFTWARE_KB_VIS_CHANGED` is associated with `softwareKeyboardVisible`
|
||||
* changes, not `editorAction`.
|
||||
* - `APP_CMD_EDITOR_ACTION` is associated with this state but unlike for
|
||||
* `window` state there's no synchonization that blocks the Java main
|
||||
* thread, so we can't say that this is only valid within the `APP_CMD_` handler.
|
||||
*/
|
||||
int editorAction;
|
||||
/**
|
||||
* true when editorAction has been set
|
||||
*/
|
||||
bool pendingEditorAction;
|
||||
|
||||
/**
|
||||
* Current state of the app's activity. May be either APP_CMD_START,
|
||||
* APP_CMD_RESUME, APP_CMD_PAUSE, or APP_CMD_STOP.
|
||||
@@ -322,7 +314,7 @@ struct android_app {
|
||||
* Looper ID of commands coming from the app's main thread, an AInputQueue or
|
||||
* user-defined sources.
|
||||
*/
|
||||
enum NativeAppGlueLooperId {
|
||||
enum NativeAppGlueLooperId : int8_t {
|
||||
/**
|
||||
* Looper data ID of commands coming from the app's main thread, which
|
||||
* is returned as an identifier from ALooper_pollOnce(). The data for this
|
||||
@@ -346,8 +338,11 @@ enum NativeAppGlueLooperId {
|
||||
|
||||
/**
|
||||
* Commands passed from the application's main Java thread to the game's thread.
|
||||
*
|
||||
* Values from 0 to 127 are reserved for this library; values from -128 to -1
|
||||
* can be used for custom user's events.
|
||||
*/
|
||||
enum NativeAppGlueAppCmd {
|
||||
enum NativeAppGlueAppCmd : int8_t {
|
||||
/**
|
||||
* Unused. Reserved for future use when usage of AInputQueue will be
|
||||
* supported.
|
||||
@@ -389,6 +384,11 @@ enum NativeAppGlueAppCmd {
|
||||
*/
|
||||
APP_CMD_CONTENT_RECT_CHANGED,
|
||||
|
||||
/**
|
||||
* Command from main thread: the software keyboard was shown or hidden.
|
||||
*/
|
||||
APP_CMD_SOFTWARE_KB_VIS_CHANGED,
|
||||
|
||||
/**
|
||||
* Command from main thread: the app's activity window has gained
|
||||
* input focus.
|
||||
@@ -452,10 +452,25 @@ enum NativeAppGlueAppCmd {
|
||||
*/
|
||||
APP_CMD_WINDOW_INSETS_CHANGED,
|
||||
|
||||
/**
|
||||
* Command from main thread: an editor action has been triggered.
|
||||
*/
|
||||
// APP_CMD_EDITOR_ACTION,
|
||||
|
||||
/**
|
||||
* Command from main thread: a keyboard event has been received.
|
||||
*/
|
||||
// APP_CMD_KEY_EVENT,
|
||||
|
||||
/**
|
||||
* Command from main thread: a touch event has been received.
|
||||
*/
|
||||
// APP_CMD_TOUCH_EVENT,
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Call when ALooper_pollAll() returns LOOPER_ID_MAIN, reading the next
|
||||
* Call when ALooper_pollOnce() returns LOOPER_ID_MAIN, reading the next
|
||||
* app command message.
|
||||
*/
|
||||
int8_t android_app_read_cmd(struct android_app* android_app);
|
||||
@@ -478,8 +493,7 @@ void android_app_post_exec_cmd(struct android_app* android_app, int8_t cmd);
|
||||
* Call this before processing input events to get the events buffer.
|
||||
* The function returns NULL if there are no events to process.
|
||||
*/
|
||||
struct android_input_buffer* android_app_swap_input_buffers(
|
||||
struct android_app* android_app);
|
||||
struct android_input_buffer* android_app_swap_input_buffers(struct android_app* android_app);
|
||||
|
||||
/**
|
||||
* Clear the array of motion events that were waiting to be handled, and release
|
||||
@@ -512,8 +526,7 @@ extern void _rust_glue_entry(struct android_app* app);
|
||||
*
|
||||
* The default key filter will filter out volume and camera button presses.
|
||||
*/
|
||||
void android_app_set_key_event_filter(struct android_app* app,
|
||||
android_key_event_filter filter);
|
||||
void android_app_set_key_event_filter(struct android_app* app, android_key_event_filter filter);
|
||||
|
||||
/**
|
||||
* Set the filter to use when processing touch and motion events.
|
||||
@@ -527,13 +540,23 @@ void android_app_set_key_event_filter(struct android_app* app,
|
||||
void android_app_set_motion_event_filter(struct android_app* app,
|
||||
android_motion_event_filter filter);
|
||||
|
||||
/**
|
||||
* You can send your custom events using the function below.
|
||||
*
|
||||
* Make sure your custom codes do not overlap with this library's ones.
|
||||
*
|
||||
* Values from 0 to 127 are reserved for this library; values from -128 to -1
|
||||
* can be used for custom user's events.
|
||||
*
|
||||
* The function returns true if the write operation was successful.
|
||||
*/
|
||||
bool android_app_write_cmd(struct android_app* android_app, int8_t cmd);
|
||||
|
||||
/**
|
||||
* Determines if a looper wake up was due to new input becoming available
|
||||
*/
|
||||
bool android_app_input_available_wake_up(struct android_app* app);
|
||||
|
||||
void GameActivity_onCreate_C(GameActivity* activity, void* savedState,
|
||||
size_t savedStateSize);
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"export_libraries": [],
|
||||
"library_name": null,
|
||||
"android": {
|
||||
"export_libraries": ["-landroid", "-llog"],
|
||||
"library_name": null
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,304 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <game-activity/GameActivityEvents.h>
|
||||
#include <game-activity/GameActivityLog.h>
|
||||
#include <sys/system_properties.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "GameActivityEvents_internal.h"
|
||||
#include "system_utils.h"
|
||||
|
||||
static bool enabledAxes[GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT] = {
|
||||
/* AMOTION_EVENT_AXIS_X */ true,
|
||||
/* AMOTION_EVENT_AXIS_Y */ true,
|
||||
// Disable all other axes by default (they can be enabled using
|
||||
// `GameActivityPointerAxes_enableAxis`).
|
||||
false};
|
||||
|
||||
extern "C" void GameActivityPointerAxes_enableAxis(int32_t axis) {
|
||||
if (axis < 0 || axis >= GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT) {
|
||||
return;
|
||||
}
|
||||
|
||||
enabledAxes[axis] = true;
|
||||
}
|
||||
|
||||
float GameActivityPointerAxes_getAxisValue(const GameActivityPointerAxes* pointerInfo,
|
||||
int32_t axis) {
|
||||
if (axis < 0 || axis >= GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!enabledAxes[axis]) {
|
||||
ALOGW("Axis %d must be enabled before it can be accessed.", axis);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return pointerInfo->axisValues[axis];
|
||||
}
|
||||
|
||||
extern "C" void GameActivityPointerAxes_disableAxis(int32_t axis) {
|
||||
if (axis < 0 || axis >= GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT) {
|
||||
return;
|
||||
}
|
||||
|
||||
enabledAxes[axis] = false;
|
||||
}
|
||||
|
||||
float GameActivityMotionEvent_getHistoricalAxisValue(const GameActivityMotionEvent* event, int axis,
|
||||
int pointerIndex, int historyPos) {
|
||||
if (axis < 0 || axis >= GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT) {
|
||||
ALOGE("Invalid axis %d", axis);
|
||||
return -1;
|
||||
}
|
||||
if (pointerIndex < 0 || pointerIndex >= event->pointerCount) {
|
||||
ALOGE("Invalid pointer index %d", pointerIndex);
|
||||
return -1;
|
||||
}
|
||||
if (historyPos < 0 || historyPos >= event->historySize) {
|
||||
ALOGE("Invalid history index %d", historyPos);
|
||||
return -1;
|
||||
}
|
||||
if (!enabledAxes[axis]) {
|
||||
ALOGW("Axis %d must be enabled before it can be accessed.", axis);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int pointerOffset = pointerIndex * GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT;
|
||||
int historyValuesOffset =
|
||||
historyPos * event->pointerCount * GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT;
|
||||
return event->historicalAxisValues[historyValuesOffset + pointerOffset + axis];
|
||||
}
|
||||
|
||||
static struct {
|
||||
jmethodID getDeviceId;
|
||||
jmethodID getSource;
|
||||
jmethodID getAction;
|
||||
|
||||
jmethodID getEventTime;
|
||||
jmethodID getDownTime;
|
||||
|
||||
jmethodID getFlags;
|
||||
jmethodID getMetaState;
|
||||
|
||||
jmethodID getActionButton;
|
||||
jmethodID getButtonState;
|
||||
jmethodID getClassification;
|
||||
jmethodID getEdgeFlags;
|
||||
|
||||
jmethodID getHistorySize;
|
||||
jmethodID getHistoricalEventTime;
|
||||
|
||||
jmethodID getPointerCount;
|
||||
jmethodID getPointerId;
|
||||
|
||||
jmethodID getToolType;
|
||||
|
||||
jmethodID getRawX;
|
||||
jmethodID getRawY;
|
||||
jmethodID getXPrecision;
|
||||
jmethodID getYPrecision;
|
||||
jmethodID getAxisValue;
|
||||
|
||||
jmethodID getHistoricalAxisValue;
|
||||
} gMotionEventClassInfo;
|
||||
|
||||
extern "C" void GameActivityMotionEvent_destroy(GameActivityMotionEvent* c_event) {
|
||||
delete c_event->historicalAxisValues;
|
||||
delete c_event->historicalEventTimesMillis;
|
||||
delete c_event->historicalEventTimesNanos;
|
||||
}
|
||||
|
||||
static void initMotionEvents(JNIEnv* env) {
|
||||
int sdkVersion = gamesdk::GetSystemPropAsInt("ro.build.version.sdk");
|
||||
gMotionEventClassInfo = {0};
|
||||
jclass motionEventClass = env->FindClass("android/view/MotionEvent");
|
||||
gMotionEventClassInfo.getDeviceId = env->GetMethodID(motionEventClass, "getDeviceId", "()I");
|
||||
gMotionEventClassInfo.getSource = env->GetMethodID(motionEventClass, "getSource", "()I");
|
||||
gMotionEventClassInfo.getAction = env->GetMethodID(motionEventClass, "getAction", "()I");
|
||||
gMotionEventClassInfo.getEventTime = env->GetMethodID(motionEventClass, "getEventTime", "()J");
|
||||
gMotionEventClassInfo.getDownTime = env->GetMethodID(motionEventClass, "getDownTime", "()J");
|
||||
gMotionEventClassInfo.getFlags = env->GetMethodID(motionEventClass, "getFlags", "()I");
|
||||
gMotionEventClassInfo.getMetaState = env->GetMethodID(motionEventClass, "getMetaState", "()I");
|
||||
if (sdkVersion >= 23) {
|
||||
gMotionEventClassInfo.getActionButton =
|
||||
env->GetMethodID(motionEventClass, "getActionButton", "()I");
|
||||
}
|
||||
if (sdkVersion >= 14) {
|
||||
gMotionEventClassInfo.getButtonState =
|
||||
env->GetMethodID(motionEventClass, "getButtonState", "()I");
|
||||
}
|
||||
if (sdkVersion >= 29) {
|
||||
gMotionEventClassInfo.getClassification =
|
||||
env->GetMethodID(motionEventClass, "getClassification", "()I");
|
||||
}
|
||||
gMotionEventClassInfo.getEdgeFlags = env->GetMethodID(motionEventClass, "getEdgeFlags", "()I");
|
||||
|
||||
gMotionEventClassInfo.getHistorySize =
|
||||
env->GetMethodID(motionEventClass, "getHistorySize", "()I");
|
||||
gMotionEventClassInfo.getHistoricalEventTime =
|
||||
env->GetMethodID(motionEventClass, "getHistoricalEventTime", "(I)J");
|
||||
|
||||
gMotionEventClassInfo.getPointerCount =
|
||||
env->GetMethodID(motionEventClass, "getPointerCount", "()I");
|
||||
gMotionEventClassInfo.getPointerId = env->GetMethodID(motionEventClass, "getPointerId", "(I)I");
|
||||
gMotionEventClassInfo.getToolType = env->GetMethodID(motionEventClass, "getToolType", "(I)I");
|
||||
if (sdkVersion >= 29) {
|
||||
gMotionEventClassInfo.getRawX = env->GetMethodID(motionEventClass, "getRawX", "(I)F");
|
||||
gMotionEventClassInfo.getRawY = env->GetMethodID(motionEventClass, "getRawY", "(I)F");
|
||||
}
|
||||
gMotionEventClassInfo.getXPrecision =
|
||||
env->GetMethodID(motionEventClass, "getXPrecision", "()F");
|
||||
gMotionEventClassInfo.getYPrecision =
|
||||
env->GetMethodID(motionEventClass, "getYPrecision", "()F");
|
||||
gMotionEventClassInfo.getAxisValue =
|
||||
env->GetMethodID(motionEventClass, "getAxisValue", "(II)F");
|
||||
|
||||
gMotionEventClassInfo.getHistoricalAxisValue =
|
||||
env->GetMethodID(motionEventClass, "getHistoricalAxisValue", "(III)F");
|
||||
}
|
||||
|
||||
extern "C" void GameActivityMotionEvent_fromJava(JNIEnv* env, jobject motionEvent,
|
||||
GameActivityMotionEvent* out_event,
|
||||
int pointerCount, int historySize) {
|
||||
pointerCount = std::min(pointerCount, GAMEACTIVITY_MAX_NUM_POINTERS_IN_MOTION_EVENT);
|
||||
out_event->pointerCount = pointerCount;
|
||||
for (int i = 0; i < pointerCount; ++i) {
|
||||
out_event->pointers[i] = {
|
||||
/*id=*/env->CallIntMethod(motionEvent, gMotionEventClassInfo.getPointerId, i),
|
||||
/*toolType=*/
|
||||
env->CallIntMethod(motionEvent, gMotionEventClassInfo.getToolType, i),
|
||||
/*axisValues=*/{0},
|
||||
/*rawX=*/gMotionEventClassInfo.getRawX
|
||||
? env->CallFloatMethod(motionEvent, gMotionEventClassInfo.getRawX, i)
|
||||
: 0,
|
||||
/*rawY=*/gMotionEventClassInfo.getRawY
|
||||
? env->CallFloatMethod(motionEvent, gMotionEventClassInfo.getRawY, i)
|
||||
: 0,
|
||||
};
|
||||
|
||||
for (int axisIndex = 0; axisIndex < GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT; ++axisIndex) {
|
||||
if (enabledAxes[axisIndex]) {
|
||||
out_event->pointers[i].axisValues[axisIndex] =
|
||||
env->CallFloatMethod(motionEvent, gMotionEventClassInfo.getAxisValue,
|
||||
axisIndex, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
out_event->historySize = historySize;
|
||||
out_event->historicalAxisValues =
|
||||
new float[historySize * pointerCount * GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT];
|
||||
out_event->historicalEventTimesMillis = new int64_t[historySize];
|
||||
out_event->historicalEventTimesNanos = new int64_t[historySize];
|
||||
|
||||
for (int historyIndex = 0; historyIndex < historySize; historyIndex++) {
|
||||
out_event->historicalEventTimesMillis[historyIndex] =
|
||||
env->CallLongMethod(motionEvent, gMotionEventClassInfo.getHistoricalEventTime,
|
||||
historyIndex);
|
||||
out_event->historicalEventTimesNanos[historyIndex] =
|
||||
out_event->historicalEventTimesMillis[historyIndex] * 1000000;
|
||||
for (int i = 0; i < pointerCount; ++i) {
|
||||
int pointerOffset = i * GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT;
|
||||
int historyAxisOffset =
|
||||
historyIndex * pointerCount * GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT;
|
||||
float* axisValues = &out_event->historicalAxisValues[historyAxisOffset + pointerOffset];
|
||||
for (int axisIndex = 0; axisIndex < GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT;
|
||||
++axisIndex) {
|
||||
if (enabledAxes[axisIndex]) {
|
||||
axisValues[axisIndex] =
|
||||
env->CallFloatMethod(motionEvent,
|
||||
gMotionEventClassInfo.getHistoricalAxisValue,
|
||||
axisIndex, i, historyIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static struct {
|
||||
jmethodID getDeviceId;
|
||||
jmethodID getSource;
|
||||
jmethodID getAction;
|
||||
|
||||
jmethodID getEventTime;
|
||||
jmethodID getDownTime;
|
||||
|
||||
jmethodID getFlags;
|
||||
jmethodID getMetaState;
|
||||
|
||||
jmethodID getModifiers;
|
||||
jmethodID getRepeatCount;
|
||||
jmethodID getKeyCode;
|
||||
jmethodID getScanCode;
|
||||
// jmethodID getUnicodeChar;
|
||||
} gKeyEventClassInfo;
|
||||
|
||||
static void initKeyEvents(JNIEnv* env) {
|
||||
int sdkVersion = gamesdk::GetSystemPropAsInt("ro.build.version.sdk");
|
||||
gKeyEventClassInfo = {0};
|
||||
jclass keyEventClass = env->FindClass("android/view/KeyEvent");
|
||||
gKeyEventClassInfo.getDeviceId = env->GetMethodID(keyEventClass, "getDeviceId", "()I");
|
||||
gKeyEventClassInfo.getSource = env->GetMethodID(keyEventClass, "getSource", "()I");
|
||||
gKeyEventClassInfo.getAction = env->GetMethodID(keyEventClass, "getAction", "()I");
|
||||
gKeyEventClassInfo.getEventTime = env->GetMethodID(keyEventClass, "getEventTime", "()J");
|
||||
gKeyEventClassInfo.getDownTime = env->GetMethodID(keyEventClass, "getDownTime", "()J");
|
||||
gKeyEventClassInfo.getFlags = env->GetMethodID(keyEventClass, "getFlags", "()I");
|
||||
gKeyEventClassInfo.getMetaState = env->GetMethodID(keyEventClass, "getMetaState", "()I");
|
||||
if (sdkVersion >= 13) {
|
||||
gKeyEventClassInfo.getModifiers = env->GetMethodID(keyEventClass, "getModifiers", "()I");
|
||||
}
|
||||
gKeyEventClassInfo.getRepeatCount = env->GetMethodID(keyEventClass, "getRepeatCount", "()I");
|
||||
gKeyEventClassInfo.getKeyCode = env->GetMethodID(keyEventClass, "getKeyCode", "()I");
|
||||
gKeyEventClassInfo.getScanCode = env->GetMethodID(keyEventClass, "getScanCode", "()I");
|
||||
// gKeyEventClassInfo.getUnicodeChar =
|
||||
// env->GetMethodID(keyEventClass, "getUnicodeChar", "()I");
|
||||
}
|
||||
|
||||
extern "C" void GameActivityKeyEvent_fromJava(JNIEnv* env, jobject keyEvent,
|
||||
GameActivityKeyEvent* out_event) {
|
||||
*out_event = {
|
||||
/*deviceId=*/env->CallIntMethod(keyEvent, gKeyEventClassInfo.getDeviceId),
|
||||
/*source=*/env->CallIntMethod(keyEvent, gKeyEventClassInfo.getSource),
|
||||
/*action=*/env->CallIntMethod(keyEvent, gKeyEventClassInfo.getAction),
|
||||
// TODO: introduce a millisecondsToNanoseconds helper:
|
||||
/*eventTime=*/
|
||||
env->CallLongMethod(keyEvent, gKeyEventClassInfo.getEventTime) * 1000000,
|
||||
/*downTime=*/
|
||||
env->CallLongMethod(keyEvent, gKeyEventClassInfo.getDownTime) * 1000000,
|
||||
/*flags=*/env->CallIntMethod(keyEvent, gKeyEventClassInfo.getFlags),
|
||||
/*metaState=*/
|
||||
env->CallIntMethod(keyEvent, gKeyEventClassInfo.getMetaState),
|
||||
/*modifiers=*/gKeyEventClassInfo.getModifiers
|
||||
? env->CallIntMethod(keyEvent, gKeyEventClassInfo.getModifiers)
|
||||
: 0,
|
||||
/*repeatCount=*/
|
||||
env->CallIntMethod(keyEvent, gKeyEventClassInfo.getRepeatCount),
|
||||
/*keyCode=*/
|
||||
env->CallIntMethod(keyEvent, gKeyEventClassInfo.getKeyCode),
|
||||
/*scanCode=*/
|
||||
env->CallIntMethod(keyEvent, gKeyEventClassInfo.getScanCode)
|
||||
/*unicodeChar=*/
|
||||
// env->CallIntMethod(keyEvent, gKeyEventClassInfo.getUnicodeChar)
|
||||
};
|
||||
}
|
||||
|
||||
extern "C" void GameActivityEventsInit(JNIEnv* env) {
|
||||
initMotionEvents(env);
|
||||
initKeyEvents(env);
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @addtogroup GameActivity Game Activity Events Internal
|
||||
* These functions are internal details of Game Activity Events.
|
||||
* Please do not rely on anything in this file as this can be changed
|
||||
* without notice.
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file GameActivityEvents_internal.h
|
||||
*/
|
||||
#ifndef ANDROID_GAME_SDK_GAME_ACTIVITY_EVENTS_INTERNAL_H
|
||||
#define ANDROID_GAME_SDK_GAME_ACTIVITY_EVENTS_INTERNAL_H
|
||||
|
||||
#include <jni.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/** \brief Performs necessary initialization steps for GameActivityEvents.
|
||||
*
|
||||
* User must call this function before calling any other functions of this unit.
|
||||
* If you use GameActivity it will call this function for you.
|
||||
*/
|
||||
void GameActivityEventsInit(JNIEnv* env);
|
||||
|
||||
/**
|
||||
* \brief Convert a Java `MotionEvent` to a `GameActivityMotionEvent`.
|
||||
*
|
||||
* This is done automatically by the GameActivity: see `onTouchEvent` to set
|
||||
* a callback to consume the received events.
|
||||
* This function can be used if you re-implement events handling in your own
|
||||
* activity.
|
||||
* Ownership of out_event is maintained by the caller.
|
||||
* Note that we pass as much information from Java Activity as possible
|
||||
* to avoid extra JNI calls.
|
||||
*/
|
||||
void GameActivityMotionEvent_fromJava(JNIEnv* env, jobject motionEvent,
|
||||
GameActivityMotionEvent* out_event, int pointerCount,
|
||||
int historySize);
|
||||
|
||||
/**
|
||||
* \brief Convert a Java `KeyEvent` to a `GameActivityKeyEvent`.
|
||||
*
|
||||
* This is done automatically by the GameActivity: see `onKeyUp` and `onKeyDown`
|
||||
* to set a callback to consume the received events.
|
||||
* This function can be used if you re-implement events handling in your own
|
||||
* activity.
|
||||
* Ownership of out_event is maintained by the caller.
|
||||
*/
|
||||
void GameActivityKeyEvent_fromJava(JNIEnv* env, jobject motionEvent,
|
||||
GameActivityKeyEvent* out_event);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
/** @} */
|
||||
|
||||
#endif // ANDROID_GAME_SDK_GAME_ACTIVITY_EVENTS_INTERNAL_H
|
||||
@@ -14,9 +14,10 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "android_native_app_glue.h"
|
||||
#include "game-activity/native_app_glue/android_native_app_glue.h"
|
||||
|
||||
#include <android/log.h>
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <jni.h>
|
||||
#include <stdlib.h>
|
||||
@@ -24,26 +25,25 @@
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#define LOGI(...) \
|
||||
((void)__android_log_print(ANDROID_LOG_INFO, "threaded_app", __VA_ARGS__))
|
||||
#define LOGE(...) \
|
||||
((void)__android_log_print(ANDROID_LOG_ERROR, "threaded_app", __VA_ARGS__))
|
||||
#define LOGW(...) \
|
||||
((void)__android_log_print(ANDROID_LOG_WARN, "threaded_app", __VA_ARGS__))
|
||||
#define LOGW_ONCE(...) \
|
||||
#define NATIVE_APP_GLUE_MOTION_EVENTS_DEFAULT_BUF_SIZE 16
|
||||
#define NATIVE_APP_GLUE_KEY_EVENTS_DEFAULT_BUF_SIZE 4
|
||||
#define NATIVE_APP_GLUE_CMD_WAIT_TIMEOUT_SECONDS 2
|
||||
|
||||
#define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, "threaded_app", __VA_ARGS__))
|
||||
#define LOGE(...) ((void)__android_log_print(ANDROID_LOG_ERROR, "threaded_app", __VA_ARGS__))
|
||||
#define LOGW(...) ((void)__android_log_print(ANDROID_LOG_WARN, "threaded_app", __VA_ARGS__))
|
||||
#define LOGW_ONCE(...) \
|
||||
do { \
|
||||
static bool alogw_once##__FILE__##__LINE__##__ = true; \
|
||||
if (alogw_once##__FILE__##__LINE__##__) { \
|
||||
alogw_once##__FILE__##__LINE__##__ = false; \
|
||||
LOGW(__VA_ARGS__); \
|
||||
LOGW(__VA_ARGS__); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
/* For debug builds, always enable the debug traces in this library */
|
||||
#ifndef NDEBUG
|
||||
#define LOGV(...) \
|
||||
((void)__android_log_print(ANDROID_LOG_VERBOSE, "threaded_app", \
|
||||
__VA_ARGS__))
|
||||
#define LOGV(...) ((void)__android_log_print(ANDROID_LOG_VERBOSE, "threaded_app", __VA_ARGS__))
|
||||
#else
|
||||
#define LOGV(...) ((void)0)
|
||||
#endif
|
||||
@@ -73,25 +73,23 @@ static void print_cur_config(struct android_app* android_app) {
|
||||
AConfiguration_getLanguage(android_app->config, lang);
|
||||
AConfiguration_getCountry(android_app->config, country);
|
||||
|
||||
LOGV(
|
||||
"Config: mcc=%d mnc=%d lang=%c%c cnt=%c%c orien=%d touch=%d dens=%d "
|
||||
"keys=%d nav=%d keysHid=%d navHid=%d sdk=%d size=%d long=%d "
|
||||
"modetype=%d modenight=%d",
|
||||
AConfiguration_getMcc(android_app->config),
|
||||
AConfiguration_getMnc(android_app->config), lang[0], lang[1],
|
||||
country[0], country[1],
|
||||
AConfiguration_getOrientation(android_app->config),
|
||||
AConfiguration_getTouchscreen(android_app->config),
|
||||
AConfiguration_getDensity(android_app->config),
|
||||
AConfiguration_getKeyboard(android_app->config),
|
||||
AConfiguration_getNavigation(android_app->config),
|
||||
AConfiguration_getKeysHidden(android_app->config),
|
||||
AConfiguration_getNavHidden(android_app->config),
|
||||
AConfiguration_getSdkVersion(android_app->config),
|
||||
AConfiguration_getScreenSize(android_app->config),
|
||||
AConfiguration_getScreenLong(android_app->config),
|
||||
AConfiguration_getUiModeType(android_app->config),
|
||||
AConfiguration_getUiModeNight(android_app->config));
|
||||
LOGV("Config: mcc=%d mnc=%d lang=%c%c cnt=%c%c orien=%d touch=%d dens=%d "
|
||||
"keys=%d nav=%d keysHid=%d navHid=%d sdk=%d size=%d long=%d "
|
||||
"modetype=%d modenight=%d",
|
||||
AConfiguration_getMcc(android_app->config), AConfiguration_getMnc(android_app->config),
|
||||
lang[0], lang[1], country[0], country[1],
|
||||
AConfiguration_getOrientation(android_app->config),
|
||||
AConfiguration_getTouchscreen(android_app->config),
|
||||
AConfiguration_getDensity(android_app->config),
|
||||
AConfiguration_getKeyboard(android_app->config),
|
||||
AConfiguration_getNavigation(android_app->config),
|
||||
AConfiguration_getKeysHidden(android_app->config),
|
||||
AConfiguration_getNavHidden(android_app->config),
|
||||
AConfiguration_getSdkVersion(android_app->config),
|
||||
AConfiguration_getScreenSize(android_app->config),
|
||||
AConfiguration_getScreenLong(android_app->config),
|
||||
AConfiguration_getUiModeType(android_app->config),
|
||||
AConfiguration_getUiModeNight(android_app->config));
|
||||
}
|
||||
|
||||
void android_app_pre_exec_cmd(struct android_app* android_app, int8_t cmd) {
|
||||
@@ -128,8 +126,8 @@ void android_app_pre_exec_cmd(struct android_app* android_app, int8_t cmd) {
|
||||
|
||||
case APP_CMD_CONFIG_CHANGED:
|
||||
LOGV("APP_CMD_CONFIG_CHANGED");
|
||||
AConfiguration_fromAssetManager(
|
||||
android_app->config, android_app->activity->assetManager);
|
||||
AConfiguration_fromAssetManager(android_app->config,
|
||||
android_app->activity->assetManager);
|
||||
print_cur_config(android_app);
|
||||
break;
|
||||
|
||||
@@ -178,8 +176,7 @@ static void android_app_destroy(struct android_app* android_app) {
|
||||
// Can't touch android_app object after this.
|
||||
}
|
||||
|
||||
static void process_cmd(struct android_app* app,
|
||||
struct android_poll_source* source) {
|
||||
static void process_cmd(struct android_app* app, struct android_poll_source* source) {
|
||||
int8_t cmd = android_app_read_cmd(app);
|
||||
android_app_pre_exec_cmd(app, cmd);
|
||||
if (app->onAppCmd != NULL) app->onAppCmd(app, cmd);
|
||||
@@ -189,6 +186,7 @@ static void process_cmd(struct android_app* app,
|
||||
// This is run on a separate thread (i.e: not the main thread).
|
||||
static void* android_app_entry(void* param) {
|
||||
struct android_app* android_app = (struct android_app*)param;
|
||||
int input_buf_idx = 0;
|
||||
|
||||
LOGV("android_app_entry called");
|
||||
android_app->config = AConfiguration_new();
|
||||
@@ -196,18 +194,30 @@ static void* android_app_entry(void* param) {
|
||||
LOGV("config = %p", android_app->config);
|
||||
LOGV("activity = %p", android_app->activity);
|
||||
LOGV("assetmanager = %p", android_app->activity->assetManager);
|
||||
AConfiguration_fromAssetManager(android_app->config,
|
||||
android_app->activity->assetManager);
|
||||
AConfiguration_fromAssetManager(android_app->config, android_app->activity->assetManager);
|
||||
|
||||
print_cur_config(android_app);
|
||||
|
||||
/* initialize event buffers */
|
||||
for (input_buf_idx = 0; input_buf_idx < NATIVE_APP_GLUE_MAX_INPUT_BUFFERS; input_buf_idx++) {
|
||||
struct android_input_buffer* buf = &android_app->inputBuffers[input_buf_idx];
|
||||
|
||||
buf->motionEventsBufferSize = NATIVE_APP_GLUE_MOTION_EVENTS_DEFAULT_BUF_SIZE;
|
||||
buf->motionEvents = (GameActivityMotionEvent*)malloc(sizeof(GameActivityMotionEvent) *
|
||||
buf->motionEventsBufferSize);
|
||||
|
||||
buf->keyEventsBufferSize = NATIVE_APP_GLUE_KEY_EVENTS_DEFAULT_BUF_SIZE;
|
||||
buf->keyEvents = (GameActivityKeyEvent*)malloc(sizeof(GameActivityKeyEvent) *
|
||||
buf->keyEventsBufferSize);
|
||||
}
|
||||
|
||||
android_app->cmdPollSource.id = LOOPER_ID_MAIN;
|
||||
android_app->cmdPollSource.app = android_app;
|
||||
android_app->cmdPollSource.process = process_cmd;
|
||||
|
||||
ALooper* looper = ALooper_prepare(ALOOPER_PREPARE_ALLOW_NON_CALLBACKS);
|
||||
ALooper_addFd(looper, android_app->msgread, LOOPER_ID_MAIN,
|
||||
ALOOPER_EVENT_INPUT, NULL, &android_app->cmdPollSource);
|
||||
ALooper_addFd(looper, android_app->msgread, LOOPER_ID_MAIN, ALOOPER_EVENT_INPUT, NULL,
|
||||
&android_app->cmdPollSource);
|
||||
android_app->looper = looper;
|
||||
|
||||
pthread_mutex_lock(&android_app->mutex);
|
||||
@@ -246,19 +256,17 @@ static bool default_key_filter(const GameActivityKeyEvent* event) {
|
||||
|
||||
static bool default_motion_filter(const GameActivityMotionEvent* event) {
|
||||
// Ignore any non-touch events.
|
||||
return event->source == SOURCE_TOUCHSCREEN;
|
||||
return (event->source & SOURCE_TOUCHSCREEN) != 0;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
// Native activity interaction (called from main thread)
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
static struct android_app* android_app_create(GameActivity* activity,
|
||||
void* savedState,
|
||||
static struct android_app* android_app_create(GameActivity* activity, void* savedState,
|
||||
size_t savedStateSize) {
|
||||
// struct android_app* android_app = calloc(1, sizeof(struct android_app));
|
||||
struct android_app* android_app =
|
||||
(struct android_app*)malloc(sizeof(struct android_app));
|
||||
struct android_app* android_app = (struct android_app*)malloc(sizeof(struct android_app));
|
||||
memset(android_app, 0, sizeof(struct android_app));
|
||||
android_app->activity = activity;
|
||||
|
||||
@@ -271,6 +279,13 @@ static struct android_app* android_app_create(GameActivity* activity,
|
||||
memcpy(android_app->savedState, savedState, savedStateSize);
|
||||
}
|
||||
|
||||
android_app->mainLooper = ALooper_forThread();
|
||||
if (android_app->mainLooper == NULL) {
|
||||
LOGE("Failed to get main looper");
|
||||
return NULL;
|
||||
}
|
||||
ALooper_acquire(android_app->mainLooper);
|
||||
|
||||
int msgpipe[2];
|
||||
if (pipe(msgpipe)) {
|
||||
LOGE("could not create pipe: %s", strerror(errno));
|
||||
@@ -298,16 +313,26 @@ static struct android_app* android_app_create(GameActivity* activity,
|
||||
return android_app;
|
||||
}
|
||||
|
||||
static void android_app_write_cmd(struct android_app* android_app, int8_t cmd) {
|
||||
bool android_app_write_cmd(struct android_app* android_app, int8_t cmd) {
|
||||
if (write(android_app->msgwrite, &cmd, sizeof(cmd)) != sizeof(cmd)) {
|
||||
LOGE("Failure writing android_app cmd: %s", strerror(errno));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void android_app_set_window(struct android_app* android_app,
|
||||
ANativeWindow* window) {
|
||||
static void android_app_set_window(struct android_app* android_app, ANativeWindow* window) {
|
||||
LOGV("android_app_set_window called");
|
||||
pthread_mutex_lock(&android_app->mutex);
|
||||
|
||||
// NB: we have to consider that the native thread could have already
|
||||
// (gracefully) exit (setting android_app->destroyed) and so we need
|
||||
// to be careful to avoid a deadlock waiting for a thread that's
|
||||
// already exit.
|
||||
if (android_app->destroyed) {
|
||||
pthread_mutex_unlock(&android_app->mutex);
|
||||
return;
|
||||
}
|
||||
if (android_app->pendingWindow != NULL) {
|
||||
android_app_write_cmd(android_app, APP_CMD_TERM_WINDOW);
|
||||
}
|
||||
@@ -321,28 +346,69 @@ static void android_app_set_window(struct android_app* android_app,
|
||||
pthread_mutex_unlock(&android_app->mutex);
|
||||
}
|
||||
|
||||
static void android_app_set_activity_state(struct android_app* android_app,
|
||||
int8_t cmd) {
|
||||
static void android_app_set_activity_state(struct android_app* android_app, int8_t cmd) {
|
||||
pthread_mutex_lock(&android_app->mutex);
|
||||
android_app_write_cmd(android_app, cmd);
|
||||
while (android_app->activityState != cmd) {
|
||||
pthread_cond_wait(&android_app->cond, &android_app->mutex);
|
||||
// NB: we have to consider that the native thread could have already
|
||||
// (gracefully) exit (setting android_app->destroyed) and so we need
|
||||
// to be careful to avoid a deadlock waiting for a thread that's
|
||||
// already exit.
|
||||
if (android_app->destroyed) {
|
||||
pthread_mutex_unlock(&android_app->mutex);
|
||||
return;
|
||||
}
|
||||
if (android_app_write_cmd(android_app, cmd)) {
|
||||
struct timespec timeout;
|
||||
clock_gettime(CLOCK_REALTIME, &timeout);
|
||||
timeout.tv_sec += NATIVE_APP_GLUE_CMD_WAIT_TIMEOUT_SECONDS;
|
||||
|
||||
int wait_result = 0;
|
||||
while (android_app->activityState != cmd) {
|
||||
wait_result = pthread_cond_timedwait(&android_app->cond, &android_app->mutex, &timeout);
|
||||
if (wait_result == ETIMEDOUT) {
|
||||
LOGE("android_app_set_activity_state timed out waiting for cmd %d", cmd);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
pthread_mutex_unlock(&android_app->mutex);
|
||||
}
|
||||
|
||||
static void android_app_free(struct android_app* android_app) {
|
||||
int input_buf_idx = 0;
|
||||
|
||||
pthread_mutex_lock(&android_app->mutex);
|
||||
|
||||
// It's possible that onDestroy is called after we have already 'destroyed'
|
||||
// the app (via `android_app_destroy` due to `android_main` returning.
|
||||
//
|
||||
// In this case `->destroyed` will already be set (so we won't deadlock in
|
||||
// the loop below) but we still need to close the messaging fds and finish
|
||||
// freeing the android_app
|
||||
|
||||
android_app_write_cmd(android_app, APP_CMD_DESTROY);
|
||||
while (!android_app->destroyed) {
|
||||
pthread_cond_wait(&android_app->cond, &android_app->mutex);
|
||||
}
|
||||
pthread_mutex_unlock(&android_app->mutex);
|
||||
|
||||
for (input_buf_idx = 0; input_buf_idx < NATIVE_APP_GLUE_MAX_INPUT_BUFFERS; input_buf_idx++) {
|
||||
struct android_input_buffer* buf = &android_app->inputBuffers[input_buf_idx];
|
||||
|
||||
android_app_clear_motion_events(buf);
|
||||
free(buf->motionEvents);
|
||||
free(buf->keyEvents);
|
||||
}
|
||||
|
||||
close(android_app->msgread);
|
||||
close(android_app->msgwrite);
|
||||
|
||||
pthread_cond_destroy(&android_app->cond);
|
||||
pthread_mutex_destroy(&android_app->mutex);
|
||||
|
||||
if (android_app->mainLooper != NULL) {
|
||||
ALooper_release(android_app->mainLooper);
|
||||
}
|
||||
|
||||
free(android_app);
|
||||
}
|
||||
|
||||
@@ -365,13 +431,22 @@ static void onResume(GameActivity* activity) {
|
||||
android_app_set_activity_state(ToApp(activity), APP_CMD_RESUME);
|
||||
}
|
||||
|
||||
static void onSaveInstanceState(GameActivity* activity,
|
||||
SaveInstanceStateRecallback recallback,
|
||||
static void onSaveInstanceState(GameActivity* activity, SaveInstanceStateRecallback recallback,
|
||||
void* context) {
|
||||
LOGV("SaveInstanceState: %p", activity);
|
||||
|
||||
struct android_app* android_app = ToApp(activity);
|
||||
pthread_mutex_lock(&android_app->mutex);
|
||||
|
||||
// NB: we have to consider that the native thread could have already
|
||||
// (gracefully) exit (setting android_app->destroyed) and so we need
|
||||
// to be careful to avoid a deadlock waiting for a thread that's
|
||||
// already exit.
|
||||
if (android_app->destroyed) {
|
||||
pthread_mutex_unlock(&android_app->mutex);
|
||||
return;
|
||||
}
|
||||
|
||||
android_app->stateSaved = 0;
|
||||
android_app_write_cmd(android_app, APP_CMD_SAVE_STATE);
|
||||
while (!android_app->stateSaved) {
|
||||
@@ -380,8 +455,7 @@ static void onSaveInstanceState(GameActivity* activity,
|
||||
|
||||
if (android_app->savedState != NULL) {
|
||||
// Tell the Java side about our state.
|
||||
recallback((const char*)android_app->savedState,
|
||||
android_app->savedStateSize, context);
|
||||
recallback((const char*)android_app->savedState, android_app->savedStateSize, context);
|
||||
// Now we can free it.
|
||||
free(android_app->savedState);
|
||||
android_app->savedState = NULL;
|
||||
@@ -413,32 +487,27 @@ static void onTrimMemory(GameActivity* activity, int level) {
|
||||
|
||||
static void onWindowFocusChanged(GameActivity* activity, bool focused) {
|
||||
LOGV("WindowFocusChanged: %p -- %d", activity, focused);
|
||||
android_app_write_cmd(ToApp(activity),
|
||||
focused ? APP_CMD_GAINED_FOCUS : APP_CMD_LOST_FOCUS);
|
||||
android_app_write_cmd(ToApp(activity), focused ? APP_CMD_GAINED_FOCUS : APP_CMD_LOST_FOCUS);
|
||||
}
|
||||
|
||||
static void onNativeWindowCreated(GameActivity* activity,
|
||||
ANativeWindow* window) {
|
||||
static void onNativeWindowCreated(GameActivity* activity, ANativeWindow* window) {
|
||||
LOGV("NativeWindowCreated: %p -- %p", activity, window);
|
||||
android_app_set_window(ToApp(activity), window);
|
||||
}
|
||||
|
||||
static void onNativeWindowDestroyed(GameActivity* activity,
|
||||
ANativeWindow* window) {
|
||||
static void onNativeWindowDestroyed(GameActivity* activity, ANativeWindow* window) {
|
||||
LOGV("NativeWindowDestroyed: %p -- %p", activity, window);
|
||||
android_app_set_window(ToApp(activity), NULL);
|
||||
}
|
||||
|
||||
static void onNativeWindowRedrawNeeded(GameActivity* activity,
|
||||
ANativeWindow* window) {
|
||||
static void onNativeWindowRedrawNeeded(GameActivity* activity, ANativeWindow* window) {
|
||||
LOGV("NativeWindowRedrawNeeded: %p -- %p", activity, window);
|
||||
android_app_write_cmd(ToApp(activity), APP_CMD_WINDOW_REDRAW_NEEDED);
|
||||
}
|
||||
|
||||
static void onNativeWindowResized(GameActivity* activity, ANativeWindow* window,
|
||||
int32_t width, int32_t height) {
|
||||
LOGV("NativeWindowResized: %p -- %p ( %d x %d )", activity, window, width,
|
||||
height);
|
||||
static void onNativeWindowResized(GameActivity* activity, ANativeWindow* window, int32_t width,
|
||||
int32_t height) {
|
||||
LOGV("NativeWindowResized: %p -- %p ( %d x %d )", activity, window, width, height);
|
||||
android_app_write_cmd(ToApp(activity), APP_CMD_WINDOW_RESIZED);
|
||||
}
|
||||
|
||||
@@ -451,7 +520,6 @@ void android_app_set_motion_event_filter(struct android_app* app,
|
||||
|
||||
bool android_app_input_available_wake_up(struct android_app* app) {
|
||||
pthread_mutex_lock(&app->mutex);
|
||||
// TODO: use atomic ops for this
|
||||
bool available = app->inputAvailableWakeUp;
|
||||
app->inputAvailableWakeUp = false;
|
||||
pthread_mutex_unlock(&app->mutex);
|
||||
@@ -466,7 +534,6 @@ static void notifyInput(struct android_app* android_app) {
|
||||
}
|
||||
|
||||
if (android_app->looper != NULL) {
|
||||
LOGV("Input Notify: %p", android_app);
|
||||
// for the app thread to know why it received the wake() up
|
||||
android_app->inputAvailableWakeUp = true;
|
||||
android_app->inputSwapPending = true;
|
||||
@@ -474,70 +541,62 @@ static void notifyInput(struct android_app* android_app) {
|
||||
}
|
||||
}
|
||||
|
||||
static bool onTouchEvent(GameActivity* activity,
|
||||
const GameActivityMotionEvent* event,
|
||||
const GameActivityHistoricalPointerAxes* historical,
|
||||
int historicalLen) {
|
||||
static bool onTouchEvent(GameActivity* activity, const GameActivityMotionEvent* event) {
|
||||
struct android_app* android_app = ToApp(activity);
|
||||
pthread_mutex_lock(&android_app->mutex);
|
||||
|
||||
if (android_app->motionEventFilter != NULL &&
|
||||
!android_app->motionEventFilter(event)) {
|
||||
// NB: we have to consider that the native thread could have already
|
||||
// (gracefully) exit (setting android_app->destroyed) and so we need
|
||||
// to be careful to avoid a deadlock waiting for a thread that's
|
||||
// already exit.
|
||||
if (android_app->destroyed) {
|
||||
pthread_mutex_unlock(&android_app->mutex);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (android_app->motionEventFilter != NULL && !android_app->motionEventFilter(event)) {
|
||||
pthread_mutex_unlock(&android_app->mutex);
|
||||
return false;
|
||||
}
|
||||
|
||||
struct android_input_buffer* inputBuffer =
|
||||
&android_app->inputBuffers[android_app->currentInputBuffer];
|
||||
&android_app->inputBuffers[android_app->currentInputBuffer];
|
||||
|
||||
// Add to the list of active motion events
|
||||
if (inputBuffer->motionEventsCount <
|
||||
NATIVE_APP_GLUE_MAX_NUM_MOTION_EVENTS) {
|
||||
int new_ix = inputBuffer->motionEventsCount;
|
||||
memcpy(&inputBuffer->motionEvents[new_ix], event,
|
||||
sizeof(GameActivityMotionEvent));
|
||||
++inputBuffer->motionEventsCount;
|
||||
if (inputBuffer->motionEventsCount >= inputBuffer->motionEventsBufferSize) {
|
||||
inputBuffer->motionEventsBufferSize *= 2;
|
||||
inputBuffer->motionEvents =
|
||||
(GameActivityMotionEvent*)realloc(inputBuffer->motionEvents,
|
||||
sizeof(GameActivityMotionEvent) *
|
||||
inputBuffer->motionEventsBufferSize);
|
||||
|
||||
if (inputBuffer->historicalSamplesCount + historicalLen <=
|
||||
NATIVE_APP_GLUE_MAX_HISTORICAL_POINTER_SAMPLES) {
|
||||
|
||||
int start_ix = inputBuffer->historicalSamplesCount;
|
||||
memcpy(&inputBuffer->historicalAxisSamples[start_ix], historical,
|
||||
sizeof(historical[0]) * historicalLen);
|
||||
inputBuffer->historicalSamplesCount += event->historicalCount;
|
||||
|
||||
inputBuffer->motionEvents[new_ix].historicalStart = start_ix;
|
||||
inputBuffer->motionEvents[new_ix].historicalCount = historicalLen;
|
||||
} else {
|
||||
inputBuffer->motionEvents[new_ix].historicalCount = 0;
|
||||
if (inputBuffer->motionEvents == NULL) {
|
||||
LOGE("onTouchEvent: out of memory");
|
||||
abort();
|
||||
}
|
||||
|
||||
notifyInput(android_app);
|
||||
} else {
|
||||
LOGW_ONCE("Motion event will be dropped because the number of unconsumed motion"
|
||||
" events exceeded NATIVE_APP_GLUE_MAX_NUM_MOTION_EVENTS (%d). Consider setting"
|
||||
" NATIVE_APP_GLUE_MAX_NUM_MOTION_EVENTS_OVERRIDE to a larger value",
|
||||
NATIVE_APP_GLUE_MAX_NUM_MOTION_EVENTS);
|
||||
}
|
||||
|
||||
int new_ix = inputBuffer->motionEventsCount;
|
||||
memcpy(&inputBuffer->motionEvents[new_ix], event, sizeof(GameActivityMotionEvent));
|
||||
++inputBuffer->motionEventsCount;
|
||||
notifyInput(android_app);
|
||||
|
||||
// android_app_write_cmd(android_app, APP_CMD_TOUCH_EVENT);
|
||||
pthread_mutex_unlock(&android_app->mutex);
|
||||
return true;
|
||||
}
|
||||
|
||||
struct android_input_buffer* android_app_swap_input_buffers(
|
||||
struct android_app* android_app) {
|
||||
struct android_input_buffer* android_app_swap_input_buffers(struct android_app* android_app) {
|
||||
pthread_mutex_lock(&android_app->mutex);
|
||||
|
||||
struct android_input_buffer* inputBuffer =
|
||||
&android_app->inputBuffers[android_app->currentInputBuffer];
|
||||
&android_app->inputBuffers[android_app->currentInputBuffer];
|
||||
|
||||
if (inputBuffer->motionEventsCount == 0 &&
|
||||
inputBuffer->keyEventsCount == 0) {
|
||||
if (inputBuffer->motionEventsCount == 0 && inputBuffer->keyEventsCount == 0) {
|
||||
inputBuffer = NULL;
|
||||
} else {
|
||||
android_app->currentInputBuffer =
|
||||
(android_app->currentInputBuffer + 1) %
|
||||
NATIVE_APP_GLUE_MAX_INPUT_BUFFERS;
|
||||
(android_app->currentInputBuffer + 1) % NATIVE_APP_GLUE_MAX_INPUT_BUFFERS;
|
||||
}
|
||||
|
||||
android_app->inputSwapPending = false;
|
||||
@@ -549,11 +608,18 @@ struct android_input_buffer* android_app_swap_input_buffers(
|
||||
}
|
||||
|
||||
void android_app_clear_motion_events(struct android_input_buffer* inputBuffer) {
|
||||
inputBuffer->motionEventsCount = 0;
|
||||
// We do not need to lock here if the inputBuffer has already been swapped
|
||||
// as is handled by the game loop thread
|
||||
while (inputBuffer->motionEventsCount > 0) {
|
||||
GameActivityMotionEvent_destroy(
|
||||
&inputBuffer->motionEvents[inputBuffer->motionEventsCount - 1]);
|
||||
|
||||
inputBuffer->motionEventsCount--;
|
||||
}
|
||||
assert(inputBuffer->motionEventsCount == 0);
|
||||
}
|
||||
|
||||
void android_app_set_key_event_filter(struct android_app* app,
|
||||
android_key_event_filter filter) {
|
||||
void android_app_set_key_event_filter(struct android_app* app, android_key_event_filter filter) {
|
||||
pthread_mutex_lock(&app->mutex);
|
||||
app->keyEventFilter = filter;
|
||||
pthread_mutex_unlock(&app->mutex);
|
||||
@@ -563,30 +629,43 @@ static bool onKey(GameActivity* activity, const GameActivityKeyEvent* event) {
|
||||
struct android_app* android_app = ToApp(activity);
|
||||
pthread_mutex_lock(&android_app->mutex);
|
||||
|
||||
if (android_app->keyEventFilter != NULL &&
|
||||
!android_app->keyEventFilter(event)) {
|
||||
// NB: we have to consider that the native thread could have already
|
||||
// (gracefully) exit (setting android_app->destroyed) and so we need
|
||||
// to be careful to avoid a deadlock waiting for a thread that's
|
||||
// already exit.
|
||||
if (android_app->destroyed) {
|
||||
pthread_mutex_unlock(&android_app->mutex);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (android_app->keyEventFilter != NULL && !android_app->keyEventFilter(event)) {
|
||||
pthread_mutex_unlock(&android_app->mutex);
|
||||
return false;
|
||||
}
|
||||
|
||||
struct android_input_buffer* inputBuffer =
|
||||
&android_app->inputBuffers[android_app->currentInputBuffer];
|
||||
&android_app->inputBuffers[android_app->currentInputBuffer];
|
||||
|
||||
// Add to the list of active key down events
|
||||
if (inputBuffer->keyEventsCount < NATIVE_APP_GLUE_MAX_NUM_KEY_EVENTS) {
|
||||
int new_ix = inputBuffer->keyEventsCount;
|
||||
memcpy(&inputBuffer->keyEvents[new_ix], event,
|
||||
sizeof(GameActivityKeyEvent));
|
||||
++inputBuffer->keyEventsCount;
|
||||
if (inputBuffer->keyEventsCount >= inputBuffer->keyEventsBufferSize) {
|
||||
inputBuffer->keyEventsBufferSize = inputBuffer->keyEventsBufferSize * 2;
|
||||
inputBuffer->keyEvents =
|
||||
(GameActivityKeyEvent*)realloc(inputBuffer->keyEvents,
|
||||
sizeof(GameActivityKeyEvent) *
|
||||
inputBuffer->keyEventsBufferSize);
|
||||
|
||||
notifyInput(android_app);
|
||||
} else {
|
||||
LOGW_ONCE("Key event will be dropped because the number of unconsumed key events exceeded"
|
||||
" NATIVE_APP_GLUE_MAX_NUM_KEY_EVENTS (%d). Consider setting"
|
||||
" NATIVE_APP_GLUE_MAX_NUM_KEY_EVENTS_OVERRIDE to a larger value",
|
||||
NATIVE_APP_GLUE_MAX_NUM_KEY_EVENTS);
|
||||
if (inputBuffer->keyEvents == NULL) {
|
||||
LOGE("onKey: out of memory");
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
int new_ix = inputBuffer->keyEventsCount;
|
||||
memcpy(&inputBuffer->keyEvents[new_ix], event, sizeof(GameActivityKeyEvent));
|
||||
++inputBuffer->keyEventsCount;
|
||||
notifyInput(android_app);
|
||||
|
||||
// android_app_write_cmd(android_app, APP_CMD_KEY_EVENT);
|
||||
pthread_mutex_unlock(&android_app->mutex);
|
||||
return true;
|
||||
}
|
||||
@@ -595,12 +674,13 @@ void android_app_clear_key_events(struct android_input_buffer* inputBuffer) {
|
||||
inputBuffer->keyEventsCount = 0;
|
||||
}
|
||||
|
||||
static void onTextInputEvent(GameActivity* activity,
|
||||
const GameTextInputState* state) {
|
||||
static void onTextInputEvent(GameActivity* activity, const GameTextInputState* state) {
|
||||
struct android_app* android_app = ToApp(activity);
|
||||
pthread_mutex_lock(&android_app->mutex);
|
||||
|
||||
android_app->textInputState = 1;
|
||||
if (!android_app->destroyed) {
|
||||
android_app->textInputState = 1;
|
||||
notifyInput(android_app);
|
||||
}
|
||||
pthread_mutex_unlock(&android_app->mutex);
|
||||
}
|
||||
|
||||
@@ -609,9 +689,61 @@ static void onWindowInsetsChanged(GameActivity* activity) {
|
||||
android_app_write_cmd(ToApp(activity), APP_CMD_WINDOW_INSETS_CHANGED);
|
||||
}
|
||||
|
||||
static void onContentRectChanged(GameActivity* activity, const ARect* rect) {
|
||||
LOGV("ContentRectChanged: %p -- (%d %d) (%d %d)", activity, rect->left, rect->top, rect->right,
|
||||
rect->bottom);
|
||||
|
||||
struct android_app* android_app = ToApp(activity);
|
||||
|
||||
pthread_mutex_lock(&android_app->mutex);
|
||||
android_app->contentRect = *rect;
|
||||
|
||||
android_app_write_cmd(android_app, APP_CMD_CONTENT_RECT_CHANGED);
|
||||
pthread_mutex_unlock(&android_app->mutex);
|
||||
}
|
||||
|
||||
static void onSoftwareKeyboardVisibilityChanged(GameActivity* activity, bool visible) {
|
||||
LOGV("SoftwareKeyboardVisibilityChanged: %p -- %d", activity, (int)visible);
|
||||
|
||||
struct android_app* android_app = ToApp(activity);
|
||||
|
||||
pthread_mutex_lock(&android_app->mutex);
|
||||
android_app->softwareKeyboardVisible = visible;
|
||||
|
||||
android_app_write_cmd(android_app, APP_CMD_SOFTWARE_KB_VIS_CHANGED);
|
||||
pthread_mutex_unlock(&android_app->mutex);
|
||||
}
|
||||
|
||||
static bool onEditorAction(GameActivity* activity, int action) {
|
||||
LOGV("EditorAction: %p -- %d", activity, action);
|
||||
|
||||
struct android_app* android_app = ToApp(activity);
|
||||
|
||||
pthread_mutex_lock(&android_app->mutex);
|
||||
|
||||
// XXX: this is a racy design that could lose InputConnection actions if the
|
||||
// application doesn't manage to look at app->editorAction before another
|
||||
// action is delivered.
|
||||
if (android_app->pendingEditorAction) {
|
||||
LOGW("Dropping editor action %d because previous action %d not yet "
|
||||
"handled",
|
||||
action, android_app->editorAction);
|
||||
}
|
||||
android_app->editorAction = action;
|
||||
android_app->pendingEditorAction = true;
|
||||
notifyInput(android_app);
|
||||
// TODO: buffer IME text events and editor actions like other input events
|
||||
|
||||
// android_app_write_cmd(android_app, APP_CMD_EDITOR_ACTION);
|
||||
pthread_mutex_unlock(&android_app->mutex);
|
||||
return true;
|
||||
}
|
||||
|
||||
// XXX: This symbol is renamed with a _C suffix so we can implement
|
||||
// `GameActivity_onCreate` as a wrapper in Rust that does some additional setup
|
||||
// before calling this function,
|
||||
JNIEXPORT
|
||||
void GameActivity_onCreate_C(GameActivity* activity, void* savedState,
|
||||
size_t savedStateSize) {
|
||||
void GameActivity_onCreate_C(GameActivity* activity, void* savedState, size_t savedStateSize) {
|
||||
LOGV("Creating: %p", activity);
|
||||
activity->callbacks->onDestroy = onDestroy;
|
||||
activity->callbacks->onStart = onStart;
|
||||
@@ -628,12 +760,13 @@ void GameActivity_onCreate_C(GameActivity* activity, void* savedState,
|
||||
activity->callbacks->onWindowFocusChanged = onWindowFocusChanged;
|
||||
activity->callbacks->onNativeWindowCreated = onNativeWindowCreated;
|
||||
activity->callbacks->onNativeWindowDestroyed = onNativeWindowDestroyed;
|
||||
activity->callbacks->onNativeWindowRedrawNeeded =
|
||||
onNativeWindowRedrawNeeded;
|
||||
activity->callbacks->onNativeWindowRedrawNeeded = onNativeWindowRedrawNeeded;
|
||||
activity->callbacks->onNativeWindowResized = onNativeWindowResized;
|
||||
activity->callbacks->onWindowInsetsChanged = onWindowInsetsChanged;
|
||||
activity->callbacks->onContentRectChanged = onContentRectChanged;
|
||||
activity->callbacks->onSoftwareKeyboardVisibilityChanged = onSoftwareKeyboardVisibilityChanged;
|
||||
activity->callbacks->onEditorAction = onEditorAction;
|
||||
LOGV("Callbacks set: %p", activity->callbacks);
|
||||
|
||||
activity->instance =
|
||||
android_app_create(activity, savedState, savedStateSize);
|
||||
activity->instance = android_app_create(activity, savedState, savedStateSize);
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"name": "game-activity",
|
||||
"schema_version": 1,
|
||||
"dependencies": [],
|
||||
"version": "0.0.1",
|
||||
"cpp_files": [
|
||||
"src/common/system_utils.cpp",
|
||||
"src/game-activity/GameActivity.cpp",
|
||||
"src/game-activity/native_app_glue/android_native_app_glue.c",
|
||||
"src/game-activity/GameActivityEvents.cpp",
|
||||
"src/game-text-input/gametextinput.cpp"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,918 @@
|
||||
/*
|
||||
* Copyright (C) 2021 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @defgroup game_text_input Game Text Input
|
||||
* The interface to use GameTextInput.
|
||||
* @{
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <android/rect.h>
|
||||
#include <jni.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "common/gamesdk_common.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define GAMETEXTINPUT_VERSION_REVISION 7cd950d0022d01f1e7e2b470aba5a7b1abacdfaa
|
||||
#define GAMETEXTINPUT_MAJOR_VERSION 4
|
||||
#define GAMETEXTINPUT_MINOR_VERSION 3
|
||||
#define GAMETEXTINPUT_BUGFIX_VERSION 0
|
||||
#define GAMETEXTINPUT_PACKED_VERSION \
|
||||
ANDROID_GAMESDK_PACKED_VERSION(GAMETEXTINPUT_MAJOR_VERSION, GAMETEXTINPUT_MINOR_VERSION, \
|
||||
GAMETEXTINPUT_BUGFIX_VERSION)
|
||||
|
||||
/**
|
||||
* This struct holds a span within a region of text from start (inclusive) to
|
||||
* end (exclusive). An empty span or cursor position is specified with
|
||||
* start==end. An undefined span is specified with start = end = SPAN_UNDEFINED.
|
||||
*/
|
||||
typedef struct GameTextInputSpan {
|
||||
/** The start of the region (inclusive). */
|
||||
int32_t start;
|
||||
/** The end of the region (exclusive). */
|
||||
int32_t end;
|
||||
} GameTextInputSpan;
|
||||
|
||||
/**
|
||||
* Values with special meaning in a GameTextInputSpan.
|
||||
*/
|
||||
enum GameTextInputSpanFlag : int32_t { SPAN_UNDEFINED = -1 };
|
||||
|
||||
/**
|
||||
* This struct holds the state of an editable section of text.
|
||||
* The text can have a selection and a composing region defined on it.
|
||||
* A composing region is used by IMEs that allow input using multiple steps to
|
||||
* compose a glyph or word. Use functions GameTextInput_getState and
|
||||
* GameTextInput_setState to read and modify the state that an IME is editing.
|
||||
*/
|
||||
typedef struct GameTextInputState {
|
||||
/**
|
||||
* Text owned by the state, as a modified UTF-8 string. Null-terminated.
|
||||
* https://en.wikipedia.org/wiki/UTF-8#Modified_UTF-8
|
||||
*/
|
||||
const char* text_UTF8;
|
||||
/**
|
||||
* Length in bytes of text_UTF8, *not* including the null at end.
|
||||
*/
|
||||
int32_t text_length;
|
||||
/**
|
||||
* A selection defined on the text.
|
||||
*/
|
||||
GameTextInputSpan selection;
|
||||
/**
|
||||
* A composing region defined on the text.
|
||||
*/
|
||||
GameTextInputSpan composingRegion;
|
||||
} GameTextInputState;
|
||||
|
||||
/**
|
||||
* A callback called by GameTextInput_getState.
|
||||
* @param context User-defined context.
|
||||
* @param state State, owned by the library, that will be valid for the duration
|
||||
* of the callback.
|
||||
*/
|
||||
typedef void (*GameTextInputGetStateCallback)(void* context,
|
||||
const struct GameTextInputState* state);
|
||||
|
||||
/**
|
||||
* Opaque handle to the GameTextInput API.
|
||||
*/
|
||||
typedef struct GameTextInput GameTextInput;
|
||||
|
||||
/**
|
||||
* Initialize the GameTextInput library.
|
||||
* If called twice without GameTextInput_destroy being called, the same pointer
|
||||
* will be returned and a warning will be issued.
|
||||
* @param env A JNI env valid on the calling thread. All other calls to the resulting GameTextInput
|
||||
* object must be done on the same calling thread.
|
||||
* @param max_string_size The maximum length of a string that can be edited. If
|
||||
* zero, the maximum defaults to 65536 bytes. A buffer of this size is allocated
|
||||
* at initialization.
|
||||
* @return A handle to the library.
|
||||
*/
|
||||
GameTextInput* GameTextInput_init(JNIEnv* env, uint32_t max_string_size);
|
||||
|
||||
/**
|
||||
* When using GameTextInput, you need to create a gametextinput.InputConnection
|
||||
* on the Java side and pass it using this function to the library, unless using
|
||||
* GameActivity in which case this will be done for you. See the GameActivity
|
||||
* source code or GameTextInput samples for examples of usage.
|
||||
* @param input A valid GameTextInput library handle.
|
||||
* @param inputConnection A gametextinput.InputConnection object.
|
||||
*/
|
||||
void GameTextInput_setInputConnection(GameTextInput* input, jobject inputConnection);
|
||||
|
||||
/**
|
||||
* Unless using GameActivity, it is required to call this function from your
|
||||
* Java gametextinput.Listener.stateChanged method to convert eventState and
|
||||
* trigger any event callbacks. When using GameActivity, this does not need to
|
||||
* be called as event processing is handled by the Activity.
|
||||
* @param input A valid GameTextInput library handle.
|
||||
* @param eventState A Java gametextinput.State object.
|
||||
*/
|
||||
void GameTextInput_processEvent(GameTextInput* input, jobject eventState);
|
||||
|
||||
/**
|
||||
* Free any resources owned by the GameTextInput library.
|
||||
* Any subsequent calls to the library will fail until GameTextInput_init is
|
||||
* called again.
|
||||
* @param input A valid GameTextInput library handle.
|
||||
*/
|
||||
void GameTextInput_destroy(GameTextInput* input);
|
||||
|
||||
/**
|
||||
* Flags to be passed to GameTextInput_showIme.
|
||||
*/
|
||||
enum ShowImeFlags : uint32_t {
|
||||
SHOW_IME_UNDEFINED = 0, // Default value.
|
||||
SHOW_IMPLICIT = 1, // Indicates that the user has forced the input method open so it
|
||||
// should not be closed until they explicitly do so.
|
||||
SHOW_FORCED = 2 // Indicates that this is an implicit request to show the
|
||||
// input window, not as the result of a direct request by
|
||||
// the user. The window may not be shown in this case.
|
||||
};
|
||||
|
||||
/**
|
||||
* Show the IME. Calls InputMethodManager.showSoftInput().
|
||||
* @param input A valid GameTextInput library handle.
|
||||
* @param flags Defined in ShowImeFlags above. For more information see:
|
||||
* https://developer.android.com/reference/android/view/inputmethod/InputMethodManager
|
||||
*/
|
||||
void GameTextInput_showIme(GameTextInput* input, uint32_t flags);
|
||||
|
||||
/**
|
||||
* Flags to be passed to GameTextInput_hideIme.
|
||||
*/
|
||||
enum HideImeFlags : uint32_t {
|
||||
HIDE_IME_UNDEFINED = 0, // Default value.
|
||||
HIDE_IMPLICIT_ONLY = 1, // Indicates that the soft input window should only be hidden if it
|
||||
// was not explicitly shown by the user.
|
||||
HIDE_NOT_ALWAYS = 2, // Indicates that the soft input window should normally be hidden,
|
||||
// unless it was originally shown with SHOW_FORCED.
|
||||
};
|
||||
|
||||
/**
|
||||
* Hide the IME. Calls InputMethodManager.hideSoftInputFromWindow().
|
||||
* @param input A valid GameTextInput library handle.
|
||||
* @param flags Defined in HideImeFlags above. For more information see:
|
||||
* https://developer.android.com/reference/android/view/inputmethod/InputMethodManager
|
||||
*/
|
||||
void GameTextInput_hideIme(GameTextInput* input, uint32_t flags);
|
||||
|
||||
/**
|
||||
* Restarts the input method. Calls InputMethodManager.restartInput().
|
||||
* @param input A valid GameTextInput library handle.
|
||||
*/
|
||||
void GameTextInput_restartInput(GameTextInput* input);
|
||||
|
||||
/**
|
||||
* Call a callback with the current GameTextInput state, which may have been
|
||||
* modified by changes in the IME and calls to GameTextInput_setState. We use a
|
||||
* callback rather than returning the state in order to simplify ownership of
|
||||
* text_UTF8 strings. These strings are only valid during the calling of the
|
||||
* callback.
|
||||
* @param input A valid GameTextInput library handle.
|
||||
* @param callback A function that will be called with valid state.
|
||||
* @param context Context used by the callback.
|
||||
*/
|
||||
void GameTextInput_getState(GameTextInput* input, GameTextInputGetStateCallback callback,
|
||||
void* context);
|
||||
|
||||
/**
|
||||
* Set the current GameTextInput state. This state is reflected to any active
|
||||
* IME.
|
||||
* @param input A valid GameTextInput library handle.
|
||||
* @param state The state to set. Ownership is maintained by the caller and must
|
||||
* remain valid for the duration of the call.
|
||||
*/
|
||||
void GameTextInput_setState(GameTextInput* input, const GameTextInputState* state);
|
||||
|
||||
/**
|
||||
* Type of the callback needed by GameTextInput_setEventCallback that will be
|
||||
* called every time the IME state changes.
|
||||
* @param context User-defined context set in GameTextInput_setEventCallback.
|
||||
* @param current_state Current IME state, owned by the library and valid during
|
||||
* the callback.
|
||||
*/
|
||||
typedef void (*GameTextInputEventCallback)(void* context, const GameTextInputState* current_state);
|
||||
|
||||
/**
|
||||
* Optionally set a callback to be called whenever the IME state changes.
|
||||
* Not necessary if you are using GameActivity, which handles these callbacks
|
||||
* for you.
|
||||
* @param input A valid GameTextInput library handle.
|
||||
* @param callback Called by the library when the IME state changes.
|
||||
* @param context Context passed as first argument to the callback.
|
||||
* <b>This function is deprecated. Don't perform any complex processing inside
|
||||
* the callback other than copying the state variable. Using any synchronization
|
||||
* primitives inside this callback may cause a deadlock.</b>
|
||||
*/
|
||||
void GameTextInput_setEventCallback(GameTextInput* input, GameTextInputEventCallback callback,
|
||||
void* context);
|
||||
|
||||
/**
|
||||
* Type of the callback needed by GameTextInput_setImeInsetsCallback that will
|
||||
* be called every time the IME window insets change.
|
||||
* @param context User-defined context set in
|
||||
* GameTextInput_setImeWIndowInsetsCallback.
|
||||
* @param current_insets Current IME insets, owned by the library and valid
|
||||
* during the callback.
|
||||
*/
|
||||
typedef void (*GameTextInputImeInsetsCallback)(void* context, const ARect* current_insets);
|
||||
|
||||
/**
|
||||
* Optionally set a callback to be called whenever the IME insets change.
|
||||
* Not necessary if you are using GameActivity, which handles these callbacks
|
||||
* for you.
|
||||
* @param input A valid GameTextInput library handle.
|
||||
* @param callback Called by the library when the IME insets change.
|
||||
* @param context Context passed as first argument to the callback.
|
||||
*/
|
||||
void GameTextInput_setImeInsetsCallback(GameTextInput* input,
|
||||
GameTextInputImeInsetsCallback callback, void* context);
|
||||
|
||||
/**
|
||||
* Get the current window insets for the IME.
|
||||
* @param input A valid GameTextInput library handle.
|
||||
* @param insets Filled with the current insets by this function.
|
||||
*/
|
||||
void GameTextInput_getImeInsets(const GameTextInput* input, ARect* insets);
|
||||
|
||||
/**
|
||||
* Unless using GameActivity, it is required to call this function from your
|
||||
* Java gametextinput.Listener.onImeInsetsChanged method to
|
||||
* trigger any event callbacks. When using GameActivity, this does not need to
|
||||
* be called as insets processing is handled by the Activity.
|
||||
* @param input A valid GameTextInput library handle.
|
||||
* @param eventState A Java gametextinput.State object.
|
||||
*/
|
||||
void GameTextInput_processImeInsets(GameTextInput* input, const ARect* insets);
|
||||
|
||||
/**
|
||||
* Convert a GameTextInputState struct to a Java gametextinput.State object.
|
||||
* Don't forget to delete the returned Java local ref when you're done.
|
||||
* @param input A valid GameTextInput library handle.
|
||||
* @param state Input state to convert.
|
||||
* @return A Java object of class gametextinput.State. The caller is required to
|
||||
* delete this local reference.
|
||||
*/
|
||||
jobject GameTextInputState_toJava(const GameTextInput* input, const GameTextInputState* state);
|
||||
|
||||
/**
|
||||
* Convert from a Java gametextinput.State object into a C GameTextInputState
|
||||
* struct.
|
||||
* @param input A valid GameTextInput library handle.
|
||||
* @param state A Java gametextinput.State object.
|
||||
* @param callback A function called with the C struct, valid for the duration
|
||||
* of the call.
|
||||
* @param context Context passed to the callback.
|
||||
*/
|
||||
void GameTextInputState_fromJava(const GameTextInput* input, jobject state,
|
||||
GameTextInputGetStateCallback callback, void* context);
|
||||
|
||||
/**
|
||||
* Definitions for inputType argument of GameActivity_setImeEditorInfo()
|
||||
*
|
||||
* <pre>
|
||||
* |-------|-------|-------|-------|
|
||||
* 1111 TYPE_MASK_CLASS
|
||||
* 11111111 TYPE_MASK_VARIATION
|
||||
* 111111111111 TYPE_MASK_FLAGS
|
||||
* |-------|-------|-------|-------|
|
||||
* TYPE_NULL
|
||||
* |-------|-------|-------|-------|
|
||||
* 1 TYPE_CLASS_TEXT
|
||||
* 1 TYPE_TEXT_VARIATION_URI
|
||||
* 1 TYPE_TEXT_VARIATION_EMAIL_ADDRESS
|
||||
* 11 TYPE_TEXT_VARIATION_EMAIL_SUBJECT
|
||||
* 1 TYPE_TEXT_VARIATION_SHORT_MESSAGE
|
||||
* 1 1 TYPE_TEXT_VARIATION_LONG_MESSAGE
|
||||
* 11 TYPE_TEXT_VARIATION_PERSON_NAME
|
||||
* 111 TYPE_TEXT_VARIATION_POSTAL_ADDRESS
|
||||
* 1 TYPE_TEXT_VARIATION_PASSWORD
|
||||
* 1 1 TYPE_TEXT_VARIATION_VISIBLE_PASSWORD
|
||||
* 1 1 TYPE_TEXT_VARIATION_WEB_EDIT_TEXT
|
||||
* 1 11 TYPE_TEXT_VARIATION_FILTER
|
||||
* 11 TYPE_TEXT_VARIATION_PHONETIC
|
||||
* 11 1 TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS
|
||||
* 111 TYPE_TEXT_VARIATION_WEB_PASSWORD
|
||||
* 1 TYPE_TEXT_FLAG_CAP_CHARACTERS
|
||||
* 1 TYPE_TEXT_FLAG_CAP_WORDS
|
||||
* 1 TYPE_TEXT_FLAG_CAP_SENTENCES
|
||||
* 1 TYPE_TEXT_FLAG_AUTO_CORRECT
|
||||
* 1 TYPE_TEXT_FLAG_AUTO_COMPLETE
|
||||
* 1 TYPE_TEXT_FLAG_MULTI_LINE
|
||||
* 1 TYPE_TEXT_FLAG_IME_MULTI_LINE
|
||||
* 1 TYPE_TEXT_FLAG_NO_SUGGESTIONS
|
||||
* 1 TYPE_TEXT_FLAG_ENABLE_TEXT_CONVERSION_SUGGESTIONS
|
||||
* |-------|-------|-------|-------|
|
||||
* 1 TYPE_CLASS_NUMBER
|
||||
* 1 TYPE_NUMBER_VARIATION_PASSWORD
|
||||
* 1 TYPE_NUMBER_FLAG_SIGNED
|
||||
* 1 TYPE_NUMBER_FLAG_DECIMAL
|
||||
* |-------|-------|-------|-------|
|
||||
* 11 TYPE_CLASS_PHONE
|
||||
* |-------|-------|-------|-------|
|
||||
* 1 TYPE_CLASS_DATETIME
|
||||
* 1 TYPE_DATETIME_VARIATION_DATE
|
||||
* 1 TYPE_DATETIME_VARIATION_TIME
|
||||
* |-------|-------|-------|-------|</pre>
|
||||
*/
|
||||
|
||||
enum GameTextInputType : uint32_t {
|
||||
/**
|
||||
* Mask of bits that determine the overall class
|
||||
* of text being given. Currently supported classes are:
|
||||
* {@link #TYPE_CLASS_TEXT}, {@link #TYPE_CLASS_NUMBER},
|
||||
* {@link #TYPE_CLASS_PHONE}, {@link #TYPE_CLASS_DATETIME}.
|
||||
* <p>IME authors: If the class is not one you
|
||||
* understand, assume {@link #TYPE_CLASS_TEXT} with NO variation
|
||||
* or flags.<p>
|
||||
*/
|
||||
TYPE_MASK_CLASS = 0x0000000f,
|
||||
|
||||
/**
|
||||
* Mask of bits that determine the variation of
|
||||
* the base content class.
|
||||
*/
|
||||
TYPE_MASK_VARIATION = 0x00000ff0,
|
||||
|
||||
/**
|
||||
* Mask of bits that provide addition bit flags
|
||||
* of options.
|
||||
*/
|
||||
TYPE_MASK_FLAGS = 0x00fff000,
|
||||
|
||||
/**
|
||||
* Special content type for when no explicit type has been specified.
|
||||
* This should be interpreted to mean that the target input connection
|
||||
* is not rich, it can not process and show things like candidate text nor
|
||||
* retrieve the current text, so the input method will need to run in a
|
||||
* limited "generate key events" mode, if it supports it. Note that some
|
||||
* input methods may not support it, for example a voice-based input
|
||||
* method will likely not be able to generate key events even if this
|
||||
* flag is set.
|
||||
*/
|
||||
TYPE_NULL = 0x00000000,
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Class for normal text. This class supports the following flags (only
|
||||
* one of which should be set):
|
||||
* {@link #TYPE_TEXT_FLAG_CAP_CHARACTERS},
|
||||
* {@link #TYPE_TEXT_FLAG_CAP_WORDS}, and.
|
||||
* {@link #TYPE_TEXT_FLAG_CAP_SENTENCES}. It also supports the
|
||||
* following variations:
|
||||
* {@link #TYPE_TEXT_VARIATION_NORMAL}, and
|
||||
* {@link #TYPE_TEXT_VARIATION_URI}. If you do not recognize the
|
||||
* variation, normal should be assumed.
|
||||
*/
|
||||
TYPE_CLASS_TEXT = 0x00000001,
|
||||
|
||||
/**
|
||||
* Flag for {@link #TYPE_CLASS_TEXT}: capitalize all characters. Overrides
|
||||
* {@link #TYPE_TEXT_FLAG_CAP_WORDS} and
|
||||
* {@link #TYPE_TEXT_FLAG_CAP_SENTENCES}. This value is explicitly defined
|
||||
* to be the same as {@link TextUtils#CAP_MODE_CHARACTERS}. Of course,
|
||||
* this only affects languages where there are upper-case and lower-case
|
||||
* letters.
|
||||
*/
|
||||
TYPE_TEXT_FLAG_CAP_CHARACTERS = 0x00001000,
|
||||
|
||||
/**
|
||||
* Flag for {@link #TYPE_CLASS_TEXT}: capitalize the first character of
|
||||
* every word. Overrides {@link #TYPE_TEXT_FLAG_CAP_SENTENCES}. This
|
||||
* value is explicitly defined
|
||||
* to be the same as {@link TextUtils#CAP_MODE_WORDS}. Of course,
|
||||
* this only affects languages where there are upper-case and lower-case
|
||||
* letters.
|
||||
*/
|
||||
TYPE_TEXT_FLAG_CAP_WORDS = 0x00002000,
|
||||
|
||||
/**
|
||||
* Flag for {@link #TYPE_CLASS_TEXT}: capitalize the first character of
|
||||
* each sentence. This value is explicitly defined
|
||||
* to be the same as {@link TextUtils#CAP_MODE_SENTENCES}. For example
|
||||
* in English it means to capitalize after a period and a space (note that
|
||||
* other languages may have different characters for period, or not use
|
||||
* spaces, or use different grammatical rules). Of course, this only affects
|
||||
* languages where there are upper-case and lower-case letters.
|
||||
*/
|
||||
TYPE_TEXT_FLAG_CAP_SENTENCES = 0x00004000,
|
||||
|
||||
/**
|
||||
* Flag for {@link #TYPE_CLASS_TEXT}: the user is entering free-form
|
||||
* text that should have auto-correction applied to it. Without this flag,
|
||||
* the IME will not try to correct typos. You should always set this flag
|
||||
* unless you really expect users to type non-words in this field, for
|
||||
* example to choose a name for a character in a game.
|
||||
* Contrast this with {@link #TYPE_TEXT_FLAG_AUTO_COMPLETE} and
|
||||
* {@link #TYPE_TEXT_FLAG_NO_SUGGESTIONS}:
|
||||
* {@code TYPE_TEXT_FLAG_AUTO_CORRECT} means that the IME will try to
|
||||
* auto-correct typos as the user is typing, but does not define whether
|
||||
* the IME offers an interface to show suggestions.
|
||||
*/
|
||||
TYPE_TEXT_FLAG_AUTO_CORRECT = 0x00008000,
|
||||
|
||||
/**
|
||||
* Flag for {@link #TYPE_CLASS_TEXT}: the text editor (which means
|
||||
* the application) is performing auto-completion of the text being entered
|
||||
* based on its own semantics, which it will present to the user as they type.
|
||||
* This generally means that the input method should not be showing
|
||||
* candidates itself, but can expect the editor to supply its own
|
||||
* completions/candidates from
|
||||
* {@link android.view.inputmethod.InputMethodSession#displayCompletions
|
||||
* InputMethodSession.displayCompletions()} as a result of the editor calling
|
||||
* {@link android.view.inputmethod.InputMethodManager#displayCompletions
|
||||
* InputMethodManager.displayCompletions()}.
|
||||
* Note the contrast with {@link #TYPE_TEXT_FLAG_AUTO_CORRECT} and
|
||||
* {@link #TYPE_TEXT_FLAG_NO_SUGGESTIONS}:
|
||||
* {@code TYPE_TEXT_FLAG_AUTO_COMPLETE} means the editor should show an
|
||||
* interface for displaying suggestions, but instead of supplying its own
|
||||
* it will rely on the Editor to pass completions/corrections.
|
||||
*/
|
||||
TYPE_TEXT_FLAG_AUTO_COMPLETE = 0x00010000,
|
||||
|
||||
/**
|
||||
* Flag for {@link #TYPE_CLASS_TEXT}: multiple lines of text can be
|
||||
* entered into the field. If this flag is not set, the text field
|
||||
* will be constrained to a single line. The IME may also choose not to
|
||||
* display an enter key when this flag is not set, as there should be no
|
||||
* need to create new lines.
|
||||
*/
|
||||
TYPE_TEXT_FLAG_MULTI_LINE = 0x00020000,
|
||||
|
||||
/**
|
||||
* Flag for {@link #TYPE_CLASS_TEXT}: the regular text view associated
|
||||
* with this should not be multi-line, but when a fullscreen input method
|
||||
* is providing text it should use multiple lines if it can.
|
||||
*/
|
||||
TYPE_TEXT_FLAG_IME_MULTI_LINE = 0x00040000,
|
||||
|
||||
/**
|
||||
* Flag for {@link #TYPE_CLASS_TEXT}: the input method does not need to
|
||||
* display any dictionary-based candidates. This is useful for text views that
|
||||
* do not contain words from the language and do not benefit from any
|
||||
* dictionary-based completions or corrections. It overrides the
|
||||
* {@link #TYPE_TEXT_FLAG_AUTO_CORRECT} value when set.
|
||||
* Please avoid using this unless you are certain this is what you want.
|
||||
* Many input methods need suggestions to work well, for example the ones
|
||||
* based on gesture typing. Consider clearing
|
||||
* {@link #TYPE_TEXT_FLAG_AUTO_CORRECT} instead if you just do not
|
||||
* want the IME to correct typos.
|
||||
* Note the contrast with {@link #TYPE_TEXT_FLAG_AUTO_CORRECT} and
|
||||
* {@link #TYPE_TEXT_FLAG_AUTO_COMPLETE}:
|
||||
* {@code TYPE_TEXT_FLAG_NO_SUGGESTIONS} means the IME does not need to
|
||||
* show an interface to display suggestions. Most IMEs will also take this to
|
||||
* mean they do not need to try to auto-correct what the user is typing.
|
||||
*/
|
||||
TYPE_TEXT_FLAG_NO_SUGGESTIONS = 0x00080000,
|
||||
|
||||
/**
|
||||
* Flag for {@link #TYPE_CLASS_TEXT}: Let the IME know the text conversion
|
||||
* suggestions are required by the application. Text conversion suggestion is
|
||||
* for the transliteration languages which has pronunciation characters and
|
||||
* target characters. When the user is typing the pronunciation charactes, the
|
||||
* IME could provide the possible target characters to the user. When this
|
||||
* flag is set, the IME should insert the text conversion suggestions through
|
||||
* {@link Builder#setTextConversionSuggestions(List)} and
|
||||
* the {@link TextAttribute} with initialized with the text conversion
|
||||
* suggestions is provided by the IME to the application. To receive the
|
||||
* additional information, the application needs to implement {@link
|
||||
* InputConnection#setComposingText(CharSequence, int, TextAttribute)},
|
||||
* {@link InputConnection#setComposingRegion(int, int, TextAttribute)}, and
|
||||
* {@link InputConnection#commitText(CharSequence, int, TextAttribute)}.
|
||||
*/
|
||||
TYPE_TEXT_FLAG_ENABLE_TEXT_CONVERSION_SUGGESTIONS = 0x00100000,
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Default variation of {@link #TYPE_CLASS_TEXT}: plain old normal text.
|
||||
*/
|
||||
TYPE_TEXT_VARIATION_NORMAL = 0x00000000,
|
||||
|
||||
/**
|
||||
* Variation of {@link #TYPE_CLASS_TEXT}: entering a URI.
|
||||
*/
|
||||
TYPE_TEXT_VARIATION_URI = 0x00000010,
|
||||
|
||||
/**
|
||||
* Variation of {@link #TYPE_CLASS_TEXT}: entering an e-mail address.
|
||||
*/
|
||||
TYPE_TEXT_VARIATION_EMAIL_ADDRESS = 0x00000020,
|
||||
|
||||
/**
|
||||
* Variation of {@link #TYPE_CLASS_TEXT}: entering the subject line of
|
||||
* an e-mail.
|
||||
*/
|
||||
TYPE_TEXT_VARIATION_EMAIL_SUBJECT = 0x00000030,
|
||||
|
||||
/**
|
||||
* Variation of {@link #TYPE_CLASS_TEXT}: entering a short, possibly informal
|
||||
* message such as an instant message or a text message.
|
||||
*/
|
||||
TYPE_TEXT_VARIATION_SHORT_MESSAGE = 0x00000040,
|
||||
|
||||
/**
|
||||
* Variation of {@link #TYPE_CLASS_TEXT}: entering the content of a long,
|
||||
* possibly formal message such as the body of an e-mail.
|
||||
*/
|
||||
TYPE_TEXT_VARIATION_LONG_MESSAGE = 0x00000050,
|
||||
|
||||
/**
|
||||
* Variation of {@link #TYPE_CLASS_TEXT}: entering the name of a person.
|
||||
*/
|
||||
TYPE_TEXT_VARIATION_PERSON_NAME = 0x00000060,
|
||||
|
||||
/**
|
||||
* Variation of {@link #TYPE_CLASS_TEXT}: entering a postal mailing address.
|
||||
*/
|
||||
TYPE_TEXT_VARIATION_POSTAL_ADDRESS = 0x00000070,
|
||||
|
||||
/**
|
||||
* Variation of {@link #TYPE_CLASS_TEXT}: entering a password.
|
||||
*/
|
||||
TYPE_TEXT_VARIATION_PASSWORD = 0x00000080,
|
||||
|
||||
/**
|
||||
* Variation of {@link #TYPE_CLASS_TEXT}: entering a password, which should
|
||||
* be visible to the user.
|
||||
*/
|
||||
TYPE_TEXT_VARIATION_VISIBLE_PASSWORD = 0x00000090,
|
||||
|
||||
/**
|
||||
* Variation of {@link #TYPE_CLASS_TEXT}: entering text inside of a web form.
|
||||
*/
|
||||
TYPE_TEXT_VARIATION_WEB_EDIT_TEXT = 0x000000a0,
|
||||
|
||||
/**
|
||||
* Variation of {@link #TYPE_CLASS_TEXT}: entering text to filter contents
|
||||
* of a list etc.
|
||||
*/
|
||||
TYPE_TEXT_VARIATION_FILTER = 0x000000b0,
|
||||
|
||||
/**
|
||||
* Variation of {@link #TYPE_CLASS_TEXT}: entering text for phonetic
|
||||
* pronunciation, such as a phonetic name field in contacts. This is mostly
|
||||
* useful for languages where one spelling may have several phonetic
|
||||
* readings, like Japanese.
|
||||
*/
|
||||
TYPE_TEXT_VARIATION_PHONETIC = 0x000000c0,
|
||||
|
||||
/**
|
||||
* Variation of {@link #TYPE_CLASS_TEXT}: entering e-mail address inside
|
||||
* of a web form. This was added in
|
||||
* {@link android.os.Build.VERSION_CODES#HONEYCOMB}. An IME must target
|
||||
* this API version or later to see this input type; if it doesn't, a request
|
||||
* for this type will be seen as {@link #TYPE_TEXT_VARIATION_EMAIL_ADDRESS}
|
||||
* when passed through {@link
|
||||
* android.view.inputmethod.EditorInfo#makeCompatible(int)
|
||||
* EditorInfo.makeCompatible(int)}.
|
||||
*/
|
||||
TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS = 0x000000d0,
|
||||
|
||||
/**
|
||||
* Variation of {@link #TYPE_CLASS_TEXT}: entering password inside
|
||||
* of a web form. This was added in
|
||||
* {@link android.os.Build.VERSION_CODES#HONEYCOMB}. An IME must target
|
||||
* this API version or later to see this input type; if it doesn't, a request
|
||||
* for this type will be seen as {@link #TYPE_TEXT_VARIATION_PASSWORD}
|
||||
* when passed through {@link
|
||||
* android.view.inputmethod.EditorInfo#makeCompatible(int)
|
||||
* EditorInfo.makeCompatible(int)}.
|
||||
*/
|
||||
TYPE_TEXT_VARIATION_WEB_PASSWORD = 0x000000e0,
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Class for numeric text. This class supports the following flags:
|
||||
* {@link #TYPE_NUMBER_FLAG_SIGNED} and
|
||||
* {@link #TYPE_NUMBER_FLAG_DECIMAL}. It also supports the following
|
||||
* variations: {@link #TYPE_NUMBER_VARIATION_NORMAL} and
|
||||
* {@link #TYPE_NUMBER_VARIATION_PASSWORD}.
|
||||
* <p>IME authors: If you do not recognize
|
||||
* the variation, normal should be assumed.</p>
|
||||
*/
|
||||
TYPE_CLASS_NUMBER = 0x00000002,
|
||||
|
||||
/**
|
||||
* Flag of {@link #TYPE_CLASS_NUMBER}: the number is signed, allowing
|
||||
* a positive or negative sign at the start.
|
||||
*/
|
||||
TYPE_NUMBER_FLAG_SIGNED = 0x00001000,
|
||||
|
||||
/**
|
||||
* Flag of {@link #TYPE_CLASS_NUMBER}: the number is decimal, allowing
|
||||
* a decimal point to provide fractional values.
|
||||
*/
|
||||
TYPE_NUMBER_FLAG_DECIMAL = 0x00002000,
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Default variation of {@link #TYPE_CLASS_NUMBER}: plain normal
|
||||
* numeric text. This was added in
|
||||
* {@link android.os.Build.VERSION_CODES#HONEYCOMB}. An IME must target
|
||||
* this API version or later to see this input type; if it doesn't, a request
|
||||
* for this type will be dropped when passed through
|
||||
* {@link android.view.inputmethod.EditorInfo#makeCompatible(int)
|
||||
* EditorInfo.makeCompatible(int)}.
|
||||
*/
|
||||
TYPE_NUMBER_VARIATION_NORMAL = 0x00000000,
|
||||
|
||||
/**
|
||||
* Variation of {@link #TYPE_CLASS_NUMBER}: entering a numeric password.
|
||||
* This was added in {@link android.os.Build.VERSION_CODES#HONEYCOMB}. An
|
||||
* IME must target this API version or later to see this input type; if it
|
||||
* doesn't, a request for this type will be dropped when passed
|
||||
* through {@link android.view.inputmethod.EditorInfo#makeCompatible(int)
|
||||
* EditorInfo.makeCompatible(int)}.
|
||||
*/
|
||||
TYPE_NUMBER_VARIATION_PASSWORD = 0x00000010,
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
/**
|
||||
* Class for a phone number. This class currently supports no variations
|
||||
* or flags.
|
||||
*/
|
||||
TYPE_CLASS_PHONE = 0x00000003,
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Class for dates and times. It supports the
|
||||
* following variations:
|
||||
* {@link #TYPE_DATETIME_VARIATION_NORMAL}
|
||||
* {@link #TYPE_DATETIME_VARIATION_DATE}, and
|
||||
* {@link #TYPE_DATETIME_VARIATION_TIME}.
|
||||
*/
|
||||
TYPE_CLASS_DATETIME = 0x00000004,
|
||||
|
||||
/**
|
||||
* Default variation of {@link #TYPE_CLASS_DATETIME}: allows entering
|
||||
* both a date and time.
|
||||
*/
|
||||
TYPE_DATETIME_VARIATION_NORMAL = 0x00000000,
|
||||
|
||||
/**
|
||||
* Default variation of {@link #TYPE_CLASS_DATETIME}: allows entering
|
||||
* only a date.
|
||||
*/
|
||||
TYPE_DATETIME_VARIATION_DATE = 0x00000010,
|
||||
|
||||
/**
|
||||
* Default variation of {@link #TYPE_CLASS_DATETIME}: allows entering
|
||||
* only a time.
|
||||
*/
|
||||
TYPE_DATETIME_VARIATION_TIME = 0x00000020,
|
||||
};
|
||||
|
||||
/**
|
||||
* actionId and imeOptions argument of GameActivity_setImeEditorInfo().
|
||||
*
|
||||
* <pre>
|
||||
* |-------|-------|-------|-------|
|
||||
* 1111 IME_MASK_ACTION
|
||||
* |-------|-------|-------|-------|
|
||||
* IME_ACTION_UNSPECIFIED
|
||||
* 1 IME_ACTION_NONE
|
||||
* 1 IME_ACTION_GO
|
||||
* 11 IME_ACTION_SEARCH
|
||||
* 1 IME_ACTION_SEND
|
||||
* 1 1 IME_ACTION_NEXT
|
||||
* 11 IME_ACTION_DONE
|
||||
* 111 IME_ACTION_PREVIOUS
|
||||
* 1 IME_FLAG_NO_PERSONALIZED_LEARNING
|
||||
* 1 IME_FLAG_NO_FULLSCREEN
|
||||
* 1 IME_FLAG_NAVIGATE_PREVIOUS
|
||||
* 1 IME_FLAG_NAVIGATE_NEXT
|
||||
* 1 IME_FLAG_NO_EXTRACT_UI
|
||||
* 1 IME_FLAG_NO_ACCESSORY_ACTION
|
||||
* 1 IME_FLAG_NO_ENTER_ACTION
|
||||
* 1 IME_FLAG_FORCE_ASCII
|
||||
* |-------|-------|-------|-------|</pre>
|
||||
*/
|
||||
|
||||
enum GameTextInputActionType : uint32_t {
|
||||
/**
|
||||
* Set of bits in {@link #imeOptions} that provide alternative actions
|
||||
* associated with the "enter" key. This both helps the IME provide
|
||||
* better feedback about what the enter key will do, and also allows it
|
||||
* to provide alternative mechanisms for providing that command.
|
||||
*/
|
||||
IME_MASK_ACTION = 0x000000ff,
|
||||
|
||||
/**
|
||||
* Bits of {@link #IME_MASK_ACTION}: no specific action has been
|
||||
* associated with this editor, let the editor come up with its own if
|
||||
* it can.
|
||||
*/
|
||||
IME_ACTION_UNSPECIFIED = 0x00000000,
|
||||
|
||||
/**
|
||||
* Bits of {@link #IME_MASK_ACTION}: there is no available action.
|
||||
*/
|
||||
IME_ACTION_NONE = 0x00000001,
|
||||
|
||||
/**
|
||||
* Bits of {@link #IME_MASK_ACTION}: the action key performs a "go"
|
||||
* operation to take the user to the target of the text they typed.
|
||||
* Typically used, for example, when entering a URL.
|
||||
*/
|
||||
IME_ACTION_GO = 0x00000002,
|
||||
|
||||
/**
|
||||
* Bits of {@link #IME_MASK_ACTION}: the action key performs a "search"
|
||||
* operation, taking the user to the results of searching for the text
|
||||
* they have typed (in whatever context is appropriate).
|
||||
*/
|
||||
IME_ACTION_SEARCH = 0x00000003,
|
||||
|
||||
/**
|
||||
* Bits of {@link #IME_MASK_ACTION}: the action key performs a "send"
|
||||
* operation, delivering the text to its target. This is typically used
|
||||
* when composing a message in IM or SMS where sending is immediate.
|
||||
*/
|
||||
IME_ACTION_SEND = 0x00000004,
|
||||
|
||||
/**
|
||||
* Bits of {@link #IME_MASK_ACTION}: the action key performs a "next"
|
||||
* operation, taking the user to the next field that will accept text.
|
||||
*/
|
||||
IME_ACTION_NEXT = 0x00000005,
|
||||
|
||||
/**
|
||||
* Bits of {@link #IME_MASK_ACTION}: the action key performs a "done"
|
||||
* operation, typically meaning there is nothing more to input and the
|
||||
* IME will be closed.
|
||||
*/
|
||||
IME_ACTION_DONE = 0x00000006,
|
||||
|
||||
/**
|
||||
* Bits of {@link #IME_MASK_ACTION}: like {@link #IME_ACTION_NEXT}, but
|
||||
* for moving to the previous field. This will normally not be used to
|
||||
* specify an action (since it precludes {@link #IME_ACTION_NEXT}), but
|
||||
* can be returned to the app if it sets {@link #IME_FLAG_NAVIGATE_PREVIOUS}.
|
||||
*/
|
||||
IME_ACTION_PREVIOUS = 0x00000007,
|
||||
};
|
||||
|
||||
enum GameTextInputImeOptions : uint32_t {
|
||||
/**
|
||||
* Flag of {@link #imeOptions}: used to request that the IME should not update
|
||||
* any personalized data such as typing history and personalized language
|
||||
* model based on what the user typed on this text editing object. Typical
|
||||
* use cases are: <ul> <li>When the application is in a special mode, where
|
||||
* user's activities are expected to be not recorded in the application's
|
||||
* history. Some web browsers and chat applications may have this kind of
|
||||
* modes.</li> <li>When storing typing history does not make much sense.
|
||||
* Specifying this flag in typing games may help to avoid typing history from
|
||||
* being filled up with words that the user is less likely to type in their
|
||||
* daily life. Another example is that when the application already knows
|
||||
* that the expected input is not a valid word (e.g. a promotion code that is
|
||||
* not a valid word in any natural language).</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>Applications need to be aware that the flag is not a guarantee, and some
|
||||
* IMEs may not respect it.</p>
|
||||
*/
|
||||
IME_FLAG_NO_PERSONALIZED_LEARNING = 0x1000000,
|
||||
|
||||
/**
|
||||
* Flag of {@link #imeOptions}: used to request that the IME never go
|
||||
* into fullscreen mode.
|
||||
* By default, IMEs may go into full screen mode when they think
|
||||
* it's appropriate, for example on small screens in landscape
|
||||
* orientation where displaying a software keyboard may occlude
|
||||
* such a large portion of the screen that the remaining part is
|
||||
* too small to meaningfully display the application UI.
|
||||
* If this flag is set, compliant IMEs will never go into full screen mode,
|
||||
* and always leave some space to display the application UI.
|
||||
* Applications need to be aware that the flag is not a guarantee, and
|
||||
* some IMEs may ignore it.
|
||||
*/
|
||||
IME_FLAG_NO_FULLSCREEN = 0x2000000,
|
||||
|
||||
/**
|
||||
* Flag of {@link #imeOptions}: like {@link #IME_FLAG_NAVIGATE_NEXT}, but
|
||||
* specifies there is something interesting that a backward navigation
|
||||
* can focus on. If the user selects the IME's facility to backward
|
||||
* navigate, this will show up in the application as an {@link
|
||||
* #IME_ACTION_PREVIOUS} at {@link InputConnection#performEditorAction(int)
|
||||
* InputConnection.performEditorAction(int)}.
|
||||
*/
|
||||
IME_FLAG_NAVIGATE_PREVIOUS = 0x4000000,
|
||||
|
||||
/**
|
||||
* Flag of {@link #imeOptions}: used to specify that there is something
|
||||
* interesting that a forward navigation can focus on. This is like using
|
||||
* {@link #IME_ACTION_NEXT}, except allows the IME to be multiline (with
|
||||
* an enter key) as well as provide forward navigation. Note that some
|
||||
* IMEs may not be able to do this, especially when running on a small
|
||||
* screen where there is little space. In that case it does not need to
|
||||
* present a UI for this option. Like {@link #IME_ACTION_NEXT}, if the
|
||||
* user selects the IME's facility to forward navigate, this will show up
|
||||
* in the application at {@link InputConnection#performEditorAction(int)
|
||||
* InputConnection.performEditorAction(int)}.
|
||||
*/
|
||||
IME_FLAG_NAVIGATE_NEXT = 0x8000000,
|
||||
|
||||
/**
|
||||
* Flag of {@link #imeOptions}: used to specify that the IME does not need
|
||||
* to show its extracted text UI. For input methods that may be fullscreen,
|
||||
* often when in landscape mode, this allows them to be smaller and let part
|
||||
* of the application be shown behind, through transparent UI parts in the
|
||||
* fullscreen IME. The part of the UI visible to the user may not be
|
||||
* responsive to touch because the IME will receive touch events, which may
|
||||
* confuse the user; use {@link #IME_FLAG_NO_FULLSCREEN} instead for a better
|
||||
* experience. Using this flag is discouraged and it may become deprecated in
|
||||
* the future. Its meaning is unclear in some situations and it may not work
|
||||
* appropriately on older versions of the platform.
|
||||
*/
|
||||
IME_FLAG_NO_EXTRACT_UI = 0x10000000,
|
||||
|
||||
/**
|
||||
* Flag of {@link #imeOptions}: used in conjunction with one of the actions
|
||||
* masked by {@link #IME_MASK_ACTION}, this indicates that the action
|
||||
* should not be available as an accessory button on the right of the
|
||||
* extracted text when the input method is full-screen. Note that by setting
|
||||
* this flag, there can be cases where the action is simply never available to
|
||||
* the user. Setting this generally means that you think that in fullscreen
|
||||
* mode, where there is little space to show the text, it's not worth taking
|
||||
* some screen real estate to display the action and it should be used instead
|
||||
* to show more text.
|
||||
*/
|
||||
IME_FLAG_NO_ACCESSORY_ACTION = 0x20000000,
|
||||
|
||||
/**
|
||||
* Flag of {@link #imeOptions}: used in conjunction with one of the actions
|
||||
* masked by {@link #IME_MASK_ACTION}. If this flag is not set, IMEs will
|
||||
* normally replace the "enter" key with the action supplied. This flag
|
||||
* indicates that the action should not be available in-line as a replacement
|
||||
* for the "enter" key. Typically this is because the action has such a
|
||||
* significant impact or is not recoverable enough that accidentally hitting
|
||||
* it should be avoided, such as sending a message. Note that
|
||||
* {@link android.widget.TextView} will automatically set this flag for you
|
||||
* on multi-line text views.
|
||||
*/
|
||||
IME_FLAG_NO_ENTER_ACTION = 0x40000000,
|
||||
|
||||
/**
|
||||
* Flag of {@link #imeOptions}: used to request an IME that is capable of
|
||||
* inputting ASCII characters. The intention of this flag is to ensure that
|
||||
* the user can type Roman alphabet characters in a {@link
|
||||
* android.widget.TextView}. It is typically used for an account ID or
|
||||
* password input. A lot of the time, IMEs are already able to input ASCII
|
||||
* even without being told so (such IMEs already respect this flag in a
|
||||
* sense), but there are cases when this is not the default. For instance,
|
||||
* users of languages using a different script like Arabic, Greek, Hebrew or
|
||||
* Russian typically have a keyboard that can't input ASCII characters by
|
||||
* default. Applications need to be aware that the flag is not a guarantee,
|
||||
* and some IMEs may not respect it. However, it is strongly recommended for
|
||||
* IME authors to respect this flag especially when their IME could end up
|
||||
* with a state where only languages using non-ASCII are enabled.
|
||||
*/
|
||||
IME_FLAG_FORCE_ASCII = 0x80000000,
|
||||
|
||||
/**
|
||||
* Flag of {@link #internalImeOptions}: flag is set when app window containing
|
||||
* this
|
||||
* {@link EditorInfo} is using {@link Configuration#ORIENTATION_PORTRAIT}
|
||||
* mode.
|
||||
* @hide
|
||||
*/
|
||||
IME_INTERNAL_FLAG_APP_WINDOW_PORTRAIT = 0x00000001,
|
||||
|
||||
/**
|
||||
* Generic unspecified type for {@link #imeOptions}.
|
||||
*/
|
||||
IME_NULL = 0x00000000,
|
||||
};
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
/** @} */
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"export_libraries": [],
|
||||
"library_name": null,
|
||||
"android": {
|
||||
"export_libraries": null,
|
||||
"library_name": null
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,7 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
|
||||
#define LOG_TAG "GameTextInput"
|
||||
@@ -39,47 +40,52 @@ struct StateClassInfo {
|
||||
|
||||
// Main GameTextInput object.
|
||||
struct GameTextInput {
|
||||
public:
|
||||
GameTextInput(JNIEnv *env, uint32_t max_string_size);
|
||||
public:
|
||||
GameTextInput(JNIEnv* env, uint32_t max_string_size);
|
||||
~GameTextInput();
|
||||
void setState(const GameTextInputState &state);
|
||||
const GameTextInputState &getState() const { return currentState_; }
|
||||
void setState(const GameTextInputState& state);
|
||||
GameTextInputState getState() const {
|
||||
std::lock_guard<std::mutex> lock(currentStateMutex_);
|
||||
return currentState_;
|
||||
}
|
||||
void setInputConnection(jobject inputConnection);
|
||||
void processEvent(jobject textInputEvent);
|
||||
void showIme(uint32_t flags);
|
||||
void hideIme(uint32_t flags);
|
||||
void setEventCallback(GameTextInputEventCallback callback, void *context);
|
||||
jobject stateToJava(const GameTextInputState &state) const;
|
||||
void stateFromJava(jobject textInputEvent,
|
||||
GameTextInputGetStateCallback callback,
|
||||
void *context) const;
|
||||
void setImeInsetsCallback(GameTextInputImeInsetsCallback callback,
|
||||
void *context);
|
||||
void processImeInsets(const ARect *insets);
|
||||
const ARect &getImeInsets() const { return currentInsets_; }
|
||||
void restartInput();
|
||||
void setEventCallback(GameTextInputEventCallback callback, void* context);
|
||||
jobject stateToJava(const GameTextInputState& state) const;
|
||||
void stateFromJava(jobject textInputEvent, GameTextInputGetStateCallback callback,
|
||||
void* context) const;
|
||||
void setImeInsetsCallback(GameTextInputImeInsetsCallback callback, void* context);
|
||||
void processImeInsets(const ARect* insets);
|
||||
const ARect& getImeInsets() const {
|
||||
return currentInsets_;
|
||||
}
|
||||
|
||||
private:
|
||||
private:
|
||||
// Copy string and set other fields
|
||||
void setStateInner(const GameTextInputState &state);
|
||||
static void processCallback(void *context, const GameTextInputState *state);
|
||||
JNIEnv *env_ = nullptr;
|
||||
void setStateInner(const GameTextInputState& state);
|
||||
static void processCallback(void* context, const GameTextInputState* state);
|
||||
JNIEnv* env_ = nullptr;
|
||||
// Cached at initialization from
|
||||
// com/google/androidgamesdk/gametextinput/State.
|
||||
jclass stateJavaClass_ = nullptr;
|
||||
// The latest text input update.
|
||||
GameTextInputState currentState_ = {};
|
||||
// A mutex to protect currentState_.
|
||||
mutable std::mutex currentStateMutex_;
|
||||
// An instance of gametextinput.InputConnection.
|
||||
jclass inputConnectionClass_ = nullptr;
|
||||
jobject inputConnection_ = nullptr;
|
||||
jmethodID inputConnectionSetStateMethod_;
|
||||
jmethodID setSoftKeyboardActiveMethod_;
|
||||
void (*eventCallback_)(void *context,
|
||||
const struct GameTextInputState *state) = nullptr;
|
||||
void *eventCallbackContext_ = nullptr;
|
||||
void (*insetsCallback_)(void *context,
|
||||
const struct ARect *insets) = nullptr;
|
||||
jmethodID restartInputMethod_;
|
||||
void (*eventCallback_)(void* context, const struct GameTextInputState* state) = nullptr;
|
||||
void* eventCallbackContext_ = nullptr;
|
||||
void (*insetsCallback_)(void* context, const struct ARect* insets) = nullptr;
|
||||
ARect currentInsets_ = {};
|
||||
void *insetsCallbackContext_ = nullptr;
|
||||
void* insetsCallbackContext_ = nullptr;
|
||||
StateClassInfo stateClassInfo_ = {};
|
||||
// Constant-sized buffer used to store state text.
|
||||
std::vector<char> stateStringBuffer_;
|
||||
@@ -94,17 +100,14 @@ extern "C" {
|
||||
///////////////////////////////////////////////////////////
|
||||
|
||||
// Convert to a Java structure.
|
||||
jobject currentState_toJava(const GameTextInput *gameTextInput,
|
||||
const GameTextInputState *state) {
|
||||
jobject currentState_toJava(const GameTextInput* gameTextInput, const GameTextInputState* state) {
|
||||
if (state == nullptr) return NULL;
|
||||
return gameTextInput->stateToJava(*state);
|
||||
}
|
||||
|
||||
// Convert from Java structure.
|
||||
void currentState_fromJava(const GameTextInput *gameTextInput,
|
||||
jobject textInputEvent,
|
||||
GameTextInputGetStateCallback callback,
|
||||
void *context) {
|
||||
void currentState_fromJava(const GameTextInput* gameTextInput, jobject textInputEvent,
|
||||
GameTextInputGetStateCallback callback, void* context) {
|
||||
gameTextInput->stateFromJava(textInputEvent, callback, context);
|
||||
}
|
||||
|
||||
@@ -112,8 +115,7 @@ void currentState_fromJava(const GameTextInput *gameTextInput,
|
||||
/// GameTextInput C Functions
|
||||
///////////////////////////////////////////////////////////
|
||||
|
||||
struct GameTextInput *GameTextInput_init(JNIEnv *env,
|
||||
uint32_t max_string_size) {
|
||||
struct GameTextInput* GameTextInput_init(JNIEnv* env, uint32_t max_string_size) {
|
||||
if (s_gameTextInput.get() != nullptr) {
|
||||
__android_log_print(ANDROID_LOG_WARN, LOG_TAG,
|
||||
"Warning: called GameTextInput_init twice without "
|
||||
@@ -121,95 +123,91 @@ struct GameTextInput *GameTextInput_init(JNIEnv *env,
|
||||
return s_gameTextInput.get();
|
||||
}
|
||||
// Don't use make_unique, for C++11 compatibility
|
||||
s_gameTextInput =
|
||||
std::unique_ptr<GameTextInput>(new GameTextInput(env, max_string_size));
|
||||
s_gameTextInput = std::unique_ptr<GameTextInput>(new GameTextInput(env, max_string_size));
|
||||
return s_gameTextInput.get();
|
||||
}
|
||||
|
||||
void GameTextInput_destroy(GameTextInput *input) {
|
||||
void GameTextInput_destroy(GameTextInput* input) {
|
||||
if (input == nullptr || s_gameTextInput.get() == nullptr) return;
|
||||
s_gameTextInput.reset();
|
||||
}
|
||||
|
||||
void GameTextInput_setState(GameTextInput *input,
|
||||
const GameTextInputState *state) {
|
||||
void GameTextInput_setState(GameTextInput* input, const GameTextInputState* state) {
|
||||
if (state == nullptr) return;
|
||||
input->setState(*state);
|
||||
}
|
||||
|
||||
void GameTextInput_getState(GameTextInput *input,
|
||||
GameTextInputGetStateCallback callback,
|
||||
void *context) {
|
||||
callback(context, &input->getState());
|
||||
void GameTextInput_getState(GameTextInput* input, GameTextInputGetStateCallback callback,
|
||||
void* context) {
|
||||
GameTextInputState state = input->getState();
|
||||
callback(context, &state);
|
||||
}
|
||||
|
||||
void GameTextInput_setInputConnection(GameTextInput *input,
|
||||
jobject inputConnection) {
|
||||
void GameTextInput_setInputConnection(GameTextInput* input, jobject inputConnection) {
|
||||
input->setInputConnection(inputConnection);
|
||||
}
|
||||
|
||||
void GameTextInput_processEvent(GameTextInput *input, jobject textInputEvent) {
|
||||
void GameTextInput_processEvent(GameTextInput* input, jobject textInputEvent) {
|
||||
input->processEvent(textInputEvent);
|
||||
}
|
||||
|
||||
void GameTextInput_processImeInsets(GameTextInput *input, const ARect *insets) {
|
||||
void GameTextInput_processImeInsets(GameTextInput* input, const ARect* insets) {
|
||||
input->processImeInsets(insets);
|
||||
}
|
||||
|
||||
void GameTextInput_showIme(struct GameTextInput *input, uint32_t flags) {
|
||||
void GameTextInput_showIme(struct GameTextInput* input, uint32_t flags) {
|
||||
input->showIme(flags);
|
||||
}
|
||||
|
||||
void GameTextInput_hideIme(struct GameTextInput *input, uint32_t flags) {
|
||||
void GameTextInput_hideIme(struct GameTextInput* input, uint32_t flags) {
|
||||
input->hideIme(flags);
|
||||
}
|
||||
|
||||
void GameTextInput_setEventCallback(struct GameTextInput *input,
|
||||
GameTextInputEventCallback callback,
|
||||
void *context) {
|
||||
void GameTextInput_restartInput(struct GameTextInput* input) {
|
||||
input->restartInput();
|
||||
}
|
||||
|
||||
void GameTextInput_setEventCallback(struct GameTextInput* input,
|
||||
GameTextInputEventCallback callback, void* context) {
|
||||
input->setEventCallback(callback, context);
|
||||
}
|
||||
|
||||
void GameTextInput_setImeInsetsCallback(struct GameTextInput *input,
|
||||
GameTextInputImeInsetsCallback callback,
|
||||
void *context) {
|
||||
void GameTextInput_setImeInsetsCallback(struct GameTextInput* input,
|
||||
GameTextInputImeInsetsCallback callback, void* context) {
|
||||
input->setImeInsetsCallback(callback, context);
|
||||
}
|
||||
|
||||
void GameTextInput_getImeInsets(const GameTextInput *input, ARect *insets) {
|
||||
void GameTextInput_getImeInsets(const GameTextInput* input, ARect* insets) {
|
||||
*insets = input->getImeInsets();
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
} // extern "C"
|
||||
|
||||
///////////////////////////////////////////////////////////
|
||||
/// GameTextInput C++ class Implementation
|
||||
///////////////////////////////////////////////////////////
|
||||
|
||||
GameTextInput::GameTextInput(JNIEnv *env, uint32_t max_string_size)
|
||||
: env_(env),
|
||||
stateStringBuffer_(max_string_size == 0 ? DEFAULT_MAX_STRING_SIZE
|
||||
: max_string_size) {
|
||||
GameTextInput::GameTextInput(JNIEnv* env, uint32_t max_string_size)
|
||||
: env_(env),
|
||||
stateStringBuffer_(max_string_size == 0 ? DEFAULT_MAX_STRING_SIZE : max_string_size) {
|
||||
stateJavaClass_ = (jclass)env_->NewGlobalRef(
|
||||
env_->FindClass("com/google/androidgamesdk/gametextinput/State"));
|
||||
inputConnectionClass_ = (jclass)env_->NewGlobalRef(env_->FindClass(
|
||||
"com/google/androidgamesdk/gametextinput/InputConnection"));
|
||||
env_->FindClass("com/google/androidgamesdk/gametextinput/State"));
|
||||
inputConnectionClass_ = (jclass)env_->NewGlobalRef(
|
||||
env_->FindClass("com/google/androidgamesdk/gametextinput/InputConnection"));
|
||||
inputConnectionSetStateMethod_ =
|
||||
env_->GetMethodID(inputConnectionClass_, "setState",
|
||||
"(Lcom/google/androidgamesdk/gametextinput/State;)V");
|
||||
setSoftKeyboardActiveMethod_ = env_->GetMethodID(
|
||||
inputConnectionClass_, "setSoftKeyboardActive", "(ZI)V");
|
||||
env_->GetMethodID(inputConnectionClass_, "setState",
|
||||
"(Lcom/google/androidgamesdk/gametextinput/State;)V");
|
||||
setSoftKeyboardActiveMethod_ =
|
||||
env_->GetMethodID(inputConnectionClass_, "setSoftKeyboardActive", "(ZI)V");
|
||||
restartInputMethod_ = env_->GetMethodID(inputConnectionClass_, "restartInput", "()V");
|
||||
|
||||
stateClassInfo_.text =
|
||||
env_->GetFieldID(stateJavaClass_, "text", "Ljava/lang/String;");
|
||||
stateClassInfo_.selectionStart =
|
||||
env_->GetFieldID(stateJavaClass_, "selectionStart", "I");
|
||||
stateClassInfo_.selectionEnd =
|
||||
env_->GetFieldID(stateJavaClass_, "selectionEnd", "I");
|
||||
stateClassInfo_.text = env_->GetFieldID(stateJavaClass_, "text", "Ljava/lang/String;");
|
||||
stateClassInfo_.selectionStart = env_->GetFieldID(stateJavaClass_, "selectionStart", "I");
|
||||
stateClassInfo_.selectionEnd = env_->GetFieldID(stateJavaClass_, "selectionEnd", "I");
|
||||
stateClassInfo_.composingRegionStart =
|
||||
env_->GetFieldID(stateJavaClass_, "composingRegionStart", "I");
|
||||
env_->GetFieldID(stateJavaClass_, "composingRegionStart", "I");
|
||||
stateClassInfo_.composingRegionEnd =
|
||||
env_->GetFieldID(stateJavaClass_, "composingRegionEnd", "I");
|
||||
env_->GetFieldID(stateJavaClass_, "composingRegionEnd", "I");
|
||||
}
|
||||
|
||||
GameTextInput::~GameTextInput() {
|
||||
@@ -227,16 +225,17 @@ GameTextInput::~GameTextInput() {
|
||||
}
|
||||
}
|
||||
|
||||
void GameTextInput::setState(const GameTextInputState &state) {
|
||||
void GameTextInput::setState(const GameTextInputState& state) {
|
||||
if (inputConnection_ == nullptr) return;
|
||||
jobject jstate = stateToJava(state);
|
||||
env_->CallVoidMethod(inputConnection_, inputConnectionSetStateMethod_,
|
||||
jstate);
|
||||
env_->CallVoidMethod(inputConnection_, inputConnectionSetStateMethod_, jstate);
|
||||
env_->DeleteLocalRef(jstate);
|
||||
setStateInner(state);
|
||||
}
|
||||
|
||||
void GameTextInput::setStateInner(const GameTextInputState &state) {
|
||||
void GameTextInput::setStateInner(const GameTextInputState& state) {
|
||||
std::lock_guard<std::mutex> lock(currentStateMutex_);
|
||||
|
||||
// Check if we're setting using our own string (other parts may be
|
||||
// different)
|
||||
if (state.text_UTF8 == currentState_.text_UTF8) {
|
||||
@@ -244,12 +243,10 @@ void GameTextInput::setStateInner(const GameTextInputState &state) {
|
||||
return;
|
||||
}
|
||||
// Otherwise, copy across the string.
|
||||
auto bytes_needed =
|
||||
std::min(static_cast<uint32_t>(state.text_length + 1),
|
||||
static_cast<uint32_t>(stateStringBuffer_.size()));
|
||||
auto bytes_needed = std::min(static_cast<uint32_t>(state.text_length + 1),
|
||||
static_cast<uint32_t>(stateStringBuffer_.size()));
|
||||
currentState_.text_UTF8 = stateStringBuffer_.data();
|
||||
std::copy(state.text_UTF8, state.text_UTF8 + bytes_needed - 1,
|
||||
stateStringBuffer_.data());
|
||||
std::copy(state.text_UTF8, state.text_UTF8 + bytes_needed - 1, stateStringBuffer_.data());
|
||||
currentState_.text_length = state.text_length;
|
||||
currentState_.selection = state.selection;
|
||||
currentState_.composingRegion = state.composingRegion;
|
||||
@@ -263,15 +260,15 @@ void GameTextInput::setInputConnection(jobject inputConnection) {
|
||||
inputConnection_ = env_->NewGlobalRef(inputConnection);
|
||||
}
|
||||
|
||||
/*static*/ void GameTextInput::processCallback(
|
||||
void *context, const GameTextInputState *state) {
|
||||
auto thiz = static_cast<GameTextInput *>(context);
|
||||
/*static*/ void GameTextInput::processCallback(void* context, const GameTextInputState* state) {
|
||||
auto thiz = static_cast<GameTextInput*>(context);
|
||||
if (state != nullptr) thiz->setStateInner(*state);
|
||||
}
|
||||
|
||||
void GameTextInput::processEvent(jobject textInputEvent) {
|
||||
stateFromJava(textInputEvent, processCallback, this);
|
||||
if (eventCallback_) {
|
||||
std::lock_guard<std::mutex> lock(currentStateMutex_);
|
||||
eventCallback_(eventCallbackContext_, ¤tState_);
|
||||
}
|
||||
}
|
||||
@@ -279,22 +276,20 @@ void GameTextInput::processEvent(jobject textInputEvent) {
|
||||
void GameTextInput::showIme(uint32_t flags) {
|
||||
if (inputConnection_ == nullptr) return;
|
||||
env_->CallVoidMethod(inputConnection_, setSoftKeyboardActiveMethod_, true,
|
||||
flags);
|
||||
static_cast<jint>(flags));
|
||||
}
|
||||
|
||||
void GameTextInput::setEventCallback(GameTextInputEventCallback callback,
|
||||
void *context) {
|
||||
void GameTextInput::setEventCallback(GameTextInputEventCallback callback, void* context) {
|
||||
eventCallback_ = callback;
|
||||
eventCallbackContext_ = context;
|
||||
}
|
||||
|
||||
void GameTextInput::setImeInsetsCallback(
|
||||
GameTextInputImeInsetsCallback callback, void *context) {
|
||||
void GameTextInput::setImeInsetsCallback(GameTextInputImeInsetsCallback callback, void* context) {
|
||||
insetsCallback_ = callback;
|
||||
insetsCallbackContext_ = context;
|
||||
}
|
||||
|
||||
void GameTextInput::processImeInsets(const ARect *insets) {
|
||||
void GameTextInput::processImeInsets(const ARect* insets) {
|
||||
currentInsets_ = *insets;
|
||||
if (insetsCallback_) {
|
||||
insetsCallback_(insetsCallbackContext_, ¤tInsets_);
|
||||
@@ -304,21 +299,25 @@ void GameTextInput::processImeInsets(const ARect *insets) {
|
||||
void GameTextInput::hideIme(uint32_t flags) {
|
||||
if (inputConnection_ == nullptr) return;
|
||||
env_->CallVoidMethod(inputConnection_, setSoftKeyboardActiveMethod_, false,
|
||||
flags);
|
||||
static_cast<jint>(flags));
|
||||
}
|
||||
|
||||
jobject GameTextInput::stateToJava(const GameTextInputState &state) const {
|
||||
void GameTextInput::restartInput() {
|
||||
if (inputConnection_ == nullptr) return;
|
||||
env_->CallVoidMethod(inputConnection_, restartInputMethod_);
|
||||
}
|
||||
|
||||
jobject GameTextInput::stateToJava(const GameTextInputState& state) const {
|
||||
static jmethodID constructor = nullptr;
|
||||
if (constructor == nullptr) {
|
||||
constructor = env_->GetMethodID(stateJavaClass_, "<init>",
|
||||
"(Ljava/lang/String;IIII)V");
|
||||
constructor = env_->GetMethodID(stateJavaClass_, "<init>", "(Ljava/lang/String;IIII)V");
|
||||
if (constructor == nullptr) {
|
||||
__android_log_print(ANDROID_LOG_ERROR, LOG_TAG,
|
||||
"Can't find gametextinput.State constructor");
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
const char *text = state.text_UTF8;
|
||||
const char* text = state.text_UTF8;
|
||||
if (text == nullptr) {
|
||||
static char empty_string[] = "";
|
||||
text = empty_string;
|
||||
@@ -326,34 +325,27 @@ jobject GameTextInput::stateToJava(const GameTextInputState &state) const {
|
||||
// Note that this expects 'modified' UTF-8 which is not the same as UTF-8
|
||||
// https://en.wikipedia.org/wiki/UTF-8#Modified_UTF-8
|
||||
jstring jtext = env_->NewStringUTF(text);
|
||||
jobject jobj =
|
||||
env_->NewObject(stateJavaClass_, constructor, jtext,
|
||||
state.selection.start, state.selection.end,
|
||||
state.composingRegion.start, state.composingRegion.end);
|
||||
jobject jobj = env_->NewObject(stateJavaClass_, constructor, jtext, state.selection.start,
|
||||
state.selection.end, state.composingRegion.start,
|
||||
state.composingRegion.end);
|
||||
env_->DeleteLocalRef(jtext);
|
||||
return jobj;
|
||||
}
|
||||
|
||||
void GameTextInput::stateFromJava(jobject textInputEvent,
|
||||
GameTextInputGetStateCallback callback,
|
||||
void *context) const {
|
||||
jstring text =
|
||||
(jstring)env_->GetObjectField(textInputEvent, stateClassInfo_.text);
|
||||
void GameTextInput::stateFromJava(jobject textInputEvent, GameTextInputGetStateCallback callback,
|
||||
void* context) const {
|
||||
jstring text = (jstring)env_->GetObjectField(textInputEvent, stateClassInfo_.text);
|
||||
// Note this is 'modified' UTF-8, not true UTF-8. It has no NULLs in it,
|
||||
// except at the end. It's actually not specified whether the value returned
|
||||
// by GetStringUTFChars includes a null at the end, but it *seems to* on
|
||||
// Android.
|
||||
const char *text_chars = env_->GetStringUTFChars(text, NULL);
|
||||
int text_len = env_->GetStringUTFLength(
|
||||
text); // Length in bytes, *not* including the null.
|
||||
int selectionStart =
|
||||
env_->GetIntField(textInputEvent, stateClassInfo_.selectionStart);
|
||||
int selectionEnd =
|
||||
env_->GetIntField(textInputEvent, stateClassInfo_.selectionEnd);
|
||||
const char* text_chars = env_->GetStringUTFChars(text, NULL);
|
||||
int text_len = env_->GetStringUTFLength(text); // Length in bytes, *not* including the null.
|
||||
int selectionStart = env_->GetIntField(textInputEvent, stateClassInfo_.selectionStart);
|
||||
int selectionEnd = env_->GetIntField(textInputEvent, stateClassInfo_.selectionEnd);
|
||||
int composingRegionStart =
|
||||
env_->GetIntField(textInputEvent, stateClassInfo_.composingRegionStart);
|
||||
int composingRegionEnd =
|
||||
env_->GetIntField(textInputEvent, stateClassInfo_.composingRegionEnd);
|
||||
env_->GetIntField(textInputEvent, stateClassInfo_.composingRegionStart);
|
||||
int composingRegionEnd = env_->GetIntField(textInputEvent, stateClassInfo_.composingRegionEnd);
|
||||
GameTextInputState state{text_chars,
|
||||
text_len,
|
||||
{selectionStart, selectionEnd},
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"name": "game-text-input",
|
||||
"schema_version": 1,
|
||||
"dependencies": [],
|
||||
"version": "0.0.1",
|
||||
"cpp_files": [
|
||||
"src/game-text-input/gametextinput.cpp"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
#!/bin/bash
|
||||
set -xe
|
||||
|
||||
# Copies the native, prefab-src for GameActivity + GameTextInput from the
|
||||
# upstream, android-games-sdk, including our android-activity integration
|
||||
# changes.
|
||||
#
|
||||
# This code is maintained out-of-tree, based on a fork of Google's AGDK repo, so
|
||||
# it's more practical to try and upstream changes we make, or to rebase on new
|
||||
# versions.
|
||||
|
||||
if [ $# -ne 1 ]; then
|
||||
echo "Usage: $0 <android-games-sdk dir>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
SOURCE_DIR="$1"
|
||||
TOP_DIR=$(git rev-parse --show-toplevel)
|
||||
DEST_DIR="$TOP_DIR/android-activity/android-games-sdk"
|
||||
|
||||
if [ ! -d "$SOURCE_DIR" ]; then
|
||||
echo "Error: Source directory '$SOURCE_DIR' does not exist."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -d "$DEST_DIR" ]; then
|
||||
echo "Error: expected find destination directory $DEST_DIR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
rm -fr "$DEST_DIR/game-activity"
|
||||
rm -fr "$DEST_DIR/game-text-input"
|
||||
rm -fr "$DEST_DIR/src/common"
|
||||
rm -fr "$DEST_DIR/include/common"
|
||||
|
||||
mkdir -p "$DEST_DIR/game-activity"
|
||||
mkdir -p "$DEST_DIR/game-text-input"
|
||||
mkdir -p "$DEST_DIR/include/common"
|
||||
mkdir -p "$DEST_DIR/src/common"
|
||||
|
||||
cp -av "$SOURCE_DIR/game-activity/prefab-src" "$DEST_DIR/game-activity"
|
||||
cp -av "$SOURCE_DIR/game-text-input/prefab-src" "$DEST_DIR/game-text-input"
|
||||
cp -av "$SOURCE_DIR/include/common/gamesdk_common.h" "$DEST_DIR/include/common"
|
||||
cp -av "$SOURCE_DIR/src/common/system_utils.h" "$DEST_DIR/src/common"
|
||||
cp -av "$SOURCE_DIR/src/common/system_utils.cpp" "$DEST_DIR/src/common"
|
||||
|
||||
# Remove symlinks so the android-activity crate is easily buildable
|
||||
# from Git on Windows.
|
||||
|
||||
rm "$DEST_DIR/game-activity/prefab-src/modules/game-activity/include/common"
|
||||
rm -fr "$DEST_DIR/game-activity/prefab-src/modules/game-activity/include/game-text-input"
|
||||
rm -fr "$DEST_DIR/game-activity/prefab-src/modules/game-activity/src/common"
|
||||
rm -fr "$DEST_DIR/game-activity/prefab-src/modules/game-activity/src/game-text-input"
|
||||
|
||||
rm "$DEST_DIR/game-text-input/prefab-src/modules/game-text-input/include/common"
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright 2020 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/*
|
||||
* This is the main interface to the Android Performance Tuner library, also
|
||||
* known as Tuning Fork.
|
||||
*
|
||||
* It is part of the Android Games SDK and produces best results when integrated
|
||||
* with the Swappy Frame Pacing Library.
|
||||
*
|
||||
* See the documentation at
|
||||
* https://developer.android.com/games/sdk/performance-tuner/custom-engine for
|
||||
* more information on using this library in a native Android game.
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
// There are separate versions for each GameSDK component that use this format:
|
||||
#define ANDROID_GAMESDK_PACKED_VERSION(MAJOR, MINOR, BUGFIX) \
|
||||
((MAJOR << 16) | (MINOR << 8) | (BUGFIX))
|
||||
// Accessors
|
||||
#define ANDROID_GAMESDK_MAJOR_VERSION(PACKED) ((PACKED) >> 16)
|
||||
#define ANDROID_GAMESDK_MINOR_VERSION(PACKED) (((PACKED) >> 8) & 0xff)
|
||||
#define ANDROID_GAMESDK_BUGFIX_VERSION(PACKED) ((PACKED) & 0xff)
|
||||
|
||||
#define AGDK_STRINGIFY(NUMBER) #NUMBER
|
||||
#define AGDK_STRING_VERSION(MAJOR, MINOR, BUGFIX) \
|
||||
AGDK_STRINGIFY(MAJOR) "." AGDK_STRINGIFY(MINOR) "." AGDK_STRINGIFY(BUGFIX)
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright 2021 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "system_utils.h"
|
||||
|
||||
#include <android/api-level.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/system_properties.h>
|
||||
|
||||
namespace gamesdk {
|
||||
|
||||
#if __ANDROID_API__ >= 26
|
||||
std::string getSystemPropViaCallback(const char* key, const char* default_value = "") {
|
||||
const prop_info* prop = __system_property_find(key);
|
||||
if (prop == nullptr) {
|
||||
return default_value;
|
||||
}
|
||||
std::string return_value;
|
||||
auto thunk = [](void* cookie, const char* /*name*/, const char* value, uint32_t /*serial*/) {
|
||||
if (value != nullptr) {
|
||||
std::string* r = static_cast<std::string*>(cookie);
|
||||
*r = value;
|
||||
}
|
||||
};
|
||||
__system_property_read_callback(prop, thunk, &return_value);
|
||||
return return_value;
|
||||
}
|
||||
#else
|
||||
std::string getSystemPropViaGet(const char* key, const char* default_value = "") {
|
||||
char buffer[PROP_VALUE_MAX + 1] = ""; // +1 for terminator
|
||||
int bufferLen = __system_property_get(key, buffer);
|
||||
if (bufferLen > 0)
|
||||
return buffer;
|
||||
else
|
||||
return "";
|
||||
}
|
||||
#endif
|
||||
|
||||
std::string GetSystemProp(const char* key, const char* default_value) {
|
||||
#if __ANDROID_API__ >= 26
|
||||
return getSystemPropViaCallback(key, default_value);
|
||||
#else
|
||||
return getSystemPropViaGet(key, default_value);
|
||||
#endif
|
||||
}
|
||||
|
||||
int GetSystemPropAsInt(const char* key, int default_value) {
|
||||
std::string prop = GetSystemProp(key);
|
||||
return prop == "" ? default_value : strtoll(prop.c_str(), nullptr, 10);
|
||||
}
|
||||
|
||||
bool GetSystemPropAsBool(const char* key, bool default_value) {
|
||||
return GetSystemPropAsInt(key, default_value) != 0;
|
||||
}
|
||||
|
||||
} // namespace gamesdk
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright 2021 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "string"
|
||||
|
||||
namespace gamesdk {
|
||||
|
||||
// Get the value of the given system property
|
||||
std::string GetSystemProp(const char* key, const char* default_value = "");
|
||||
|
||||
// Get the value of the given system property as an integer
|
||||
int GetSystemPropAsInt(const char* key, int default_value = 0);
|
||||
|
||||
// Get the value of the given system property as a bool
|
||||
bool GetSystemPropAsBool(const char* key, bool default_value = false);
|
||||
|
||||
} // namespace gamesdk
|
||||
@@ -1,31 +1,90 @@
|
||||
#![allow(dead_code)]
|
||||
|
||||
fn build_glue_for_native_activity() {
|
||||
cc::Build::new()
|
||||
.include("native-activity-csrc")
|
||||
.include("native-activity-csrc/native-activity/native_app_glue")
|
||||
.file("native-activity-csrc/native-activity/native_app_glue/android_native_app_glue.c")
|
||||
.compile("libnative_app_glue.a");
|
||||
}
|
||||
|
||||
fn build_glue_for_game_activity() {
|
||||
let android_games_sdk =
|
||||
std::env::var("ANDROID_GAMES_SDK").unwrap_or_else(|_err| "android-games-sdk".to_string());
|
||||
|
||||
let activity_path = |src_inc, name| {
|
||||
format!("{android_games_sdk}/game-activity/prefab-src/modules/game-activity/{src_inc}/game-activity/{name}")
|
||||
};
|
||||
let textinput_path = |src_inc, name| {
|
||||
format!("{android_games_sdk}/game-text-input/prefab-src/modules/game-text-input/{src_inc}/game-text-input/{name}")
|
||||
};
|
||||
|
||||
for f in [
|
||||
"GameActivity.cpp",
|
||||
"GameActivityEvents.cpp",
|
||||
"GameActivityEvents_internal.h",
|
||||
] {
|
||||
println!("cargo:rerun-if-changed={}", activity_path("src", f));
|
||||
}
|
||||
|
||||
for f in [
|
||||
"GameActivity.h",
|
||||
"GameActivityEvents.h",
|
||||
"GameActivityLog.h",
|
||||
] {
|
||||
println!("cargo:rerun-if-changed={}", activity_path("include", f));
|
||||
}
|
||||
|
||||
cc::Build::new()
|
||||
.cpp(true)
|
||||
.include("game-activity-csrc")
|
||||
.file("game-activity-csrc/game-activity/GameActivity.cpp")
|
||||
.include("android-games-sdk/src/common")
|
||||
.file("android-games-sdk/src/common/system_utils.cpp")
|
||||
.extra_warnings(false)
|
||||
.cpp_link_stdlib("c++_static")
|
||||
.compile("libgame_common.a");
|
||||
|
||||
println!("cargo:rerun-if-changed=android-games-sdk/src/common/system_utils.cpp");
|
||||
println!("cargo:rerun-if-changed=android-games-sdk/src/common/system_utils.h");
|
||||
|
||||
cc::Build::new()
|
||||
.cpp(true)
|
||||
.include("android-games-sdk/src/common")
|
||||
.include("android-games-sdk/include")
|
||||
.include("android-games-sdk/game-activity/prefab-src/modules/game-activity/include")
|
||||
.include("android-games-sdk/game-text-input/prefab-src/modules/game-text-input/include")
|
||||
.file(activity_path("src", "GameActivity.cpp"))
|
||||
.file(activity_path("src", "GameActivityEvents.cpp"))
|
||||
.extra_warnings(false)
|
||||
.cpp_link_stdlib("c++_static")
|
||||
.compile("libgame_activity.a");
|
||||
|
||||
println!(
|
||||
"cargo:rerun-if-changed={}",
|
||||
textinput_path("include", "gametextinput.h")
|
||||
);
|
||||
println!(
|
||||
"cargo:rerun-if-changed={}",
|
||||
textinput_path("src", "gametextinput.cpp")
|
||||
);
|
||||
|
||||
cc::Build::new()
|
||||
.cpp(true)
|
||||
.include("game-activity-csrc")
|
||||
.file("game-activity-csrc/game-text-input/gametextinput.cpp")
|
||||
.include("android-games-sdk/src/common")
|
||||
.include("android-games-sdk/include")
|
||||
.include("android-games-sdk/game-text-input/prefab-src/modules/game-text-input/include")
|
||||
.file(textinput_path("src", "gametextinput.cpp"))
|
||||
.cpp_link_stdlib("c++_static")
|
||||
.compile("libgame_text_input.a");
|
||||
|
||||
println!(
|
||||
"cargo:rerun-if-changed={}",
|
||||
activity_path("src", "native_app_glue/android_native_app_glue.c")
|
||||
);
|
||||
println!(
|
||||
"cargo:rerun-if-changed={}",
|
||||
activity_path("include", "native_app_glue/android_native_app_glue.h")
|
||||
);
|
||||
|
||||
cc::Build::new()
|
||||
.include("game-activity-csrc")
|
||||
.include("game-activity-csrc/game-activity/native_app_glue")
|
||||
.file("game-activity-csrc/game-activity/native_app_glue/android_native_app_glue.c")
|
||||
.include("android-games-sdk/src/common")
|
||||
.include("android-games-sdk/include")
|
||||
.include("android-games-sdk/game-activity/prefab-src/modules/game-activity/include")
|
||||
.include("android-games-sdk/game-text-input/prefab-src/modules/game-text-input/include")
|
||||
.include(activity_path("include", ""))
|
||||
.file(activity_path(
|
||||
"src",
|
||||
"native_app_glue/android_native_app_glue.c",
|
||||
))
|
||||
.extra_warnings(false)
|
||||
.cpp_link_stdlib("c++_static")
|
||||
.compile("libnative_app_glue.a");
|
||||
@@ -36,8 +95,21 @@ fn build_glue_for_game_activity() {
|
||||
}
|
||||
|
||||
fn main() {
|
||||
#[cfg(feature = "game-activity")]
|
||||
build_glue_for_game_activity();
|
||||
#[cfg(feature = "native-activity")]
|
||||
build_glue_for_native_activity();
|
||||
// Enable Cargo's change-detection to avoid re-running build script if
|
||||
// irrelvant parts changed. Using build.rs here is just a dummy used to
|
||||
// disable the default "rerun on every change" behaviour Cargo has.
|
||||
println!("cargo:rerun-if-changed=build.rs");
|
||||
|
||||
if cfg!(feature = "game-activity") {
|
||||
build_glue_for_game_activity();
|
||||
}
|
||||
|
||||
// Whether this is used directly in or as a dependency on docs.rs.
|
||||
//
|
||||
// `cfg(docsrs)` cannot be used, since it's only set for the crate being
|
||||
// built, and not for any dependent crates.
|
||||
println!("cargo:rustc-check-cfg=cfg(used_on_docsrs)");
|
||||
if std::env::var("DOCS_RS").is_ok() {
|
||||
println!("cargo:rustc-cfg=used_on_docsrs");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2021 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @defgroup game_common Game Common
|
||||
* Common structures and functions used within AGDK
|
||||
* @{
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
/**
|
||||
* The type of a component for which to retrieve insets. See
|
||||
* https://developer.android.com/reference/androidx/core/view/WindowInsetsCompat.Type
|
||||
*/
|
||||
typedef enum GameCommonInsetsType {
|
||||
GAMECOMMON_INSETS_TYPE_CAPTION_BAR = 0,
|
||||
GAMECOMMON_INSETS_TYPE_DISPLAY_CUTOUT,
|
||||
GAMECOMMON_INSETS_TYPE_IME,
|
||||
GAMECOMMON_INSETS_TYPE_MANDATORY_SYSTEM_GESTURES,
|
||||
GAMECOMMON_INSETS_TYPE_NAVIGATION_BARS,
|
||||
GAMECOMMON_INSETS_TYPE_STATUS_BARS,
|
||||
GAMECOMMON_INSETS_TYPE_SYSTEM_BARS,
|
||||
GAMECOMMON_INSETS_TYPE_SYSTEM_GESTURES,
|
||||
GAMECOMMON_INSETS_TYPE_TAPABLE_ELEMENT,
|
||||
GAMECOMMON_INSETS_TYPE_WATERFALL,
|
||||
GAMECOMMON_INSETS_TYPE_COUNT
|
||||
} GameCommonInsetsType;
|
||||
@@ -1,290 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2021 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @defgroup game_text_input Game Text Input
|
||||
* The interface to use GameTextInput.
|
||||
* @{
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <android/rect.h>
|
||||
#include <jni.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "gamecommon.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* This struct holds a span within a region of text from start (inclusive) to
|
||||
* end (exclusive). An empty span or cursor position is specified with
|
||||
* start==end. An undefined span is specified with start = end = SPAN_UNDEFINED.
|
||||
*/
|
||||
typedef struct GameTextInputSpan {
|
||||
/** The start of the region (inclusive). */
|
||||
int32_t start;
|
||||
/** The end of the region (exclusive). */
|
||||
int32_t end;
|
||||
} GameTextInputSpan;
|
||||
|
||||
/**
|
||||
* Values with special meaning in a GameTextInputSpan.
|
||||
*/
|
||||
enum GameTextInputSpanFlag { SPAN_UNDEFINED = -1 };
|
||||
|
||||
/**
|
||||
* This struct holds the state of an editable section of text.
|
||||
* The text can have a selection and a composing region defined on it.
|
||||
* A composing region is used by IMEs that allow input using multiple steps to
|
||||
* compose a glyph or word. Use functions GameTextInput_getState and
|
||||
* GameTextInput_setState to read and modify the state that an IME is editing.
|
||||
*/
|
||||
typedef struct GameTextInputState {
|
||||
/**
|
||||
* Text owned by the state, as a modified UTF-8 string. Null-terminated.
|
||||
* https://en.wikipedia.org/wiki/UTF-8#Modified_UTF-8
|
||||
*/
|
||||
const char *text_UTF8;
|
||||
/**
|
||||
* Length in bytes of text_UTF8, *not* including the null at end.
|
||||
*/
|
||||
int32_t text_length;
|
||||
/**
|
||||
* A selection defined on the text.
|
||||
*/
|
||||
GameTextInputSpan selection;
|
||||
/**
|
||||
* A composing region defined on the text.
|
||||
*/
|
||||
GameTextInputSpan composingRegion;
|
||||
} GameTextInputState;
|
||||
|
||||
/**
|
||||
* A callback called by GameTextInput_getState.
|
||||
* @param context User-defined context.
|
||||
* @param state State, owned by the library, that will be valid for the duration
|
||||
* of the callback.
|
||||
*/
|
||||
typedef void (*GameTextInputGetStateCallback)(
|
||||
void *context, const struct GameTextInputState *state);
|
||||
|
||||
/**
|
||||
* Opaque handle to the GameTextInput API.
|
||||
*/
|
||||
typedef struct GameTextInput GameTextInput;
|
||||
|
||||
/**
|
||||
* Initialize the GameTextInput library.
|
||||
* If called twice without GameTextInput_destroy being called, the same pointer
|
||||
* will be returned and a warning will be issued.
|
||||
* @param env A JNI env valid on the calling thread.
|
||||
* @param max_string_size The maximum length of a string that can be edited. If
|
||||
* zero, the maximum defaults to 65536 bytes. A buffer of this size is allocated
|
||||
* at initialization.
|
||||
* @return A handle to the library.
|
||||
*/
|
||||
GameTextInput *GameTextInput_init(JNIEnv *env, uint32_t max_string_size);
|
||||
|
||||
/**
|
||||
* When using GameTextInput, you need to create a gametextinput.InputConnection
|
||||
* on the Java side and pass it using this function to the library, unless using
|
||||
* GameActivity in which case this will be done for you. See the GameActivity
|
||||
* source code or GameTextInput samples for examples of usage.
|
||||
* @param input A valid GameTextInput library handle.
|
||||
* @param inputConnection A gametextinput.InputConnection object.
|
||||
*/
|
||||
void GameTextInput_setInputConnection(GameTextInput *input,
|
||||
jobject inputConnection);
|
||||
|
||||
/**
|
||||
* Unless using GameActivity, it is required to call this function from your
|
||||
* Java gametextinput.Listener.stateChanged method to convert eventState and
|
||||
* trigger any event callbacks. When using GameActivity, this does not need to
|
||||
* be called as event processing is handled by the Activity.
|
||||
* @param input A valid GameTextInput library handle.
|
||||
* @param eventState A Java gametextinput.State object.
|
||||
*/
|
||||
void GameTextInput_processEvent(GameTextInput *input, jobject eventState);
|
||||
|
||||
/**
|
||||
* Free any resources owned by the GameTextInput library.
|
||||
* Any subsequent calls to the library will fail until GameTextInput_init is
|
||||
* called again.
|
||||
* @param input A valid GameTextInput library handle.
|
||||
*/
|
||||
void GameTextInput_destroy(GameTextInput *input);
|
||||
|
||||
/**
|
||||
* Flags to be passed to GameTextInput_showIme.
|
||||
*/
|
||||
enum ShowImeFlags {
|
||||
SHOW_IME_UNDEFINED = 0, // Default value.
|
||||
SHOW_IMPLICIT =
|
||||
1, // Indicates that the user has forced the input method open so it
|
||||
// should not be closed until they explicitly do so.
|
||||
SHOW_FORCED = 2 // Indicates that this is an implicit request to show the
|
||||
// input window, not as the result of a direct request by
|
||||
// the user. The window may not be shown in this case.
|
||||
};
|
||||
|
||||
/**
|
||||
* Show the IME. Calls InputMethodManager.showSoftInput().
|
||||
* @param input A valid GameTextInput library handle.
|
||||
* @param flags Defined in ShowImeFlags above. For more information see:
|
||||
* https://developer.android.com/reference/android/view/inputmethod/InputMethodManager
|
||||
*/
|
||||
void GameTextInput_showIme(GameTextInput *input, uint32_t flags);
|
||||
|
||||
/**
|
||||
* Flags to be passed to GameTextInput_hideIme.
|
||||
*/
|
||||
enum HideImeFlags {
|
||||
HIDE_IME_UNDEFINED = 0, // Default value.
|
||||
HIDE_IMPLICIT_ONLY =
|
||||
1, // Indicates that the soft input window should only be hidden if it
|
||||
// was not explicitly shown by the user.
|
||||
HIDE_NOT_ALWAYS =
|
||||
2, // Indicates that the soft input window should normally be hidden,
|
||||
// unless it was originally shown with SHOW_FORCED.
|
||||
};
|
||||
|
||||
/**
|
||||
* Show the IME. Calls InputMethodManager.hideSoftInputFromWindow().
|
||||
* @param input A valid GameTextInput library handle.
|
||||
* @param flags Defined in HideImeFlags above. For more information see:
|
||||
* https://developer.android.com/reference/android/view/inputmethod/InputMethodManager
|
||||
*/
|
||||
void GameTextInput_hideIme(GameTextInput *input, uint32_t flags);
|
||||
|
||||
/**
|
||||
* Call a callback with the current GameTextInput state, which may have been
|
||||
* modified by changes in the IME and calls to GameTextInput_setState. We use a
|
||||
* callback rather than returning the state in order to simplify ownership of
|
||||
* text_UTF8 strings. These strings are only valid during the calling of the
|
||||
* callback.
|
||||
* @param input A valid GameTextInput library handle.
|
||||
* @param callback A function that will be called with valid state.
|
||||
* @param context Context used by the callback.
|
||||
*/
|
||||
void GameTextInput_getState(GameTextInput *input,
|
||||
GameTextInputGetStateCallback callback,
|
||||
void *context);
|
||||
|
||||
/**
|
||||
* Set the current GameTextInput state. This state is reflected to any active
|
||||
* IME.
|
||||
* @param input A valid GameTextInput library handle.
|
||||
* @param state The state to set. Ownership is maintained by the caller and must
|
||||
* remain valid for the duration of the call.
|
||||
*/
|
||||
void GameTextInput_setState(GameTextInput *input,
|
||||
const GameTextInputState *state);
|
||||
|
||||
/**
|
||||
* Type of the callback needed by GameTextInput_setEventCallback that will be
|
||||
* called every time the IME state changes.
|
||||
* @param context User-defined context set in GameTextInput_setEventCallback.
|
||||
* @param current_state Current IME state, owned by the library and valid during
|
||||
* the callback.
|
||||
*/
|
||||
typedef void (*GameTextInputEventCallback)(
|
||||
void *context, const GameTextInputState *current_state);
|
||||
|
||||
/**
|
||||
* Optionally set a callback to be called whenever the IME state changes.
|
||||
* Not necessary if you are using GameActivity, which handles these callbacks
|
||||
* for you.
|
||||
* @param input A valid GameTextInput library handle.
|
||||
* @param callback Called by the library when the IME state changes.
|
||||
* @param context Context passed as first argument to the callback.
|
||||
*/
|
||||
void GameTextInput_setEventCallback(GameTextInput *input,
|
||||
GameTextInputEventCallback callback,
|
||||
void *context);
|
||||
|
||||
/**
|
||||
* Type of the callback needed by GameTextInput_setImeInsetsCallback that will
|
||||
* be called every time the IME window insets change.
|
||||
* @param context User-defined context set in
|
||||
* GameTextInput_setImeWIndowInsetsCallback.
|
||||
* @param current_insets Current IME insets, owned by the library and valid
|
||||
* during the callback.
|
||||
*/
|
||||
typedef void (*GameTextInputImeInsetsCallback)(void *context,
|
||||
const ARect *current_insets);
|
||||
|
||||
/**
|
||||
* Optionally set a callback to be called whenever the IME insets change.
|
||||
* Not necessary if you are using GameActivity, which handles these callbacks
|
||||
* for you.
|
||||
* @param input A valid GameTextInput library handle.
|
||||
* @param callback Called by the library when the IME insets change.
|
||||
* @param context Context passed as first argument to the callback.
|
||||
*/
|
||||
void GameTextInput_setImeInsetsCallback(GameTextInput *input,
|
||||
GameTextInputImeInsetsCallback callback,
|
||||
void *context);
|
||||
|
||||
/**
|
||||
* Get the current window insets for the IME.
|
||||
* @param input A valid GameTextInput library handle.
|
||||
* @param insets Filled with the current insets by this function.
|
||||
*/
|
||||
void GameTextInput_getImeInsets(const GameTextInput *input, ARect *insets);
|
||||
|
||||
/**
|
||||
* Unless using GameActivity, it is required to call this function from your
|
||||
* Java gametextinput.Listener.onImeInsetsChanged method to
|
||||
* trigger any event callbacks. When using GameActivity, this does not need to
|
||||
* be called as insets processing is handled by the Activity.
|
||||
* @param input A valid GameTextInput library handle.
|
||||
* @param eventState A Java gametextinput.State object.
|
||||
*/
|
||||
void GameTextInput_processImeInsets(GameTextInput *input, const ARect *insets);
|
||||
|
||||
/**
|
||||
* Convert a GameTextInputState struct to a Java gametextinput.State object.
|
||||
* Don't forget to delete the returned Java local ref when you're done.
|
||||
* @param input A valid GameTextInput library handle.
|
||||
* @param state Input state to convert.
|
||||
* @return A Java object of class gametextinput.State. The caller is required to
|
||||
* delete this local reference.
|
||||
*/
|
||||
jobject GameTextInputState_toJava(const GameTextInput *input,
|
||||
const GameTextInputState *state);
|
||||
|
||||
/**
|
||||
* Convert from a Java gametextinput.State object into a C GameTextInputState
|
||||
* struct.
|
||||
* @param input A valid GameTextInput library handle.
|
||||
* @param state A Java gametextinput.State object.
|
||||
* @param callback A function called with the C struct, valid for the duration
|
||||
* of the call.
|
||||
* @param context Context passed to the callback.
|
||||
*/
|
||||
void GameTextInputState_fromJava(const GameTextInput *input, jobject state,
|
||||
GameTextInputGetStateCallback callback,
|
||||
void *context);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
/** @} */
|
||||
@@ -1,5 +1,8 @@
|
||||
#!/bin/sh
|
||||
|
||||
# First install bindgen-cli via `cargo install bindgen-cli`
|
||||
|
||||
SDK_DIR="${ANDROID_GAMES_SDK:-android-games-sdk}"
|
||||
if test -z "${ANDROID_NDK_ROOT}"; then
|
||||
export ANDROID_NDK_ROOT=${ANDROID_NDK_HOME}
|
||||
fi
|
||||
@@ -12,10 +15,15 @@ while read ARCH && read TARGET ; do
|
||||
|
||||
# --module-raw-line 'use '
|
||||
bindgen game-activity-ffi.h -o src/game_activity/ffi_$ARCH.rs \
|
||||
--rust-target '1.85.0' \
|
||||
--blocklist-item 'JNI\w+' \
|
||||
--blocklist-item 'C?_?JNIEnv' \
|
||||
--blocklist-item '_?JavaVM' \
|
||||
--blocklist-item '_?j\w+' \
|
||||
--blocklist-item 'size_t' \
|
||||
--blocklist-item 'pthread_\w*' \
|
||||
--blocklist-function 'pthread_\w' \
|
||||
--blocklist-item 'ARect' \
|
||||
--blocklist-item 'ALooper\w*' \
|
||||
--blocklist-function 'ALooper\w*' \
|
||||
--blocklist-item 'AAsset\w*' \
|
||||
@@ -30,32 +38,11 @@ while read ARCH && read TARGET ; do
|
||||
--blocklist-function 'GameActivity_onCreate_C' \
|
||||
--newtype-enum '\w+_(result|status)_t' \
|
||||
-- \
|
||||
-Igame-activity-csrc \
|
||||
"-I$SDK_DIR/game-activity/prefab-src/modules/game-activity/include" \
|
||||
"-I$SDK_DIR/game-text-input/prefab-src/modules/game-text-input/include" \
|
||||
"-I$SDK_DIR/include" \
|
||||
--sysroot="$SYSROOT" --target=$TARGET
|
||||
|
||||
bindgen native-activity-ffi.h -o src/native_activity/ffi_$ARCH.rs \
|
||||
--blocklist-item 'JNI\w+' \
|
||||
--blocklist-item 'C?_?JNIEnv' \
|
||||
--blocklist-item '_?JavaVM' \
|
||||
--blocklist-item '_?j\w+' \
|
||||
--blocklist-item 'ALooper\w*' \
|
||||
--blocklist-function 'ALooper\w*' \
|
||||
--blocklist-item 'AAsset\w*' \
|
||||
--blocklist-item 'AAssetManager\w*' \
|
||||
--blocklist-function 'AAssetManager\w*' \
|
||||
--blocklist-item 'ANativeWindow\w*' \
|
||||
--blocklist-function 'ANativeWindow\w*' \
|
||||
--blocklist-item 'AConfiguration\w*' \
|
||||
--blocklist-function 'AConfiguration\w*' \
|
||||
--blocklist-function 'android_main' \
|
||||
--blocklist-item 'AInputQueue\w*' \
|
||||
--blocklist-function 'AInputQueue\w*' \
|
||||
--blocklist-item 'GameActivity_onCreate' \
|
||||
--blocklist-function 'GameActivity_onCreate_C' \
|
||||
--newtype-enum '\w+_(result|status)_t' \
|
||||
-- \
|
||||
-Inative-activity-csrc \
|
||||
--sysroot="$SYSROOT" --target=$TARGET
|
||||
done << EOF
|
||||
arm
|
||||
arm-linux-androideabi
|
||||
|
||||
@@ -1,457 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2010 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <jni.h>
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/resource.h>
|
||||
|
||||
#include "android_native_app_glue.h"
|
||||
#include <android/log.h>
|
||||
|
||||
#define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, "threaded_app", __VA_ARGS__))
|
||||
#define LOGE(...) ((void)__android_log_print(ANDROID_LOG_ERROR, "threaded_app", __VA_ARGS__))
|
||||
|
||||
/* For debug builds, always enable the debug traces in this library */
|
||||
#ifndef NDEBUG
|
||||
# define LOGV(...) ((void)__android_log_print(ANDROID_LOG_VERBOSE, "threaded_app", __VA_ARGS__))
|
||||
#else
|
||||
# define LOGV(...) ((void)0)
|
||||
#endif
|
||||
|
||||
static void free_saved_state(struct android_app* android_app) {
|
||||
pthread_mutex_lock(&android_app->mutex);
|
||||
if (android_app->savedState != NULL) {
|
||||
free(android_app->savedState);
|
||||
android_app->savedState = NULL;
|
||||
android_app->savedStateSize = 0;
|
||||
}
|
||||
pthread_mutex_unlock(&android_app->mutex);
|
||||
}
|
||||
|
||||
int8_t android_app_read_cmd(struct android_app* android_app) {
|
||||
int8_t cmd;
|
||||
if (read(android_app->msgread, &cmd, sizeof(cmd)) == sizeof(cmd)) {
|
||||
switch (cmd) {
|
||||
case APP_CMD_SAVE_STATE:
|
||||
free_saved_state(android_app);
|
||||
break;
|
||||
}
|
||||
return cmd;
|
||||
} else {
|
||||
LOGE("No data on command pipe!");
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void print_cur_config(struct android_app* android_app) {
|
||||
char lang[2], country[2];
|
||||
AConfiguration_getLanguage(android_app->config, lang);
|
||||
AConfiguration_getCountry(android_app->config, country);
|
||||
|
||||
LOGV("Config: mcc=%d mnc=%d lang=%c%c cnt=%c%c orien=%d touch=%d dens=%d "
|
||||
"keys=%d nav=%d keysHid=%d navHid=%d sdk=%d size=%d long=%d "
|
||||
"modetype=%d modenight=%d",
|
||||
AConfiguration_getMcc(android_app->config),
|
||||
AConfiguration_getMnc(android_app->config),
|
||||
lang[0], lang[1], country[0], country[1],
|
||||
AConfiguration_getOrientation(android_app->config),
|
||||
AConfiguration_getTouchscreen(android_app->config),
|
||||
AConfiguration_getDensity(android_app->config),
|
||||
AConfiguration_getKeyboard(android_app->config),
|
||||
AConfiguration_getNavigation(android_app->config),
|
||||
AConfiguration_getKeysHidden(android_app->config),
|
||||
AConfiguration_getNavHidden(android_app->config),
|
||||
AConfiguration_getSdkVersion(android_app->config),
|
||||
AConfiguration_getScreenSize(android_app->config),
|
||||
AConfiguration_getScreenLong(android_app->config),
|
||||
AConfiguration_getUiModeType(android_app->config),
|
||||
AConfiguration_getUiModeNight(android_app->config));
|
||||
}
|
||||
|
||||
void android_app_attach_input_queue_looper(struct android_app* android_app) {
|
||||
if (android_app->inputQueue != NULL) {
|
||||
LOGV("Attaching input queue to looper");
|
||||
AInputQueue_attachLooper(android_app->inputQueue,
|
||||
android_app->looper, LOOPER_ID_INPUT, NULL,
|
||||
&android_app->inputPollSource);
|
||||
}
|
||||
}
|
||||
|
||||
void android_app_detach_input_queue_looper(struct android_app* android_app) {
|
||||
if (android_app->inputQueue != NULL) {
|
||||
LOGV("Detaching input queue from looper");
|
||||
AInputQueue_detachLooper(android_app->inputQueue);
|
||||
}
|
||||
}
|
||||
|
||||
void android_app_pre_exec_cmd(struct android_app* android_app, int8_t cmd) {
|
||||
switch (cmd) {
|
||||
case APP_CMD_INPUT_CHANGED:
|
||||
LOGV("APP_CMD_INPUT_CHANGED\n");
|
||||
pthread_mutex_lock(&android_app->mutex);
|
||||
if (android_app->inputQueue != NULL) {
|
||||
android_app_detach_input_queue_looper(android_app);
|
||||
}
|
||||
android_app->inputQueue = android_app->pendingInputQueue;
|
||||
if (android_app->inputQueue != NULL) {
|
||||
android_app_attach_input_queue_looper(android_app);
|
||||
}
|
||||
pthread_cond_broadcast(&android_app->cond);
|
||||
pthread_mutex_unlock(&android_app->mutex);
|
||||
break;
|
||||
|
||||
case APP_CMD_INIT_WINDOW:
|
||||
LOGV("APP_CMD_INIT_WINDOW\n");
|
||||
pthread_mutex_lock(&android_app->mutex);
|
||||
android_app->window = android_app->pendingWindow;
|
||||
pthread_cond_broadcast(&android_app->cond);
|
||||
pthread_mutex_unlock(&android_app->mutex);
|
||||
break;
|
||||
|
||||
case APP_CMD_TERM_WINDOW:
|
||||
LOGV("APP_CMD_TERM_WINDOW\n");
|
||||
pthread_cond_broadcast(&android_app->cond);
|
||||
break;
|
||||
|
||||
case APP_CMD_RESUME:
|
||||
case APP_CMD_START:
|
||||
case APP_CMD_PAUSE:
|
||||
case APP_CMD_STOP:
|
||||
LOGV("activityState=%d\n", cmd);
|
||||
pthread_mutex_lock(&android_app->mutex);
|
||||
android_app->activityState = cmd;
|
||||
pthread_cond_broadcast(&android_app->cond);
|
||||
pthread_mutex_unlock(&android_app->mutex);
|
||||
break;
|
||||
|
||||
case APP_CMD_CONFIG_CHANGED:
|
||||
LOGV("APP_CMD_CONFIG_CHANGED\n");
|
||||
AConfiguration_fromAssetManager(android_app->config,
|
||||
android_app->activity->assetManager);
|
||||
print_cur_config(android_app);
|
||||
break;
|
||||
|
||||
case APP_CMD_DESTROY:
|
||||
LOGV("APP_CMD_DESTROY\n");
|
||||
android_app->destroyRequested = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void android_app_post_exec_cmd(struct android_app* android_app, int8_t cmd) {
|
||||
switch (cmd) {
|
||||
case APP_CMD_TERM_WINDOW:
|
||||
LOGV("APP_CMD_TERM_WINDOW\n");
|
||||
pthread_mutex_lock(&android_app->mutex);
|
||||
android_app->window = NULL;
|
||||
pthread_cond_broadcast(&android_app->cond);
|
||||
pthread_mutex_unlock(&android_app->mutex);
|
||||
break;
|
||||
|
||||
case APP_CMD_SAVE_STATE:
|
||||
LOGV("APP_CMD_SAVE_STATE\n");
|
||||
pthread_mutex_lock(&android_app->mutex);
|
||||
android_app->stateSaved = 1;
|
||||
pthread_cond_broadcast(&android_app->cond);
|
||||
pthread_mutex_unlock(&android_app->mutex);
|
||||
break;
|
||||
|
||||
case APP_CMD_RESUME:
|
||||
free_saved_state(android_app);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void app_dummy() {
|
||||
|
||||
}
|
||||
|
||||
static void android_app_destroy(struct android_app* android_app) {
|
||||
LOGV("android_app_destroy!");
|
||||
free_saved_state(android_app);
|
||||
pthread_mutex_lock(&android_app->mutex);
|
||||
if (android_app->inputQueue != NULL) {
|
||||
AInputQueue_detachLooper(android_app->inputQueue);
|
||||
}
|
||||
AConfiguration_delete(android_app->config);
|
||||
android_app->destroyed = 1;
|
||||
pthread_cond_broadcast(&android_app->cond);
|
||||
pthread_mutex_unlock(&android_app->mutex);
|
||||
// Can't touch android_app object after this.
|
||||
}
|
||||
|
||||
/*
|
||||
static void process_input(struct android_app* app, __attribute__((unused)) struct android_poll_source* source) {
|
||||
AInputEvent* event = NULL;
|
||||
while (AInputQueue_getEvent(app->inputQueue, &event) >= 0) {
|
||||
LOGV("New input event: type=%d\n", AInputEvent_getType(event));
|
||||
if (AInputQueue_preDispatchEvent(app->inputQueue, event)) {
|
||||
continue;
|
||||
}
|
||||
int32_t handled = 0;
|
||||
if (app->onInputEvent != NULL) handled = app->onInputEvent(app, event);
|
||||
AInputQueue_finishEvent(app->inputQueue, event, handled);
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
static void process_cmd(struct android_app* app, __attribute__((unused)) struct android_poll_source* source) {
|
||||
int8_t cmd = android_app_read_cmd(app);
|
||||
android_app_pre_exec_cmd(app, cmd);
|
||||
if (app->onAppCmd != NULL) app->onAppCmd(app, cmd);
|
||||
android_app_post_exec_cmd(app, cmd);
|
||||
}
|
||||
|
||||
static void* android_app_entry(void* param) {
|
||||
struct android_app* android_app = (struct android_app*)param;
|
||||
|
||||
android_app->config = AConfiguration_new();
|
||||
AConfiguration_fromAssetManager(android_app->config, android_app->activity->assetManager);
|
||||
|
||||
print_cur_config(android_app);
|
||||
|
||||
android_app->cmdPollSource.id = LOOPER_ID_MAIN;
|
||||
android_app->cmdPollSource.app = android_app;
|
||||
android_app->cmdPollSource.process = process_cmd;
|
||||
//android_app->inputPollSource.id = LOOPER_ID_INPUT;
|
||||
//android_app->inputPollSource.app = android_app;
|
||||
//android_app->inputPollSource.process = process_input;
|
||||
|
||||
ALooper* looper = ALooper_prepare(ALOOPER_PREPARE_ALLOW_NON_CALLBACKS);
|
||||
ALooper_addFd(looper, android_app->msgread, LOOPER_ID_MAIN, ALOOPER_EVENT_INPUT, NULL,
|
||||
&android_app->cmdPollSource);
|
||||
android_app->looper = looper;
|
||||
|
||||
pthread_mutex_lock(&android_app->mutex);
|
||||
android_app->running = 1;
|
||||
pthread_cond_broadcast(&android_app->cond);
|
||||
pthread_mutex_unlock(&android_app->mutex);
|
||||
|
||||
_rust_glue_entry(android_app);
|
||||
|
||||
android_app_destroy(android_app);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
// Native activity interaction (called from main thread)
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
static struct android_app* android_app_create(ANativeActivity* activity,
|
||||
void* savedState, size_t savedStateSize) {
|
||||
struct android_app* android_app = (struct android_app*)malloc(sizeof(struct android_app));
|
||||
memset(android_app, 0, sizeof(struct android_app));
|
||||
android_app->activity = activity;
|
||||
|
||||
pthread_mutex_init(&android_app->mutex, NULL);
|
||||
pthread_cond_init(&android_app->cond, NULL);
|
||||
|
||||
if (savedState != NULL) {
|
||||
android_app->savedState = malloc(savedStateSize);
|
||||
android_app->savedStateSize = savedStateSize;
|
||||
memcpy(android_app->savedState, savedState, savedStateSize);
|
||||
}
|
||||
|
||||
int msgpipe[2];
|
||||
if (pipe(msgpipe)) {
|
||||
LOGE("could not create pipe: %s", strerror(errno));
|
||||
return NULL;
|
||||
}
|
||||
android_app->msgread = msgpipe[0];
|
||||
android_app->msgwrite = msgpipe[1];
|
||||
|
||||
pthread_attr_t attr;
|
||||
pthread_attr_init(&attr);
|
||||
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
|
||||
pthread_create(&android_app->thread, &attr, android_app_entry, android_app);
|
||||
|
||||
// Wait for thread to start.
|
||||
pthread_mutex_lock(&android_app->mutex);
|
||||
while (!android_app->running) {
|
||||
pthread_cond_wait(&android_app->cond, &android_app->mutex);
|
||||
}
|
||||
pthread_mutex_unlock(&android_app->mutex);
|
||||
|
||||
return android_app;
|
||||
}
|
||||
|
||||
static void android_app_write_cmd(struct android_app* android_app, int8_t cmd) {
|
||||
if (write(android_app->msgwrite, &cmd, sizeof(cmd)) != sizeof(cmd)) {
|
||||
LOGE("Failure writing android_app cmd: %s\n", strerror(errno));
|
||||
}
|
||||
}
|
||||
|
||||
static void android_app_set_input(struct android_app* android_app, AInputQueue* inputQueue) {
|
||||
pthread_mutex_lock(&android_app->mutex);
|
||||
android_app->pendingInputQueue = inputQueue;
|
||||
android_app_write_cmd(android_app, APP_CMD_INPUT_CHANGED);
|
||||
while (android_app->inputQueue != android_app->pendingInputQueue) {
|
||||
pthread_cond_wait(&android_app->cond, &android_app->mutex);
|
||||
}
|
||||
pthread_mutex_unlock(&android_app->mutex);
|
||||
}
|
||||
|
||||
static void android_app_set_window(struct android_app* android_app, ANativeWindow* window) {
|
||||
pthread_mutex_lock(&android_app->mutex);
|
||||
if (android_app->pendingWindow != NULL) {
|
||||
android_app_write_cmd(android_app, APP_CMD_TERM_WINDOW);
|
||||
}
|
||||
android_app->pendingWindow = window;
|
||||
if (window != NULL) {
|
||||
android_app_write_cmd(android_app, APP_CMD_INIT_WINDOW);
|
||||
}
|
||||
while (android_app->window != android_app->pendingWindow) {
|
||||
pthread_cond_wait(&android_app->cond, &android_app->mutex);
|
||||
}
|
||||
pthread_mutex_unlock(&android_app->mutex);
|
||||
}
|
||||
|
||||
static void android_app_set_activity_state(struct android_app* android_app, int8_t cmd) {
|
||||
pthread_mutex_lock(&android_app->mutex);
|
||||
android_app_write_cmd(android_app, cmd);
|
||||
while (android_app->activityState != cmd) {
|
||||
pthread_cond_wait(&android_app->cond, &android_app->mutex);
|
||||
}
|
||||
pthread_mutex_unlock(&android_app->mutex);
|
||||
}
|
||||
|
||||
static void android_app_free(struct android_app* android_app) {
|
||||
pthread_mutex_lock(&android_app->mutex);
|
||||
android_app_write_cmd(android_app, APP_CMD_DESTROY);
|
||||
while (!android_app->destroyed) {
|
||||
pthread_cond_wait(&android_app->cond, &android_app->mutex);
|
||||
}
|
||||
pthread_mutex_unlock(&android_app->mutex);
|
||||
|
||||
close(android_app->msgread);
|
||||
close(android_app->msgwrite);
|
||||
pthread_cond_destroy(&android_app->cond);
|
||||
pthread_mutex_destroy(&android_app->mutex);
|
||||
free(android_app);
|
||||
}
|
||||
|
||||
static void onDestroy(ANativeActivity* activity) {
|
||||
LOGV("Destroy: %p\n", activity);
|
||||
android_app_free((struct android_app*)activity->instance);
|
||||
}
|
||||
|
||||
static void onStart(ANativeActivity* activity) {
|
||||
LOGV("Start: %p\n", activity);
|
||||
android_app_set_activity_state((struct android_app*)activity->instance, APP_CMD_START);
|
||||
}
|
||||
|
||||
static void onResume(ANativeActivity* activity) {
|
||||
LOGV("Resume: %p\n", activity);
|
||||
android_app_set_activity_state((struct android_app*)activity->instance, APP_CMD_RESUME);
|
||||
}
|
||||
|
||||
static void* onSaveInstanceState(ANativeActivity* activity, size_t* outLen) {
|
||||
struct android_app* android_app = (struct android_app*)activity->instance;
|
||||
void* savedState = NULL;
|
||||
|
||||
LOGV("SaveInstanceState: %p\n", activity);
|
||||
pthread_mutex_lock(&android_app->mutex);
|
||||
android_app->stateSaved = 0;
|
||||
android_app_write_cmd(android_app, APP_CMD_SAVE_STATE);
|
||||
while (!android_app->stateSaved) {
|
||||
pthread_cond_wait(&android_app->cond, &android_app->mutex);
|
||||
}
|
||||
|
||||
if (android_app->savedState != NULL) {
|
||||
savedState = android_app->savedState;
|
||||
*outLen = android_app->savedStateSize;
|
||||
android_app->savedState = NULL;
|
||||
android_app->savedStateSize = 0;
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&android_app->mutex);
|
||||
|
||||
return savedState;
|
||||
}
|
||||
|
||||
static void onPause(ANativeActivity* activity) {
|
||||
LOGV("Pause: %p\n", activity);
|
||||
android_app_set_activity_state((struct android_app*)activity->instance, APP_CMD_PAUSE);
|
||||
}
|
||||
|
||||
static void onStop(ANativeActivity* activity) {
|
||||
LOGV("Stop: %p\n", activity);
|
||||
android_app_set_activity_state((struct android_app*)activity->instance, APP_CMD_STOP);
|
||||
}
|
||||
|
||||
static void onConfigurationChanged(ANativeActivity* activity) {
|
||||
struct android_app* android_app = (struct android_app*)activity->instance;
|
||||
LOGV("ConfigurationChanged: %p\n", activity);
|
||||
android_app_write_cmd(android_app, APP_CMD_CONFIG_CHANGED);
|
||||
}
|
||||
|
||||
static void onLowMemory(ANativeActivity* activity) {
|
||||
struct android_app* android_app = (struct android_app*)activity->instance;
|
||||
LOGV("LowMemory: %p\n", activity);
|
||||
android_app_write_cmd(android_app, APP_CMD_LOW_MEMORY);
|
||||
}
|
||||
|
||||
static void onWindowFocusChanged(ANativeActivity* activity, int focused) {
|
||||
LOGV("WindowFocusChanged: %p -- %d\n", activity, focused);
|
||||
android_app_write_cmd((struct android_app*)activity->instance,
|
||||
focused ? APP_CMD_GAINED_FOCUS : APP_CMD_LOST_FOCUS);
|
||||
}
|
||||
|
||||
static void onNativeWindowCreated(ANativeActivity* activity, ANativeWindow* window) {
|
||||
LOGV("NativeWindowCreated: %p -- %p\n", activity, window);
|
||||
android_app_set_window((struct android_app*)activity->instance, window);
|
||||
}
|
||||
|
||||
static void onNativeWindowDestroyed(ANativeActivity* activity, ANativeWindow* window) {
|
||||
LOGV("NativeWindowDestroyed: %p -- %p\n", activity, window);
|
||||
android_app_set_window((struct android_app*)activity->instance, NULL);
|
||||
}
|
||||
|
||||
static void onInputQueueCreated(ANativeActivity* activity, AInputQueue* queue) {
|
||||
LOGV("InputQueueCreated: %p -- %p\n", activity, queue);
|
||||
android_app_set_input((struct android_app*)activity->instance, queue);
|
||||
}
|
||||
|
||||
static void onInputQueueDestroyed(ANativeActivity* activity, AInputQueue* queue) {
|
||||
LOGV("InputQueueDestroyed: %p -- %p\n", activity, queue);
|
||||
android_app_set_input((struct android_app*)activity->instance, NULL);
|
||||
}
|
||||
|
||||
JNIEXPORT
|
||||
void ANativeActivity_onCreate_C(ANativeActivity* activity, void* savedState,
|
||||
size_t savedStateSize) {
|
||||
LOGV("Creating: %p\n", activity);
|
||||
activity->callbacks->onDestroy = onDestroy;
|
||||
activity->callbacks->onStart = onStart;
|
||||
activity->callbacks->onResume = onResume;
|
||||
activity->callbacks->onSaveInstanceState = onSaveInstanceState;
|
||||
activity->callbacks->onPause = onPause;
|
||||
activity->callbacks->onStop = onStop;
|
||||
activity->callbacks->onConfigurationChanged = onConfigurationChanged;
|
||||
activity->callbacks->onLowMemory = onLowMemory;
|
||||
activity->callbacks->onWindowFocusChanged = onWindowFocusChanged;
|
||||
activity->callbacks->onNativeWindowCreated = onNativeWindowCreated;
|
||||
activity->callbacks->onNativeWindowDestroyed = onNativeWindowDestroyed;
|
||||
activity->callbacks->onInputQueueCreated = onInputQueueCreated;
|
||||
activity->callbacks->onInputQueueDestroyed = onInputQueueDestroyed;
|
||||
|
||||
activity->instance = android_app_create(activity, savedState, savedStateSize);
|
||||
}
|
||||
@@ -1,357 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2010 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _ANDROID_NATIVE_APP_GLUE_H
|
||||
#define _ANDROID_NATIVE_APP_GLUE_H
|
||||
|
||||
#include <poll.h>
|
||||
#include <pthread.h>
|
||||
#include <sched.h>
|
||||
|
||||
#include <android/configuration.h>
|
||||
#include <android/looper.h>
|
||||
#include <android/native_activity.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* The native activity interface provided by <android/native_activity.h>
|
||||
* is based on a set of application-provided callbacks that will be called
|
||||
* by the Activity's main thread when certain events occur.
|
||||
*
|
||||
* This means that each one of this callbacks _should_ _not_ block, or they
|
||||
* risk having the system force-close the application. This programming
|
||||
* model is direct, lightweight, but constraining.
|
||||
*
|
||||
* The 'android_native_app_glue' static library is used to provide a different
|
||||
* execution model where the application can implement its own main event
|
||||
* loop in a different thread instead. Here's how it works:
|
||||
*
|
||||
* 1/ The application must provide a function named "android_main()" that
|
||||
* will be called when the activity is created, in a new thread that is
|
||||
* distinct from the activity's main thread.
|
||||
*
|
||||
* 2/ android_main() receives a pointer to a valid "android_app" structure
|
||||
* that contains references to other important objects, e.g. the
|
||||
* ANativeActivity obejct instance the application is running in.
|
||||
*
|
||||
* 3/ the "android_app" object holds an ALooper instance that already
|
||||
* listens to two important things:
|
||||
*
|
||||
* - activity lifecycle events (e.g. "pause", "resume"). See APP_CMD_XXX
|
||||
* declarations below.
|
||||
*
|
||||
* - input events coming from the AInputQueue attached to the activity.
|
||||
*
|
||||
* Each of these correspond to an ALooper identifier returned by
|
||||
* ALooper_pollOnce with values of LOOPER_ID_MAIN and LOOPER_ID_INPUT,
|
||||
* respectively.
|
||||
*
|
||||
* Your application can use the same ALooper to listen to additional
|
||||
* file-descriptors. They can either be callback based, or with return
|
||||
* identifiers starting with LOOPER_ID_USER.
|
||||
*
|
||||
* 4/ Whenever you receive a LOOPER_ID_MAIN or LOOPER_ID_INPUT event,
|
||||
* the returned data will point to an android_poll_source structure. You
|
||||
* can call the process() function on it, and fill in android_app->onAppCmd
|
||||
* and android_app->onInputEvent to be called for your own processing
|
||||
* of the event.
|
||||
*
|
||||
* Alternatively, you can call the low-level functions to read and process
|
||||
* the data directly... look at the process_cmd() and process_input()
|
||||
* implementations in the glue to see how to do this.
|
||||
*
|
||||
* See the sample named "native-activity" that comes with the NDK with a
|
||||
* full usage example. Also look at the JavaDoc of NativeActivity.
|
||||
*/
|
||||
|
||||
struct android_app;
|
||||
|
||||
/**
|
||||
* Data associated with an ALooper fd that will be returned as the "outData"
|
||||
* when that source has data ready.
|
||||
*/
|
||||
struct android_poll_source {
|
||||
// The identifier of this source. May be LOOPER_ID_MAIN or
|
||||
// LOOPER_ID_INPUT.
|
||||
int32_t id;
|
||||
|
||||
// The android_app this ident is associated with.
|
||||
struct android_app* app;
|
||||
|
||||
// Function to call to perform the standard processing of data from
|
||||
// this source.
|
||||
void (*process)(struct android_app* app, struct android_poll_source* source);
|
||||
};
|
||||
|
||||
/**
|
||||
* This is the interface for the standard glue code of a threaded
|
||||
* application. In this model, the application's code is running
|
||||
* in its own thread separate from the main thread of the process.
|
||||
* It is not required that this thread be associated with the Java
|
||||
* VM, although it will need to be in order to make JNI calls any
|
||||
* Java objects.
|
||||
*/
|
||||
struct android_app {
|
||||
// The application can place a pointer to its own state object
|
||||
// here if it likes.
|
||||
void* userData;
|
||||
|
||||
// Fill this in with the function to process main app commands (APP_CMD_*)
|
||||
void (*onAppCmd)(struct android_app* app, int32_t cmd);
|
||||
|
||||
// Fill this in with the function to process input events. At this point
|
||||
// the event has already been pre-dispatched, and it will be finished upon
|
||||
// return. Return 1 if you have handled the event, 0 for any default
|
||||
// dispatching.
|
||||
int32_t (*onInputEvent)(struct android_app* app, AInputEvent* event);
|
||||
|
||||
// The ANativeActivity object instance that this app is running in.
|
||||
ANativeActivity* activity;
|
||||
|
||||
// The current configuration the app is running in.
|
||||
AConfiguration* config;
|
||||
|
||||
// This is the last instance's saved state, as provided at creation time.
|
||||
// It is NULL if there was no state. You can use this as you need; the
|
||||
// memory will remain around until you call android_app_exec_cmd() for
|
||||
// APP_CMD_RESUME, at which point it will be freed and savedState set to NULL.
|
||||
// These variables should only be changed when processing a APP_CMD_SAVE_STATE,
|
||||
// at which point they will be initialized to NULL and you can malloc your
|
||||
// state and place the information here. In that case the memory will be
|
||||
// freed for you later.
|
||||
void* savedState;
|
||||
size_t savedStateSize;
|
||||
|
||||
// The ALooper associated with the app's thread.
|
||||
ALooper* looper;
|
||||
|
||||
// When non-NULL, this is the input queue from which the app will
|
||||
// receive user input events.
|
||||
AInputQueue* inputQueue;
|
||||
|
||||
// When non-NULL, this is the window surface that the app can draw in.
|
||||
ANativeWindow* window;
|
||||
|
||||
// Current content rectangle of the window; this is the area where the
|
||||
// window's content should be placed to be seen by the user.
|
||||
ARect contentRect;
|
||||
|
||||
// Current state of the app's activity. May be either APP_CMD_START,
|
||||
// APP_CMD_RESUME, APP_CMD_PAUSE, or APP_CMD_STOP; see below.
|
||||
int activityState;
|
||||
|
||||
// This is non-zero when the application's NativeActivity is being
|
||||
// destroyed and waiting for the app thread to complete.
|
||||
int destroyRequested;
|
||||
|
||||
// -------------------------------------------------
|
||||
// Below are "private" implementation of the glue code.
|
||||
|
||||
pthread_mutex_t mutex;
|
||||
pthread_cond_t cond;
|
||||
|
||||
int msgread;
|
||||
int msgwrite;
|
||||
|
||||
pthread_t thread;
|
||||
|
||||
struct android_poll_source cmdPollSource;
|
||||
struct android_poll_source inputPollSource;
|
||||
|
||||
int running;
|
||||
int stateSaved;
|
||||
int destroyed;
|
||||
int redrawNeeded;
|
||||
AInputQueue* pendingInputQueue;
|
||||
ANativeWindow* pendingWindow;
|
||||
ARect pendingContentRect;
|
||||
};
|
||||
|
||||
enum {
|
||||
/**
|
||||
* Looper data ID of commands coming from the app's main thread, which
|
||||
* is returned as an identifier from ALooper_pollOnce(). The data for this
|
||||
* identifier is a pointer to an android_poll_source structure.
|
||||
* These can be retrieved and processed with android_app_read_cmd()
|
||||
* and android_app_exec_cmd().
|
||||
*/
|
||||
LOOPER_ID_MAIN = 1,
|
||||
|
||||
/**
|
||||
* Looper data ID of events coming from the AInputQueue of the
|
||||
* application's window, which is returned as an identifier from
|
||||
* ALooper_pollOnce(). The data for this identifier is a pointer to an
|
||||
* android_poll_source structure. These can be read via the inputQueue
|
||||
* object of android_app.
|
||||
*/
|
||||
LOOPER_ID_INPUT = 2,
|
||||
|
||||
/**
|
||||
* Start of user-defined ALooper identifiers.
|
||||
*/
|
||||
LOOPER_ID_USER = 3,
|
||||
};
|
||||
|
||||
enum {
|
||||
/**
|
||||
* Command from main thread: the AInputQueue has changed. Upon processing
|
||||
* this command, android_app->inputQueue will be updated to the new queue
|
||||
* (or NULL).
|
||||
*/
|
||||
APP_CMD_INPUT_CHANGED,
|
||||
|
||||
/**
|
||||
* Command from main thread: a new ANativeWindow is ready for use. Upon
|
||||
* receiving this command, android_app->window will contain the new window
|
||||
* surface.
|
||||
*/
|
||||
APP_CMD_INIT_WINDOW,
|
||||
|
||||
/**
|
||||
* Command from main thread: the existing ANativeWindow needs to be
|
||||
* terminated. Upon receiving this command, android_app->window still
|
||||
* contains the existing window; after calling android_app_exec_cmd
|
||||
* it will be set to NULL.
|
||||
*/
|
||||
APP_CMD_TERM_WINDOW,
|
||||
|
||||
/**
|
||||
* Command from main thread: the current ANativeWindow has been resized.
|
||||
* Please redraw with its new size.
|
||||
*/
|
||||
APP_CMD_WINDOW_RESIZED,
|
||||
|
||||
/**
|
||||
* Command from main thread: the system needs that the current ANativeWindow
|
||||
* be redrawn. You should redraw the window before handing this to
|
||||
* android_app_exec_cmd() in order to avoid transient drawing glitches.
|
||||
*/
|
||||
APP_CMD_WINDOW_REDRAW_NEEDED,
|
||||
|
||||
/**
|
||||
* Command from main thread: the content area of the window has changed,
|
||||
* such as from the soft input window being shown or hidden. You can
|
||||
* find the new content rect in android_app::contentRect.
|
||||
*/
|
||||
APP_CMD_CONTENT_RECT_CHANGED,
|
||||
|
||||
/**
|
||||
* Command from main thread: the app's activity window has gained
|
||||
* input focus.
|
||||
*/
|
||||
APP_CMD_GAINED_FOCUS,
|
||||
|
||||
/**
|
||||
* Command from main thread: the app's activity window has lost
|
||||
* input focus.
|
||||
*/
|
||||
APP_CMD_LOST_FOCUS,
|
||||
|
||||
/**
|
||||
* Command from main thread: the current device configuration has changed.
|
||||
*/
|
||||
APP_CMD_CONFIG_CHANGED,
|
||||
|
||||
/**
|
||||
* Command from main thread: the system is running low on memory.
|
||||
* Try to reduce your memory use.
|
||||
*/
|
||||
APP_CMD_LOW_MEMORY,
|
||||
|
||||
/**
|
||||
* Command from main thread: the app's activity has been started.
|
||||
*/
|
||||
APP_CMD_START,
|
||||
|
||||
/**
|
||||
* Command from main thread: the app's activity has been resumed.
|
||||
*/
|
||||
APP_CMD_RESUME,
|
||||
|
||||
/**
|
||||
* Command from main thread: the app should generate a new saved state
|
||||
* for itself, to restore from later if needed. If you have saved state,
|
||||
* allocate it with malloc and place it in android_app.savedState with
|
||||
* the size in android_app.savedStateSize. The will be freed for you
|
||||
* later.
|
||||
*/
|
||||
APP_CMD_SAVE_STATE,
|
||||
|
||||
/**
|
||||
* Command from main thread: the app's activity has been paused.
|
||||
*/
|
||||
APP_CMD_PAUSE,
|
||||
|
||||
/**
|
||||
* Command from main thread: the app's activity has been stopped.
|
||||
*/
|
||||
APP_CMD_STOP,
|
||||
|
||||
/**
|
||||
* Command from main thread: the app's activity is being destroyed,
|
||||
* and waiting for the app thread to clean up and exit before proceeding.
|
||||
*/
|
||||
APP_CMD_DESTROY,
|
||||
};
|
||||
|
||||
/**
|
||||
* Call when ALooper_pollAll() returns LOOPER_ID_MAIN, reading the next
|
||||
* app command message.
|
||||
*/
|
||||
int8_t android_app_read_cmd(struct android_app* android_app);
|
||||
|
||||
/**
|
||||
* Call with the command returned by android_app_read_cmd() to do the
|
||||
* initial pre-processing of the given command. You can perform your own
|
||||
* actions for the command after calling this function.
|
||||
*/
|
||||
void android_app_pre_exec_cmd(struct android_app* android_app, int8_t cmd);
|
||||
|
||||
/**
|
||||
* Call with the command returned by android_app_read_cmd() to do the
|
||||
* final post-processing of the given command. You must have done your own
|
||||
* actions for the command before calling this function.
|
||||
*/
|
||||
void android_app_post_exec_cmd(struct android_app* android_app, int8_t cmd);
|
||||
|
||||
void android_app_attach_input_queue_looper(struct android_app* android_app);
|
||||
void android_app_detach_input_queue_looper(struct android_app* android_app);
|
||||
|
||||
/**
|
||||
* Dummy function that used to be used to prevent the linker from stripping app
|
||||
* glue code. No longer necessary, since __attribute__((visibility("default")))
|
||||
* does this for us.
|
||||
*/
|
||||
__attribute__((
|
||||
deprecated("Calls to app_dummy are no longer necessary. See "
|
||||
"https://github.com/android-ndk/ndk/issues/381."))) void
|
||||
app_dummy();
|
||||
|
||||
/**
|
||||
* This is the function that application code must implement, representing
|
||||
* the main entry to the app.
|
||||
*/
|
||||
extern void _rust_glue_entry(struct android_app* app);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* _ANDROID_NATIVE_APP_GLUE_H */
|
||||
@@ -6,6 +6,14 @@ use ndk::configuration::{
|
||||
ScreenSize, Touchscreen, UiModeNight, UiModeType,
|
||||
};
|
||||
|
||||
/// A runtime-replacable reference to [`ndk::configuration::Configuration`].
|
||||
///
|
||||
/// # Warning
|
||||
///
|
||||
/// The value held by this reference **will change** with every [`super::MainEvent::ConfigChanged`]
|
||||
/// event that is raised. You should **not** [`Clone`] this type to compare it against a
|
||||
/// "new" [`super::AndroidApp::config()`] when that event is raised, since both point to the same
|
||||
/// internal [`ndk::configuration::Configuration`] and will be identical.
|
||||
#[derive(Clone)]
|
||||
pub struct ConfigurationRef {
|
||||
config: Arc<RwLock<Configuration>>,
|
||||
@@ -21,8 +29,6 @@ impl PartialEq for ConfigurationRef {
|
||||
}
|
||||
}
|
||||
impl Eq for ConfigurationRef {}
|
||||
unsafe impl Send for ConfigurationRef {}
|
||||
unsafe impl Sync for ConfigurationRef {}
|
||||
|
||||
impl fmt::Debug for ConfigurationRef {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum AppError {
|
||||
#[error("Operation only supported from the android_main() thread: {0}")]
|
||||
NonMainThread(String),
|
||||
|
||||
#[error("Java VM or JNI error, including Java exceptions")]
|
||||
JavaError(String),
|
||||
|
||||
#[error("Input unavailable")]
|
||||
InputUnavailable,
|
||||
}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, AppError>;
|
||||
|
||||
// XXX: we don't want to expose jni-rs in the public API
|
||||
// so we have an internal error type that we can generally
|
||||
// use in the backends and then we can strip the error
|
||||
// in the frontend of the API.
|
||||
//
|
||||
// This way we avoid exposing a public trait implementation for
|
||||
// `From<jni::errors::Error>`
|
||||
#[derive(Error, Debug)]
|
||||
pub(crate) enum InternalAppError {
|
||||
#[error("A JNI error")]
|
||||
JniError(jni::errors::JniError),
|
||||
// For internal errors that don't lead to a Java exception but are
|
||||
// still JNI related.
|
||||
#[error("A bad argument was passed to a JNI method: {0}")]
|
||||
JniBadArgument(String),
|
||||
#[error("A Java VM error")]
|
||||
JvmError(jni::errors::Error),
|
||||
#[error("Input unavailable")]
|
||||
InputUnavailable,
|
||||
}
|
||||
|
||||
pub(crate) type InternalResult<T> = std::result::Result<T, InternalAppError>;
|
||||
|
||||
impl From<jni::errors::Error> for InternalAppError {
|
||||
fn from(value: jni::errors::Error) -> Self {
|
||||
InternalAppError::JvmError(value)
|
||||
}
|
||||
}
|
||||
impl From<jni::errors::JniError> for InternalAppError {
|
||||
fn from(value: jni::errors::JniError) -> Self {
|
||||
InternalAppError::JniError(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<InternalAppError> for AppError {
|
||||
fn from(value: InternalAppError) -> Self {
|
||||
match value {
|
||||
InternalAppError::JniError(err) => AppError::JavaError(err.to_string()),
|
||||
InternalAppError::JniBadArgument(msg) => AppError::JavaError(msg),
|
||||
InternalAppError::JvmError(err) => AppError::JavaError(err.to_string()),
|
||||
InternalAppError::InputUnavailable => AppError::InputUnavailable,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,22 +12,18 @@
|
||||
#![allow(deref_nullptr)]
|
||||
#![allow(dead_code)]
|
||||
|
||||
use jni_sys::*;
|
||||
use ndk_sys::AAssetManager;
|
||||
use ndk_sys::ANativeWindow;
|
||||
use ndk_sys::{AConfiguration, ALooper, ALooper_callbackFunc};
|
||||
use jni::sys::*;
|
||||
use libc::{pthread_cond_t, pthread_mutex_t, pthread_t};
|
||||
use ndk_sys::{AAssetManager, AConfiguration, ALooper, ALooper_callbackFunc, ANativeWindow, ARect};
|
||||
|
||||
#[cfg(all(
|
||||
any(target_os = "android", feature = "test"),
|
||||
any(target_arch = "arm", target_arch = "armv7")
|
||||
))]
|
||||
#[cfg(all(any(target_os = "android"), target_arch = "arm"))]
|
||||
include!("ffi_arm.rs");
|
||||
|
||||
#[cfg(all(any(target_os = "android", feature = "test"), target_arch = "aarch64"))]
|
||||
#[cfg(all(any(target_os = "android"), target_arch = "aarch64"))]
|
||||
include!("ffi_aarch64.rs");
|
||||
|
||||
#[cfg(all(any(target_os = "android", feature = "test"), target_arch = "x86"))]
|
||||
#[cfg(all(any(target_os = "android"), target_arch = "x86"))]
|
||||
include!("ffi_i686.rs");
|
||||
|
||||
#[cfg(all(any(target_os = "android", feature = "test"), target_arch = "x86_64"))]
|
||||
#[cfg(all(any(target_os = "android"), target_arch = "x86_64"))]
|
||||
include!("ffi_x86_64.rs");
|
||||
|
||||
@@ -0,0 +1,323 @@
|
||||
use jni::{
|
||||
jni_sig, jni_str,
|
||||
objects::{JObject, JString, JThread},
|
||||
vm::JavaVM,
|
||||
};
|
||||
use log::Level;
|
||||
use ndk::asset::AssetManager;
|
||||
use std::{
|
||||
ffi::{c_void, CStr, CString},
|
||||
fs::File,
|
||||
io::{BufRead as _, BufReader},
|
||||
os::fd::{FromRawFd as _, RawFd},
|
||||
sync::OnceLock,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
main_callbacks::MainCallbacks, util::android_log, OnCreateState, ANDROID_ACTIVITY_TAG,
|
||||
};
|
||||
|
||||
fn forward_stdio_to_logcat() -> std::thread::JoinHandle<std::io::Result<()>> {
|
||||
// XXX: make this stdout/stderr redirection an optional / opt-in feature?...
|
||||
|
||||
let file = unsafe {
|
||||
let mut logpipe: [RawFd; 2] = Default::default();
|
||||
libc::pipe2(logpipe.as_mut_ptr(), libc::O_CLOEXEC);
|
||||
libc::dup2(logpipe[1], libc::STDOUT_FILENO);
|
||||
libc::dup2(logpipe[1], libc::STDERR_FILENO);
|
||||
libc::close(logpipe[1]);
|
||||
|
||||
File::from_raw_fd(logpipe[0])
|
||||
};
|
||||
|
||||
std::thread::Builder::new()
|
||||
.name("stdio-to-logcat".to_string())
|
||||
.spawn(move || -> std::io::Result<()> {
|
||||
let tag = c"RustStdoutStderr";
|
||||
let mut reader = BufReader::new(file);
|
||||
let mut buffer = String::new();
|
||||
loop {
|
||||
buffer.clear();
|
||||
let len = match reader.read_line(&mut buffer) {
|
||||
Ok(len) => len,
|
||||
Err(e) => {
|
||||
android_log(
|
||||
Level::Error,
|
||||
ANDROID_ACTIVITY_TAG,
|
||||
&CString::new(format!(
|
||||
"Logcat forwarder failed to read stdin/stderr: {e:?}"
|
||||
))
|
||||
.unwrap(),
|
||||
);
|
||||
break Err(e);
|
||||
}
|
||||
};
|
||||
if len == 0 {
|
||||
break Ok(());
|
||||
} else if let Ok(msg) = CString::new(buffer.clone()) {
|
||||
android_log(Level::Info, tag, &msg);
|
||||
}
|
||||
}
|
||||
})
|
||||
.expect("Failed to start stdout/stderr to logcat forwarder thread")
|
||||
}
|
||||
|
||||
unsafe extern "C" fn _android_activity_anchor() {}
|
||||
|
||||
/// Get a handle to the shared library that we are linked into, so that we can
|
||||
/// look up symbols within it.
|
||||
fn dlopen_self() -> Result<*mut c_void, String> {
|
||||
unsafe {
|
||||
let mut info: libc::Dl_info = std::mem::zeroed();
|
||||
|
||||
// NB: `dladdr` does not update the `dlerror` state
|
||||
if libc::dladdr(
|
||||
_android_activity_anchor as *const () as *const c_void,
|
||||
&mut info,
|
||||
) == 0
|
||||
{
|
||||
return Err("dladdr failed".into());
|
||||
}
|
||||
if info.dli_fname.is_null() {
|
||||
return Err("dladdr returned null dli_fname".into());
|
||||
}
|
||||
|
||||
// Clear any existing error
|
||||
libc::dlerror();
|
||||
|
||||
let handle = libc::dlopen(info.dli_fname, libc::RTLD_NOW | libc::RTLD_NOLOAD);
|
||||
if handle.is_null() {
|
||||
let err = CStr::from_ptr(libc::dlerror())
|
||||
.to_string_lossy()
|
||||
.into_owned();
|
||||
let path = CStr::from_ptr(info.dli_fname)
|
||||
.to_string_lossy()
|
||||
.into_owned();
|
||||
return Err(format!("dlopen({path}) failed: {err}"));
|
||||
}
|
||||
|
||||
Ok(handle)
|
||||
}
|
||||
}
|
||||
|
||||
/// Look up a symbol within our own shared library
|
||||
///
|
||||
/// This can be used to look up optional application entry points, such as
|
||||
/// `android_on_create`
|
||||
///
|
||||
/// Returns `None` if the symbol is not found (which is not considered an error)
|
||||
fn lookup_self_symbol(symbol: &CStr) -> Option<*mut c_void> {
|
||||
unsafe {
|
||||
let handle = match dlopen_self() {
|
||||
Ok(h) => h,
|
||||
Err(err) => {
|
||||
let msg = format!(
|
||||
"Warning: failed to dlopen self, looking for symbol {}: {err}",
|
||||
symbol.to_string_lossy()
|
||||
);
|
||||
android_log(
|
||||
Level::Warn,
|
||||
ANDROID_ACTIVITY_TAG,
|
||||
&CString::new(msg).unwrap(),
|
||||
);
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
// Clear any existing error
|
||||
libc::dlerror();
|
||||
|
||||
let sym = libc::dlsym(handle, symbol.as_ptr());
|
||||
|
||||
// Close the handle to avoid leaking a reference count
|
||||
if libc::dlclose(handle) != 0 {
|
||||
let err = CStr::from_ptr(libc::dlerror())
|
||||
.to_string_lossy()
|
||||
.into_owned();
|
||||
let msg = format!("dlclose failed for self handle: {err}");
|
||||
android_log(
|
||||
Level::Warn,
|
||||
ANDROID_ACTIVITY_TAG,
|
||||
&CString::new(msg).unwrap(),
|
||||
);
|
||||
}
|
||||
|
||||
if sym.is_null() {
|
||||
None
|
||||
} else {
|
||||
Some(sym)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Attempt to call an optional "android_on_create" entry point within the
|
||||
/// application's shared library
|
||||
///
|
||||
/// Note: this function does not propagate any errors, while it's assumed that
|
||||
/// this is called within an `onCreate` native method.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// - This must be called from the Java main thread, while onCreate is running
|
||||
/// - The `jni_activity` pointer must be a valid JNI reference to the Java
|
||||
/// Activity instance being created
|
||||
///
|
||||
/// The safety here also depends on the application declaring an
|
||||
/// `android_on_create` function with the correct signature. (It's safe to not
|
||||
/// declare an `android_on_create` function at all, and the code will simply
|
||||
/// skip calling it)
|
||||
pub(crate) unsafe fn init_java_main_thread_on_create(
|
||||
jvm: JavaVM,
|
||||
jni_activity: *mut c_void,
|
||||
saved_state: &[u8],
|
||||
) {
|
||||
let _join_log_forwarder = forward_stdio_to_logcat();
|
||||
|
||||
let msg = CString::new(format!(
|
||||
"Creating: Activity = {:p}, saved state size = {}",
|
||||
jni_activity,
|
||||
saved_state.len()
|
||||
))
|
||||
.unwrap();
|
||||
android_log(Level::Info, ANDROID_ACTIVITY_TAG, &msg);
|
||||
|
||||
// SAFETY: It's the application's responsibility to declare any `android_on_create`
|
||||
// function with the correct signature and ABI.
|
||||
let android_on_create: extern "Rust" fn(state: &OnCreateState) = unsafe {
|
||||
let Some(symbol) = lookup_self_symbol(c"android_on_create") else {
|
||||
// android_on_create is optional, so simply return if not found
|
||||
return;
|
||||
};
|
||||
std::mem::transmute(symbol)
|
||||
};
|
||||
|
||||
let state = OnCreateState::new(jvm.clone(), jni_activity, saved_state);
|
||||
// Catch any exceptions from the callback and log them instead of allowing any
|
||||
// exception to propagate back to the Activity.
|
||||
let res = jvm.attach_current_thread(|_env| -> jni::errors::Result<()> {
|
||||
android_on_create(&state);
|
||||
Ok(())
|
||||
});
|
||||
if let Err(err) = res {
|
||||
let msg = CString::new(format!(
|
||||
"JNI error while running android_on_create: {:?}",
|
||||
err
|
||||
))
|
||||
.unwrap();
|
||||
android_log(Level::Error, ANDROID_ACTIVITY_TAG, &msg);
|
||||
}
|
||||
}
|
||||
|
||||
struct AppState {
|
||||
main_callbacks: MainCallbacks,
|
||||
app_asset_manager: AssetManager,
|
||||
}
|
||||
|
||||
static APP_ONCE: OnceLock<AppState> = OnceLock::new();
|
||||
|
||||
// Get the Application instance from the Activity
|
||||
fn get_application<'local, 'any>(
|
||||
env: &mut jni::Env<'local>,
|
||||
activity: &JObject<'any>,
|
||||
) -> jni::errors::Result<JObject<'local>> {
|
||||
let app = env
|
||||
.call_method(
|
||||
activity,
|
||||
jni_str!("getApplication"),
|
||||
jni_sig!(() -> android.app.Application),
|
||||
&[],
|
||||
)?
|
||||
.l()?;
|
||||
Ok(app)
|
||||
}
|
||||
|
||||
fn get_assets<'local, 'any>(
|
||||
env: &mut jni::Env<'local>,
|
||||
application: &JObject<'any>,
|
||||
) -> jni::errors::Result<JObject<'local>> {
|
||||
let assets_manager = env
|
||||
.call_method(
|
||||
application,
|
||||
jni_str!("getAssets"),
|
||||
jni_sig!(() -> android.content.res.AssetManager),
|
||||
&[],
|
||||
)?
|
||||
.l()?;
|
||||
Ok(assets_manager)
|
||||
}
|
||||
|
||||
fn try_init_current_thread(env: &mut jni::Env, activity: &JObject) -> jni::errors::Result<()> {
|
||||
let activity_class = env.get_object_class(activity)?;
|
||||
let class_loader = activity_class.get_class_loader(env)?;
|
||||
|
||||
let thread = JThread::current_thread(env)?;
|
||||
thread.set_context_class_loader(env, &class_loader)?;
|
||||
let thread_name = JString::from_jni_str(env, jni_str!("android_main"))?;
|
||||
thread.set_name(env, &thread_name)?;
|
||||
|
||||
// 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 = c"android_main";
|
||||
let _ = libc::pthread_setname_np(libc::pthread_self(), thread_name.as_ptr());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Name the Java Thread + native thread "android_main" and set the Java Thread context class loader
|
||||
/// so that jni code can more-easily find non-system Java classes.
|
||||
pub(crate) fn init_android_main_thread(
|
||||
vm: &JavaVM,
|
||||
jni_activity: &JObject,
|
||||
java_main_looper: &ndk::looper::ForeignLooper,
|
||||
) -> jni::errors::Result<(AssetManager, MainCallbacks)> {
|
||||
vm.with_local_frame(10, |env| -> jni::errors::Result<_> {
|
||||
let app_state = APP_ONCE.get_or_init(|| unsafe {
|
||||
let application =
|
||||
get_application(env, jni_activity).expect("Failed to get Application instance");
|
||||
let app_asset_manager =
|
||||
get_assets(env, &application).expect("Failed to get AssetManager");
|
||||
let app_global = env
|
||||
.new_global_ref(application)
|
||||
.expect("Failed to create global ref for Application");
|
||||
// Make sure we don't delete the global reference via Drop
|
||||
let app_global = app_global.into_raw();
|
||||
ndk_context::initialize_android_context(vm.get_raw().cast(), app_global.cast());
|
||||
|
||||
let asset_manager_global = env
|
||||
.new_global_ref(app_asset_manager)
|
||||
.expect("Failed to create global ref for AssetManager");
|
||||
// Make sure we don't delete the global reference via Drop because
|
||||
// the AAssetManager pointer will only be valid while we can
|
||||
// guarantee that the Java AssetManager is not garbage collected
|
||||
let asset_manager_global = asset_manager_global.into_raw();
|
||||
let asset_manager_ptr =
|
||||
ndk_sys::AAssetManager_fromJava(env.get_raw() as _, asset_manager_global as _);
|
||||
assert_ne!(
|
||||
asset_manager_ptr,
|
||||
std::ptr::null_mut(),
|
||||
"Failed to get Application AAssetManager"
|
||||
);
|
||||
let app_asset_manager =
|
||||
AssetManager::from_ptr(std::ptr::NonNull::new(asset_manager_ptr).unwrap());
|
||||
|
||||
let main_callbacks = MainCallbacks::new(java_main_looper);
|
||||
|
||||
AppState {
|
||||
main_callbacks,
|
||||
app_asset_manager,
|
||||
}
|
||||
});
|
||||
|
||||
if let Err(err) = try_init_current_thread(env, jni_activity) {
|
||||
let msg =
|
||||
CString::new(format!("Failed to initialize Java thread state: {:?}", err)).unwrap();
|
||||
android_log(Level::Error, ANDROID_ACTIVITY_TAG, &msg);
|
||||
}
|
||||
|
||||
let asset_manager = unsafe { AssetManager::from_ptr(app_state.app_asset_manager.ptr()) };
|
||||
let main_callbacks = app_state.main_callbacks.clone();
|
||||
|
||||
Ok((asset_manager, main_callbacks))
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,275 @@
|
||||
use jni::sys::jint;
|
||||
use jni::{objects::Global, JavaVM};
|
||||
|
||||
use crate::error::{AppError, InternalAppError, InternalResult};
|
||||
use crate::input::{Keycode, MetaState};
|
||||
use crate::jni_utils;
|
||||
|
||||
/// An enum representing the types of keyboards that may generate key events
|
||||
///
|
||||
/// See [getKeyboardType() docs](https://developer.android.com/reference/android/view/KeyCharacterMap#getKeyboardType())
|
||||
///
|
||||
/// # Android Extensible Enum
|
||||
///
|
||||
/// This is a runtime [extensible enum](`crate#android-extensible-enums`) and
|
||||
/// should be handled similar to a `#[non_exhaustive]` enum to maintain
|
||||
/// forwards compatibility.
|
||||
///
|
||||
/// This implements `Into<u32>` and `From<u32>` for converting to/from Android
|
||||
/// SDK integer values.
|
||||
#[derive(
|
||||
Debug, Clone, Copy, PartialEq, Eq, Hash, num_enum::FromPrimitive, num_enum::IntoPrimitive,
|
||||
)]
|
||||
#[non_exhaustive]
|
||||
#[repr(u32)]
|
||||
pub enum KeyboardType {
|
||||
/// A numeric (12-key) keyboard.
|
||||
///
|
||||
/// A numeric keyboard supports text entry using a multi-tap approach. It may be necessary to tap a key multiple times to generate the desired letter or symbol.
|
||||
///
|
||||
/// This type of keyboard is generally designed for thumb typing.
|
||||
Numeric,
|
||||
|
||||
/// A keyboard with all the letters, but with more than one letter per key.
|
||||
///
|
||||
/// This type of keyboard is generally designed for thumb typing.
|
||||
Predictive,
|
||||
|
||||
/// A keyboard with all the letters, and maybe some numbers.
|
||||
///
|
||||
/// An alphabetic keyboard supports text entry directly but may have a condensed layout with a small form factor. In contrast to a full keyboard, some symbols may only be accessible using special on-screen character pickers. In addition, to improve typing speed and accuracy, the framework provides special affordances for alphabetic keyboards such as auto-capitalization and toggled / locked shift and alt keys.
|
||||
///
|
||||
/// This type of keyboard is generally designed for thumb typing.
|
||||
Alpha,
|
||||
|
||||
/// A full PC-style keyboard.
|
||||
///
|
||||
/// A full keyboard behaves like a PC keyboard. All symbols are accessed directly by pressing keys on the keyboard without on-screen support or affordances such as auto-capitalization.
|
||||
///
|
||||
/// This type of keyboard is generally designed for full two hand typing.
|
||||
Full,
|
||||
|
||||
/// A keyboard that is only used to control special functions rather than for typing.
|
||||
///
|
||||
/// A special function keyboard consists only of non-printing keys such as HOME and POWER that are not actually used for typing.
|
||||
SpecialFunction,
|
||||
|
||||
#[doc(hidden)]
|
||||
#[num_enum(catch_all)]
|
||||
__Unknown(u32),
|
||||
}
|
||||
|
||||
/// Either represents, a unicode character or combining accent from a
|
||||
/// [`KeyCharacterMap`], or `None` for non-printable keys.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum KeyMapChar {
|
||||
None,
|
||||
Unicode(char),
|
||||
CombiningAccent(char),
|
||||
}
|
||||
|
||||
jni::bind_java_type! {
|
||||
pub(crate) AKeyCharacterMap => "android.view.KeyCharacterMap",
|
||||
methods {
|
||||
priv fn _get(key_code: jint, meta_state: jint) -> jint,
|
||||
priv static fn _get_dead_char(accent_char: jint, base_char: jint) -> jint,
|
||||
priv fn _get_keyboard_type() -> jint,
|
||||
}
|
||||
}
|
||||
|
||||
impl AKeyCharacterMap<'_> {
|
||||
pub(crate) fn get(
|
||||
&self,
|
||||
env: &mut jni::Env,
|
||||
key_code: jint,
|
||||
meta_state: jint,
|
||||
) -> Result<jint, InternalAppError> {
|
||||
self._get(env, key_code, meta_state)
|
||||
.map_err(|err| jni_utils::clear_and_map_exception_to_err(env, err))
|
||||
}
|
||||
|
||||
pub(crate) fn get_dead_char(
|
||||
env: &mut jni::Env,
|
||||
accent_char: jint,
|
||||
base_char: jint,
|
||||
) -> Result<jint, InternalAppError> {
|
||||
Self::_get_dead_char(env, accent_char, base_char)
|
||||
.map_err(|err| jni_utils::clear_and_map_exception_to_err(env, err))
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
||||
jni::bind_java_type! {
|
||||
pub(crate) AInputDevice => "android.view.InputDevice",
|
||||
type_map {
|
||||
AKeyCharacterMap => "android.view.KeyCharacterMap",
|
||||
},
|
||||
methods {
|
||||
static fn get_device(id: jint) -> AInputDevice,
|
||||
fn get_key_character_map() -> AKeyCharacterMap,
|
||||
}
|
||||
}
|
||||
|
||||
/// Describes the keys provided by a keyboard device and their associated labels.
|
||||
#[derive(Debug)]
|
||||
pub struct KeyCharacterMap {
|
||||
jvm: JavaVM,
|
||||
key_map: Global<AKeyCharacterMap<'static>>,
|
||||
}
|
||||
impl Clone for KeyCharacterMap {
|
||||
fn clone(&self) -> Self {
|
||||
let jvm = self.jvm.clone();
|
||||
jvm.attach_current_thread(|env| -> jni::errors::Result<_> {
|
||||
Ok(Self {
|
||||
jvm: jvm.clone(),
|
||||
key_map: env.new_global_ref(&self.key_map)?,
|
||||
})
|
||||
})
|
||||
.expect("Failed to attach thread to JVM and clone key map")
|
||||
}
|
||||
}
|
||||
|
||||
impl KeyCharacterMap {
|
||||
pub(crate) fn new(jvm: JavaVM, key_map: Global<AKeyCharacterMap<'static>>) -> Self {
|
||||
Self { jvm, key_map }
|
||||
}
|
||||
|
||||
/// Gets the Unicode character generated by the specified [`Keycode`] and [`MetaState`] combination.
|
||||
///
|
||||
/// Returns [`KeyMapChar::None`] if the key is not one that is used to type Unicode characters.
|
||||
///
|
||||
/// Returns [`KeyMapChar::CombiningAccent`] if the key is a "dead key" that should be combined with
|
||||
/// another to actually produce a character -- see [`KeyCharacterMap::get_dead_char`].
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Since this API needs to use JNI internally to call into the Android JVM it may return
|
||||
/// a [`AppError::JavaError`] in case there is a spurious JNI error or an exception
|
||||
/// is caught.
|
||||
pub fn get(&self, key_code: Keycode, meta_state: MetaState) -> Result<KeyMapChar, AppError> {
|
||||
let key_code: u32 = key_code.into();
|
||||
let key_code = key_code as jni::sys::jint;
|
||||
let meta_state: u32 = meta_state.0;
|
||||
let meta_state = meta_state as jni::sys::jint;
|
||||
|
||||
let vm = self.jvm.clone();
|
||||
vm.attach_current_thread(|env| -> InternalResult<_> {
|
||||
let unicode = self.key_map.get(env, key_code, meta_state)?;
|
||||
let unicode = unicode as u32;
|
||||
|
||||
const COMBINING_ACCENT: u32 = 0x80000000;
|
||||
const COMBINING_ACCENT_MASK: u32 = !COMBINING_ACCENT;
|
||||
|
||||
if unicode == 0 {
|
||||
Ok(KeyMapChar::None)
|
||||
} else if unicode & COMBINING_ACCENT == COMBINING_ACCENT {
|
||||
let accent = unicode & COMBINING_ACCENT_MASK;
|
||||
// Safety: assumes Android key maps don't contain invalid unicode characters
|
||||
Ok(KeyMapChar::CombiningAccent(unsafe {
|
||||
char::from_u32_unchecked(accent)
|
||||
}))
|
||||
} else {
|
||||
// Safety: assumes Android key maps don't contain invalid unicode characters
|
||||
Ok(KeyMapChar::Unicode(unsafe {
|
||||
char::from_u32_unchecked(unicode)
|
||||
}))
|
||||
}
|
||||
})
|
||||
.map_err(|err| {
|
||||
let err: InternalAppError = err;
|
||||
err.into()
|
||||
})
|
||||
}
|
||||
|
||||
/// Get the character that is produced by combining the dead key producing accent with the key producing character c.
|
||||
///
|
||||
/// For example, ``get_dead_char('`', 'e')`` returns `'è'`. `get_dead_char('^', ' ')` returns `'^'` and `get_dead_char('^', '^')` returns `'^'`.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Since this API needs to use JNI internally to call into the Android JVM it may return a
|
||||
/// [`AppError::JavaError`] in case there is a spurious JNI error or an exception is caught.
|
||||
pub fn get_dead_char(
|
||||
&self,
|
||||
accent_char: char,
|
||||
base_char: char,
|
||||
) -> Result<Option<char>, AppError> {
|
||||
let accent_char = accent_char as jni::sys::jint;
|
||||
let base_char = base_char as jni::sys::jint;
|
||||
|
||||
let vm = self.jvm.clone();
|
||||
vm.attach_current_thread(|env| -> InternalResult<_> {
|
||||
let unicode = AKeyCharacterMap::get_dead_char(env, accent_char, base_char)?;
|
||||
let unicode = unicode as u32;
|
||||
|
||||
// Safety: assumes Android key maps don't contain invalid unicode characters
|
||||
Ok(if unicode == 0 {
|
||||
None
|
||||
} else {
|
||||
Some(unsafe { char::from_u32_unchecked(unicode) })
|
||||
})
|
||||
})
|
||||
.map_err(|err| {
|
||||
let err: InternalAppError = err;
|
||||
err.into()
|
||||
})
|
||||
}
|
||||
|
||||
/// Gets the keyboard type.
|
||||
///
|
||||
/// Different keyboard types have different semantics. See [`KeyboardType`] for details.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Since this API needs to use JNI internally to call into the Android JVM it may return
|
||||
/// a [`AppError::JavaError`] in case there is a spurious JNI error or an exception
|
||||
/// is caught.
|
||||
pub fn get_keyboard_type(&self) -> Result<KeyboardType, AppError> {
|
||||
let vm = self.jvm.clone();
|
||||
vm.attach_current_thread(|env| -> InternalResult<_> {
|
||||
let keyboard_type = self.key_map.get_keyboard_type(env)?;
|
||||
let keyboard_type = keyboard_type as u32;
|
||||
Ok(keyboard_type.into())
|
||||
})
|
||||
.map_err(|err| {
|
||||
let err: InternalAppError = err;
|
||||
err.into()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn device_key_character_map_with_env(
|
||||
env: &mut jni::Env<'_>,
|
||||
device_id: i32,
|
||||
) -> jni::errors::Result<KeyCharacterMap> {
|
||||
let device = AInputDevice::get_device(env, device_id)?;
|
||||
if device.is_null() {
|
||||
// This isn't really an error from a JNI perspective but we would only expect
|
||||
// this to return null for a device ID of zero or an invalid device ID.
|
||||
log::error!("No input device with id {}", device_id);
|
||||
return Err(jni::errors::Error::WrongObjectType);
|
||||
}
|
||||
let character_map = device.get_key_character_map(env)?;
|
||||
let character_map = env.new_global_ref(character_map)?;
|
||||
let jvm = JavaVM::singleton().expect("Failed to get singleton JavaVM");
|
||||
Ok(KeyCharacterMap::new(jvm, character_map))
|
||||
}
|
||||
|
||||
pub(crate) fn device_key_character_map(
|
||||
jvm: JavaVM,
|
||||
device_id: i32,
|
||||
) -> InternalResult<KeyCharacterMap> {
|
||||
jvm.attach_current_thread(|env| {
|
||||
if device_id == 0 {
|
||||
return Err(InternalAppError::JniBadArgument(
|
||||
"Can't get key character map for non-physical device_id 0".into(),
|
||||
));
|
||||
}
|
||||
device_key_character_map_with_env(env, device_id)
|
||||
.map_err(|err| jni_utils::clear_and_map_exception_to_err(env, err))
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
//! The JNI calls we make in this crate are often not part of a Java native
|
||||
//! method implementation and so we can't assume we have a JNI local frame that
|
||||
//! is going to unwind and free local references, and we also can't just leave
|
||||
//! exceptions to get thrown when returning to Java.
|
||||
//!
|
||||
//! These utilities help us check + clear exceptions and map them into Rust Errors.
|
||||
|
||||
use crate::error::InternalAppError;
|
||||
|
||||
/// Use with `.map_err()` to map `jni::errors::Error::JavaException` into a
|
||||
/// richer error based on the actual contents of the `JThrowable`
|
||||
///
|
||||
/// (The `jni` crate doesn't do that automatically since it's more
|
||||
/// common to let the exception get thrown when returning to Java)
|
||||
///
|
||||
/// This will also clear the exception
|
||||
pub(crate) fn clear_and_map_exception_to_err(
|
||||
env: &mut jni::Env<'_>,
|
||||
err: jni::errors::Error,
|
||||
) -> InternalAppError {
|
||||
if matches!(err, jni::errors::Error::JavaException) {
|
||||
env.exception_catch()
|
||||
.expect_err("Spurious JavaException error with no exception to catch")
|
||||
} else {
|
||||
err
|
||||
}
|
||||
.into()
|
||||
}
|
||||
@@ -0,0 +1,226 @@
|
||||
use jni::vm::JavaVM;
|
||||
use std::{
|
||||
ffi::c_void,
|
||||
panic::{catch_unwind, AssertUnwindSafe},
|
||||
sync::{atomic::AtomicBool, Arc, Mutex, Weak},
|
||||
};
|
||||
|
||||
use crate::util::abort_on_panic;
|
||||
|
||||
struct CallbackBuffers {
|
||||
pub front: Vec<Box<dyn FnOnce() + Send>>,
|
||||
pub back: Vec<Box<dyn FnOnce() + Send>>,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for CallbackBuffers {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("CallbackBuffers")
|
||||
.field("front", &self.front.len())
|
||||
.field("back", &self.back.len())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl CallbackBuffers {
|
||||
pub fn take_front(&mut self) -> Vec<Box<dyn FnOnce() + Send>> {
|
||||
std::mem::swap(&mut self.front, &mut self.back);
|
||||
std::mem::take(&mut self.back)
|
||||
}
|
||||
|
||||
// After calling `take_front` and draining callbacks then the empty
|
||||
// vec should be put back so the capacity can be reused
|
||||
//
|
||||
// The given `back` vector must be empty
|
||||
pub fn replace_back(&mut self, back: Vec<Box<dyn FnOnce() + Send>>) {
|
||||
assert!(back.is_empty());
|
||||
self.back = back;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct MainCallbacksState {
|
||||
_pending_detach: AtomicBool,
|
||||
event_fd: libc::c_int,
|
||||
callbacks: Mutex<CallbackBuffers>,
|
||||
}
|
||||
|
||||
impl Drop for MainCallbacksState {
|
||||
fn drop(&mut self) {
|
||||
eprintln!("Dropping MainCallbacksState");
|
||||
log::warn!("Dropping MainCallbacksState");
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct MainCallbacks {
|
||||
inner: Arc<MainCallbacksState>,
|
||||
}
|
||||
|
||||
impl std::ops::Deref for MainCallbacks {
|
||||
type Target = MainCallbacksState;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl MainCallbacks {
|
||||
pub fn new(java_main_looper: &ndk::looper::ForeignLooper) -> Self {
|
||||
let java_main_callbacks_event_fd =
|
||||
unsafe { libc::eventfd(0, libc::EFD_NONBLOCK | libc::EFD_CLOEXEC) };
|
||||
assert_ne!(
|
||||
java_main_callbacks_event_fd, -1,
|
||||
"Failed to create Java main looper event fd"
|
||||
);
|
||||
|
||||
let inner = Arc::new(MainCallbacksState {
|
||||
_pending_detach: AtomicBool::new(false),
|
||||
event_fd: java_main_callbacks_event_fd,
|
||||
callbacks: Mutex::new(CallbackBuffers {
|
||||
front: Vec::new(),
|
||||
back: Vec::new(),
|
||||
}),
|
||||
});
|
||||
|
||||
let weak = Arc::downgrade(&inner);
|
||||
let weak = weak.into_raw();
|
||||
unsafe {
|
||||
ndk_sys::ALooper_addFd(
|
||||
java_main_looper.ptr().as_ptr(),
|
||||
java_main_callbacks_event_fd,
|
||||
ndk_sys::ALOOPER_POLL_CALLBACK,
|
||||
ndk_sys::ALOOPER_EVENT_INPUT as libc::c_int,
|
||||
Some(run_java_main_callbacks),
|
||||
weak as _,
|
||||
);
|
||||
}
|
||||
|
||||
Self { inner }
|
||||
}
|
||||
|
||||
pub fn wake_java_main_for_callbacks(&self) {
|
||||
let count: u64 = 1;
|
||||
|
||||
loop {
|
||||
match unsafe {
|
||||
libc::write(self.event_fd, &count as *const _ as *const libc::c_void, 8)
|
||||
} {
|
||||
8 => break,
|
||||
-1 => {
|
||||
let err = std::io::Error::last_os_error();
|
||||
if err.kind() != std::io::ErrorKind::Interrupted {
|
||||
log::error!("Failure waking up java main loop: {}", err);
|
||||
return;
|
||||
}
|
||||
}
|
||||
count => {
|
||||
log::error!("Spurious write of {count} bytes while waking up java main loop");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run_on_java_main_thread<F>(&self, f: Box<F>)
|
||||
where
|
||||
F: FnOnce() + Send + 'static,
|
||||
{
|
||||
{
|
||||
let mut guard = self.callbacks.lock().unwrap();
|
||||
guard.front.push(f);
|
||||
}
|
||||
|
||||
self.wake_java_main_for_callbacks();
|
||||
}
|
||||
|
||||
// Asynchronously detach the callbacks event fd from the Java main looper
|
||||
//
|
||||
// Note: we can't do this synchronously because ALooper_removeFd can't
|
||||
// guarantee that there isn't already a callback pending (which will still
|
||||
// require a valid data pointer)
|
||||
//
|
||||
// Since the java main Looper runs for the lifetime of the application
|
||||
// process we never actually expect to detach the callbacks event fd, and in
|
||||
// the unlikely case where there is no future callback after calling
|
||||
// `wake_java_main_for_callbacks` then the event fd and `MainCallbacks` will
|
||||
// be leaked - but the implication is that the process is about to terminate
|
||||
// (otherwise the Looper would still be running)
|
||||
pub fn _detach_callbacks_event_fd_from_java_main_looper(&mut self) {
|
||||
self._pending_detach
|
||||
.store(true, std::sync::atomic::Ordering::SeqCst);
|
||||
self.wake_java_main_for_callbacks();
|
||||
}
|
||||
}
|
||||
|
||||
unsafe extern "C" fn run_java_main_callbacks(fd: i32, events: i32, data: *mut c_void) -> i32 {
|
||||
abort_on_panic(|| {
|
||||
// Reset the eventfd counter
|
||||
if events & ndk_sys::ALOOPER_EVENT_INPUT as i32 != 0 {
|
||||
let counter: u64 = 0;
|
||||
loop {
|
||||
match unsafe { libc::read(fd, &counter as *const _ as *mut libc::c_void, 8) } {
|
||||
8 => break,
|
||||
-1 => {
|
||||
let error = std::io::Error::last_os_error();
|
||||
if error.kind() != std::io::ErrorKind::Interrupted {
|
||||
log::error!("Error reading from fd: {:?}", error);
|
||||
break;
|
||||
}
|
||||
}
|
||||
count => {
|
||||
log::error!("Unexpected read count from event fd: {}", count);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let weak_ptr: *const MainCallbacksState = data.cast();
|
||||
let weak_ref = Weak::from_raw(weak_ptr);
|
||||
let maybe_upgraded = weak_ref.upgrade();
|
||||
|
||||
// Make sure we don't Drop the Weak reference (so the data pointer
|
||||
// remains valid for future callbacks)
|
||||
let _ = weak_ref.into_raw();
|
||||
|
||||
if let Some(main_callbacks) = maybe_upgraded {
|
||||
if main_callbacks
|
||||
._pending_detach
|
||||
.load(std::sync::atomic::Ordering::SeqCst)
|
||||
{
|
||||
let _ = unsafe { libc::close(main_callbacks.event_fd) };
|
||||
let _drop_weak = Weak::from_raw(weak_ptr);
|
||||
// Returning zero indicates that the fd / callback should be
|
||||
// removed from the Looper
|
||||
return 0;
|
||||
}
|
||||
|
||||
let mut callbacks = main_callbacks.callbacks.lock().unwrap().take_front();
|
||||
|
||||
let jvm = JavaVM::singleton().unwrap();
|
||||
|
||||
for callback in callbacks.drain(0..) {
|
||||
let res = jvm.attach_current_thread(|_env| -> jni::errors::Result<()> {
|
||||
let res = catch_unwind(AssertUnwindSafe(|| {
|
||||
callback();
|
||||
}));
|
||||
if let Err(err) = res {
|
||||
log::error!("Panic in Java main/UI thread callback: {:?}", err);
|
||||
}
|
||||
Ok(())
|
||||
});
|
||||
if let Err(err) = res {
|
||||
log::error!(
|
||||
"JNI Error while running Java main/UI thread callback: {:?}",
|
||||
err
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// put callbacks vec back so we can keep reusing its capacity
|
||||
let mut guard = main_callbacks.callbacks.lock().unwrap();
|
||||
guard.replace_back(callbacks);
|
||||
}
|
||||
|
||||
1
|
||||
})
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
//! The bindings are pre-generated and the right one for the platform is selected at compile time.
|
||||
|
||||
// Bindgen lints
|
||||
#![allow(non_upper_case_globals)]
|
||||
#![allow(non_camel_case_types)]
|
||||
#![allow(non_snake_case)]
|
||||
#![allow(improper_ctypes)]
|
||||
#![allow(clippy::all)]
|
||||
// Temporarily allow UB nullptr dereference in bindgen layout tests until fixed upstream:
|
||||
// https://github.com/rust-lang/rust-bindgen/pull/2055
|
||||
// https://github.com/rust-lang/rust-bindgen/pull/2064
|
||||
#![allow(deref_nullptr)]
|
||||
#![allow(dead_code)]
|
||||
|
||||
use jni_sys::*;
|
||||
use ndk_sys::AAssetManager;
|
||||
use ndk_sys::ANativeWindow;
|
||||
use ndk_sys::{AConfiguration, AInputQueue, ALooper};
|
||||
|
||||
#[cfg(all(
|
||||
any(target_os = "android", feature = "test"),
|
||||
any(target_arch = "arm", target_arch = "armv7")
|
||||
))]
|
||||
include!("ffi_arm.rs");
|
||||
|
||||
#[cfg(all(any(target_os = "android", feature = "test"), target_arch = "aarch64"))]
|
||||
include!("ffi_aarch64.rs");
|
||||
|
||||
#[cfg(all(any(target_os = "android", feature = "test"), target_arch = "x86"))]
|
||||
include!("ffi_i686.rs");
|
||||
|
||||
#[cfg(all(any(target_os = "android", feature = "test"), target_arch = "x86_64"))]
|
||||
include!("ffi_x86_64.rs");
|
||||
@@ -0,0 +1,554 @@
|
||||
use std::{iter::FusedIterator, marker::PhantomData, ptr::NonNull};
|
||||
|
||||
use crate::input::{
|
||||
Axis, Button, ButtonState, EdgeFlags, KeyAction, Keycode, MetaState, MotionAction,
|
||||
MotionEventFlags, Pointer, PointersIter, Source, ToolType,
|
||||
};
|
||||
|
||||
/// A motion event
|
||||
///
|
||||
/// For general discussion of motion events in Android, see [the relevant
|
||||
/// javadoc](https://developer.android.com/reference/android/view/MotionEvent).
|
||||
#[derive(Debug)]
|
||||
#[repr(transparent)]
|
||||
pub struct MotionEvent<'a> {
|
||||
ndk_event: ndk::event::MotionEvent,
|
||||
_lifetime: PhantomData<&'a ndk::event::MotionEvent>,
|
||||
}
|
||||
impl MotionEvent<'_> {
|
||||
pub(crate) fn new(ndk_event: ndk::event::MotionEvent) -> Self {
|
||||
Self {
|
||||
ndk_event,
|
||||
_lifetime: PhantomData,
|
||||
}
|
||||
}
|
||||
pub(crate) fn into_ndk_event(self) -> ndk::event::MotionEvent {
|
||||
self.ndk_event
|
||||
}
|
||||
|
||||
/// Get the source of the event.
|
||||
///
|
||||
#[inline]
|
||||
pub fn source(&self) -> Source {
|
||||
// XXX: we use `AInputEvent_getSource` directly (instead of calling
|
||||
// ndk_event.source()) since we have our own `Source` enum that we
|
||||
// share between backends, which may also capture unknown variants
|
||||
// added in new versions of Android.
|
||||
let source =
|
||||
unsafe { ndk_sys::AInputEvent_getSource(self.ndk_event.ptr().as_ptr()) as u32 };
|
||||
source.into()
|
||||
}
|
||||
|
||||
/// Get the device id associated with the event.
|
||||
///
|
||||
#[inline]
|
||||
pub fn device_id(&self) -> i32 {
|
||||
self.ndk_event.device_id()
|
||||
}
|
||||
|
||||
/// Returns the motion action associated with the event.
|
||||
///
|
||||
/// See [the MotionEvent docs](https://developer.android.com/reference/android/view/MotionEvent#getActionMasked())
|
||||
#[inline]
|
||||
pub fn action(&self) -> MotionAction {
|
||||
// XXX: we use `AMotionEvent_getAction` directly since we have our own
|
||||
// `MotionAction` enum that we share between backends, which may also
|
||||
// capture unknown variants added in new versions of Android.
|
||||
let action =
|
||||
unsafe { ndk_sys::AMotionEvent_getAction(self.ndk_event.ptr().as_ptr()) as u32 }
|
||||
& ndk_sys::AMOTION_EVENT_ACTION_MASK;
|
||||
action.into()
|
||||
}
|
||||
|
||||
/// Returns which button has been modified during a press or release action.
|
||||
///
|
||||
/// For actions other than [`MotionAction::ButtonPress`] and
|
||||
/// [`MotionAction::ButtonRelease`] the returned value is undefined.
|
||||
///
|
||||
/// See [the MotionEvent docs](https://developer.android.com/reference/android/view/MotionEvent#getActionButton())
|
||||
#[inline]
|
||||
pub fn action_button(&self) -> Button {
|
||||
let action_button =
|
||||
unsafe { ndk_sys::AMotionEvent_getActionButton(self.ndk_event.ptr().as_ptr()) as u32 };
|
||||
action_button.into()
|
||||
}
|
||||
|
||||
/// Returns the pointer index of an `Up` or `Down` event.
|
||||
///
|
||||
/// Pointer indices can change per motion event. For an identifier that stays the same, see
|
||||
/// [`Pointer::pointer_id()`].
|
||||
///
|
||||
/// This only has a meaning when the [action](Self::action) is one of [`Up`](MotionAction::Up),
|
||||
/// [`Down`](MotionAction::Down), [`PointerUp`](MotionAction::PointerUp),
|
||||
/// or [`PointerDown`](MotionAction::PointerDown).
|
||||
#[inline]
|
||||
pub fn pointer_index(&self) -> usize {
|
||||
self.ndk_event.pointer_index()
|
||||
}
|
||||
|
||||
/*
|
||||
/// Returns the pointer id associated with the given pointer index.
|
||||
///
|
||||
/// See [the NDK
|
||||
/// docs](https://developer.android.com/ndk/reference/group/input#amotionevent_getpointerid)
|
||||
// TODO: look at output with out-of-range pointer index
|
||||
// Probably -1 though
|
||||
pub fn pointer_id_for(&self, pointer_index: usize) -> i32 {
|
||||
unsafe { ndk_sys::AMotionEvent_getPointerId(self.ndk_event.ptr.as_ptr(), pointer_index) }
|
||||
}
|
||||
*/
|
||||
|
||||
/// Returns the number of pointers in this event
|
||||
///
|
||||
/// See [the MotionEvent docs](https://developer.android.com/reference/android/view/MotionEvent#getPointerCount())
|
||||
#[inline]
|
||||
pub fn pointer_count(&self) -> usize {
|
||||
self.ndk_event.pointer_count()
|
||||
}
|
||||
|
||||
/// An iterator over the pointers in this motion event
|
||||
#[inline]
|
||||
pub fn pointers(&self) -> PointersIter<'_> {
|
||||
PointersIter {
|
||||
inner: PointersIterImpl {
|
||||
event: self.ndk_event.ptr(),
|
||||
pointer_index: 0,
|
||||
pointer_count: self.ndk_event.pointer_count(),
|
||||
_marker: std::marker::PhantomData,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// The pointer at a given pointer index. Panics if the pointer index is out of bounds.
|
||||
///
|
||||
/// If you need to loop over all the pointers, prefer the [`pointers()`](Self::pointers) method.
|
||||
#[inline]
|
||||
pub fn pointer_at_index(&self, index: usize) -> Pointer<'_> {
|
||||
Pointer {
|
||||
inner: PointerImpl {
|
||||
event: self.ndk_event.ptr(),
|
||||
pointer_index: index,
|
||||
_marker: std::marker::PhantomData,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the state of any modifier keys that were pressed during the event.
|
||||
///
|
||||
/// See [the NDK
|
||||
/// docs](https://developer.android.com/ndk/reference/group/input#amotionevent_getmetastate)
|
||||
#[inline]
|
||||
pub fn meta_state(&self) -> MetaState {
|
||||
self.ndk_event.meta_state().into()
|
||||
}
|
||||
|
||||
/// Returns the button state during this event, as a bitfield.
|
||||
///
|
||||
/// See [the NDK
|
||||
/// docs](https://developer.android.com/ndk/reference/group/input#amotionevent_getbuttonstate)
|
||||
#[inline]
|
||||
pub fn button_state(&self) -> ButtonState {
|
||||
self.ndk_event.button_state().into()
|
||||
}
|
||||
|
||||
/// Returns the time of the start of this gesture, in the `java.lang.System.nanoTime()` time
|
||||
/// base
|
||||
///
|
||||
/// See [the NDK
|
||||
/// docs](https://developer.android.com/ndk/reference/group/input#amotionevent_getdowntime)
|
||||
#[inline]
|
||||
pub fn down_time(&self) -> i64 {
|
||||
self.ndk_event.down_time()
|
||||
}
|
||||
|
||||
/// Returns a bitfield indicating which edges were touched by this event.
|
||||
///
|
||||
/// See [the NDK
|
||||
/// docs](https://developer.android.com/ndk/reference/group/input#amotionevent_getedgeflags)
|
||||
#[inline]
|
||||
pub fn edge_flags(&self) -> EdgeFlags {
|
||||
self.ndk_event.edge_flags().into()
|
||||
}
|
||||
|
||||
/// Returns the time of this event, in the `java.lang.System.nanoTime()` time base
|
||||
///
|
||||
/// See [the NDK
|
||||
/// docs](https://developer.android.com/ndk/reference/group/input#amotionevent_geteventtime)
|
||||
#[inline]
|
||||
pub fn event_time(&self) -> i64 {
|
||||
self.ndk_event.event_time()
|
||||
}
|
||||
|
||||
/// The flags associated with a motion event.
|
||||
///
|
||||
/// See [the NDK
|
||||
/// docs](https://developer.android.com/ndk/reference/group/input#amotionevent_getflags)
|
||||
#[inline]
|
||||
pub fn flags(&self) -> MotionEventFlags {
|
||||
self.ndk_event.flags().into()
|
||||
}
|
||||
|
||||
/* Missing from GameActivity currently...
|
||||
/// Returns the offset in the x direction between the coordinates and the raw coordinates
|
||||
///
|
||||
/// See [the NDK
|
||||
/// docs](https://developer.android.com/ndk/reference/group/input#amotionevent_getxoffset)
|
||||
#[inline]
|
||||
pub fn x_offset(&self) -> f32 {
|
||||
self.ndk_event.x_offset()
|
||||
}
|
||||
|
||||
/// Returns the offset in the y direction between the coordinates and the raw coordinates
|
||||
///
|
||||
/// See [the NDK
|
||||
/// docs](https://developer.android.com/ndk/reference/group/input#amotionevent_getyoffset)
|
||||
#[inline]
|
||||
pub fn y_offset(&self) -> f32 {
|
||||
self.ndk_event.y_offset()
|
||||
}
|
||||
*/
|
||||
|
||||
/// Returns the precision of the x value of the coordinates
|
||||
///
|
||||
/// See [the NDK
|
||||
/// docs](https://developer.android.com/ndk/reference/group/input#amotionevent_getxprecision)
|
||||
#[inline]
|
||||
pub fn x_precision(&self) -> f32 {
|
||||
self.ndk_event.x_precision()
|
||||
}
|
||||
|
||||
/// Returns the precision of the y value of the coordinates
|
||||
///
|
||||
/// See [the NDK
|
||||
/// docs](https://developer.android.com/ndk/reference/group/input#amotionevent_getyprecision)
|
||||
#[inline]
|
||||
pub fn y_precision(&self) -> f32 {
|
||||
self.ndk_event.y_precision()
|
||||
}
|
||||
}
|
||||
|
||||
/// A view into the data of a specific pointer in a motion event.
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct PointerImpl<'a> {
|
||||
event: NonNull<ndk_sys::AInputEvent>,
|
||||
pointer_index: usize,
|
||||
_marker: std::marker::PhantomData<&'a MotionEvent<'a>>,
|
||||
}
|
||||
|
||||
impl PointerImpl<'_> {
|
||||
#[inline]
|
||||
pub fn pointer_index(&self) -> usize {
|
||||
self.pointer_index
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn pointer_id(&self) -> i32 {
|
||||
unsafe { ndk_sys::AMotionEvent_getPointerId(self.event.as_ptr(), self.pointer_index) }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn axis_value(&self, axis: Axis) -> f32 {
|
||||
let value: u32 = axis.into();
|
||||
let value = value as i32;
|
||||
unsafe {
|
||||
ndk_sys::AMotionEvent_getAxisValue(self.event.as_ptr(), value, self.pointer_index)
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn raw_x(&self) -> f32 {
|
||||
unsafe { ndk_sys::AMotionEvent_getRawX(self.event.as_ptr(), self.pointer_index) }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn raw_y(&self) -> f32 {
|
||||
unsafe { ndk_sys::AMotionEvent_getRawY(self.event.as_ptr(), self.pointer_index) }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn tool_type(&self) -> ToolType {
|
||||
let value =
|
||||
unsafe { ndk_sys::AMotionEvent_getToolType(self.event.as_ptr(), self.pointer_index) };
|
||||
let value = value as u32;
|
||||
value.into()
|
||||
}
|
||||
|
||||
pub fn history(&self) -> crate::input::PointerHistoryIter<'_> {
|
||||
let history_size =
|
||||
unsafe { ndk_sys::AMotionEvent_getHistorySize(self.event.as_ptr()) } as usize;
|
||||
crate::input::PointerHistoryIter {
|
||||
inner: PointerHistoryIterImpl {
|
||||
event: self.event,
|
||||
pointer_index: self.pointer_index,
|
||||
front: 0,
|
||||
back: history_size,
|
||||
_marker: std::marker::PhantomData,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct PointersIterImpl<'a> {
|
||||
event: NonNull<ndk_sys::AInputEvent>,
|
||||
pointer_count: usize,
|
||||
pointer_index: usize,
|
||||
_marker: std::marker::PhantomData<&'a MotionEvent<'a>>,
|
||||
}
|
||||
|
||||
impl<'a> Iterator for PointersIterImpl<'a> {
|
||||
type Item = Pointer<'a>;
|
||||
fn next(&mut self) -> Option<Pointer<'a>> {
|
||||
if self.pointer_index == self.pointer_count {
|
||||
return None;
|
||||
}
|
||||
let pointer = Pointer {
|
||||
inner: PointerImpl {
|
||||
event: self.event,
|
||||
pointer_index: self.pointer_index,
|
||||
_marker: std::marker::PhantomData,
|
||||
},
|
||||
};
|
||||
self.pointer_index += 1;
|
||||
Some(pointer)
|
||||
}
|
||||
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
let remaining = self.pointer_count - self.pointer_index;
|
||||
(remaining, Some(remaining))
|
||||
}
|
||||
}
|
||||
|
||||
impl ExactSizeIterator for PointersIterImpl<'_> {}
|
||||
|
||||
/// A view into a pointer at a historical moment
|
||||
#[derive(Debug)]
|
||||
pub struct HistoricalPointerImpl<'a> {
|
||||
event: NonNull<ndk_sys::AInputEvent>,
|
||||
pointer_index: usize,
|
||||
history_index: usize,
|
||||
_marker: std::marker::PhantomData<&'a MotionEvent<'a>>,
|
||||
}
|
||||
|
||||
impl<'a> HistoricalPointerImpl<'a> {
|
||||
#[inline]
|
||||
pub fn pointer_index(&self) -> usize {
|
||||
self.pointer_index
|
||||
}
|
||||
|
||||
/// Returns the time of the historical event, in the `java.lang.System.nanoTime()` time base
|
||||
///
|
||||
/// See [`MotionEvent.getHistoricalEventTimeNanos`](https://developer.android.com/reference/android/view/MotionEvent#getHistoricalEventTimeNanos(int)) SDK docs
|
||||
#[inline]
|
||||
pub fn event_time(&self) -> i64 {
|
||||
unsafe {
|
||||
ndk_sys::AMotionEvent_getHistoricalEventTime(self.event.as_ptr(), self.history_index)
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn pointer_id(&self) -> i32 {
|
||||
unsafe { ndk_sys::AMotionEvent_getPointerId(self.event.as_ptr(), self.pointer_index) }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn history_index(&self) -> usize {
|
||||
self.history_index
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn axis_value(&self, axis: Axis) -> f32 {
|
||||
unsafe {
|
||||
ndk_sys::AMotionEvent_getHistoricalAxisValue(
|
||||
self.event.as_ptr(),
|
||||
Into::<u32>::into(axis) as i32,
|
||||
self.pointer_index,
|
||||
self.history_index,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An iterator over the historical points of a specific pointer in a [`MotionEvent`].
|
||||
#[derive(Debug)]
|
||||
pub struct PointerHistoryIterImpl<'a> {
|
||||
event: NonNull<ndk_sys::AInputEvent>,
|
||||
pointer_index: usize,
|
||||
front: usize,
|
||||
back: usize,
|
||||
_marker: std::marker::PhantomData<&'a MotionEvent<'a>>,
|
||||
}
|
||||
|
||||
impl<'a> Iterator for PointerHistoryIterImpl<'a> {
|
||||
type Item = crate::input::HistoricalPointer<'a>;
|
||||
|
||||
fn next(&mut self) -> Option<crate::input::HistoricalPointer<'a>> {
|
||||
if self.front == self.back {
|
||||
return None;
|
||||
}
|
||||
|
||||
let history_index = self.front;
|
||||
self.front += 1;
|
||||
Some(crate::input::HistoricalPointer {
|
||||
inner: crate::input::HistoricalPointerImpl {
|
||||
event: self.event,
|
||||
history_index,
|
||||
pointer_index: self.pointer_index,
|
||||
_marker: std::marker::PhantomData,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
let size = self.back - self.front;
|
||||
(size, Some(size))
|
||||
}
|
||||
}
|
||||
impl<'a> DoubleEndedIterator for PointerHistoryIterImpl<'a> {
|
||||
fn next_back(&mut self) -> Option<crate::input::HistoricalPointer<'a>> {
|
||||
if self.front == self.back {
|
||||
return None;
|
||||
}
|
||||
|
||||
self.back -= 1;
|
||||
let history_index = self.back;
|
||||
Some(crate::input::HistoricalPointer {
|
||||
inner: crate::input::HistoricalPointerImpl {
|
||||
event: self.event,
|
||||
history_index,
|
||||
pointer_index: self.pointer_index,
|
||||
_marker: std::marker::PhantomData,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
impl ExactSizeIterator for PointerHistoryIterImpl<'_> {}
|
||||
impl FusedIterator for PointerHistoryIterImpl<'_> {}
|
||||
|
||||
/// A key event
|
||||
///
|
||||
/// For general discussion of key events in Android, see [the relevant
|
||||
/// javadoc](https://developer.android.com/reference/android/view/KeyEvent).
|
||||
#[derive(Debug)]
|
||||
#[repr(transparent)]
|
||||
pub struct KeyEvent<'a> {
|
||||
ndk_event: ndk::event::KeyEvent,
|
||||
_lifetime: PhantomData<&'a ndk::event::KeyEvent>,
|
||||
}
|
||||
impl KeyEvent<'_> {
|
||||
pub(crate) fn new(ndk_event: ndk::event::KeyEvent) -> Self {
|
||||
Self {
|
||||
ndk_event,
|
||||
_lifetime: PhantomData,
|
||||
}
|
||||
}
|
||||
pub(crate) fn into_ndk_event(self) -> ndk::event::KeyEvent {
|
||||
self.ndk_event
|
||||
}
|
||||
|
||||
/// Get the source of the event.
|
||||
///
|
||||
#[inline]
|
||||
pub fn source(&self) -> Source {
|
||||
// XXX: we use `AInputEvent_getSource` directly (instead of calling
|
||||
// ndk_event.source()) since we have our own `Source` enum that we
|
||||
// share between backends, which may also capture unknown variants
|
||||
// added in new versions of Android.
|
||||
let source =
|
||||
unsafe { ndk_sys::AInputEvent_getSource(self.ndk_event.ptr().as_ptr()) as u32 };
|
||||
source.into()
|
||||
}
|
||||
|
||||
/// Get the device id associated with the event.
|
||||
///
|
||||
#[inline]
|
||||
pub fn device_id(&self) -> i32 {
|
||||
self.ndk_event.device_id()
|
||||
}
|
||||
|
||||
/// Returns the key action associated with the event.
|
||||
///
|
||||
/// See [the KeyEvent docs](https://developer.android.com/reference/android/view/KeyEvent#getAction())
|
||||
#[inline]
|
||||
pub fn action(&self) -> KeyAction {
|
||||
// XXX: we use `AInputEvent_getAction` directly since we have our own
|
||||
// `KeyAction` enum that we share between backends, which may also
|
||||
// capture unknown variants added in new versions of Android.
|
||||
let action = unsafe { ndk_sys::AKeyEvent_getAction(self.ndk_event.ptr().as_ptr()) as u32 };
|
||||
action.into()
|
||||
}
|
||||
|
||||
/// Returns the last time the key was pressed. This is on the scale of
|
||||
/// `java.lang.System.nanoTime()`, which has nanosecond precision, but no defined start time.
|
||||
///
|
||||
/// See [the NDK
|
||||
/// docs](https://developer.android.com/ndk/reference/group/input#akeyevent_getdowntime)
|
||||
#[inline]
|
||||
pub fn down_time(&self) -> i64 {
|
||||
self.ndk_event.down_time()
|
||||
}
|
||||
|
||||
/// Returns the time this event occured. This is on the scale of
|
||||
/// `java.lang.System.nanoTime()`, which has nanosecond precision, but no defined start time.
|
||||
///
|
||||
/// See [the NDK
|
||||
/// docs](https://developer.android.com/ndk/reference/group/input#akeyevent_geteventtime)
|
||||
#[inline]
|
||||
pub fn event_time(&self) -> i64 {
|
||||
self.ndk_event.event_time()
|
||||
}
|
||||
|
||||
/// Returns the keycode associated with this key event
|
||||
///
|
||||
/// See [the NDK
|
||||
/// docs](https://developer.android.com/ndk/reference/group/input#akeyevent_getkeycode)
|
||||
#[inline]
|
||||
pub fn key_code(&self) -> Keycode {
|
||||
// XXX: we use `AInputEvent_getKeyCode` directly since we have our own
|
||||
// `Keycode` enum that we share between backends, which may also
|
||||
// capture unknown variants added in new versions of Android.
|
||||
let keycode =
|
||||
unsafe { ndk_sys::AKeyEvent_getKeyCode(self.ndk_event.ptr().as_ptr()) as u32 };
|
||||
keycode.into()
|
||||
}
|
||||
|
||||
/// Returns the number of repeats of a key.
|
||||
///
|
||||
/// See [the NDK
|
||||
/// docs](https://developer.android.com/ndk/reference/group/input#akeyevent_getrepeatcount)
|
||||
#[inline]
|
||||
pub fn repeat_count(&self) -> i32 {
|
||||
self.ndk_event.repeat_count()
|
||||
}
|
||||
|
||||
/// Returns the hardware keycode of a key. This varies from device to device.
|
||||
///
|
||||
/// See [the NDK
|
||||
/// docs](https://developer.android.com/ndk/reference/group/input#akeyevent_getscancode)
|
||||
#[inline]
|
||||
pub fn scan_code(&self) -> i32 {
|
||||
self.ndk_event.scan_code()
|
||||
}
|
||||
|
||||
/// Returns the state of the modifiers during this key event, represented by a bitmask.
|
||||
///
|
||||
/// See [the NDK
|
||||
/// docs](https://developer.android.com/ndk/reference/group/input#akeyevent_getmetastate)
|
||||
#[inline]
|
||||
pub fn meta_state(&self) -> MetaState {
|
||||
self.ndk_event.meta_state().into()
|
||||
}
|
||||
}
|
||||
|
||||
// We use our own wrapper type for input events to have better consistency
|
||||
// with GameActivity and ensure the enum can be extended without needing a
|
||||
// semver bump
|
||||
/// Enum of possible input events
|
||||
#[derive(Debug)]
|
||||
#[non_exhaustive]
|
||||
pub enum InputEvent<'a> {
|
||||
MotionEvent(self::MotionEvent<'a>),
|
||||
KeyEvent(self::KeyEvent<'a>),
|
||||
TextEvent(crate::input::TextInputState),
|
||||
TextAction(crate::input::TextInputAction),
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
jni::bind_java_type! { pub(crate) IBinder => "android.os.IBinder" }
|
||||
jni::bind_java_type! {
|
||||
pub(crate) View => "android.view.View",
|
||||
type_map {
|
||||
IBinder => "android.os.IBinder",
|
||||
},
|
||||
methods {
|
||||
fn get_window_token() -> IBinder,
|
||||
}
|
||||
}
|
||||
jni::bind_java_type! {
|
||||
pub(crate) InputMethodManager => "android.view.inputmethod.InputMethodManager",
|
||||
type_map {
|
||||
View => "android.view.View",
|
||||
IBinder => "android.os.IBinder",
|
||||
},
|
||||
methods {
|
||||
fn show_soft_input(view: View, flags: i32) -> bool,
|
||||
fn hide_soft_input_from_window(window_token: IBinder, flags: i32) -> bool,
|
||||
}
|
||||
}
|
||||
jni::bind_java_type! {
|
||||
pub(crate) Context => "android.content.Context",
|
||||
fields {
|
||||
#[allow(non_snake_case)]
|
||||
static INPUT_METHOD_SERVICE: JString
|
||||
},
|
||||
methods {
|
||||
fn get_system_service(service_name: JString) -> JObject,
|
||||
}
|
||||
}
|
||||
jni::bind_java_type! {
|
||||
pub(crate) Window => "android.view.Window",
|
||||
type_map {
|
||||
View => "android.view.View",
|
||||
},
|
||||
methods {
|
||||
fn get_decor_view() -> View,
|
||||
}
|
||||
}
|
||||
jni::bind_java_type! {
|
||||
pub(crate) Activity => "android.app.Activity",
|
||||
type_map {
|
||||
Context => "android.content.Context",
|
||||
Window => "android.view.Window",
|
||||
},
|
||||
is_instance_of {
|
||||
context: Context
|
||||
},
|
||||
methods {
|
||||
fn get_window() -> Window,
|
||||
}
|
||||
}
|
||||
|
||||
// Explicitly initialize the JNI bindings so we can get and early, upfront,
|
||||
// error if something is wrong.
|
||||
pub(crate) fn jni_init(env: &jni::Env) -> jni::errors::Result<()> {
|
||||
let _ = IBinderAPI::get(env, &Default::default())?;
|
||||
let _ = ViewAPI::get(env, &Default::default())?;
|
||||
let _ = InputMethodManagerAPI::get(env, &Default::default())?;
|
||||
let _ = ContextAPI::get(env, &Default::default())?;
|
||||
let _ = WindowAPI::get(env, &Default::default())?;
|
||||
let _ = ActivityAPI::get(env, &Default::default())?;
|
||||
let _ = crate::input::AKeyCharacterMapAPI::get(env, &Default::default())?;
|
||||
let _ = crate::input::AInputDeviceAPI::get(env, &Default::default())?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,15 +1,67 @@
|
||||
use std::{ffi::CStr, os::raw::c_char, ptr};
|
||||
use log::Level;
|
||||
use std::{
|
||||
ffi::{CStr, CString},
|
||||
os::raw::c_char,
|
||||
};
|
||||
|
||||
pub fn try_get_path_from_ptr(path: *const c_char) -> Option<std::path::PathBuf> {
|
||||
if path == ptr::null() {
|
||||
if path.is_null() {
|
||||
return None;
|
||||
}
|
||||
let cstr = unsafe {
|
||||
let cstr_slice = CStr::from_ptr(path.cast());
|
||||
cstr_slice.to_str().ok()?
|
||||
};
|
||||
if cstr.len() == 0 {
|
||||
if cstr.is_empty() {
|
||||
return None;
|
||||
}
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn log_panic(panic: Box<dyn std::any::Any + Send>) {
|
||||
let rust_panic = c"RustPanic";
|
||||
|
||||
if let Some(panic) = panic.downcast_ref::<String>() {
|
||||
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 {
|
||||
android_log(Level::Error, rust_panic, c"UnknownPanic");
|
||||
}
|
||||
}
|
||||
|
||||
/// 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<R>(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(|| log_panic(panic)));
|
||||
std::process::abort();
|
||||
})
|
||||
}
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
use std::ptr::NonNull;
|
||||
|
||||
#[cfg(doc)]
|
||||
use crate::AndroidApp;
|
||||
|
||||
/// A means to wake up the main thread while it is blocked waiting for I/O
|
||||
pub struct AndroidAppWaker {
|
||||
looper: NonNull<ndk_sys::ALooper>,
|
||||
}
|
||||
|
||||
impl Clone for AndroidAppWaker {
|
||||
fn clone(&self) -> Self {
|
||||
unsafe { ndk_sys::ALooper_acquire(self.looper.as_ptr()) }
|
||||
Self {
|
||||
looper: self.looper,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for AndroidAppWaker {
|
||||
fn drop(&mut self) {
|
||||
unsafe { ndk_sys::ALooper_release(self.looper.as_ptr()) }
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl Send for AndroidAppWaker {}
|
||||
unsafe impl Sync for AndroidAppWaker {}
|
||||
|
||||
impl AndroidAppWaker {
|
||||
/// Acquire a ref to a looper as a means to be able to wake up the event loop
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The `ALooper` pointer must be valid and not null.
|
||||
pub(crate) unsafe fn new(looper: *mut ndk_sys::ALooper) -> Self {
|
||||
assert!(!looper.is_null(), "looper pointer must not be null");
|
||||
unsafe {
|
||||
// Give the waker its own reference to the looper
|
||||
ndk_sys::ALooper_acquire(looper);
|
||||
AndroidAppWaker {
|
||||
looper: NonNull::new_unchecked(looper),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Interrupts the main thread if it is blocked within [`AndroidApp::poll_events()`]
|
||||
///
|
||||
/// If [`AndroidApp::poll_events()`] is interrupted it will invoke the poll
|
||||
/// callback with a [PollEvent::Wake][wake_event] event.
|
||||
///
|
||||
/// [wake_event]: crate::PollEvent::Wake
|
||||
pub fn wake(&self) {
|
||||
unsafe {
|
||||
ndk_sys::ALooper_wake(self.looper.as_ptr());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
*.iml
|
||||
.gradle
|
||||
/local.properties
|
||||
/.idea/caches
|
||||
/.idea/libraries
|
||||
/.idea/modules.xml
|
||||
/.idea/workspace.xml
|
||||
/.idea/navEditor.xml
|
||||
/.idea/assetWizardSettings.xml
|
||||
.DS_Store
|
||||
/build
|
||||
/captures
|
||||
.externalNativeBuild
|
||||
.cxx
|
||||
local.properties
|
||||
*.so
|
||||
|
||||
# Added by cargo
|
||||
|
||||
/target
|
||||
Cargo.lock
|
||||
@@ -1,3 +0,0 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
@@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="CompilerConfiguration">
|
||||
<bytecodeTargetLevel target="11" />
|
||||
</component>
|
||||
</project>
|
||||
@@ -1,20 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="GradleMigrationSettings" migrationVersion="1" />
|
||||
<component name="GradleSettings">
|
||||
<option name="linkedExternalProjectsSettings">
|
||||
<GradleProjectSettings>
|
||||
<option name="testRunner" value="GRADLE" />
|
||||
<option name="distributionType" value="DEFAULT_WRAPPED" />
|
||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||
<option name="modules">
|
||||
<set>
|
||||
<option value="$PROJECT_DIR$" />
|
||||
<option value="$PROJECT_DIR$/app" />
|
||||
</set>
|
||||
</option>
|
||||
<option name="resolveModulePerSourceSet" value="false" />
|
||||
</GradleProjectSettings>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
||||
@@ -1,9 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="Android Studio default JDK" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/build/classes" />
|
||||
</component>
|
||||
<component name="ProjectType">
|
||||
<option name="id" value="Android" />
|
||||
</component>
|
||||
</project>
|
||||
@@ -1,7 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$/../.." vcs="Git" />
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
@@ -1,23 +0,0 @@
|
||||
[package]
|
||||
name = "agdk-cpal"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
log = "0.4"
|
||||
android_logger = "0.11.0"
|
||||
android-activity = { path="../../android-activity", features = ["game-activity"] }
|
||||
cpal = "0.13"
|
||||
atomic_float = "0.1"
|
||||
anyhow = "1"
|
||||
|
||||
# We currently need to use cpal master which doesn't have ndk-glue as a dependency
|
||||
# and is also updated to ndk 0.7
|
||||
[patch.crates-io]
|
||||
cpal = { git = "https://github.com/RustAudio/cpal", commit = "9b7bb339650" }
|
||||
|
||||
[lib]
|
||||
name="main"
|
||||
crate_type=["cdylib"]
|
||||
@@ -1,16 +0,0 @@
|
||||
This is a minimal test application based on `GameActivity` that just
|
||||
runs a mainloop based on android_activity::poll_events() and plays a
|
||||
sine wave audio test using the Cpal audio library.
|
||||
|
||||
```
|
||||
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
|
||||
adb shell am start -n co.realfit.agdkcpal/.MainActivity
|
||||
```
|
||||
@@ -1 +0,0 @@
|
||||
/build
|
||||
@@ -1,61 +0,0 @@
|
||||
plugins {
|
||||
id 'com.android.application'
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdk 31
|
||||
|
||||
defaultConfig {
|
||||
applicationId "co.realfit.agdkcpal"
|
||||
minSdk 28
|
||||
targetSdk 31
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
debug {
|
||||
minifyEnabled false
|
||||
//packagingOptions {
|
||||
// doNotStrip '**/*.so'
|
||||
//}
|
||||
//debuggable true
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
implementation 'androidx.appcompat:appcompat:1.4.1'
|
||||
implementation 'com.google.android.material:material:1.5.0'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
|
||||
testImplementation 'junit:junit:4.13.2'
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
|
||||
|
||||
// To use the Android Frame Pacing library
|
||||
//implementation "androidx.games:games-frame-pacing:1.9.1"
|
||||
|
||||
// To use the Android Performance Tuner
|
||||
//implementation "androidx.games:games-performance-tuner:1.5.0"
|
||||
|
||||
// To use the Games Activity library
|
||||
implementation "androidx.games:games-activity:1.1.0"
|
||||
|
||||
// To use the Games Controller Library
|
||||
//implementation "androidx.games:games-controller:1.1.0"
|
||||
|
||||
// To use the Games Text Input Library
|
||||
//implementation "androidx.games:games-text-input:1.1.0"
|
||||
}
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
||||
@@ -1,25 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="co.realfit.agdkcpal">
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="AGDK Cpal"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/Theme.RustTemplate">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:configChanges="orientation|screenSize|screenLayout|keyboardHidden"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
|
||||
<meta-data android:name="android.app.lib_name" android:value="main" />
|
||||
</activity>
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
@@ -1,70 +0,0 @@
|
||||
package co.realfit.agdkcpal;
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.core.view.WindowCompat;
|
||||
import androidx.core.view.WindowInsetsCompat;
|
||||
import androidx.core.view.WindowInsetsControllerCompat;
|
||||
|
||||
import com.google.androidgamesdk.GameActivity;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Build.VERSION;
|
||||
import android.os.Build.VERSION_CODES;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.view.WindowManager;
|
||||
|
||||
public class MainActivity extends GameActivity {
|
||||
|
||||
static {
|
||||
// Load the STL first to workaround issues on old Android versions:
|
||||
// "if your app targets a version of Android earlier than Android 4.3
|
||||
// (Android API level 18),
|
||||
// and you use libc++_shared.so, you must load the shared library before any other
|
||||
// library that depends on it."
|
||||
// See https://developer.android.com/ndk/guides/cpp-support#shared_runtimes
|
||||
//System.loadLibrary("c++_shared");
|
||||
|
||||
// Load the native library.
|
||||
// The name "android-game" depends on your CMake configuration, must be
|
||||
// consistent here and inside AndroidManifect.xml
|
||||
System.loadLibrary("main");
|
||||
}
|
||||
|
||||
private void hideSystemUI() {
|
||||
// This will put the game behind any cutouts and waterfalls on devices which have
|
||||
// them, so the corresponding insets will be non-zero.
|
||||
if (VERSION.SDK_INT >= VERSION_CODES.P) {
|
||||
getWindow().getAttributes().layoutInDisplayCutoutMode
|
||||
= WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
|
||||
}
|
||||
// From API 30 onwards, this is the recommended way to hide the system UI, rather than
|
||||
// using View.setSystemUiVisibility.
|
||||
View decorView = getWindow().getDecorView();
|
||||
WindowInsetsControllerCompat controller = new WindowInsetsControllerCompat(getWindow(),
|
||||
decorView);
|
||||
controller.hide(WindowInsetsCompat.Type.systemBars());
|
||||
controller.hide(WindowInsetsCompat.Type.displayCutout());
|
||||
controller.setSystemBarsBehavior(
|
||||
WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
// When true, the app will fit inside any system UI windows.
|
||||
// When false, we render behind any system UI windows.
|
||||
WindowCompat.setDecorFitsSystemWindows(getWindow(), false);
|
||||
hideSystemUI();
|
||||
// You can set IME fields here or in native code using GameActivity_setImeEditorInfoFields.
|
||||
// We set the fields in native_engine.cpp.
|
||||
// super.setImeEditorInfoFields(InputType.TYPE_CLASS_TEXT,
|
||||
// IME_ACTION_NONE, IME_FLAG_NO_FULLSCREEN );
|
||||
super.onCreate(savedInstanceState);
|
||||
}
|
||||
|
||||
public boolean isGooglePlayGames() {
|
||||
PackageManager pm = getPackageManager();
|
||||
return pm.hasSystemFeature("com.google.android.play.feature.HPE_EXPERIENCE");
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:aapt="http://schemas.android.com/aapt"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:endX="85.84757"
|
||||
android:endY="92.4963"
|
||||
android:startX="42.9492"
|
||||
android:startY="49.59793"
|
||||
android:type="linear">
|
||||
<item
|
||||
android:color="#44000000"
|
||||
android:offset="0.0" />
|
||||
<item
|
||||
android:color="#00000000"
|
||||
android:offset="1.0" />
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:fillType="nonZero"
|
||||
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
|
||||
android:strokeWidth="1"
|
||||
android:strokeColor="#00000000" />
|
||||
</vector>
|
||||
@@ -1,170 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<path
|
||||
android:fillColor="#3DDC84"
|
||||
android:pathData="M0,0h108v108h-108z" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M9,0L9,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,0L19,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M29,0L29,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M39,0L39,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M49,0L49,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M59,0L59,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M69,0L69,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M79,0L79,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M89,0L89,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M99,0L99,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,9L108,9"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,19L108,19"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,29L108,29"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,39L108,39"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,49L108,49"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,59L108,59"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,69L108,69"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,79L108,79"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,89L108,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,99L108,99"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,29L89,29"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,39L89,39"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,49L89,49"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,59L89,59"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,69L89,69"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,79L89,79"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M29,19L29,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M39,19L39,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M49,19L49,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M59,19L59,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M69,19L69,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M79,19L79,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
</vector>
|
||||
@@ -1,18 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".MainActivity">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Hello World!"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
@@ -1,5 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_background" />
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||
</adaptive-icon>
|
||||
@@ -1,5 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_background" />
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||
</adaptive-icon>
|
||||
|
Before Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 982 B |
|
Before Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 5.8 KiB |
|
Before Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 7.6 KiB |
@@ -1,16 +0,0 @@
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<!-- Base application theme. -->
|
||||
<style name="Theme.RustTemplate" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
|
||||
<!-- Primary brand color. -->
|
||||
<item name="colorPrimary">@color/purple_200</item>
|
||||
<item name="colorPrimaryVariant">@color/purple_700</item>
|
||||
<item name="colorOnPrimary">@color/black</item>
|
||||
<!-- Secondary brand color. -->
|
||||
<item name="colorSecondary">@color/teal_200</item>
|
||||
<item name="colorSecondaryVariant">@color/teal_200</item>
|
||||
<item name="colorOnSecondary">@color/black</item>
|
||||
<!-- Status bar color. -->
|
||||
<item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
|
||||
<!-- Customize your theme here. -->
|
||||
</style>
|
||||
</resources>
|
||||
@@ -1,16 +0,0 @@
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<!-- Base application theme. -->
|
||||
<style name="Theme.RustTemplate" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
|
||||
<!-- Primary brand color. -->
|
||||
<item name="colorPrimary">@color/purple_500</item>
|
||||
<item name="colorPrimaryVariant">@color/purple_700</item>
|
||||
<item name="colorOnPrimary">@color/white</item>
|
||||
<!-- Secondary brand color. -->
|
||||
<item name="colorSecondary">@color/teal_200</item>
|
||||
<item name="colorSecondaryVariant">@color/teal_700</item>
|
||||
<item name="colorOnSecondary">@color/black</item>
|
||||
<!-- Status bar color. -->
|
||||
<item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
|
||||
<!-- Customize your theme here. -->
|
||||
</style>
|
||||
</resources>
|
||||
@@ -1,10 +0,0 @@
|
||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
plugins {
|
||||
id 'com.android.application' version '7.1.2' apply false
|
||||
id 'com.android.library' version '7.1.2' apply false
|
||||
}
|
||||
|
||||
task clean(type: Delete) {
|
||||
delete rootProject.buildDir
|
||||
}
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
# Project-wide Gradle settings.
|
||||
# IDE (e.g. Android Studio) users:
|
||||
# Gradle settings configured through the IDE *will override*
|
||||
# any settings specified in this file.
|
||||
# For more details on how to configure your build environment visit
|
||||
# http://www.gradle.org/docs/current/userguide/build_environment.html
|
||||
# Specifies the JVM arguments used for the daemon process.
|
||||
# The setting is particularly useful for tweaking memory settings.
|
||||
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
|
||||
# When configured, Gradle will run in incubating parallel mode.
|
||||
# This option should only be used with decoupled projects. More details, visit
|
||||
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
||||
# org.gradle.parallel=true
|
||||
# AndroidX package structure to make it clearer which packages are bundled with the
|
||||
# Android operating system, and which are packaged with your app"s APK
|
||||
# https://developer.android.com/topic/libraries/support-library/androidx-rn
|
||||
android.useAndroidX=true
|
||||
# Enables namespacing of each library's R class so that its R class includes only the
|
||||
# resources declared in the library itself and none from the library's dependencies,
|
||||
# thereby reducing the size of the R class for that library
|
||||
android.nonTransitiveRClass=true
|
||||
@@ -1,6 +0,0 @@
|
||||
#Mon May 02 15:39:12 BST 2022
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip
|
||||
distributionPath=wrapper/dists
|
||||
zipStorePath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
@@ -1,185 +0,0 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
#
|
||||
# Copyright 2015 the original author or authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
## Gradle start up script for UN*X
|
||||
##
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
# Resolve links: $0 may be a link
|
||||
PRG="$0"
|
||||
# Need this for relative symlinks.
|
||||
while [ -h "$PRG" ] ; do
|
||||
ls=`ls -ld "$PRG"`
|
||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||
if expr "$link" : '/.*' > /dev/null; then
|
||||
PRG="$link"
|
||||
else
|
||||
PRG=`dirname "$PRG"`"/$link"
|
||||
fi
|
||||
done
|
||||
SAVED="`pwd`"
|
||||
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||
APP_HOME="`pwd -P`"
|
||||
cd "$SAVED" >/dev/null
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
}
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
NONSTOP* )
|
||||
nonstop=true
|
||||
;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||
else
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD="java"
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||
MAX_FD_LIMIT=`ulimit -H -n`
|
||||
if [ $? -eq 0 ] ; then
|
||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||
MAX_FD="$MAX_FD_LIMIT"
|
||||
fi
|
||||
ulimit -n $MAX_FD
|
||||
if [ $? -ne 0 ] ; then
|
||||
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||
fi
|
||||
else
|
||||
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||
fi
|
||||
fi
|
||||
|
||||
# For Darwin, add options to specify how the application appears in the dock
|
||||
if $darwin; then
|
||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||
fi
|
||||
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
|
||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||
|
||||
# We build the pattern for arguments to be converted via cygpath
|
||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||
SEP=""
|
||||
for dir in $ROOTDIRSRAW ; do
|
||||
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||
SEP="|"
|
||||
done
|
||||
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||
# Add a user-defined pattern to the cygpath arguments
|
||||
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||
fi
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
i=0
|
||||
for arg in "$@" ; do
|
||||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||
|
||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||
else
|
||||
eval `echo args$i`="\"$arg\""
|
||||
fi
|
||||
i=`expr $i + 1`
|
||||
done
|
||||
case $i in
|
||||
0) set -- ;;
|
||||
1) set -- "$args0" ;;
|
||||
2) set -- "$args0" "$args1" ;;
|
||||
3) set -- "$args0" "$args1" "$args2" ;;
|
||||
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# Escape application args
|
||||
save () {
|
||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||
echo " "
|
||||
}
|
||||
APP_ARGS=`save "$@"`
|
||||
|
||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
@@ -1,89 +0,0 @@
|
||||
@rem
|
||||
@rem Copyright 2015 the original author or authors.
|
||||
@rem
|
||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@rem you may not use this file except in compliance with the License.
|
||||
@rem You may obtain a copy of the License at
|
||||
@rem
|
||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||
@rem
|
||||
@rem Unless required by applicable law or agreed to in writing, software
|
||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%" == "" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
||||
@@ -1,16 +0,0 @@
|
||||
pluginManagement {
|
||||
repositories {
|
||||
gradlePluginPortal()
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
dependencyResolutionManagement {
|
||||
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
//rootProject.name = "Rust Template"
|
||||
include ':app'
|
||||