From 21e78afac1c11976462c331e122374ab24d9e78c Mon Sep 17 00:00:00 2001 From: David Craven Date: Mon, 14 Feb 2022 18:45:22 +0100 Subject: [PATCH] Add ndk-context. (#223) * Add ndk-context. * Add doc comment for `initialize_android_context`. * Add links to README and some metadata. * Fix ndk-build doc link. * Deprecate `native_activity`. * Revert "Deprecate `native_activity`." This reverts commit bb117f01a3cd3061cd941b8f492005d75a607205. * Address review comments. * Panic if initialized more than once. * Update ndk-examples/examples/jni_audio.rs Co-authored-by: Marijn Suijten * Update ndk-examples/examples/jni_audio.rs Co-authored-by: Marijn Suijten * Use cast() function * fmt * Update ndk-context/src/lib.rs Co-authored-by: Marijn Suijten * Address review comments. * Update ndk-context/src/lib.rs Co-authored-by: Marijn Suijten * Add deprecation warning. * Prepare release. Co-authored-by: Marijn Suijten --- Cargo.toml | 1 + README.md | 6 +- ndk-build/Cargo.toml | 2 +- ndk-context/CHANGELOG.md | 5 ++ ndk-context/Cargo.toml | 11 ++++ ndk-context/README.md | 6 ++ ndk-context/src/lib.rs | 88 ++++++++++++++++++++++++++++++ ndk-examples/Cargo.toml | 1 + ndk-examples/examples/jni_audio.rs | 7 +-- ndk-examples/examples/looper.rs | 1 + ndk-glue/CHANGELOG.md | 4 ++ ndk-glue/Cargo.toml | 5 +- ndk-glue/src/lib.rs | 2 + 13 files changed, 130 insertions(+), 9 deletions(-) create mode 100644 ndk-context/CHANGELOG.md create mode 100644 ndk-context/Cargo.toml create mode 100644 ndk-context/README.md create mode 100644 ndk-context/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index 4f5bb626..b189f3e0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,7 @@ [workspace] members = [ "ndk", + "ndk-context", "ndk-macro", "ndk-build", "ndk-examples", diff --git a/README.md b/README.md index 86ff5a80..e3dc8548 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ Name | Description | Badges --- | --- | --- [`ndk-sys`](./ndk-sys) | Raw FFI bindings to the NDK | [![crates.io](https://img.shields.io/crates/v/ndk-sys.svg)](https://crates.io/crates/ndk-sys) [![crates.io](https://docs.rs/ndk-sys/badge.svg)](https://docs.rs/ndk-sys) [`ndk`](./ndk) | Safe abstraction of the bindings | [![crates.io](https://img.shields.io/crates/v/ndk.svg)](https://crates.io/crates/ndk) [![crates.io](https://docs.rs/ndk/badge.svg)](https://docs.rs/ndk) +[`ndk-context`](./ndk-context) | Android handles | [![crates.io](https://img.shields.io/crates/v/ndk-context.svg)](https://crates.io/crates/ndk-context) [`ndk-glue`](./ndk-glue) | Startup code | [![crates.io](https://img.shields.io/crates/v/ndk-glue.svg)](https://crates.io/crates/ndk-glue) [![crates.io](https://docs.rs/ndk-glue/badge.svg)](https://docs.rs/ndk-glue) [`ndk-build`](./ndk-build) | Everything for building apk's | [![crates.io](https://img.shields.io/crates/v/ndk-build.svg)](https://crates.io/crates/ndk-build) [![crates.io](https://docs.rs/ndk-build/badge.svg)](https://docs.rs/ndk-build) [`cargo-apk`](./cargo-apk) | Build tool | [![crates.io](https://img.shields.io/crates/v/cargo-apk.svg)](https://crates.io/crates/cargo-apk) [![crates.io](https://docs.rs/cargo-apk/badge.svg)](https://docs.rs/cargo-apk) @@ -105,7 +106,8 @@ fn main() {} ``` ## JNI -Java Native Interface (JNI) allows executing Java code in a VM from native applications. -`ndk-examples` contains an `jni_audio` example which will print out all output audio devices in the log. +Java Native Interface (JNI) allows executing Java code in a VM from native applications. To access +the JNI use the `AndroidContext` from the `ndk-context` crate. `ndk-examples` contains a `jni_audio` +example which will print out all output audio devices in the log. - [`jni`](https://crates.io/crates/jni), JNI bindings for Rust diff --git a/ndk-build/Cargo.toml b/ndk-build/Cargo.toml index f501b553..a462de81 100644 --- a/ndk-build/Cargo.toml +++ b/ndk-build/Cargo.toml @@ -6,7 +6,7 @@ edition = "2018" description = "Utilities for building Android binaries" license = "MIT OR Apache-2.0" keywords = ["android", "ndk", "apk"] -documentation = "https://docs.rs/android-build-tools" +documentation = "https://docs.rs/ndk-build" homepage = "https://github.com/rust-windowing/android-ndk-rs" repository = "https://github.com/rust-windowing/android-ndk-rs" diff --git a/ndk-context/CHANGELOG.md b/ndk-context/CHANGELOG.md new file mode 100644 index 00000000..fd73a0da --- /dev/null +++ b/ndk-context/CHANGELOG.md @@ -0,0 +1,5 @@ +# Unreleased + +# 0.1.0 (2022-02-14) + +- Initial release! 🎉 diff --git a/ndk-context/Cargo.toml b/ndk-context/Cargo.toml new file mode 100644 index 00000000..9215a91f --- /dev/null +++ b/ndk-context/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "ndk-context" +version = "0.1.0" +authors = ["The Rust Windowing contributors"] +edition = "2021" +description = "Handles for accessing Android APIs" +license = "MIT OR Apache-2.0" +keywords = ["android", "ndk", "apk", "jni"] +documentation = "https://docs.rs/ndk-context" +homepage = "https://github.com/rust-windowing/android-ndk-rs" +repository = "https://github.com/rust-windowing/android-ndk-rs" diff --git a/ndk-context/README.md b/ndk-context/README.md new file mode 100644 index 00000000..bd8858ab --- /dev/null +++ b/ndk-context/README.md @@ -0,0 +1,6 @@ +# ndk-context + +Provides a stable api to rust crates for interfacing with the android platform. It is +initialized by the runtime, usually [__ndk-glue__](https://crates.io/crates/ndk-glue), +but could also be initialized by java or kotlin code when embedding in an existing android +project. diff --git a/ndk-context/src/lib.rs b/ndk-context/src/lib.rs new file mode 100644 index 00000000..53d8440f --- /dev/null +++ b/ndk-context/src/lib.rs @@ -0,0 +1,88 @@ +//! Provides a stable api to rust crates for interfacing with the android platform. It is +//! initialized by the runtime, usually [__ndk-glue__](https://crates.io/crates/ndk-glue), +//! but could also be initialized by java or kotlin code when embedding in an existing android +//! project. +//! +//! ```no_run +//! let ctx = ndk_context::android_context(); +//! let vm = unsafe { jni::JavaVM::from_raw(ctx.vm().cast()) }?; +//! let env = vm.attach_current_thread(); +//! let class_ctx = env.find_class("android/content/Context")?; +//! let audio_service = env.get_static_field(class_ctx, "AUDIO_SERVICE", "Ljava/lang/String;")?; +//! let audio_manager = env +//! .call_method( +//! ctx.context() as jni::sys::jobject, +//! "getSystemService", +//! "(Ljava/lang/String;)Ljava/lang/Object;", +//! &[audio_service], +//! )? +//! .l()?; +//! ``` +use std::ffi::c_void; + +static mut ANDROID_CONTEXT: Option = None; + +/// [`AndroidContext`] provides the pointers required to interface with the jni on android +/// platforms. +#[derive(Clone, Copy, Debug)] +pub struct AndroidContext { + java_vm: *mut c_void, + context_jobject: *mut c_void, +} + +impl AndroidContext { + /// A handle to the `JavaVM` object. + /// + /// Usage with [__jni__](https://crates.io/crates/jni) crate: + /// ```no_run + /// let ctx = ndk_context::android_context(); + /// let vm = unsafe { jni::JavaVM::from_raw(ctx.vm().cast()) }?; + /// let env = vm.attach_current_thread(); + /// ``` + pub fn vm(self) -> *mut c_void { + self.java_vm + } + + /// A handle to an [android.content.Context](https://developer.android.com/reference/android/content/Context). + /// In most cases this will be a ptr to an `Activity`, but this isn't guaranteed. + /// + /// Usage with [__jni__](https://crates.io/crates/jni) crate: + /// ```no_run + /// let ctx = ndk_context::android_context(); + /// let vm = unsafe { jni::JavaVM::from_raw(ctx.vm().cast()) }?; + /// let env = vm.attach_current_thread(); + /// let class_ctx = env.find_class("android/content/Context")?; + /// let audio_service = env.get_static_field(class_ctx, "AUDIO_SERVICE", "Ljava/lang/String;")?; + /// let audio_manager = env + /// .call_method( + /// ctx.context() as jni::sys::jobject, + /// "getSystemService", + /// "(Ljava/lang/String;)Ljava/lang/Object;", + /// &[audio_service], + /// )? + /// .l()?; + /// ``` + pub fn context(self) -> *mut c_void { + self.context_jobject + } +} + +/// Main entry point to this crate. Returns an [`AndroidContext`]. +pub fn android_context() -> AndroidContext { + unsafe { ANDROID_CONTEXT.expect("android context was not initialized") } +} + +/// Initializes the [`AndroidContext`]. [`AndroidContext`] is initialized by [__ndk-glue__](https://crates.io/crates/ndk-glue) +/// before `main` is called. +/// +/// # Safety +/// +/// The pointers must be valid and this function must be called exactly once before `main` is +/// called. +pub unsafe fn initialize_android_context(java_vm: *mut c_void, context_jobject: *mut c_void) { + let previous = ANDROID_CONTEXT.replace(AndroidContext { + java_vm, + context_jobject, + }); + assert!(previous.is_none()); +} diff --git a/ndk-examples/Cargo.toml b/ndk-examples/Cargo.toml index f3dddfaa..2dd8c7f8 100644 --- a/ndk-examples/Cargo.toml +++ b/ndk-examples/Cargo.toml @@ -10,6 +10,7 @@ jni = "0.18.0" libc = "0.2" log = "0.4.14" ndk = { path = "../ndk", features = ["trace"] } +ndk-context = { path = "../ndk-context" } ndk-glue = { path = "../ndk-glue", features = ["logger"] } [[example]] diff --git a/ndk-examples/examples/jni_audio.rs b/ndk-examples/examples/jni_audio.rs index f4654ea0..9880bf67 100644 --- a/ndk-examples/examples/jni_audio.rs +++ b/ndk-examples/examples/jni_audio.rs @@ -7,9 +7,8 @@ const GET_DEVICES_OUTPUTS: jni::sys::jint = 2; fn enumerate_audio_devices() -> Result<(), Box> { // Create a VM for executing Java calls - let native_activity = ndk_glue::native_activity(); - let vm_ptr = native_activity.vm(); - let vm = unsafe { jni::JavaVM::from_raw(vm_ptr) }?; + let ctx = ndk_context::android_context(); + let vm = unsafe { jni::JavaVM::from_raw(ctx.vm().cast()) }?; let env = vm.attach_current_thread()?; // Query the global Audio Service @@ -18,7 +17,7 @@ fn enumerate_audio_devices() -> Result<(), Box> { let audio_manager = env .call_method( - native_activity.activity(), + ctx.context().cast(), "getSystemService", // JNI type signature needs to be derived from the Java API // (ArgTys)ResultTy diff --git a/ndk-examples/examples/looper.rs b/ndk-examples/examples/looper.rs index 8a7e71de..eb07e89b 100644 --- a/ndk-examples/examples/looper.rs +++ b/ndk-examples/examples/looper.rs @@ -154,5 +154,6 @@ fn main() { } // Stop the activity + #[allow(deprecated)] ndk_glue::native_activity().finish() } diff --git a/ndk-glue/CHANGELOG.md b/ndk-glue/CHANGELOG.md index 151df426..879267db 100644 --- a/ndk-glue/CHANGELOG.md +++ b/ndk-glue/CHANGELOG.md @@ -1,5 +1,9 @@ # Unreleased +# 0.6.1 (2022-02-14) + +- Initializes `ndk-context`. + # 0.6.0 (2022-01-05) - **Breaking:** Update to `ndk-sys 0.3.0` and `ndk 0.6.0`. diff --git a/ndk-glue/Cargo.toml b/ndk-glue/Cargo.toml index 351dc857..8916bc19 100644 --- a/ndk-glue/Cargo.toml +++ b/ndk-glue/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ndk-glue" -version = "0.6.0" +version = "0.6.1" authors = ["The Rust Windowing contributors"] edition = "2018" description = "Startup code for android binaries" @@ -13,8 +13,9 @@ repository = "https://github.com/rust-windowing/android-ndk-rs" [dependencies] ndk = { path = "../ndk", version = "0.6.0" } -ndk-sys = { path = "../ndk-sys", version = "0.3.0" } +ndk-context = { path = "../ndk-context", version = "0.1.0" } ndk-macro = { path = "../ndk-macro", version = "0.3.0" } +ndk-sys = { path = "../ndk-sys", version = "0.3.0" } lazy_static = "1.4.0" libc = "0.2.84" log = "0.4.14" diff --git a/ndk-glue/src/lib.rs b/ndk-glue/src/lib.rs index 3d83214f..4dba447e 100644 --- a/ndk-glue/src/lib.rs +++ b/ndk-glue/src/lib.rs @@ -55,6 +55,7 @@ lazy_static! { static mut NATIVE_ACTIVITY: Option = None; +#[deprecated = "Use `ndk_context::android_context().vm()` instead."] pub fn native_activity() -> &'static NativeActivity { unsafe { NATIVE_ACTIVITY.as_ref().unwrap() } } @@ -165,6 +166,7 @@ pub unsafe fn init( callbacks.onLowMemory = Some(on_low_memory); let activity = NativeActivity::from_ptr(activity); + ndk_context::initialize_android_context(activity.vm().cast(), activity.activity().cast()); NATIVE_ACTIVITY = Some(activity); let mut logpipe: [RawFd; 2] = Default::default();