Skip to content

Commit

Permalink
Android: rework backend to use android-activity crate (rust-windowing…
Browse files Browse the repository at this point in the history
…#2444)

This updates the Android backend to use the android-activity crate instead
of ndk-glue. This solves a few issues:
1. The backend is agnostic of the application's choice of Activity base
   class
2. Winit is no longer responsible for handling any Java synchronization
   details, since these are encapsulated by the design of
   android_activity
3. The backend no longer depends on global / static getters for state
   such as the native_window() which puts it in a better position to
   support running multiple activities within a single Android process.
4. Redraw requests are flagged, not queued, in a way that avoids taking
   priority over user events (resolves rust-windowing#2299)

To make it possible for application crates to avoid explicitly
depending on the `android-activity` crate (and avoid version conflicts)
this re-exports the android-activity crate under:

  `winit::platform::android::activity::*`

This also adds `android-native-activity` and `android-game-activity`
features that set the corresponding android-activity features.

Addresses: PR rust-windowing#1892
Addresses: PR rust-windowing#2307
Addresses: PR rust-windowing#2343

Addresses: rust-windowing#2293
Resolves: rust-windowing#2299

Co-authored-by: Markus Siglreithmaier <m.siglreith@gmail.com>

Co-authored-by: Markus Siglreithmaier <m.siglreith@gmail.com>
  • Loading branch information
rib and msiglreith committed Nov 10, 2022
1 parent 50bbc85 commit 05484c5
Show file tree
Hide file tree
Showing 8 changed files with 741 additions and 427 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Expand Up @@ -33,7 +33,7 @@ jobs:
- { target: x86_64-unknown-linux-gnu, os: ubuntu-latest, }
- { target: x86_64-unknown-linux-gnu, os: ubuntu-latest, options: --no-default-features, features: x11 }
- { target: x86_64-unknown-linux-gnu, os: ubuntu-latest, options: --no-default-features, features: "wayland,wayland-dlopen" }
- { target: aarch64-linux-android, os: ubuntu-latest, cmd: 'apk --' }
- { target: aarch64-linux-android, os: ubuntu-latest, cmd: 'apk --', features: "android-native-activity" }
- { target: x86_64-apple-darwin, os: macos-latest, }
- { target: x86_64-apple-ios, os: macos-latest, }
- { target: aarch64-apple-ios, os: macos-latest, }
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -31,6 +31,7 @@ And please only add new entries to the top of this list, right below the `# Unre
- **Breaking:** Removed `WindowExtWindows::theme` in favour of `Window::theme`.
- Enabled `doc_auto_cfg` when generating docs on docs.rs for feature labels.
- On macOS, fix panic when getting current monitor without any monitor attached.
- **Breaking:** On Android, switched to using [`android-activity`](https://github.com/rib/android-activity) crate as a glue layer instead of [`ndk-glue](https://github.com/rust-windowing/android-ndk-rs/tree/master/ndk-glue). See [README.md#Android](https://github.com/rust-windowing/winit#Android) for more details. ([#2444](https://github.com/rust-windowing/winit/pull/2444))

# 0.27.5

Expand Down
4 changes: 3 additions & 1 deletion Cargo.toml
Expand Up @@ -42,6 +42,8 @@ wayland-dlopen = ["sctk/dlopen", "wayland-client/dlopen"]
wayland-csd-adwaita = ["sctk-adwaita", "sctk-adwaita/ab_glyph"]
wayland-csd-adwaita-crossfont = ["sctk-adwaita", "sctk-adwaita/crossfont"]
wayland-csd-adwaita-notitle = ["sctk-adwaita"]
android-native-activity = [ "android-activity/native-activity" ]
android-game-activity = [ "android-activity/game-activity" ]

[dependencies]
instant = { version = "0.1", features = ["wasm-bindgen"] }
Expand All @@ -60,7 +62,7 @@ simple_logger = { version = "2.1.0", default_features = false }
[target.'cfg(target_os = "android")'.dependencies]
# Coordinate the next winit release with android-ndk-rs: https://github.com/rust-windowing/winit/issues/1995
ndk = "0.7.0"
ndk-glue = "0.7.0"
android-activity = "0.4.0-beta.1"

[target.'cfg(any(target_os = "ios", target_os = "macos"))'.dependencies]
objc2 = "=0.3.0-beta.3"
Expand Down
22 changes: 11 additions & 11 deletions FEATURES.md
Expand Up @@ -188,41 +188,41 @@ Legend:
|Fullscreen |✔️ |✔️ |✔️ |✔️ |**N/A**|✔️ |✔️ |
|Fullscreen toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|✔️ |✔️ |
|Exclusive fullscreen |✔️ |✔️ |✔️ |**N/A** ||✔️ |**N/A**|
|HiDPI support |✔️ |✔️ |✔️ |✔️ |[#721]|✔️ |✔️ |
|HiDPI support |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |
|Popup windows |||||||**N/A**|

### System information
|Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |WASM |
|---------------- | ----- | ---- | ------- | ----------- | ----- | ------- | -------- |
|Monitor list |✔️ |✔️ |✔️ |✔️ |**N/A**|✔️ |**N/A**|
|Video mode query |✔️ |✔️ |✔️ |✔️ | |✔️ |**N/A**|
|Monitor list |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |**N/A**|
|Video mode query |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |**N/A**|

### Input handling
|Feature |Windows |MacOS |Linux x11|Linux Wayland|Android|iOS |WASM |
|----------------------- | ----- | ---- | ------- | ----------- | ----- | ----- | -------- |
|Mouse events |✔️ |[#63] |✔️ |✔️ |**N/A**|**N/A**|✔️ |
|Mouse set location |✔️ |✔️ |✔️ |✔️(when locked) |**N/A**|**N/A**|**N/A**|
|Cursor locking ||✔️ ||✔️ |**N/A**|**N/A**|✔️ |
|Cursor confining |✔️ ||✔️ |✔️ |**N/A**|**N/A**||
|Cursor icon |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|✔️ |
|Cursor hittest |✔️ |✔️ ||✔️ |**N/A**|**N/A**||
|Cursor locking | |✔️ | |✔️ |**N/A**|**N/A**|✔️ |
|Cursor confining |✔️ | |✔️ |✔️ |**N/A**|**N/A**||
|Cursor icon |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|✔️ |
|Cursor hittest |✔️ |✔️ ||✔️ |**N/A**|**N/A**||
|Touch events |✔️ ||✔️ |✔️ |✔️ |✔️ ||
|Touch pressure |✔️ |||||✔️ ||
|Multitouch |✔️ ||✔️ |✔️ |✔️ |✔️ ||
|Keyboard events |✔️ |✔️ |✔️ |✔️ | ||✔️ |
|Keyboard events |✔️ |✔️ |✔️ |✔️ |✔️ ||✔️ |
|Drag & Drop |[#720] |[#720] |[#720] |[#306] |**N/A**|**N/A**||
|Raw Device Events |[#750] |[#750] |[#750] |||||
|Gamepad/Joystick events |[#804] |||||||
|Device movement events ||||||||
|Drag window with cursor |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A** |
|Drag window with cursor |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A** |

### Pending API Reworks
Changes in the API that have been agreed upon but aren't implemented across all platforms.

|Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |WASM |
|------------------------------ | ----- | ---- | ------- | ----------- | ----- | ----- | -------- |
|New API for HiDPI ([#315] [#319]) |✔️ |✔️ |✔️ |✔️ |[#721]|✔️ ||
|Event Loop 2.0 ([#459]) |✔️ |✔️ ||✔️ | |✔️ ||
|New API for HiDPI ([#315] [#319]) |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ ||
|Event Loop 2.0 ([#459]) |✔️ |✔️ ||✔️ |✔️ |✔️ ||
|Keyboard Input ([#812]) ||||||||

### Completed API Reworks
Expand Down
91 changes: 75 additions & 16 deletions README.md
Expand Up @@ -99,36 +99,95 @@ book].

#### Android

This library makes use of the [ndk-rs](https://github.com/rust-windowing/android-ndk-rs) crates, refer to that repo for more documentation.
The Android backend builds on (and exposes types from) the [`ndk`](https://docs.rs/ndk/0.7.0/ndk/) crate.

The `ndk-glue` version needs to match the version used by `winit`. Otherwise, the application will not start correctly as `ndk-glue`'s internal `NativeActivity` static is not the same due to version mismatch.
Native Android applications need some form of "glue" crate that is responsible
for defining the main entry point for your Rust application as well as tracking
various life-cycle events and synchronizing with the main JVM thread.

`winit` compatibility table with `ndk-glue`:
Winit uses the [android-activity](https://github.com/rib/android-activity) as a
glue crate (prior to `0.28` it used
[ndk-glue](https://github.com/rust-windowing/android-ndk-rs/tree/master/ndk-glue).

| winit | ndk-glue |
| :---: | :------------------: |
| 0.24 | `ndk-glue = "0.2.0"` |
| 0.25 | `ndk-glue = "0.3.0"` |
| 0.26 | `ndk-glue = "0.5.0"` |
| 0.27 | `ndk-glue = "0.7.0"` |
The version of the glue crate that your application depends on _must_ match the
version that Winit depends on because the glue crate is responsible for your
application's main entrypoint. If Cargo resolves multiple versions they will
clash.

`winit` glue compatibility table:

| winit | ndk-glue |
| :---: | :--------------------------: |
| 0.28 | `android-activity = "0.4"` |
| 0.27 | `ndk-glue = "0.7"` |
| 0.26 | `ndk-glue = "0.5"` |
| 0.25 | `ndk-glue = "0.3"` |
| 0.24 | `ndk-glue = "0.2"` |

The recommended way to avoid a conflict with the glue version is to avoid explicitly
depending on the `android-activity` crate, and instead consume the API that
is re-exported by Winit under `winit::platform::android::activity::*`

Running on an Android device needs a dynamic system library, add this to Cargo.toml:

```toml
[[example]]
name = "request_redraw_threaded"
[lib]
name = "main"
crate-type = ["cdylib"]
```

And add this to the example file to add the native activity glue:
All Android applications are based on an `Activity` subclass and the
`android-activity` crate is designed to support different choices for this base
class. Your application _must_ specify the base class it needs via a feature flag:

| Base Class | Feature Flag | Notes |
| :--------------: | :---------------: | :-----: |
| `NativeActivity` | `android-native-activity` | Built-in to Android - it is possible to use without compiling any Java or Kotlin code. Java or Kotlin code may be needed to subclass `NativeActivity` to access some platform features. It does not derive from the [`AndroidAppCompat`] base class.|
| [`GameActivity`] | `android-game-activity` | Derives from [`AndroidAppCompat`] which is a defacto standard `Activity` base class that helps support a wider range of Android versions. Requires a build system that can compile Java or Kotlin and fetch Android dependencies from a [Maven repository][agdk_jetpack] (or link with an embedded [release][agdk_releases] of [`GameActivity`]) |

[`GameActivity`]: https://developer.android.com/games/agdk/game-activity
[`GameTextInput`]: https://developer.android.com/games/agdk/add-support-for-text-input
[`AndroidAppCompat`]: https://developer.android.com/reference/androidx/appcompat/app/AppCompatActivity
[agdk_jetpack]: https://developer.android.com/jetpack/androidx/releases/games
[agdk_releases]: https://developer.android.com/games/agdk/download#agdk-libraries
[Gradle]: https://developer.android.com/studio/build

For example, add this to Cargo.toml:
```toml
winit = { version = "0.28", features = [ "android-native-activity" ] }

[target.'cfg(target_os = "android")'.dependencies]
android_logger = "0.11.0"
```

And, for example, define an entry point for your library like this:
```rust
#[cfg_attr(target_os = "android", ndk_glue::main(backtrace = "on"))]
fn main() {
...
#[cfg(target_os = "android")]
use winit::platform::android::activity::AndroidApp;

#[cfg(target_os = "android")]
#[no_mangle]
fn android_main(app: AndroidApp) {
use winit::platform::android::EventLoopBuilderExtAndroid;

android_logger::init_once(android_logger::Config::default().with_min_level(log::Level::Trace));

let event_loop = EventLoopBuilder::with_user_event()
.with_android_app(app)
.build();
_main(event_loop);
}
```

And run the application with `cargo apk run --example request_redraw_threaded`
For more details, refer to these `android-activity` [example applications](https://github.com/rib/android-activity/tree/main/examples).

##### Converting from `ndk-glue` to `android-activity`

If your application is currently based on `NativeActivity` via the `ndk-glue` crate and building with `cargo apk` then the minimal changes would be:
1. Remove `ndk-glue` from your `Cargo.toml`
2. Enable the `"android-native-activity"` feature for Winit: `winit = { version = "0.28", features = [ "android-native-activity" ] }`
3. Add an `android_main` entrypoint (as above), instead of using the '`[ndk_glue::main]` proc macro from `ndk-macros` (optionally add a dependency on `android_logger` and initialize logging as above).
4. Pass a clone of the `AndroidApp` that your application receives to Winit when building your event loop (as shown above).

#### MacOS

Expand Down
10 changes: 10 additions & 0 deletions src/event_loop.rs
Expand Up @@ -97,8 +97,18 @@ impl<T> EventLoopBuilder<T> {
/// `WINIT_UNIX_BACKEND`. Legal values are `x11` and `wayland`.
/// If it is not set, winit will try to connect to a Wayland connection, and if that fails,
/// will fall back on X11. If this variable is set with any other value, winit will panic.
/// - **Android:** Must be configured with an `AndroidApp` from `android_main()` by calling
/// [`.with_android_app(app)`] before calling `.build()`.
///
/// [`platform`]: crate::platform
#[cfg_attr(
target_os = "android",
doc = "[`.with_android_app(app)`]: crate::platform::android::EventLoopBuilderExtAndroid::with_android_app"
)]
#[cfg_attr(
not(target_os = "android"),
doc = "[`.with_android_app(app)`]: #only-available-on-android"
)]
#[inline]
pub fn build(&mut self) -> EventLoop<T> {
static EVENT_LOOP_CREATED: OnceCell<()> = OnceCell::new();
Expand Down
50 changes: 45 additions & 5 deletions src/platform/android.rs
@@ -1,9 +1,9 @@
use crate::{
event_loop::{EventLoop, EventLoopWindowTarget},
event_loop::{EventLoop, EventLoopBuilder, EventLoopWindowTarget},
window::{Window, WindowBuilder},
};
use ndk::configuration::Configuration;
use ndk_glue::Rect;

use android_activity::{AndroidApp, ConfigurationRef, Rect};

/// Additional methods on [`EventLoop`] that are specific to Android.
pub trait EventLoopExtAndroid {}
Expand All @@ -17,15 +17,15 @@ pub trait EventLoopWindowTargetExtAndroid {}
pub trait WindowExtAndroid {
fn content_rect(&self) -> Rect;

fn config(&self) -> Configuration;
fn config(&self) -> ConfigurationRef;
}

impl WindowExtAndroid for Window {
fn content_rect(&self) -> Rect {
self.window.content_rect()
}

fn config(&self) -> Configuration {
fn config(&self) -> ConfigurationRef {
self.window.config()
}
}
Expand All @@ -36,3 +36,43 @@ impl<T> EventLoopWindowTargetExtAndroid for EventLoopWindowTarget<T> {}
pub trait WindowBuilderExtAndroid {}

impl WindowBuilderExtAndroid for WindowBuilder {}

pub trait EventLoopBuilderExtAndroid {
/// Associates the `AndroidApp` that was passed to `android_main()` with the event loop
///
/// This must be called on Android since the `AndroidApp` is not global state.
fn with_android_app(&mut self, app: AndroidApp) -> &mut Self;
}

impl<T> EventLoopBuilderExtAndroid for EventLoopBuilder<T> {
fn with_android_app(&mut self, app: AndroidApp) -> &mut Self {
self.platform_specific.android_app = Some(app);
self
}
}

/// Re-export of the `android_activity` API
///
/// Winit re-exports the `android_activity` API for convenience so that most
/// applications can rely on the Winit crate to resolve the required version of
/// `android_activity` and avoid any chance of a conflict between Winit and the
/// application crate.
///
/// Unlike most libraries there can only be a single implementation
/// of the `android_activity` glue crate linked with an application because
/// it is responsible for the application's `android_main()` entry point.
///
/// Since Winit depends on a specific version of `android_activity` the simplest
/// way to avoid creating a conflict is for applications to avoid explicitly
/// depending on the `android_activity` crate, and instead consume the API that
/// is re-exported by Winit.
///
/// For compatibility applications should then import the `AndroidApp` type for
/// their `android_main(app: AndroidApp)` function like:
/// ```rust
/// #[cfg(target_os="android")]
/// use winit::platform::android::activity::AndroidApp;
/// ```
pub mod activity {
pub use android_activity::*;
}

0 comments on commit 05484c5

Please sign in to comment.