Compare commits
36 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 |
@@ -2,7 +2,7 @@ name: ci
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: '*'
|
||||
branches: "*"
|
||||
pull_request:
|
||||
|
||||
env:
|
||||
@@ -17,9 +17,9 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
# See top README for MSRV policy
|
||||
rust-version: [1.73.0, stable]
|
||||
rust-version: [1.85.0, stable]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
# Downgrade all dependencies to their minimum version, both to ensure our
|
||||
# minimum version bounds are correct and buildable, as well as to satisfy
|
||||
@@ -109,7 +109,7 @@ jobs:
|
||||
format:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Format
|
||||
run: cargo fmt --all -- --check
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
[](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/2023/03/09/Rust-1.68.0.html)
|
||||
[](https://blog.rust-lang.org/2025/02/20/Rust-1.85.0/)
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -13,7 +13,7 @@ It's comparable to [`android_native_app_glue.c`][ndk_concepts]
|
||||
for C/C++ applications and is an alternative to the [ndk-glue] crate.
|
||||
|
||||
`android-activity` provides a way to load your crate as a `cdylib` library via
|
||||
the `onCreate` method of your Android `Activity` class; run an `android_main()`
|
||||
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.
|
||||
|
||||
@@ -25,34 +25,49 @@ 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
|
||||
[agdk]: https://developer.android.com/games/agdk/overview
|
||||
|
||||
## Example
|
||||
## Quick Start
|
||||
|
||||
Cargo.toml
|
||||
**Cargo.toml:**
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
log = "0.4"
|
||||
android_logger = "0.13"
|
||||
android-activity = { version = "0.5", features = [ "native-activity" ] }
|
||||
android-activity = { version = "0.6", features = [ "native-activity" ] }
|
||||
|
||||
[lib]
|
||||
crate_type = ["cdylib"]
|
||||
crate-type = ["cdylib"]
|
||||
```
|
||||
|
||||
_Note: that you will need to either specify the **"native-activity"** feature or **"game-activity"** feature to identify which `Activity` base class your application is based on_
|
||||
_Note: that you will need to either specify the **"native-activity"** feature or
|
||||
**"game-activity"** feature to identify which `Activity` base class your
|
||||
application is based on_
|
||||
|
||||
lib.rs
|
||||
**lib.rs:**
|
||||
|
||||
```rust
|
||||
use std::sync::OnceLock;
|
||||
use android_activity::{AndroidApp, InputStatus, MainEvent, PollEvent};
|
||||
|
||||
#[no_mangle]
|
||||
// - 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| {
|
||||
@@ -62,6 +77,11 @@ fn android_main(app: AndroidApp) {
|
||||
PollEvent::Main(main_event) => {
|
||||
log::info!("Main event: {:?}", main_event);
|
||||
match main_event {
|
||||
// Once you receive a `Destroy` event, your `AndroidApp` will no longer
|
||||
// be associated with any `Activity` and it's methods will effectively be no-ops.
|
||||
//
|
||||
// You should return from `android_main` and if your `Activity` gets recreated then
|
||||
// a new `AndroidApp` will be passed to a new invocation of `android_main`.
|
||||
MainEvent::Destroy => { return; }
|
||||
_ => {}
|
||||
}
|
||||
@@ -85,80 +105,247 @@ cargo apk run
|
||||
adb logcat example:V *:S
|
||||
```
|
||||
|
||||
_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._
|
||||
|
||||
## Full Examples
|
||||
|
||||
See [this collection of examples](https://github.com/rust-mobile/rust-android-examples) (based on both `GameActivity` and `NativeActivity`).
|
||||
See [this collection of
|
||||
examples](https://github.com/rust-mobile/rust-android-examples) (based on both
|
||||
`GameActivity` and `NativeActivity`).
|
||||
|
||||
Each example is a standalone project that may also be a convenient templates for starting a new project.
|
||||
Each example is a standalone Android Studio project that can serve as a
|
||||
convenient template for starting a new project.
|
||||
|
||||
For the examples based on middleware frameworks (Winit and or Egui) they also aim to demonstrate how it's possible to write portable code that will run on Android and other systems.
|
||||
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.
|
||||
|
||||
## Should I use NativeActivity or GameActivity?
|
||||
## Optional `android_on_create` entry point
|
||||
|
||||
To learn more about the `NativeActivity` class that's shipped with Android see [here](https://developer.android.com/ndk/guides/concepts#naa).
|
||||
`android-activity` also supports an optional `android_on_create` entry point
|
||||
that gets called from the `Activity.onCreate()` callback before `android_main()`
|
||||
is called.
|
||||
|
||||
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)
|
||||
`android_on_create` is called from the Java main / UI thread before the
|
||||
`android_main` thread is spawned.
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
It's expected that the `GameActivity` backend will gain more sophisticated input handling features over time (such as for supporting input via onscreen keyboards or game controllers) and only `GameActivity` is based on the [`AppCompatActivity`] subclass which you may want in some situations to help with compatibility across devices.
|
||||
|
||||
Even if you start out using `NativeActivity` for the convenience, it's likely that most moderately complex applications will eventually need to define their own `Activity` subclass (either subclassing `NativeActivity` or `GameActivity`) which will require compiling at least a small amount of Java or Kotlin code. This is generally due to Android's design which directs numerous events via the `Activity` class which can only be processed by overloading some associated Activity method.
|
||||
|
||||
## Switching from ndk-glue to android-activity
|
||||
|
||||
### Winit-based applications
|
||||
|
||||
Firstly; if you have a [Winit](https://crates.io/crates/winit) based application and also have an explicit dependency on `ndk-glue` your application will need to remove its dependency on `ndk-glue` for the 0.28 release of Winit which will be based on android-activity (Since glue crates, due to their nature, can't be compatible with alternative glue crates).
|
||||
|
||||
Winit-based applications can follow the [Android README](https://github.com/rust-windowing/winit#android) guidance for advice on how to switch over. Most Winit-based applications should aim to remove any explicit dependency on a specific glue crate (so not depend directly on `ndk-glue` or `android-activity` and instead rely on Winit to pull in the right glue crate). The main practical change will then be to add a `#[no_mangle]fn android_main(app: AndroidApp)` entry point.
|
||||
|
||||
See the [Android README](https://github.com/rust-windowing/winit#android) for more details and also see the [Winit-based examples here](https://github.com/rust-mobile/rust-android-examples).
|
||||
|
||||
### Middleware crates (i.e. not applications)
|
||||
|
||||
If you have a crate that would be considered a middleware library (for example using JNI to support access to Bluetooth, or Android's Camera APIs) then the crate should almost certainly remove any dependence on a specific glue crate because this imposes a strict compatibility constraint that means the crate can only be used by applications that use that exact same glue crate version.
|
||||
|
||||
Middleware libraries can instead look at using the [ndk-context](https://crates.io/crates/ndk-context) crate as a means for being able to use JNI without making any assumptions about the applications overall architecture. This way a middleware crate can work with alternative glue crates (including `ndk-glue` and `android-activity`) as well as work with embedded use cases (i.e. non-native applications that may just embed a dynamic library written in Rust to implement certain native functions).
|
||||
|
||||
### Other, non-Winit-based applications
|
||||
|
||||
The steps to switch a simple standalone application over from `ndk-glue` to `android-activity` (still based on `NativeActivity`) should be:
|
||||
|
||||
1. Remove `ndk-glue` from your Cargo.toml
|
||||
2. Add a dependency on `android-activity`, like `android-activity = { version="0.5", features = [ "native-activity" ] }`
|
||||
3. Optionally add a dependency on `android_logger = "0.13.0"`
|
||||
4. Update the `main` entry point to look like this:
|
||||
For example:
|
||||
|
||||
```rust
|
||||
use android_activity::AndroidApp;
|
||||
use std::sync::OnceLock;
|
||||
use jni::{JavaVM, objects::JObject};
|
||||
|
||||
#[no_mangle]
|
||||
fn android_main(app: AndroidApp) {
|
||||
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) {
|
||||
|
||||
// `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
|
||||
}
|
||||
```
|
||||
|
||||
See this minimal [NativeActivity Mainloop](https://github.com/rust-mobile/android-activity/tree/main/examples/na-mainloop) for more details about how to poll for events.
|
||||
_(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)_
|
||||
|
||||
There is is no `#[ndk_glue::main]` replacement considering that `android_main()` entry point needs to be passed an `AndroidApp` argument which isn't compatible with a traditional `main()` function. Having an Android specific entry point also gives a place to initialize Android logging and handle other Android specific details (such as building an event loop based on the `app` argument)
|
||||
## Should I use NativeActivity or GameActivity?
|
||||
|
||||
### Design Summary / Motivation behind android-activity
|
||||
To learn more about the `NativeActivity` class that's shipped with Android see
|
||||
[here](https://developer.android.com/ndk/guides/concepts#naa).
|
||||
|
||||
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:
|
||||
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)
|
||||
|
||||
1. **Support alternative Activity classes**: Prior glue crates were based on `NativeActivity` and their API precluded supporting alternatives. In particular there was an interest in the [`GameActivity`] class in conjunction with it's [`GameTextInput`] library that can facilitate onscreen keyboard support. This also allows building applications based on the standard [`AppCompatActivity`] base class which isn't possible with `NativeActivity`. Finally there was interest in paving the way towards supporting a first-party `RustActivity` that could be best tailored towards the needs of Rust applications on Android.
|
||||
2. **Encapsulate IPC + synchronization between the native thread and the JVM thread**: For example with `ndk-glue` the application itself needs to avoid race conditions between the native and Java thread by following a locking convention) and it wasn't clear how this would extend to support other requests (like state saving) that also require synchronization.
|
||||
3. **Avoid static global state**: Keeping in mind the possibility of supporting applications with multiple native activities there was interest in having an API that didn't rely on global statics to track top-level state. Instead of having global getters for state then `android-activity` passes an explicit `app: AndroidApp` argument to the entry point that encapsulates the state connected with a single `Activity`.
|
||||
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 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_.
|
||||
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.
|
||||
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.
|
||||
|
||||
@@ -6,15 +6,121 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [0.6.1] - 2026-03-30
|
||||
|
||||
### Added
|
||||
|
||||
- input: `TextInputAction` enum representing action button types on soft keyboards. ([#216](https://github.com/rust-mobile/android-activity/pull/216))
|
||||
- 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))
|
||||
- input: TextInputAction enum representing action button types on soft keyboards.
|
||||
- input: InputEvent::TextAction event for handling action button presses from soft keyboards.
|
||||
- `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.73.0 ([#193](https://github.com/rust-mobile/android-activity/pull/193))
|
||||
- GameActivity updated to 4.0.0 (requires the corresponding 4.0.0 `.aar` release from Google) ([#191](https://github.com/rust-mobile/android-activity/pull/191))
|
||||
|
||||
- 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
|
||||
|
||||
@@ -229,7 +335,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
### Added
|
||||
- Initial release
|
||||
|
||||
[unreleased]: https://github.com/rust-mobile/android-activity/compare/v0.6.0...HEAD
|
||||
[unreleased]: https://github.com/rust-mobile/android-activity/compare/v0.6.1...HEAD
|
||||
[0.6.1]: https://github.com/rust-mobile/android-activity/compare/v0.6.0...v0.6.1
|
||||
[0.6.0]: https://github.com/rust-mobile/android-activity/compare/v0.5.2...v0.6.0
|
||||
[0.5.2]: https://github.com/rust-mobile/android-activity/compare/v0.5.1...v0.5.2
|
||||
[0.5.1]: https://github.com/rust-mobile/android-activity/compare/v0.5.0...v0.5.1
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "android-activity"
|
||||
version = "0.6.0"
|
||||
version = "0.6.1"
|
||||
edition = "2021"
|
||||
keywords = ["android", "ndk"]
|
||||
readme = "../README.md"
|
||||
@@ -9,17 +9,9 @@ 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",
|
||||
]
|
||||
include = ["/build.rs", "/android-games-sdk", "/LICENSE*", "/src"]
|
||||
|
||||
# Even though we could technically still build with 1.69, 1.73 has a fix for the
|
||||
# definition of the `stat` struct on Android, and so it seems worthwhile drawing
|
||||
# a line under that to ensure android-activity applications have that fix.
|
||||
rust-version = "1.73.0"
|
||||
rust-version = "1.85.0"
|
||||
|
||||
[features]
|
||||
# Note: we don't enable any backend by default since features
|
||||
@@ -29,15 +21,14 @@ rust-version = "1.73.0"
|
||||
# In general it's only the final application crate that needs
|
||||
# to decide on a backend.
|
||||
default = []
|
||||
game-activity = []
|
||||
game-activity = ["simd_cesu8"]
|
||||
native-activity = []
|
||||
api-level-30 = ["ndk/api-level-30"]
|
||||
|
||||
[dependencies]
|
||||
log = "0.4"
|
||||
jni-sys = "0.3"
|
||||
cesu8 = "1"
|
||||
jni = "0.21"
|
||||
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"
|
||||
@@ -45,11 +36,14 @@ android-properties = "0.2"
|
||||
num_enum = "0.7"
|
||||
bitflags = "2.0"
|
||||
libc = "0.2.139"
|
||||
thiserror = "1"
|
||||
thiserror = "2"
|
||||
|
||||
[build-dependencies]
|
||||
cc = { version = "1.0.42", features = ["parallel"] }
|
||||
|
||||
[dev-dependencies]
|
||||
jni = "0.22.4"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = [
|
||||
"aarch64-linux-android",
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
../../../../../include/common/
|
||||
@@ -43,30 +43,30 @@
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define GAMEACTIVITY_VERSION_REVISION a0e943c3a84fd7f344c3d36cdf4e88fd595f81b8
|
||||
#define GAMEACTIVITY_MAJOR_VERSION 4
|
||||
#define GAMEACTIVITY_MINOR_VERSION 0
|
||||
#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)
|
||||
#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
|
||||
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;
|
||||
|
||||
/**
|
||||
@@ -80,74 +80,73 @@ struct GameActivityCallbacks;
|
||||
* code as it is being launched.
|
||||
*/
|
||||
typedef struct GameActivity {
|
||||
/**
|
||||
* Pointer to the callback function table of the native application.
|
||||
* You can set the functions here to your own callbacks. The callbacks
|
||||
* pointer itself here should not be changed; it is allocated and managed
|
||||
* for you by the framework.
|
||||
*/
|
||||
struct GameActivityCallbacks* callbacks;
|
||||
/**
|
||||
* Pointer to the callback function table of the native application.
|
||||
* You can set the functions here to your own callbacks. The callbacks
|
||||
* pointer itself here should not be changed; it is allocated and managed
|
||||
* for you by the framework.
|
||||
*/
|
||||
struct GameActivityCallbacks* callbacks;
|
||||
|
||||
/**
|
||||
* The global handle on the process's Java VM.
|
||||
*/
|
||||
JavaVM* vm;
|
||||
/**
|
||||
* The global handle on the process's Java VM.
|
||||
*/
|
||||
JavaVM* vm;
|
||||
|
||||
/**
|
||||
* JNI context for the main thread of the app. Note that this field
|
||||
* can ONLY be used from the main thread of the process; that is, the
|
||||
* thread that calls into the GameActivityCallbacks.
|
||||
*/
|
||||
JNIEnv* env;
|
||||
/**
|
||||
* JNI context for the main thread of the app. Note that this field
|
||||
* can ONLY be used from the main thread of the process; that is, the
|
||||
* thread that calls into the GameActivityCallbacks.
|
||||
*/
|
||||
JNIEnv* env;
|
||||
|
||||
/**
|
||||
* The GameActivity object handle.
|
||||
*/
|
||||
jobject javaGameActivity;
|
||||
/**
|
||||
* The GameActivity object handle.
|
||||
*/
|
||||
jobject javaGameActivity;
|
||||
|
||||
/**
|
||||
* Path to this application's internal data directory.
|
||||
*/
|
||||
const char* internalDataPath;
|
||||
/**
|
||||
* Path to this application's internal data directory.
|
||||
*/
|
||||
const char* internalDataPath;
|
||||
|
||||
/**
|
||||
* Path to this application's external (removable/mountable) data directory.
|
||||
*/
|
||||
const char* externalDataPath;
|
||||
/**
|
||||
* Path to this application's external (removable/mountable) data directory.
|
||||
*/
|
||||
const char* externalDataPath;
|
||||
|
||||
/**
|
||||
* The platform's SDK version code.
|
||||
*/
|
||||
int32_t sdkVersion;
|
||||
/**
|
||||
* The platform's SDK version code.
|
||||
*/
|
||||
int32_t sdkVersion;
|
||||
|
||||
/**
|
||||
* This is the native instance of the application. It is not used by
|
||||
* the framework, but can be set by the application to its own instance
|
||||
* state.
|
||||
*/
|
||||
void* instance;
|
||||
/**
|
||||
* This is the native instance of the application. It is not used by
|
||||
* the framework, but can be set by the application to its own instance
|
||||
* state.
|
||||
*/
|
||||
void* instance;
|
||||
|
||||
/**
|
||||
* Pointer to the Asset Manager instance for the application. The
|
||||
* application uses this to access binary assets bundled inside its own .apk
|
||||
* file.
|
||||
*/
|
||||
AAssetManager* assetManager;
|
||||
/**
|
||||
* Pointer to the Asset Manager instance for the application. The
|
||||
* application uses this to access binary assets bundled inside its own .apk
|
||||
* file.
|
||||
*/
|
||||
AAssetManager* assetManager;
|
||||
|
||||
/**
|
||||
* Available starting with Honeycomb: path to the directory containing
|
||||
* the application's OBB files (if any). If the app doesn't have any
|
||||
* OBB files, this directory may not exist.
|
||||
*/
|
||||
const char* obbPath;
|
||||
/**
|
||||
* Available starting with Honeycomb: path to the directory containing
|
||||
* the application's OBB files (if any). If the app doesn't have any
|
||||
* OBB files, this directory may not exist.
|
||||
*/
|
||||
const char* obbPath;
|
||||
} GameActivity;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
@@ -156,153 +155,147 @@ typedef void (*SaveInstanceStateRecallback)(const char* bytes, int len,
|
||||
* to have it called.
|
||||
*/
|
||||
typedef struct GameActivityCallbacks {
|
||||
/**
|
||||
* GameActivity has started. See Java documentation for Activity.onStart()
|
||||
* for more information.
|
||||
*/
|
||||
void (*onStart)(GameActivity* activity);
|
||||
/**
|
||||
* GameActivity has started. See Java documentation for Activity.onStart()
|
||||
* for more information.
|
||||
*/
|
||||
void (*onStart)(GameActivity* activity);
|
||||
|
||||
/**
|
||||
* GameActivity has resumed. See Java documentation for Activity.onResume()
|
||||
* for more information.
|
||||
*/
|
||||
void (*onResume)(GameActivity* activity);
|
||||
/**
|
||||
* GameActivity has resumed. See Java documentation for Activity.onResume()
|
||||
* for more information.
|
||||
*/
|
||||
void (*onResume)(GameActivity* activity);
|
||||
|
||||
/**
|
||||
* The framework is asking GameActivity to save its current instance state.
|
||||
* See the Java documentation for Activity.onSaveInstanceState() for more
|
||||
* information. The user should call the recallback with their data, its
|
||||
* length and the provided context; they retain ownership of the data. Note
|
||||
* 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* context);
|
||||
/**
|
||||
* The framework is asking GameActivity to save its current instance state.
|
||||
* See the Java documentation for Activity.onSaveInstanceState() for more
|
||||
* information. The user should call the recallback with their data, its
|
||||
* length and the provided context; they retain ownership of the data. Note
|
||||
* 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* context);
|
||||
|
||||
/**
|
||||
* GameActivity has paused. See Java documentation for Activity.onPause()
|
||||
* for more information.
|
||||
*/
|
||||
void (*onPause)(GameActivity* activity);
|
||||
/**
|
||||
* GameActivity has paused. See Java documentation for Activity.onPause()
|
||||
* for more information.
|
||||
*/
|
||||
void (*onPause)(GameActivity* activity);
|
||||
|
||||
/**
|
||||
* GameActivity has stopped. See Java documentation for Activity.onStop()
|
||||
* for more information.
|
||||
*/
|
||||
void (*onStop)(GameActivity* activity);
|
||||
/**
|
||||
* GameActivity has stopped. See Java documentation for Activity.onStop()
|
||||
* for more information.
|
||||
*/
|
||||
void (*onStop)(GameActivity* activity);
|
||||
|
||||
/**
|
||||
* GameActivity is being destroyed. See Java documentation for
|
||||
* Activity.onDestroy() for more information.
|
||||
*/
|
||||
void (*onDestroy)(GameActivity* activity);
|
||||
/**
|
||||
* GameActivity is being destroyed. See Java documentation for
|
||||
* Activity.onDestroy() for more information.
|
||||
*/
|
||||
void (*onDestroy)(GameActivity* activity);
|
||||
|
||||
/**
|
||||
* Focus has changed in this GameActivity's window. This is often used,
|
||||
* for example, to pause a game when it loses input focus.
|
||||
*/
|
||||
void (*onWindowFocusChanged)(GameActivity* activity, bool hasFocus);
|
||||
/**
|
||||
* Focus has changed in this GameActivity's window. This is often used,
|
||||
* for example, to pause a game when it loses input focus.
|
||||
*/
|
||||
void (*onWindowFocusChanged)(GameActivity* activity, bool hasFocus);
|
||||
|
||||
/**
|
||||
* 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);
|
||||
/**
|
||||
* 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);
|
||||
|
||||
/**
|
||||
* 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);
|
||||
/**
|
||||
* 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);
|
||||
|
||||
/**
|
||||
* The drawing window for this native activity needs to be redrawn. To
|
||||
* avoid transient artifacts during screen changes (such resizing after
|
||||
* 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);
|
||||
/**
|
||||
* The drawing window for this native activity needs to be redrawn. To
|
||||
* avoid transient artifacts during screen changes (such resizing after
|
||||
* 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);
|
||||
|
||||
/**
|
||||
* The drawing window for this native activity is going to be destroyed.
|
||||
* You MUST ensure that you do not touch the window object after returning
|
||||
* from this function: in the common case of drawing to the window from
|
||||
* another thread, that means the implementation of this callback must
|
||||
* properly synchronize with the other thread to stop its drawing before
|
||||
* returning from here.
|
||||
*/
|
||||
void (*onNativeWindowDestroyed)(GameActivity* activity,
|
||||
ANativeWindow* window);
|
||||
/**
|
||||
* The drawing window for this native activity is going to be destroyed.
|
||||
* You MUST ensure that you do not touch the window object after returning
|
||||
* from this function: in the common case of drawing to the window from
|
||||
* another thread, that means the implementation of this callback must
|
||||
* properly synchronize with the other thread to stop its drawing before
|
||||
* returning from here.
|
||||
*/
|
||||
void (*onNativeWindowDestroyed)(GameActivity* activity, ANativeWindow* window);
|
||||
|
||||
/**
|
||||
* The current device AConfiguration has changed. The new configuration can
|
||||
* be retrieved from assetManager.
|
||||
*/
|
||||
void (*onConfigurationChanged)(GameActivity* activity);
|
||||
/**
|
||||
* The current device AConfiguration has changed. The new configuration can
|
||||
* be retrieved from assetManager.
|
||||
*/
|
||||
void (*onConfigurationChanged)(GameActivity* activity);
|
||||
|
||||
/**
|
||||
* The system is running low on memory. Use this callback to release
|
||||
* resources you do not need, to help the system avoid killing more
|
||||
* important processes.
|
||||
*/
|
||||
void (*onTrimMemory)(GameActivity* activity, int level);
|
||||
/**
|
||||
* The system is running low on memory. Use this callback to release
|
||||
* resources you do not need, to help the system avoid killing more
|
||||
* important processes.
|
||||
*/
|
||||
void (*onTrimMemory)(GameActivity* activity, int level);
|
||||
|
||||
/**
|
||||
* Callback called for every MotionEvent done on the GameActivity
|
||||
* SurfaceView. Ownership of `event` is maintained by the library and it is
|
||||
* only valid during the callback.
|
||||
*/
|
||||
bool (*onTouchEvent)(GameActivity* activity,
|
||||
const GameActivityMotionEvent* event);
|
||||
/**
|
||||
* Callback called for every MotionEvent done on the GameActivity
|
||||
* SurfaceView. Ownership of `event` is maintained by the library and it is
|
||||
* only valid during the callback.
|
||||
*/
|
||||
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);
|
||||
/**
|
||||
* 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);
|
||||
|
||||
/**
|
||||
* Callback called for every key up event on the GameActivity SurfaceView.
|
||||
* Ownership of `event` is maintained by the library and it is only valid
|
||||
* during the callback.
|
||||
*/
|
||||
bool (*onKeyUp)(GameActivity* activity, const GameActivityKeyEvent* event);
|
||||
/**
|
||||
* Callback called for every key up event on the GameActivity SurfaceView.
|
||||
* Ownership of `event` is maintained by the library and it is only valid
|
||||
* during the callback.
|
||||
*/
|
||||
bool (*onKeyUp)(GameActivity* activity, const GameActivityKeyEvent* event);
|
||||
|
||||
/**
|
||||
* Callback called for every soft-keyboard text input event.
|
||||
* Ownership of `state` is maintained by the library and it is only valid
|
||||
* during the callback.
|
||||
*/
|
||||
void (*onTextInputEvent)(GameActivity* activity,
|
||||
const GameTextInputState* state);
|
||||
/**
|
||||
* Callback called for every soft-keyboard text input event.
|
||||
* Ownership of `state` is maintained by the library and it is only valid
|
||||
* during the callback.
|
||||
*/
|
||||
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 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 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.
|
||||
*/
|
||||
void (*onSoftwareKeyboardVisibilityChanged)(GameActivity* activity, bool visible);
|
||||
|
||||
/**
|
||||
* Callback called when the software keyboard is shown or hidden.
|
||||
*/
|
||||
bool (*onEditorAction)(GameActivity* activity, int action);
|
||||
/**
|
||||
* Callback called when the software keyboard is shown or hidden.
|
||||
*/
|
||||
bool (*onEditorAction)(GameActivity* activity, int action);
|
||||
} GameActivityCallbacks;
|
||||
|
||||
/**
|
||||
@@ -322,7 +315,7 @@ typedef void GameActivity_createFunc(GameActivity* activity, void* savedState,
|
||||
* "android.app.func_name" string meta-data in your manifest to use a different
|
||||
* function.
|
||||
*/
|
||||
extern GameActivity_createFunc GameActivity_onCreate_C;
|
||||
extern GameActivity_createFunc GameActivity_onCreate;
|
||||
|
||||
/**
|
||||
* Finish the given activity. Its finish() method will be called, causing it
|
||||
@@ -337,186 +330,186 @@ void GameActivity_finish(GameActivity* activity);
|
||||
* as per the Java API at android.view.WindowManager.LayoutParams.
|
||||
*/
|
||||
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
|
||||
* independently, or in combination with {@link
|
||||
* GAMEACTIVITY_FLAG_KEEP_SCREEN_ON} and/or {@link
|
||||
* GAMEACTIVITY_FLAG_SHOW_WHEN_LOCKED}
|
||||
*/
|
||||
GAMEACTIVITY_FLAG_ALLOW_LOCK_WHILE_SCREEN_ON = 0x00000001,
|
||||
/** Everything behind this window will be dimmed. */
|
||||
GAMEACTIVITY_FLAG_DIM_BEHIND = 0x00000002,
|
||||
/**
|
||||
* Blur everything behind this window.
|
||||
* @deprecated Blurring is no longer supported.
|
||||
*/
|
||||
GAMEACTIVITY_FLAG_BLUR_BEHIND = 0x00000004,
|
||||
/**
|
||||
* This window won't ever get key input focus, so the
|
||||
* user can not send key or other button events to it. Those will
|
||||
* instead go to whatever focusable window is behind it. This flag
|
||||
* will also enable {@link GAMEACTIVITY_FLAG_NOT_TOUCH_MODAL} whether or not
|
||||
* that is explicitly set.
|
||||
*
|
||||
* Setting this flag also implies that the window will not need to
|
||||
* interact with
|
||||
* a soft input method, so it will be Z-ordered and positioned
|
||||
* independently of any active input method (typically this means it
|
||||
* gets Z-ordered on top of the input method, so it can use the full
|
||||
* screen for its content and cover the input method if needed. You
|
||||
* can use {@link GAMEACTIVITY_FLAG_ALT_FOCUSABLE_IM} to modify this
|
||||
* behavior.
|
||||
*/
|
||||
GAMEACTIVITY_FLAG_NOT_FOCUSABLE = 0x00000008,
|
||||
/** This window can never receive touch events. */
|
||||
GAMEACTIVITY_FLAG_NOT_TOUCHABLE = 0x00000010,
|
||||
/**
|
||||
* Even when this window is focusable (its
|
||||
* {@link GAMEACTIVITY_FLAG_NOT_FOCUSABLE} is not set), allow any pointer
|
||||
* events outside of the window to be sent to the windows behind it.
|
||||
* Otherwise it will consume all pointer events itself, regardless of
|
||||
* whether they are inside of the window.
|
||||
*/
|
||||
GAMEACTIVITY_FLAG_NOT_TOUCH_MODAL = 0x00000020,
|
||||
/**
|
||||
* When set, if the device is asleep when the touch
|
||||
* screen is pressed, you will receive this first touch event. Usually
|
||||
* the first touch event is consumed by the system since the user can
|
||||
* not see what they are pressing on.
|
||||
*
|
||||
* @deprecated This flag has no effect.
|
||||
*/
|
||||
GAMEACTIVITY_FLAG_TOUCHABLE_WHEN_WAKING = 0x00000040,
|
||||
/**
|
||||
* As long as this window is visible to the user, keep
|
||||
* the device's screen turned on and bright.
|
||||
*/
|
||||
GAMEACTIVITY_FLAG_KEEP_SCREEN_ON = 0x00000080,
|
||||
/**
|
||||
* Place the window within the entire screen, ignoring
|
||||
* decorations around the border (such as the status bar). The
|
||||
* window must correctly position its contents to take the screen
|
||||
* decoration into account.
|
||||
*/
|
||||
GAMEACTIVITY_FLAG_LAYOUT_IN_SCREEN = 0x00000100,
|
||||
/** Allows the window to extend outside of the screen. */
|
||||
GAMEACTIVITY_FLAG_LAYOUT_NO_LIMITS = 0x00000200,
|
||||
/**
|
||||
* Hide all screen decorations (such as the status
|
||||
* bar) while this window is displayed. This allows the window to
|
||||
* use the entire display space for itself -- the status bar will
|
||||
* be hidden when an app window with this flag set is on the top
|
||||
* layer. A fullscreen window will ignore a value of {@link
|
||||
* GAMEACTIVITY_SOFT_INPUT_ADJUST_RESIZE}; the window will stay
|
||||
* fullscreen and will not resize.
|
||||
*/
|
||||
GAMEACTIVITY_FLAG_FULLSCREEN = 0x00000400,
|
||||
/**
|
||||
* Override {@link GAMEACTIVITY_FLAG_FULLSCREEN} and force the
|
||||
* screen decorations (such as the status bar) to be shown.
|
||||
*/
|
||||
GAMEACTIVITY_FLAG_FORCE_NOT_FULLSCREEN = 0x00000800,
|
||||
/**
|
||||
* Turn on dithering when compositing this window to
|
||||
* the screen.
|
||||
* @deprecated This flag is no longer used.
|
||||
*/
|
||||
GAMEACTIVITY_FLAG_DITHER = 0x00001000,
|
||||
/**
|
||||
* Treat the content of the window as secure, preventing
|
||||
* it from appearing in screenshots or from being viewed on non-secure
|
||||
* displays.
|
||||
*/
|
||||
GAMEACTIVITY_FLAG_SECURE = 0x00002000,
|
||||
/**
|
||||
* A special mode where the layout parameters are used
|
||||
* to perform scaling of the surface when it is composited to the
|
||||
* screen.
|
||||
*/
|
||||
GAMEACTIVITY_FLAG_SCALED = 0x00004000,
|
||||
/**
|
||||
* Intended for windows that will often be used when the user is
|
||||
* holding the screen against their face, it will aggressively
|
||||
* filter the event stream to prevent unintended presses in this
|
||||
* situation that may not be desired for a particular window, when
|
||||
* such an event stream is detected, the application will receive
|
||||
* a {@link AMOTION_EVENT_ACTION_CANCEL} to indicate this so
|
||||
* applications can handle this accordingly by taking no action on
|
||||
* the event until the finger is released.
|
||||
*/
|
||||
GAMEACTIVITY_FLAG_IGNORE_CHEEK_PRESSES = 0x00008000,
|
||||
/**
|
||||
* A special option only for use in combination with
|
||||
* {@link GAMEACTIVITY_FLAG_LAYOUT_IN_SCREEN}. When requesting layout in
|
||||
* the screen your window may appear on top of or behind screen decorations
|
||||
* such as the status bar. By also including this flag, the window
|
||||
* manager will report the inset rectangle needed to ensure your
|
||||
* content is not covered by screen decorations.
|
||||
*/
|
||||
GAMEACTIVITY_FLAG_LAYOUT_INSET_DECOR = 0x00010000,
|
||||
/**
|
||||
* Invert the state of {@link GAMEACTIVITY_FLAG_NOT_FOCUSABLE} with
|
||||
* respect to how this window interacts with the current method.
|
||||
* That is, if FLAG_NOT_FOCUSABLE is set and this flag is set,
|
||||
* then the window will behave as if it needs to interact with the
|
||||
* input method and thus be placed behind/away from it; if {@link
|
||||
* GAMEACTIVITY_FLAG_NOT_FOCUSABLE} is not set and this flag is set,
|
||||
* then the window will behave as if it doesn't need to interact
|
||||
* with the input method and can be placed to use more space and
|
||||
* cover the input method.
|
||||
*/
|
||||
GAMEACTIVITY_FLAG_ALT_FOCUSABLE_IM = 0x00020000,
|
||||
/**
|
||||
* If you have set {@link GAMEACTIVITY_FLAG_NOT_TOUCH_MODAL}, you
|
||||
* can set this flag to receive a single special MotionEvent with
|
||||
* the action
|
||||
* {@link AMOTION_EVENT_ACTION_OUTSIDE} for
|
||||
* touches that occur outside of your window. Note that you will not
|
||||
* receive the full down/move/up gesture, only the location of the
|
||||
* first down as an {@link AMOTION_EVENT_ACTION_OUTSIDE}.
|
||||
*/
|
||||
GAMEACTIVITY_FLAG_WATCH_OUTSIDE_TOUCH = 0x00040000,
|
||||
/**
|
||||
* Special flag to let windows be shown when the screen
|
||||
* is locked. This will let application windows take precedence over
|
||||
* key guard or any other lock screens. Can be used with
|
||||
* {@link GAMEACTIVITY_FLAG_KEEP_SCREEN_ON} to turn screen on and display
|
||||
* windows directly before showing the key guard window. Can be used with
|
||||
* {@link GAMEACTIVITY_FLAG_DISMISS_KEYGUARD} to automatically fully
|
||||
* dismisss non-secure keyguards. This flag only applies to the top-most
|
||||
* full-screen window.
|
||||
*/
|
||||
GAMEACTIVITY_FLAG_SHOW_WHEN_LOCKED = 0x00080000,
|
||||
/**
|
||||
* Ask that the system wallpaper be shown behind
|
||||
* your window. The window surface must be translucent to be able
|
||||
* to actually see the wallpaper behind it; this flag just ensures
|
||||
* that the wallpaper surface will be there if this window actually
|
||||
* has translucent regions.
|
||||
*/
|
||||
GAMEACTIVITY_FLAG_SHOW_WALLPAPER = 0x00100000,
|
||||
/**
|
||||
* When set as a window is being added or made
|
||||
* visible, once the window has been shown then the system will
|
||||
* poke the power manager's user activity (as if the user had woken
|
||||
* up the device) to turn the screen on.
|
||||
*/
|
||||
GAMEACTIVITY_FLAG_TURN_SCREEN_ON = 0x00200000,
|
||||
/**
|
||||
* When set the window will cause the keyguard to
|
||||
* be dismissed, only if it is not a secure lock keyguard. Because such
|
||||
* a keyguard is not needed for security, it will never re-appear if
|
||||
* the user navigates to another window (in contrast to
|
||||
* {@link GAMEACTIVITY_FLAG_SHOW_WHEN_LOCKED}, which will only temporarily
|
||||
* hide both secure and non-secure keyguards but ensure they reappear
|
||||
* when the user moves to another UI that doesn't hide them).
|
||||
* If the keyguard is currently active and is secure (requires an
|
||||
* unlock pattern) than the user will still need to confirm it before
|
||||
* seeing this window, unless {@link GAMEACTIVITY_FLAG_SHOW_WHEN_LOCKED} has
|
||||
* also been set.
|
||||
*/
|
||||
GAMEACTIVITY_FLAG_DISMISS_KEYGUARD = 0x00400000,
|
||||
/**
|
||||
* 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
|
||||
* independently, or in combination with {@link
|
||||
* GAMEACTIVITY_FLAG_KEEP_SCREEN_ON} and/or {@link
|
||||
* GAMEACTIVITY_FLAG_SHOW_WHEN_LOCKED}
|
||||
*/
|
||||
GAMEACTIVITY_FLAG_ALLOW_LOCK_WHILE_SCREEN_ON = 0x00000001,
|
||||
/** Everything behind this window will be dimmed. */
|
||||
GAMEACTIVITY_FLAG_DIM_BEHIND = 0x00000002,
|
||||
/**
|
||||
* Blur everything behind this window.
|
||||
* @deprecated Blurring is no longer supported.
|
||||
*/
|
||||
GAMEACTIVITY_FLAG_BLUR_BEHIND = 0x00000004,
|
||||
/**
|
||||
* This window won't ever get key input focus, so the
|
||||
* user can not send key or other button events to it. Those will
|
||||
* instead go to whatever focusable window is behind it. This flag
|
||||
* will also enable {@link GAMEACTIVITY_FLAG_NOT_TOUCH_MODAL} whether or not
|
||||
* that is explicitly set.
|
||||
*
|
||||
* Setting this flag also implies that the window will not need to
|
||||
* interact with
|
||||
* a soft input method, so it will be Z-ordered and positioned
|
||||
* independently of any active input method (typically this means it
|
||||
* gets Z-ordered on top of the input method, so it can use the full
|
||||
* screen for its content and cover the input method if needed. You
|
||||
* can use {@link GAMEACTIVITY_FLAG_ALT_FOCUSABLE_IM} to modify this
|
||||
* behavior.
|
||||
*/
|
||||
GAMEACTIVITY_FLAG_NOT_FOCUSABLE = 0x00000008,
|
||||
/** This window can never receive touch events. */
|
||||
GAMEACTIVITY_FLAG_NOT_TOUCHABLE = 0x00000010,
|
||||
/**
|
||||
* Even when this window is focusable (its
|
||||
* {@link GAMEACTIVITY_FLAG_NOT_FOCUSABLE} is not set), allow any pointer
|
||||
* events outside of the window to be sent to the windows behind it.
|
||||
* Otherwise it will consume all pointer events itself, regardless of
|
||||
* whether they are inside of the window.
|
||||
*/
|
||||
GAMEACTIVITY_FLAG_NOT_TOUCH_MODAL = 0x00000020,
|
||||
/**
|
||||
* When set, if the device is asleep when the touch
|
||||
* screen is pressed, you will receive this first touch event. Usually
|
||||
* the first touch event is consumed by the system since the user can
|
||||
* not see what they are pressing on.
|
||||
*
|
||||
* @deprecated This flag has no effect.
|
||||
*/
|
||||
GAMEACTIVITY_FLAG_TOUCHABLE_WHEN_WAKING = 0x00000040,
|
||||
/**
|
||||
* As long as this window is visible to the user, keep
|
||||
* the device's screen turned on and bright.
|
||||
*/
|
||||
GAMEACTIVITY_FLAG_KEEP_SCREEN_ON = 0x00000080,
|
||||
/**
|
||||
* Place the window within the entire screen, ignoring
|
||||
* decorations around the border (such as the status bar). The
|
||||
* window must correctly position its contents to take the screen
|
||||
* decoration into account.
|
||||
*/
|
||||
GAMEACTIVITY_FLAG_LAYOUT_IN_SCREEN = 0x00000100,
|
||||
/** Allows the window to extend outside of the screen. */
|
||||
GAMEACTIVITY_FLAG_LAYOUT_NO_LIMITS = 0x00000200,
|
||||
/**
|
||||
* Hide all screen decorations (such as the status
|
||||
* bar) while this window is displayed. This allows the window to
|
||||
* use the entire display space for itself -- the status bar will
|
||||
* be hidden when an app window with this flag set is on the top
|
||||
* layer. A fullscreen window will ignore a value of {@link
|
||||
* GAMEACTIVITY_SOFT_INPUT_ADJUST_RESIZE}; the window will stay
|
||||
* fullscreen and will not resize.
|
||||
*/
|
||||
GAMEACTIVITY_FLAG_FULLSCREEN = 0x00000400,
|
||||
/**
|
||||
* Override {@link GAMEACTIVITY_FLAG_FULLSCREEN} and force the
|
||||
* screen decorations (such as the status bar) to be shown.
|
||||
*/
|
||||
GAMEACTIVITY_FLAG_FORCE_NOT_FULLSCREEN = 0x00000800,
|
||||
/**
|
||||
* Turn on dithering when compositing this window to
|
||||
* the screen.
|
||||
* @deprecated This flag is no longer used.
|
||||
*/
|
||||
GAMEACTIVITY_FLAG_DITHER = 0x00001000,
|
||||
/**
|
||||
* Treat the content of the window as secure, preventing
|
||||
* it from appearing in screenshots or from being viewed on non-secure
|
||||
* displays.
|
||||
*/
|
||||
GAMEACTIVITY_FLAG_SECURE = 0x00002000,
|
||||
/**
|
||||
* A special mode where the layout parameters are used
|
||||
* to perform scaling of the surface when it is composited to the
|
||||
* screen.
|
||||
*/
|
||||
GAMEACTIVITY_FLAG_SCALED = 0x00004000,
|
||||
/**
|
||||
* Intended for windows that will often be used when the user is
|
||||
* holding the screen against their face, it will aggressively
|
||||
* filter the event stream to prevent unintended presses in this
|
||||
* situation that may not be desired for a particular window, when
|
||||
* such an event stream is detected, the application will receive
|
||||
* a {@link AMOTION_EVENT_ACTION_CANCEL} to indicate this so
|
||||
* applications can handle this accordingly by taking no action on
|
||||
* the event until the finger is released.
|
||||
*/
|
||||
GAMEACTIVITY_FLAG_IGNORE_CHEEK_PRESSES = 0x00008000,
|
||||
/**
|
||||
* A special option only for use in combination with
|
||||
* {@link GAMEACTIVITY_FLAG_LAYOUT_IN_SCREEN}. When requesting layout in
|
||||
* the screen your window may appear on top of or behind screen decorations
|
||||
* such as the status bar. By also including this flag, the window
|
||||
* manager will report the inset rectangle needed to ensure your
|
||||
* content is not covered by screen decorations.
|
||||
*/
|
||||
GAMEACTIVITY_FLAG_LAYOUT_INSET_DECOR = 0x00010000,
|
||||
/**
|
||||
* Invert the state of {@link GAMEACTIVITY_FLAG_NOT_FOCUSABLE} with
|
||||
* respect to how this window interacts with the current method.
|
||||
* That is, if FLAG_NOT_FOCUSABLE is set and this flag is set,
|
||||
* then the window will behave as if it needs to interact with the
|
||||
* input method and thus be placed behind/away from it; if {@link
|
||||
* GAMEACTIVITY_FLAG_NOT_FOCUSABLE} is not set and this flag is set,
|
||||
* then the window will behave as if it doesn't need to interact
|
||||
* with the input method and can be placed to use more space and
|
||||
* cover the input method.
|
||||
*/
|
||||
GAMEACTIVITY_FLAG_ALT_FOCUSABLE_IM = 0x00020000,
|
||||
/**
|
||||
* If you have set {@link GAMEACTIVITY_FLAG_NOT_TOUCH_MODAL}, you
|
||||
* can set this flag to receive a single special MotionEvent with
|
||||
* the action
|
||||
* {@link AMOTION_EVENT_ACTION_OUTSIDE} for
|
||||
* touches that occur outside of your window. Note that you will not
|
||||
* receive the full down/move/up gesture, only the location of the
|
||||
* first down as an {@link AMOTION_EVENT_ACTION_OUTSIDE}.
|
||||
*/
|
||||
GAMEACTIVITY_FLAG_WATCH_OUTSIDE_TOUCH = 0x00040000,
|
||||
/**
|
||||
* Special flag to let windows be shown when the screen
|
||||
* is locked. This will let application windows take precedence over
|
||||
* key guard or any other lock screens. Can be used with
|
||||
* {@link GAMEACTIVITY_FLAG_KEEP_SCREEN_ON} to turn screen on and display
|
||||
* windows directly before showing the key guard window. Can be used with
|
||||
* {@link GAMEACTIVITY_FLAG_DISMISS_KEYGUARD} to automatically fully
|
||||
* dismisss non-secure keyguards. This flag only applies to the top-most
|
||||
* full-screen window.
|
||||
*/
|
||||
GAMEACTIVITY_FLAG_SHOW_WHEN_LOCKED = 0x00080000,
|
||||
/**
|
||||
* Ask that the system wallpaper be shown behind
|
||||
* your window. The window surface must be translucent to be able
|
||||
* to actually see the wallpaper behind it; this flag just ensures
|
||||
* that the wallpaper surface will be there if this window actually
|
||||
* has translucent regions.
|
||||
*/
|
||||
GAMEACTIVITY_FLAG_SHOW_WALLPAPER = 0x00100000,
|
||||
/**
|
||||
* When set as a window is being added or made
|
||||
* visible, once the window has been shown then the system will
|
||||
* poke the power manager's user activity (as if the user had woken
|
||||
* up the device) to turn the screen on.
|
||||
*/
|
||||
GAMEACTIVITY_FLAG_TURN_SCREEN_ON = 0x00200000,
|
||||
/**
|
||||
* When set the window will cause the keyguard to
|
||||
* be dismissed, only if it is not a secure lock keyguard. Because such
|
||||
* a keyguard is not needed for security, it will never re-appear if
|
||||
* the user navigates to another window (in contrast to
|
||||
* {@link GAMEACTIVITY_FLAG_SHOW_WHEN_LOCKED}, which will only temporarily
|
||||
* hide both secure and non-secure keyguards but ensure they reappear
|
||||
* when the user moves to another UI that doesn't hide them).
|
||||
* If the keyguard is currently active and is secure (requires an
|
||||
* unlock pattern) than the user will still need to confirm it before
|
||||
* seeing this window, unless {@link GAMEACTIVITY_FLAG_SHOW_WHEN_LOCKED} has
|
||||
* also been set.
|
||||
*/
|
||||
GAMEACTIVITY_FLAG_DISMISS_KEYGUARD = 0x00400000,
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -529,26 +522,25 @@ enum GameActivitySetWindowFlags : uint32_t {
|
||||
* *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 : uint8_t {
|
||||
/**
|
||||
* Implicit request to show the input window, not as the result
|
||||
* of a direct request by the user.
|
||||
*/
|
||||
GAMEACTIVITY_SHOW_SOFT_INPUT_IMPLICIT = 0x0001,
|
||||
/**
|
||||
* Implicit request to show the input window, not as the result
|
||||
* of a direct request by the user.
|
||||
*/
|
||||
GAMEACTIVITY_SHOW_SOFT_INPUT_IMPLICIT = 0x0001,
|
||||
|
||||
/**
|
||||
* The user has forced the input method open (such as by
|
||||
* long-pressing menu) so it should not be closed until they
|
||||
* explicitly do so.
|
||||
*/
|
||||
GAMEACTIVITY_SHOW_SOFT_INPUT_FORCED = 0x0002,
|
||||
/**
|
||||
* The user has forced the input method open (such as by
|
||||
* long-pressing menu) so it should not be closed until they
|
||||
* explicitly do so.
|
||||
*/
|
||||
GAMEACTIVITY_SHOW_SOFT_INPUT_FORCED = 0x0002,
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -572,16 +564,14 @@ void GameActivity_restartInput(GameActivity* activity);
|
||||
*
|
||||
* 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);
|
||||
|
||||
/**
|
||||
@@ -594,16 +584,16 @@ GameTextInput* GameActivity_getTextInput(const GameActivity* activity);
|
||||
* API for documentation.
|
||||
*/
|
||||
enum GameActivityHideSoftInputFlags : uint16_t {
|
||||
/**
|
||||
* The soft input window should only be hidden if it was not
|
||||
* explicitly shown by the user.
|
||||
*/
|
||||
GAMEACTIVITY_HIDE_SOFT_INPUT_IMPLICIT_ONLY = 0x0001,
|
||||
/**
|
||||
* The soft input window should normally be hidden, unless it was
|
||||
* originally shown with {@link GAMEACTIVITY_SHOW_SOFT_INPUT_FORCED}.
|
||||
*/
|
||||
GAMEACTIVITY_HIDE_SOFT_INPUT_NOT_ALWAYS = 0x0002,
|
||||
/**
|
||||
* The soft input window should only be hidden if it was not
|
||||
* explicitly shown by the user.
|
||||
*/
|
||||
GAMEACTIVITY_HIDE_SOFT_INPUT_IMPLICIT_ONLY = 0x0001,
|
||||
/**
|
||||
* The soft input window should normally be hidden, unless it was
|
||||
* originally shown with {@link GAMEACTIVITY_SHOW_SOFT_INPUT_FORCED}.
|
||||
*/
|
||||
GAMEACTIVITY_HIDE_SOFT_INPUT_NOT_ALWAYS = 0x0002,
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -620,8 +610,7 @@ 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.
|
||||
@@ -636,8 +625,7 @@ bool GameActivity_isSoftwareKeyboardVisible(GameActivity* activity);
|
||||
*
|
||||
* <b>Note:</b> currently only TYPE_NULL AND TYPE_CLASS_NUMBER are supported.
|
||||
*/
|
||||
void GameActivity_setImeEditorInfo(GameActivity* activity,
|
||||
enum GameTextInputType inputType,
|
||||
void GameActivity_setImeEditorInfo(GameActivity* activity, enum GameTextInputType inputType,
|
||||
enum GameTextInputActionType actionId,
|
||||
enum GameTextInputImeOptions imeOptions);
|
||||
|
||||
@@ -693,14 +681,14 @@ int GameActivity_getUIMode(GameActivity* activity);
|
||||
*
|
||||
* 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);
|
||||
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
|
||||
}
|
||||
@@ -708,4 +696,4 @@ int GameActivity_getLocaleVariant(char* dst, size_t dst_size,
|
||||
|
||||
/** @} */
|
||||
|
||||
#endif // ANDROID_GAME_SDK_GAME_ACTIVITY_H
|
||||
#endif // ANDROID_GAME_SDK_GAME_ACTIVITY_H
|
||||
|
||||
@@ -57,29 +57,26 @@ extern "C" {
|
||||
* \see GameActivityMotionEvent
|
||||
*/
|
||||
typedef struct GameActivityPointerAxes {
|
||||
int32_t id;
|
||||
int32_t toolType;
|
||||
float axisValues[GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT];
|
||||
float rawX;
|
||||
float rawY;
|
||||
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;
|
||||
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];
|
||||
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];
|
||||
inline float GameActivityPointerAxes_getY(const GameActivityPointerAxes* pointerInfo) {
|
||||
return pointerInfo->axisValues[AMOTION_EVENT_AXIS_Y];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -121,49 +118,35 @@ void GameActivityPointerAxes_disableAxis(int32_t axis);
|
||||
* @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);
|
||||
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_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_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_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_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_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_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);
|
||||
inline float GameActivityPointerAxes_getOrientation(const GameActivityPointerAxes* pointerInfo) {
|
||||
return GameActivityPointerAxes_getAxisValue(pointerInfo, AMOTION_EVENT_AXIS_ORIENTATION);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -171,7 +154,7 @@ inline float GameActivityPointerAxes_getOrientation(
|
||||
*/
|
||||
#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
|
||||
GAMEACTIVITY_MAX_NUM_POINTERS_IN_MOTION_EVENT_OVERRIDE
|
||||
#else
|
||||
#define GAMEACTIVITY_MAX_NUM_POINTERS_IN_MOTION_EVENT 8
|
||||
#endif
|
||||
@@ -183,95 +166,92 @@ inline float GameActivityPointerAxes_getOrientation(
|
||||
* (see https://developer.android.com/reference/android/view/MotionEvent).
|
||||
*/
|
||||
typedef struct GameActivityMotionEvent {
|
||||
int32_t deviceId;
|
||||
int32_t source;
|
||||
int32_t action;
|
||||
int32_t deviceId;
|
||||
int32_t source;
|
||||
int32_t action;
|
||||
|
||||
int64_t eventTime;
|
||||
int64_t downTime;
|
||||
int64_t eventTime;
|
||||
int64_t downTime;
|
||||
|
||||
int32_t flags;
|
||||
int32_t metaState;
|
||||
int32_t flags;
|
||||
int32_t metaState;
|
||||
|
||||
int32_t actionButton;
|
||||
int32_t buttonState;
|
||||
int32_t classification;
|
||||
int32_t edgeFlags;
|
||||
int32_t actionButton;
|
||||
int32_t buttonState;
|
||||
int32_t classification;
|
||||
int32_t edgeFlags;
|
||||
|
||||
uint32_t pointerCount;
|
||||
GameActivityPointerAxes
|
||||
pointers[GAMEACTIVITY_MAX_NUM_POINTERS_IN_MOTION_EVENT];
|
||||
uint32_t pointerCount;
|
||||
GameActivityPointerAxes pointers[GAMEACTIVITY_MAX_NUM_POINTERS_IN_MOTION_EVENT];
|
||||
|
||||
int historySize;
|
||||
int64_t* historicalEventTimesMillis;
|
||||
int64_t* historicalEventTimesNanos;
|
||||
float* historicalAxisValues;
|
||||
int historySize;
|
||||
int64_t* historicalEventTimesMillis;
|
||||
int64_t* historicalEventTimesNanos;
|
||||
float* historicalAxisValues;
|
||||
|
||||
float precisionX;
|
||||
float precisionY;
|
||||
float precisionX;
|
||||
float precisionY;
|
||||
} GameActivityMotionEvent;
|
||||
|
||||
float GameActivityMotionEvent_getHistoricalAxisValue(
|
||||
const GameActivityMotionEvent* event, int axis, int pointerIndex,
|
||||
int historyPos);
|
||||
float GameActivityMotionEvent_getHistoricalAxisValue(const GameActivityMotionEvent* event, int axis,
|
||||
int pointerIndex, int historyPos);
|
||||
|
||||
inline int GameActivityMotionEvent_getHistorySize(
|
||||
const GameActivityMotionEvent* event) {
|
||||
return event->historySize;
|
||||
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_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_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_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_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_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_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_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_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);
|
||||
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. */
|
||||
@@ -286,21 +266,21 @@ void GameActivityMotionEvent_destroy(GameActivityMotionEvent* c_event);
|
||||
* nanoseconds in this struct.
|
||||
*/
|
||||
typedef struct GameActivityKeyEvent {
|
||||
int32_t deviceId;
|
||||
int32_t source;
|
||||
int32_t action;
|
||||
int32_t deviceId;
|
||||
int32_t source;
|
||||
int32_t action;
|
||||
|
||||
int64_t eventTime;
|
||||
int64_t downTime;
|
||||
int64_t eventTime;
|
||||
int64_t downTime;
|
||||
|
||||
int32_t flags;
|
||||
int32_t metaState;
|
||||
int32_t flags;
|
||||
int32_t metaState;
|
||||
|
||||
int32_t modifiers;
|
||||
int32_t repeatCount;
|
||||
int32_t keyCode;
|
||||
int32_t scanCode;
|
||||
// int32_t unicodeChar;
|
||||
int32_t modifiers;
|
||||
int32_t repeatCount;
|
||||
int32_t keyCode;
|
||||
int32_t scanCode;
|
||||
// int32_t unicodeChar;
|
||||
} GameActivityKeyEvent;
|
||||
|
||||
#ifdef __cplusplus
|
||||
@@ -309,4 +289,4 @@ typedef struct GameActivityKeyEvent {
|
||||
|
||||
/** @} */
|
||||
|
||||
#endif // ANDROID_GAME_SDK_GAME_ACTIVITY_EVENTS_H
|
||||
#endif // ANDROID_GAME_SDK_GAME_ACTIVITY_EVENTS_H
|
||||
|
||||
@@ -25,8 +25,7 @@
|
||||
#ifdef NDEBUG
|
||||
#define ALOGV(...)
|
||||
#else
|
||||
#define ALOGV(...) \
|
||||
__android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__);
|
||||
#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
|
||||
@@ -40,36 +39,30 @@
|
||||
#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))
|
||||
__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)
|
||||
#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__)))
|
||||
#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__))
|
||||
#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)
|
||||
#define SLOGW_IF(cond, ...) \
|
||||
((__predict_false(cond)) ? ((void)__android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__)) \
|
||||
: (void)0)
|
||||
#endif
|
||||
|
||||
/*
|
||||
@@ -106,4 +99,4 @@
|
||||
|
||||
#define LOG_TRACE(...)
|
||||
|
||||
#endif // ANDROID_GAME_SDK_GAME_ACTIVITY_LOG_H_
|
||||
#endif // ANDROID_GAME_SDK_GAME_ACTIVITY_LOG_H_
|
||||
|
||||
@@ -87,54 +87,54 @@ struct android_app;
|
||||
* 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 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;
|
||||
/** 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);
|
||||
/**
|
||||
* Function to call to perform the standard processing of data from
|
||||
* this source.
|
||||
*/
|
||||
void (*process)(struct android_app* app, struct android_poll_source* source);
|
||||
};
|
||||
|
||||
struct android_input_buffer {
|
||||
/**
|
||||
* Pointer to a read-only array of GameActivityMotionEvent.
|
||||
* Only the first motionEventsCount events are valid.
|
||||
*/
|
||||
GameActivityMotionEvent* motionEvents;
|
||||
/**
|
||||
* Pointer to a read-only array of GameActivityMotionEvent.
|
||||
* Only the first motionEventsCount events are valid.
|
||||
*/
|
||||
GameActivityMotionEvent* motionEvents;
|
||||
|
||||
/**
|
||||
* The number of valid motion events in `motionEvents`.
|
||||
*/
|
||||
uint64_t motionEventsCount;
|
||||
/**
|
||||
* The number of valid motion events in `motionEvents`.
|
||||
*/
|
||||
uint64_t motionEventsCount;
|
||||
|
||||
/**
|
||||
* The size of the `motionEvents` buffer.
|
||||
*/
|
||||
uint64_t motionEventsBufferSize;
|
||||
/**
|
||||
* The size of the `motionEvents` buffer.
|
||||
*/
|
||||
uint64_t motionEventsBufferSize;
|
||||
|
||||
/**
|
||||
* Pointer to a read-only array of GameActivityKeyEvent.
|
||||
* Only the first keyEventsCount events are valid.
|
||||
*/
|
||||
GameActivityKeyEvent* keyEvents;
|
||||
/**
|
||||
* Pointer to a read-only array of GameActivityKeyEvent.
|
||||
* Only the first keyEventsCount events are valid.
|
||||
*/
|
||||
GameActivityKeyEvent* keyEvents;
|
||||
|
||||
/**
|
||||
* The number of valid "Key" events in `keyEvents`.
|
||||
*/
|
||||
uint64_t keyEventsCount;
|
||||
/**
|
||||
* The number of valid "Key" events in `keyEvents`.
|
||||
*/
|
||||
uint64_t keyEventsCount;
|
||||
|
||||
/**
|
||||
* The size of the `keyEvents` buffer.
|
||||
*/
|
||||
uint64_t keyEventsBufferSize;
|
||||
/**
|
||||
* The size of the `keyEvents` buffer.
|
||||
*/
|
||||
uint64_t keyEventsBufferSize;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -164,147 +164,150 @@ typedef bool (*android_motion_event_filter)(const GameActivityMotionEvent*);
|
||||
* Java objects.
|
||||
*/
|
||||
struct android_app {
|
||||
/**
|
||||
* An optional pointer to application-defined state.
|
||||
*/
|
||||
void* userData;
|
||||
/**
|
||||
* An optional pointer to application-defined state.
|
||||
*/
|
||||
void* userData;
|
||||
|
||||
/**
|
||||
* A required callback for processing main app commands (`APP_CMD_*`).
|
||||
* This is called each frame if there are app commands that need processing.
|
||||
*/
|
||||
void (*onAppCmd)(struct android_app* app, int32_t cmd);
|
||||
/**
|
||||
* A required callback for processing main app commands (`APP_CMD_*`).
|
||||
* This is called each frame if there are app commands that need processing.
|
||||
*/
|
||||
void (*onAppCmd)(struct android_app* app, int32_t cmd);
|
||||
|
||||
/** The GameActivity object instance that this app is running in. */
|
||||
GameActivity* activity;
|
||||
/** The GameActivity object instance that this app is running in. */
|
||||
GameActivity* activity;
|
||||
|
||||
/** The current configuration the app is running in. */
|
||||
AConfiguration* config;
|
||||
/** The current configuration the app is running in. */
|
||||
AConfiguration* config;
|
||||
|
||||
/**
|
||||
* The last activity 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;
|
||||
/**
|
||||
* The last activity 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;
|
||||
|
||||
/**
|
||||
* The size of the activity saved state. It is 0 if `savedState` is NULL.
|
||||
*/
|
||||
size_t savedStateSize;
|
||||
/**
|
||||
* The size of the activity saved state. It is 0 if `savedState` is NULL.
|
||||
*/
|
||||
size_t savedStateSize;
|
||||
|
||||
/** The ALooper associated with the app's thread. */
|
||||
ALooper* looper;
|
||||
/** The ALooper associated with the app's thread. */
|
||||
ALooper* looper;
|
||||
|
||||
/** When non-NULL, this is the window surface that the app can draw in. */
|
||||
ANativeWindow* window;
|
||||
/** The ALooper associated with the app's Java main/UI thread. */
|
||||
ALooper* mainLooper;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
/** When non-NULL, this is the window surface that the app can draw in. */
|
||||
ANativeWindow* window;
|
||||
|
||||
/**
|
||||
* Whether the software keyboard is visible or not.
|
||||
*/
|
||||
bool softwareKeyboardVisible;
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
/**
|
||||
* Whether the software keyboard is visible or not.
|
||||
*/
|
||||
bool softwareKeyboardVisible;
|
||||
|
||||
/**
|
||||
* Current state of the app's activity. May be either APP_CMD_START,
|
||||
* APP_CMD_RESUME, APP_CMD_PAUSE, or APP_CMD_STOP.
|
||||
*/
|
||||
int activityState;
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* This is non-zero when the application's GameActivity is being
|
||||
* destroyed and waiting for the app thread to complete.
|
||||
*/
|
||||
int destroyRequested;
|
||||
/**
|
||||
* Current state of the app's activity. May be either APP_CMD_START,
|
||||
* APP_CMD_RESUME, APP_CMD_PAUSE, or APP_CMD_STOP.
|
||||
*/
|
||||
int activityState;
|
||||
|
||||
/**
|
||||
* This is non-zero when the application's GameActivity is being
|
||||
* destroyed and waiting for the app thread to complete.
|
||||
*/
|
||||
int destroyRequested;
|
||||
|
||||
#define NATIVE_APP_GLUE_MAX_INPUT_BUFFERS 2
|
||||
|
||||
/**
|
||||
* This is used for buffering input from GameActivity. Once ready, the
|
||||
* application thread switches the buffers and processes what was
|
||||
* accumulated.
|
||||
*/
|
||||
struct android_input_buffer inputBuffers[NATIVE_APP_GLUE_MAX_INPUT_BUFFERS];
|
||||
/**
|
||||
* This is used for buffering input from GameActivity. Once ready, the
|
||||
* application thread switches the buffers and processes what was
|
||||
* accumulated.
|
||||
*/
|
||||
struct android_input_buffer inputBuffers[NATIVE_APP_GLUE_MAX_INPUT_BUFFERS];
|
||||
|
||||
int currentInputBuffer;
|
||||
int currentInputBuffer;
|
||||
|
||||
/**
|
||||
* 0 if no text input event is outstanding, 1 if it is.
|
||||
* Use `GameActivity_getTextInputState` to get information
|
||||
* about the text entered by the user.
|
||||
*/
|
||||
int textInputState;
|
||||
/**
|
||||
* 0 if no text input event is outstanding, 1 if it is.
|
||||
* Use `GameActivity_getTextInputState` to get information
|
||||
* about the text entered by the user.
|
||||
*/
|
||||
int textInputState;
|
||||
|
||||
// Below are "private" implementation of the glue code.
|
||||
/** @cond INTERNAL */
|
||||
// Below are "private" implementation of the glue code.
|
||||
/** @cond INTERNAL */
|
||||
|
||||
pthread_mutex_t mutex;
|
||||
pthread_cond_t cond;
|
||||
pthread_mutex_t mutex;
|
||||
pthread_cond_t cond;
|
||||
|
||||
int msgread;
|
||||
int msgwrite;
|
||||
int msgread;
|
||||
int msgwrite;
|
||||
|
||||
pthread_t thread;
|
||||
pthread_t thread;
|
||||
|
||||
struct android_poll_source cmdPollSource;
|
||||
struct android_poll_source cmdPollSource;
|
||||
|
||||
int running;
|
||||
int stateSaved;
|
||||
int destroyed;
|
||||
int redrawNeeded;
|
||||
ANativeWindow* pendingWindow;
|
||||
ARect pendingContentRect;
|
||||
int running;
|
||||
int stateSaved;
|
||||
int destroyed;
|
||||
int redrawNeeded;
|
||||
ANativeWindow* pendingWindow;
|
||||
ARect pendingContentRect;
|
||||
|
||||
android_key_event_filter keyEventFilter;
|
||||
android_motion_event_filter motionEventFilter;
|
||||
android_key_event_filter keyEventFilter;
|
||||
android_motion_event_filter motionEventFilter;
|
||||
|
||||
// When new input is received we set both of these flags and use the looper to
|
||||
// wake up the application mainloop.
|
||||
//
|
||||
// To avoid spamming the mainloop with wake ups from lots of input though we
|
||||
// don't sent a wake up if the inputSwapPending flag is already set. (i.e.
|
||||
// we already expect input to be processed in a finite amount of time due to
|
||||
// our previous wake up)
|
||||
//
|
||||
// When a wake up is received then we will check this flag (clearing it
|
||||
// at the same time). If it was set then an InputAvailable event is sent to
|
||||
// the application - which should lead to all input being processed within
|
||||
// a finite amount of time.
|
||||
//
|
||||
// The next time android_app_swap_input_buffers is called, both flags will be
|
||||
// cleared.
|
||||
//
|
||||
// NB: both of these should only be read with the app mutex held
|
||||
bool inputAvailableWakeUp;
|
||||
bool inputSwapPending;
|
||||
// When new input is received we set both of these flags and use the looper to
|
||||
// wake up the application mainloop.
|
||||
//
|
||||
// To avoid spamming the mainloop with wake ups from lots of input though we
|
||||
// don't sent a wake up if the inputSwapPending flag is already set. (i.e.
|
||||
// we already expect input to be processed in a finite amount of time due to
|
||||
// our previous wake up)
|
||||
//
|
||||
// When a wake up is received then we will check this flag (clearing it
|
||||
// at the same time). If it was set then an InputAvailable event is sent to
|
||||
// the application - which should lead to all input being processed within
|
||||
// a finite amount of time.
|
||||
//
|
||||
// The next time android_app_swap_input_buffers is called, both flags will be
|
||||
// cleared.
|
||||
//
|
||||
// NB: both of these should only be read with the app mutex held
|
||||
bool inputAvailableWakeUp;
|
||||
bool inputSwapPending;
|
||||
|
||||
/** @endcond */
|
||||
/** @endcond */
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -312,25 +315,25 @@ struct android_app {
|
||||
* user-defined sources.
|
||||
*/
|
||||
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
|
||||
* 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 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,
|
||||
|
||||
/**
|
||||
* Unused. Reserved for future use when usage of AInputQueue will be
|
||||
* supported.
|
||||
*/
|
||||
LOOPER_ID_INPUT = 2,
|
||||
/**
|
||||
* Unused. Reserved for future use when usage of AInputQueue will be
|
||||
* supported.
|
||||
*/
|
||||
LOOPER_ID_INPUT = 2,
|
||||
|
||||
/**
|
||||
* Start of user-defined ALooper identifiers.
|
||||
*/
|
||||
LOOPER_ID_USER = 3,
|
||||
/**
|
||||
* Start of user-defined ALooper identifiers.
|
||||
*/
|
||||
LOOPER_ID_USER = 3,
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -340,134 +343,134 @@ enum NativeAppGlueLooperId : int8_t {
|
||||
* can be used for custom user's events.
|
||||
*/
|
||||
enum NativeAppGlueAppCmd : int8_t {
|
||||
/**
|
||||
* Unused. Reserved for future use when usage of AInputQueue will be
|
||||
* supported.
|
||||
*/
|
||||
UNUSED_APP_CMD_INPUT_CHANGED,
|
||||
/**
|
||||
* Unused. Reserved for future use when usage of AInputQueue will be
|
||||
* supported.
|
||||
*/
|
||||
UNUSED_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: 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 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 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 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 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 software keyboard was shown or hidden.
|
||||
*/
|
||||
APP_CMD_SOFTWARE_KB_VIS_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.
|
||||
*/
|
||||
APP_CMD_GAINED_FOCUS,
|
||||
/**
|
||||
* 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 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 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 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 started.
|
||||
*/
|
||||
APP_CMD_START,
|
||||
|
||||
/**
|
||||
* Command from main thread: the app's activity has been resumed.
|
||||
*/
|
||||
APP_CMD_RESUME,
|
||||
/**
|
||||
* 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 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 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 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,
|
||||
/**
|
||||
* 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,
|
||||
|
||||
/**
|
||||
* Command from main thread: the app's insets have changed.
|
||||
*/
|
||||
APP_CMD_WINDOW_INSETS_CHANGED,
|
||||
/**
|
||||
* Command from main thread: the app's insets have changed.
|
||||
*/
|
||||
APP_CMD_WINDOW_INSETS_CHANGED,
|
||||
|
||||
/**
|
||||
* Command from main thread: an editor action has been triggered.
|
||||
*/
|
||||
//APP_CMD_EDITOR_ACTION,
|
||||
/**
|
||||
* 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 keyboard event has been received.
|
||||
*/
|
||||
// APP_CMD_KEY_EVENT,
|
||||
|
||||
/**
|
||||
* Command from main thread: a touch event has been received.
|
||||
*/
|
||||
//APP_CMD_TOUCH_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);
|
||||
@@ -490,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
|
||||
@@ -524,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.
|
||||
@@ -546,8 +547,10 @@ void android_app_set_motion_event_filter(struct android_app* app,
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
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);
|
||||
|
||||
/**
|
||||
* Determines if a looper wake up was due to new input becoming available
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
../../../../../../game-text-input/prefab-src/modules/game-text-input/include/game-text-input/gametextinput.h
|
||||
@@ -1 +0,0 @@
|
||||
../../../../../../src/common/system_utils.cpp
|
||||
@@ -24,312 +24,281 @@
|
||||
#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};
|
||||
/* 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;
|
||||
}
|
||||
if (axis < 0 || axis >= GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT) {
|
||||
return;
|
||||
}
|
||||
|
||||
enabledAxes[axis] = true;
|
||||
enabledAxes[axis] = true;
|
||||
}
|
||||
|
||||
float GameActivityPointerAxes_getAxisValue(
|
||||
const GameActivityPointerAxes *pointerInfo, int32_t axis) {
|
||||
if (axis < 0 || axis >= GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT) {
|
||||
return 0;
|
||||
}
|
||||
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;
|
||||
}
|
||||
if (!enabledAxes[axis]) {
|
||||
ALOGW("Axis %d must be enabled before it can be accessed.", axis);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return pointerInfo->axisValues[axis];
|
||||
return pointerInfo->axisValues[axis];
|
||||
}
|
||||
|
||||
extern "C" void GameActivityPointerAxes_disableAxis(int32_t axis) {
|
||||
if (axis < 0 || axis >= GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT) {
|
||||
return;
|
||||
}
|
||||
if (axis < 0 || axis >= GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT) {
|
||||
return;
|
||||
}
|
||||
|
||||
enabledAxes[axis] = false;
|
||||
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;
|
||||
}
|
||||
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];
|
||||
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 getDeviceId;
|
||||
jmethodID getSource;
|
||||
jmethodID getAction;
|
||||
|
||||
jmethodID getEventTime;
|
||||
jmethodID getDownTime;
|
||||
jmethodID getEventTime;
|
||||
jmethodID getDownTime;
|
||||
|
||||
jmethodID getFlags;
|
||||
jmethodID getMetaState;
|
||||
jmethodID getFlags;
|
||||
jmethodID getMetaState;
|
||||
|
||||
jmethodID getActionButton;
|
||||
jmethodID getButtonState;
|
||||
jmethodID getClassification;
|
||||
jmethodID getEdgeFlags;
|
||||
jmethodID getActionButton;
|
||||
jmethodID getButtonState;
|
||||
jmethodID getClassification;
|
||||
jmethodID getEdgeFlags;
|
||||
|
||||
jmethodID getHistorySize;
|
||||
jmethodID getHistoricalEventTime;
|
||||
jmethodID getHistorySize;
|
||||
jmethodID getHistoricalEventTime;
|
||||
|
||||
jmethodID getPointerCount;
|
||||
jmethodID getPointerId;
|
||||
jmethodID getPointerCount;
|
||||
jmethodID getPointerId;
|
||||
|
||||
jmethodID getToolType;
|
||||
jmethodID getToolType;
|
||||
|
||||
jmethodID getRawX;
|
||||
jmethodID getRawY;
|
||||
jmethodID getXPrecision;
|
||||
jmethodID getYPrecision;
|
||||
jmethodID getAxisValue;
|
||||
jmethodID getRawX;
|
||||
jmethodID getRawY;
|
||||
jmethodID getXPrecision;
|
||||
jmethodID getYPrecision;
|
||||
jmethodID getAxisValue;
|
||||
|
||||
jmethodID getHistoricalAxisValue;
|
||||
jmethodID getHistoricalAxisValue;
|
||||
} gMotionEventClassInfo;
|
||||
|
||||
extern "C" void GameActivityMotionEvent_destroy(
|
||||
GameActivityMotionEvent *c_event) {
|
||||
delete c_event->historicalAxisValues;
|
||||
delete c_event->historicalEventTimesMillis;
|
||||
delete c_event->historicalEventTimesNanos;
|
||||
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);
|
||||
}
|
||||
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");
|
||||
|
||||
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];
|
||||
gMotionEventClassInfo.getHistorySize =
|
||||
env->GetMethodID(motionEventClass, "getHistorySize", "()I");
|
||||
gMotionEventClassInfo.getHistoricalEventTime =
|
||||
env->GetMethodID(motionEventClass, "getHistoricalEventTime", "(I)J");
|
||||
|
||||
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;
|
||||
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) {
|
||||
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);
|
||||
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 getDeviceId;
|
||||
jmethodID getSource;
|
||||
jmethodID getAction;
|
||||
|
||||
jmethodID getEventTime;
|
||||
jmethodID getDownTime;
|
||||
jmethodID getEventTime;
|
||||
jmethodID getDownTime;
|
||||
|
||||
jmethodID getFlags;
|
||||
jmethodID getMetaState;
|
||||
jmethodID getFlags;
|
||||
jmethodID getMetaState;
|
||||
|
||||
jmethodID getModifiers;
|
||||
jmethodID getRepeatCount;
|
||||
jmethodID getKeyCode;
|
||||
jmethodID getScanCode;
|
||||
// jmethodID getUnicodeChar;
|
||||
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");
|
||||
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 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);
|
||||
extern "C" void GameActivityEventsInit(JNIEnv* env) {
|
||||
initMotionEvents(env);
|
||||
initKeyEvents(env);
|
||||
}
|
||||
|
||||
@@ -53,8 +53,8 @@ void GameActivityEventsInit(JNIEnv* env);
|
||||
* to avoid extra JNI calls.
|
||||
*/
|
||||
void GameActivityMotionEvent_fromJava(JNIEnv* env, jobject motionEvent,
|
||||
GameActivityMotionEvent* out_event,
|
||||
int pointerCount, int historySize);
|
||||
GameActivityMotionEvent* out_event, int pointerCount,
|
||||
int historySize);
|
||||
|
||||
/**
|
||||
* \brief Convert a Java `KeyEvent` to a `GameActivityKeyEvent`.
|
||||
@@ -74,4 +74,4 @@ void GameActivityKeyEvent_fromJava(JNIEnv* env, jobject motionEvent,
|
||||
|
||||
/** @} */
|
||||
|
||||
#endif // ANDROID_GAME_SDK_GAME_ACTIVITY_EVENTS_INTERNAL_H
|
||||
#endif // ANDROID_GAME_SDK_GAME_ACTIVITY_EVENTS_INTERNAL_H
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
../../../../../../game-text-input/prefab-src/modules/game-text-input/src/game-text-input/gametextinput.cpp
|
||||
@@ -1 +0,0 @@
|
||||
../../../../../../game-text-input/prefab-src/modules/game-text-input/include/game-text-input/gametextinput.h
|
||||
@@ -1 +0,0 @@
|
||||
../../../../../include/common/
|
||||
@@ -31,65 +31,64 @@ static constexpr int32_t DEFAULT_MAX_STRING_SIZE = 1 << 16;
|
||||
|
||||
// Cache of field ids in the Java GameTextInputState class
|
||||
struct StateClassInfo {
|
||||
jfieldID text;
|
||||
jfieldID selectionStart;
|
||||
jfieldID selectionEnd;
|
||||
jfieldID composingRegionStart;
|
||||
jfieldID composingRegionEnd;
|
||||
jfieldID text;
|
||||
jfieldID selectionStart;
|
||||
jfieldID selectionEnd;
|
||||
jfieldID composingRegionStart;
|
||||
jfieldID composingRegionEnd;
|
||||
};
|
||||
|
||||
// Main GameTextInput object.
|
||||
struct GameTextInput {
|
||||
public:
|
||||
GameTextInput(JNIEnv *env, uint32_t max_string_size);
|
||||
~GameTextInput();
|
||||
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 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_; }
|
||||
public:
|
||||
GameTextInput(JNIEnv* env, uint32_t max_string_size);
|
||||
~GameTextInput();
|
||||
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 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:
|
||||
// Copy string and set other fields
|
||||
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_;
|
||||
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;
|
||||
StateClassInfo stateClassInfo_ = {};
|
||||
// Constant-sized buffer used to store state text.
|
||||
std::vector<char> stateStringBuffer_;
|
||||
private:
|
||||
// Copy string and set other fields
|
||||
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_;
|
||||
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;
|
||||
StateClassInfo stateClassInfo_ = {};
|
||||
// Constant-sized buffer used to store state text.
|
||||
std::vector<char> stateStringBuffer_;
|
||||
};
|
||||
|
||||
std::unique_ptr<GameTextInput> s_gameTextInput;
|
||||
@@ -101,286 +100,257 @@ extern "C" {
|
||||
///////////////////////////////////////////////////////////
|
||||
|
||||
// Convert to a Java structure.
|
||||
jobject currentState_toJava(const GameTextInput *gameTextInput,
|
||||
const GameTextInputState *state) {
|
||||
if (state == nullptr) return NULL;
|
||||
return gameTextInput->stateToJava(*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) {
|
||||
gameTextInput->stateFromJava(textInputEvent, callback, context);
|
||||
void currentState_fromJava(const GameTextInput* gameTextInput, jobject textInputEvent,
|
||||
GameTextInputGetStateCallback callback, void* context) {
|
||||
gameTextInput->stateFromJava(textInputEvent, callback, context);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////
|
||||
/// GameTextInput C Functions
|
||||
///////////////////////////////////////////////////////////
|
||||
|
||||
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 "
|
||||
"calling GameTextInput_destroy");
|
||||
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 "
|
||||
"calling GameTextInput_destroy");
|
||||
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));
|
||||
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));
|
||||
return s_gameTextInput.get();
|
||||
}
|
||||
|
||||
void GameTextInput_destroy(GameTextInput *input) {
|
||||
if (input == nullptr || s_gameTextInput.get() == nullptr) return;
|
||||
s_gameTextInput.reset();
|
||||
void GameTextInput_destroy(GameTextInput* input) {
|
||||
if (input == nullptr || s_gameTextInput.get() == nullptr) return;
|
||||
s_gameTextInput.reset();
|
||||
}
|
||||
|
||||
void GameTextInput_setState(GameTextInput *input,
|
||||
const GameTextInputState *state) {
|
||||
if (state == nullptr) return;
|
||||
input->setState(*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) {
|
||||
GameTextInputState state = input->getState();
|
||||
callback(context, &state);
|
||||
void GameTextInput_getState(GameTextInput* input, GameTextInputGetStateCallback callback,
|
||||
void* context) {
|
||||
GameTextInputState state = input->getState();
|
||||
callback(context, &state);
|
||||
}
|
||||
|
||||
void GameTextInput_setInputConnection(GameTextInput *input,
|
||||
jobject inputConnection) {
|
||||
input->setInputConnection(inputConnection);
|
||||
void GameTextInput_setInputConnection(GameTextInput* input, jobject inputConnection) {
|
||||
input->setInputConnection(inputConnection);
|
||||
}
|
||||
|
||||
void GameTextInput_processEvent(GameTextInput *input, jobject textInputEvent) {
|
||||
input->processEvent(textInputEvent);
|
||||
void GameTextInput_processEvent(GameTextInput* input, jobject textInputEvent) {
|
||||
input->processEvent(textInputEvent);
|
||||
}
|
||||
|
||||
void GameTextInput_processImeInsets(GameTextInput *input, const ARect *insets) {
|
||||
input->processImeInsets(insets);
|
||||
void GameTextInput_processImeInsets(GameTextInput* input, const ARect* insets) {
|
||||
input->processImeInsets(insets);
|
||||
}
|
||||
|
||||
void GameTextInput_showIme(struct GameTextInput *input, uint32_t flags) {
|
||||
input->showIme(flags);
|
||||
void GameTextInput_showIme(struct GameTextInput* input, uint32_t flags) {
|
||||
input->showIme(flags);
|
||||
}
|
||||
|
||||
void GameTextInput_hideIme(struct GameTextInput *input, uint32_t flags) {
|
||||
input->hideIme(flags);
|
||||
void GameTextInput_hideIme(struct GameTextInput* input, uint32_t flags) {
|
||||
input->hideIme(flags);
|
||||
}
|
||||
|
||||
void GameTextInput_restartInput(struct GameTextInput *input) {
|
||||
input->restartInput();
|
||||
void GameTextInput_restartInput(struct GameTextInput* input) {
|
||||
input->restartInput();
|
||||
}
|
||||
|
||||
void GameTextInput_setEventCallback(struct GameTextInput *input,
|
||||
GameTextInputEventCallback callback,
|
||||
void *context) {
|
||||
input->setEventCallback(callback, context);
|
||||
void GameTextInput_setEventCallback(struct GameTextInput* input,
|
||||
GameTextInputEventCallback callback, void* context) {
|
||||
input->setEventCallback(callback, context);
|
||||
}
|
||||
|
||||
void GameTextInput_setImeInsetsCallback(struct GameTextInput *input,
|
||||
GameTextInputImeInsetsCallback callback,
|
||||
void *context) {
|
||||
input->setImeInsetsCallback(callback, context);
|
||||
void GameTextInput_setImeInsetsCallback(struct GameTextInput* input,
|
||||
GameTextInputImeInsetsCallback callback, void* context) {
|
||||
input->setImeInsetsCallback(callback, context);
|
||||
}
|
||||
|
||||
void GameTextInput_getImeInsets(const GameTextInput *input, ARect *insets) {
|
||||
*insets = input->getImeInsets();
|
||||
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) {
|
||||
stateJavaClass_ = (jclass)env_->NewGlobalRef(
|
||||
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");
|
||||
restartInputMethod_ =
|
||||
env_->GetMethodID(inputConnectionClass_, "restartInput", "()V");
|
||||
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"));
|
||||
inputConnectionSetStateMethod_ =
|
||||
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_.composingRegionStart =
|
||||
env_->GetFieldID(stateJavaClass_, "composingRegionStart", "I");
|
||||
stateClassInfo_.composingRegionEnd =
|
||||
env_->GetFieldID(stateJavaClass_, "composingRegionEnd", "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");
|
||||
stateClassInfo_.composingRegionEnd =
|
||||
env_->GetFieldID(stateJavaClass_, "composingRegionEnd", "I");
|
||||
}
|
||||
|
||||
GameTextInput::~GameTextInput() {
|
||||
if (stateJavaClass_ != NULL) {
|
||||
env_->DeleteGlobalRef(stateJavaClass_);
|
||||
stateJavaClass_ = NULL;
|
||||
}
|
||||
if (inputConnectionClass_ != NULL) {
|
||||
env_->DeleteGlobalRef(inputConnectionClass_);
|
||||
inputConnectionClass_ = NULL;
|
||||
}
|
||||
if (inputConnection_ != NULL) {
|
||||
env_->DeleteGlobalRef(inputConnection_);
|
||||
inputConnection_ = NULL;
|
||||
}
|
||||
if (stateJavaClass_ != NULL) {
|
||||
env_->DeleteGlobalRef(stateJavaClass_);
|
||||
stateJavaClass_ = NULL;
|
||||
}
|
||||
if (inputConnectionClass_ != NULL) {
|
||||
env_->DeleteGlobalRef(inputConnectionClass_);
|
||||
inputConnectionClass_ = NULL;
|
||||
}
|
||||
if (inputConnection_ != NULL) {
|
||||
env_->DeleteGlobalRef(inputConnection_);
|
||||
inputConnection_ = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void GameTextInput::setState(const GameTextInputState &state) {
|
||||
if (inputConnection_ == nullptr) return;
|
||||
jobject jstate = stateToJava(state);
|
||||
env_->CallVoidMethod(inputConnection_, inputConnectionSetStateMethod_,
|
||||
jstate);
|
||||
env_->DeleteLocalRef(jstate);
|
||||
setStateInner(state);
|
||||
void GameTextInput::setState(const GameTextInputState& state) {
|
||||
if (inputConnection_ == nullptr) return;
|
||||
jobject jstate = stateToJava(state);
|
||||
env_->CallVoidMethod(inputConnection_, inputConnectionSetStateMethod_, jstate);
|
||||
env_->DeleteLocalRef(jstate);
|
||||
setStateInner(state);
|
||||
}
|
||||
|
||||
void GameTextInput::setStateInner(const GameTextInputState &state) {
|
||||
std::lock_guard<std::mutex> lock(currentStateMutex_);
|
||||
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) {
|
||||
currentState_ = 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()));
|
||||
currentState_.text_UTF8 = 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;
|
||||
stateStringBuffer_[bytes_needed - 1] = 0;
|
||||
// Check if we're setting using our own string (other parts may be
|
||||
// different)
|
||||
if (state.text_UTF8 == currentState_.text_UTF8) {
|
||||
currentState_ = 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()));
|
||||
currentState_.text_UTF8 = 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;
|
||||
stateStringBuffer_[bytes_needed - 1] = 0;
|
||||
}
|
||||
|
||||
void GameTextInput::setInputConnection(jobject inputConnection) {
|
||||
if (inputConnection_ != NULL) {
|
||||
env_->DeleteGlobalRef(inputConnection_);
|
||||
}
|
||||
inputConnection_ = env_->NewGlobalRef(inputConnection);
|
||||
if (inputConnection_ != NULL) {
|
||||
env_->DeleteGlobalRef(inputConnection_);
|
||||
}
|
||||
inputConnection_ = env_->NewGlobalRef(inputConnection);
|
||||
}
|
||||
|
||||
/*static*/ void GameTextInput::processCallback(
|
||||
void *context, const GameTextInputState *state) {
|
||||
auto thiz = static_cast<GameTextInput *>(context);
|
||||
if (state != nullptr) thiz->setStateInner(*state);
|
||||
/*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_);
|
||||
}
|
||||
stateFromJava(textInputEvent, processCallback, this);
|
||||
if (eventCallback_) {
|
||||
std::lock_guard<std::mutex> lock(currentStateMutex_);
|
||||
eventCallback_(eventCallbackContext_, ¤tState_);
|
||||
}
|
||||
}
|
||||
|
||||
void GameTextInput::showIme(uint32_t flags) {
|
||||
if (inputConnection_ == nullptr) return;
|
||||
env_->CallVoidMethod(inputConnection_, setSoftKeyboardActiveMethod_, true,
|
||||
static_cast<jint>(flags));
|
||||
if (inputConnection_ == nullptr) return;
|
||||
env_->CallVoidMethod(inputConnection_, setSoftKeyboardActiveMethod_, true,
|
||||
static_cast<jint>(flags));
|
||||
}
|
||||
|
||||
void GameTextInput::setEventCallback(GameTextInputEventCallback callback,
|
||||
void *context) {
|
||||
eventCallback_ = callback;
|
||||
eventCallbackContext_ = context;
|
||||
void GameTextInput::setEventCallback(GameTextInputEventCallback callback, void* context) {
|
||||
eventCallback_ = callback;
|
||||
eventCallbackContext_ = context;
|
||||
}
|
||||
|
||||
void GameTextInput::setImeInsetsCallback(
|
||||
GameTextInputImeInsetsCallback callback, void *context) {
|
||||
insetsCallback_ = callback;
|
||||
insetsCallbackContext_ = context;
|
||||
void GameTextInput::setImeInsetsCallback(GameTextInputImeInsetsCallback callback, void* context) {
|
||||
insetsCallback_ = callback;
|
||||
insetsCallbackContext_ = context;
|
||||
}
|
||||
|
||||
void GameTextInput::processImeInsets(const ARect *insets) {
|
||||
currentInsets_ = *insets;
|
||||
if (insetsCallback_) {
|
||||
insetsCallback_(insetsCallbackContext_, ¤tInsets_);
|
||||
}
|
||||
void GameTextInput::processImeInsets(const ARect* insets) {
|
||||
currentInsets_ = *insets;
|
||||
if (insetsCallback_) {
|
||||
insetsCallback_(insetsCallbackContext_, ¤tInsets_);
|
||||
}
|
||||
}
|
||||
|
||||
void GameTextInput::hideIme(uint32_t flags) {
|
||||
if (inputConnection_ == nullptr) return;
|
||||
env_->CallVoidMethod(inputConnection_, setSoftKeyboardActiveMethod_, false,
|
||||
static_cast<jint>(flags));
|
||||
if (inputConnection_ == nullptr) return;
|
||||
env_->CallVoidMethod(inputConnection_, setSoftKeyboardActiveMethod_, false,
|
||||
static_cast<jint>(flags));
|
||||
}
|
||||
|
||||
void GameTextInput::restartInput() {
|
||||
if (inputConnection_ == nullptr) return;
|
||||
env_->CallVoidMethod(inputConnection_, restartInputMethod_);
|
||||
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");
|
||||
jobject GameTextInput::stateToJava(const GameTextInputState& state) const {
|
||||
static jmethodID constructor = nullptr;
|
||||
if (constructor == nullptr) {
|
||||
__android_log_print(ANDROID_LOG_ERROR, LOG_TAG,
|
||||
"Can't find gametextinput.State constructor");
|
||||
return nullptr;
|
||||
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;
|
||||
if (text == nullptr) {
|
||||
static char empty_string[] = "";
|
||||
text = empty_string;
|
||||
}
|
||||
// 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);
|
||||
env_->DeleteLocalRef(jtext);
|
||||
return jobj;
|
||||
const char* text = state.text_UTF8;
|
||||
if (text == nullptr) {
|
||||
static char empty_string[] = "";
|
||||
text = empty_string;
|
||||
}
|
||||
// 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);
|
||||
env_->DeleteLocalRef(jtext);
|
||||
return jobj;
|
||||
}
|
||||
|
||||
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);
|
||||
int composingRegionStart =
|
||||
env_->GetIntField(textInputEvent, stateClassInfo_.composingRegionStart);
|
||||
int composingRegionEnd =
|
||||
env_->GetIntField(textInputEvent, stateClassInfo_.composingRegionEnd);
|
||||
GameTextInputState state{text_chars,
|
||||
text_len,
|
||||
{selectionStart, selectionEnd},
|
||||
{composingRegionStart, composingRegionEnd}};
|
||||
callback(context, &state);
|
||||
env_->ReleaseStringUTFChars(text, text_chars);
|
||||
env_->DeleteLocalRef(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);
|
||||
int composingRegionStart =
|
||||
env_->GetIntField(textInputEvent, stateClassInfo_.composingRegionStart);
|
||||
int composingRegionEnd = env_->GetIntField(textInputEvent, stateClassInfo_.composingRegionEnd);
|
||||
GameTextInputState state{text_chars,
|
||||
text_len,
|
||||
{selectionStart, selectionEnd},
|
||||
{composingRegionStart, composingRegionEnd}};
|
||||
callback(context, &state);
|
||||
env_->ReleaseStringUTFChars(text, text_chars);
|
||||
env_->DeleteLocalRef(text);
|
||||
}
|
||||
|
||||
@@ -42,4 +42,14 @@ 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"
|
||||
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"
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
|
||||
// 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))
|
||||
((MAJOR << 16) | (MINOR << 8) | (BUGFIX))
|
||||
// Accessors
|
||||
#define ANDROID_GAMESDK_MAJOR_VERSION(PACKED) ((PACKED) >> 16)
|
||||
#define ANDROID_GAMESDK_MINOR_VERSION(PACKED) (((PACKED) >> 8) & 0xff)
|
||||
@@ -39,4 +39,4 @@
|
||||
|
||||
#define AGDK_STRINGIFY(NUMBER) #NUMBER
|
||||
#define AGDK_STRING_VERSION(MAJOR, MINOR, BUGFIX) \
|
||||
AGDK_STRINGIFY(MAJOR) "." AGDK_STRINGIFY(MINOR) "." AGDK_STRINGIFY(BUGFIX)
|
||||
AGDK_STRINGIFY(MAJOR) "." AGDK_STRINGIFY(MINOR) "." AGDK_STRINGIFY(BUGFIX)
|
||||
|
||||
@@ -23,15 +23,13 @@
|
||||
namespace gamesdk {
|
||||
|
||||
#if __ANDROID_API__ >= 26
|
||||
std::string getSystemPropViaCallback(const char* key,
|
||||
const char* default_value = "") {
|
||||
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*/) {
|
||||
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;
|
||||
@@ -41,9 +39,8 @@ std::string getSystemPropViaCallback(const char* key,
|
||||
return return_value;
|
||||
}
|
||||
#else
|
||||
std::string getSystemPropViaGet(const char* key,
|
||||
const char* default_value = "") {
|
||||
char buffer[PROP_VALUE_MAX + 1] = ""; // +1 for terminator
|
||||
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;
|
||||
@@ -69,4 +66,4 @@ bool GetSystemPropAsBool(const char* key, bool default_value) {
|
||||
return GetSystemPropAsInt(key, default_value) != 0;
|
||||
}
|
||||
|
||||
} // namespace gamesdk
|
||||
} // namespace gamesdk
|
||||
@@ -29,4 +29,4 @@ 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
|
||||
} // namespace gamesdk
|
||||
@@ -9,7 +9,11 @@ fn build_glue_for_game_activity() {
|
||||
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"] {
|
||||
for f in [
|
||||
"GameActivity.cpp",
|
||||
"GameActivityEvents.cpp",
|
||||
"GameActivityEvents_internal.h",
|
||||
] {
|
||||
println!("cargo:rerun-if-changed={}", activity_path("src", f));
|
||||
}
|
||||
|
||||
@@ -17,7 +21,6 @@ fn build_glue_for_game_activity() {
|
||||
"GameActivity.h",
|
||||
"GameActivityEvents.h",
|
||||
"GameActivityLog.h",
|
||||
"GameActivityEvents_internal.h",
|
||||
] {
|
||||
println!("cargo:rerun-if-changed={}", activity_path("include", f));
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ 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.73.0' \
|
||||
--rust-target '1.85.0' \
|
||||
--blocklist-item 'JNI\w+' \
|
||||
--blocklist-item 'C?_?JNIEnv' \
|
||||
--blocklist-item '_?JavaVM' \
|
||||
|
||||
@@ -25,8 +25,10 @@ pub type Result<T> = std::result::Result<T, AppError>;
|
||||
pub(crate) enum InternalAppError {
|
||||
#[error("A JNI error")]
|
||||
JniError(jni::errors::JniError),
|
||||
#[error("A Java Exception was thrown via a JNI method call")]
|
||||
JniException(String),
|
||||
// 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")]
|
||||
@@ -50,7 +52,7 @@ impl From<InternalAppError> for AppError {
|
||||
fn from(value: InternalAppError) -> Self {
|
||||
match value {
|
||||
InternalAppError::JniError(err) => AppError::JavaError(err.to_string()),
|
||||
InternalAppError::JniException(msg) => AppError::JavaError(msg),
|
||||
InternalAppError::JniBadArgument(msg) => AppError::JavaError(msg),
|
||||
InternalAppError::JvmError(err) => AppError::JavaError(err.to_string()),
|
||||
InternalAppError::InputUnavailable => AppError::InputUnavailable,
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
#![allow(deref_nullptr)]
|
||||
#![allow(dead_code)]
|
||||
|
||||
use jni_sys::*;
|
||||
use jni::sys::*;
|
||||
use libc::{pthread_cond_t, pthread_mutex_t, pthread_t};
|
||||
use ndk_sys::{AAssetManager, AConfiguration, ALooper, ALooper_callbackFunc, ANativeWindow, ARect};
|
||||
|
||||
|
||||
@@ -13,7 +13,9 @@
|
||||
// The `Class` was also bound differently to `android-ndk-rs` considering how the class is defined
|
||||
// by masking bits from the `Source`.
|
||||
|
||||
use crate::activity_impl::ffi::{GameActivityKeyEvent, GameActivityMotionEvent};
|
||||
use std::iter::FusedIterator;
|
||||
|
||||
use super::ffi::{self, GameActivityKeyEvent, GameActivityMotionEvent};
|
||||
use crate::input::{
|
||||
Axis, Button, ButtonState, EdgeFlags, KeyAction, KeyEventFlags, Keycode, MetaState,
|
||||
MotionAction, MotionEventFlags, Pointer, PointersIter, Source, ToolType,
|
||||
@@ -141,28 +143,6 @@ impl<'a> MotionEvent<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
/// Returns the size of the history contained in this event.
|
||||
///
|
||||
/// See [the NDK
|
||||
/// docs](https://developer.android.com/ndk/reference/group/input#amotionevent_gethistorysize)
|
||||
#[inline]
|
||||
pub fn history_size(&self) -> usize {
|
||||
unsafe { ndk_sys::AMotionEvent_getHistorySize(self.ga_event.ptr.as_ptr()) as usize }
|
||||
}
|
||||
|
||||
/// An iterator over the historical events contained in this event.
|
||||
#[inline]
|
||||
pub fn history(&self) -> HistoricalMotionEventsIter<'_> {
|
||||
HistoricalMotionEventsIter {
|
||||
event: self.ga_event.ptr,
|
||||
next_history_index: 0,
|
||||
history_size: self.history_size(),
|
||||
_marker: std::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
/// Returns the state of any modifier keys that were pressed during the event.
|
||||
///
|
||||
/// See [the NDK
|
||||
@@ -206,7 +186,7 @@ impl<'a> MotionEvent<'a> {
|
||||
/// docs](https://developer.android.com/ndk/reference/group/input#amotionevent_geteventtime)
|
||||
#[inline]
|
||||
pub fn event_time(&self) -> i64 {
|
||||
self.ga_event.eventTime
|
||||
self.ga_event.eventTime * 1_000_000 // Convert from milliseconds to nanoseconds
|
||||
}
|
||||
|
||||
/// The flags associated with a motion event.
|
||||
@@ -301,6 +281,18 @@ impl PointerImpl<'_> {
|
||||
let tool_type = pointer.toolType as u32;
|
||||
tool_type.into()
|
||||
}
|
||||
|
||||
pub fn history(&self) -> crate::input::PointerHistoryIter<'_> {
|
||||
let history_size = self.event.ga_event.historySize as usize;
|
||||
crate::input::PointerHistoryIter {
|
||||
inner: PointerHistoryIterImpl {
|
||||
event: self.event.ga_event,
|
||||
pointer_index: self.index,
|
||||
front: 0,
|
||||
back: history_size,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An iterator over the pointers in a [`MotionEvent`].
|
||||
@@ -334,136 +326,34 @@ impl<'a> Iterator for PointersIterImpl<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
impl ExactSizeIterator for PointersIterImpl<'_> {
|
||||
fn len(&self) -> usize {
|
||||
self.count - self.next_index
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
/// Represents a view into a past moment of a motion event
|
||||
#[derive(Debug)]
|
||||
pub struct HistoricalMotionEvent<'a> {
|
||||
event: NonNull<ndk_sys::AInputEvent>,
|
||||
history_index: usize,
|
||||
_marker: std::marker::PhantomData<&'a MotionEvent>,
|
||||
}
|
||||
|
||||
// TODO: thread safety?
|
||||
|
||||
impl<'a> HistoricalMotionEvent<'a> {
|
||||
/// Returns the "history index" associated with this historical event. Older events have smaller indices.
|
||||
#[inline]
|
||||
pub fn history_index(&self) -> usize {
|
||||
self.history_index
|
||||
}
|
||||
|
||||
/// Returns the time of the historical event, in the `java.lang.System.nanoTime()` time base
|
||||
///
|
||||
/// See [the NDK
|
||||
/// docs](https://developer.android.com/ndk/reference/group/input#amotionevent_gethistoricaleventtime)
|
||||
#[inline]
|
||||
pub fn event_time(&self) -> i64 {
|
||||
unsafe {
|
||||
ndk_sys::AMotionEvent_getHistoricalEventTime(
|
||||
self.event.as_ptr(),
|
||||
self.history_index as ndk_sys::size_t,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// An iterator over the pointers of this historical motion event
|
||||
#[inline]
|
||||
pub fn pointers(&self) -> HistoricalPointersIter<'a> {
|
||||
HistoricalPointersIter {
|
||||
event: self.event,
|
||||
history_index: self.history_index,
|
||||
next_pointer_index: 0,
|
||||
pointer_count: unsafe {
|
||||
ndk_sys::AMotionEvent_getPointerCount(self.event.as_ptr()) as usize
|
||||
},
|
||||
_marker: std::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An iterator over all the historical moments in a [`MotionEvent`].
|
||||
///
|
||||
/// It iterates from oldest to newest.
|
||||
#[derive(Debug)]
|
||||
pub struct HistoricalMotionEventsIter<'a> {
|
||||
event: NonNull<ndk_sys::AInputEvent>,
|
||||
next_history_index: usize,
|
||||
history_size: usize,
|
||||
_marker: std::marker::PhantomData<&'a MotionEvent>,
|
||||
}
|
||||
|
||||
// TODO: thread safety?
|
||||
|
||||
impl<'a> Iterator for HistoricalMotionEventsIter<'a> {
|
||||
type Item = HistoricalMotionEvent<'a>;
|
||||
|
||||
fn next(&mut self) -> Option<HistoricalMotionEvent<'a>> {
|
||||
if self.next_history_index < self.history_size {
|
||||
let res = HistoricalMotionEvent {
|
||||
event: self.event,
|
||||
history_index: self.next_history_index,
|
||||
_marker: std::marker::PhantomData,
|
||||
};
|
||||
self.next_history_index += 1;
|
||||
Some(res)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
let size = self.history_size - self.next_history_index;
|
||||
(size, Some(size))
|
||||
}
|
||||
}
|
||||
impl ExactSizeIterator for HistoricalMotionEventsIter<'_> {
|
||||
fn len(&self) -> usize {
|
||||
self.history_size - self.next_history_index
|
||||
}
|
||||
}
|
||||
impl<'a> DoubleEndedIterator for HistoricalMotionEventsIter<'a> {
|
||||
fn next_back(&mut self) -> Option<HistoricalMotionEvent<'a>> {
|
||||
if self.next_history_index < self.history_size {
|
||||
self.history_size -= 1;
|
||||
Some(HistoricalMotionEvent {
|
||||
event: self.event,
|
||||
history_index: self.history_size,
|
||||
_marker: std::marker::PhantomData,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
impl ExactSizeIterator for PointersIterImpl<'_> {}
|
||||
|
||||
/// A view into a pointer at a historical moment
|
||||
#[derive(Debug)]
|
||||
pub struct HistoricalPointer<'a> {
|
||||
event: NonNull<ndk_sys::AInputEvent>,
|
||||
pub struct HistoricalPointerImpl<'a> {
|
||||
event: &'a GameActivityMotionEvent,
|
||||
pointer_index: usize,
|
||||
history_index: usize,
|
||||
_marker: std::marker::PhantomData<&'a MotionEvent>,
|
||||
}
|
||||
|
||||
// TODO: thread safety?
|
||||
|
||||
impl<'a> HistoricalPointer<'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 { *self.event.historicalEventTimesNanos.add(self.history_index) }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn pointer_id(&self) -> i32 {
|
||||
unsafe {
|
||||
ndk_sys::AMotionEvent_getPointerId(self.event.as_ptr(), self.pointer_index as ndk_sys::size_t)
|
||||
}
|
||||
let pointer = &self.event.pointers[self.pointer_index];
|
||||
pointer.id
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -474,179 +364,68 @@ impl<'a> HistoricalPointer<'a> {
|
||||
#[inline]
|
||||
pub fn axis_value(&self, axis: Axis) -> f32 {
|
||||
unsafe {
|
||||
ndk_sys::AMotionEvent_getHistoricalAxisValue(
|
||||
self.event.as_ptr(),
|
||||
axis as i32,
|
||||
self.pointer_index as ndk_sys::size_t,
|
||||
self.history_index as ndk_sys::size_t,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn orientation(&self) -> f32 {
|
||||
unsafe {
|
||||
ndk_sys::AMotionEvent_getHistoricalOrientation(
|
||||
self.event.as_ptr(),
|
||||
self.pointer_index as ndk_sys::size_t,
|
||||
self.history_index as ndk_sys::size_t,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn pressure(&self) -> f32 {
|
||||
unsafe {
|
||||
ndk_sys::AMotionEvent_getHistoricalPressure(
|
||||
self.event.as_ptr(),
|
||||
self.pointer_index as ndk_sys::size_t,
|
||||
self.history_index as ndk_sys::size_t,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn raw_x(&self) -> f32 {
|
||||
unsafe {
|
||||
ndk_sys::AMotionEvent_getHistoricalRawX(
|
||||
self.event.as_ptr(),
|
||||
self.pointer_index as ndk_sys::size_t,
|
||||
self.history_index as ndk_sys::size_t,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn raw_y(&self) -> f32 {
|
||||
unsafe {
|
||||
ndk_sys::AMotionEvent_getHistoricalRawY(
|
||||
self.event.as_ptr(),
|
||||
self.pointer_index as ndk_sys::size_t,
|
||||
self.history_index as ndk_sys::size_t,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn x(&self) -> f32 {
|
||||
unsafe {
|
||||
ndk_sys::AMotionEvent_getHistoricalX(
|
||||
self.event.as_ptr(),
|
||||
self.pointer_index as ndk_sys::size_t,
|
||||
self.history_index as ndk_sys::size_t,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn y(&self) -> f32 {
|
||||
unsafe {
|
||||
ndk_sys::AMotionEvent_getHistoricalY(
|
||||
self.event.as_ptr(),
|
||||
self.pointer_index as ndk_sys::size_t,
|
||||
self.history_index as ndk_sys::size_t,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn size(&self) -> f32 {
|
||||
unsafe {
|
||||
ndk_sys::AMotionEvent_getHistoricalSize(
|
||||
self.event.as_ptr(),
|
||||
self.pointer_index as ndk_sys::size_t,
|
||||
self.history_index as ndk_sys::size_t,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn tool_major(&self) -> f32 {
|
||||
unsafe {
|
||||
ndk_sys::AMotionEvent_getHistoricalToolMajor(
|
||||
self.event.as_ptr(),
|
||||
self.pointer_index as ndk_sys::size_t,
|
||||
self.history_index as ndk_sys::size_t,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn tool_minor(&self) -> f32 {
|
||||
unsafe {
|
||||
ndk_sys::AMotionEvent_getHistoricalToolMinor(
|
||||
self.event.as_ptr(),
|
||||
self.pointer_index as ndk_sys::size_t,
|
||||
self.history_index as ndk_sys::size_t,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn touch_major(&self) -> f32 {
|
||||
unsafe {
|
||||
ndk_sys::AMotionEvent_getHistoricalTouchMajor(
|
||||
self.event.as_ptr(),
|
||||
self.pointer_index as ndk_sys::size_t,
|
||||
self.history_index as ndk_sys::size_t,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn touch_minor(&self) -> f32 {
|
||||
unsafe {
|
||||
ndk_sys::AMotionEvent_getHistoricalTouchMinor(
|
||||
self.event.as_ptr(),
|
||||
self.pointer_index as ndk_sys::size_t,
|
||||
self.history_index as ndk_sys::size_t,
|
||||
ffi::GameActivityMotionEvent_getHistoricalAxisValue(
|
||||
self.event,
|
||||
Into::<u32>::into(axis) as i32,
|
||||
self.pointer_index as i32,
|
||||
self.history_index as i32,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An iterator over the pointers in a historical motion event
|
||||
/// An iterator over the historical points of a specific pointer in a [`MotionEvent`].
|
||||
#[derive(Debug)]
|
||||
pub struct HistoricalPointersIter<'a> {
|
||||
event: NonNull<ndk_sys::AInputEvent>,
|
||||
history_index: usize,
|
||||
next_pointer_index: usize,
|
||||
pointer_count: usize,
|
||||
_marker: std::marker::PhantomData<&'a MotionEvent>,
|
||||
pub struct PointerHistoryIterImpl<'a> {
|
||||
event: &'a GameActivityMotionEvent,
|
||||
pointer_index: usize,
|
||||
front: usize,
|
||||
back: usize,
|
||||
}
|
||||
|
||||
// TODO: thread safety?
|
||||
impl<'a> Iterator for PointerHistoryIterImpl<'a> {
|
||||
type Item = crate::input::HistoricalPointer<'a>;
|
||||
|
||||
impl<'a> Iterator for HistoricalPointersIter<'a> {
|
||||
type Item = HistoricalPointer<'a>;
|
||||
|
||||
fn next(&mut self) -> Option<HistoricalPointer<'a>> {
|
||||
if self.next_pointer_index < self.pointer_count {
|
||||
let ptr = HistoricalPointer {
|
||||
event: self.event,
|
||||
history_index: self.history_index,
|
||||
pointer_index: self.next_pointer_index,
|
||||
_marker: std::marker::PhantomData,
|
||||
};
|
||||
self.next_pointer_index += 1;
|
||||
Some(ptr)
|
||||
} else {
|
||||
None
|
||||
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,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
let size = self.pointer_count - self.next_pointer_index;
|
||||
let size = self.back - self.front;
|
||||
(size, Some(size))
|
||||
}
|
||||
}
|
||||
impl ExactSizeIterator for HistoricalPointersIter<'_> {
|
||||
fn len(&self) -> usize {
|
||||
self.pointer_count - self.next_pointer_index
|
||||
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,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
*/
|
||||
impl ExactSizeIterator for PointerHistoryIterImpl<'_> {}
|
||||
impl FusedIterator for PointerHistoryIterImpl<'_> {}
|
||||
|
||||
/// A key event.
|
||||
///
|
||||
|
||||
@@ -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))
|
||||
})
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
use std::iter::FusedIterator;
|
||||
|
||||
use bitflags::bitflags;
|
||||
|
||||
pub use crate::activity_impl::input::*;
|
||||
@@ -907,6 +909,16 @@ pub struct TextInputState {
|
||||
pub compose_region: Option<TextSpan>,
|
||||
}
|
||||
|
||||
impl Default for TextInputState {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
text: String::new(),
|
||||
selection: TextSpan { start: 0, end: 0 },
|
||||
compose_region: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Represents the action button on a soft keyboard.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, num_enum::FromPrimitive, num_enum::IntoPrimitive)]
|
||||
#[non_exhaustive]
|
||||
@@ -934,6 +946,378 @@ pub enum TextInputAction {
|
||||
__Unknown(i32),
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
/// Flags for [`AndroidApp::set_ime_editor_info`]
|
||||
/// as per the [android.view.inputmethod.EditorInfo Java API](https://developer.android.com/reference/android/view/inputmethod/EditorInfo)
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||
pub struct ImeOptions: u32 {
|
||||
|
||||
/// The mask of bits that configure alternative actions for the "enter" key. This helps the
|
||||
/// IME provide clear feedback for what the key will do and provide alternative mechanisms
|
||||
/// for taking the same action.
|
||||
const IME_MASK_ACTION = 0x000000ff;
|
||||
|
||||
/// Indicates that ascii input is a priority (such as for entering an account ID)
|
||||
const IME_FLAG_FORCE_ASCII = 0x80000000;
|
||||
|
||||
/// Indicates that it's possible to navigate focus forwards to something.
|
||||
///
|
||||
/// This is similar to using `IME_ACTION_NEXT` except it allows for multi-line input with
|
||||
/// an enter key in addition to forward navigation for focus.
|
||||
///
|
||||
/// This may not be supported by all IMEs (especially on small screens)
|
||||
const IME_FLAG_NAVIGATE_NEXT = 0x08000000;
|
||||
|
||||
/// Similar to `IME_FLAG_NAVIGATE_NEXT`, except it indicates that it's possible to navigate
|
||||
/// focus backwards to something.
|
||||
const IME_FLAG_NAVIGATE_PREVIOUS = 0x04000000;
|
||||
|
||||
/// This requests that the IME should not show any accessory actions next to the extracted
|
||||
/// text UI, when it is in fullscreen mode.
|
||||
///
|
||||
/// The implication is that you think it's more important to prioritize having room for
|
||||
/// previewing more text, instead of showing accessory actions.
|
||||
///
|
||||
/// Note: In some cases this can make the action unavailable.
|
||||
const IME_FLAG_NO_ACCESSORY_ACTION = 0x20000000;
|
||||
|
||||
/// 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.
|
||||
const IME_FLAG_NO_ENTER_ACTION = 0x40000000;
|
||||
|
||||
/// Don't show any "extracted-text UI" as part of the on-screen IME.
|
||||
///
|
||||
/// Some keyboards may show an additional text box above the keyboard for previewing what
|
||||
/// you type (referred to as the extracted text UI) and it can sometimes be quite large.
|
||||
///
|
||||
/// The exact semantics of this flag can be unclear sometimes and the UI that becomes
|
||||
/// visible may not respond to input as you would expect.
|
||||
///
|
||||
/// This flag may be deprecated in the future and it's recommend to use
|
||||
/// `IME_FLAG_NO_FULLSCREEN` instead, to avoid having the extracted text UI appear to cover
|
||||
/// the full screen.
|
||||
const IMG_FLAG_NO_EXTRACT_UI = 0x10000000;
|
||||
|
||||
/// Request that the IME should avoid ever entering a fullscreen mode and should always
|
||||
/// leave some room for the application UI.
|
||||
///
|
||||
/// Note: It's not guaranteed that an IME will honor this state
|
||||
const IME_FLAG_NO_FULLSCREEN = 0x02000000;
|
||||
|
||||
/// Request that the IME should not update personalized data, such as typing history.
|
||||
///
|
||||
/// Note: It's not guaranteed that an IME will honor this state
|
||||
const IME_FLAG_NO_PERSONALIZED_LEARNING = 0x01000000;
|
||||
|
||||
/// Generic unspecified type for ImeOptions
|
||||
const IME_NULL = 0;
|
||||
}
|
||||
}
|
||||
|
||||
impl ImeOptions {
|
||||
/// Specify what action the IME's "enter" key should perform.
|
||||
///
|
||||
/// This helps the IME provide clear feedback for what the key will do and provide alternative
|
||||
/// mechanisms for taking the same action.
|
||||
pub fn set_action(&mut self, action: TextInputAction) {
|
||||
let action: i32 = action.into();
|
||||
let action = action as u32;
|
||||
*self = Self::from_bits_truncate(
|
||||
(self.bits() & !Self::IME_MASK_ACTION.bits()) | (action & Self::IME_MASK_ACTION.bits()),
|
||||
);
|
||||
}
|
||||
|
||||
/// Get the current action of the IME's "enter" key.
|
||||
pub fn action(&self) -> TextInputAction {
|
||||
let action_bits = self.bits() & Self::IME_MASK_ACTION.bits();
|
||||
TextInputAction::from(action_bits as i32)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, num_enum::FromPrimitive, num_enum::IntoPrimitive)]
|
||||
#[non_exhaustive]
|
||||
#[repr(u32)]
|
||||
pub enum InputTypeClass {
|
||||
/// 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.
|
||||
Null = 0,
|
||||
|
||||
/// Class for normal text.
|
||||
///
|
||||
/// This class supports the following flags (only one of which should be set):
|
||||
/// - TYPE_TEXT_FLAG_CAP_CHARACTERS
|
||||
/// - TYPE_TEXT_FLAG_CAP_WORDS
|
||||
/// - TYPE_TEXT_FLAG_CAP_SENTENCES.
|
||||
///
|
||||
/// It also supports the following variations:
|
||||
/// - TYPE_TEXT_VARIATION_NORMAL
|
||||
/// - TYPE_TEXT_VARIATION_URI
|
||||
///
|
||||
/// *If you do not recognize the variation, normal should be assumed.*
|
||||
Text = 1,
|
||||
|
||||
/// Class for numeric text.
|
||||
///
|
||||
/// This class supports the following flags:
|
||||
/// - `TYPE_NUMBER_FLAG_SIGNED`
|
||||
/// - `TYPE_NUMBER_FLAG_DECIMAL`
|
||||
///
|
||||
/// It also supports the following variations:
|
||||
/// - `TYPE_NUMBER_VARIATION_NORMAL`
|
||||
/// - `TYPE_NUMBER_VARIATION_PASSWORD`
|
||||
///
|
||||
/// *IME authors: If you do not recognize the variation, normal should be assumed.*
|
||||
Number = 2,
|
||||
|
||||
/// Class for a phone number.
|
||||
///
|
||||
/// This class currently supports no variations or flags.
|
||||
Phone = 3,
|
||||
|
||||
/// Class for dates and times.
|
||||
///
|
||||
/// It supports the following variations:
|
||||
/// - TYPE_DATETIME_VARIATION_NORMAL
|
||||
/// - TYPE_DATETIME_VARIATION_DATE
|
||||
/// - TYPE_DATETIME_VARIATION_TIME
|
||||
DateTime = 4,
|
||||
|
||||
#[doc(hidden)]
|
||||
#[num_enum(catch_all)]
|
||||
__Unknown(u32),
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
/// Flags specifying the content type of text being input.
|
||||
///
|
||||
/// Corresponds to the Android SDK [InputType](https://developer.android.com/reference/android/text/InputType) API
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||
pub struct InputType: u32 {
|
||||
/// Mask of bits that determine the overall class of text being given. Currently
|
||||
/// supported classes are: TYPE_CLASS_TEXT, TYPE_CLASS_NUMBER, TYPE_CLASS_PHONE,
|
||||
/// TYPE_CLASS_DATETIME. IME authors: If the class is not one you understand, assume
|
||||
/// TYPE_CLASS_TEXT with NO variation or flags.
|
||||
const TYPE_MASK_CLASS = 0x0000000f;
|
||||
|
||||
/// Mask of bits that determine the variation of the base content class.
|
||||
const TYPE_MASK_VARIATION = 0x00000ff0;
|
||||
|
||||
/// Mask of bits that provide addition bit flags of options.
|
||||
const 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.
|
||||
const TYPE_NULL = 0;
|
||||
|
||||
/// Class for normal text. This class supports the following flags (only one of which
|
||||
/// should be set): TYPE_TEXT_FLAG_CAP_CHARACTERS, TYPE_TEXT_FLAG_CAP_WORDS, and.
|
||||
/// TYPE_TEXT_FLAG_CAP_SENTENCES. It also supports the following variations:
|
||||
/// TYPE_TEXT_VARIATION_NORMAL, and TYPE_TEXT_VARIATION_URI. If you do not recognize the
|
||||
/// variation, normal should be assumed.
|
||||
const TYPE_CLASS_TEXT = 1;
|
||||
|
||||
/// Flag for TYPE_CLASS_TEXT: capitalize all characters. Overrides
|
||||
/// #TYPE_TEXT_FLAG_CAP_WORDS} and #TYPE_TEXT_FLAG_CAP_SENTENCES}. This value is
|
||||
/// explicitly defined to be the same as TextUtils#CAP_MODE_CHARACTERS}. Of
|
||||
/// course, this only affects languages where there are upper-case and lower-case
|
||||
/// letters.
|
||||
const TYPE_TEXT_FLAG_CAP_CHARACTERS = 0x00001000;
|
||||
|
||||
/// Flag for TYPE_CLASS_TEXT: capitalize the first character of every word.
|
||||
/// Overrides TYPE_TEXT_FLAG_CAP_SENTENCES. This value is explicitly defined
|
||||
/// to be the same as TextUtils#CAP_MODE_WORDS. Of course, this only affects
|
||||
/// languages where there are upper-case and lower-case letters.
|
||||
const TYPE_TEXT_FLAG_CAP_WORDS = 0x00002000;
|
||||
|
||||
/// Flag for TYPE_CLASS_TEXT: capitalize the first character of each sentence. This value
|
||||
/// is explicitly defined to be the same as 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.
|
||||
const TYPE_TEXT_FLAG_CAP_SENTENCES = 0x00004000;
|
||||
|
||||
/// Flag for 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 TYPE_TEXT_FLAG_AUTO_COMPLETE and TYPE_TEXT_FLAG_NO_SUGGESTIONS:
|
||||
/// 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.
|
||||
const TYPE_TEXT_FLAG_AUTO_CORRECT = 0x00008000;
|
||||
|
||||
/// Flag for 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
|
||||
/// android.view.inputmethod.InputMethodSession#displayCompletions
|
||||
/// InputMethodSession.displayCompletions()} as a result of the editor calling
|
||||
/// android.view.inputmethod.InputMethodManager#displayCompletions
|
||||
/// InputMethodManager.displayCompletions()}. Note the contrast with
|
||||
/// TYPE_TEXT_FLAG_AUTO_CORRECT and TYPE_TEXT_FLAG_NO_SUGGESTIONS:
|
||||
/// 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.
|
||||
const TYPE_TEXT_FLAG_AUTO_COMPLETE = 0x00010000;
|
||||
|
||||
/// Flag for 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.
|
||||
const TYPE_TEXT_FLAG_MULTI_LINE = 0x00020000;
|
||||
|
||||
/// Flag for 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.
|
||||
const TYPE_TEXT_FLAG_IME_MULTI_LINE = 0x00040000;
|
||||
|
||||
/// Flag for 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 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 TYPE_TEXT_FLAG_AUTO_CORRECT instead if you just do not want the IME to
|
||||
/// correct typos. Note the contrast with TYPE_TEXT_FLAG_AUTO_CORRECT and
|
||||
/// TYPE_TEXT_FLAG_AUTO_COMPLETE: 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.
|
||||
const TYPE_TEXT_FLAG_NO_SUGGESTIONS = 0x00080000;
|
||||
|
||||
/// Flag for 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 Builder#setTextConversionSuggestions(List)} and the
|
||||
/// 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 InputConnection#setComposingText(CharSequence, int,
|
||||
/// TextAttribute)}, InputConnection#setComposingRegion(int, int, TextAttribute)}, and
|
||||
/// InputConnection#commitText(CharSequence, int, TextAttribute)}.
|
||||
const TYPE_TEXT_FLAG_ENABLE_TEXT_CONVERSION_SUGGESTIONS = 0x00100000;
|
||||
|
||||
/// Flag for TYPE_CLASS_TEXT: Let the IME know that conversion candidate selection
|
||||
/// information is requested by the application. Text conversion suggestion is for the
|
||||
/// transliteration languages, which have the notions of pronunciation and target
|
||||
/// characters. When the user actively selects a candidate from the conversion suggestions,
|
||||
/// notifying when candidate selection is occurring helps assistive technologies generate
|
||||
/// more effective feedback. When this flag is set, and there is an active selected
|
||||
/// suggestion, the IME should set that a conversion suggestion is selected when
|
||||
/// initializing the TextAttribute. To receive this information, the application should
|
||||
/// implement InputConnection.setComposingText(CharSequence, int, TextAttribute),
|
||||
/// InputConnection.setComposingRegion(int, int, TextAttribute), and
|
||||
/// InputConnection.commitText(CharSequence, int, TextAttribute)
|
||||
const TYPE_TEXT_FLAG_ENABLE_TEXT_SUGGESTION_SELECTED = 0x00200000;
|
||||
|
||||
/// Default variation of TYPE_CLASS_TEXT: plain old normal text.
|
||||
const TYPE_TEXT_VARIATION_NORMAL = 0;
|
||||
/// Variation of TYPE_CLASS_TEXT: entering a URI.
|
||||
const TYPE_TEXT_VARIATION_URI = 0x00000010;
|
||||
/// Variation of TYPE_CLASS_TEXT: entering an e-mail address.
|
||||
const TYPE_TEXT_VARIATION_EMAIL_ADDRESS = 0x00000020;
|
||||
/// Variation of TYPE_CLASS_TEXT: entering the subject line of an e-mail.
|
||||
const TYPE_TEXT_VARIATION_EMAIL_SUBJECT = 0x00000030;
|
||||
/// Variation of TYPE_CLASS_TEXT: entering a short, possibly informal message such as an instant message or a text message.
|
||||
const TYPE_TEXT_VARIATION_SHORT_MESSAGE = 64;
|
||||
/// Variation of TYPE_CLASS_TEXT: entering the content of a long, possibly formal message such as the body of an e-mail.
|
||||
const TYPE_TEXT_VARIATION_LONG_MESSAGE = 0x00000050;
|
||||
/// Variation of TYPE_CLASS_TEXT: entering the name of a person.
|
||||
const TYPE_TEXT_VARIATION_PERSON_NAME = 0x00000060;
|
||||
/// Variation of TYPE_CLASS_TEXT: entering a postal mailing address.
|
||||
const TYPE_TEXT_VARIATION_POSTAL_ADDRESS = 0x00000070;
|
||||
/// Variation of TYPE_CLASS_TEXT: entering a password.
|
||||
const TYPE_TEXT_VARIATION_PASSWORD = 0x00000080;
|
||||
/// Variation of TYPE_CLASS_TEXT: entering a password, which should be visible to the user.
|
||||
const TYPE_TEXT_VARIATION_VISIBLE_PASSWORD = 0x00000090;
|
||||
/// Variation of TYPE_CLASS_TEXT: entering text inside of a web form.
|
||||
const TYPE_TEXT_VARIATION_WEB_EDIT_TEXT = 0x000000a0;
|
||||
/// Variation of TYPE_CLASS_TEXT: entering text to filter contents of a list etc.
|
||||
const TYPE_TEXT_VARIATION_FILTER = 0x000000b0;
|
||||
/// Variation of 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.
|
||||
const TYPE_TEXT_VARIATION_PHONETIC = 0x000000c0;
|
||||
/// Variation of TYPE_CLASS_TEXT: entering e-mail address inside of a web form. This
|
||||
/// was added in 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 #TYPE_TEXT_VARIATION_EMAIL_ADDRESS} when passed through
|
||||
/// android.view.inputmethod.EditorInfo#makeCompatible(int)
|
||||
/// EditorInfo.makeCompatible(int)}.
|
||||
const TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS = 0x000000d0;
|
||||
/// Variation of TYPE_CLASS_TEXT: entering password inside of a web form. This was
|
||||
/// added in 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 #TYPE_TEXT_VARIATION_PASSWORD} when passed through
|
||||
/// android.view.inputmethod.EditorInfo#makeCompatible(int)
|
||||
/// EditorInfo.makeCompatible(int)}.
|
||||
const TYPE_TEXT_VARIATION_WEB_PASSWORD = 0x000000e0;
|
||||
/// Class for numeric text. This class supports the following flags:
|
||||
/// #TYPE_NUMBER_FLAG_SIGNED} and #TYPE_NUMBER_FLAG_DECIMAL}. It also supports the
|
||||
/// following variations: #TYPE_NUMBER_VARIATION_NORMAL} and
|
||||
/// #TYPE_NUMBER_VARIATION_PASSWORD}. <p>IME authors: If you do not recognize the
|
||||
/// variation, normal should be assumed.</p>
|
||||
const TYPE_CLASS_NUMBER = 2;
|
||||
/// Flag of TYPE_CLASS_NUMBER: the number is signed, allowing a positive or negative
|
||||
/// sign at the start.
|
||||
const TYPE_NUMBER_FLAG_SIGNED = 0x00001000;
|
||||
/// Flag of TYPE_CLASS_NUMBER: the number is decimal, allowing a decimal point to
|
||||
/// provide fractional values.
|
||||
const TYPE_NUMBER_FLAG_DECIMAL = 0x00002000;
|
||||
/// Default variation of TYPE_CLASS_NUMBER: plain normal numeric text. This was added
|
||||
/// in 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 android.view.inputmethod.EditorInfo#makeCompatible(int)
|
||||
/// EditorInfo.makeCompatible(int)}.
|
||||
const TYPE_NUMBER_VARIATION_NORMAL = 0;
|
||||
/// Variation of TYPE_CLASS_NUMBER: entering a numeric password. This was added in
|
||||
/// 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 android.view.inputmethod.EditorInfo#makeCompatible(int)
|
||||
/// EditorInfo.makeCompatible(int)}.
|
||||
const TYPE_NUMBER_VARIATION_PASSWORD = 0x00000010;
|
||||
/// Class for a phone number. This class currently supports no variations or flags.
|
||||
const TYPE_CLASS_PHONE = 3;
|
||||
/// Class for dates and times. It supports the following variations:
|
||||
/// #TYPE_DATETIME_VARIATION_NORMAL} #TYPE_DATETIME_VARIATION_DATE}, and
|
||||
/// #TYPE_DATETIME_VARIATION_TIME}.
|
||||
const TYPE_CLASS_DATETIME = 4;
|
||||
/// Default variation of #TYPE_CLASS_DATETIME}: allows entering both a date and time.
|
||||
const TYPE_DATETIME_VARIATION_NORMAL = 0;
|
||||
/// Default variation of #TYPE_CLASS_DATETIME}: allows entering only a date.
|
||||
const TYPE_DATETIME_VARIATION_DATE = 16;
|
||||
/// Default variation of #TYPE_CLASS_DATETIME}: allows entering only a time.
|
||||
const TYPE_DATETIME_VARIATION_TIME = 32;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
impl InputType {
|
||||
/// Extract just the class of the input type.
|
||||
pub fn class(&self) -> InputTypeClass {
|
||||
let class = self.bits() & InputType::TYPE_MASK_CLASS.bits();
|
||||
InputTypeClass::from(class)
|
||||
}
|
||||
}
|
||||
|
||||
/// An exclusive, lending iterator for input events
|
||||
pub struct InputIterator<'a> {
|
||||
pub(crate) inner: crate::activity_impl::InputIteratorInner<'a>,
|
||||
@@ -1034,6 +1418,20 @@ impl Pointer<'_> {
|
||||
pub fn tool_type(&self) -> ToolType {
|
||||
self.inner.tool_type()
|
||||
}
|
||||
|
||||
/// Gives access to the historical data of a pointer in a [`MotionEvent`].
|
||||
///
|
||||
/// This provides access to higher-frequency data points that were recorded
|
||||
/// between the current event and the previous event, which can be used for
|
||||
/// more accurate gesture detection and smoother animations.
|
||||
///
|
||||
/// For a single [`MotionEvent`] each pointer will have the same number of
|
||||
/// historical events, and the corresponding historical events will have the
|
||||
/// same timestamps.
|
||||
#[inline]
|
||||
pub fn history(&self) -> PointerHistoryIter<'_> {
|
||||
self.inner.history()
|
||||
}
|
||||
}
|
||||
|
||||
/// An iterator over the pointers in a [`MotionEvent`].
|
||||
@@ -1053,8 +1451,108 @@ impl<'a> Iterator for PointersIter<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
impl ExactSizeIterator for PointersIter<'_> {
|
||||
fn len(&self) -> usize {
|
||||
self.inner.len()
|
||||
impl ExactSizeIterator for PointersIter<'_> {}
|
||||
|
||||
/// An iterator over the historical data of a pointer in a [`MotionEvent`].
|
||||
///
|
||||
/// This provides access to higher-frequency data points that were recorded
|
||||
/// between the current event and the previous event, which can be used for more
|
||||
/// accurate gesture detection and smoother animations.
|
||||
///
|
||||
/// For a single [`MotionEvent`] each pointer will have the same number of
|
||||
/// historical events, and the corresponding historical events will have the
|
||||
/// same timestamps.
|
||||
///
|
||||
#[derive(Debug)]
|
||||
pub struct PointerHistoryIter<'a> {
|
||||
pub(crate) inner: PointerHistoryIterImpl<'a>,
|
||||
}
|
||||
|
||||
impl<'a> Iterator for PointerHistoryIter<'a> {
|
||||
type Item = HistoricalPointer<'a>;
|
||||
fn next(&mut self) -> Option<HistoricalPointer<'a>> {
|
||||
self.inner.next()
|
||||
}
|
||||
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
self.inner.size_hint()
|
||||
}
|
||||
}
|
||||
impl<'a> DoubleEndedIterator for PointerHistoryIter<'a> {
|
||||
fn next_back(&mut self) -> Option<HistoricalPointer<'a>> {
|
||||
self.inner.next_back()
|
||||
}
|
||||
}
|
||||
impl ExactSizeIterator for PointerHistoryIter<'_> {}
|
||||
impl FusedIterator for PointerHistoryIter<'_> {}
|
||||
|
||||
pub struct HistoricalPointer<'a> {
|
||||
pub(crate) inner: HistoricalPointerImpl<'a>,
|
||||
}
|
||||
|
||||
impl HistoricalPointer<'_> {
|
||||
#[inline]
|
||||
pub fn history_index(&self) -> usize {
|
||||
self.inner.history_index()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn pointer_index(&self) -> usize {
|
||||
self.inner.pointer_index()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn event_time(&self) -> i64 {
|
||||
self.inner.event_time()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn axis_value(&self, axis: Axis) -> f32 {
|
||||
self.inner.axis_value(axis)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn orientation(&self) -> f32 {
|
||||
self.axis_value(Axis::Orientation)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn pressure(&self) -> f32 {
|
||||
self.axis_value(Axis::Pressure)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn x(&self) -> f32 {
|
||||
self.axis_value(Axis::X)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn y(&self) -> f32 {
|
||||
self.axis_value(Axis::Y)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn size(&self) -> f32 {
|
||||
self.axis_value(Axis::Size)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn tool_major(&self) -> f32 {
|
||||
self.axis_value(Axis::ToolMajor)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn tool_minor(&self) -> f32 {
|
||||
self.axis_value(Axis::ToolMinor)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn touch_major(&self) -> f32 {
|
||||
self.axis_value(Axis::TouchMajor)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn touch_minor(&self) -> f32 {
|
||||
self.axis_value(Axis::TouchMinor)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,9 @@
|
||||
use std::sync::Arc;
|
||||
use jni::sys::jint;
|
||||
use jni::{objects::Global, JavaVM};
|
||||
|
||||
use jni::{
|
||||
objects::{GlobalRef, JClass, JMethodID, JObject, JStaticMethodID, JValue},
|
||||
signature::{Primitive, ReturnType},
|
||||
JNIEnv,
|
||||
};
|
||||
use jni_sys::jint;
|
||||
|
||||
use crate::{
|
||||
input::{Keycode, MetaState},
|
||||
jni_utils::CloneJavaVM,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
error::{AppError, InternalAppError},
|
||||
jni_utils,
|
||||
};
|
||||
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
|
||||
///
|
||||
@@ -80,157 +68,74 @@ pub enum KeyMapChar {
|
||||
CombiningAccent(char),
|
||||
}
|
||||
|
||||
// I've also tried to think here about how to we could potentially automatically
|
||||
// generate a binding struct like `KeyCharacterMapBinding` with a procmacro and
|
||||
// so have intentionally limited the `Binding` being a very thin, un-opinionated
|
||||
// wrapper based on basic JNI types.
|
||||
|
||||
/// Lower-level JNI binding for `KeyCharacterMap` class only holds 'static state
|
||||
/// and can be shared with an `Arc` ref count.
|
||||
///
|
||||
/// The separation here also neatly helps us separate `InternalAppError` from
|
||||
/// `AppError` for mapping JNI errors without exposing any `jni-rs` types in the
|
||||
/// public API.
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct KeyCharacterMapBinding {
|
||||
//vm: JavaVM,
|
||||
klass: GlobalRef,
|
||||
get_method_id: JMethodID,
|
||||
get_dead_char_method_id: JStaticMethodID,
|
||||
get_keyboard_type_method_id: JMethodID,
|
||||
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 KeyCharacterMapBinding {
|
||||
pub(crate) fn new(env: &mut JNIEnv) -> Result<Self, InternalAppError> {
|
||||
let binding = env.with_local_frame::<_, _, InternalAppError>(10, |env| {
|
||||
let klass = env.find_class("android/view/KeyCharacterMap")?; // Creates a local ref
|
||||
Ok(Self {
|
||||
get_method_id: env.get_method_id(&klass, "get", "(II)I")?,
|
||||
get_dead_char_method_id: env.get_static_method_id(
|
||||
&klass,
|
||||
"getDeadChar",
|
||||
"(II)I",
|
||||
)?,
|
||||
get_keyboard_type_method_id: env.get_method_id(&klass, "getKeyboardType", "()I")?,
|
||||
klass: env.new_global_ref(&klass)?,
|
||||
})
|
||||
})?;
|
||||
Ok(binding)
|
||||
}
|
||||
|
||||
pub fn get<'local>(
|
||||
impl AKeyCharacterMap<'_> {
|
||||
pub(crate) fn get(
|
||||
&self,
|
||||
env: &'local mut JNIEnv,
|
||||
key_map: impl AsRef<JObject<'local>>,
|
||||
env: &mut jni::Env,
|
||||
key_code: jint,
|
||||
meta_state: jint,
|
||||
) -> Result<jint, InternalAppError> {
|
||||
let key_map = key_map.as_ref();
|
||||
|
||||
// Safety:
|
||||
// - we know our global `key_map` reference is non-null and valid.
|
||||
// - we know `get_method_id` remains valid
|
||||
// - we know that the signature of KeyCharacterMap::get is `(int, int) -> int`
|
||||
// - we know this won't leak any local references as a side effect
|
||||
//
|
||||
// We know it's ok to unwrap the `.i()` value since we explicitly
|
||||
// specify the return type as `Int`
|
||||
let unicode = unsafe {
|
||||
env.call_method_unchecked(
|
||||
key_map,
|
||||
self.get_method_id,
|
||||
ReturnType::Primitive(Primitive::Int),
|
||||
&[
|
||||
JValue::Int(key_code).as_jni(),
|
||||
JValue::Int(meta_state).as_jni(),
|
||||
],
|
||||
)
|
||||
}
|
||||
.map_err(|err| jni_utils::clear_and_map_exception_to_err(env, err))?;
|
||||
Ok(unicode.i().unwrap())
|
||||
self._get(env, key_code, meta_state)
|
||||
.map_err(|err| jni_utils::clear_and_map_exception_to_err(env, err))
|
||||
}
|
||||
|
||||
pub fn get_dead_char(
|
||||
&self,
|
||||
env: &mut JNIEnv,
|
||||
pub(crate) fn get_dead_char(
|
||||
env: &mut jni::Env,
|
||||
accent_char: jint,
|
||||
base_char: jint,
|
||||
) -> Result<jint, InternalAppError> {
|
||||
// Safety:
|
||||
// - we know `get_dead_char_method_id` remains valid
|
||||
// - we know that KeyCharacterMap::getDeadKey is a static method
|
||||
// - we know that the signature of KeyCharacterMap::getDeadKey is `(int, int) -> int`
|
||||
// - we know this won't leak any local references as a side effect
|
||||
//
|
||||
// We know it's ok to unwrap the `.i()` value since we explicitly
|
||||
// specify the return type as `Int`
|
||||
|
||||
// Urgh, it's pretty terrible that there's no ergonomic/safe way to get a JClass reference from a GlobalRef
|
||||
// Safety: we don't do anything that would try to delete the JClass as if it were a real local reference
|
||||
let klass = unsafe { JClass::from_raw(self.klass.as_obj().as_raw()) };
|
||||
let unicode = unsafe {
|
||||
env.call_static_method_unchecked(
|
||||
&klass,
|
||||
self.get_dead_char_method_id,
|
||||
ReturnType::Primitive(Primitive::Int),
|
||||
&[
|
||||
JValue::Int(accent_char).as_jni(),
|
||||
JValue::Int(base_char).as_jni(),
|
||||
],
|
||||
)
|
||||
}
|
||||
.map_err(|err| jni_utils::clear_and_map_exception_to_err(env, err))?;
|
||||
Ok(unicode.i().unwrap())
|
||||
Self::_get_dead_char(env, accent_char, base_char)
|
||||
.map_err(|err| jni_utils::clear_and_map_exception_to_err(env, err))
|
||||
}
|
||||
|
||||
pub fn get_keyboard_type<'local>(
|
||||
&self,
|
||||
env: &'local mut JNIEnv,
|
||||
key_map: impl AsRef<JObject<'local>>,
|
||||
) -> Result<jint, InternalAppError> {
|
||||
let key_map = key_map.as_ref();
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
||||
// Safety:
|
||||
// - we know our global `key_map` reference is non-null and valid.
|
||||
// - we know `get_keyboard_type_method_id` remains valid
|
||||
// - we know that the signature of KeyCharacterMap::getKeyboardType is `() -> int`
|
||||
// - we know this won't leak any local references as a side effect
|
||||
//
|
||||
// We know it's ok to unwrap the `.i()` value since we explicitly
|
||||
// specify the return type as `Int`
|
||||
Ok(unsafe {
|
||||
env.call_method_unchecked(
|
||||
key_map,
|
||||
self.get_keyboard_type_method_id,
|
||||
ReturnType::Primitive(Primitive::Int),
|
||||
&[],
|
||||
)
|
||||
}
|
||||
.map_err(|err| jni_utils::clear_and_map_exception_to_err(env, err))?
|
||||
.i()
|
||||
.unwrap())
|
||||
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(Clone, Debug)]
|
||||
#[derive(Debug)]
|
||||
pub struct KeyCharacterMap {
|
||||
jvm: CloneJavaVM,
|
||||
binding: Arc<KeyCharacterMapBinding>,
|
||||
key_map: GlobalRef,
|
||||
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: CloneJavaVM,
|
||||
binding: Arc<KeyCharacterMapBinding>,
|
||||
key_map: GlobalRef,
|
||||
) -> Self {
|
||||
Self {
|
||||
jvm,
|
||||
binding,
|
||||
key_map,
|
||||
}
|
||||
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.
|
||||
@@ -247,41 +152,37 @@ impl KeyCharacterMap {
|
||||
/// 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 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 meta_state = meta_state as jni::sys::jint;
|
||||
|
||||
// Since we expect this API to be called from the `main` thread then we expect to already be
|
||||
// attached to the JVM
|
||||
//
|
||||
// Safety: there's no other JNIEnv in scope so this env can't be used to subvert the mutable
|
||||
// borrow rules that ensure we can only add local references to the top JNI frame.
|
||||
let mut env = self.jvm.get_env().map_err(|err| {
|
||||
let err: InternalAppError = err.into();
|
||||
err
|
||||
})?;
|
||||
let unicode = self
|
||||
.binding
|
||||
.get(&mut env, self.key_map.as_obj(), key_code, meta_state)?;
|
||||
let unicode = unicode as u32;
|
||||
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;
|
||||
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)
|
||||
}))
|
||||
}
|
||||
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.
|
||||
@@ -297,28 +198,24 @@ impl KeyCharacterMap {
|
||||
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 accent_char = accent_char as jni::sys::jint;
|
||||
let base_char = base_char as jni::sys::jint;
|
||||
|
||||
// Since we expect this API to be called from the `main` thread then we expect to already be
|
||||
// attached to the JVM
|
||||
//
|
||||
// Safety: there's no other JNIEnv in scope so this env can't be used to subvert the mutable
|
||||
// borrow rules that ensure we can only add local references to the top JNI frame.
|
||||
let mut env = self.jvm.get_env().map_err(|err| {
|
||||
let err: InternalAppError = err.into();
|
||||
err
|
||||
})?;
|
||||
let unicode = self
|
||||
.binding
|
||||
.get_dead_char(&mut env, accent_char, base_char)?;
|
||||
let unicode = unicode as u32;
|
||||
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) })
|
||||
// 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()
|
||||
})
|
||||
}
|
||||
|
||||
@@ -332,19 +229,47 @@ impl KeyCharacterMap {
|
||||
/// 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> {
|
||||
// Since we expect this API to be called from the `main` thread then we expect to already be
|
||||
// attached to the JVM
|
||||
//
|
||||
// Safety: there's no other JNIEnv in scope so this env can't be used to subvert the mutable
|
||||
// borrow rules that ensure we can only add local references to the top JNI frame.
|
||||
let mut env = self.jvm.get_env().map_err(|err| {
|
||||
let err: InternalAppError = err.into();
|
||||
err
|
||||
})?;
|
||||
let keyboard_type = self
|
||||
.binding
|
||||
.get_keyboard_type(&mut env, self.key_map.as_obj())?;
|
||||
let keyboard_type = keyboard_type as u32;
|
||||
Ok(keyboard_type.into())
|
||||
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))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -5,47 +5,7 @@
|
||||
//!
|
||||
//! These utilities help us check + clear exceptions and map them into Rust Errors.
|
||||
|
||||
use std::{ops::Deref, sync::Arc};
|
||||
|
||||
use jni::{
|
||||
objects::{JObject, JString},
|
||||
JavaVM,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
error::{InternalAppError, InternalResult},
|
||||
input::{KeyCharacterMap, KeyCharacterMapBinding},
|
||||
};
|
||||
|
||||
// TODO: JavaVM should implement Clone
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct CloneJavaVM {
|
||||
pub jvm: JavaVM,
|
||||
}
|
||||
impl Clone for CloneJavaVM {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
jvm: unsafe { JavaVM::from_raw(self.jvm.get_java_vm_pointer()).unwrap() },
|
||||
}
|
||||
}
|
||||
}
|
||||
impl CloneJavaVM {
|
||||
pub unsafe fn from_raw(jvm: *mut jni_sys::JavaVM) -> InternalResult<Self> {
|
||||
Ok(Self {
|
||||
jvm: JavaVM::from_raw(jvm)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
unsafe impl Send for CloneJavaVM {}
|
||||
unsafe impl Sync for CloneJavaVM {}
|
||||
|
||||
impl Deref for CloneJavaVM {
|
||||
type Target = JavaVM;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.jvm
|
||||
}
|
||||
}
|
||||
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`
|
||||
@@ -55,97 +15,14 @@ impl Deref for CloneJavaVM {
|
||||
///
|
||||
/// This will also clear the exception
|
||||
pub(crate) fn clear_and_map_exception_to_err(
|
||||
env: &mut jni::JNIEnv<'_>,
|
||||
env: &mut jni::Env<'_>,
|
||||
err: jni::errors::Error,
|
||||
) -> InternalAppError {
|
||||
if matches!(err, jni::errors::Error::JavaException) {
|
||||
let result = env.with_local_frame::<_, _, InternalAppError>(5, |env| {
|
||||
let e = env.exception_occurred()?;
|
||||
assert!(!e.is_null()); // should only be called after receiving a JavaException Result
|
||||
env.exception_clear()?;
|
||||
|
||||
let class = env.get_object_class(&e)?;
|
||||
//let get_stack_trace_method = env.get_method_id(&class, "getStackTrace", "()[Ljava/lang/StackTraceElement;")?;
|
||||
let get_message_method =
|
||||
env.get_method_id(&class, "getMessage", "()Ljava/lang/String;")?;
|
||||
|
||||
let msg = unsafe {
|
||||
env.call_method_unchecked(
|
||||
&e,
|
||||
get_message_method,
|
||||
jni::signature::ReturnType::Object,
|
||||
&[],
|
||||
)?
|
||||
.l()
|
||||
.unwrap()
|
||||
};
|
||||
let msg = unsafe { JString::from_raw(JObject::into_raw(msg)) };
|
||||
let msg = env.get_string(&msg)?;
|
||||
let msg: String = msg.into();
|
||||
|
||||
// TODO: get Java backtrace:
|
||||
/*
|
||||
if let JValue::Object(elements) = env.call_method_unchecked(&e, get_stack_trace_method, jni::signature::ReturnType::Array, &[])? {
|
||||
let elements = env.auto_local(elements);
|
||||
|
||||
}
|
||||
*/
|
||||
|
||||
Ok(msg)
|
||||
});
|
||||
|
||||
match result {
|
||||
Ok(msg) => InternalAppError::JniException(msg),
|
||||
Err(err) => InternalAppError::JniException(format!(
|
||||
"UNKNOWN (Failed to query JThrowable: {err:?})"
|
||||
)),
|
||||
}
|
||||
env.exception_catch()
|
||||
.expect_err("Spurious JavaException error with no exception to catch")
|
||||
} else {
|
||||
err.into()
|
||||
err
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn device_key_character_map(
|
||||
jvm: CloneJavaVM,
|
||||
key_map_binding: Arc<KeyCharacterMapBinding>,
|
||||
device_id: i32,
|
||||
) -> InternalResult<KeyCharacterMap> {
|
||||
// Don't really need to 'attach' since this should be called from the app's main thread that
|
||||
// should already be attached, but the redundancy should be fine
|
||||
//
|
||||
// Attach 'permanently' to avoid any chance of detaching the thread from the VM
|
||||
let mut env = jvm.attach_current_thread_permanently()?;
|
||||
|
||||
// We don't want to accidentally leak any local references while we
|
||||
// aren't going to be returning from here back to the JVM, to unwind, so
|
||||
// we make a local frame
|
||||
let character_map = env.with_local_frame::<_, _, jni::errors::Error>(10, |env| {
|
||||
let input_device_class = env.find_class("android/view/InputDevice")?; // Creates a local ref
|
||||
let device = env
|
||||
.call_static_method(
|
||||
input_device_class,
|
||||
"getDevice",
|
||||
"(I)Landroid/view/InputDevice;",
|
||||
&[device_id.into()],
|
||||
)?
|
||||
.l()?; // Creates a local ref
|
||||
|
||||
let character_map = env
|
||||
.call_method(
|
||||
&device,
|
||||
"getKeyCharacterMap",
|
||||
"()Landroid/view/KeyCharacterMap;",
|
||||
&[],
|
||||
)?
|
||||
.l()?;
|
||||
let character_map = env.new_global_ref(character_map)?;
|
||||
|
||||
Ok(character_map)
|
||||
})?;
|
||||
|
||||
Ok(KeyCharacterMap::new(
|
||||
jvm.clone(),
|
||||
key_map_binding,
|
||||
character_map,
|
||||
))
|
||||
.into()
|
||||
}
|
||||
|
||||
@@ -1,70 +1,270 @@
|
||||
//! A glue layer for building standalone, Rust applications on Android
|
||||
//!
|
||||
//! This crate 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.
|
||||
//! This crate 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.
|
||||
//!
|
||||
//! Currently the crate supports two `Activity` base classes:
|
||||
//! 1. [`NativeActivity`] - Built in to Android, this doesn't require compiling any Java or Kotlin code.
|
||||
//! 1. [`NativeActivity`] - Built in to Android, this doesn't require compiling
|
||||
//! any Java or Kotlin code.
|
||||
//! 2. [`GameActivity`] - From the Android Game Development Kit, it has more
|
||||
//! sophisticated input handling support than `NativeActivity`. `GameActivity`
|
||||
//! is also based on the `AndroidAppCompat` class which can help with supporting
|
||||
//! a wider range of devices.
|
||||
//! sophisticated input handling support than `NativeActivity`.
|
||||
//! `GameActivity` is also based on the `AndroidAppCompat` class which can
|
||||
//! help with supporting a wider range of devices.
|
||||
//!
|
||||
//! Standalone applications based on this crate need to be built as `cdylib` libraries, like:
|
||||
//! Standalone applications based on this crate need to be built as `cdylib`
|
||||
//! libraries, like:
|
||||
//! ```toml
|
||||
//! [lib]
|
||||
//! crate_type=["cdylib"]
|
||||
//! crate-type=["cdylib"]
|
||||
//! ```
|
||||
//!
|
||||
//! and implement a `#[no_mangle]` `android_main` entry point like this:
|
||||
//! ```no_run
|
||||
//! #[no_mangle]
|
||||
//! fn android_main(app: android_activity::AndroidApp) {
|
||||
//! ## Lifecycle of an Activity
|
||||
//!
|
||||
//! Keep in mind that Android's application programming model is based around
|
||||
//! the
|
||||
//! [lifecycle](https://developer.android.com/guide/components/activities/activity-lifecycle)
|
||||
//! of [`Activity`] and [`Service`] components, and not the lifecycle of the
|
||||
//! application process.
|
||||
//!
|
||||
//! An Android application may have multiple [`Activity`] and [`Service`]
|
||||
//! instances created and destroyed over its lifetime, and each of these
|
||||
//! [`Activity`] and [`Service`] instances will have their own lifecycles that
|
||||
//! are independent from the lifecycle of the application process.
|
||||
//!
|
||||
//! See the Android SDK [activity lifecycle
|
||||
//! documentation](https://developer.android.com/guide/components/activities/activity-lifecycle)
|
||||
//! for more details on the [`Activity`] lifecycle.
|
||||
//!
|
||||
//! Although native applications will typically only have a single instance of
|
||||
//! [`NativeActivity`] or [`GameActivity`], it's possible for these activities
|
||||
//! to be created and destroyed multiple times within the lifetime of your
|
||||
//! application process.
|
||||
//!
|
||||
//! Although [`NativeActivity`] and [`GameActivity`] were historically designed
|
||||
//! for full-screen games and based on the assumption that there would only be a
|
||||
//! single instance of these activities, it is good to keep in mind that Android
|
||||
//! itself makes no such assumption. It's very common for non-native Android
|
||||
//! applications to be tracking multiple `Activity` instances at the same time.
|
||||
//!
|
||||
//! The `android-activity` crate is designed to be robust to multiple `Activity`
|
||||
//! instances being created and destroyed over the lifetime of the application
|
||||
//! process.
|
||||
//!
|
||||
//! ## Entrypoints
|
||||
//!
|
||||
//! There are currently two supported entrypoints for an `android-activity`
|
||||
//! application:
|
||||
//!
|
||||
//! 1. `android_on_create` **(optional)** - This runs early, on the Java main /
|
||||
//! UI thread, during `Activity.onCreate()`. It can be a good place to
|
||||
//! initialize logging and JNI bindings.
|
||||
//! 2. `android_main` **(required)** - This run a dedicated main loop thread for
|
||||
//! handling lifecycle and input events for your `Activity`.
|
||||
//!
|
||||
//! **Important**: Your `android-activity` entrypoints are tied to the lifecycle
|
||||
//! of your native **`Activity`** (i.e. [`NativeActivity`] or [`GameActivity`])
|
||||
//! and not the lifecycle of your application process! This means that if your
|
||||
//! `Activity` is destroyed and re-created (e.g. depending on how your
|
||||
//! application handles configuration changes) then these entrypoints may be
|
||||
//! called multiple times, for each `Activity` instance.
|
||||
//!
|
||||
//! #### Your AndroidManifest `configureChanges` state affects Activity re-creation
|
||||
//!
|
||||
//! Beware that, by default, certain configuration changes (e.g. device
|
||||
//! rotation) will cause the Android system to destroy and re-create your
|
||||
//! `Activity`, which will lead to a [`MainEvent::Destroy`] event being sent to
|
||||
//! your `android_main()` thread and then `android_main()` will be called again
|
||||
//! as a new native `Activity` instance is created.
|
||||
//!
|
||||
//! Since this can be awkward to handle, it is common practice to set the
|
||||
//! `android:configChanges` property to indicate that your application can
|
||||
//! handle these changes at runtime via events instead.
|
||||
//!
|
||||
//! **Example**:
|
||||
//!
|
||||
//! Here's how you can set `android:configChanges` for your `Activity` in your
|
||||
//! AndroidManifest.xml:
|
||||
//!
|
||||
//! ```xml
|
||||
//! <activity
|
||||
//! android:name="android.app.NativeActivity"
|
||||
//! android:configChanges="orientation|screenSize|screenLayout|keyboardHidden"
|
||||
//! android:label="NativeActivity Example"
|
||||
//! android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
|
||||
//! android:exported="true">
|
||||
//!
|
||||
//! <!-- ... -->
|
||||
//! </activity>
|
||||
//! ```
|
||||
//!
|
||||
//! ### onCreate entrypoint: `android_on_create` (optional)
|
||||
//!
|
||||
//! The `android_on_create` entry point will be called from the Java main
|
||||
//! thread, within the `Activity`'s `onCreate` method, before the `android_main`
|
||||
//! entry point is called.
|
||||
//!
|
||||
//! This must be an exported, unmangled, `"Rust"` ABI function with the
|
||||
//! signature `fn android_on_create(state: &OnCreateState)`.
|
||||
//!
|
||||
//! The easiest way to achieve this is with `#[unsafe(no_mangle)]` like this:
|
||||
//! ```no_run
|
||||
//! #[unsafe(no_mangle)]
|
||||
//! fn android_on_create(state: &android_activity::OnCreateState) {
|
||||
//! // Initialization code here
|
||||
//! }
|
||||
//! ```
|
||||
//! (Note `extern "Rust"` is the default ABI)
|
||||
//!
|
||||
//! **I/O redirection**: Before `android_on_create()` is called an I/O thread is
|
||||
//! spawned that will handle redirecting standard input and output to the
|
||||
//! Android log, visible via `logcat`.
|
||||
//!
|
||||
//! [`OnCreateState`] provides access to the Java VM and a JNI reference to the
|
||||
//! `Activity` instance, as well as any saved state from a previous instance of
|
||||
//! the Activity.
|
||||
//!
|
||||
//! Due to the way JNI class loading works, this can be a convenient place to
|
||||
//! initialize JNI bindings because it's called while the `Activity`'s
|
||||
//! `onCreate` callback is on the stack, so the default class loader will be
|
||||
//! able to find the application's Java classes. See the Android
|
||||
//! [JNI tips](https://developer.android.com/ndk/guides/jni-tips#faq:-why-didnt-findclass-find-my-class)
|
||||
//! guide for more details on this.
|
||||
//!
|
||||
//! This can also be a good place to initialize logging, since it's called
|
||||
//! first.
|
||||
//!
|
||||
//! **Important**: This entrypoint must not block for a long time or do heavy
|
||||
//! work, since it's running on the Java main thread and will block the
|
||||
//! `Activity` from being created until it returns.
|
||||
//!
|
||||
//! Blocking the Java main thread for too long may cause an "Application Not
|
||||
//! Responding" (ANR) dialog to be shown to the user, and cause users to force
|
||||
//! close your application.
|
||||
//!
|
||||
//! **Panic behavior**: If `android_on_create` panics, the application will
|
||||
//! abort. This is because the callback runs within a native JNI callback where
|
||||
//! unwinding is not permitted. Ensure your initialization code either cannot
|
||||
//! panic or uses `catch_unwind` internally if you want to allow partial
|
||||
//! initialization failures.
|
||||
//!
|
||||
//! #### Example:
|
||||
//!
|
||||
//! ```no_run
|
||||
//! # use std::sync::OnceLock;
|
||||
//! # use android_activity::OnCreateState;
|
||||
//! # use jni::{JavaVM, 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, depending on
|
||||
//! // the crate, logger initialization may panic if attempted 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:?}");
|
||||
//! }
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! Once your application's `Activity` class has loaded and it calls `onCreate` then
|
||||
//! `android-activity` will spawn a dedicated thread to run your `android_main` function,
|
||||
//! separate from the Java thread that created the corresponding `Activity`.
|
||||
//! ### Main loop thread entrypoint: `android_main` (required)
|
||||
//!
|
||||
//! Your application must always define an `android_main` function as an entry
|
||||
//! point for running a main loop thread for your Activity.
|
||||
//!
|
||||
//! This must be an exported, unmangled, `"Rust"` ABI function with the
|
||||
//! signature `fn android_main(app: AndroidApp)`.
|
||||
//!
|
||||
//! The easiest way to achieve this is with `#[unsafe(no_mangle)]` like this:
|
||||
//! ```no_run
|
||||
//! #[unsafe(no_mangle)]
|
||||
//! fn android_main(app: android_activity::AndroidApp) {
|
||||
//! // Main loop code here
|
||||
//! }
|
||||
//! ```
|
||||
//! (Note `extern "Rust"` is the default ABI)
|
||||
//!
|
||||
//! Once your application's `Activity` class has loaded and it calls `onCreate`
|
||||
//! then `android-activity` will spawn a dedicated thread to run your
|
||||
//! `android_main` function, separate from the Java thread that created the
|
||||
//! corresponding `Activity`.
|
||||
//!
|
||||
//! Before `android_main()` is called:
|
||||
//! - A `JavaVM` and
|
||||
//! [`android.content.Context`](https://developer.android.com/reference/android/content/Context)
|
||||
//! instance will be associated with the [`ndk_context`] crate so that other,
|
||||
//! independent, Rust crates are able to find a JavaVM for making JNI calls.
|
||||
//! - The `JavaVM` will be attached to the native thread (for JNI)
|
||||
//! - A [Looper] is attached to the Rust native thread.
|
||||
//!
|
||||
//! **Important:** This thread *must* call [`AndroidApp::poll_events()`]
|
||||
//! regularly in order to receive lifecycle and input events for the `Activity`.
|
||||
//! Some `Activity` lifecycle callbacks on the Java main thread will block until
|
||||
//! the next time `poll_events()` is called, so if you don't call
|
||||
//! `poll_events()` regularly you may trigger an ANR dialog and cause users to
|
||||
//! force close your application.
|
||||
//!
|
||||
//! **Important**: You should return from `android_main()` as soon as possible
|
||||
//! if you receive a [`MainEvent::Destroy`] event from `poll_events()`. Most
|
||||
//! [`AndroidApp`] methods will become a no-op after [`MainEvent::Destroy`] is
|
||||
//! received, since it no longer has an associated `Activity`.
|
||||
//!
|
||||
//! **Important**: Do *not* call `std::process::exit()` from your
|
||||
//! `android_main()` function since that will subvert the normal lifecycle of
|
||||
//! the `Activity` and other components. Keep in mind that code running in
|
||||
//! `android_main()` does not logically own the entire process since there may
|
||||
//! be other Android components (e.g. Services) running within the process.
|
||||
//!
|
||||
//! ## AndroidApp: State and Event Loop
|
||||
//!
|
||||
//! [`AndroidApp`] provides an interface to query state for the application as
|
||||
//! well as monitor events, such as lifecycle and input events, that are
|
||||
//! marshalled between the Java thread that owns the `Activity` and the native
|
||||
//! thread that runs the `android_main()` code.
|
||||
//! well as monitor events, such as lifecycle and input events for the
|
||||
//! associated native `Activity` instance.
|
||||
//!
|
||||
//! # Cheaply Clonable [`AndroidApp`]
|
||||
//! ### Cheaply Cloneable [`AndroidApp`]
|
||||
//!
|
||||
//! [`AndroidApp`] is intended to be something that can be cheaply passed around
|
||||
//! by referenced within an application. It is reference counted and can be
|
||||
//! cheaply cloned.
|
||||
//! within an application. It is reference-counted and can be cheaply cloned.
|
||||
//!
|
||||
//! # `Send` and `Sync` [`AndroidApp`]
|
||||
//! ### `Send` and `Sync` [`AndroidApp`] (**but...**)
|
||||
//!
|
||||
//! Although an [`AndroidApp`] implements `Send` and `Sync` you do need to take
|
||||
//! into consideration that some APIs, such as [`AndroidApp::poll_events()`] are
|
||||
//! explicitly documented to only be usable from your `android_main()` thread.
|
||||
//!
|
||||
//! # Main Thread Initialization
|
||||
//! ### No associated Activity after [`MainEvent::Destroy`]
|
||||
//!
|
||||
//! Before `android_main()` is called, the following application state
|
||||
//! is also initialized:
|
||||
//! After you receive a [`MainEvent::Destroy`] event from `poll_events()` then
|
||||
//! the [`AndroidApp`] will no longer have an associated `Activity` and most of
|
||||
//! its methods will become no-ops. You should return from `android_main()` as
|
||||
//! soon as possible after receiving a `Destroy` event since your native
|
||||
//! `Activity` no longer exists.
|
||||
//!
|
||||
//! 1. An I/O thread is spawned that will handle redirecting standard input
|
||||
//! and output to the Android log, visible via `logcat`.
|
||||
//! 2. A `JavaVM` and `Activity` instance will be associated with the [`ndk_context`] crate
|
||||
//! so that other, independent, Rust crates are able to find a JavaVM
|
||||
//! for making JNI calls.
|
||||
//! 3. The `JavaVM` will be attached to the native thread
|
||||
//! 4. A [Looper] is attached to the Rust native thread.
|
||||
//! If a new [`Activity`] instance is created after that then a new
|
||||
//! [`AndroidApp`] will be created for that new [`Activity`] instance and sent
|
||||
//! to a new call to `android_main()`.
|
||||
//!
|
||||
//!
|
||||
//! These are undone after `android_main()` returns
|
||||
//! **Important**: It's not recommended to store an [`AndroidApp`] as global
|
||||
//! static state and it should instead be passed around by reference within your
|
||||
//! application so it can be reliably dropped when the `Activity` is destroyed
|
||||
//! and you return from `android_main()`.
|
||||
//!
|
||||
//! # Android Extensible Enums
|
||||
// TODO: Move this to the NDK crate, which now implements this for most of the code?
|
||||
//!
|
||||
//! There are numerous enums in the `android-activity` API which are effectively
|
||||
//! bindings to enums declared in the Android SDK which need to be considered
|
||||
@@ -75,7 +275,7 @@
|
||||
//! build an application that might be installed on new versions of Android.
|
||||
//!
|
||||
//! This crate follows a convention of adding a hidden `__Unknown(u32)` variant
|
||||
//! to these enum to ensure we can always do lossless conversions between the
|
||||
//! to these enums to ensure we can always do lossless conversions between the
|
||||
//! integers from the SDK and our corresponding Rust enums. This can be
|
||||
//! important in case you need to pass certain variants back to the SDK
|
||||
//! regardless of whether you knew about that variants specific semantics at
|
||||
@@ -108,19 +308,25 @@
|
||||
//! ```
|
||||
//!
|
||||
//! [`Activity`]: https://developer.android.com/reference/android/app/Activity
|
||||
//! [`NativeActivity`]: https://developer.android.com/reference/android/app/NativeActivity
|
||||
//! [`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/integrate-game-activity
|
||||
//! [`Service`]: https://developer.android.com/reference/android/app/Service
|
||||
//! [Looper]: https://developer.android.com/reference/android/os/Looper
|
||||
//! [`Context`]: https://developer.android.com/reference/android/content/Context
|
||||
|
||||
#![deny(clippy::manual_let_else)]
|
||||
|
||||
use std::ffi::CStr;
|
||||
use std::hash::Hash;
|
||||
use std::sync::Arc;
|
||||
use std::sync::RwLock;
|
||||
use std::time::Duration;
|
||||
|
||||
use bitflags::bitflags;
|
||||
use jni::vm::JavaVM;
|
||||
use libc::c_void;
|
||||
|
||||
use ndk::asset::AssetManager;
|
||||
@@ -177,6 +383,8 @@ pub(crate) mod activity_impl;
|
||||
pub mod error;
|
||||
use error::Result;
|
||||
|
||||
mod init;
|
||||
|
||||
pub mod input;
|
||||
use input::KeyCharacterMap;
|
||||
|
||||
@@ -187,6 +395,15 @@ mod util;
|
||||
|
||||
mod jni_utils;
|
||||
|
||||
mod sdk;
|
||||
|
||||
mod waker;
|
||||
pub use waker::AndroidAppWaker;
|
||||
|
||||
mod main_callbacks;
|
||||
|
||||
pub(crate) const ANDROID_ACTIVITY_TAG: &CStr = c"android-activity";
|
||||
|
||||
/// A rectangle with integer edge coordinates. Used to represent window insets, for example.
|
||||
#[derive(Clone, Debug, Default, Eq, PartialEq)]
|
||||
pub struct Rect {
|
||||
@@ -344,7 +561,6 @@ pub enum InputStatus {
|
||||
}
|
||||
|
||||
use activity_impl::AndroidAppInner;
|
||||
pub use activity_impl::AndroidAppWaker;
|
||||
|
||||
bitflags! {
|
||||
/// Flags for [`AndroidApp::set_window_flags`]
|
||||
@@ -560,13 +776,29 @@ impl AndroidApp {
|
||||
self.inner.read().unwrap().native_window()
|
||||
}
|
||||
|
||||
/// Returns a [`ndk::looper::ForeignLooper`] associated with the Java
|
||||
/// main / UI thread.
|
||||
///
|
||||
/// This can be used to register file descriptors that may wake up the
|
||||
/// Java main / UI thread and optionally run callbacks on that thread.
|
||||
///
|
||||
/// ```ignore
|
||||
/// # use ndk;
|
||||
/// # let app: AndroidApp = todo!();
|
||||
/// let looper = app.java_main_looper();
|
||||
/// looper.add_fd_with_callback(todo!(), ndk::looper::FdEvent::INPUT, todo!()).unwrap();
|
||||
/// ```
|
||||
pub fn java_main_looper(&self) -> ndk::looper::ForeignLooper {
|
||||
self.inner.read().unwrap().java_main_looper().clone()
|
||||
}
|
||||
|
||||
/// Returns a pointer to the Java Virtual Machine, for making JNI calls
|
||||
///
|
||||
/// This returns a pointer to the Java Virtual Machine which can be used
|
||||
/// with the [`jni`] crate (or similar crates) to make JNI calls that bridge
|
||||
/// between native Rust code and Java/Kotlin code running within the JVM.
|
||||
///
|
||||
/// If you use the [`jni`] crate you can wrap this as a [`JavaVM`] via:
|
||||
/// If you use the [`jni`] crate you can could this as a [`JavaVM`] via:
|
||||
/// ```no_run
|
||||
/// # use jni::JavaVM;
|
||||
/// # let app: android_activity::AndroidApp = todo!();
|
||||
@@ -576,51 +808,87 @@ impl AndroidApp {
|
||||
/// [`jni`]: https://crates.io/crates/jni
|
||||
/// [`JavaVM`]: https://docs.rs/jni/latest/jni/struct.JavaVM.html
|
||||
pub fn vm_as_ptr(&self) -> *mut c_void {
|
||||
self.inner.read().unwrap().vm_as_ptr()
|
||||
JavaVM::singleton().unwrap().get_raw() as _
|
||||
}
|
||||
|
||||
/// Returns a JNI object reference for this application's JVM `Activity` as a pointer
|
||||
/// Returns an (*unowned*) JNI global object reference for this
|
||||
/// application's JVM `Activity` as a pointer
|
||||
///
|
||||
/// If you use the [`jni`] crate you can wrap this as an object reference via:
|
||||
/// If you use the [`jni`] crate you can cast this as a `JObject` reference
|
||||
/// via:
|
||||
/// ```no_run
|
||||
/// # use jni::objects::JObject;
|
||||
/// # let app: android_activity::AndroidApp = todo!();
|
||||
/// let activity = unsafe { JObject::from_raw(app.activity_as_ptr().cast()) };
|
||||
/// # use jni::refs::Global;
|
||||
/// # fn use_jni(env: &jni::Env, app: &android_activity::AndroidApp) -> jni::errors::Result<()> {
|
||||
/// let raw_activity_global = app.activity_as_ptr() as jni::sys::jobject;
|
||||
/// // SAFETY: The reference / pointer is valid as long as `app` is valid
|
||||
/// let activity = unsafe { env.as_cast_raw::<Global<JObject>>(&raw_activity_global)? };
|
||||
/// # Ok(()) }
|
||||
/// ```
|
||||
///
|
||||
/// # JNI Safety
|
||||
///
|
||||
/// Note that the object reference will be a JNI global reference, not a
|
||||
/// local reference and it should not be deleted. Don't wrap the reference
|
||||
/// in an [`AutoLocal`] which would try to explicitly delete the reference
|
||||
/// when dropped. Similarly, don't wrap the reference as a [`GlobalRef`]
|
||||
/// which would also try to explicitly delete the reference when dropped.
|
||||
/// Note that the returned reference will be a JNI global reference *that
|
||||
/// you do not own*.
|
||||
/// - Don't wrap the reference as a [`Global`] which would try to delete the
|
||||
/// reference when dropped.
|
||||
/// - Don't wrap the reference in an [`Auto`] which would treat the
|
||||
/// reference like a local reference and try to delete it when dropped.
|
||||
///
|
||||
/// The reference is only guaranteed to be valid until you drop the
|
||||
/// [`AndroidApp`].
|
||||
///
|
||||
/// **Warning:** Don't assume the returned reference has a `'static` lifetime
|
||||
/// since it's possible for `android_main()` to run multiple times over the
|
||||
/// lifetime of an application with a new `AndroidApp` instance each time.
|
||||
///
|
||||
/// [`jni`]: https://crates.io/crates/jni
|
||||
/// [`AutoLocal`]: https://docs.rs/jni/latest/jni/objects/struct.AutoLocal.html
|
||||
/// [`GlobalRef`]: https://docs.rs/jni/latest/jni/objects/struct.GlobalRef.html
|
||||
/// [`Auto`]: https://docs.rs/jni/latest/jni/refs/struct.Auto.html
|
||||
/// [`Global`]: https://docs.rs/jni/latest/jni/refs/struct.Global.html
|
||||
pub fn activity_as_ptr(&self) -> *mut c_void {
|
||||
self.inner.read().unwrap().activity_as_ptr()
|
||||
}
|
||||
|
||||
/// Polls for any events associated with this [AndroidApp] and processes those events
|
||||
/// (such as lifecycle events) via the given `callback`.
|
||||
/// Polls for any events associated with this [AndroidApp] and processes
|
||||
/// those events (such as lifecycle events) via the given `callback`.
|
||||
///
|
||||
/// It's important to use this API for polling, and not call [`ALooper_pollAll`] directly since
|
||||
/// some events require pre- and post-processing either side of the callback. For correct
|
||||
/// behavior events should be handled immediately, before returning from the callback and
|
||||
/// not simply queued for batch processing later. For example the existing [`NativeWindow`]
|
||||
/// is accessible during a [`MainEvent::TerminateWindow`] callback and will be
|
||||
/// set to `None` once the callback returns, and this is also synchronized with the Java
|
||||
/// main thread. The [`MainEvent::SaveState`] event is also synchronized with the
|
||||
/// It's important to use this API for polling, and not call
|
||||
/// [`ALooper_pollAll`] or [`ALooper_pollOnce`] directly since some events
|
||||
/// require pre- and post-processing either side of the callback. For
|
||||
/// correct behavior events should be handled immediately, before returning
|
||||
/// from the callback and not simply queued for batch processing later. For
|
||||
/// example the existing [`NativeWindow`] is accessible during a
|
||||
/// [`MainEvent::TerminateWindow`] callback and will be set to `None` once
|
||||
/// the callback returns, and this is also synchronized with the Java main
|
||||
/// thread. The [`MainEvent::SaveState`] event is also synchronized with the
|
||||
/// Java main thread.
|
||||
///
|
||||
/// Internally this is based on [`ALooper_pollOnce`] and will only poll
|
||||
/// file descriptors once per invocation.
|
||||
///
|
||||
/// # Wake Events
|
||||
///
|
||||
/// Note that although there is an explicit [PollEvent::Wake] that _can_
|
||||
/// indicate that the main loop was explicitly woken up (E.g. via
|
||||
/// [`AndroidAppWaker::wake`]) it's possible that there will be
|
||||
/// more-specific events that will be delivered after a wake up.
|
||||
///
|
||||
/// In other words you should only expect to explicitly see
|
||||
/// [`PollEvent::Wake`] events after an early wake up if there were no
|
||||
/// other, more-specific, events that could be delivered after the wake up.
|
||||
///
|
||||
/// Again, said another way - it's possible that _any_ event could
|
||||
/// effectively be delivered after an early wake up so don't assume there is
|
||||
/// a 1:1 relationship between invoking a wake up via
|
||||
/// [`AndroidAppWaker::wake`] and the delivery of [PollEvent::Wake].
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This must only be called from your `android_main()` thread and it may panic if called
|
||||
/// from another thread.
|
||||
/// This must only be called from your `android_main()` thread and it may
|
||||
/// panic if called from another thread.
|
||||
///
|
||||
/// [`ALooper_pollAll`]: ndk::looper::ThreadLooper::poll_all
|
||||
/// [`ALooper_pollOnce`]: ndk::looper::ThreadLooper::poll_once
|
||||
pub fn poll_events<F>(&self, timeout: Option<Duration>, callback: F)
|
||||
where
|
||||
F: FnMut(PollEvent<'_>),
|
||||
@@ -634,6 +902,91 @@ impl AndroidApp {
|
||||
self.inner.read().unwrap().create_waker()
|
||||
}
|
||||
|
||||
/// Runs the given closure on the Java main / UI thread.
|
||||
///
|
||||
/// This is useful for performing operations that must be executed on the
|
||||
/// main thread, such as interacting with Android SDK APIs that require
|
||||
/// execution on the main thread.
|
||||
///
|
||||
/// Any panic within the closure will be caught and logged as an error,
|
||||
/// (assuming your application is built to allow unwinding).
|
||||
///
|
||||
/// The thread will be attached to the JVM (for using JNI) and any
|
||||
/// un-cleared Java exceptions left over by the callback will be caught,
|
||||
/// cleared and logged as an error.
|
||||
///
|
||||
/// There is no built-in mechanism to propagate results back to the caller
|
||||
/// but you can use channels or other synchronization primitives that you
|
||||
/// capture.
|
||||
///
|
||||
/// It's important to avoid blocking the `android_main` thread while waiting
|
||||
/// for any results because this could lead to deadlocks for `Activity`
|
||||
/// callbacks that require a synchronous response for the `android_activity`
|
||||
/// thread.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// This example demonstrates using the `jni` 0.22 API to show a toast
|
||||
/// message from the Java main thread.
|
||||
///
|
||||
/// ```no_run
|
||||
/// use android_activity::AndroidApp;
|
||||
/// use jni::{objects::JString, refs::Global};
|
||||
///
|
||||
/// jni::bind_java_type! { Context => "android.content.Context" }
|
||||
/// jni::bind_java_type! {
|
||||
/// Activity => "android.app.Activity",
|
||||
/// type_map {
|
||||
/// Context => "android.content.Context",
|
||||
/// },
|
||||
/// is_instance_of {
|
||||
/// context: Context
|
||||
/// },
|
||||
/// }
|
||||
///
|
||||
/// jni::bind_java_type! {
|
||||
/// Toast => "android.widget.Toast",
|
||||
/// type_map {
|
||||
/// Context => "android.content.Context",
|
||||
/// },
|
||||
/// methods {
|
||||
/// static fn make_text(context: Context, text: JCharSequence, duration: i32) -> Toast,
|
||||
/// fn show(),
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// enum ToastDuration {
|
||||
/// Short = 0,
|
||||
/// Long = 1,
|
||||
/// }
|
||||
///
|
||||
/// fn send_toast(outer_app: &AndroidApp, msg: impl AsRef<str>, duration: ToastDuration) {
|
||||
/// let app = outer_app.clone();
|
||||
/// let msg = msg.as_ref().to_string();
|
||||
/// outer_app.run_on_java_main_thread(Box::new(move || {
|
||||
/// let jvm = unsafe { jni::JavaVM::from_raw(app.vm_as_ptr() as _) };
|
||||
/// // As an micro optimization you could use jvm.with_top_local_frame, since we know
|
||||
/// // we're already attached
|
||||
/// if let Err(err) = jvm.attach_current_thread(|env| -> jni::errors::Result<()> {
|
||||
/// let activity: jni::sys::jobject = app.activity_as_ptr() as _;
|
||||
/// let activity = unsafe { env.as_cast_raw::<Global<Activity>>(&activity)? };
|
||||
/// let message = JString::new(env, &msg)?;
|
||||
/// let toast = Toast::make_text(env, activity.as_ref(), &message, duration as i32)?;
|
||||
/// toast.show(env)?;
|
||||
/// Ok(())
|
||||
/// }) {
|
||||
/// log::error!("Failed to show toast on main thread: {err:?}");
|
||||
/// }
|
||||
/// }));
|
||||
/// }
|
||||
/// ```
|
||||
pub fn run_on_java_main_thread<F>(&self, f: Box<F>)
|
||||
where
|
||||
F: FnOnce() + Send + 'static,
|
||||
{
|
||||
self.inner.read().unwrap().run_on_java_main_thread(f);
|
||||
}
|
||||
|
||||
/// Returns a **reference** to this application's [`ndk::configuration::Configuration`].
|
||||
///
|
||||
/// # Warning
|
||||
@@ -652,9 +1005,28 @@ impl AndroidApp {
|
||||
self.inner.read().unwrap().content_rect()
|
||||
}
|
||||
|
||||
/// Queries the Asset Manager instance for the application.
|
||||
/// Returns the `AssetManager` for the application's `Application` context.
|
||||
///
|
||||
/// Use this to access binary assets bundled inside your application's .apk file.
|
||||
/// Use this to access raw files bundled in the application's .apk file.
|
||||
///
|
||||
/// This is an `Application`-scoped asset manager, not an `Activity`-scoped
|
||||
/// one. In normal usage those behave the same for packaged assets, so this
|
||||
/// is usually the correct API to use.
|
||||
///
|
||||
/// In uncommon cases, an `Activity` may have a context-specific
|
||||
/// asset/resource view that differs from the `Application` context. If you
|
||||
/// specifically need the current `Activity`'s `AssetManager`, obtain the
|
||||
/// `Activity` via [`AndroidApp::activity_as_ptr`] and call `getAssets()`
|
||||
/// through JNI.
|
||||
///
|
||||
/// The returned `AssetManager` has a `'static` lifetime and remains valid
|
||||
/// across `Activity` recreation, including when `android_main()` is
|
||||
/// re-entered.
|
||||
///
|
||||
/// **Beware**: If you consider accessing the `Activity` context's
|
||||
/// `AssetManager` through JNI you must keep the `AssetManager` alive via a
|
||||
/// global reference before accessing the ndk `AAssetManager` and
|
||||
/// `ndk::asset::AssetManager` does not currently handle this for you.
|
||||
pub fn asset_manager(&self) -> AssetManager {
|
||||
self.inner.read().unwrap().asset_manager()
|
||||
}
|
||||
@@ -722,6 +1094,23 @@ impl AndroidApp {
|
||||
self.inner.read().unwrap().set_text_input_state(state);
|
||||
}
|
||||
|
||||
/// Specify the type of text being input, how the IME enter/action key
|
||||
/// should behave and any additional IME options.
|
||||
///
|
||||
/// Also see the Android SDK documentation for
|
||||
/// [android.view.inputmethod.EditorInfo](https://developer.android.com/reference/android/view/inputmethod/EditorInfo)
|
||||
pub fn set_ime_editor_info(
|
||||
&self,
|
||||
input_type: input::InputType,
|
||||
action: input::TextInputAction,
|
||||
options: input::ImeOptions,
|
||||
) {
|
||||
self.inner
|
||||
.read()
|
||||
.unwrap()
|
||||
.set_ime_editor_info(input_type, action, options);
|
||||
}
|
||||
|
||||
/// Get an exclusive, lending iterator over buffered input events
|
||||
///
|
||||
/// Applications are expected to call this in-sync with their rendering or
|
||||
@@ -855,6 +1244,9 @@ impl AndroidApp {
|
||||
/// Since this API needs to use JNI internally to call into the Android JVM it may return
|
||||
/// a [`error::AppError::JavaError`] in case there is a spurious JNI error or an exception
|
||||
/// is caught.
|
||||
///
|
||||
/// This API should not be called with a `device_id` of `0`, since that indicates a non-physical
|
||||
/// device and will result in a [`error::AppError::JavaError`].
|
||||
pub fn device_key_character_map(&self, device_id: i32) -> Result<KeyCharacterMap> {
|
||||
Ok(self
|
||||
.inner
|
||||
@@ -897,3 +1289,81 @@ fn test_app_is_send_sync() {
|
||||
fn needs_send_sync<T: Send + Sync>() {}
|
||||
needs_send_sync::<AndroidApp>();
|
||||
}
|
||||
|
||||
/// The state passed to the optional `android_on_create` entry point if
|
||||
/// available.
|
||||
///
|
||||
/// This gives access to the Java VM, the Java `Activity` and any saved state
|
||||
/// from a previous instance of the `Activity` that was saved via the
|
||||
/// `onSaveInstanceState` callback.
|
||||
///
|
||||
/// Each time `android_on_create` is called it will receive a new `Activity`
|
||||
/// reference.
|
||||
///
|
||||
/// See the top-level [`android-activity`](crate) documentation for more details
|
||||
/// on `android_on_create`.
|
||||
pub struct OnCreateState<'a> {
|
||||
jvm: JavaVM,
|
||||
java_activity: *mut c_void,
|
||||
saved_state: &'a [u8],
|
||||
}
|
||||
|
||||
impl<'a> OnCreateState<'a> {
|
||||
pub(crate) fn new(jvm: JavaVM, java_activity: *mut c_void, saved_state: &'a [u8]) -> Self {
|
||||
Self {
|
||||
jvm,
|
||||
java_activity,
|
||||
saved_state,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a pointer to the Java Virtual Machine, for making JNI calls
|
||||
///
|
||||
/// If you use the `jni` crate, you can wrap this pointer as a `JavaVM` via:
|
||||
/// ```no_run
|
||||
/// # use jni::JavaVM;
|
||||
/// # let on_create_state: android_activity::OnCreateState = todo!();
|
||||
/// let vm = unsafe { JavaVM::from_raw(on_create_state.vm_as_ptr().cast()) };
|
||||
/// ```
|
||||
pub fn vm_as_ptr(&self) -> *mut c_void {
|
||||
self.jvm.get_raw().cast()
|
||||
}
|
||||
|
||||
/// Returns an (*unowned*) JNI global object reference for this `Activity`
|
||||
/// as a pointer
|
||||
///
|
||||
/// If you use the `jni` crate, you can cast this as a `JObject` reference
|
||||
/// via:
|
||||
///
|
||||
/// ```no_run
|
||||
/// # use jni::{JavaVM, objects::JObject};
|
||||
/// # let on_create_state: android_activity::OnCreateState = todo!();
|
||||
/// let vm = unsafe { JavaVM::from_raw(on_create_state.vm_as_ptr().cast()) };
|
||||
/// let _res = vm.attach_current_thread(|env| -> jni::errors::Result<()> {
|
||||
/// let activity = on_create_state.activity_as_ptr() as jni::sys::jobject;
|
||||
/// // SAFETY: The reference / pointer is valid at least until we return from `android_on_create`
|
||||
/// let activity = unsafe { env.as_cast_raw::<JObject>(&activity)? };
|
||||
/// // Do something with `activity` here
|
||||
/// Ok(())
|
||||
/// });
|
||||
/// ```
|
||||
///
|
||||
/// # JNI Safety
|
||||
///
|
||||
/// It is not specified whether this will be a global or local reference and
|
||||
/// in any case you must treat is as a reference that you do not own and
|
||||
/// must not attempt to delete it.
|
||||
/// - Don't wrap the reference as a `Global` which would try to delete the
|
||||
/// reference when dropped.
|
||||
/// - Don't wrap the reference in an `Auto` which would treat the reference
|
||||
/// like a local reference and try to delete it when dropped.
|
||||
pub fn activity_as_ptr(&self) -> *mut c_void {
|
||||
self.java_activity
|
||||
}
|
||||
|
||||
/// Returns the saved state of the `Activity` as a byte slice, which may be
|
||||
/// empty if there is no saved state.
|
||||
pub fn saved_state(&self) -> &[u8] {
|
||||
self.saved_state
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
})
|
||||
}
|
||||
@@ -9,11 +9,12 @@ use std::{
|
||||
sync::{Arc, Condvar, Mutex, Weak},
|
||||
};
|
||||
|
||||
use jni::{objects::JObject, refs::Global, vm::AttachConfig};
|
||||
use ndk::{configuration::Configuration, input_queue::InputQueue, native_window::NativeWindow};
|
||||
|
||||
use crate::{
|
||||
jni_utils::CloneJavaVM,
|
||||
util::{abort_on_panic, forward_stdio_to_logcat, log_panic},
|
||||
init::{init_android_main_thread, init_java_main_thread_on_create},
|
||||
util::{abort_on_panic, log_panic},
|
||||
ConfigurationRef,
|
||||
};
|
||||
|
||||
@@ -75,8 +76,6 @@ pub enum State {
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct WaitableNativeActivityState {
|
||||
pub activity: *mut ndk_sys::ANativeActivity,
|
||||
|
||||
pub mutex: Mutex<NativeActivityState>,
|
||||
pub cond: Condvar,
|
||||
}
|
||||
@@ -210,6 +209,29 @@ pub enum NativeThreadState {
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct NativeActivityState {
|
||||
/// Set as soon as the Java main thread notifies us of an `onDestroyed`
|
||||
/// callback.
|
||||
pub destroyed: bool,
|
||||
|
||||
/// The `ANativeActivity` associated with the NativeActivity instance
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// This pointer will be reset to `null` when the NativeActivity is
|
||||
/// destroyed.
|
||||
///
|
||||
/// Keep in mind that `NativeActivityState` is ref-counted and can
|
||||
/// potentially out-last an `onDestroy` callback where we may reset this to
|
||||
/// be a null pointer!
|
||||
///
|
||||
/// For example:
|
||||
/// - An application could put an `AndroidApp` into a global `'static` and
|
||||
/// keep it alive beyond `android_main`
|
||||
/// - An application could schedule a callback to run on the Java main
|
||||
/// thread with an `AndroidApp` clone and by the time it runs then the
|
||||
/// associated `ANativeActivity` could have been destroyed.
|
||||
pub activity: *mut ndk_sys::ANativeActivity,
|
||||
|
||||
pub msg_read: libc::c_int,
|
||||
pub msg_write: libc::c_int,
|
||||
pub config: ConfigurationRef,
|
||||
@@ -222,9 +244,6 @@ pub struct NativeActivityState {
|
||||
pub thread_state: NativeThreadState,
|
||||
pub app_has_saved_state: bool,
|
||||
|
||||
/// Set as soon as the Java main thread notifies us of an
|
||||
/// `onDestroyed` callback.
|
||||
pub destroyed: bool,
|
||||
pub pending_input_queue: *mut ndk_sys::AInputQueue,
|
||||
pub pending_window: Option<NativeWindow>,
|
||||
}
|
||||
@@ -310,7 +329,7 @@ impl NativeActivityState {
|
||||
|
||||
impl Drop for WaitableNativeActivityState {
|
||||
fn drop(&mut self) {
|
||||
log::debug!("WaitableNativeActivityState::drop!");
|
||||
log::info!("WaitableNativeActivityState::drop!");
|
||||
unsafe {
|
||||
let mut guard = self.mutex.lock().unwrap();
|
||||
guard.detach_input_queue_from_looper();
|
||||
@@ -357,8 +376,8 @@ impl WaitableNativeActivityState {
|
||||
};
|
||||
|
||||
Self {
|
||||
activity,
|
||||
mutex: Mutex::new(NativeActivityState {
|
||||
activity,
|
||||
msg_read: msgpipe[0],
|
||||
msg_write: msgpipe[1],
|
||||
config,
|
||||
@@ -392,6 +411,11 @@ impl WaitableNativeActivityState {
|
||||
guard.msg_read = -1;
|
||||
libc::close(guard.msg_write);
|
||||
guard.msg_write = -1;
|
||||
|
||||
// The last thing that `NativeActivity` `onDestroy` does is to call a
|
||||
// native method (`unloadNativeCode`) which will `delete` the
|
||||
// `ANativeActivity` instance.
|
||||
guard.activity = ptr::null_mut();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -605,7 +629,7 @@ impl WaitableNativeActivityState {
|
||||
AppCmd::ConfigChanged => {
|
||||
let guard = self.mutex.lock().unwrap();
|
||||
let config = ndk_sys::AConfiguration_new();
|
||||
ndk_sys::AConfiguration_fromAssetManager(config, (*self.activity).assetManager);
|
||||
ndk_sys::AConfiguration_fromAssetManager(config, (*guard.activity).assetManager);
|
||||
let config = Configuration::from_ptr(NonNull::new_unchecked(config));
|
||||
guard.config.replace(config);
|
||||
log::debug!("Config: {:#?}", guard.config);
|
||||
@@ -647,12 +671,19 @@ unsafe fn try_with_waitable_activity_ref(
|
||||
assert!(!(*activity).instance.is_null());
|
||||
let weak_ptr: *const WaitableNativeActivityState = (*activity).instance.cast();
|
||||
let weak_ref = Weak::from_raw(weak_ptr);
|
||||
if let Some(waitable_activity) = weak_ref.upgrade() {
|
||||
let maybe_upgraded = weak_ref.upgrade();
|
||||
|
||||
// Make sure we don't Drop the Weak reference (even if we failed to upgrade it
|
||||
// and also considering the possibility that we unwind due to a panic in `closure()`)
|
||||
// (The raw weak pointer associated with activity->instance must remain valid
|
||||
// until `on_destroy` is called).
|
||||
let _ = weak_ref.into_raw();
|
||||
|
||||
if let Some(waitable_activity) = maybe_upgraded {
|
||||
closure(waitable_activity);
|
||||
} else {
|
||||
log::error!("Ignoring spurious JVM callback after last activity reference was dropped!")
|
||||
}
|
||||
let _ = weak_ref.into_raw();
|
||||
}
|
||||
|
||||
unsafe extern "C" fn on_destroy(activity: *mut ndk_sys::ANativeActivity) {
|
||||
@@ -661,6 +692,16 @@ unsafe extern "C" fn on_destroy(activity: *mut ndk_sys::ANativeActivity) {
|
||||
try_with_waitable_activity_ref(activity, |waitable_activity| {
|
||||
waitable_activity.notify_destroyed()
|
||||
});
|
||||
|
||||
// Once we return from here the `ANativeActivity` will be deleted via an
|
||||
// `unloadNativeCode` native method and so we can't get any more
|
||||
// callbacks and we can release the `Weak<WaitableNativeActivityState>`
|
||||
// reference we have associated with `activity->instance`
|
||||
|
||||
assert!(!(*activity).instance.is_null());
|
||||
let weak_ptr: *const WaitableNativeActivityState = (*activity).instance.cast();
|
||||
let _drop_weak_ref = Weak::from_raw(weak_ptr);
|
||||
(*activity).instance = std::ptr::null_mut();
|
||||
})
|
||||
}
|
||||
|
||||
@@ -838,14 +879,22 @@ extern "C" fn ANativeActivity_onCreate(
|
||||
saved_state_size: libc::size_t,
|
||||
) {
|
||||
abort_on_panic(|| {
|
||||
let _join_log_forwarder = forward_stdio_to_logcat();
|
||||
let main_looper =
|
||||
ndk::looper::ForeignLooper::for_thread().expect("Failed to get Java main looper");
|
||||
|
||||
log::trace!(
|
||||
"Creating: {:p}, saved_state = {:p}, save_state_size = {}",
|
||||
activity,
|
||||
saved_state,
|
||||
saved_state_size
|
||||
);
|
||||
let (jvm, jni_activity) = unsafe {
|
||||
let jvm: *mut jni::sys::JavaVM = (*activity).vm.cast();
|
||||
let jni_activity: jni::sys::jobject = (*activity).clazz as _; // Completely bogus name; this is the _instance_ not class pointer
|
||||
(jni::JavaVM::from_raw(jvm), jni_activity)
|
||||
};
|
||||
unsafe {
|
||||
let saved_state = if !saved_state.is_null() && saved_state_size > 0 {
|
||||
std::slice::from_raw_parts(saved_state.cast(), saved_state_size)
|
||||
} else {
|
||||
&[]
|
||||
};
|
||||
init_java_main_thread_on_create(jvm, jni_activity as _, saved_state);
|
||||
};
|
||||
|
||||
// Conceptually we associate a glue reference with the JVM main thread, and another
|
||||
// reference with the Rust main thread
|
||||
@@ -858,60 +907,7 @@ extern "C" fn ANativeActivity_onCreate(
|
||||
// Note: we drop the thread handle which will detach the thread
|
||||
std::thread::spawn(move || {
|
||||
let activity: *mut ndk_sys::ANativeActivity = activity_ptr as *mut _;
|
||||
|
||||
let jvm = abort_on_panic(|| unsafe {
|
||||
let na = activity;
|
||||
let jvm: *mut jni_sys::JavaVM = (*na).vm;
|
||||
let activity = (*na).clazz; // Completely bogus name; this is the _instance_ not class pointer
|
||||
ndk_context::initialize_android_context(jvm.cast(), activity.cast());
|
||||
|
||||
let jvm = CloneJavaVM::from_raw(jvm).unwrap();
|
||||
// Since this is a newly spawned thread then the JVM hasn't been attached
|
||||
// to the thread yet. Attach before calling the applications main function
|
||||
// so they can safely make JNI calls
|
||||
jvm.attach_current_thread_permanently().unwrap();
|
||||
jvm
|
||||
});
|
||||
|
||||
let app = AndroidApp::new(rust_glue.clone(), jvm.clone());
|
||||
|
||||
rust_glue.notify_main_thread_running();
|
||||
|
||||
unsafe {
|
||||
// Name thread - this needs to happen here after attaching to a JVM thread,
|
||||
// since that changes the thread name to something like "Thread-2".
|
||||
let thread_name = std::ffi::CStr::from_bytes_with_nul(b"android_main\0").unwrap();
|
||||
libc::pthread_setname_np(libc::pthread_self(), thread_name.as_ptr());
|
||||
|
||||
// We want to specifically catch any panic from the application's android_main
|
||||
// so we can finish + destroy the Activity gracefully via the JVM
|
||||
catch_unwind(|| {
|
||||
// XXX: If we were in control of the Java Activity subclass then
|
||||
// we could potentially run the android_main function via a Java native method
|
||||
// springboard (e.g. call an Activity subclass method that calls a jni native
|
||||
// method that then just calls android_main()) that would make sure there was
|
||||
// a Java frame at the base of our call stack which would then be recognised
|
||||
// when calling FindClass to lookup a suitable classLoader, instead of
|
||||
// defaulting to the system loader. Without this then it's difficult for native
|
||||
// code to look up non-standard Java classes.
|
||||
android_main(app);
|
||||
})
|
||||
.unwrap_or_else(log_panic);
|
||||
|
||||
// Let JVM know that our Activity can be destroyed before detaching from the JVM
|
||||
//
|
||||
// "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 finish call will take place"
|
||||
ndk_sys::ANativeActivity_finish(activity);
|
||||
|
||||
// This should detach automatically but lets detach explicitly to avoid depending
|
||||
// on the TLS trickery in `jni-rs`
|
||||
jvm.detach_current_thread();
|
||||
|
||||
ndk_context::release_android_context();
|
||||
}
|
||||
|
||||
rust_glue.notify_main_thread_stopped_running();
|
||||
rust_glue_entry(rust_glue, activity, main_looper);
|
||||
});
|
||||
|
||||
// Wait for thread to start.
|
||||
@@ -924,3 +920,84 @@ extern "C" fn ANativeActivity_onCreate(
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn rust_glue_entry(
|
||||
rust_glue: NativeActivityGlue,
|
||||
activity: *mut ndk_sys::ANativeActivity,
|
||||
main_looper: ndk::looper::ForeignLooper,
|
||||
) {
|
||||
abort_on_panic(|| {
|
||||
let (jvm, jni_activity) = unsafe {
|
||||
let jvm: *mut jni::sys::JavaVM = (*activity).vm.cast();
|
||||
let jni_activity: jni::sys::jobject = (*activity).clazz as _; // Completely bogus name; this is the _instance_ not class pointer
|
||||
(jni::JavaVM::from_raw(jvm), jni_activity)
|
||||
};
|
||||
// Note: At this point we can assume jni::JavaVM::singleton is initialized
|
||||
|
||||
// Since this is a newly spawned thread then the JVM hasn't been attached to the
|
||||
// thread yet.
|
||||
//
|
||||
// For compatibility we attach before calling the applications main function to
|
||||
// allow it to assume the thread is attached before making JNI calls.
|
||||
jvm.attach_current_thread_with_config(
|
||||
|| AttachConfig::new().thread_name(jni::jni_str!("android_main")),
|
||||
Some(16),
|
||||
|env| -> jni::errors::Result<()> {
|
||||
// SAFETY: We know jni_activity is a valid JNI global ref to an Activity instance
|
||||
// that will remain valid until `onDestroy` is handled (not possible until we start
|
||||
// `android_main()`).
|
||||
let jni_activity = unsafe { env.as_cast_raw::<Global<JObject>>(&jni_activity)? };
|
||||
|
||||
let (app_asset_manager, main_callbacks) =
|
||||
match init_android_main_thread(&jvm, &jni_activity, &main_looper) {
|
||||
Ok((asset_manager, callbacks)) => (asset_manager, callbacks),
|
||||
Err(err) => {
|
||||
eprintln!(
|
||||
"Failed to name Java thread and set thread context class loader: {err}"
|
||||
);
|
||||
return Err(err);
|
||||
}
|
||||
};
|
||||
|
||||
let app = AndroidApp::new(
|
||||
jvm.clone(),
|
||||
main_looper,
|
||||
main_callbacks,
|
||||
app_asset_manager,
|
||||
rust_glue.clone(),
|
||||
&jni_activity,
|
||||
);
|
||||
|
||||
rust_glue.notify_main_thread_running();
|
||||
|
||||
unsafe {
|
||||
// We want to specifically catch any panic from the application's android_main
|
||||
// so we can finish + destroy the Activity gracefully via the JVM
|
||||
catch_unwind(|| {
|
||||
// XXX: If we were in control of the Java Activity subclass then
|
||||
// we could potentially run the android_main function via a Java native method
|
||||
// springboard (e.g. call an Activity subclass method that calls a jni native
|
||||
// method that then just calls android_main()) that would make sure there was
|
||||
// a Java frame at the base of our call stack which would then be recognised
|
||||
// when calling FindClass to lookup a suitable classLoader, instead of
|
||||
// defaulting to the system loader. Without this then it's difficult for native
|
||||
// code to look up non-standard Java classes.
|
||||
android_main(app);
|
||||
})
|
||||
.unwrap_or_else(log_panic);
|
||||
|
||||
// Let JVM know that our Activity can be destroyed before detaching from the JVM
|
||||
//
|
||||
// "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 finish call will take place"
|
||||
ndk_sys::ANativeActivity_finish(activity);
|
||||
}
|
||||
|
||||
rust_glue.notify_main_thread_stopped_running();
|
||||
|
||||
Ok(())
|
||||
},
|
||||
)
|
||||
.expect("Failed to attach thread to JVM");
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use std::marker::PhantomData;
|
||||
use std::{iter::FusedIterator, marker::PhantomData, ptr::NonNull};
|
||||
|
||||
use crate::input::{
|
||||
Axis, Button, ButtonState, EdgeFlags, KeyAction, Keycode, MetaState, MotionAction,
|
||||
@@ -111,7 +111,10 @@ impl MotionEvent<'_> {
|
||||
pub fn pointers(&self) -> PointersIter<'_> {
|
||||
PointersIter {
|
||||
inner: PointersIterImpl {
|
||||
ndk_pointers_iter: self.ndk_event.pointers(),
|
||||
event: self.ndk_event.ptr(),
|
||||
pointer_index: 0,
|
||||
pointer_count: self.ndk_event.pointer_count(),
|
||||
_marker: std::marker::PhantomData,
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -123,31 +126,13 @@ impl MotionEvent<'_> {
|
||||
pub fn pointer_at_index(&self, index: usize) -> Pointer<'_> {
|
||||
Pointer {
|
||||
inner: PointerImpl {
|
||||
ndk_pointer: self.ndk_event.pointer_at_index(index),
|
||||
event: self.ndk_event.ptr(),
|
||||
pointer_index: index,
|
||||
_marker: std::marker::PhantomData,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
XXX: Not currently supported with GameActivity so we don't currently expose for NativeActivity
|
||||
either, for consistency.
|
||||
|
||||
/// Returns the size of the history contained in this event.
|
||||
///
|
||||
/// See [the NDK
|
||||
/// docs](https://developer.android.com/ndk/reference/group/input#amotionevent_gethistorysize)
|
||||
#[inline]
|
||||
pub fn history_size(&self) -> usize {
|
||||
self.ndk_event.history_size()
|
||||
}
|
||||
|
||||
/// An iterator over the historical events contained in this event.
|
||||
#[inline]
|
||||
pub fn history(&self) -> HistoricalMotionEventsIter<'_> {
|
||||
self.ndk_event.history()
|
||||
}
|
||||
*/
|
||||
|
||||
/// Returns the state of any modifier keys that were pressed during the event.
|
||||
///
|
||||
/// See [the NDK
|
||||
@@ -245,69 +230,200 @@ impl MotionEvent<'_> {
|
||||
/// A view into the data of a specific pointer in a motion event.
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct PointerImpl<'a> {
|
||||
ndk_pointer: ndk::event::Pointer<'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.ndk_pointer.pointer_index()
|
||||
self.pointer_index
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn pointer_id(&self) -> i32 {
|
||||
self.ndk_pointer.pointer_id()
|
||||
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;
|
||||
self.ndk_pointer.axis_value(value.into())
|
||||
unsafe {
|
||||
ndk_sys::AMotionEvent_getAxisValue(self.event.as_ptr(), value, self.pointer_index)
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn raw_x(&self) -> f32 {
|
||||
self.ndk_pointer.raw_x()
|
||||
unsafe { ndk_sys::AMotionEvent_getRawX(self.event.as_ptr(), self.pointer_index) }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn raw_y(&self) -> f32 {
|
||||
self.ndk_pointer.raw_y()
|
||||
unsafe { ndk_sys::AMotionEvent_getRawY(self.event.as_ptr(), self.pointer_index) }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn tool_type(&self) -> ToolType {
|
||||
let value: i32 = self.ndk_pointer.tool_type().into();
|
||||
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,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An iterator over the pointers in a [`MotionEvent`].
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct PointersIterImpl<'a> {
|
||||
ndk_pointers_iter: ndk::event::PointersIter<'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>> {
|
||||
self.ndk_pointers_iter.next().map(|ndk_pointer| Pointer {
|
||||
inner: PointerImpl { ndk_pointer },
|
||||
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>) {
|
||||
self.ndk_pointers_iter.size_hint()
|
||||
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;
|
||||
}
|
||||
|
||||
impl ExactSizeIterator for PointersIterImpl<'_> {
|
||||
fn len(&self) -> usize {
|
||||
self.ndk_pointers_iter.len()
|
||||
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
|
||||
///
|
||||
|
||||
@@ -2,24 +2,29 @@ use std::collections::HashMap;
|
||||
use std::marker::PhantomData;
|
||||
use std::panic::AssertUnwindSafe;
|
||||
use std::ptr;
|
||||
use std::ptr::NonNull;
|
||||
use std::sync::{Arc, Mutex, RwLock, Weak};
|
||||
use std::time::Duration;
|
||||
|
||||
use jni::objects::JObject;
|
||||
use jni::JavaVM;
|
||||
use libc::c_void;
|
||||
use log::{error, trace};
|
||||
use ndk::input_queue::InputQueue;
|
||||
use ndk::{asset::AssetManager, native_window::NativeWindow};
|
||||
|
||||
use crate::error::InternalResult;
|
||||
use crate::input::{Axis, KeyCharacterMap, KeyCharacterMapBinding};
|
||||
use crate::input::{TextInputState, TextSpan};
|
||||
use crate::jni_utils::{self, CloneJavaVM};
|
||||
use crate::main_callbacks::MainCallbacks;
|
||||
use crate::sdk::{Activity, Context, InputMethodManager};
|
||||
use crate::{
|
||||
util, AndroidApp, ConfigurationRef, InputStatus, MainEvent, PollEvent, Rect, WindowManagerFlags,
|
||||
util, AndroidApp, AndroidAppWaker, ConfigurationRef, InputStatus, MainEvent, PollEvent, Rect,
|
||||
WindowManagerFlags,
|
||||
};
|
||||
|
||||
pub mod input;
|
||||
use crate::input::{
|
||||
device_key_character_map, Axis, ImeOptions, InputType, KeyCharacterMap, TextInputAction,
|
||||
TextInputState, TextSpan,
|
||||
};
|
||||
|
||||
mod glue;
|
||||
use self::glue::NativeActivityGlue;
|
||||
@@ -58,95 +63,87 @@ impl StateLoader<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
/// A means to wake up the main thread while it is blocked waiting for I/O
|
||||
#[derive(Clone)]
|
||||
pub struct AndroidAppWaker {
|
||||
// The looper pointer is owned by the android_app and effectively
|
||||
// has a 'static lifetime, and the ALooper_wake C API is thread
|
||||
// safe, so this can be cloned safely and is send + sync safe
|
||||
looper: NonNull<ndk_sys::ALooper>,
|
||||
}
|
||||
unsafe impl Send for AndroidAppWaker {}
|
||||
unsafe impl Sync for AndroidAppWaker {}
|
||||
|
||||
impl AndroidAppWaker {
|
||||
/// 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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AndroidApp {
|
||||
pub(crate) fn new(native_activity: NativeActivityGlue, jvm: CloneJavaVM) -> Self {
|
||||
let mut env = jvm.get_env().unwrap(); // We attach to the thread before creating the AndroidApp
|
||||
pub(crate) fn new(
|
||||
jvm: JavaVM,
|
||||
main_looper: ndk::looper::ForeignLooper,
|
||||
main_callbacks: MainCallbacks,
|
||||
app_asset_manager: AssetManager,
|
||||
native_activity: NativeActivityGlue,
|
||||
jni_activity: &JObject,
|
||||
) -> Self {
|
||||
jvm.with_local_frame(10, |env| -> jni::errors::Result<_> {
|
||||
if let Err(err) = crate::sdk::jni_init(env) {
|
||||
panic!("Failed to init JNI bindings: {err:?}");
|
||||
};
|
||||
|
||||
let key_map_binding = match KeyCharacterMapBinding::new(&mut env) {
|
||||
Ok(b) => b,
|
||||
Err(err) => {
|
||||
panic!("Failed to create KeyCharacterMap JNI bindings: {err:?}");
|
||||
}
|
||||
};
|
||||
|
||||
let app = Self {
|
||||
inner: Arc::new(RwLock::new(AndroidAppInner {
|
||||
jvm,
|
||||
native_activity,
|
||||
looper: Looper {
|
||||
ptr: ptr::null_mut(),
|
||||
},
|
||||
key_map_binding: Arc::new(key_map_binding),
|
||||
key_maps: Mutex::new(HashMap::new()),
|
||||
input_receiver: Mutex::new(None),
|
||||
})),
|
||||
};
|
||||
|
||||
{
|
||||
let mut guard = app.inner.write().unwrap();
|
||||
|
||||
let main_fd = guard.native_activity.cmd_read_fd();
|
||||
unsafe {
|
||||
guard.looper.ptr = ndk_sys::ALooper_prepare(
|
||||
let looper = unsafe {
|
||||
let ptr = ndk_sys::ALooper_prepare(
|
||||
ndk_sys::ALOOPER_PREPARE_ALLOW_NON_CALLBACKS as libc::c_int,
|
||||
);
|
||||
ndk_sys::ALooper_addFd(
|
||||
guard.looper.ptr,
|
||||
main_fd,
|
||||
LOOPER_ID_MAIN,
|
||||
ndk_sys::ALOOPER_EVENT_INPUT as libc::c_int,
|
||||
None,
|
||||
//&mut guard.cmd_poll_source as *mut _ as *mut _);
|
||||
ptr::null_mut(),
|
||||
);
|
||||
}
|
||||
}
|
||||
ndk::looper::ForeignLooper::from_ptr(ptr::NonNull::new(ptr).unwrap())
|
||||
};
|
||||
|
||||
app
|
||||
// The global reference in `ANativeActivity` is only guaranteed to be valid until
|
||||
// `onDestroy` returns, so we create our own global reference that we can guarantee will
|
||||
// remain valid until `AndroidApp` is dropped.
|
||||
let activity = env
|
||||
.new_global_ref(jni_activity)
|
||||
.expect("Failed to create global ref for Activity instance");
|
||||
|
||||
let app = Self {
|
||||
inner: Arc::new(RwLock::new(AndroidAppInner {
|
||||
jvm: jvm.clone(),
|
||||
main_looper,
|
||||
main_callbacks,
|
||||
app_asset_manager,
|
||||
native_activity,
|
||||
activity,
|
||||
looper,
|
||||
key_maps: Mutex::new(HashMap::new()),
|
||||
input_receiver: Mutex::new(None),
|
||||
})),
|
||||
};
|
||||
|
||||
{
|
||||
let guard = app.inner.write().unwrap();
|
||||
|
||||
let main_fd = guard.native_activity.cmd_read_fd();
|
||||
unsafe {
|
||||
ndk_sys::ALooper_addFd(
|
||||
guard.looper.ptr().as_ptr(),
|
||||
main_fd,
|
||||
LOOPER_ID_MAIN,
|
||||
ndk_sys::ALOOPER_EVENT_INPUT as libc::c_int,
|
||||
None,
|
||||
//&mut guard.cmd_poll_source as *mut _ as *mut _);
|
||||
ptr::null_mut(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(app)
|
||||
})
|
||||
.expect("Failed to create AndroidApp instance")
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Looper {
|
||||
pub ptr: *mut ndk_sys::ALooper,
|
||||
}
|
||||
unsafe impl Send for Looper {}
|
||||
unsafe impl Sync for Looper {}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct AndroidAppInner {
|
||||
pub(crate) jvm: CloneJavaVM,
|
||||
pub(crate) jvm: JavaVM,
|
||||
|
||||
pub(crate) native_activity: NativeActivityGlue,
|
||||
looper: Looper,
|
||||
|
||||
/// Shared JNI bindings for the `KeyCharacterMap` class
|
||||
key_map_binding: Arc<KeyCharacterMapBinding>,
|
||||
activity: jni::refs::Global<jni::objects::JObject<'static>>,
|
||||
|
||||
main_callbacks: MainCallbacks,
|
||||
|
||||
/// Looper associated with the Rust `android_main` thread
|
||||
looper: ndk::looper::ForeignLooper,
|
||||
|
||||
/// Looper associated with the activity's Java main thread, sometimes called
|
||||
/// the UI thread.
|
||||
main_looper: ndk::looper::ForeignLooper,
|
||||
|
||||
/// A table of `KeyCharacterMap`s per `InputDevice` ID
|
||||
/// these are used to be able to map key presses to unicode
|
||||
@@ -157,24 +154,30 @@ pub(crate) struct AndroidAppInner {
|
||||
/// InputReceiver reference which we track to ensure
|
||||
/// we don't hand out more than one receiver at a time
|
||||
input_receiver: Mutex<Option<Weak<InputReceiver>>>,
|
||||
|
||||
/// An `AAssetManager` wrapper for the `Application` `AssetManager`
|
||||
/// Note: `AAssetManager_fromJava` specifies that the pointer is only valid
|
||||
/// while we hold a global reference to the `AssetManager` Java object
|
||||
/// to ensure it is not garbage collected. This AssetManager comes from
|
||||
/// a OnceLock initialization that leaks a single global JNI reference
|
||||
/// to guarantee that it remains valid for the lifetime of the process.
|
||||
app_asset_manager: AssetManager,
|
||||
}
|
||||
|
||||
impl AndroidAppInner {
|
||||
pub(crate) fn vm_as_ptr(&self) -> *mut c_void {
|
||||
unsafe { (*self.native_activity.activity).vm as _ }
|
||||
}
|
||||
|
||||
pub(crate) fn activity_as_ptr(&self) -> *mut c_void {
|
||||
// "clazz" is a completely bogus name; this is the _instance_ not class pointer
|
||||
unsafe { (*self.native_activity.activity).clazz as _ }
|
||||
// Note: The global reference in `ANativeActivity::clazz` (misnomer for instance reference)
|
||||
// is only guaranteed to be valid until `onDestroy` returns, so we have our own global
|
||||
// reference that we can instead guarantee will remain valid until `AndroidApp` is dropped.
|
||||
self.activity.as_raw() as *mut c_void
|
||||
}
|
||||
|
||||
pub(crate) fn native_activity(&self) -> *const ndk_sys::ANativeActivity {
|
||||
self.native_activity.activity
|
||||
pub(crate) fn looper_as_ptr(&self) -> *mut ndk_sys::ALooper {
|
||||
self.looper.ptr().as_ptr()
|
||||
}
|
||||
|
||||
pub(crate) fn looper(&self) -> *mut ndk_sys::ALooper {
|
||||
self.looper.ptr
|
||||
pub fn java_main_looper(&self) -> ndk::looper::ForeignLooper {
|
||||
self.main_looper.clone()
|
||||
}
|
||||
|
||||
pub fn native_window(&self) -> Option<NativeWindow> {
|
||||
@@ -198,42 +201,42 @@ impl AndroidAppInner {
|
||||
-1
|
||||
};
|
||||
|
||||
trace!("Calling ALooper_pollAll, timeout = {timeout_milliseconds}");
|
||||
trace!("Calling ALooper_pollOnce, timeout = {timeout_milliseconds}");
|
||||
assert_eq!(
|
||||
ndk_sys::ALooper_forThread(),
|
||||
self.looper.ptr,
|
||||
self.looper_as_ptr(),
|
||||
"Application tried to poll events from non-main thread"
|
||||
);
|
||||
let id = ndk_sys::ALooper_pollAll(
|
||||
let id = ndk_sys::ALooper_pollOnce(
|
||||
timeout_milliseconds,
|
||||
&mut fd,
|
||||
&mut events,
|
||||
&mut source as *mut *mut c_void,
|
||||
);
|
||||
trace!("pollAll id = {id}");
|
||||
trace!("pollOnce id = {id}");
|
||||
match id {
|
||||
ndk_sys::ALOOPER_POLL_WAKE => {
|
||||
trace!("ALooper_pollAll returned POLL_WAKE");
|
||||
trace!("ALooper_pollOnce returned POLL_WAKE");
|
||||
callback(PollEvent::Wake);
|
||||
}
|
||||
ndk_sys::ALOOPER_POLL_CALLBACK => {
|
||||
// ALooper_pollAll is documented to handle all callback sources internally so it should
|
||||
// ALooper_pollOnce is documented to handle all callback sources internally so it should
|
||||
// never return a _CALLBACK source id...
|
||||
error!("Spurious ALOOPER_POLL_CALLBACK from ALopper_pollAll() (ignored)");
|
||||
error!("Spurious ALOOPER_POLL_CALLBACK from ALooper_pollOnce() (ignored)");
|
||||
}
|
||||
ndk_sys::ALOOPER_POLL_TIMEOUT => {
|
||||
trace!("ALooper_pollAll returned POLL_TIMEOUT");
|
||||
trace!("ALooper_pollOnce returned POLL_TIMEOUT");
|
||||
callback(PollEvent::Timeout);
|
||||
}
|
||||
ndk_sys::ALOOPER_POLL_ERROR => {
|
||||
// If we have an IO error with our pipe to the main Java thread that's surely
|
||||
// not something we can recover from
|
||||
panic!("ALooper_pollAll returned POLL_ERROR");
|
||||
panic!("ALooper_pollOnce returned POLL_ERROR");
|
||||
}
|
||||
id if id >= 0 => {
|
||||
match id {
|
||||
LOOPER_ID_MAIN => {
|
||||
trace!("ALooper_pollAll returned ID_MAIN");
|
||||
trace!("ALooper_pollOnce returned ID_MAIN");
|
||||
if let Some(ipc_cmd) = self.native_activity.read_cmd() {
|
||||
let main_cmd = match ipc_cmd {
|
||||
// We don't forward info about the AInputQueue to apps since it's
|
||||
@@ -273,7 +276,7 @@ impl AndroidAppInner {
|
||||
trace!("Calling pre_exec_cmd({ipc_cmd:#?})");
|
||||
self.native_activity.pre_exec_cmd(
|
||||
ipc_cmd,
|
||||
self.looper(),
|
||||
self.looper_as_ptr(),
|
||||
LOOPER_ID_INPUT,
|
||||
);
|
||||
|
||||
@@ -287,7 +290,7 @@ impl AndroidAppInner {
|
||||
}
|
||||
}
|
||||
LOOPER_ID_INPUT => {
|
||||
trace!("ALooper_pollAll returned ID_INPUT");
|
||||
trace!("ALooper_pollOnce returned ID_INPUT");
|
||||
|
||||
// To avoid spamming the application with event loop iterations notifying them of
|
||||
// input events then we only send one `InputAvailable` per iteration of input
|
||||
@@ -302,20 +305,22 @@ impl AndroidAppInner {
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
error!("Spurious ALooper_pollAll return value {id} (ignored)");
|
||||
error!("Spurious ALooper_pollOnce return value {id} (ignored)");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_waker(&self) -> AndroidAppWaker {
|
||||
unsafe {
|
||||
// From the application's pov we assume the looper pointer has a static
|
||||
// lifetimes and we can safely assume it is never NULL.
|
||||
AndroidAppWaker {
|
||||
looper: NonNull::new_unchecked(self.looper.ptr),
|
||||
}
|
||||
}
|
||||
// Safety: we know that the looper is a valid, non-null pointer
|
||||
unsafe { AndroidAppWaker::new(self.looper_as_ptr()) }
|
||||
}
|
||||
|
||||
pub fn run_on_java_main_thread<F>(&self, f: Box<F>)
|
||||
where
|
||||
F: FnOnce() + Send + 'static,
|
||||
{
|
||||
self.main_callbacks.run_on_java_main_thread(f);
|
||||
}
|
||||
|
||||
pub fn config(&self) -> ConfigurationRef {
|
||||
@@ -327,11 +332,11 @@ impl AndroidAppInner {
|
||||
}
|
||||
|
||||
pub fn asset_manager(&self) -> AssetManager {
|
||||
unsafe {
|
||||
let activity_ptr = self.native_activity.activity;
|
||||
let am_ptr = NonNull::new_unchecked((*activity_ptr).assetManager);
|
||||
AssetManager::from_ptr(am_ptr)
|
||||
}
|
||||
// Safety: While constructing the AndroidApp we do a OnceLock initialization
|
||||
// where we get the Application AssetManager and leak a single global JNI
|
||||
// reference that guarantees it will not be garbage collected, so we can
|
||||
// safely return the corresponding AAssetManager here.
|
||||
unsafe { AssetManager::from_ptr(self.app_asset_manager.ptr()) }
|
||||
}
|
||||
|
||||
pub fn set_window_flags(
|
||||
@@ -339,8 +344,14 @@ impl AndroidAppInner {
|
||||
add_flags: WindowManagerFlags,
|
||||
remove_flags: WindowManagerFlags,
|
||||
) {
|
||||
let na = self.native_activity();
|
||||
let na_mut = na as *mut ndk_sys::ANativeActivity;
|
||||
let guard = self.native_activity.mutex.lock().unwrap();
|
||||
let na = guard.activity;
|
||||
if na.is_null() {
|
||||
log::error!("Can't set window flags after NativeActivity has been destroyed");
|
||||
return;
|
||||
}
|
||||
|
||||
let na_mut = na;
|
||||
unsafe {
|
||||
ndk_sys::ANativeActivity_setWindowFlags(
|
||||
na_mut.cast(),
|
||||
@@ -352,27 +363,71 @@ impl AndroidAppInner {
|
||||
|
||||
// TODO: move into a trait
|
||||
pub fn show_soft_input(&self, show_implicit: bool) {
|
||||
let na = self.native_activity();
|
||||
unsafe {
|
||||
let flags = if show_implicit {
|
||||
ndk_sys::ANATIVEACTIVITY_SHOW_SOFT_INPUT_IMPLICIT
|
||||
} else {
|
||||
0
|
||||
};
|
||||
ndk_sys::ANativeActivity_showSoftInput(na as *mut _, flags);
|
||||
let guard = self.native_activity.mutex.lock().unwrap();
|
||||
let na = guard.activity;
|
||||
if na.is_null() {
|
||||
log::error!("Can't show soft input after NativeActivity has been destroyed");
|
||||
return;
|
||||
}
|
||||
|
||||
// Note: `.attach_current_thread()` will also handle catching any Java exceptions that
|
||||
// might be thrown by the JNI calls we make.
|
||||
let res = self
|
||||
.jvm
|
||||
.attach_current_thread(|env| -> jni::errors::Result<()> {
|
||||
let activity = env.as_cast::<Activity>(self.activity.as_ref())?;
|
||||
|
||||
let ims = Context::INPUT_METHOD_SERVICE(env)?;
|
||||
let im_manager = activity.as_context().get_system_service(env, ims)?;
|
||||
let im_manager = InputMethodManager::cast_local(env, im_manager)?;
|
||||
let jni_window = activity.get_window(env)?;
|
||||
let view = jni_window.get_decor_view(env)?;
|
||||
let flags = if show_implicit {
|
||||
ndk_sys::ANATIVEACTIVITY_SHOW_SOFT_INPUT_IMPLICIT as i32
|
||||
} else {
|
||||
0
|
||||
};
|
||||
im_manager.show_soft_input(env, view, flags)?;
|
||||
Ok(())
|
||||
});
|
||||
if let Err(err) = res {
|
||||
log::warn!("Failed to show soft input: {err:?}");
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: move into a trait
|
||||
pub fn hide_soft_input(&self, hide_implicit_only: bool) {
|
||||
let na = self.native_activity();
|
||||
unsafe {
|
||||
let flags = if hide_implicit_only {
|
||||
ndk_sys::ANATIVEACTIVITY_HIDE_SOFT_INPUT_IMPLICIT_ONLY
|
||||
} else {
|
||||
0
|
||||
};
|
||||
ndk_sys::ANativeActivity_hideSoftInput(na as *mut _, flags);
|
||||
let guard = self.native_activity.mutex.lock().unwrap();
|
||||
let na = guard.activity;
|
||||
if na.is_null() {
|
||||
log::error!("Can't hide soft input after NativeActivity has been destroyed");
|
||||
return;
|
||||
}
|
||||
|
||||
// Note: `.attach_current_thread()` will also handle catching any Java exceptions that
|
||||
// might be thrown by the JNI calls we make.
|
||||
let res = self
|
||||
.jvm
|
||||
.attach_current_thread(|env| -> jni::errors::Result<()> {
|
||||
let activity = env.as_cast::<Activity>(self.activity.as_ref())?;
|
||||
|
||||
let ims = Context::INPUT_METHOD_SERVICE(env)?;
|
||||
let imm_obj = activity.as_context().get_system_service(env, ims)?;
|
||||
let imm = InputMethodManager::cast_local(env, imm_obj)?;
|
||||
|
||||
let window = activity.get_window(env)?;
|
||||
let decor = window.get_decor_view(env)?;
|
||||
let token = decor.get_window_token(env)?;
|
||||
|
||||
// HIDE_IMPLICIT_ONLY == 1, HIDE_NOT_ALWAYS == 2
|
||||
let flags = if hide_implicit_only { 1 } else { 0 };
|
||||
|
||||
let _hidden = imm.hide_soft_input_from_window(env, token, flags)?;
|
||||
Ok(())
|
||||
});
|
||||
|
||||
if let Err(err) = res {
|
||||
error!("Failed to hide soft input: {err:?}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -390,17 +445,23 @@ impl AndroidAppInner {
|
||||
// NOP: Unsupported
|
||||
}
|
||||
|
||||
// TODO: move into a trait
|
||||
pub fn set_ime_editor_info(
|
||||
&self,
|
||||
_input_type: InputType,
|
||||
_action: TextInputAction,
|
||||
_options: ImeOptions,
|
||||
) {
|
||||
// NOP: Unsupported
|
||||
}
|
||||
|
||||
pub fn device_key_character_map(&self, device_id: i32) -> InternalResult<KeyCharacterMap> {
|
||||
let mut guard = self.key_maps.lock().unwrap();
|
||||
|
||||
let key_map = match guard.entry(device_id) {
|
||||
std::collections::hash_map::Entry::Occupied(occupied) => occupied.get().clone(),
|
||||
std::collections::hash_map::Entry::Vacant(vacant) => {
|
||||
let character_map = jni_utils::device_key_character_map(
|
||||
self.jvm.clone(),
|
||||
self.key_map_binding.clone(),
|
||||
device_id,
|
||||
)?;
|
||||
let character_map = device_key_character_map(self.jvm.clone(), device_id)?;
|
||||
vacant.insert(character_map.clone());
|
||||
character_map
|
||||
}
|
||||
@@ -432,7 +493,7 @@ impl AndroidAppInner {
|
||||
// trigger a wake up)
|
||||
let queue = self
|
||||
.native_activity
|
||||
.looper_attached_input_queue(self.looper(), LOOPER_ID_INPUT);
|
||||
.looper_attached_input_queue(self.looper_as_ptr(), LOOPER_ID_INPUT);
|
||||
|
||||
// Note: we don't treat it as an error if there is no queue, so if applications
|
||||
// iterate input before a queue has been created (e.g. before onStart) then
|
||||
@@ -444,17 +505,32 @@ impl AndroidAppInner {
|
||||
}
|
||||
|
||||
pub fn internal_data_path(&self) -> Option<std::path::PathBuf> {
|
||||
let na = self.native_activity();
|
||||
let guard = self.native_activity.mutex.lock().unwrap();
|
||||
let na = guard.activity;
|
||||
if na.is_null() {
|
||||
log::error!("Can't get internal data path after NativeActivity has been destroyed");
|
||||
return None;
|
||||
}
|
||||
unsafe { util::try_get_path_from_ptr((*na).internalDataPath) }
|
||||
}
|
||||
|
||||
pub fn external_data_path(&self) -> Option<std::path::PathBuf> {
|
||||
let na = self.native_activity();
|
||||
let guard = self.native_activity.mutex.lock().unwrap();
|
||||
let na = guard.activity;
|
||||
if na.is_null() {
|
||||
log::error!("Can't get external data path after NativeActivity has been destroyed");
|
||||
return None;
|
||||
}
|
||||
unsafe { util::try_get_path_from_ptr((*na).externalDataPath) }
|
||||
}
|
||||
|
||||
pub fn obb_path(&self) -> Option<std::path::PathBuf> {
|
||||
let na = self.native_activity();
|
||||
let guard = self.native_activity.mutex.lock().unwrap();
|
||||
let na = guard.activity;
|
||||
if na.is_null() {
|
||||
log::error!("Can't get OBB path after NativeActivity has been destroyed");
|
||||
return None;
|
||||
}
|
||||
unsafe { util::try_get_path_from_ptr((*na).obbPath) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,12 +1,7 @@
|
||||
use log::{error, Level};
|
||||
use log::Level;
|
||||
use std::{
|
||||
ffi::{CStr, CString},
|
||||
fs::File,
|
||||
io::{BufRead as _, BufReader, Result},
|
||||
os::{
|
||||
fd::{FromRawFd as _, RawFd},
|
||||
raw::c_char,
|
||||
},
|
||||
os::raw::c_char,
|
||||
};
|
||||
|
||||
pub fn try_get_path_from_ptr(path: *const c_char) -> Option<std::path::PathBuf> {
|
||||
@@ -36,46 +31,8 @@ pub(crate) fn android_log(level: Level, tag: &CStr, msg: &CStr) {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn forward_stdio_to_logcat() -> std::thread::JoinHandle<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 || -> Result<()> {
|
||||
let tag = CStr::from_bytes_with_nul(b"RustStdoutStderr\0").unwrap();
|
||||
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) => {
|
||||
error!("Logcat forwarder failed to read stdin/stderr: {e:?}");
|
||||
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")
|
||||
}
|
||||
|
||||
pub(crate) fn log_panic(panic: Box<dyn std::any::Any + Send>) {
|
||||
let rust_panic = unsafe { CStr::from_bytes_with_nul_unchecked(b"RustPanic\0") };
|
||||
let rust_panic = c"RustPanic";
|
||||
|
||||
if let Some(panic) = panic.downcast_ref::<String>() {
|
||||
if let Ok(msg) = CString::new(panic.clone()) {
|
||||
@@ -86,10 +43,7 @@ pub(crate) fn log_panic(panic: Box<dyn std::any::Any + Send>) {
|
||||
android_log(Level::Error, rust_panic, &msg);
|
||||
}
|
||||
} else {
|
||||
let unknown_panic = unsafe { CStr::from_bytes_with_nul_unchecked(b"UnknownPanic\0") };
|
||||
android_log(Level::Error, unknown_panic, unsafe {
|
||||
CStr::from_bytes_with_nul_unchecked(b"\0")
|
||||
});
|
||||
android_log(Level::Error, rust_panic, c"UnknownPanic");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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,8 +1,8 @@
|
||||
*.iml
|
||||
.idea
|
||||
.gradle
|
||||
/local.properties
|
||||
/.idea
|
||||
/.vscode
|
||||
.DS_Store
|
||||
/build
|
||||
/captures
|
||||
|
||||
@@ -0,0 +1,679 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "agdk-mainloop"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"android-activity",
|
||||
"jni",
|
||||
"paranoid-android",
|
||||
"tracing",
|
||||
"tracing-log",
|
||||
"tracing-subscriber",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "android-activity"
|
||||
version = "0.6.0"
|
||||
dependencies = [
|
||||
"android-properties",
|
||||
"bitflags",
|
||||
"cc",
|
||||
"jni",
|
||||
"libc",
|
||||
"log",
|
||||
"ndk",
|
||||
"ndk-context",
|
||||
"ndk-sys 0.6.0+11769913",
|
||||
"num_enum",
|
||||
"simd_cesu8",
|
||||
"thiserror 2.0.18",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "android-properties"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af"
|
||||
|
||||
[[package]]
|
||||
name = "bytes"
|
||||
version = "1.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.57"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a0dd1ca384932ff3641c8718a02769f1698e7563dc6974ffd03346116310423"
|
||||
dependencies = [
|
||||
"find-msvc-tools",
|
||||
"jobserver",
|
||||
"libc",
|
||||
"shlex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
||||
|
||||
[[package]]
|
||||
name = "combine"
|
||||
version = "4.6.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
||||
|
||||
[[package]]
|
||||
name = "find-msvc-tools"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"r-efi",
|
||||
"wasip2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.16.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jni"
|
||||
version = "0.22.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"combine",
|
||||
"jni-macros",
|
||||
"jni-sys 0.4.1",
|
||||
"log",
|
||||
"simd_cesu8",
|
||||
"thiserror 2.0.18",
|
||||
"walkdir",
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jni-macros"
|
||||
version = "0.22.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rustc_version",
|
||||
"simd_cesu8",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jni-sys"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130"
|
||||
|
||||
[[package]]
|
||||
name = "jni-sys"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2"
|
||||
dependencies = [
|
||||
"jni-sys-macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jni-sys-macros"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jobserver"
|
||||
version = "0.1.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.183"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.29"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
|
||||
|
||||
[[package]]
|
||||
name = "matchers"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9"
|
||||
dependencies = [
|
||||
"regex-automata",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
|
||||
|
||||
[[package]]
|
||||
name = "ndk"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"jni-sys 0.3.0",
|
||||
"log",
|
||||
"ndk-sys 0.6.0+11769913",
|
||||
"num_enum",
|
||||
"thiserror 1.0.69",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ndk-context"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b"
|
||||
|
||||
[[package]]
|
||||
name = "ndk-sys"
|
||||
version = "0.5.0+25.2.9519653"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8c196769dd60fd4f363e11d948139556a344e79d451aeb2fa2fd040738ef7691"
|
||||
dependencies = [
|
||||
"jni-sys 0.3.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ndk-sys"
|
||||
version = "0.6.0+11769913"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873"
|
||||
dependencies = [
|
||||
"jni-sys 0.3.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nu-ansi-term"
|
||||
version = "0.50.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
|
||||
dependencies = [
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_enum"
|
||||
version = "0.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5d0bca838442ec211fa11de3a8b0e0e8f3a4522575b5c4c06ed722e005036f26"
|
||||
dependencies = [
|
||||
"num_enum_derive",
|
||||
"rustversion",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_enum_derive"
|
||||
version = "0.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8"
|
||||
dependencies = [
|
||||
"proc-macro-crate",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.21.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
|
||||
|
||||
[[package]]
|
||||
name = "paranoid-android"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "101795d63d371b43e38d6e7254677657be82f17022f7f7893c268f33ac0caadc"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"ndk-sys 0.5.0+25.2.9519653",
|
||||
"sharded-slab",
|
||||
"smallvec",
|
||||
"tracing-core",
|
||||
"tracing-subscriber",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-crate"
|
||||
version = "3.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f"
|
||||
dependencies = [
|
||||
"toml_edit",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.106"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.45"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "r-efi"
|
||||
version = "5.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.8.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a"
|
||||
|
||||
[[package]]
|
||||
name = "rustc_version"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
|
||||
dependencies = [
|
||||
"semver",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustversion"
|
||||
version = "1.0.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
|
||||
|
||||
[[package]]
|
||||
name = "same-file"
|
||||
version = "1.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
|
||||
dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "1.0.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2"
|
||||
|
||||
[[package]]
|
||||
name = "serde_core"
|
||||
version = "1.0.228"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.228"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sharded-slab"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||
|
||||
[[package]]
|
||||
name = "simd_cesu8"
|
||||
version = "1.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "94f90157bb87cddf702797c5dadfa0be7d266cdf49e22da2fcaa32eff75b2c33"
|
||||
dependencies = [
|
||||
"rustc_version",
|
||||
"simdutf8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "simdutf8"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e"
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.15.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.117"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.69"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
|
||||
dependencies = [
|
||||
"thiserror-impl 1.0.69",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "2.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4"
|
||||
dependencies = [
|
||||
"thiserror-impl 2.0.18",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.69"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "2.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thread_local"
|
||||
version = "1.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
version = "1.0.0+spec-1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32c2555c699578a4f59f0cc68e5116c8d7cabbd45e1409b989d4be085b53f13e"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.25.4+spec-1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7193cbd0ce53dc966037f54351dbbcf0d5a642c7f0038c382ef9e677ce8c13f2"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"toml_datetime",
|
||||
"toml_parser",
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_parser"
|
||||
version = "1.0.9+spec-1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "702d4415e08923e7e1ef96cd5727c0dfed80b4d2fa25db9647fe5eb6f7c5a4c4"
|
||||
dependencies = [
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing"
|
||||
version = "0.1.44"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100"
|
||||
dependencies = [
|
||||
"pin-project-lite",
|
||||
"tracing-attributes",
|
||||
"tracing-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-attributes"
|
||||
version = "0.1.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-core"
|
||||
version = "0.1.36"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"valuable",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-log"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3"
|
||||
dependencies = [
|
||||
"log",
|
||||
"once_cell",
|
||||
"tracing-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-subscriber"
|
||||
version = "0.3.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319"
|
||||
dependencies = [
|
||||
"matchers",
|
||||
"nu-ansi-term",
|
||||
"once_cell",
|
||||
"regex-automata",
|
||||
"sharded-slab",
|
||||
"smallvec",
|
||||
"thread_local",
|
||||
"tracing",
|
||||
"tracing-core",
|
||||
"tracing-log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
|
||||
|
||||
[[package]]
|
||||
name = "valuable"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
|
||||
|
||||
[[package]]
|
||||
name = "walkdir"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
|
||||
dependencies = [
|
||||
"same-file",
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasip2"
|
||||
version = "1.0.2+wasi-0.2.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5"
|
||||
dependencies = [
|
||||
"wit-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-util"
|
||||
version = "0.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
|
||||
dependencies = [
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-link"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.61.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.7.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen"
|
||||
version = "0.51.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5"
|
||||
@@ -6,12 +6,20 @@ 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"] }
|
||||
ndk-sys = "0.6.0"
|
||||
ndk = "0.9.0"
|
||||
tracing = "0.1"
|
||||
tracing-subscriber = { version = "0.3", features = [
|
||||
"fmt",
|
||||
"env-filter",
|
||||
"tracing-log",
|
||||
] }
|
||||
paranoid-android = "0.2"
|
||||
tracing-log = "0.2"
|
||||
|
||||
android-activity = { path = "../../android-activity", features = [
|
||||
"game-activity",
|
||||
] }
|
||||
jni = "0.22"
|
||||
|
||||
[lib]
|
||||
name="main"
|
||||
crate_type=["cdylib"]
|
||||
name = "main"
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
@@ -13,5 +13,5 @@ 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.agdkmainloop/.MainActivity
|
||||
adb shell am start -n com.github.rust_mobile.agdkmainloop/.MainActivity
|
||||
```
|
||||
|
||||
@@ -3,20 +3,19 @@ plugins {
|
||||
}
|
||||
|
||||
android {
|
||||
ndkVersion "25.2.9519653"
|
||||
compileSdk 31
|
||||
compileSdk = 35
|
||||
|
||||
defaultConfig {
|
||||
applicationId "co.realfit.agdkmainloop"
|
||||
minSdk 28
|
||||
targetSdk 31
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
applicationId = "com.github.rust_mobile.agdkmainloop"
|
||||
minSdk = 31
|
||||
targetSdk = 35
|
||||
versionCode = 1
|
||||
versionName = "1.0"
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
minifyEnabled = false
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
debug {
|
||||
@@ -28,32 +27,17 @@ android {
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
sourceCompatibility JavaVersion.VERSION_17
|
||||
targetCompatibility JavaVersion.VERSION_17
|
||||
}
|
||||
namespace 'co.realfit.agdkmainloop'
|
||||
namespace = 'com.github.rust_mobile.agdkmainloop'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
implementation "androidx.core:core:1.5.0"
|
||||
implementation "androidx.constraintlayout:constraintlayout:2.0.4"
|
||||
implementation 'androidx.fragment:fragment:1.2.5'
|
||||
implementation 'com.google.oboe:oboe:1.5.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"
|
||||
implementation 'androidx.appcompat:appcompat:1.7.0'
|
||||
|
||||
// To use the Games Activity library
|
||||
implementation "androidx.games:games-activity:4.0.0"
|
||||
|
||||
// To use the Games Controller Library
|
||||
//implementation "androidx.games:games-controller:2.0.2"
|
||||
|
||||
// To use the Games Text Input Library
|
||||
//implementation "androidx.games:games-text-input:2.0.2"
|
||||
implementation "androidx.games:games-activity:4.4.0"
|
||||
// Note: don't include game-text-input separately, since it's integrated into game-activity
|
||||
}
|
||||
|
||||
|
||||
@@ -2,12 +2,11 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="AGDK Mainloop"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/Theme.RustTemplate">
|
||||
android:theme="@style/ActivityTheme">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:configChanges="orientation|screenSize|screenLayout|keyboardHidden"
|
||||
|
||||
|
After Width: | Height: | Size: 63 KiB |
@@ -1,6 +1,5 @@
|
||||
package co.realfit.agdkmainloop;
|
||||
package com.github.rust_mobile.agdkmainloop;
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.core.view.WindowCompat;
|
||||
import androidx.core.view.WindowInsetsCompat;
|
||||
import androidx.core.view.WindowInsetsControllerCompat;
|
||||
@@ -11,7 +10,6 @@ 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;
|
||||
|
||||
@@ -35,10 +33,8 @@ public class MainActivity extends GameActivity {
|
||||
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;
|
||||
}
|
||||
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();
|
||||
@@ -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,5 +1,5 @@
|
||||
<?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" />
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
||||
@@ -1,5 +1,5 @@
|
||||
<?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" />
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
||||
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 3.0 KiB |
|
After Width: | Height: | Size: 4.3 KiB |
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 5.4 KiB |
|
Before Width: | Height: | Size: 982 B After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 4.3 KiB |
|
After Width: | Height: | Size: 5.9 KiB |
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 7.4 KiB |
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 7.0 KiB |
|
After Width: | Height: | Size: 9.3 KiB |
|
Before Width: | Height: | Size: 5.8 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 9.5 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 7.6 KiB After Width: | Height: | Size: 16 KiB |
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="ic_launcher_background">#C8C49F</color>
|
||||
</resources>
|
||||
@@ -1,3 +1,8 @@
|
||||
<resources xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<style name="Theme.RustTemplate" parent="Theme.AppCompat.Light.NoActionBar" />
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<style name="ActivityTheme" parent="Theme.AppCompat.Light.NoActionBar">
|
||||
<!-- For full-screen layout, cutout support -->
|
||||
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
|
||||
<item name="android:windowFullscreen">true</item>
|
||||
</style>
|
||||
</resources>
|
||||
@@ -1,10 +1,6 @@
|
||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
plugins {
|
||||
id 'com.android.application' version '8.0.0' apply false
|
||||
id 'com.android.library' version '8.0.0' apply false
|
||||
}
|
||||
|
||||
task clean(type: Delete) {
|
||||
delete rootProject.buildDir
|
||||
id 'com.android.application' version '9.1.0' apply false
|
||||
id 'com.android.library' version '9.1.0' apply false
|
||||
}
|
||||
|
||||
|
||||
@@ -1,23 +1,15 @@
|
||||
# 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
|
||||
# Enable Gradle Daemon
|
||||
org.gradle.daemon=true
|
||||
|
||||
# JVM arguments
|
||||
org.gradle.jvmargs=-Xmx4g -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
|
||||
# Enable AndroidX
|
||||
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
|
||||
android.defaults.buildfeatures.buildconfig=true
|
||||
android.nonFinalResIds=false
|
||||
# Build caching and parallel execution
|
||||
org.gradle.caching=true
|
||||
org.gradle.parallel=true
|
||||
# Incremental Kotlin compilation
|
||||
kotlin.incremental=true
|
||||
|
||||
# File system watching for faster builds
|
||||
org.gradle.unsafe.watch-fs=true
|
||||
@@ -1,6 +1,7 @@
|
||||
#Mon May 02 15:39:12 BST 2022
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip
|
||||
distributionPath=wrapper/dists
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env sh
|
||||
#!/bin/sh
|
||||
|
||||
#
|
||||
# Copyright 2015 the original author or authors.
|
||||
# Copyright © 2015-2021 the original authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
@@ -15,69 +15,104 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
## Gradle start up script for UN*X
|
||||
##
|
||||
#
|
||||
# Gradle start up script for POSIX generated by Gradle.
|
||||
#
|
||||
# Important for running:
|
||||
#
|
||||
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
|
||||
# noncompliant, but you have some other compliant shell such as ksh or
|
||||
# bash, then to run this script, type that shell name before the whole
|
||||
# command line, like:
|
||||
#
|
||||
# ksh Gradle
|
||||
#
|
||||
# Busybox and similar reduced shells will NOT work, because this script
|
||||
# requires all of these POSIX shell features:
|
||||
# * functions;
|
||||
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
|
||||
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
|
||||
# * compound commands having a testable exit status, especially «case»;
|
||||
# * various built-in commands including «command», «set», and «ulimit».
|
||||
#
|
||||
# Important for patching:
|
||||
#
|
||||
# (2) This script targets any POSIX shell, so it avoids extensions provided
|
||||
# by Bash, Ksh, etc; in particular arrays are avoided.
|
||||
#
|
||||
# The "traditional" practice of packing multiple parameters into a
|
||||
# space-separated string is a well documented source of bugs and security
|
||||
# problems, so this is (mostly) avoided, by progressively accumulating
|
||||
# options in "$@", and eventually passing that to Java.
|
||||
#
|
||||
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
|
||||
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
|
||||
# see the in-line comments for details.
|
||||
#
|
||||
# There are tweaks for specific operating systems such as AIX, CygWin,
|
||||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# within the Gradle project.
|
||||
#
|
||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
# 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
|
||||
app_path=$0
|
||||
|
||||
# Need this for daisy-chained symlinks.
|
||||
while
|
||||
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
|
||||
[ -h "$app_path" ]
|
||||
do
|
||||
ls=$( ls -ld "$app_path" )
|
||||
link=${ls#*' -> '}
|
||||
case $link in #(
|
||||
/*) app_path=$link ;; #(
|
||||
*) app_path=$APP_HOME$link ;;
|
||||
esac
|
||||
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"'
|
||||
# This is normally unused
|
||||
# shellcheck disable=SC2034
|
||||
APP_BASE_NAME=${0##*/}
|
||||
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s
|
||||
' "$PWD" ) || exit
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
MAX_FD=maximum
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
}
|
||||
} >&2
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
} >&2
|
||||
|
||||
# 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
|
||||
;;
|
||||
case "$( uname )" in #(
|
||||
CYGWIN* ) cygwin=true ;; #(
|
||||
Darwin* ) darwin=true ;; #(
|
||||
MSYS* | MINGW* ) msys=true ;; #(
|
||||
NONSTOP* ) nonstop=true ;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
@@ -87,9 +122,9 @@ CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
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"
|
||||
JAVACMD=$JAVA_HOME/jre/sh/java
|
||||
else
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
JAVACMD=$JAVA_HOME/bin/java
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
@@ -98,88 +133,120 @@ 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.
|
||||
JAVACMD=java
|
||||
if ! command -v java >/dev/null 2>&1
|
||||
then
|
||||
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
|
||||
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" ;;
|
||||
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||
case $MAX_FD in #(
|
||||
max*)
|
||||
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
MAX_FD=$( ulimit -H -n ) ||
|
||||
warn "Could not query maximum file descriptor limit"
|
||||
esac
|
||||
case $MAX_FD in #(
|
||||
'' | soft) :;; #(
|
||||
*)
|
||||
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
ulimit -n "$MAX_FD" ||
|
||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||
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, stacking in reverse order:
|
||||
# * args from the command line
|
||||
# * the main class name
|
||||
# * -classpath
|
||||
# * -D...appname settings
|
||||
# * --module-path (only if needed)
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
|
||||
|
||||
# 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"
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if "$cygwin" || "$msys" ; then
|
||||
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
||||
|
||||
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
for arg do
|
||||
if
|
||||
case $arg in #(
|
||||
-*) false ;; # don't mess with options #(
|
||||
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
|
||||
[ -e "$t" ] ;; #(
|
||||
*) false ;;
|
||||
esac
|
||||
then
|
||||
arg=$( cygpath --path --ignore --mixed "$arg" )
|
||||
fi
|
||||
# Roll the args list around exactly as many times as the number of
|
||||
# args, so each arg winds up back in the position where it started, but
|
||||
# possibly modified.
|
||||
#
|
||||
# NB: a `for` loop captures its iteration list before it begins, so
|
||||
# changing the positional parameters here affects neither the number of
|
||||
# iterations, nor the values presented in `arg`.
|
||||
shift # remove old arg
|
||||
set -- "$@" "$arg" # push replacement arg
|
||||
done
|
||||
fi
|
||||
|
||||
|
||||
# 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"'
|
||||
|
||||
# Collect all arguments for the java command:
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||
# and any embedded shellness will be escaped.
|
||||
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
|
||||
# treated as '${Hostname}' itself on the command line.
|
||||
|
||||
set -- \
|
||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||
-classpath "$CLASSPATH" \
|
||||
org.gradle.wrapper.GradleWrapperMain \
|
||||
"$@"
|
||||
|
||||
# Stop when "xargs" is not available.
|
||||
if ! command -v xargs >/dev/null 2>&1
|
||||
then
|
||||
die "xargs is not available"
|
||||
fi
|
||||
|
||||
# Use "xargs" to parse quoted args.
|
||||
#
|
||||
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||
#
|
||||
# In Bash we could simply go:
|
||||
#
|
||||
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
|
||||
# set -- "${ARGS[@]}" "$@"
|
||||
#
|
||||
# but POSIX shell has neither arrays nor command substitution, so instead we
|
||||
# post-process each arg (as a line of input to sed) to backslash-escape any
|
||||
# character that might be a shell metacharacter, then use eval to reverse
|
||||
# that process (while maintaining the separation between arguments), and wrap
|
||||
# the whole thing up as a single "set" statement.
|
||||
#
|
||||
# This will of course break if any of these variables contains a newline or
|
||||
# an unmatched quote.
|
||||
#
|
||||
|
||||
eval "set -- $(
|
||||
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
|
||||
xargs -n1 |
|
||||
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
|
||||
tr '\n' ' '
|
||||
)" '"$@"'
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
|
||||
@@ -1,89 +1,94 @@
|
||||
@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
|
||||
@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
|
||||
@rem SPDX-License-Identifier: Apache-2.0
|
||||
@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=.
|
||||
@rem This is normally unused
|
||||
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% equ 0 goto execute
|
||||
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
|
||||
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% equ 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!
|
||||
set EXIT_CODE=%ERRORLEVEL%
|
||||
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
||||
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
||||
exit /b %EXIT_CODE%
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
||||
|
||||
@@ -1,12 +1,142 @@
|
||||
use std::sync::OnceLock;
|
||||
|
||||
use android_activity::{
|
||||
input::{InputEvent, KeyAction, KeyEvent, KeyMapChar, MotionAction},
|
||||
AndroidApp, InputStatus, MainEvent, PollEvent,
|
||||
ndk, ndk_sys, AndroidApp, InputStatus, MainEvent, OnCreateState, PollEvent,
|
||||
};
|
||||
use log::info;
|
||||
use jni::{
|
||||
objects::{JObject, JString},
|
||||
refs::Global,
|
||||
vm::JavaVM,
|
||||
};
|
||||
use tracing::{error, info};
|
||||
|
||||
#[no_mangle]
|
||||
jni::bind_java_type! { Context => "android.content.Context" }
|
||||
jni::bind_java_type! {
|
||||
Activity => "android.app.Activity",
|
||||
type_map {
|
||||
Context => "android.content.Context",
|
||||
},
|
||||
is_instance_of {
|
||||
context: Context
|
||||
},
|
||||
}
|
||||
|
||||
jni::bind_java_type! {
|
||||
Toast => "android.widget.Toast",
|
||||
type_map {
|
||||
Context => "android.content.Context",
|
||||
},
|
||||
methods {
|
||||
static fn make_text(context: Context, text: JCharSequence, duration: i32) -> Toast,
|
||||
fn show(),
|
||||
}
|
||||
}
|
||||
|
||||
// Note: The jni bindings will actually initialize lazily but it can be helpful
|
||||
// to initialize explicitly to get an up-front error in case there is an issue
|
||||
// (such as a typo with a method name or incorrect signature) rather than having
|
||||
// an unpredictable error when using the binding.
|
||||
fn jni_init(env: &jni::Env) -> jni::errors::Result<()> {
|
||||
let _ = ContextAPI::get(env, &Default::default())?;
|
||||
let _ = ActivityAPI::get(env, &Default::default())?;
|
||||
let _ = ToastAPI::get(env, &Default::default())?;
|
||||
// .. call other `get` functions for other bindings here as needed ...
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Called while Activity.onCreate is running
|
||||
// May be called multiple times if the activity is destroyed and recreated.
|
||||
#[unsafe(no_mangle)]
|
||||
fn android_on_create(state: &OnCreateState) {
|
||||
static ONCE: OnceLock<()> = OnceLock::new();
|
||||
ONCE.get_or_init(|| {
|
||||
use tracing_subscriber::prelude::*;
|
||||
|
||||
unsafe { std::env::set_var("RUST_BACKTRACE", "full") };
|
||||
|
||||
const DEFAULT_ENV_FILTER: &str = "debug,wgpu_hal=info,winit=info,naga=info";
|
||||
let filter_layer = tracing_subscriber::EnvFilter::new(DEFAULT_ENV_FILTER);
|
||||
let android_layer = paranoid_android::layer(env!("CARGO_PKG_NAME"))
|
||||
.with_ansi(false)
|
||||
.with_span_events(tracing_subscriber::fmt::format::FmtSpan::CLOSE)
|
||||
.with_thread_names(true);
|
||||
tracing_subscriber::registry()
|
||||
.with(filter_layer)
|
||||
.with(android_layer)
|
||||
.init();
|
||||
});
|
||||
|
||||
let vm = unsafe { JavaVM::from_raw(state.vm_as_ptr().cast()) };
|
||||
// Note: from here on we can also rely on `JavaVM::singleton()` now that we know it's been initialized.
|
||||
|
||||
let activity = state.activity_as_ptr() as jni::sys::jobject;
|
||||
if let Err(err) = vm.attach_current_thread(|env| -> jni::errors::Result<()> {
|
||||
// Initialize JNI bindings
|
||||
jni_init(env).expect("Failed to initialize JNI bindings");
|
||||
|
||||
// SAFETY:
|
||||
// - The 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::<Global<JObject>>(&activity)? };
|
||||
// Do something with the activity on the Java main thread, such as call a method or access a field
|
||||
Ok(())
|
||||
}) {
|
||||
error!("Failed to interact with Android SDK on Java main thread: {err:?}");
|
||||
}
|
||||
|
||||
eprintln!(
|
||||
"android_on_create called on thread {:?}",
|
||||
std::thread::current().id()
|
||||
);
|
||||
info!(
|
||||
"android_on_create called on thread {:?}",
|
||||
std::thread::current().id()
|
||||
);
|
||||
}
|
||||
|
||||
enum ToastDuration {
|
||||
Short = 0,
|
||||
Long = 1,
|
||||
}
|
||||
|
||||
fn send_toast(outer_app: &AndroidApp, msg: impl AsRef<str>, duration: ToastDuration) {
|
||||
let app = outer_app.clone();
|
||||
let msg = msg.as_ref().to_string();
|
||||
outer_app.run_on_java_main_thread(Box::new(move || {
|
||||
// We initialize JavaVM::singleton at the start of `android_main`
|
||||
let jvm = jni::JavaVM::singleton().expect("Failed to get singleton JavaVM instance");
|
||||
// We use `with_top_local_frame` as a minor optimization because it's guaranteed by
|
||||
// `run_on_java_main_thread` that we already have an underlying JNI attachment and local
|
||||
// frame. It would also be perfectly reasonable to use `jvm.attach_current_thread()`.
|
||||
if let Err(err) = jvm.with_top_local_frame(|env| -> jni::errors::Result<()> {
|
||||
let activity: jni::sys::jobject = app.activity_as_ptr() as _;
|
||||
let activity = unsafe { env.as_cast_raw::<Global<Activity>>(&activity)? };
|
||||
|
||||
let message = JString::new(env, &msg)?;
|
||||
let toast = Toast::make_text(env, activity.as_ref(), &message, duration as i32)?;
|
||||
info!("Showing Toast from Rust JNI callback: {msg}");
|
||||
toast.show(env)?;
|
||||
|
||||
Ok(())
|
||||
}) {
|
||||
error!("Failed to execute callback on main thread: {err:?}");
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
// Called on a dedicated Activity main loop thread, spawned after `android_on_create` returns
|
||||
// May be called multiple times if the activity is destroyed and recreated.
|
||||
#[unsafe(no_mangle)]
|
||||
fn android_main(app: AndroidApp) {
|
||||
android_logger::init_once(android_logger::Config::default().with_min_level(log::Level::Info));
|
||||
eprintln!(
|
||||
"android_main started on thread {:?}",
|
||||
std::thread::current().id()
|
||||
);
|
||||
info!(
|
||||
"android_main started on thread {:?}",
|
||||
std::thread::current().id()
|
||||
);
|
||||
|
||||
let mut quit = false;
|
||||
let mut redraw_pending = true;
|
||||
@@ -14,9 +144,11 @@ fn android_main(app: AndroidApp) {
|
||||
|
||||
let mut combining_accent = None;
|
||||
|
||||
send_toast(&app, "Hello from Rust on Android!", ToastDuration::Long);
|
||||
|
||||
while !quit {
|
||||
app.poll_events(
|
||||
Some(std::time::Duration::from_secs(1)), /* timeout */
|
||||
Some(std::time::Duration::from_secs(2)), /* timeout */
|
||||
|event| {
|
||||
match event {
|
||||
PollEvent::Wake => {
|
||||
@@ -40,6 +172,7 @@ fn android_main(app: AndroidApp) {
|
||||
info!("Resumed with saved state = {uri:#?}");
|
||||
}
|
||||
}
|
||||
send_toast(&app, "Resumed!", ToastDuration::Short);
|
||||
}
|
||||
MainEvent::InitWindow { .. } => {
|
||||
native_window = app.native_window();
|
||||
@@ -47,6 +180,7 @@ fn android_main(app: AndroidApp) {
|
||||
}
|
||||
MainEvent::TerminateWindow { .. } => {
|
||||
native_window = None;
|
||||
redraw_pending = false;
|
||||
}
|
||||
MainEvent::WindowResized { .. } => {
|
||||
redraw_pending = true;
|
||||
@@ -59,8 +193,12 @@ fn android_main(app: AndroidApp) {
|
||||
}
|
||||
MainEvent::ConfigChanged { .. } => {
|
||||
info!("Config Changed: {:#?}", app.config());
|
||||
send_toast(&app, "Config Changed!", ToastDuration::Short);
|
||||
}
|
||||
MainEvent::LowMemory => {
|
||||
info!("Low Memory Warning");
|
||||
send_toast(&app, "Low Memory!", ToastDuration::Short);
|
||||
}
|
||||
MainEvent::LowMemory => {}
|
||||
|
||||
MainEvent::Destroy => quit = true,
|
||||
_ => { /* ... */ }
|
||||
@@ -98,13 +236,54 @@ fn android_main(app: AndroidApp) {
|
||||
let y = pointer.y();
|
||||
|
||||
println!("POINTER UP {x}, {y}");
|
||||
if x < 200.0 && y < 200.0 {
|
||||
|
||||
if x < 500.0 && y < 500.0 {
|
||||
println!("Requesting to show keyboard");
|
||||
send_toast(
|
||||
&app,
|
||||
"Requesting to show keyboard",
|
||||
ToastDuration::Short,
|
||||
);
|
||||
app.show_soft_input(true);
|
||||
} else if x >= 500.0 && y < 500.0 {
|
||||
println!("Requesting to hide keyboard");
|
||||
send_toast(
|
||||
&app,
|
||||
"Requesting to hide keyboard",
|
||||
ToastDuration::Short,
|
||||
);
|
||||
app.hide_soft_input(false);
|
||||
} else {
|
||||
send_toast(
|
||||
&app,
|
||||
format!("POINTER UP {x}, {y}"),
|
||||
ToastDuration::Short,
|
||||
);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
let num_pointers = motion_event.pointer_count();
|
||||
for i in 0..num_pointers {
|
||||
let pointer = motion_event.pointer_at_index(i);
|
||||
|
||||
println!(
|
||||
"Pointer[{i}]: id={}, time={}, x={}, y={}",
|
||||
pointer.pointer_id(),
|
||||
motion_event.event_time(),
|
||||
pointer.x(),
|
||||
pointer.y(),
|
||||
);
|
||||
for sample in pointer.history() {
|
||||
println!(
|
||||
" History[{}]: x={}, y={}, time={:?}",
|
||||
sample.history_index(),
|
||||
sample.x(),
|
||||
sample.y(),
|
||||
sample.event_time()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
InputEvent::TextEvent(state) => {
|
||||
info!("Input Method State: {state:?}");
|
||||
@@ -113,6 +292,16 @@ fn android_main(app: AndroidApp) {
|
||||
}
|
||||
|
||||
info!("Input Event: {event:?}");
|
||||
app.run_on_java_main_thread(Box::new(move || {
|
||||
println!(
|
||||
"Callback on main thread {:?}",
|
||||
std::thread::current().id()
|
||||
);
|
||||
info!(
|
||||
"Callback on main thread {:?}",
|
||||
std::thread::current().id()
|
||||
);
|
||||
}));
|
||||
InputStatus::Unhandled
|
||||
}) {
|
||||
info!("No more input available");
|
||||
@@ -120,7 +309,7 @@ fn android_main(app: AndroidApp) {
|
||||
}
|
||||
},
|
||||
Err(err) => {
|
||||
log::error!("Failed to get input events iterator: {err:?}");
|
||||
error!("Failed to get input events iterator: {err:?}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -148,7 +337,7 @@ fn character_map_and_combine_key(
|
||||
let key_map = match app.device_key_character_map(device_id) {
|
||||
Ok(key_map) => key_map,
|
||||
Err(err) => {
|
||||
log::error!("Failed to look up `KeyCharacterMap` for device {device_id}: {err:?}");
|
||||
error!("Failed to look up `KeyCharacterMap` for device {device_id}: {err:?}");
|
||||
return None;
|
||||
}
|
||||
};
|
||||
@@ -160,12 +349,16 @@ fn character_map_and_combine_key(
|
||||
let combined_unicode = if let Some(accent) = combining_accent {
|
||||
match key_map.get_dead_char(*accent, unicode) {
|
||||
Ok(Some(key)) => {
|
||||
info!("KeyEvent: Combined '{unicode}' with accent '{accent}' to give '{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:?}");
|
||||
error!(
|
||||
"KeyEvent: Failed to combine 'dead key' accent '{accent}' with '{unicode}': {err:?}"
|
||||
);
|
||||
None
|
||||
}
|
||||
}
|
||||
@@ -193,7 +386,7 @@ fn character_map_and_combine_key(
|
||||
None
|
||||
}
|
||||
Err(err) => {
|
||||
log::error!("KeyEvent: Failed to get key map character: {err:?}");
|
||||
error!("KeyEvent: Failed to get key map character: {err:?}");
|
||||
*combining_accent = None;
|
||||
None
|
||||
}
|
||||
|
||||
@@ -1,12 +1,8 @@
|
||||
*.iml
|
||||
.gradle
|
||||
/local.properties
|
||||
/.idea/caches
|
||||
/.idea/libraries
|
||||
/.idea/modules.xml
|
||||
/.idea/workspace.xml
|
||||
/.idea/navEditor.xml
|
||||
/.idea/assetWizardSettings.xml
|
||||
/.idea
|
||||
/.vscode
|
||||
.DS_Store
|
||||
/build
|
||||
/captures
|
||||
|
||||
@@ -0,0 +1,674 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "android-activity"
|
||||
version = "0.6.0"
|
||||
dependencies = [
|
||||
"android-properties",
|
||||
"bitflags",
|
||||
"cc",
|
||||
"jni",
|
||||
"libc",
|
||||
"log",
|
||||
"ndk",
|
||||
"ndk-context",
|
||||
"ndk-sys 0.6.0+11769913",
|
||||
"num_enum",
|
||||
"thiserror 2.0.18",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "android-properties"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af"
|
||||
|
||||
[[package]]
|
||||
name = "bytes"
|
||||
version = "1.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.57"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a0dd1ca384932ff3641c8718a02769f1698e7563dc6974ffd03346116310423"
|
||||
dependencies = [
|
||||
"find-msvc-tools",
|
||||
"jobserver",
|
||||
"libc",
|
||||
"shlex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
||||
|
||||
[[package]]
|
||||
name = "combine"
|
||||
version = "4.6.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
||||
|
||||
[[package]]
|
||||
name = "find-msvc-tools"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"r-efi",
|
||||
"wasip2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.16.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jni"
|
||||
version = "0.22.4"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"combine",
|
||||
"jni-macros",
|
||||
"jni-sys 0.4.1",
|
||||
"log",
|
||||
"simd_cesu8",
|
||||
"thiserror 2.0.18",
|
||||
"walkdir",
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jni-macros"
|
||||
version = "0.22.4"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rustc_version",
|
||||
"simd_cesu8",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jni-sys"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130"
|
||||
|
||||
[[package]]
|
||||
name = "jni-sys"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2"
|
||||
dependencies = [
|
||||
"jni-sys-macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jni-sys-macros"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jobserver"
|
||||
version = "0.1.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.183"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.29"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
|
||||
|
||||
[[package]]
|
||||
name = "matchers"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9"
|
||||
dependencies = [
|
||||
"regex-automata",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
|
||||
|
||||
[[package]]
|
||||
name = "na-mainloop"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"android-activity",
|
||||
"jni",
|
||||
"paranoid-android",
|
||||
"tracing",
|
||||
"tracing-log",
|
||||
"tracing-subscriber",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ndk"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"jni-sys 0.3.0",
|
||||
"log",
|
||||
"ndk-sys 0.6.0+11769913",
|
||||
"num_enum",
|
||||
"thiserror 1.0.69",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ndk-context"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b"
|
||||
|
||||
[[package]]
|
||||
name = "ndk-sys"
|
||||
version = "0.5.0+25.2.9519653"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8c196769dd60fd4f363e11d948139556a344e79d451aeb2fa2fd040738ef7691"
|
||||
dependencies = [
|
||||
"jni-sys 0.3.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ndk-sys"
|
||||
version = "0.6.0+11769913"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873"
|
||||
dependencies = [
|
||||
"jni-sys 0.3.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nu-ansi-term"
|
||||
version = "0.50.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
|
||||
dependencies = [
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_enum"
|
||||
version = "0.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5d0bca838442ec211fa11de3a8b0e0e8f3a4522575b5c4c06ed722e005036f26"
|
||||
dependencies = [
|
||||
"num_enum_derive",
|
||||
"rustversion",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_enum_derive"
|
||||
version = "0.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8"
|
||||
dependencies = [
|
||||
"proc-macro-crate",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.21.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
|
||||
|
||||
[[package]]
|
||||
name = "paranoid-android"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "101795d63d371b43e38d6e7254677657be82f17022f7f7893c268f33ac0caadc"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"ndk-sys 0.5.0+25.2.9519653",
|
||||
"sharded-slab",
|
||||
"smallvec",
|
||||
"tracing-core",
|
||||
"tracing-subscriber",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-crate"
|
||||
version = "3.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f"
|
||||
dependencies = [
|
||||
"toml_edit",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.106"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.45"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "r-efi"
|
||||
version = "5.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.8.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a"
|
||||
|
||||
[[package]]
|
||||
name = "rustc_version"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
|
||||
dependencies = [
|
||||
"semver",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustversion"
|
||||
version = "1.0.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
|
||||
|
||||
[[package]]
|
||||
name = "same-file"
|
||||
version = "1.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
|
||||
dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "1.0.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2"
|
||||
|
||||
[[package]]
|
||||
name = "serde_core"
|
||||
version = "1.0.228"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.228"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sharded-slab"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||
|
||||
[[package]]
|
||||
name = "simd_cesu8"
|
||||
version = "1.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "94f90157bb87cddf702797c5dadfa0be7d266cdf49e22da2fcaa32eff75b2c33"
|
||||
dependencies = [
|
||||
"rustc_version",
|
||||
"simdutf8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "simdutf8"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e"
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.15.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.117"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.69"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
|
||||
dependencies = [
|
||||
"thiserror-impl 1.0.69",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "2.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4"
|
||||
dependencies = [
|
||||
"thiserror-impl 2.0.18",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.69"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "2.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thread_local"
|
||||
version = "1.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
version = "1.0.0+spec-1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32c2555c699578a4f59f0cc68e5116c8d7cabbd45e1409b989d4be085b53f13e"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.25.4+spec-1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7193cbd0ce53dc966037f54351dbbcf0d5a642c7f0038c382ef9e677ce8c13f2"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"toml_datetime",
|
||||
"toml_parser",
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_parser"
|
||||
version = "1.0.9+spec-1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "702d4415e08923e7e1ef96cd5727c0dfed80b4d2fa25db9647fe5eb6f7c5a4c4"
|
||||
dependencies = [
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing"
|
||||
version = "0.1.44"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100"
|
||||
dependencies = [
|
||||
"pin-project-lite",
|
||||
"tracing-attributes",
|
||||
"tracing-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-attributes"
|
||||
version = "0.1.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-core"
|
||||
version = "0.1.36"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"valuable",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-log"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3"
|
||||
dependencies = [
|
||||
"log",
|
||||
"once_cell",
|
||||
"tracing-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-subscriber"
|
||||
version = "0.3.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319"
|
||||
dependencies = [
|
||||
"matchers",
|
||||
"nu-ansi-term",
|
||||
"once_cell",
|
||||
"regex-automata",
|
||||
"sharded-slab",
|
||||
"smallvec",
|
||||
"thread_local",
|
||||
"tracing",
|
||||
"tracing-core",
|
||||
"tracing-log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
|
||||
|
||||
[[package]]
|
||||
name = "valuable"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
|
||||
|
||||
[[package]]
|
||||
name = "walkdir"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
|
||||
dependencies = [
|
||||
"same-file",
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasip2"
|
||||
version = "1.0.2+wasi-0.2.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5"
|
||||
dependencies = [
|
||||
"wit-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-util"
|
||||
version = "0.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
|
||||
dependencies = [
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-link"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.61.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.7.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen"
|
||||
version = "0.51.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5"
|
||||
@@ -1,21 +1,28 @@
|
||||
[package]
|
||||
name = "na-mainloop"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
edition = "2024"
|
||||
|
||||
# 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 = [ "native-activity" ] }
|
||||
ndk-sys = "0.6.0"
|
||||
ndk = "0.9.0"
|
||||
tracing = "0.1"
|
||||
tracing-subscriber = { version = "0.3", features = [
|
||||
"fmt",
|
||||
"env-filter",
|
||||
"tracing-log",
|
||||
] }
|
||||
paranoid-android = "0.2"
|
||||
tracing-log = "0.2"
|
||||
|
||||
android-activity = { path = "../../android-activity", features = [
|
||||
"native-activity",
|
||||
] }
|
||||
jni = "0.22"
|
||||
|
||||
[lib]
|
||||
#name="na_mainloop"
|
||||
crate_type=["cdylib"]
|
||||
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
####################
|
||||
# cargo apk config #
|
||||
@@ -23,10 +30,10 @@ crate_type=["cdylib"]
|
||||
|
||||
[package.metadata.android]
|
||||
# Specifies the package property of the manifest.
|
||||
package = "com.foo.bar"
|
||||
package = "com.github.rust_mobile.namainloop"
|
||||
|
||||
# Specifies the array of targets to build for.
|
||||
build_targets = [ "aarch64-linux-android" ]
|
||||
build_targets = ["aarch64-linux-android"]
|
||||
|
||||
# Path to your application's resources folder.
|
||||
# If not specified, resources will not be included in the APK.
|
||||
@@ -49,9 +56,9 @@ build_targets = [ "aarch64-linux-android" ]
|
||||
#
|
||||
# Defaults to a `min_sdk_version` of 23 and `target_sdk_version` of 30 (or lower if the detected NDK doesn't support this).
|
||||
[package.metadata.android.sdk]
|
||||
min_sdk_version = 28
|
||||
target_sdk_version = 31
|
||||
#max_sdk_version = 31
|
||||
min_sdk_version = 31
|
||||
target_sdk_version = 35
|
||||
#max_sdk_version = 35
|
||||
|
||||
# See https://developer.android.com/guide/topics/manifest/uses-feature-element
|
||||
#
|
||||
@@ -109,7 +116,7 @@ version = 1
|
||||
# See https://developer.android.com/guide/topics/manifest/application-element#label
|
||||
#
|
||||
# Defaults to the compiled artifact's name.
|
||||
label = "Application Name"
|
||||
label = "NativeActivity Mainloop"
|
||||
|
||||
# See https://developer.android.com/guide/topics/manifest/meta-data-element
|
||||
#
|
||||
|
||||
@@ -19,6 +19,12 @@ cargo ndk -t arm64-v8a -o app/src/main/jniLibs/ build
|
||||
./gradlew installDebug
|
||||
```
|
||||
|
||||
Run with:
|
||||
|
||||
```
|
||||
adb shell am start -n com.github.realfit_mobile.namainloop/android.app.NativeActivity
|
||||
```
|
||||
|
||||
# Cargo APK Build
|
||||
```
|
||||
export ANDROID_NDK_HOME="path/to/ndk"
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
#!/usr/bin/python3
|
||||
import sys
|
||||
import re
|
||||
|
||||
for line in sys.stdin:
|
||||
search = re.search('#[0-9]+ +pc +([0-9A-Fa-f]+) +', line)
|
||||
if search != None:
|
||||
print(search.group(1))
|
||||
@@ -3,26 +3,23 @@ plugins {
|
||||
}
|
||||
|
||||
android {
|
||||
ndkVersion "25.2.9519653"
|
||||
compileSdk 31
|
||||
compileSdk = 35
|
||||
|
||||
defaultConfig {
|
||||
applicationId "co.realfit.namainloop"
|
||||
minSdk 28
|
||||
targetSdk 31
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
applicationId = "com.github.rust_mobile.namainloop"
|
||||
minSdk = 31
|
||||
targetSdk = 35
|
||||
versionCode = 1
|
||||
versionName = "1.0"
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
minifyEnabled = false
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
debug {
|
||||
minifyEnabled false
|
||||
minifyEnabled = false
|
||||
//packagingOptions {
|
||||
// doNotStrip '**/*.so'
|
||||
//}
|
||||
@@ -30,10 +27,10 @@ android {
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
sourceCompatibility JavaVersion.VERSION_17
|
||||
targetCompatibility JavaVersion.VERSION_17
|
||||
}
|
||||
namespace 'co.realfit.namainloop'
|
||||
namespace = 'com.github.rust_mobile.namainloop'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
@@ -2,15 +2,14 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="NativeActivity Mainloop"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@android:style/Theme.NoTitleBar.Fullscreen">
|
||||
>
|
||||
<activity
|
||||
android:name="android.app.NativeActivity"
|
||||
android:configChanges="orientation|screenSize|screenLayout|keyboardHidden"
|
||||
android:theme="@android:style/Theme.DeviceDefault.Light"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
|
After Width: | Height: | Size: 63 KiB |
@@ -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,5 +1,5 @@
|
||||
<?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" />
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
||||
@@ -1,5 +1,5 @@
|
||||
<?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" />
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
||||
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 3.0 KiB |
|
After Width: | Height: | Size: 4.3 KiB |
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 5.4 KiB |
|
Before Width: | Height: | Size: 982 B After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 3.2 KiB |