iris: add the android-app cdylib crate (I2, part 3)
iris/android-app is the concrete app RUST.md's I2 is judged against: JNI_OnLoad, a Client implementing AndroidAppState, and new_view_peer wrapping iris::android::new_peer's generic function in the plain function pointer register_view_class needs. Its UI is tabs-ui::build, unchanged from the winit example. Deliberately excluded from the iris workspace (iris/Cargo.toml's new `exclude`): android-view needs the NDK sysroot to link, so folding this crate in would break `cargo build --workspace --all-targets` on the host. It resolves as its own single-crate workspace instead, built with `cd iris/android-app && cargo ndk -t x86_64 -P 26 build`. Verified: cross-compiles and clippys clean for x86_64-linux-android API 26; the host iris workspace (build/clippy/fmt/19 tests) is unaffected. Not yet built: the Gradle shell (IrisView.java, MainActivity, AndroidManifest) to actually install and run this on the emulator -- next in RUST.md's I2. Co-Authored-By: Claude Sonnet <noreply@anthropic.com>
This commit is contained in:
1 parent
9c935f8ce8
commit
f79bd7ca71
5 files changed
+4280
No files matched your search
@@ -26,3 +26,4 @@ sessions/
|
|||||||
|
|
||||||
# iris, the in-house UI library, is vendored at iris/ and built by cargo.
|
# iris, the in-house UI library, is vendored at iris/ and built by cargo.
|
||||||
iris/target/
|
iris/target/
|
||||||
|
iris/android-app/target/
|
||||||
@@ -49,6 +49,13 @@ tabs-ui = { path = "tabs-ui" }
|
|||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
members = ["core", "macro", "tabs-ui"]
|
members = ["core", "macro", "tabs-ui"]
|
||||||
|
# android-app pulls in android-view, which needs the NDK sysroot to link
|
||||||
|
# -- excluded so `cargo build --workspace --all-targets` on the host stays
|
||||||
|
# buildable. Cross-compile it from its own directory (its own single-crate
|
||||||
|
# workspace, since it has no `[workspace]` table of its own and this
|
||||||
|
# exclusion stops it inheriting this one): `cd android-app && cargo ndk
|
||||||
|
# -t x86_64 -P 26 build`.
|
||||||
|
exclude = ["android-app"]
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|||||||
Generated
+4156
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,29 @@
|
|||||||
|
[package]
|
||||||
|
name = "iris-android-app"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
# Deliberately outside the `iris` workspace (see that Cargo.toml's
|
||||||
|
# `[workspace] exclude`): this crate exists only to be cross-compiled with
|
||||||
|
# `cargo ndk` for the emulator/a phone, and pulls in android-view, which
|
||||||
|
# needs the NDK sysroot to link. Folding it into the main workspace would
|
||||||
|
# make `cargo build --workspace --all-targets` -- the host command RUST.md
|
||||||
|
# and AGENTS.md both require to stay clean -- try to link a cdylib against
|
||||||
|
# libraries that do not exist on this machine. See RUST.md's I2.
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
name = "main"
|
||||||
|
crate-type = ["cdylib"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
iris = { path = "../" }
|
||||||
|
tabs-ui = { path = "../tabs-ui" }
|
||||||
|
android-view = { git = "https://github.com/rust-mobile/android-view.git", rev = "bec6c62a96cef8239b0fd7fedeef9b184d02e3a1" }
|
||||||
|
android_logger = "0.15.0"
|
||||||
|
log = "0.4.28"
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
panic = "abort"
|
||||||
|
|
||||||
|
[profile.dev]
|
||||||
|
panic = "abort"
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
//! The android-view demo app: iris's `tabs` widget tree (`tabs_ui::build`,
|
||||||
|
//! shared with the winit example) running through
|
||||||
|
//! `iris::android`'s `ViewPeer`. This is RUST.md's I2 pass condition made
|
||||||
|
//! concrete -- there is no UI here beyond what `tabs-ui` already draws.
|
||||||
|
//!
|
||||||
|
//! `JNI_OnLoad` and `new_view_peer` mirror android-view's own demo
|
||||||
|
//! (`~/src/android-view/demo/src/lib.rs`): the only android-view-specific
|
||||||
|
//! plumbing a real app needs is registering its `View` subclass and
|
||||||
|
//! wrapping `iris::android::new_peer`'s generic function in a concrete
|
||||||
|
//! `extern "system" fn`, since `register_view_class` wants a plain
|
||||||
|
//! function pointer.
|
||||||
|
|
||||||
|
use android_view::{
|
||||||
|
Context, View,
|
||||||
|
jni::{
|
||||||
|
JNIEnv, JavaVM,
|
||||||
|
sys::{JNI_VERSION_1_6, JavaVM as RawJavaVM, jint, jlong},
|
||||||
|
},
|
||||||
|
register_view_class,
|
||||||
|
};
|
||||||
|
use iris::android::{AndroidAppState, AndroidRsc, AndroidUiState, HasAndroidUiState};
|
||||||
|
use iris::prelude::*;
|
||||||
|
use log::LevelFilter;
|
||||||
|
use std::ffi::c_void;
|
||||||
|
|
||||||
|
/// The app's `View` subclass, matching the Java side's package --
|
||||||
|
/// `app/src/main/java/dev/iris/android/demo/IrisView.java`.
|
||||||
|
const VIEW_CLASS: &str = "dev/iris/android/demo/IrisView";
|
||||||
|
|
||||||
|
pub struct Client {
|
||||||
|
ui_state: AndroidUiState,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HasAndroidUiState for Client {
|
||||||
|
fn android_state(&self) -> &AndroidUiState {
|
||||||
|
&self.ui_state
|
||||||
|
}
|
||||||
|
fn android_state_mut(&mut self) -> &mut AndroidUiState {
|
||||||
|
&mut self.ui_state
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AndroidAppState for Client {
|
||||||
|
fn new(mut ui_state: AndroidUiState, rsc: &mut AndroidRsc<Self>) -> Self {
|
||||||
|
// `widgets.info` is the winit example's frame-debug readout, kept
|
||||||
|
// current from `DefaultAppState::window_event` -- android-view has
|
||||||
|
// no per-frame hook to drive the equivalent from here yet, so it
|
||||||
|
// is left at its built "" text rather than wired to nothing.
|
||||||
|
let _ = tabs_ui::build(rsc, &mut ui_state);
|
||||||
|
Self { ui_state }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn back_pressed(&mut self, _rsc: &mut AndroidRsc<Self>, _render: &mut UiRenderState) -> bool {
|
||||||
|
// Nothing in the tabs example has a back stack of its own to pop --
|
||||||
|
// declining lets the activity finish, which is the same "no
|
||||||
|
// handler" behaviour the default impl gives. Present as an
|
||||||
|
// explicit override (rather than relying on the default) so a
|
||||||
|
// reader checking "does the back gesture reach this app" finds an
|
||||||
|
// answer here rather than nothing.
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "system" fn new_view_peer<'local>(
|
||||||
|
env: JNIEnv<'local>,
|
||||||
|
view: View<'local>,
|
||||||
|
context: Context<'local>,
|
||||||
|
) -> jlong {
|
||||||
|
iris::android::new_peer::<Client>(env, view, context)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// # Safety
|
||||||
|
/// Interacting with JNI at load time is always unsafe at some level --
|
||||||
|
/// mirrors android-view's own demo, which carries the same comment.
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub unsafe extern "system" fn JNI_OnLoad(vm: *mut RawJavaVM, _: *mut c_void) -> jint {
|
||||||
|
android_logger::init_once(
|
||||||
|
android_logger::Config::default()
|
||||||
|
.with_max_level(LevelFilter::Debug)
|
||||||
|
.with_tag("iris-android-app"),
|
||||||
|
);
|
||||||
|
let vm = unsafe { JavaVM::from_raw(vm) }.unwrap();
|
||||||
|
let mut env = vm.get_env().unwrap();
|
||||||
|
register_view_class(&mut env, VIEW_CLASS, new_view_peer);
|
||||||
|
iris::android::register_native_methods(&mut env, VIEW_CLASS);
|
||||||
|
JNI_VERSION_1_6
|
||||||
|
}
|
||||||
Reference in new issue
Block a user