From 13d1aa573826832f3ca7c941af688dc97b0da671 Mon Sep 17 00:00:00 2001 From: enricobuehler Date: Thu, 9 Jul 2026 11:35:16 +0200 Subject: [PATCH] =?UTF-8?q?feat(clients/android):=20OnFrameRendered=20disp?= =?UTF-8?q?lay=20stage=20=E2=80=94=20HUD=20headline=20becomes=20capture?= =?UTF-8?q?=E2=86=92displayed?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The long-deferred Android display stage (design/stats-unification.md; plan 4.1 of design/client-parity-and-network-resilience.md): AMediaCodec_setOnFrameRenderedCallback (API 26, under the minSdk-28 floor ⇒ hard-linked via ndk-sys) reports SurfaceFlinger's per-frame render timestamp, giving the HUD the spec's `display` = decoded→displayed term and the directly-measured capture→displayed end-to-end headline on both decode loops. Falls back per spec to the v1 capture→decoded endpoint on any window without render callbacks (the platform may drop them under load), and to it permanently if registration is refused. - The render timestamp arrives on CLOCK_MONOTONIC; it's re-based onto CLOCK_REALTIME against monotonic-now at callback time, which also cancels the (batchable) callback delivery lag. - The `ndk` crate exposes neither the callback nor the codec pointer needed to bind it raw, so the workspace pins `ndk` 0.9.0 to a vendored copy (clients/android/native/ vendor/ndk) whose ONLY change makes MediaCodec::as_ptr public — the "as_ptr patch". Workspace-excluded so host builds never compile it; drop when upstream exposes either. - nativeVideoStats grows to 26 doubles (22–25: dispValid, displayP50, e2eDispP50/P95; 0–21 unchanged for older readers); StatsOverlay moves headline endpoint + equation together so the equation always tiles the headline interval. Verified: host cargo check/test/clippy, aarch64-linux-android check/clippy, Kotlin app+kit+tests compile, roborazzi HUD render shows the full 4-term equation. Device verification rides plan 4.2's phone A/B. Co-Authored-By: Claude Fable 5 --- Cargo.lock | 3 +- Cargo.toml | 14 +- .../kotlin/io/unom/punktfunk/StatsOverlay.kt | 35 +- .../unom/punktfunk/screenshots/ShotScenes.kt | 16 +- .../io/unom/punktfunk/kit/NativeBridge.kt | 14 +- clients/android/native/Cargo.toml | 6 + clients/android/native/src/decode.rs | 247 ++- clients/android/native/src/session/planes.rs | 23 +- clients/android/native/src/stats.rs | 70 +- clients/android/native/vendor/ndk/.gitignore | 3 + .../android/native/vendor/ndk/CHANGELOG.md | 137 ++ clients/android/native/vendor/ndk/Cargo.toml | 125 ++ clients/android/native/vendor/ndk/README.md | 21 + .../native/vendor/ndk/README.punktfunk.md | 14 + .../android/native/vendor/ndk/src/asset.rs | 323 ++++ .../android/native/vendor/ndk/src/audio.rs | 1419 +++++++++++++++ .../android/native/vendor/ndk/src/bitmap.rs | 489 +++++ .../native/vendor/ndk/src/configuration.rs | 600 +++++++ .../native/vendor/ndk/src/data_space.rs | 648 +++++++ .../android/native/vendor/ndk/src/event.rs | 1583 +++++++++++++++++ clients/android/native/vendor/ndk/src/font.rs | 552 ++++++ .../native/vendor/ndk/src/hardware_buffer.rs | 640 +++++++ .../vendor/ndk/src/hardware_buffer_format.rs | 106 ++ .../native/vendor/ndk/src/input_queue.rs | 143 ++ clients/android/native/vendor/ndk/src/lib.rs | 33 + .../android/native/vendor/ndk/src/looper.rs | 459 +++++ .../vendor/ndk/src/media/image_reader.rs | 459 +++++ .../vendor/ndk/src/media/media_codec.rs | 634 +++++++ .../vendor/ndk/src/media/media_format.rs | 264 +++ .../native/vendor/ndk/src/media/mod.rs | 8 + .../native/vendor/ndk/src/media_error.rs | 140 ++ .../native/vendor/ndk/src/native_activity.rs | 241 +++ .../native/vendor/ndk/src/native_window.rs | 459 +++++ .../native/vendor/ndk/src/shared_memory.rs | 153 ++ .../native/vendor/ndk/src/surface_texture.rs | 159 ++ clients/android/native/vendor/ndk/src/sync.rs | 143 ++ .../android/native/vendor/ndk/src/trace.rs | 92 + .../android/native/vendor/ndk/src/utils.rs | 70 + docs-site/content/docs/stats.md | 2 +- 39 files changed, 10477 insertions(+), 70 deletions(-) create mode 100644 clients/android/native/vendor/ndk/.gitignore create mode 100644 clients/android/native/vendor/ndk/CHANGELOG.md create mode 100644 clients/android/native/vendor/ndk/Cargo.toml create mode 100644 clients/android/native/vendor/ndk/README.md create mode 100644 clients/android/native/vendor/ndk/README.punktfunk.md create mode 100644 clients/android/native/vendor/ndk/src/asset.rs create mode 100644 clients/android/native/vendor/ndk/src/audio.rs create mode 100644 clients/android/native/vendor/ndk/src/bitmap.rs create mode 100644 clients/android/native/vendor/ndk/src/configuration.rs create mode 100644 clients/android/native/vendor/ndk/src/data_space.rs create mode 100644 clients/android/native/vendor/ndk/src/event.rs create mode 100644 clients/android/native/vendor/ndk/src/font.rs create mode 100644 clients/android/native/vendor/ndk/src/hardware_buffer.rs create mode 100644 clients/android/native/vendor/ndk/src/hardware_buffer_format.rs create mode 100644 clients/android/native/vendor/ndk/src/input_queue.rs create mode 100644 clients/android/native/vendor/ndk/src/lib.rs create mode 100644 clients/android/native/vendor/ndk/src/looper.rs create mode 100644 clients/android/native/vendor/ndk/src/media/image_reader.rs create mode 100644 clients/android/native/vendor/ndk/src/media/media_codec.rs create mode 100644 clients/android/native/vendor/ndk/src/media/media_format.rs create mode 100644 clients/android/native/vendor/ndk/src/media/mod.rs create mode 100644 clients/android/native/vendor/ndk/src/media_error.rs create mode 100644 clients/android/native/vendor/ndk/src/native_activity.rs create mode 100644 clients/android/native/vendor/ndk/src/native_window.rs create mode 100644 clients/android/native/vendor/ndk/src/shared_memory.rs create mode 100644 clients/android/native/vendor/ndk/src/surface_texture.rs create mode 100644 clients/android/native/vendor/ndk/src/sync.rs create mode 100644 clients/android/native/vendor/ndk/src/trace.rs create mode 100644 clients/android/native/vendor/ndk/src/utils.rs diff --git a/Cargo.lock b/Cargo.lock index 87a8d017..020e935f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2407,8 +2407,6 @@ dependencies = [ [[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.1", @@ -3011,6 +3009,7 @@ dependencies = [ "log", "mdns-sd", "ndk", + "ndk-sys", "opus", "punktfunk-core", "tracing", diff --git a/Cargo.toml b/Cargo.toml index 74a83963..f3f8042d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,7 +19,19 @@ members = [ "tools/loss-harness", ] # Standalone PoC (built on its own; pulls usbip/tokio/libusb we don't want in the workspace). -exclude = ["packaging/linux/steam-deck-gadget/usbip-poc"] +# The vendored `ndk` is a [patch.crates-io] source, not a member: it only compiles for the +# `*-linux-android` targets, so workspace membership would break host `cargo build --workspace`. +exclude = [ + "packaging/linux/steam-deck-gadget/usbip-poc", + "clients/android/native/vendor/ndk", +] + +# ndk 0.9.0 verbatim from crates.io plus ONE visibility change: `MediaCodec::as_ptr` made public +# (upstream keeps it private and exposes no frame-rendered binding), so the Android client can +# call `AMediaCodec_setOnFrameRenderedCallback` via ndk-sys for the HUD's `display` stage +# (design/stats-unification.md). Drop the patch when upstream exposes the pointer or the callback. +[patch.crates-io] +ndk = { path = "clients/android/native/vendor/ndk" } [workspace.package] version = "0.8.4" diff --git a/clients/android/app/src/main/kotlin/io/unom/punktfunk/StatsOverlay.kt b/clients/android/app/src/main/kotlin/io/unom/punktfunk/StatsOverlay.kt index ea947b24..1c62799f 100644 --- a/clients/android/app/src/main/kotlin/io/unom/punktfunk/StatsOverlay.kt +++ b/clients/android/app/src/main/kotlin/io/unom/punktfunk/StatsOverlay.kt @@ -15,12 +15,14 @@ import io.unom.punktfunk.kit.NativeBridge import kotlin.math.roundToInt /** - * The live stats overlay — the unified HUD (`design/stats-unification.md`, Android v1: headline is - * `capture→decoded`, tiled by `host+network` + `decode`). Reads the 22-double layout from - * [NativeBridge.nativeVideoStats]: + * The live stats overlay — the unified HUD (`design/stats-unification.md`): headline is + * `capture→displayed` tiled by `host+network` + `decode` + `display` when the platform delivered + * OnFrameRendered render callbacks this window (`dispValid`), falling back to the v1 + * `capture→decoded` headline without the `display` term when it didn't. Reads the 26-double + * layout from [NativeBridge.nativeVideoStats]: * `[fps, mbps, e2eP50Ms, e2eP95Ms, latValid, skew, w, h, hz, lostTotal, bitDepth, colorPrimaries, * colorTransfer, chromaFormatIdc, hostNetP50Ms, decodeP50Ms, hostP50Ms, netP50Ms, lost, skipped, - * fec, frames]`. + * fec, frames, dispValid, displayP50Ms, e2eDispP50Ms, e2eDispP95Ms]`. * * [verbosity] selects how many lines render (each tier a superset of the last — see * [StatsVerbosity]): @@ -67,21 +69,32 @@ internal fun StatsOverlay( videoFeedLine(s)?.let { statLine(it, Color.White) } } if (latValid) { + // Display stage (s[22]–s[25], from OnFrameRendered): when a render timestamp landed + // this window the headline is the directly-measured capture→displayed pair and the + // equation gains its `display` term; otherwise (older lib / no callbacks) the endpoint + // honestly stays capture→decoded — the equation always tiles the headline interval. + val dispValid = s.size >= 26 && s[22] != 0.0 val tag = if (skew) "" else " (same-host clock)" + val (p50, p95, endpoint) = if (dispValid) { + Triple(s[24], s[25], "capture→displayed") + } else { + Triple(s[2], s[3], "capture→decoded") + } statLine( - "end-to-end ${"%.1f".format(s[2])} ms p50 · ${"%.1f".format(s[3])} p95 · capture→decoded$tag", + "end-to-end ${"%.1f".format(p50)} ms p50 · ${"%.1f".format(p95)} p95 · $endpoint$tag", Color.White, ) if (detailed && s.size >= 16) { // Phase-2 split (s[16]/s[17]): render `host + network` separately when the host // reported its share this window; otherwise the combined term (old host / no // matched 0xCF timing). - val equation = if (s.size >= 18 && s[16] > 0) { - "= host ${"%.1f".format(s[16])} + network ${"%.1f".format(s[17])} + decode ${"%.1f".format(s[15])}" + val hostTerms = if (s.size >= 18 && s[16] > 0) { + "host ${"%.1f".format(s[16])} + network ${"%.1f".format(s[17])}" } else { - "= host+network ${"%.1f".format(s[14])} + decode ${"%.1f".format(s[15])}" + "host+network ${"%.1f".format(s[14])}" } - statLine(equation, Color.White) + val displayTerm = if (dispValid) " + display ${"%.1f".format(s[23])}" else "" + statLine("= $hostTerms + decode ${"%.1f".format(s[15])}$displayTerm", Color.White) } } counterLine(s, lost)?.let { statLine(it, Color(0xFFFFB0B0)) } @@ -101,9 +114,11 @@ private fun statLine(text: String, color: Color) { * one reliability signal worth surfacing even at the tersest tier. */ private fun compactLine(s: DoubleArray, latValid: Boolean): String { + // Prefer the capture→displayed end-to-end (s[24]) when a render timestamp landed this window. + val e2eP50 = if (s.size >= 26 && s[22] != 0.0) s[24] else s[2] val parts = buildList { add("${s[0].roundToInt()} fps") - if (latValid) add("${"%.1f".format(s[2])} ms") + if (latValid) add("${"%.1f".format(e2eP50)} ms") add("${s[1].roundToInt()} Mb/s") } val lostWindow = if (s.size >= 22) s[18].toLong() else s[9].toLong() diff --git a/clients/android/app/src/test/kotlin/io/unom/punktfunk/screenshots/ShotScenes.kt b/clients/android/app/src/test/kotlin/io/unom/punktfunk/screenshots/ShotScenes.kt index f52d4878..16b463c5 100644 --- a/clients/android/app/src/test/kotlin/io/unom/punktfunk/screenshots/ShotScenes.kt +++ b/clients/android/app/src/test/kotlin/io/unom/punktfunk/screenshots/ShotScenes.kt @@ -191,19 +191,23 @@ internal fun StreamScene(verbosity: StatsVerbosity = StatsVerbosity.DETAILED) { Brush.linearGradient(listOf(Color(0xFF2A1E5C), Color(0xFF0E1B3D), Color(0xFF06122B))), ), ) { - // The full 22-double unified layout (design/stats-unification.md): [fps, mbps, e2eP50, + // The full 26-double unified layout (design/stats-unification.md): [fps, mbps, e2eP50, // e2eP95, latValid, skew, w, h, hz, lostTotal, bitDepth, colorPrimaries, colorTransfer, - // chromaFormatIdc, hostNetP50, decodeP50, hostP50, netP50, lost, skipped, fec, frames]. + // chromaFormatIdc, hostNetP50, decodeP50, hostP50, netP50, lost, skipped, fec, frames, + // dispValid, displayP50, e2eDispP50, e2eDispP95]. // 10/9/16/1 = a 10-bit BT.2020 PQ (HDR) 4:2:0 feed so the DETAILED HUD renders its - // video-feed line; the Phase-2 stage terms (host 0.6 + network 0.3 + decode 0.4) tile the - // 1.3 ms headline so it renders the full split equation, and the decoder label shows the - // ranked low-latency decoder. Light per-window loss (lost 2 · skipped 1 · FEC 5 of 238) so - // the reliability line (NORMAL/DETAILED) and the compact loss flag both render. + // video-feed line; the display stage is valid (dispValid 1) so the headline is the + // directly-measured capture→displayed pair (1.8/2.6) and the Phase-2 stage terms + // (host 0.6 + network 0.3 + decode 0.4 + display 0.5) tile it, rendering the full split + // equation; the decoder label shows the ranked low-latency decoder. Light per-window loss + // (lost 2 · skipped 1 · FEC 5 of 238) so the reliability line (NORMAL/DETAILED) and the + // compact loss flag both render. StatsOverlay( doubleArrayOf( 238.0, 921.4, 1.3, 2.1, 1.0, 1.0, 5120.0, 1440.0, 240.0, 2.0, 10.0, 9.0, 16.0, 1.0, 0.9, 0.4, 0.6, 0.3, 2.0, 1.0, 5.0, 238.0, + 1.0, 0.5, 1.8, 2.6, ), verbosity = verbosity, decoderLabel = "c2.qti.hevc.decoder · low-latency", diff --git a/clients/android/kit/src/main/kotlin/io/unom/punktfunk/kit/NativeBridge.kt b/clients/android/kit/src/main/kotlin/io/unom/punktfunk/kit/NativeBridge.kt index 67de5d76..25992812 100644 --- a/clients/android/kit/src/main/kotlin/io/unom/punktfunk/kit/NativeBridge.kt +++ b/clients/android/kit/src/main/kotlin/io/unom/punktfunk/kit/NativeBridge.kt @@ -183,16 +183,22 @@ object NativeBridge { /** * Drain ~1 s of live decode stats for the on-stream HUD, or `null` when no decode thread runs. - * Returns 18 doubles (unified stats spec, `design/stats-unification.md`): + * Returns 26 doubles (unified stats spec, `design/stats-unification.md`): * `[fps, mbps, e2eP50Ms, e2eP95Ms, latValid, skewCorrected, width, height, refreshHz, framesLost, * bitDepth, colorPrimaries, colorTransfer, chromaFormatIdc, hostNetP50Ms, decodeP50Ms, hostP50Ms, - * netP50Ms]` - * (the two flags are 1.0/0.0; indexes 2/3 are the end-to-end capture→decoded headline; 10–13 + * netP50Ms, lostWindow, skippedWindow, fecWindow, framesWindow, dispValid, displayP50Ms, + * e2eDispP50Ms, e2eDispP95Ms]` + * (the flags are 1.0/0.0; indexes 2/3 are the end-to-end capture→decoded headline; 10–13 * describe the negotiated video feed — bit depth 8/10, CICP primaries/transfer, and the HEVC * chroma_format_idc 1=4:2:0 / 3=4:4:4; 14/15 are the stage p50s tiling the headline — * `host+network` = capture→received, `decode` = received→decoded; 16/17 split the * `host+network` term via the host's per-AU 0xCF timings — `host` = the host's capture→sent, - * `network` = the remainder — both 0.0 when no timing matched this window, i.e. an old host). + * `network` = the remainder — both 0.0 when no timing matched this window, i.e. an old host; + * 18–21 are the per-window reliability counters — lost/skipped/FEC/received; 22–25 are the + * `display` stage from the OnFrameRendered render timestamps — when `dispValid` is 1.0 the + * headline becomes the directly-measured capture→displayed pair at 24/25, tiled by + * `host+network` + `decode` + `display` (23), and when 0.0 the HUD falls back to the + * capture→decoded headline at 2/3 without the `display` term). * Poll ~1 Hz; each call resets the measurement window. */ external fun nativeVideoStats(handle: Long): DoubleArray? diff --git a/clients/android/native/Cargo.toml b/clients/android/native/Cargo.toml index 0b0a4db5..e3b79b21 100644 --- a/clients/android/native/Cargo.toml +++ b/clients/android/native/Cargo.toml @@ -47,6 +47,12 @@ tracing = { version = "0.1", default-features = false, features = ["std", "log"] # (ANativeWindow_setFrameRate) is dlsym-resolved at runtime (see decode::try_set_frame_rate), not # linked, so the .so still loads on API 28/29. ndk = { version = "0.9", features = ["media", "audio", "nativewindow", "api-level-28"] } +# Raw FFI for the one AMediaCodec entry point the `ndk` wrapper lacks: +# `AMediaCodec_setOnFrameRenderedCallback` (API 26, under the minSdk-28 floor ⇒ hard-linked) — the +# per-frame render-timestamp callback behind the HUD's `display` stage (see decode::DisplayTracker). +# Reaching it needs the codec's raw pointer, which is why the workspace pins `ndk` to the vendored +# copy whose only patch makes `MediaCodec::as_ptr` public (vendor/ndk, wired in the root Cargo.toml). +ndk-sys = { version = "0.6", features = ["media"] } # setpriority/gettid to raise the decode thread toward URGENT_DISPLAY (see decode::boost_thread_priority). libc = "0.2" # Opus decode for the host→client audio plane (0xC9: 48 kHz stereo, 5 ms frames). Same crate the diff --git a/clients/android/native/src/decode.rs b/clients/android/native/src/decode.rs index b3837ffd..5827e7af 100644 --- a/clients/android/native/src/decode.rs +++ b/clients/android/native/src/decode.rs @@ -36,6 +36,12 @@ const IN_FLIGHT_CAP: usize = 64; /// this deep is a lost datagram (or an old host that never sends any) and gets evicted. const PENDING_SPLIT_CAP: usize = 256; +/// Cap on rendered frames parked in [`DisplayTracker`] awaiting their `OnFrameRendered` render +/// timestamp: the callback trails its release by at most a vsync or two, so anything this deep +/// means the platform stopped delivering render callbacks (allowed under load, per the docs) and +/// gets evicted. +const RENDERED_CAP: usize = 64; + /// Whether low-latency mode uses the event-driven async decode loop (default) or the synchronous /// poll loop. Flip to `false` to A/B the two on the HUD (`design/…`); the async loop presents a /// decoded frame the instant it's ready instead of waiting out a poll interval. Only consulted when @@ -208,6 +214,12 @@ fn run_sync( // host-minus-client clock offset (0 if the host didn't answer the skew handshake — then the // HUD flags it "(same-host clock)"). let clock_offset = client.clock_offset_ns; + // Display stage (spec `display` + the capture→displayed headline): frames released with + // render = true are parked in the tracker; the OnFrameRendered callback pairs them with + // SurfaceFlinger's render timestamp. `render_cb` is the callback's leaked Arc refcount, + // reclaimed after the codec is dropped below. + let tracker = DisplayTracker::new(stats.clone(), clock_offset); + let render_cb = install_render_callback(&codec, &tracker); // HUD stage split: receipt timestamps keyed by the pts we queue into the codec, so the decoded // point (output-buffer dequeue — MediaCodec round-trips presentationTimeUs) can be paired back // to its receipt for the `decode` stage. Only fed while the HUD is visible. @@ -309,6 +321,7 @@ fn run_sync( &stats, &mut in_flight, clock_offset, + &tracker, ); rendered += r; discarded += d; @@ -367,6 +380,11 @@ fn run_sync( } let _ = codec.stop(); + drop(codec); // AMediaCodec_delete — after this no render callback can fire + if let Some(ud) = render_cb { + // SAFETY: the codec was dropped above; this registration's single reclaim. + unsafe { release_render_callback(ud) }; + } log::info!("decode: stopped (fed={fed} rendered={rendered} discarded={discarded})"); } @@ -380,6 +398,145 @@ fn now_realtime_ns() -> i128 { .unwrap_or(0) } +/// `CLOCK_MONOTONIC` now in nanoseconds — the base of the `systemNano` render timestamp the +/// `OnFrameRendered` callback reports (Android's `System.nanoTime`), read only to re-base that +/// stamp onto `CLOCK_REALTIME` (see [`on_frame_rendered`]). +fn now_monotonic_ns() -> i128 { + let mut ts = libc::timespec { + tv_sec: 0, + tv_nsec: 0, + }; + // SAFETY: `clock_gettime` with a valid out-pointer is an always-safe syscall. + unsafe { libc::clock_gettime(libc::CLOCK_MONOTONIC, &mut ts) }; + ts.tv_sec as i128 * 1_000_000_000 + ts.tv_nsec as i128 +} + +/// State shared between the decode loop and the `AMediaCodec` `OnFrameRendered` callback (which +/// fires on a codec-internal thread): rendered frames awaiting their render timestamp, so the HUD +/// gets the spec's `display` stage (decoded→displayed) and the `capture→displayed` end-to-end +/// headline (`design/stats-unification.md` — this replaces Android's v1 `capture→decoded` +/// endpoint whenever the platform delivers render callbacks). +struct DisplayTracker { + stats: Arc, + /// Host-minus-client clock offset (ns) for the skew-corrected end-to-end sample. + clock_offset: i64, + /// `(pts_us, decoded_real_ns)` of frames released with `render = true`, in release order, + /// awaiting their callback. Pushes are HUD-gated by the caller, so this stays empty (and the + /// callback early-outs) while the overlay is hidden. + rendered: Mutex>, +} + +impl DisplayTracker { + fn new(stats: Arc, clock_offset: i64) -> Arc { + Arc::new(DisplayTracker { + stats, + clock_offset, + rendered: Mutex::new(VecDeque::new()), + }) + } + + /// Park one just-rendered frame's `(pts, decoded stamp)` for the render callback to pair. + /// Caller gates on the HUD being visible. + fn note_rendered(&self, pts_us: u64, decoded_ns: i128) { + let mut g = self + .rendered + .lock() + .unwrap_or_else(std::sync::PoisonError::into_inner); + g.push_back((pts_us, decoded_ns)); + if g.len() > RENDERED_CAP { + g.pop_front(); // render callbacks stopped coming (allowed under load) — evict + } + } +} + +/// Register [`on_frame_rendered`] on the codec (`AMediaCodec_setOnFrameRenderedCallback`, API 26 — +/// under the minSdk-28 floor, so hard-linked via `ndk-sys`; the `ndk` wrapper has no binding, which +/// is what the vendored crate's public `as_ptr` patch is for). Returns the userdata pointer holding +/// a leaked `Arc` refcount; the caller MUST reclaim it with +/// [`release_render_callback`] AFTER dropping the codec (`AMediaCodec_delete` is what guarantees no +/// further callback can fire). `None` (nothing to reclaim) if the platform refused — the HUD then +/// simply has no `display` stage, exactly the pre-callback behaviour. +fn install_render_callback( + codec: &MediaCodec, + tracker: &Arc, +) -> Option<*const DisplayTracker> { + let ud = Arc::into_raw(tracker.clone()); + // SAFETY: `codec.as_ptr()` is the live codec this thread owns; `ud` outlives the registration + // (reclaimed only after the codec is deleted, per this function's contract). + let status = unsafe { + ndk_sys::AMediaCodec_setOnFrameRenderedCallback( + codec.as_ptr(), + Some(on_frame_rendered), + ud as *mut c_void, + ) + }; + if status == ndk_sys::media_status_t::AMEDIA_OK { + Some(ud) + } else { + log::warn!("decode: setOnFrameRenderedCallback failed ({status:?}) — no display stage"); + // SAFETY: registration failed, so the codec never took the reference — reclaim it now. + unsafe { drop(Arc::from_raw(ud)) }; + None + } +} + +/// Reclaim [`install_render_callback`]'s leaked `Arc` refcount. +/// +/// # Safety +/// Call exactly once, and only after the codec the callback was registered on has been dropped — +/// deleting the codec stops its internal threads, so no callback can still be running (or run +/// later) against this pointer. +unsafe fn release_render_callback(ud: *const DisplayTracker) { + drop(Arc::from_raw(ud)); +} + +/// The `AMediaCodecOnFrameRendered` trampoline: fires (possibly batched) on a codec-internal +/// thread once per output frame actually placed on the output surface, with SurfaceFlinger's +/// render timestamp. That timestamp (`system_nano`) is on `CLOCK_MONOTONIC`, so it is re-based +/// onto `CLOCK_REALTIME` here — against monotonic-now at callback time, which also cancels any lag +/// between the frame rendering and the (batchable) callback delivery — to subtract against the +/// receipt/decode stamps and the host capture pts. Records the HUD's `displayed` point: +/// `end-to-end` = capture→displayed (skew-corrected) and `display` = decoded→displayed +/// (single-clock local). Panic-free by construction (poison-proof lock, saturating math) — an +/// unwind out of an `extern "C"` fn would abort the process. +unsafe extern "C" fn on_frame_rendered( + _codec: *mut ndk_sys::AMediaCodec, + userdata: *mut c_void, + media_time_us: i64, + system_nano: i64, +) { + let t = &*(userdata as *const DisplayTracker); + if !t.stats.enabled() { + return; // HUD hidden — the ring is empty too (pushes are caller-gated) + } + let displayed_ns = now_realtime_ns() - (now_monotonic_ns() - system_nano as i128); + let pts_us = media_time_us.max(0) as u64; + // Pair the frame back to its release record, evicting older entries (their callbacks were + // dropped by the platform, or the entry predates a HUD toggle) — same monotonic-eviction + // discipline as `note_decoded_pts`. + let mut decoded_ns = None; + { + let mut g = t + .rendered + .lock() + .unwrap_or_else(std::sync::PoisonError::into_inner); + while let Some(&(p, d)) = g.front() { + if p > pts_us { + break; // future frame — leave it for its own callback + } + g.pop_front(); + if p == pts_us { + decoded_ns = Some(d); + break; + } + } + } + let e2e_ns = displayed_ns + t.clock_offset as i128 - pts_us as i128 * 1000; + let e2e_us = (e2e_ns > 0 && e2e_ns < 10_000_000_000).then_some((e2e_ns / 1000) as u64); + let display_us = decoded_ns.map(|d| ((displayed_ns - d).max(0) / 1000) as u64); + t.stats.note_displayed(e2e_us, display_us); +} + /// The MediaCodec MIME for the codec the host resolved (`Welcome.codec`). Shared by the decode /// thread and `nativeVideoMime` (which tells Kotlin what to rank decoders for). AV1 uses the /// AOSP `video/av01` type; anything not H.264/AV1 is treated as HEVC (every pre-negotiation host @@ -649,6 +806,12 @@ fn run_async( // HUD is visible. let clock_offset = client.clock_offset_ns; let in_flight = Arc::new(Mutex::new(VecDeque::<(u64, i128)>::new())); + // Display stage (spec `display` + the capture→displayed headline): the rendered frame is + // parked in the tracker at release; the OnFrameRendered callback pairs it with + // SurfaceFlinger's render timestamp. `render_cb` is the callback's leaked Arc refcount, + // reclaimed after the codec is dropped below. + let tracker = DisplayTracker::new(stats.clone(), clock_offset); + let render_cb = install_render_callback(&codec, &tracker); // Feeder thread: block on the network so this loop doesn't (an AU's arrival becomes an event that // wakes us immediately, with no input-side poll latency). It also records the `received` HUD stat. @@ -744,6 +907,7 @@ fn run_async( &stats, &in_flight, clock_offset, + &tracker, &mut rendered, &mut discarded, ); @@ -796,6 +960,11 @@ fn run_async( if let Some(j) = feeder { let _ = j.join(); } + drop(codec); // AMediaCodec_delete — after this no render callback can fire + if let Some(ud) = render_cb { + // SAFETY: the codec was dropped above; this registration's single reclaim. + unsafe { release_render_callback(ud) }; + } log::info!("decode: stopped (async, fed={fed} rendered={rendered} discarded={discarded})"); } @@ -938,13 +1107,17 @@ fn feed_ready( /// burst of stale frames on glass is worse than skipping to the freshest (the sync loop's newest-ready /// policy, callback-driven). Every dequeued buffer, rendered or not, is the HUD's `decoded` /// measurement point (it finished decoding either way); samples are recorded in pts order so the -/// receipt-map eviction stays monotonic. `ready` is drained. +/// receipt-map eviction stays monotonic. The presented frame's `(pts, decoded stamp)` is parked in +/// `tracker` for the OnFrameRendered callback — the `display` stage's other endpoint. `ready` is +/// drained. +#[allow(clippy::too_many_arguments)] // one call site; mirrors the sync loop's drain fn present_ready( codec: &MediaCodec, ready: &mut Vec, stats: &crate::stats::VideoStats, in_flight: &Mutex>, clock_offset: i64, + tracker: &DisplayTracker, rendered: &mut u64, discarded: &mut u64, ) { @@ -964,7 +1137,12 @@ fn present_ready( for (i, o) in ready.drain(..).enumerate() { let render = i == last; match codec.release_output_buffer_by_index(o.index, render) { - Ok(()) if render => *rendered += 1, + Ok(()) if render => { + *rendered += 1; + if stats.enabled() { + tracker.note_rendered(o.pts_us, o.decoded_ns); + } + } Ok(()) => { *discarded += 1; skipped += 1; @@ -1143,7 +1321,10 @@ fn feed(codec: &MediaCodec, au: &[u8], pts_us: u64) -> bool { /// Each dequeued buffer is also the HUD's `decoded` measurement point (rendered or not — the frame /// finished decoding either way): end-to-end = decoded + clock_offset − capture pts, and the /// `decode` stage pairs the buffer's echoed presentationTimeUs back to the receipt stamp in -/// `in_flight` (single-clock local difference, no skew involved). +/// `in_flight` (single-clock local difference, no skew involved). The presented frame's +/// `(pts, decoded stamp)` is additionally parked in `tracker` for the OnFrameRendered callback — +/// the `display` stage's other endpoint. +#[allow(clippy::too_many_arguments)] // one call site; mirrors the async loop's present_ready fn drain( codec: &MediaCodec, window: &NativeWindow, @@ -1152,18 +1333,27 @@ fn drain( stats: &crate::stats::VideoStats, in_flight: &mut VecDeque<(u64, i128)>, clock_offset: i64, + tracker: &DisplayTracker, ) -> (u64, u64) { - let mut held = None; // newest ready buffer so far, presented after the loop + // Newest ready buffer so far (presented after the loop) with its HUD metadata — + // `Some((pts_us, decoded_ns))` only while the HUD is visible (the stamp read is gated). + let mut held: Option<(OutputBuffer<'_>, Option<(u64, i128)>)> = None; let mut discarded: u64 = 0; let mut wait = first_wait; loop { match codec.dequeue_output_buffer(wait) { Ok(DequeuedOutputBufferInfoResult::Buffer(buf)) => { wait = Duration::ZERO; // only the first dequeue may block - if stats.enabled() { - note_decoded(stats, in_flight, clock_offset, &buf); - } - if let Some(stale) = held.replace(buf) { + let meta = if stats.enabled() { + // The dequeue IS the sync loop's decoded-availability instant. + let pts_us = buf.info().presentation_time_us().max(0) as u64; + let decoded_ns = now_realtime_ns(); + note_decoded_pts(stats, in_flight, clock_offset, pts_us, decoded_ns); + Some((pts_us, decoded_ns)) + } else { + None + }; + if let Some((stale, _)) = held.replace((buf, meta)) { // A newer frame is ready — drop the held one without rendering. if let Err(e) = codec.release_output_buffer(stale, false) { log::warn!("decode: release_output_buffer(discard): {e}"); @@ -1202,41 +1392,30 @@ fn drain( } } } - // Present the newest ready frame, if any. + // Present the newest ready frame, if any, and park its metadata for the render callback. let mut rendered = 0; - if let Some(buf) = held { + if let Some((buf, meta)) = held { match codec.release_output_buffer(buf, true) { - Ok(()) => rendered = 1, + Ok(()) => { + rendered = 1; + if let Some((pts_us, decoded_ns)) = meta { + tracker.note_rendered(pts_us, decoded_ns); + } + } Err(e) => log::warn!("decode: release_output_buffer: {e}"), } } (rendered, discarded) } -/// HUD `decoded` point for one dequeued output buffer: build the end-to-end (capture→decoded, -/// skew-corrected, clamped to (0, 10 s)) and `decode` (received→decoded, single-clock local, ≥ 0) -/// samples and hand them to [`crate::stats::VideoStats::note_decoded`]. The codec echoes the input -/// `presentationTimeUs` on the output buffer, which keys the receipt stamp in `in_flight`; entries -/// older than the echoed pts are evicted (decode order == input order here — low-latency, no +/// HUD `decoded` point for one dequeued output frame, keyed by the echoed `presentationTimeUs`: +/// build the end-to-end (capture→decoded, skew-corrected, clamped to (0, 10 s)) and `decode` +/// (received→decoded, single-clock local, ≥ 0) samples and hand them to +/// [`crate::stats::VideoStats::note_decoded`]. The pts keys the receipt stamp in `in_flight`; +/// entries older than it are evicted (decode order == input order here — low-latency, no /// B-frames — so anything before it was dropped inside the codec or stamped before a flush). -fn note_decoded( - stats: &crate::stats::VideoStats, - in_flight: &mut VecDeque<(u64, i128)>, - clock_offset: i64, - buf: &OutputBuffer<'_>, -) { - note_decoded_pts( - stats, - in_flight, - clock_offset, - buf.info().presentation_time_us().max(0) as u64, - now_realtime_ns(), // sync loop: the dequeue IS the availability instant - ); -} - -/// The [`note_decoded`] body keyed by the echoed `presentationTimeUs` directly — the async loop has -/// the pts (from the output callback's `BufferInfo`) but no borrowed `OutputBuffer`, so it calls -/// this with the `decoded` stamp taken in the output callback itself (the availability instant). +/// `decoded_ns` is the availability instant: the dequeue (sync loop) or the output callback's +/// stamp (async loop). fn note_decoded_pts( stats: &crate::stats::VideoStats, in_flight: &mut VecDeque<(u64, i128)>, diff --git a/clients/android/native/src/session/planes.rs b/clients/android/native/src/session/planes.rs index 1305c8d5..275a8787 100644 --- a/clients/android/native/src/session/planes.rs +++ b/clients/android/native/src/session/planes.rs @@ -144,11 +144,12 @@ pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeStopVideo( } /// `NativeBridge.nativeVideoStats(handle): DoubleArray?` — drain ~1 s of decode stats for the HUD -/// (unified stats spec, `design/stats-unification.md`). Returns 22 doubles +/// (unified stats spec, `design/stats-unification.md`). Returns 26 doubles /// `[fps, mbps, e2eP50Ms, e2eP95Ms, latValid, skewCorrected, width, height, refreshHz, framesLost, /// bitDepth, colorPrimaries, colorTransfer, chromaFormatIdc, hostNetP50Ms, decodeP50Ms, hostP50Ms, -/// netP50Ms, lostWindow, skippedWindow, fecWindow, framesWindow]` -/// (the two flags are 1.0/0.0; indexes 0–17 match the previous 18-double layout — 0–13 the original +/// netP50Ms, lostWindow, skippedWindow, fecWindow, framesWindow, dispValid, displayP50Ms, +/// e2eDispP50Ms, e2eDispP95Ms]` +/// (the flags are 1.0/0.0; indexes 0–21 match the previous 22-double layout — 0–13 the original /// 14-double one with the latency pair re-based to the end-to-end capture→decoded headline, 14/15 /// the stage p50s tiling it: `host+network` = capture→received, `decode` = received→decoded; 16/17 /// are the Phase-2 split of the `host+network` term from the per-AU 0xCF host timings — `host` = @@ -156,7 +157,11 @@ pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeStopVideo( /// window, i.e. an old host; 18–21 are the spec's per-window line-4 counters — `lost` = /// unrecoverable drops this window, `skipped` = client newest-wins/pacing drops, `fec` = shards /// recovered, `frames` = AUs received, so the HUD can compute `lost/(frames+lost)` — index 9 stays -/// the cumulative session total for older readers), or `null` when no decode thread is running. +/// the cumulative session total for older readers; 22–25 are the `display` stage from the +/// OnFrameRendered render timestamps — when `dispValid` is 1.0 the HUD headline becomes the +/// directly-measured capture→displayed pair at 24/25 with `display` = decoded→displayed p50 at 23 +/// closing the equation, and when 0.0 — no render callback landed this window — it falls back to +/// the capture→decoded headline at 2/3), or `null` when no decode thread is running. /// Poll ~1 Hz from the UI; each call /// resets the measurement window. Not android-gated — pure `jni` + connector reads, so it links on /// the host build too (Kotlin only ever calls it on device). @@ -180,7 +185,7 @@ pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeVideoStats( .drain(h.client.frames_dropped(), h.client.fec_recovered_shards()); let mode = h.client.mode(); let color = h.client.color; - let buf: [f64; 22] = [ + let buf: [f64; 26] = [ snap.fps, snap.mbps, snap.e2e_p50_ms, @@ -213,6 +218,14 @@ pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeVideoStats( snap.skipped as f64, snap.fec as f64, snap.frames as f64, + // `display` stage (OnFrameRendered render timestamps): validity flag, the + // decoded→displayed stage p50, and the directly-measured capture→displayed headline + // pair that supersedes 2/3 whenever the flag is set (spec: the equation always tiles + // the headline interval, so endpoint and terms move together). + if snap.disp_valid { 1.0 } else { 0.0 }, + snap.display_p50_ms, + snap.e2e_disp_p50_ms, + snap.e2e_disp_p95_ms, ]; let arr = match env.new_double_array(buf.len() as jsize) { Ok(a) => a, diff --git a/clients/android/native/src/stats.rs b/clients/android/native/src/stats.rs index f4fe417e..7489a8dd 100644 --- a/clients/android/native/src/stats.rs +++ b/clients/android/native/src/stats.rs @@ -1,9 +1,12 @@ //! Live decode stats for the on-stream HUD, following the unified stats spec -//! (`design/stats-unification.md`): FPS, receive throughput, and the Android v1 stage split — -//! headline `end-to-end` = capture→decoded (p50/p95) tiled by `host+network` = capture→received -//! and `decode` = received→decoded (stage p50s). When the host emits per-AU 0xCF host timings, the -//! `host+network` term further splits into `host` + `network` (Phase 2, `note_host_split`); an old -//! host emits none and the combined term stands. The spec's line-4 counters are per-window too: +//! (`design/stats-unification.md`): FPS, receive throughput, and the full stage split — headline +//! `end-to-end` = capture→displayed (p50/p95, measured directly from the `OnFrameRendered` render +//! timestamps — `note_displayed`) tiled by `host+network` = capture→received, `decode` = +//! received→decoded, and `display` = decoded→displayed (stage p50s). When the platform delivers no +//! render callbacks (allowed under load; `disp_valid` false), the HUD falls back to the v1 +//! capture→decoded headline without the `display` term. When the host emits per-AU 0xCF host +//! timings, the `host+network` term further splits into `host` + `network` (Phase 2, +//! `note_host_split`); an old host emits none and the combined term stands. The spec's line-4 counters are per-window too: //! `lost` / `FEC` are windowed here from the connector's cumulative counters (the caller passes the //! current totals into `set_enabled`/`drain`), `skipped` counts the client's own newest-wins drops //! (`note_skipped`). The decode thread is the sole writer @@ -57,6 +60,13 @@ struct Inner { net_us: Vec, /// `decode` stage = received→decoded samples, in microseconds (client-local, single clock). decode_us: Vec, + /// `display` stage = decoded→displayed samples, in microseconds (client-local, single clock), + /// from the `OnFrameRendered` render timestamps. Empty when the platform delivers no render + /// callbacks — the HUD then drops the term and the headline endpoint moves back to `decoded`. + display_us: Vec, + /// `end-to-end` = capture→displayed samples, µs (skew-corrected) — the spec's headline, + /// measured directly (not summed from stages). Empty under the same fallback as `display_us`. + e2e_disp_us: Vec, /// Client-side newest-wins/pacing drops this window (decoded frames released without /// rendering, or parked AUs dropped on overflow) — the spec's `skipped` counter. skipped: u64, @@ -75,12 +85,22 @@ struct Inner { pub struct Snapshot { pub fps: f64, pub mbps: f64, - /// Headline `end-to-end` (capture→decoded) percentiles, ms. + /// Headline `end-to-end` (capture→decoded) percentiles, ms — the fallback headline when no + /// render callback landed this window (`disp_valid` false). pub e2e_p50_ms: f64, pub e2e_p95_ms: f64, - /// Stage p50s (ms): `host+network` (capture→received) and `decode` (received→decoded). + /// The full headline: `end-to-end` = capture→displayed percentiles, ms, measured directly from + /// the render timestamps. Meaningful only when `disp_valid`. + pub e2e_disp_p50_ms: f64, + pub e2e_disp_p95_ms: f64, + /// Stage p50s (ms): `host+network` (capture→received), `decode` (received→decoded), and + /// `display` (decoded→displayed; 0.0 when `disp_valid` is false — the HUD drops the term). pub hostnet_p50_ms: f64, pub decode_p50_ms: f64, + pub display_p50_ms: f64, + /// Whether any capture→displayed sample landed this window — gates the HUD's headline endpoint + /// (`capture→displayed` vs the capture→decoded fallback) and the equation's `display` term. + pub disp_valid: bool, /// Phase-2 `host` / `network` split p50s (ms) — 0.0 when no 0xCF timing matched this window /// (old host / no samples yet), in which case the HUD keeps the combined `host+network` term. pub host_p50_ms: f64, @@ -122,6 +142,8 @@ impl VideoStats { host_us: Vec::with_capacity(256), net_us: Vec::with_capacity(256), decode_us: Vec::with_capacity(256), + display_us: Vec::with_capacity(256), + e2e_disp_us: Vec::with_capacity(256), skipped: 0, last_dropped_total: 0, last_fec_total: 0, @@ -157,6 +179,8 @@ impl VideoStats { g.host_us.clear(); g.net_us.clear(); g.decode_us.clear(); + g.display_us.clear(); + g.e2e_disp_us.clear(); g.skipped = 0; g.last_dropped_total = dropped_total; g.last_fec_total = fec_total; @@ -273,6 +297,30 @@ impl VideoStats { } } + /// Record one displayed frame (the `OnFrameRendered` render timestamp, re-based to the + /// realtime clock): its capture→displayed `end-to-end` sample and its decoded→displayed + /// `display` stage sample (either may be absent — the e2e clamp rejected an out-of-range + /// value, or the decoded stamp for this pts was already evicted/pre-HUD). Fired from the + /// codec's render-callback thread, not the decode thread — the lock makes that safe. + // Driven only by the android-only decode path; unreferenced on the host build — expected. + #[cfg_attr(not(target_os = "android"), allow(dead_code))] + pub fn note_displayed(&self, e2e_us: Option, display_us: Option) { + if !self.enabled.load(Ordering::Relaxed) { + return; // HUD hidden — skip the lock (the callback already skipped the clock reads) + } + // Poison-proof for the same reason as `note_received`. + let mut g = self + .inner + .lock() + .unwrap_or_else(std::sync::PoisonError::into_inner); + if let Some(l) = e2e_us { + g.e2e_disp_us.push(l); + } + if let Some(l) = display_us { + g.display_us.push(l); + } + } + /// Compute the window's rates + latency percentiles, then reset for the next window. /// `dropped_total` / `fec_total` are the connector's session-cumulative counters now; the /// snapshot's `lost` / `fec` are their deltas since the last drain (or the enabling show). @@ -291,13 +339,19 @@ impl VideoStats { g.host_us.sort_unstable(); g.net_us.sort_unstable(); g.decode_us.sort_unstable(); + g.display_us.sort_unstable(); + g.e2e_disp_us.sort_unstable(); let snap = Snapshot { fps, mbps, e2e_p50_ms: pctl_ms(&g.e2e_us, 0.50), e2e_p95_ms: pctl_ms(&g.e2e_us, 0.95), + e2e_disp_p50_ms: pctl_ms(&g.e2e_disp_us, 0.50), + e2e_disp_p95_ms: pctl_ms(&g.e2e_disp_us, 0.95), hostnet_p50_ms: pctl_ms(&g.hostnet_us, 0.50), decode_p50_ms: pctl_ms(&g.decode_us, 0.50), + display_p50_ms: pctl_ms(&g.display_us, 0.50), + disp_valid: !g.e2e_disp_us.is_empty(), host_p50_ms: pctl_ms(&g.host_us, 0.50), net_p50_ms: pctl_ms(&g.net_us, 0.50), lat_valid: !g.e2e_us.is_empty(), @@ -315,6 +369,8 @@ impl VideoStats { g.host_us.clear(); g.net_us.clear(); g.decode_us.clear(); + g.display_us.clear(); + g.e2e_disp_us.clear(); g.skipped = 0; g.last_dropped_total = dropped_total; g.last_fec_total = fec_total; diff --git a/clients/android/native/vendor/ndk/.gitignore b/clients/android/native/vendor/ndk/.gitignore new file mode 100644 index 00000000..69369904 --- /dev/null +++ b/clients/android/native/vendor/ndk/.gitignore @@ -0,0 +1,3 @@ +/target +**/*.rs.bk +Cargo.lock diff --git a/clients/android/native/vendor/ndk/CHANGELOG.md b/clients/android/native/vendor/ndk/CHANGELOG.md new file mode 100644 index 00000000..e4dc75d9 --- /dev/null +++ b/clients/android/native/vendor/ndk/CHANGELOG.md @@ -0,0 +1,137 @@ +# Unreleased + +# 0.9.0 (2024-04-26) + +- Move `MediaFormat` from `media::media_codec` to its own `media::media_format` module. (#442) +- media_format: Expose `MediaFormat::copy()` and `MediaFormat::clear()` from API level 29. (#449) +- **Breaking:** media_format: Mark all `fn set_*()` and `fn str()` as taking `self` by `&mut`. (#452) +- **Breaking:** Require all `dyn Fn*` types to implement `Send` when the FFI implementation invokes them on a separate thread: (#455) + - `audio::AudioStreamDataCallback`; + - `audio::AudioStreamErrorCallback`; + - `media::image_reader::BufferRemovedListener`; + - `media::image_reader::ImageListener`; + - `media::media_codec::ErrorCallback`; + - `media::media_codec::FormatChangedCallback`; + - `media::media_codec::InputAvailableCallback`; + - `media::media_codec::OutputAvailableCallback`. +- Drop previous `Box`ed callbacks _after_ registering new ones, instead of before. (#455) +- input_queue: Add `from_java()` constructor, available since API level 33. (#456) +- event: Add `from_java()` constructors to `KeyEvent` and `MotionEvent`, available since API level 31. (#456) +- **Breaking:** image_reader: Special-case return statuses in `Image`-acquire functions. (#457) +- **Breaking:** image_reader: Mark `ImageReader::acquire_latest_image_async()` `unsafe` to match the safety requirements on `ImageReader::acquire_next_image_async()`. (#457) +- event: Implement `SourceClass` `bitflag` and provide `Source::class()` getter. (#458) +- Ensure all `bitflags` implementations consider all (including unknown) bits in negation and `all()`. (#458) +- **Breaking:** Mark all enums as `non_exhaustive` and fix `repr` types. (#459) +- **Breaking:** native_window: Remove redundant `TRANSFORM_` prefix from `NativeWindowTransform` variants. (#460) +- **Breaking:** hardware_buffer: Convert `HardwareBufferUsage` to `bitflags`. (#461) +- bitmap: Guard `BitmapCompressError` behind missing `api-level-30` feature. (#462) +- native_window: Require linking against `libnativewindow` for most API >= 26 functions. (#465) +- **Breaking:** audio: Merge `AudioResult` variant enum into `AudioError`. (#467) +- data_space: Add missing `DataSpaceRange::Unspecified` variant. (#468) +- **Breaking:** looper: Require `Send` marker when adding fd event callbacks on `ForeignLooper`. (#469) +- **Breaking:** Upgrade to [`ndk-sys 0.6.0`](../ndk-sys/CHANGELOG.md#060-2024-04-26). (#472) + +# 0.8.0 (2023-10-15) + +- event: Add `tool_type` getter for `Pointer`. (#323) +- input_queue: Allow any non-zero return code from `pre_dispatch()` again, as per documentation. (#325) +- asset: Use entire asset length when mapping buffer. (#387) +- Bump MSRV to 1.66 for `raw-window-handle 0.5.1`, `num_enum`'s `catch_all` with arbitrary enum discriminants. (#388, #431) +- Bump optional `jni` dependency for doctest example from `0.19` to `0.21`. (#390) +- **Breaking:** Upgrade to [`ndk-sys 0.5.0`](../ndk-sys/CHANGELOG.md#050-2023-10-15). (#370) +- **Breaking:** Upgrade `bitflags` crate from `1` to `2`. (#394) +- bitmap: Add `try_format()` to `AndroidBitmapInfo` to handle unexpected formats without panicking. (#395) +- Add `Font` bindings. (#397) +- **Breaking:** Upgrade `num_enum` crate from `0.5.1` to `0.7`. (#398, #419) +- **Breaking:** Renamed, moved and flattened "`media`" error types and helpers to a new `media_error` module. (#399, #432) +- **Breaking:** media_codec: Wrap common dequeued-buffer status codes in enum. (#401) +- **Breaking:** media_codec: Return `MaybeUninit` bytes in `buffer_mut()`. (#403) +- native_window: Add `lock()` to blit raw pixel data. (#404) +- hardware_buffer_format: Add `YCbCr_P010` and `R8_UNORM` variants. (#405) +- **Breaking:** hardware_buffer_format: Add catch-all variant. (#407) +- asset: Add missing `is_allocated()` and `open_file_descriptor()` methods. (#409) +- **Breaking:** media_codec: Add support for asynchronous notification callbacks. (#410) +- Add panic guards to callbacks. (#412) +- looper: Add `remove_fd()` to unregister events/callbacks for a file descriptor. (#416) +- **Breaking:** Use `BorrowedFd` and `OwnedFd` to clarify possible ownership transitions. (#417) +- **Breaking:** Upgrade to [`ndk-sys 0.5.0`](../ndk-sys/CHANGELOG.md#050-2023-10-15). (#420) +- Add bindings for `sync.h`. (#423) +- **Breaking:** bitmap: Provide detailed implementation for `AndroidBitmapInfoFlags`. (#424) +- native_window: Add `set_buffers_transform()`, `try_allocate_buffers()` and `set_frame_rate*()`. (#425) +- Add bindings for `ASharedMemory`. (#427) +- hardware_buffer: Add `id()` to retrieve a system-wide unique identifier for a `HardwareBuffer`. (#428) +- **Breaking:** bitmap: Strip `Android` prefix from structs and enums, and `Bitmap` from `Result`. (#430) +- **Breaking:** `raw-window-handle 0.5` support is now behind an _optional_ `rwh_05` crate feature and `raw-window-handle` `0.4` and `0.6` support is provided via the new `rwh_04` and (default-enabled) `rwh_06` crate features. (#434) +- **Breaking:** looper: Provide `event` value to file descriptor poll callback. (#435) +- **Breaking:** `HardwareBufferFormat` is no longer exported from `hardware_buffer` and `native_window`, and can only be reached through the `hardware_buffer_format` module. (#436) +- **Breaking:** `get_` prefixes have been removed from all public functions in light of the [C-GETTER](https://rust-lang.github.io/api-guidelines/naming.html#getter-names-follow-rust-convention-c-getter) convention. (#437) +- Add `DataSpace` type and relevant functions on `Bitmap` and `NativeWindow`. (#438) +- bitmap: Add `Bitmap::compress()` and `Bitmap::compress_raw()` functions. (#440) +- **Breaking:** Turn `BitmapError` into a `non_exhaustive` `enum`. (#440) +- **Breaking:** audio: Rename `AudioErrorResult` to `AudioResult` and turn into a `non_exhaustive` `enum`. (#441) + +# 0.7.0 (2022-07-24) + +- hardware_buffer: Make `HardwareBuffer::as_ptr()` public for interop with Vulkan. (#213) +- **Breaking:** `Configuration::country()` now returns `None` when the country is unset (akin to `Configuration::language()`). (#220) +- Add `MediaCodec` and `MediaFormat` bindings. (#216) +- **Breaking:** Upgrade to [`ndk-sys 0.4.0`](../ndk-sys/CHANGELOG.md#040-2022-07-24) and use new `enum` newtype wrappers. (#245) +- native_window: Use `release`/`acquire` for `Drop` and `Clone` respectively. (#207) +- **Breaking:** audio: Rename from `aaudio` to `audio` and drop `A` prefix. (#273) +- Implement `HasRawWindowHandle` directly on `NativeWindow`. (#274, #319) +- **Breaking:** native_activity: Replace `CStr` return types with `Path`. (#279) +- native_window: Add `format()` getter and `set_buffers_geometry()` setter. (#276) +- native_activity: Add `set_window_format()` setter. (#277) +- native_activity: Add `set_window_flags()` to change window behavior. (#278) +- Add `SurfaceTexture` bindings. (#267) +- Improve library and structure documentation, linking back to the NDK docs more rigorously. (#290) +- **Breaking:** input_queue: `get_event()` now returns a `Result` with `std::io::Error`; `InputQueueError` has been removed. (#292) +- **Breaking:** input_queue: `has_events()` now returns a `bool` directly without being wrapped in `Result`. (#294) +- **Breaking:** hardware_buffer: `HardwareBufferError` has been removed and replaced with `std::io::Error` in return types. (#295) +- Fixed `HardwareBuffer` leak on buffers returned from `AndroidBitmap::get_hardware_buffer()`. (#296) +- Bump optional `jni` dependency for doctest example from `0.18` to `0.19`. (#300) +- hardware_buffer: Made `HardwareBufferDesc` fields `pub`. (#313) +- **Breaking:** Remove `hardware_buffer` and `trace` features in favour of using `api-level-26` or `api-level-23` directly. (#320) + +# 0.6.0 (2022-01-05) + +- **Breaking:** Upgrade to [`ndk-sys 0.3.0`](../ndk-sys/CHANGELOG.md#030-2022-01-05) and migrate to `jni-sys` types that it now directly uses in its bindings. (#209 / #214) + +# 0.5.0 (2021-11-22) + +- **Breaking:** Replace `add_fd_with_callback` `ident` with constant value `ALOOPER_POLL_CALLBACK`, + as per . +- **Breaking:** Accept unboxed closure in `add_fd_with_callback`. +- aaudio: Replace "Added in" comments with missing `#[cfg(feature)]`. +- aaudio: Add missing `fn get_allowed_capture_policy()`. +- configuration: Add missing `api-level-30` feature to `fn screen_round()`. + +# 0.4.0 (2021-08-02) + +- **Breaking:** Model looper file descriptor events integer as `bitflags`. + +# 0.3.0 (2021-01-30) + +- **Breaking:** Looper `ident` not passed in `data` pointer anymore. + `attach_looper` now only sets the `ident` field when attaching an + `InputQueue` to a `ForeignLooper`. + If you are relying on `Poll::Event::data` to tell event fd and + input queue apart, please use `Poll::Event::ident` and the new + constants introduced in `ndk-glue`! + +# 0.2.1 (2020-10-15) + +- Fix documentation build on docs.rs + +# 0.2.0 (2020-09-15) + +- **Breaking:** Updated to use [ndk-sys 0.2.0](../ndk-sys/CHANGELOG.md#020-2020-09-15) +- Added `media` bindings +- Added `bitmap` and `hardware_buffer` bindings +- Added `aaudio` bindings +- Fixed assets directory path to be relative to the manifest +- Added `trace` feature for native tracing + +# 0.1.0 (2020-04-22) + +- Initial release! 🎉 diff --git a/clients/android/native/vendor/ndk/Cargo.toml b/clients/android/native/vendor/ndk/Cargo.toml new file mode 100644 index 00000000..fd8ea260 --- /dev/null +++ b/clients/android/native/vendor/ndk/Cargo.toml @@ -0,0 +1,125 @@ +# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO +# +# When uploading crates to the registry Cargo will automatically +# "normalize" Cargo.toml files for maximal compatibility +# with all versions of Cargo and also rewrite `path` dependencies +# to registry (e.g., crates.io) dependencies. +# +# If you are reading this file be aware that the original Cargo.toml +# will likely look very different (and much more reasonable). +# See Cargo.toml.orig for the original contents. + +[package] +edition = "2021" +rust-version = "1.66" +name = "ndk" +version = "0.9.0" +authors = ["The Rust Mobile contributors"] +description = "Safe Rust bindings to the Android NDK" +homepage = "https://github.com/rust-mobile/ndk" +documentation = "https://docs.rs/ndk" +readme = "README.md" +keywords = [ + "android", + "ndk", +] +license = "MIT OR Apache-2.0" +repository = "https://github.com/rust-mobile/ndk" + +[package.metadata.docs.rs] +features = [ + "jni", + "all", +] +rustdoc-args = [ + "--cfg", + "docsrs", +] +targets = [ + "aarch64-linux-android", + "armv7-linux-androideabi", + "i686-linux-android", + "x86_64-linux-android", +] + +[dependencies.bitflags] +version = "2.4" + +[dependencies.ffi] +version = "0.6.0" +package = "ndk-sys" + +[dependencies.jni] +version = "0.21" +optional = true + +[dependencies.jni-sys] +version = "0.3" + +[dependencies.log] +version = "0.4.6" + +[dependencies.num_enum] +version = "0.7" + +[dependencies.rwh_04] +version = "0.4" +optional = true +package = "raw-window-handle" + +[dependencies.rwh_05] +version = "0.5" +optional = true +package = "raw-window-handle" + +[dependencies.rwh_06] +version = "0.6" +optional = true +package = "raw-window-handle" + +[dependencies.thiserror] +version = "1.0.23" + +[dev-dependencies.libc] +version = "0.2.3" + +[features] +all = [ + "audio", + "bitmap", + "media", + "nativewindow", + "sync", + "api-level-33", + "rwh_04", + "rwh_05", + "rwh_06", +] +api-level-23 = [] +api-level-24 = ["api-level-23"] +api-level-25 = ["api-level-24"] +api-level-26 = ["api-level-25"] +api-level-27 = ["api-level-26"] +api-level-28 = ["api-level-27"] +api-level-29 = ["api-level-28"] +api-level-30 = ["api-level-29"] +api-level-31 = ["api-level-30"] +api-level-32 = ["api-level-31"] +api-level-33 = ["api-level-32"] +audio = [ + "ffi/audio", + "api-level-26", +] +bitmap = ["ffi/bitmap"] +default = ["rwh_06"] +media = ["ffi/media"] +nativewindow = ["ffi/nativewindow"] +sync = [ + "ffi/sync", + "api-level-26", +] +test = [ + "ffi/test", + "jni", + "all", +] diff --git a/clients/android/native/vendor/ndk/README.md b/clients/android/native/vendor/ndk/README.md new file mode 100644 index 00000000..cf7d71c0 --- /dev/null +++ b/clients/android/native/vendor/ndk/README.md @@ -0,0 +1,21 @@ +[![ci](https://github.com/rust-mobile/ndk/actions/workflows/rust.yml/badge.svg)](https://github.com/rust-mobile/ndk/actions/workflows/rust.yml) ![MIT license](https://img.shields.io/badge/License-MIT-green.svg) ![APACHE2 license](https://img.shields.io/badge/License-APACHE2-green.svg) + +Rust bindings to the [Android NDK](https://developer.android.com/ndk) + +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) [![Docs](https://docs.rs/ndk-sys/badge.svg)](https://docs.rs/ndk-sys) [![MSRV](https://img.shields.io/badge/rustc-1.60.0+-ab6000.svg)](https://blog.rust-lang.org/2022/04/07/Rust-1.60.0.html) +[`ndk`](./ndk) | Safe abstraction of the bindings | [![crates.io](https://img.shields.io/crates/v/ndk.svg)](https://crates.io/crates/ndk) [![Docs](https://docs.rs/ndk/badge.svg)](https://docs.rs/ndk) [![MSRV](https://img.shields.io/badge/rustc-1.64.0+-ab6000.svg)](https://blog.rust-lang.org/2022/09/22/Rust-1.64.0.html) + +See these [`ndk-examples`](https://github.com/rust-mobile/cargo-apk/tree/main/examples/examples) and these [`rust-android-examples`](https://github.com/rust-mobile/rust-android-examples) for examples using the NDK. + +> [!IMPORTANT] +> This repository was recently [modularized](https://github.com/rust-mobile/ndk/issues/372) and the following crates were split into separate repositories: +> +> Crate | New Location | Notes +> ------|--------------|------ +> ndk-context | https://github.com/rust-mobile/ndk-context | +> ndk-glue | https://github.com/rust-mobile/ndk-glue | ⛔ _deprecated_ - see [android-activity](https://github.com/rust-mobile/android-activity) +> ndk-macro | https://github.com/rust-mobile/ndk-glue | ⛔ _deprecated_ - see [android-activity](https://github.com/rust-mobile/android-activity) +> ndk-build | https://github.com/rust-mobile/cargo-apk | ⛔ _deprecated_ - see [xbuild](https://github.com/rust-mobile/xbuild) +> cargo-apk | https://github.com/rust-mobile/cargo-apk | ⛔ _deprecated_ - see [xbuild](https://github.com/rust-mobile/xbuild) diff --git a/clients/android/native/vendor/ndk/README.punktfunk.md b/clients/android/native/vendor/ndk/README.punktfunk.md new file mode 100644 index 00000000..f08d710b --- /dev/null +++ b/clients/android/native/vendor/ndk/README.punktfunk.md @@ -0,0 +1,14 @@ +# Vendored `ndk` 0.9.0 (patched) + +Verbatim copy of the published `ndk` 0.9.0 crate (https://crates.io/crates/ndk, +MIT OR Apache-2.0, © the rust-mobile contributors), wired in via `[patch.crates-io]` +in the workspace root. + +**The only change** is in `src/media/media_codec.rs`: `MediaCodec::as_ptr` is made +`pub` (upstream keeps it private) so the Android client can register +`AMediaCodec_setOnFrameRenderedCallback` through `ndk-sys` — the render-timestamp +callback behind the HUD's `display` stage (`design/stats-unification.md`), which the +wrapper doesn't expose. Grep for `punktfunk vendored patch` to find it. + +Drop this vendor copy when upstream exposes the raw pointer or a frame-rendered +callback binding (tracked against https://github.com/rust-mobile/ndk). diff --git a/clients/android/native/vendor/ndk/src/asset.rs b/clients/android/native/vendor/ndk/src/asset.rs new file mode 100644 index 00000000..4ae0c200 --- /dev/null +++ b/clients/android/native/vendor/ndk/src/asset.rs @@ -0,0 +1,323 @@ +//! Bindings for [`AAsset`], [`AAssetDir`] and [`AAssetManager`] +//! +//! [`AAsset`]: https://developer.android.com/ndk/reference/group/asset#aasset +//! [`AAssetDir`]: https://developer.android.com/ndk/reference/group/asset#aassetdir +//! [`AAssetManager`]: https://developer.android.com/ndk/reference/group/asset#aassetmanager + +use std::{ + ffi::{CStr, CString}, + io, + os::fd::{FromRawFd, OwnedFd}, + ptr::NonNull, +}; + +/// A native [`AAssetManager *`] +/// +/// [`AAssetManager *`]: https://developer.android.com/ndk/reference/group/asset#aassetmanager +#[derive(Debug)] +#[doc(alias = "AAssetManager")] +pub struct AssetManager { + ptr: NonNull, +} + +// AAssetManager is thread safe. +// See https://developer.android.com/ndk/reference/group/asset#aassetmanager +unsafe impl Send for AssetManager {} +unsafe impl Sync for AssetManager {} + +impl AssetManager { + /// Create an `AssetManager` from a pointer + /// + /// # Safety + /// By calling this function, you assert that the pointer is a valid pointer to a native + /// `AAssetManager`. + pub unsafe fn from_ptr(ptr: NonNull) -> Self { + Self { ptr } + } + + /// Returns the pointer to the native `AAssetManager`. + pub fn ptr(&self) -> NonNull { + self.ptr + } + + /// Open the asset. Returns [`None`] if opening the asset fails. + /// + /// This currently always opens the asset in the streaming mode. + #[doc(alias = "AAssetManager_open")] + pub fn open(&self, filename: &CStr) -> Option { + unsafe { + let ptr = ffi::AAssetManager_open( + self.ptr.as_ptr(), + filename.as_ptr(), + ffi::AASSET_MODE_STREAMING as i32, + ); + Some(Asset::from_ptr(NonNull::new(ptr)?)) + } + } + + /// Open an asset directory. Returns [`None`] if opening the directory fails. + #[doc(alias = "AAssetManager_openDir")] + pub fn open_dir(&self, filename: &CStr) -> Option { + unsafe { + let ptr = ffi::AAssetManager_openDir(self.ptr.as_ptr(), filename.as_ptr()); + Some(AssetDir::from_ptr(NonNull::new(ptr)?)) + } + } +} + +/// A native [`AAssetDir *`] +/// +/// ```no_run +/// # use std::ffi::CString; +/// # use ndk::asset::AssetManager; +/// # let asset_manager: AssetManager = unimplemented!(); +/// use std::io::Read; +/// +/// let mut my_dir = asset_manager +/// .open_dir(&CString::new("my_dir").unwrap()) +/// .expect("Could not open directory"); +/// +/// // Use it as an iterator +/// let all_files = my_dir.collect::>(); +/// +/// // Reset the iterator +/// my_dir.rewind(); +/// +/// // Use .with_next() to iterate without allocating `CString`s +/// while let Some(asset) = my_dir.with_next(|cstr| asset_manager.open(cstr).unwrap()) { +/// let mut text = String::new(); +/// asset.read_to_string(&mut text); +/// // ... +/// } +/// ``` +/// +/// [`AAssetDir *`]: https://developer.android.com/ndk/reference/group/asset#aassetdir +#[derive(Debug)] +#[doc(alias = "AAssetDir")] +pub struct AssetDir { + ptr: NonNull, +} + +// It's unclear if AAssetDir is thread safe. +// However, AAsset is not, so there's a good chance that AAssetDir is not either. + +impl Drop for AssetDir { + #[doc(alias = "AAssetDir_close")] + fn drop(&mut self) { + unsafe { ffi::AAssetDir_close(self.ptr.as_ptr()) } + } +} + +impl AssetDir { + /// Construct an `AssetDir` from the native `AAssetDir *`. This gives ownership of the + /// `AAssetDir *` to the `AssetDir`, which will handle closing the asset. Avoid using + /// the pointer after calling this function. + /// + /// # Safety + /// By calling this function, you assert that it points to a valid native `AAssetDir`. + pub unsafe fn from_ptr(ptr: NonNull) -> Self { + Self { ptr } + } + + /// The corresponding native `AAssetDir *` + pub fn ptr(&self) -> NonNull { + self.ptr + } + + /// Get the next filename, if any, and process it. Like [`Iterator::next()`], but performs + /// no additional allocation. + /// + /// The filenames are in the correct format to be passed to [`AssetManager::open()`]. + #[doc(alias = "AAssetDir_getNextFileName")] + pub fn with_next(&mut self, f: impl for<'a> FnOnce(&'a CStr) -> T) -> Option { + unsafe { + let next_name = ffi::AAssetDir_getNextFileName(self.ptr.as_ptr()); + if next_name.is_null() { + None + } else { + Some(f(CStr::from_ptr(next_name))) + } + } + } + + /// Reset the iteration state + #[doc(alias = "AAssetDir_rewind")] + pub fn rewind(&mut self) { + unsafe { + ffi::AAssetDir_rewind(self.ptr.as_ptr()); + } + } +} + +impl Iterator for AssetDir { + type Item = CString; + + fn next(&mut self) -> Option { + self.with_next(|cstr| cstr.to_owned()) + } +} + +/// A native [`AAsset *`], opened in streaming mode +/// +/// ```no_run +/// # use std::ffi::CString; +/// # use ndk::asset::AssetManager; +/// # let asset_manager: AssetManager = unimplemented!(); +/// use std::io::Read; +/// +/// let asset = asset_manager +/// .open(&CString::new("path/to/asset").unwrap()) +/// .expect("Could not open asset"); +/// +/// let mut data = vec![]; +/// asset.read_to_end(&mut data); +/// // ... use data ... +/// ``` +/// +/// [`AAsset *`]: https://developer.android.com/ndk/reference/group/asset#aasset +#[derive(Debug)] +#[doc(alias = "AAsset")] +pub struct Asset { + ptr: NonNull, +} + +// AAsset is *not* thread safe. +// See https://developer.android.com/ndk/reference/group/asset#aasset + +impl Drop for Asset { + #[doc(alias = "AAsset_close")] + fn drop(&mut self) { + unsafe { ffi::AAsset_close(self.ptr.as_ptr()) } + } +} + +impl Asset { + /// Construct an `Asset` from the native `AAsset *`. This gives ownership of the `AAsset *` to + /// the `Asset`, which will handle closing the asset. Avoid using the pointer after calling + /// this function. + /// + /// # Safety + /// By calling this function, you assert that it points to a valid native `AAsset`, open + /// in the streaming mode. + pub unsafe fn from_ptr(ptr: NonNull) -> Self { + Self { ptr } + } + + /// The corresponding native `AAsset *` + pub fn ptr(&self) -> NonNull { + self.ptr + } + + /// Returns the total length of the asset, in bytes + #[doc(alias = "AAsset_getLength64")] + pub fn length(&self) -> usize { + unsafe { ffi::AAsset_getLength64(self.ptr.as_ptr()) as usize } + } + + /// Returns the remaining length of the asset, in bytes + #[doc(alias = "AAsset_getRemainingLength64")] + pub fn remaining_length(&self) -> usize { + unsafe { ffi::AAsset_getRemainingLength64(self.ptr.as_ptr()) as usize } + } + + /// Maps all data into a buffer and returns it + #[doc(alias = "AAsset_getBuffer")] + pub fn buffer(&mut self) -> io::Result<&[u8]> { + unsafe { + let buf_ptr = ffi::AAsset_getBuffer(self.ptr.as_ptr()); + if buf_ptr.is_null() { + Err(io::Error::new( + io::ErrorKind::Other, + "Android Asset error creating buffer", + )) + } else { + Ok(std::slice::from_raw_parts( + buf_ptr as *const u8, + self.length(), + )) + } + } + } + + /// Returns whether this asset's internal buffer is allocated in ordinary RAM (i.e. not `mmap`ped). + #[doc(alias = "AAsset_isAllocated")] + pub fn is_allocated(&self) -> bool { + unsafe { ffi::AAsset_isAllocated(self.ptr.as_ptr()) != 0 } + } + + /// Open a new file descriptor that can be used to read the asset data. + /// + /// Returns an error if direct fd access is not possible (for example, if the asset is compressed). + #[doc(alias = "AAsset_openFileDescriptor64")] + pub fn open_file_descriptor(&self) -> io::Result { + let mut offset = 0; + let mut size = 0; + let res = + unsafe { ffi::AAsset_openFileDescriptor64(self.ptr.as_ptr(), &mut offset, &mut size) }; + if res >= 0 { + Ok(OpenedFileDescriptor { + fd: unsafe { OwnedFd::from_raw_fd(res) }, + offset: offset as usize, + size: size as usize, + }) + } else { + Err(io::Error::new( + io::ErrorKind::Other, + "Android Asset openFileDescriptor error", + )) + } + } +} + +impl io::Read for Asset { + #[doc(alias = "AAsset_read")] + fn read(&mut self, buf: &mut [u8]) -> io::Result { + unsafe { + let res = ffi::AAsset_read(self.ptr.as_ptr(), buf.as_mut_ptr() as *mut _, buf.len()); + if res >= 0 { + Ok(res as usize) + } else { + Err(io::Error::new( + io::ErrorKind::Other, + "Android Asset read error", + )) + } + } + } +} + +impl io::Seek for Asset { + #[doc(alias = "AAsset_seek64")] + fn seek(&mut self, seek: io::SeekFrom) -> io::Result { + unsafe { + let res = match seek { + io::SeekFrom::Start(x) => { + ffi::AAsset_seek64(self.ptr.as_ptr(), x as i64, ffi::SEEK_SET as i32) + } + io::SeekFrom::Current(x) => { + ffi::AAsset_seek64(self.ptr.as_ptr(), x, ffi::SEEK_CUR as i32) + } + io::SeekFrom::End(x) => { + ffi::AAsset_seek64(self.ptr.as_ptr(), x, ffi::SEEK_END as i32) + } + }; + if res < 0 { + Err(io::Error::new( + io::ErrorKind::Other, + "Android Asset seek error", + )) + } else { + Ok(res as u64) + } + } + } +} + +/// Contains the opened file descriptor returned by [`Asset::open_file_descriptor()`], together +/// with the offset and size of the given asset within that file descriptor. +#[derive(Debug)] +pub struct OpenedFileDescriptor { + pub fd: OwnedFd, + pub offset: usize, + pub size: usize, +} diff --git a/clients/android/native/vendor/ndk/src/audio.rs b/clients/android/native/vendor/ndk/src/audio.rs new file mode 100644 index 00000000..0be6e1c3 --- /dev/null +++ b/clients/android/native/vendor/ndk/src/audio.rs @@ -0,0 +1,1419 @@ +//! Bindings for [`AAudioStream`] and [`AAudioStreamBuilder`] +//! +//! See [the NDK guide](https://developer.android.com/ndk/guides/audio/aaudio/aaudio) for +//! design and usage instructions, and [the NDK reference](https://developer.android.com/ndk/reference/group/audio) +//! for an API overview. +//! +//! [`AAudioStream`]: https://developer.android.com/ndk/reference/group/audio#aaudiostream +//! [`AAudioStreamBuilder`]: https://developer.android.com/ndk/reference/group/audio#aaudiostreambuilder +#![cfg(feature = "audio")] + +use std::{ + borrow::Cow, + ffi::{c_void, CStr}, + fmt, + mem::MaybeUninit, + num::NonZeroI32, + ptr::NonNull, +}; + +use num_enum::{FromPrimitive, IntoPrimitive}; + +use crate::utils::abort_on_panic; + +/// Specifying if audio may or may not be captured by other apps or the system. +/// +/// Note that these match the equivalent values in [`android.media.AudioAttributes`] +/// in the Android Java API. +/// +/// [`android.media.AudioAttributes`]: https://developer.android.com/reference/android/media/AudioAttributes +#[cfg(feature = "api-level-29")] +#[repr(i32)] +#[derive(Copy, Clone, Debug, PartialEq, Eq, FromPrimitive, IntoPrimitive)] +#[doc(alias = "aaudio_allowed_capture_policy_t")] +#[non_exhaustive] +pub enum AudioAllowedCapturePolicy { + /// Indicates that the audio may be captured by any app. + /// + /// For privacy, the following [usages][AudioUsage] can not be recorded: `VoiceCommunication*`, + /// `Notification*`, `Assistance*` and [`Assistant`][AudioUsage::Assistant]. + /// + /// On Android Q, this means only [`Media`][AudioUsage::Media] and [`Game`][AudioUsage::Game] may be captured. + /// + /// See [`MediaProjection`] and [`AudioStreamBuilder::allowed_capture_policy()`]. + /// + /// [`MediaProjection`]: https://developer.android.com/reference/android/media/projection/MediaProjection + #[doc(alias = "AAUDIO_ALLOW_CAPTURE_BY_ALL")] + AllowCaptureByAll = ffi::AAUDIO_ALLOW_CAPTURE_BY_ALL as ffi::aaudio_allowed_capture_policy_t, + /// Indicates that the audio may only be captured by system apps. + /// + /// System apps can capture for many purposes like accessibility, live captions, user + /// guidance... but abide to the following restrictions: + /// - the audio cannot leave the device; + /// - the audio cannot be passed to a third party app; + /// - the audio cannot be recorded at a higher quality than 16kHz 16bit mono. + /// + /// See [`AudioStreamBuilder::allowed_capture_policy()`]. + #[doc(alias = "AAUDIO_ALLOW_CAPTURE_BY_SYSTEM")] + AllowCaptureBySystem = + ffi::AAUDIO_ALLOW_CAPTURE_BY_SYSTEM as ffi::aaudio_allowed_capture_policy_t, + /// Indicates that the audio may not be recorded by any app, even if it is a system app. + /// + /// It is encouraged to use [`AllowCaptureBySystem`][Self::AllowCaptureBySystem] instead of + /// this value as system apps provide significant and useful features for the user (such as + /// live captioning and accessibility). + #[doc(alias = "AAUDIO_ALLOW_CAPTURE_BY_NONE")] + AllowCaptureByNone = ffi::AAUDIO_ALLOW_CAPTURE_BY_NONE as ffi::aaudio_allowed_capture_policy_t, + + #[doc(hidden)] + #[num_enum(catch_all)] + __Unknown(i32), +} + +/// The ContentType attribute describes "what" you are playing. +/// It expresses the general category of the content. This information is optional. +/// But in case it is known (for instance `Movie` for a +/// movie streaming service or `Speech` for +/// an audio book application) this information might be used by the audio framework to +/// enforce audio focus. +/// +/// Note that these match the equivalent values in [`android.media.AudioAttributes`] +/// in the Android Java API. +/// +/// [`android.media.AudioAttributes`]: https://developer.android.com/reference/android/media/AudioAttributes +#[cfg(feature = "api-level-28")] +#[repr(i32)] +#[derive(Copy, Clone, Debug, PartialEq, Eq, FromPrimitive, IntoPrimitive)] +#[doc(alias = "aaudio_content_type_t")] +#[non_exhaustive] +pub enum AudioContentType { + /// Use this for spoken voice, audio books, etcetera. + #[doc(alias = "AAUDIO_CONTENT_TYPE_SPEECH")] + Speech = ffi::AAUDIO_CONTENT_TYPE_SPEECH as ffi::aaudio_content_type_t, + /// Use this for pre-recorded or live music. + #[doc(alias = "AAUDIO_CONTENT_TYPE_MUSIC")] + Music = ffi::AAUDIO_CONTENT_TYPE_MUSIC as ffi::aaudio_content_type_t, + /// Use this for a movie or video soundtrack. + #[doc(alias = "AAUDIO_CONTENT_TYPE_MOVIE")] + Movie = ffi::AAUDIO_CONTENT_TYPE_MOVIE as ffi::aaudio_content_type_t, + /// Use this for sound is designed to accompany a user action, + /// such as a click or beep sound made when the user presses a button. + #[doc(alias = "AAUDIO_CONTENT_TYPE_SONIFICATION")] + Sonification = ffi::AAUDIO_CONTENT_TYPE_SONIFICATION as ffi::aaudio_content_type_t, + + #[doc(hidden)] + #[num_enum(catch_all)] + __Unknown(i32), +} + +#[repr(i32)] +#[derive(Copy, Clone, Debug, PartialEq, Eq, FromPrimitive, IntoPrimitive)] +#[doc(alias = "aaudio_direction_t")] +#[non_exhaustive] +pub enum AudioDirection { + /// Audio data will travel into the device, for example from a microphone. + #[doc(alias = "AAUDIO_DIRECTION_OUTPUT")] + Output = ffi::AAUDIO_DIRECTION_OUTPUT as ffi::aaudio_direction_t, + /// Audio data will travel out of the device, for example through a speaker. + #[doc(alias = "AAUDIO_DIRECTION_INPUT")] + Input = ffi::AAUDIO_DIRECTION_INPUT as ffi::aaudio_direction_t, + + #[doc(hidden)] + #[num_enum(catch_all)] + __Unknown(i32), +} + +#[repr(i32)] +#[derive(Copy, Clone, Debug, PartialEq, Eq, FromPrimitive, IntoPrimitive)] +#[allow(non_camel_case_types)] +#[doc(alias = "aaudio_format_t")] +#[non_exhaustive] +pub enum AudioFormat { + #[doc(alias = "AAUDIO_FORMAT_INVALID")] + Invalid = ffi::AAUDIO_FORMAT_INVALID as ffi::aaudio_format_t, + #[doc(alias = "AAUDIO_FORMAT_UNSPECIFIED")] + Unspecified = ffi::AAUDIO_FORMAT_UNSPECIFIED as ffi::aaudio_format_t, + + /// This format uses the i16 data type. + /// The maximum range of the data is -32768 to 32767. + #[doc(alias = "AAUDIO_FORMAT_PCM_I16")] + PCM_I16 = ffi::AAUDIO_FORMAT_PCM_I16 as ffi::aaudio_format_t, + /// This format uses the float data type. + /// The nominal range of the data is [-1.0f32, 1.0f32). + /// Values outside that range may be clipped. + /// + /// See also `audioData` at + /// AudioTrack#write(float[], int, int, int). + #[doc(alias = "AAUDIO_FORMAT_PCM_FLOAT")] + PCM_Float = ffi::AAUDIO_FORMAT_PCM_FLOAT as ffi::aaudio_format_t, + + #[doc(hidden)] + #[num_enum(catch_all)] + __Unknown(i32), +} + +/// Defines the audio source. +/// An audio source defines both a default physical source of audio signal, and a recording +/// configuration. +/// +/// Note that these match the equivalent values in MediaRecorder.AudioSource in the Android Java API. +#[cfg(feature = "api-level-28")] +#[repr(i32)] +#[derive(Copy, Clone, Debug, PartialEq, Eq, FromPrimitive, IntoPrimitive)] +#[doc(alias = "aaudio_input_preset_t")] +#[non_exhaustive] +pub enum AudioInputPreset { + /// Use this preset when other presets do not apply. + #[doc(alias = "AAUDIO_INPUT_PRESET_GENERIC")] + Generic = ffi::AAUDIO_INPUT_PRESET_GENERIC as ffi::aaudio_input_preset_t, + /// Use this preset when recording video. + #[doc(alias = "AAUDIO_INPUT_PRESET_CAMCORDER")] + Camcorder = ffi::AAUDIO_INPUT_PRESET_CAMCORDER as ffi::aaudio_input_preset_t, + /// Use this preset when doing speech recognition. + #[doc(alias = "AAUDIO_INPUT_PRESET_VOICE_RECOGNITION")] + VoiceRecognition = ffi::AAUDIO_INPUT_PRESET_VOICE_RECOGNITION as ffi::aaudio_input_preset_t, + /// Use this preset when doing telephony or voice messaging. + #[doc(alias = "AAUDIO_INPUT_PRESET_VOICE_COMMUNICATION")] + VoiceCommunication = ffi::AAUDIO_INPUT_PRESET_VOICE_COMMUNICATION as ffi::aaudio_input_preset_t, + /// Use this preset to obtain an input with no effects. + /// Note that this input will not have automatic gain control + /// so the recorded volume may be very low. + #[doc(alias = "AAUDIO_INPUT_PRESET_UNPROCESSED")] + Unprocessed = ffi::AAUDIO_INPUT_PRESET_UNPROCESSED as ffi::aaudio_input_preset_t, + /// Use this preset for capturing audio meant to be processed in real time + /// and played back for live performance (e.g karaoke). + /// The capture path will minimize latency and coupling with playback path. + #[cfg(feature = "api-level-29")] + #[doc(alias = "AAUDIO_INPUT_PRESET_VOICE_PERFORMANCE")] + VoicePerformance = ffi::AAUDIO_INPUT_PRESET_VOICE_PERFORMANCE as ffi::aaudio_input_preset_t, + + #[doc(hidden)] + #[num_enum(catch_all)] + __Unknown(i32), +} + +#[repr(i32)] +#[derive(Copy, Clone, Debug, PartialEq, Eq, FromPrimitive, IntoPrimitive)] +#[doc(alias = "aaudio_performance_mode_t")] +#[non_exhaustive] +pub enum AudioPerformanceMode { + /// No particular performance needs. Default. + #[doc(alias = "AAUDIO_PERFORMANCE_MODE_NONE")] + None = ffi::AAUDIO_PERFORMANCE_MODE_NONE as ffi::aaudio_performance_mode_t, + /// Extending battery life is more important than low latency. + /// + /// This mode is not supported in input streams. + /// For input, mode NONE will be used if this is requested. + #[doc(alias = "AAUDIO_PERFORMANCE_MODE_POWER_SAVING")] + PowerSaving = ffi::AAUDIO_PERFORMANCE_MODE_POWER_SAVING as ffi::aaudio_performance_mode_t, + /// Reducing latency is more important than battery life. + #[doc(alias = "AAUDIO_PERFORMANCE_MODE_LOW_LATENCY")] + LowLatency = ffi::AAUDIO_PERFORMANCE_MODE_LOW_LATENCY as ffi::aaudio_performance_mode_t, + + #[doc(hidden)] + #[num_enum(catch_all)] + __Unknown(i32), +} + +#[repr(i32)] +#[derive(Copy, Clone, Debug, PartialEq, Eq, FromPrimitive, IntoPrimitive)] +#[doc(alias = "aaudio_sharing_mode_t")] +#[non_exhaustive] +pub enum AudioSharingMode { + /// This will be the only stream using a particular source or sink. + /// This mode will provide the lowest possible latency. + /// You should close Exclusive streams immediately when you are not using them. + #[doc(alias = "AAUDIO_SHARING_MODE_EXCLUSIVE")] + Exclusive = ffi::AAUDIO_SHARING_MODE_EXCLUSIVE as ffi::aaudio_sharing_mode_t, + /// Multiple applications will be mixed by the AAudio Server. + /// This will have higher latency than the Exclusive mode. + #[doc(alias = "AAUDIO_SHARING_MODE_SHARED")] + Shared = ffi::AAUDIO_SHARING_MODE_SHARED as ffi::aaudio_sharing_mode_t, + + #[doc(hidden)] + #[num_enum(catch_all)] + __Unknown(i32), +} + +/// The Usage attribute expresses "why" you are playing a sound, what is this sound used for. +/// This information is used by certain platforms or routing policies +/// to make more refined volume or routing decisions. +/// +/// Note that these match the equivalent values in [`android.media.AudioAttributes`] +/// in the Android Java API. +/// +/// [`android.media.AudioAttributes`]: https://developer.android.com/reference/android/media/AudioAttributes +#[cfg(feature = "api-level-28")] +#[repr(i32)] +#[derive(Copy, Clone, Debug, PartialEq, Eq, FromPrimitive, IntoPrimitive)] +#[doc(alias = "aaudio_usage_t")] +#[non_exhaustive] +pub enum AudioUsage { + /// Use this for streaming media, music performance, video, podcasts, etcetera. + #[doc(alias = "AAUDIO_USAGE_MEDIA")] + Media = ffi::AAUDIO_USAGE_MEDIA as ffi::aaudio_usage_t, + /// Use this for voice over IP, telephony, etcetera. + #[doc(alias = "AAUDIO_USAGE_VOICE_COMMUNICATION")] + VoiceCommunication = ffi::AAUDIO_USAGE_VOICE_COMMUNICATION as ffi::aaudio_usage_t, + /// Use this for sounds associated with telephony such as busy tones, DTMF, etcetera. + #[doc(alias = "AAUDIO_USAGE_VOICE_COMMUNICATION_SIGNALLING")] + VoiceCommunicationSignalling = + ffi::AAUDIO_USAGE_VOICE_COMMUNICATION_SIGNALLING as ffi::aaudio_usage_t, + /// Use this to demand the users attention. + #[doc(alias = "AAUDIO_USAGE_ALARM")] + Alarm = ffi::AAUDIO_USAGE_ALARM as ffi::aaudio_usage_t, + /// Use this for notifying the user when a message has arrived or some + /// other background event has occured. + #[doc(alias = "AAUDIO_USAGE_NOTIFICATION")] + Notification = ffi::AAUDIO_USAGE_NOTIFICATION as ffi::aaudio_usage_t, + /// Use this when the phone rings. + #[doc(alias = "AAUDIO_USAGE_NOTIFICATION_RINGTONE")] + NotificationRingtone = ffi::AAUDIO_USAGE_NOTIFICATION_RINGTONE as ffi::aaudio_usage_t, + /// Use this to attract the users attention when, for example, the battery is low. + #[doc(alias = "AAUDIO_USAGE_NOTIFICATION_EVENT")] + NotificationEvent = ffi::AAUDIO_USAGE_NOTIFICATION_EVENT as ffi::aaudio_usage_t, + /// Use this for screen readers, etcetera. + #[doc(alias = "AAUDIO_USAGE_ASSISTANCE_ACCESSIBILITY")] + AssistanceAccessibility = ffi::AAUDIO_USAGE_ASSISTANCE_ACCESSIBILITY as ffi::aaudio_usage_t, + /// Use this for driving or navigation directions. + #[doc(alias = "AAUDIO_USAGE_ASSISTANCE_NAVIGATION_GUIDANCE")] + AssistanceNavigationGuidance = + ffi::AAUDIO_USAGE_ASSISTANCE_NAVIGATION_GUIDANCE as ffi::aaudio_usage_t, + /// Use this for user interface sounds, beeps, etcetera. + #[doc(alias = "AAUDIO_USAGE_ASSISTANCE_SONIFICATION")] + AssistanceSonification = ffi::AAUDIO_USAGE_ASSISTANCE_SONIFICATION as ffi::aaudio_usage_t, + /// Use this for game audio and sound effects. + #[doc(alias = "AAUDIO_USAGE_GAME")] + Game = ffi::AAUDIO_USAGE_GAME as ffi::aaudio_usage_t, + /// Use this for audio responses to user queries, audio instructions or help utterances. + #[doc(alias = "AAUDIO_USAGE_ASSISTANT")] + Assistant = ffi::AAUDIO_USAGE_ASSISTANT as ffi::aaudio_usage_t, + /// Use this in case of playing sounds in an emergency. + /// Privileged MODIFY_AUDIO_ROUTING permission required. + #[doc(alias = "AAUDIO_SYSTEM_USAGE_EMERGENCY")] + SystemEmergency = ffi::AAUDIO_SYSTEM_USAGE_EMERGENCY as ffi::aaudio_usage_t, + /// Use this for safety sounds and alerts, for example backup camera obstacle detection. + /// Privileged MODIFY_AUDIO_ROUTING permission required. + #[doc(alias = "AAUDIO_SYSTEM_USAGE_SAFETY")] + SystemSafety = ffi::AAUDIO_SYSTEM_USAGE_SAFETY as ffi::aaudio_usage_t, + /// Use this for vehicle status alerts and information, for example the check engine light. + /// Privileged MODIFY_AUDIO_ROUTING permission required. + #[doc(alias = "AAUDIO_SYSTEM_USAGE_VEHICLE_STATUS")] + SystemVehicleStatus = ffi::AAUDIO_SYSTEM_USAGE_VEHICLE_STATUS as ffi::aaudio_usage_t, + #[doc(alias = "announcements")] + /// Use this for traffic announcements as ffi::aaudio_usage_t, etc. + /// Privileged MODIFY_AUDIO_ROUTING permission required. + #[doc(alias = "AAUDIO_SYSTEM_USAGE_ANNOUNCEMENT")] + SystemAnnouncement = ffi::AAUDIO_SYSTEM_USAGE_ANNOUNCEMENT as ffi::aaudio_usage_t, + + #[doc(hidden)] + #[num_enum(catch_all)] + __Unknown(i32), +} + +#[repr(i32)] +#[derive(Copy, Clone, Debug, PartialEq, Eq, FromPrimitive, IntoPrimitive)] +#[doc(alias = "aaudio_stream_state_t")] +#[non_exhaustive] +pub enum AudioStreamState { + #[doc(alias = "AAUDIO_STREAM_STATE_UNINITIALIZED")] + Uninitialized = ffi::AAUDIO_STREAM_STATE_UNINITIALIZED as ffi::aaudio_stream_state_t, + #[doc(alias = "AAUDIO_STREAM_STATE_UNKNOWN")] + Unknown = ffi::AAUDIO_STREAM_STATE_UNKNOWN as ffi::aaudio_stream_state_t, + #[doc(alias = "AAUDIO_STREAM_STATE_OPEN")] + Open = ffi::AAUDIO_STREAM_STATE_OPEN as ffi::aaudio_stream_state_t, + #[doc(alias = "AAUDIO_STREAM_STATE_STARTING")] + Starting = ffi::AAUDIO_STREAM_STATE_STARTING as ffi::aaudio_stream_state_t, + #[doc(alias = "AAUDIO_STREAM_STATE_STARTED")] + Started = ffi::AAUDIO_STREAM_STATE_STARTED as ffi::aaudio_stream_state_t, + #[doc(alias = "AAUDIO_STREAM_STATE_PAUSING")] + Pausing = ffi::AAUDIO_STREAM_STATE_PAUSING as ffi::aaudio_stream_state_t, + #[doc(alias = "AAUDIO_STREAM_STATE_PAUSED")] + Paused = ffi::AAUDIO_STREAM_STATE_PAUSED as ffi::aaudio_stream_state_t, + #[doc(alias = "AAUDIO_STREAM_STATE_FLUSHING")] + Flushing = ffi::AAUDIO_STREAM_STATE_FLUSHING as ffi::aaudio_stream_state_t, + #[doc(alias = "AAUDIO_STREAM_STATE_FLUSHED")] + Flushed = ffi::AAUDIO_STREAM_STATE_FLUSHED as ffi::aaudio_stream_state_t, + #[doc(alias = "AAUDIO_STREAM_STATE_STOPPING")] + Stopping = ffi::AAUDIO_STREAM_STATE_STOPPING as ffi::aaudio_stream_state_t, + #[doc(alias = "AAUDIO_STREAM_STATE_STOPPED")] + Stopped = ffi::AAUDIO_STREAM_STATE_STOPPED as ffi::aaudio_stream_state_t, + #[doc(alias = "AAUDIO_STREAM_STATE_CLOSING")] + Closing = ffi::AAUDIO_STREAM_STATE_CLOSING as ffi::aaudio_stream_state_t, + #[doc(alias = "AAUDIO_STREAM_STATE_CLOSED")] + Closed = ffi::AAUDIO_STREAM_STATE_CLOSED as ffi::aaudio_stream_state_t, + #[doc(alias = "AAUDIO_STREAM_STATE_DISCONNECTED")] + Disconnected = ffi::AAUDIO_STREAM_STATE_DISCONNECTED as ffi::aaudio_stream_state_t, + + #[doc(hidden)] + #[num_enum(catch_all)] + __Unknown(i32), +} + +impl AudioStreamState { + #[doc(alias = "AAudio_convertStreamStateToText")] + pub fn to_text(self) -> Cow<'static, str> { + let ptr = unsafe { CStr::from_ptr(ffi::AAudio_convertStreamStateToText(self.into())) }; + ptr.to_string_lossy() + } +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[doc(alias = "aaudio_session_id_t")] +pub enum SessionId { + None, + Allocated(NonZeroI32), +} + +#[derive(Copy, Clone, Debug)] +pub struct Timestamp { + pub frame_position: i64, + pub time_nanoseconds: i64, +} + +#[repr(u32)] +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[non_exhaustive] +pub enum Clockid { + #[doc(alias = "CLOCK_MONOTONIC")] + Monotonic = ffi::CLOCK_MONOTONIC, + #[doc(alias = "CLOCK_BOOTTIME")] + Boottime = ffi::CLOCK_BOOTTIME, +} + +/// Value returned the data callback function. +#[repr(i32)] +#[derive(Copy, Clone, Debug, PartialEq, Eq, IntoPrimitive)] +#[doc(alias = "aaudio_data_callback_result_t")] +#[non_exhaustive] +pub enum AudioCallbackResult { + /// Continue calling the callback. + #[doc(alias = "AAUDIO_CALLBACK_RESULT_CONTINUE")] + Continue = ffi::AAUDIO_CALLBACK_RESULT_CONTINUE as ffi::aaudio_data_callback_result_t, + /// Stop calling the callback. + /// + /// The application will still need to call [`AudioStream::request_pause()`] + /// or [`AudioStream::request_stop()`]. + #[doc(alias = "AAUDIO_CALLBACK_RESULT_STOP")] + Stop = ffi::AAUDIO_CALLBACK_RESULT_STOP as ffi::aaudio_data_callback_result_t, +} + +#[repr(i32)] +#[derive(Copy, Clone, Debug, PartialEq, Eq, FromPrimitive, IntoPrimitive)] +#[doc(alias = "aaudio_result_t")] +#[non_exhaustive] +pub enum AudioError { + #[doc(alias = "AAUDIO_ERROR_BASE")] + Base = ffi::AAUDIO_ERROR_BASE, + /// The audio device was disconnected. This could occur, for example, when headphones + /// are plugged in or unplugged. The stream cannot be used after the device is disconnected. + /// Applications should stop and close the stream. + /// If this error is received in an error callback then another thread should be + /// used to stop and close the stream. + #[doc(alias = "AAUDIO_ERROR_DISCONNECTED")] + Disconnected = ffi::AAUDIO_ERROR_DISCONNECTED, + /// An invalid parameter was passed to AAudio. + #[doc(alias = "AAUDIO_ERROR_ILLEGAL_ARGUMENT")] + IllegalArgument = ffi::AAUDIO_ERROR_ILLEGAL_ARGUMENT, + /// The requested operation is not appropriate for the current state of AAudio. + #[doc(alias = "AAUDIO_ERROR_INTERNAL")] + Internal = ffi::AAUDIO_ERROR_INTERNAL, + /// The requested operation is not appropriate for the current state of AAudio. + #[doc(alias = "AAUDIO_ERROR_INVALID_STATE")] + InvalidState = ffi::AAUDIO_ERROR_INVALID_STATE, + /// The server rejected the handle used to identify the stream. + #[doc(alias = "AAUDIO_ERROR_INVALID_HANDLE")] + InvalidHandle = ffi::AAUDIO_ERROR_INVALID_HANDLE, + /// The function is not implemented for this stream. + #[doc(alias = "AAUDIO_ERROR_UNIMPLEMENTED")] + Unimplemented = ffi::AAUDIO_ERROR_UNIMPLEMENTED, + /// A resource or information is unavailable. + /// This could occur when an application tries to open too many streams, + /// or a timestamp is not available. + #[doc(alias = "AAUDIO_ERROR_UNAVAILABLE")] + Unavailable = ffi::AAUDIO_ERROR_UNAVAILABLE, + /// Memory could not be allocated. + #[doc(alias = "AAUDIO_ERROR_NO_FREE_HANDLES")] + NoFreeHandles = ffi::AAUDIO_ERROR_NO_FREE_HANDLES, + /// Memory could not be allocated. + #[doc(alias = "AAUDIO_ERROR_NO_MEMORY")] + NoMemory = ffi::AAUDIO_ERROR_NO_MEMORY, + #[doc(alias = "AAUDIO_ERROR_NULL")] + Null = ffi::AAUDIO_ERROR_NULL, + #[doc(alias = "AAUDIO_ERROR_TIMEOUT")] + Timeout = ffi::AAUDIO_ERROR_TIMEOUT, + #[doc(alias = "AAUDIO_ERROR_WOULD_BLOCK")] + WouldBlock = ffi::AAUDIO_ERROR_WOULD_BLOCK, + /// The requested data format is not supported. + #[doc(alias = "AAUDIO_ERROR_INVALID_FORMAT")] + InvalidFormat = ffi::AAUDIO_ERROR_INVALID_FORMAT, + /// A requested was out of range. + #[doc(alias = "AAUDIO_ERROR_OUT_OF_RANGE")] + OutOfRange = ffi::AAUDIO_ERROR_OUT_OF_RANGE, + /// The audio service was not available. + #[doc(alias = "AAUDIO_ERROR_NO_SERVICE")] + NoService = ffi::AAUDIO_ERROR_NO_SERVICE, + /// The requested sample rate was not supported. + #[doc(alias = "AAUDIO_ERROR_INVALID_RATE")] + InvalidRate = ffi::AAUDIO_ERROR_INVALID_RATE, + + // Use the OK discriminant, as no-one will be able to call `as i32` and only has access to the + // constants via `From` provided by `IntoPrimitive` which reads the contained value. + #[doc(hidden)] + #[num_enum(catch_all)] + __Unknown(i32), +} + +impl fmt::Display for AudioError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{:?}", self) + } +} + +impl std::error::Error for AudioError {} + +impl AudioError { + #[doc(alias = "AAudio_convertStreamStateToText")] + pub fn to_text(self) -> Cow<'static, str> { + let ptr = unsafe { CStr::from_ptr(ffi::AAudio_convertStreamStateToText(self.into())) }; + ptr.to_string_lossy() + } + + /// Returns [`Ok`] on [`ffi::AAUDIO_OK`], [`Err`] otherwise (including positive values). + /// + /// Note that some known error codes (currently only for `AMediaCodec`) are positive. + pub(crate) fn from_result(status: ffi::aaudio_result_t) -> Result<()> { + match status { + ffi::AAUDIO_OK => Ok(()), + x => Err(Self::from(x)), + } + } +} + +pub type Result = std::result::Result; + +fn construct(with_ptr: impl FnOnce(*mut T) -> ffi::aaudio_result_t) -> Result { + let mut result = MaybeUninit::uninit(); + let status = with_ptr(result.as_mut_ptr()); + AudioError::from_result(status).map(|()| unsafe { result.assume_init() }) +} + +/// A native [`AAudioStreamBuilder *`] +/// +/// [`AAudioStreamBuilder *`]: https://developer.android.com/ndk/reference/group/audio#aaudiostreambuilder +#[doc(alias = "AAudioStreamBuilder")] +pub struct AudioStreamBuilder { + inner: NonNull, + data_callback: Option, + error_callback: Option, +} + +impl fmt::Debug for AudioStreamBuilder { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("AAudioStreamBuilder") + .field("inner", &self.inner) + .field( + "data_callback", + match &self.data_callback { + Some(_) => &"Some(_)", + None => &"None", + }, + ) + .field( + "error_callback", + match &self.error_callback { + Some(_) => &"Some(_)", + None => &"None", + }, + ) + .finish() + } +} + +#[doc(alias = "AAudioStream_dataCallback")] +pub type AudioStreamDataCallback = + Box AudioCallbackResult + Send>; +#[doc(alias = "AAudioStream_errorCallback")] +pub type AudioStreamErrorCallback = Box; + +impl AudioStreamBuilder { + fn from_ptr(inner: NonNull) -> Self { + Self { + inner, + data_callback: None, + error_callback: None, + } + } + + fn as_ptr(&self) -> *mut ffi::AAudioStreamBuilder { + self.inner.as_ptr() + } + + #[doc(alias = "AAudio_createStreamBuilder")] + pub fn new() -> Result { + unsafe { + let ptr = construct(|res| ffi::AAudio_createStreamBuilder(res))?; + Ok(Self::from_ptr(NonNull::new_unchecked(ptr))) + } + } + + /// Specify whether this stream audio may or may not be captured by other apps or the system. + /// + /// The default is [`AudioAllowedCapturePolicy::AllowCaptureByAll`]. + /// + /// Note that an application can also set its global policy, in which case the most restrictive + /// policy is always applied. See [`android.media.AudioAttributes#setAllowedCapturePolicy(int)`]. + /// + /// # Parameters + /// + /// - `policy`: the desired level of opt-out from being captured. + /// + /// [`android.media.AudioAttributes#setAllowedCapturePolicy(int)`]: https://developer.android.com/reference/android/media/AudioAttributes.Builder#setAllowedCapturePolicy(int) + #[cfg(feature = "api-level-29")] + #[doc(alias = "AAudioStreamBuilder_setAllowedCapturePolicy")] + pub fn allowed_capture_policy(self, capture_policy: AudioAllowedCapturePolicy) -> Self { + unsafe { + ffi::AAudioStreamBuilder_setAllowedCapturePolicy(self.as_ptr(), capture_policy.into()) + }; + self + } + + /// Set the requested buffer capacity in frames. + /// The final AAudioStream capacity may differ, but will probably be at least this big. + /// + /// The default, if you do not call this function, is unspecified. + /// + /// # Parameters + /// + /// - `num_frames`: the desired buffer capacity in frames or 0 for unspecified + #[doc(alias = "AAudioStreamBuilder_setBufferCapacityInFrames")] + pub fn buffer_capacity_in_frames(self, num_frames: i32) -> Self { + unsafe { ffi::AAudioStreamBuilder_setBufferCapacityInFrames(self.as_ptr(), num_frames) }; + self + } + + /// Request a number of channels for the stream. + /// + /// The default, if you do not call this function, is unspecified. + /// An optimal value will then be chosen when the stream is opened. + /// After opening a stream with an unspecified value, the application must + /// query for the actual value, which may vary by device. + /// + /// If an exact value is specified then an opened stream will use that value. + /// If a stream cannot be opened with the specified value then the open will fail. + /// + /// # Parameters + /// + /// - `channel_count`: Number of channels desired. + #[doc(alias = "AAudioStreamBuilder_setChannelCount")] + pub fn channel_count(self, channel_count: i32) -> Self { + unsafe { ffi::AAudioStreamBuilder_setChannelCount(self.as_ptr(), channel_count) }; + self + } + + /// Set the type of audio data that the stream will carry. + /// + /// The AAudio system will use this information to optimize the + /// behavior of the stream. + /// This could, for example, affect whether a stream is paused when a notification occurs. + /// + /// The default, if you do not call this function, is [`AudioContentType::Music`]. + /// + /// # Parameters + /// + /// - `content_type`: the type of audio data, eg. [`AudioContentType::Speech`] + #[cfg(feature = "api-level-28")] + #[doc(alias = "AAudioStreamBuilder_setContentType")] + pub fn content_type(self, content_type: AudioContentType) -> Self { + unsafe { ffi::AAudioStreamBuilder_setContentType(self.as_ptr(), content_type.into()) }; + self + } + + /// Request that AAudio call the `data_callback` when the stream is running. + /// + /// Note that when using data callback, the audio data will be passed in or out + /// of the function as an argument. + /// So you cannot call [`AudioStream::write()`] or [`AudioStream::read()`] + /// on the same stream that has an active data callback. + /// + /// The data callback function will start being called after [`AudioStream::request_start()`] + /// is called. + /// It will stop being called after [`AudioStream::request_pause()`] or + /// [`AudioStream::request_stop()`] is called. + /// + /// The `data_callback` function will be called on a real-time thread owned by AAudio. + /// Note that numFrames can vary unless [`AudioStreamBuilder::frames_per_data_callback()`] + /// is called. + /// + /// Also note that this callback function should be considered a "real-time" function. + /// It must not do anything that could cause an unbounded delay because that can cause the + /// audio to glitch or pop. + /// + /// These are things the function should NOT do: + /// * allocate memory using, for example, `malloc()` or new + /// * any file operations such as opening, closing, reading or writing + /// * any network operations such as streaming + /// * use any mutexes or other synchronization primitives + /// * sleep + /// * stop or close the stream + /// * [`AudioStream::read()`] + /// * [`AudioStream::write()`] + /// + /// If you need to move data, eg. MIDI commands, in or out of the callback function then + /// we recommend the use of non-blocking techniques such as an atomic FIFO. + /// + /// Note that the AAudio callbacks will never be called simultaneously from multiple threads. + #[doc(alias = "AAudioStreamBuilder_setDataCallback")] + pub fn data_callback(mut self, callback: AudioStreamDataCallback) -> Self { + let mut boxed = Box::new(callback); + let ptr: *mut AudioStreamDataCallback = &mut *boxed; + + unsafe extern "C" fn ffi_callback( + stream: *mut ffi::AAudioStreamStruct, + user_data: *mut c_void, + audio_data: *mut c_void, + num_frames: i32, + ) -> ffi::aaudio_data_callback_result_t { + abort_on_panic(|| { + let callback = user_data as *mut AudioStreamDataCallback; + let stream = AudioStream { + inner: NonNull::new_unchecked(stream), + data_callback: None, + error_callback: None, + }; + let result = (*callback)(&stream, audio_data, num_frames); + std::mem::forget(stream); + result.into() + }) + } + + unsafe { + ffi::AAudioStreamBuilder_setDataCallback( + self.as_ptr(), + Some(ffi_callback), + ptr as *mut c_void, + ) + }; + + self.data_callback = Some(boxed); + + self + } + + /// Request an audio device identified device using an ID. + /// On Android, for example, the ID could be obtained from the Java AudioManager. + /// + /// The default, if you do not call this function, is 0, + /// in which case the primary device will be used. + /// + /// # Parameters + /// + /// - `device_id`: device identifier or 0 for unspecified + #[doc(alias = "AAudioStreamBuilder_setDeviceId")] + pub fn device_id(self, device_id: i32) -> Self { + unsafe { ffi::AAudioStreamBuilder_setDeviceId(self.as_ptr(), device_id) }; + self + } + + /// Request the direction for a stream. + /// + /// The default, if you do not call this function, is [`Output`][AudioDirection::Output]. + /// + /// # Parameters + /// + /// - `direction`: [`Output`][AudioDirection::Output] or [`Input`][AudioDirection::Input] + #[doc(alias = "AAudioStreamBuilder_setDirection")] + pub fn direction(self, direction: AudioDirection) -> Self { + unsafe { ffi::AAudioStreamBuilder_setDirection(self.as_ptr(), direction.into()) }; + self + } + + /// Request that AAudio call the `data_callback` when the stream is running and the + /// `error_callback` if any error occurs or the stream is disconnected. + /// + /// The `error_callback` will be called, for example, if a headset or a USB device is unplugged causing the stream's + /// device to be unavailable or "disconnected". + /// Another possible cause of error would be a timeout or an unanticipated internal error. + /// + /// In response, this function should signal or create another thread to stop + /// and close this stream. The other thread could then reopen a stream on another device. + /// Do not stop or close the stream, or reopen the new stream, directly from this callback. + /// + /// The `error_callback` will not be called because of actions by the application, such as stopping + /// or closing a stream. + /// + /// Note that the AAudio callbacks will never be called simultaneously from multiple threads. + #[doc(alias = "AAudioStreamBuilder_setErrorCallback")] + pub fn error_callback(mut self, callback: AudioStreamErrorCallback) -> Self { + let mut boxed = Box::new(callback); + let ptr: *mut AudioStreamErrorCallback = &mut *boxed; + + unsafe extern "C" fn ffi_callback( + stream: *mut ffi::AAudioStreamStruct, + user_data: *mut c_void, + error: ffi::aaudio_result_t, + ) { + abort_on_panic(|| { + let callback = user_data as *mut AudioStreamErrorCallback; + let stream = AudioStream { + inner: NonNull::new_unchecked(stream), + data_callback: None, + error_callback: None, + }; + let err = AudioError::from_result(error).unwrap_err(); + (*callback)(&stream, err); + std::mem::forget(stream); + }) + } + + unsafe { + ffi::AAudioStreamBuilder_setErrorCallback( + self.as_ptr(), + Some(ffi_callback), + ptr as *mut c_void, + ) + }; + + self.error_callback = Some(boxed); + + self + } + + /// Request a sample data format, for example `Format::I16`. + /// + /// The default, if you do not call this function, is [`Unspecified`][AudioFormat::Unspecified]. + /// An optimal value will then be chosen when the stream is opened. + /// After opening a stream with an unspecified value, the application must + /// query for the actual value, which may vary by device. + /// + /// If an exact value is specified then an opened stream will use that value. + /// If a stream cannot be opened with the specified value then the open will fail. + /// + /// # Parameters + /// + /// - `format`: the sample data format. + #[doc(alias = "AAudioStreamBuilder_setFormat")] + pub fn format(self, format: AudioFormat) -> Self { + unsafe { ffi::AAudioStreamBuilder_setFormat(self.as_ptr(), format.into()) }; + self + } + + /// Set the requested data callback buffer size in frames. + /// + /// See [`AudioStreamDataCallback`]. + /// + /// The default, if you do not call this function, is unspecified. + /// + /// For the lowest possible latency, do not call this function. AAudio will then + /// call the [`data_callback`][Self::data_callback] function with whatever size is optimal. + /// That size may vary from one callback to another. + /// + /// Only use this function if the application requires a specific number of frames for processing. + /// The application might, for example, be using an FFT that requires + /// a specific power-of-two sized buffer. + /// + /// AAudio may need to add additional buffering in order to adapt between the internal + /// buffer size and the requested buffer size. + /// + /// If you do call this function then the requested size should be less than + /// half the buffer capacity, to allow double buffering. + /// + /// - `num_frames`: the desired buffer size in frames or 0 for unspecified + #[doc(alias = "AAudioStreamBuilder_setFramesPerDataCallback")] + pub fn frames_per_data_callback(self, num_frames: i32) -> Self { + unsafe { ffi::AAudioStreamBuilder_setFramesPerDataCallback(self.as_ptr(), num_frames) }; + self + } + + /// Set the input (capture) preset for the stream. + /// + /// The AAudio system will use this information to optimize the + /// behavior of the stream. + /// This could, for example, affect which microphones are used and how the + /// recorded data is processed. + /// + /// The default, if you do not call this function, is [`VoiceRecognition`][AudioInputPreset::VoiceRecognition] + /// which is the preset with the lowest latency on many platforms. + /// + /// # Parameters + /// + /// - `input_preset`: the desired configuration for recording + #[cfg(feature = "api-level-28")] + #[doc(alias = "AAudioStreamBuilder_setInputPreset")] + pub fn input_preset(self, input_preset: AudioInputPreset) -> Self { + unsafe { ffi::AAudioStreamBuilder_setInputPreset(self.as_ptr(), input_preset.into()) }; + self + } + + /// Set the requested performance mode. + /// + /// Supported modes are None, PowerSaving and LowLatency. + /// + /// The default, if you do not call this function, is None. + /// + /// You may not get the mode you requested. + /// You can call [`AudioStream::performance_mode()`] + /// to find out the final mode for the stream. + /// + /// # Parameters + /// + /// - `mode`: the desired performance mode, eg. [`AudioPerformanceMode::LowLatency`] + #[doc(alias = "AAudioStreamBuilder_setPerformanceMode")] + pub fn performance_mode(self, mode: AudioPerformanceMode) -> Self { + unsafe { ffi::AAudioStreamBuilder_setPerformanceMode(self.as_ptr(), mode.into()) }; + self + } + + /// Request a sample rate in Hertz. + /// + /// The default, if you do not call this function, is 0 (unspecified). + /// An optimal value will then be chosen when the stream is opened. + /// After opening a stream with an unspecified value, the application must + /// query for the actual value, which may vary by device. + /// + /// If an exact value is specified then an opened stream will use that value. + /// If a stream cannot be opened with the specified value then the open will fail. + /// + /// # Parameters + /// + /// - `sample_rate`: frames per second. Common rates include 44100 and 48000 Hz. + #[doc(alias = "AAudioStreamBuilder_setSampleRate")] + pub fn sample_rate(self, sample_rate: i32) -> Self { + unsafe { ffi::AAudioStreamBuilder_setSampleRate(self.as_ptr(), sample_rate) }; + self + } + + #[doc(alias = "AAudioStreamBuilder_setSamplesPerFrame")] + pub fn samples_per_frame(self, samples_per_frame: i32) -> Self { + unsafe { ffi::AAudioStreamBuilder_setSamplesPerFrame(self.as_ptr(), samples_per_frame) }; + self + } + + /// The session ID can be used to associate a stream with effects processors. + /// The effects are controlled using the Android AudioEffect Java API. + /// + /// The default, if you do not call this function, is -1 (none). + /// + /// If set to [`Option::None`] then a session ID will be allocated when the stream is opened. + /// + /// The allocated session ID can be obtained by calling [`AudioStream::session_id()`] + /// and then used with this function when opening another stream. + /// This allows effects to be shared between streams. + /// + /// Session IDs from AAudio can be used with the Android Java APIs and vice versa. + /// So a session ID from an AAudio stream can be passed to Java + /// and effects applied using the Java AudioEffect API. + /// + /// Note that allocating or setting a session ID may result in a stream with higher latency. + /// + /// Allocated session IDs will always be positive and nonzero. + /// + /// # Parameters + /// + /// - `session_id`: an allocated sessionID or [`Option::None`] to allocate a new sessionID + #[cfg(feature = "api-level-28")] + #[doc(alias = "AAudioStreamBuilder_setSessionId")] + pub fn session_id(self, session_id_or_allocate: Option) -> Self { + let session_id = match session_id_or_allocate { + None => ffi::AAUDIO_SESSION_ID_ALLOCATE, + Some(SessionId::None) => ffi::AAUDIO_SESSION_ID_NONE, + Some(SessionId::Allocated(value)) => value.get(), + }; + + unsafe { ffi::AAudioStreamBuilder_setSessionId(self.as_ptr(), session_id) }; + self + } + + /// Request a mode for sharing the device. + /// + /// The default, if you do not call this function, is [`AudioSharingMode::Shared`]. + /// + /// The requested sharing mode may not be available. + /// The application can query for the actual mode after the stream is opened. + /// + /// # Parameters + /// + /// - `sharing_mode`: [`AudioSharingMode::Shared`] or [`AudioSharingMode::Exclusive`] + #[doc(alias = "AAudioStreamBuilder_setSharingMode")] + pub fn sharing_mode(self, sharing_mode: AudioSharingMode) -> Self { + unsafe { ffi::AAudioStreamBuilder_setSharingMode(self.as_ptr(), sharing_mode.into()) }; + self + } + + /// Set the intended use case for the stream. + /// + /// The AAudio system will use this information to optimize the + /// behavior of the stream. + /// This could, for example, affect how volume and focus is handled for the stream. + /// + /// The default, if you do not call this function, is [`AudioUsage::Media`]. + /// + /// - `usage`: the desired usage, eg. [`AudioUsage::Game`] + #[cfg(feature = "api-level-28")] + #[doc(alias = "AAudioStreamBuilder_setUsage")] + pub fn usage(self, usage: AudioUsage) -> Self { + unsafe { ffi::AAudioStreamBuilder_setUsage(self.as_ptr(), usage.into()) }; + self + } + + /// Open a stream based on the options in the AAudioStreamBuilder. + #[doc(alias = "AAudioStreamBuilder_openStream")] + pub fn open_stream(mut self) -> Result { + unsafe { + let ptr = construct(|res| ffi::AAudioStreamBuilder_openStream(self.as_ptr(), res))?; + + Ok(AudioStream { + inner: NonNull::new_unchecked(ptr), + data_callback: self.data_callback.take(), + error_callback: self.error_callback.take(), + }) + } + } +} + +impl Drop for AudioStreamBuilder { + #[doc(alias = "AAudioStreamBuilder_delete")] + fn drop(&mut self) { + let status = unsafe { ffi::AAudioStreamBuilder_delete(self.as_ptr()) }; + AudioError::from_result(status).unwrap(); + } +} + +/// A native [`AAudioStream *`] +/// +/// [`AAudioStream *`]: https://developer.android.com/ndk/reference/group/audio#aaudiostream +#[doc(alias = "AAudioStream")] +pub struct AudioStream { + inner: NonNull, + data_callback: Option, + error_callback: Option, +} + +impl fmt::Debug for AudioStream { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("AAudioStream") + .field("inner", &self.inner) + .field( + "data_callback", + match &self.data_callback { + Some(_) => &"Some(_)", + None => &"None", + }, + ) + .field( + "error_callback", + match &self.error_callback { + Some(_) => &"Some(_)", + None => &"None", + }, + ) + .finish() + } +} + +impl AudioStream { + fn as_ptr(&self) -> *mut ffi::AAudioStream { + self.inner.as_ptr() + } + + /// Returns the policy that determines whether the audio may or + /// may not be captured by other apps or the system. + #[cfg(feature = "api-level-29")] + #[doc(alias = "AAudioStream_getAllowedCapturePolicy")] + pub fn allowed_capture_policy(self) -> AudioAllowedCapturePolicy { + unsafe { ffi::AAudioStream_getAllowedCapturePolicy(self.as_ptr()) }.into() + } + + /// Query maximum buffer capacity in frames. + #[doc(alias = "AAudioStream_getBufferCapacityInFrames")] + pub fn buffer_capacity_in_frames(&self) -> i32 { + unsafe { ffi::AAudioStream_getBufferCapacityInFrames(self.as_ptr()) } + } + + /// Query the maximum number of frames that can be filled without blocking. + #[doc(alias = "AAudioStream_getBufferSizeInFrames")] + pub fn buffer_size_in_frames(&self) -> i32 { + unsafe { ffi::AAudioStream_getBufferSizeInFrames(self.as_ptr()) } + } + + /// A stream has one or more channels of data. + /// A frame will contain one sample for each channel. + #[doc(alias = "AAudioStream_getChannelCount")] + pub fn channel_count(&self) -> i32 { + unsafe { ffi::AAudioStream_getChannelCount(self.as_ptr()) } + } + + #[cfg(feature = "api-level-28")] + #[doc(alias = "AAudioStream_getContentType")] + pub fn content_type(&self) -> AudioContentType { + unsafe { ffi::AAudioStream_getContentType(self.as_ptr()) }.into() + } + + /// Returns the actual device ID. + #[doc(alias = "AAudioStream_getDeviceId")] + pub fn device_id(&self) -> i32 { + unsafe { ffi::AAudioStream_getDeviceId(self.as_ptr()) } + } + + /// Available since API level 26. + #[doc(alias = "AAudioStream_getDirection")] + pub fn direction(&self) -> AudioDirection { + unsafe { ffi::AAudioStream_getDirection(self.as_ptr()) }.into() + } + + /// Returns the actual data format. + #[doc(alias = "AAudioStream_getFormat")] + pub fn format(&self) -> AudioFormat { + unsafe { ffi::AAudioStream_getFormat(self.as_ptr()) }.into() + } + + /// Query the number of frames that the application should read or write at + /// one time for optimal performance. It is OK if an application writes + /// a different number of frames. But the buffer size may need to be larger + /// in order to avoid underruns or overruns. + /// + /// Note that this may or may not match the actual device burst size. + /// For some endpoints, the burst size can vary dynamically. + /// But these tend to be devices with high latency. + #[doc(alias = "AAudioStream_getFramesPerBurst")] + pub fn frames_per_burst(&self) -> i32 { + unsafe { ffi::AAudioStream_getFramesPerBurst(self.as_ptr()) } + } + + /// Query the size of the buffer that will be passed to the data callback in the `numFrames` parameter. + /// This call can be used if the application needs to know the value of numFrames before + /// the stream is started. This is not normally necessary. + /// + /// If a specific size was requested by calling + /// [`AudioStreamBuilder::frames_per_data_callback()`] then this will be the same size. + /// + /// If [`AudioStreamBuilder::frames_per_data_callback()`] was not called then this will + /// return the size chosen by AAudio, or 0. + /// + /// `None` indicates that the callback buffer size for this stream may vary from one dataProc callback to the next. + #[doc(alias = "AAudioStream_getFramesPerDataCallback")] + pub fn frames_per_data_callback(&self) -> Option { + let value = unsafe { ffi::AAudioStream_getFramesPerDataCallback(self.as_ptr()) }; + const AAUDIO_UNSPECIFIED: i32 = ffi::AAUDIO_UNSPECIFIED as i32; + match value { + AAUDIO_UNSPECIFIED => None, + val => Some(val), + } + } + + /// Returns the number of frames that have been read since the stream was created. + /// For an output stream, this will be advanced by the endpoint. + /// For an input stream, this will be advanced by the application calling [`read()`][Self::read] + /// or by a data callback. + /// + /// The frame position is monotonically increasing. + #[doc(alias = "AAudioStream_getFramesRead")] + pub fn frames_read(&self) -> i64 { + unsafe { ffi::AAudioStream_getFramesRead(self.as_ptr()) } + } + + /// Returns the number of frames that have been written since the stream was created. + /// For an output stream, this will be advanced by the application calling write() + /// or by a data callback. + /// For an input stream, this will be advanced by the endpoint. + /// + /// The frame position is monotonically increasing. + #[doc(alias = "AAudioStream_getFramesWritten")] + pub fn frames_written(&self) -> i64 { + unsafe { ffi::AAudioStream_getFramesWritten(self.as_ptr()) } + } + + #[cfg(feature = "api-level-28")] + #[doc(alias = "AAudioStream_getInputPreset")] + pub fn input_preset(&self) -> AudioInputPreset { + unsafe { ffi::AAudioStream_getInputPreset(self.as_ptr()) }.into() + } + + /// Get the performance mode used by the stream. + #[doc(alias = "AAudioStream_getPerformanceMode")] + pub fn performance_mode(&self) -> AudioPerformanceMode { + unsafe { ffi::AAudioStream_getPerformanceMode(self.as_ptr()) }.into() + } + + /// Returns the actual sample rate. + #[doc(alias = "AAudioStream_getSampleRate")] + pub fn sample_rate(&self) -> i32 { + unsafe { ffi::AAudioStream_getSampleRate(self.as_ptr()) } + } + + #[doc(alias = "AAudioStream_getSamplesPerFrame")] + pub fn samples_per_frame(&self) -> i32 { + unsafe { ffi::AAudioStream_getSamplesPerFrame(self.as_ptr()) } + } + + /// Passes back the session ID associated with this stream. + /// + /// The session ID can be used to associate a stream with effects processors. + /// The effects are controlled using the Android AudioEffect Java API. + /// + /// If [`AudioStreamBuilder::session_id()`] was called with `0` + /// then a new session ID should be allocated once when the stream is opened. + /// + /// If [`AudioStreamBuilder::session_id()`] was called with a previously allocated + /// session ID then that value should be returned. + /// + /// If [`AudioStreamBuilder::session_id()`] was not called then this function should + /// return `-1`. + /// + /// The sessionID for a stream should not change once the stream has been opened. + #[cfg(feature = "api-level-28")] + #[doc(alias = "AAudioStream_getSessionId")] + pub fn session_id(&self) -> SessionId { + let value = unsafe { ffi::AAudioStream_getSessionId(self.as_ptr()) }; + match value { + ffi::AAUDIO_SESSION_ID_NONE => SessionId::None, + allocated => SessionId::Allocated(NonZeroI32::new(allocated).unwrap()), + } + } + + /// Provide actual sharing mode. + #[doc(alias = "AAudioStream_getSharingMode")] + pub fn sharing_mode(&self) -> AudioSharingMode { + unsafe { ffi::AAudioStream_getSharingMode(self.as_ptr()) }.into() + } + + /// Query the current state of the client, eg. [`Pausing`][AudioStreamState::Pausing]. + /// + /// This function will immediately return the state without updating the state. + /// If you want to update the client state based on the server state then + /// call [`AudioStream::wait_for_state_change()`] with currentState + /// set to [`Unknown`][AudioStreamState::Unknown] and a zero timeout. + #[doc(alias = "AAudioStream_getState")] + pub fn state(&self) -> AudioStreamState { + unsafe { ffi::AAudioStream_getState(self.as_ptr()) }.into() + } + + /// Returns the time at which a particular frame was presented. + /// This can be used to synchronize audio with video or MIDI. + /// It can also be used to align a recorded stream with a playback stream. + /// + /// Timestamps are only valid when the stream is in `Started` state. + /// [`InvalidState`][AudioError::InvalidState] will be returned + /// if the stream is not started. + /// Note that because [`AudioStream::request_start()`] is asynchronous, + /// timestamps will not be valid until a short time after calling + /// [`AudioStream::request_start()`]. + /// So [`InvalidState`][AudioError::InvalidState] should not be + /// considered a fatal error. + /// Just try calling again later. + /// + /// If an error occurs, then the position and time will not be modified. + /// + /// The position and time passed back are monotonically increasing. + #[doc(alias = "AAudioStream_getTimestamp")] + pub fn timestamp(&self, clockid: Clockid) -> Result { + let frame_position; + let time_nanoseconds = unsafe { + let mut nanoseconds = MaybeUninit::uninit(); + frame_position = construct(|ptr| { + ffi::AAudioStream_getTimestamp( + self.as_ptr(), + clockid as ffi::clockid_t, + ptr, + nanoseconds.as_mut_ptr(), + ) + })?; + nanoseconds.assume_init() + }; + + Ok(Timestamp { + frame_position, + time_nanoseconds, + }) + } + + #[cfg(feature = "api-level-28")] + #[doc(alias = "AAudioStream_getUsage")] + pub fn usage(&self) -> AudioUsage { + unsafe { ffi::AAudioStream_getUsage(self.as_ptr()) }.into() + } + + /// An XRun is an Underrun or an Overrun. + /// During playing, an underrun will occur if the stream is not written in time + /// and the system runs out of valid data. + /// During recording, an overrun will occur if the stream is not read in time + /// and there is no place to put the incoming data so it is discarded. + /// + /// An underrun or overrun can cause an audible "pop" or "glitch". + /// + /// Note that some INPUT devices may not support this function. + /// In that case a 0 will always be returned. + #[doc(alias = "AAudioStream_getXRunCount")] + pub fn x_run_count(&self) -> i32 { + unsafe { ffi::AAudioStream_getXRunCount(self.as_ptr()) } + } + + /// Read data from the stream. + /// Returns the number of frames actually read or a negative error. + /// + /// The call will wait until the read is complete or until it runs out of time. + /// If timeoutNanos is zero then this call will not wait. + /// + /// Note that timeoutNanoseconds is a relative duration in wall clock time. + /// Time will not stop if the thread is asleep. + /// So it will be implemented using CLOCK_BOOTTIME. + /// + /// This call is "strong non-blocking" unless it has to wait for data. + /// + /// If the call times out then zero or a partial frame count will be returned. + /// + /// # Parameters + /// + /// - `buffer`: The slice with the samples. + /// - `num_frames`: Number of frames to read. Only complete frames will be written. + /// - `timeout_nanoseconds`: Maximum number of nanoseconds to wait for completion. + /// + /// # Safety + /// `buffer` must be a valid pointer to at least `num_frames` samples. + #[doc(alias = "AAudioStream_read")] + pub unsafe fn read( + &self, + buffer: *mut c_void, + num_frames: i32, + timeout_nanoseconds: i64, + ) -> Result { + let result = ffi::AAudioStream_read(self.as_ptr(), buffer, num_frames, timeout_nanoseconds); + + AudioError::from_result(result).map(|()| result as u32) + } + + /// Asynchronous request for the stream to flush. + /// Flushing will discard any pending data. + /// This call only works if the stream is pausing or paused. + /// Frame counters are not reset by a flush. They may be advanced. + /// After this call the state will be in [`Flushing`][AudioStreamState::Flushing] or + /// [`Flushed`][AudioStreamState::Flushed]. + /// + /// This will return [`Unimplemented`][AudioError::Unimplemented] for input streams. + #[doc(alias = "AAudioStream_requestFlush")] + pub fn request_flush(&self) -> Result<()> { + let result = unsafe { ffi::AAudioStream_requestFlush(self.as_ptr()) }; + AudioError::from_result(result) + } + + /// Asynchronous request for the stream to pause. + /// Pausing a stream will freeze the data flow but not flush any buffers. + /// Use [`AudioStream::request_start()`] to resume playback after a pause. + /// After this call the state will be in [`Pausing`][AudioStreamState::Pausing] or + /// [`Paused`][AudioStreamState::Paused]. + /// + /// This will return [`Unimplemented`][AudioError::Unimplemented] for input streams. + /// For input streams use [`AudioStream::request_stop()`]. + #[doc(alias = "AAudioStream_requestPause")] + pub fn request_pause(&self) -> Result<()> { + let result = unsafe { ffi::AAudioStream_requestPause(self.as_ptr()) }; + AudioError::from_result(result) + } + + /// Asynchronously request to start playing the stream. For output streams, one should + /// write to the stream to fill the buffer before starting. + /// Otherwise it will underflow. + /// After this call the state will be in [`Starting`][AudioStreamState::Starting] or + /// [`Started`][AudioStreamState::Started]. + #[doc(alias = "AAudioStream_requestStart")] + pub fn request_start(&self) -> Result<()> { + let result = unsafe { ffi::AAudioStream_requestStart(self.as_ptr()) }; + AudioError::from_result(result) + } + + /// Asynchronous request for the stream to stop. + /// The stream will stop after all of the data currently buffered has been played. + /// After this call the state will be in [`Stopping`][AudioStreamState::Stopping] or + /// [`Stopped`][AudioStreamState::Stopped]. + #[doc(alias = "AAudioStream_requestStop")] + pub fn request_stop(&self) -> Result<()> { + let result = unsafe { ffi::AAudioStream_requestStop(self.as_ptr()) }; + AudioError::from_result(result) + } + + /// This can be used to adjust the latency of the buffer by changing + /// the threshold where blocking will occur. + /// By combining this with [`AudioStream::x_run_count()`], the latency can be tuned + /// at run-time for each device. + /// Returns actual buffer size in frames or a negative error. + /// + /// This cannot be set higher than [`AudioStream::buffer_capacity_in_frames()`]. + /// + /// Note that you will probably not get the exact size you request. + /// You can check the return value or call [`AudioStream::buffer_size_in_frames()`] + /// to see what the actual final size is. + /// + /// # Parameters + /// + /// - `num_frames`: requested number of frames that can be filled without blocking + #[doc(alias = "AAudioStream_setBufferSizeInFrames")] + pub fn set_buffer_size_in_frames(&self, num_frames: i32) -> Result { + let result = unsafe { ffi::AAudioStream_setBufferSizeInFrames(self.as_ptr(), num_frames) }; + AudioError::from_result(result).map(|()| result) + } + + /// Wait until the current state no longer matches the input state. + /// + /// This will update the current client state. + /// + /// Returns the new state. + #[doc(alias = "AAudioStream_waitForStateChange")] + pub fn wait_for_state_change( + &self, + input_state: AudioStreamState, + timeout_nanoseconds: i64, + ) -> Result { + let value = construct(|ptr| unsafe { + ffi::AAudioStream_waitForStateChange( + self.as_ptr(), + input_state.into(), + ptr, + timeout_nanoseconds, + ) + })?; + Ok(value.into()) + } + + /// Write data to the stream. + /// Returns the number of frames actually written or a negative error. + /// + /// The call will wait until the write is complete or until it runs out of time. + /// If `timeout_nanoseconds` is zero then this call will not wait. + /// + /// Note that `timeout_nanoseconds` is a relative duration in wall clock time. + /// Time will not stop if the thread is asleep. + /// So it will be implemented using `CLOCK_BOOTTIME`. + /// + /// This call is "strong non-blocking" unless it has to wait for room in the buffer. + /// + /// If the call times out then zero or a partial frame count will be returned. + /// + /// # Parameters + /// + /// - `buffer`: The address of the first sample. + /// - `num_frames`: Number of frames to write. Only complete frames will be written. + /// - `timeout_nanoseconds`: Maximum number of nanoseconds to wait for completion. + /// + /// # Safety + /// `buffer` must be a valid pointer to at least `num_frames` samples. + #[doc(alias = "AAudioStream_write")] + pub unsafe fn write( + &self, + buffer: *const c_void, + num_frames: i32, + timeout_nanoseconds: i64, + ) -> Result { + let result = + ffi::AAudioStream_write(self.as_ptr(), buffer, num_frames, timeout_nanoseconds); + + AudioError::from_result(result).map(|()| result as u32) + } +} + +impl Drop for AudioStream { + #[doc(alias = "AAudioStream_close")] + fn drop(&mut self) { + let status = unsafe { ffi::AAudioStream_close(self.as_ptr()) }; + AudioError::from_result(status).unwrap(); + } +} diff --git a/clients/android/native/vendor/ndk/src/bitmap.rs b/clients/android/native/vendor/ndk/src/bitmap.rs new file mode 100644 index 00000000..be3687b0 --- /dev/null +++ b/clients/android/native/vendor/ndk/src/bitmap.rs @@ -0,0 +1,489 @@ +//! Bindings for [`AndroidBitmap`] functions +//! +//! These functions operate directly on a JNI [`android.graphics.Bitmap`] instance. +//! +//! [`AndroidBitmap`]: https://developer.android.com/ndk/reference/group/bitmap +//! [`android.graphics.Bitmap`]: https://developer.android.com/reference/android/graphics/Bitmap +#![cfg(feature = "bitmap")] + +use jni_sys::{jobject, JNIEnv}; +use num_enum::{FromPrimitive, IntoPrimitive}; +use std::{error, fmt, mem::MaybeUninit}; + +#[cfg(feature = "api-level-30")] +use crate::data_space::DataSpace; +#[cfg(feature = "api-level-30")] +use crate::hardware_buffer::HardwareBufferRef; + +#[repr(i32)] +#[derive(Copy, Clone, Debug, PartialEq, Eq, FromPrimitive, IntoPrimitive)] +#[non_exhaustive] +pub enum BitmapError { + #[doc(alias = "ANDROID_BITMAP_RESULT_ALLOCATION_FAILED")] + AllocationFailed = ffi::ANDROID_BITMAP_RESULT_ALLOCATION_FAILED, + #[doc(alias = "ANDROID_BITMAP_RESULT_BAD_PARAMETER")] + BadParameter = ffi::ANDROID_BITMAP_RESULT_BAD_PARAMETER, + #[doc(alias = "ANDROID_BITMAP_RESULT_JNI_EXCEPTION")] + JniException = ffi::ANDROID_BITMAP_RESULT_JNI_EXCEPTION, + + // Use the SUCCESS discriminant, as no-one will be able to call `as i32` and only has access to + // the constants via `From` provided by `IntoPrimitive` which reads the contained value. + // An autogenerated ` + 1` discriminant is normally fine, except that the + // previous variant is negative and `+ 1` would match the variant before that. + #[doc(hidden)] + #[num_enum(catch_all)] + __Unknown(i32) = ffi::ANDROID_BITMAP_RESULT_SUCCESS, +} + +impl fmt::Display for BitmapError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{:?}", self) + } +} + +impl error::Error for BitmapError {} + +pub type Result = std::result::Result; + +impl BitmapError { + pub(crate) fn from_status(status: i32) -> Result<()> { + match status { + ffi::ANDROID_BITMAP_RESULT_SUCCESS => Ok(()), + x => Err(Self::from(x)), + } + } +} + +fn construct(with_ptr: impl FnOnce(*mut T) -> i32) -> Result { + let mut result = MaybeUninit::uninit(); + let status = with_ptr(result.as_mut_ptr()); + BitmapError::from_status(status).map(|()| unsafe { result.assume_init() }) +} + +#[repr(i32)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, IntoPrimitive, FromPrimitive)] +#[allow(non_camel_case_types)] +#[doc(alias = "AndroidBitmapFormat")] +#[non_exhaustive] +pub enum BitmapFormat { + #[doc(alias = "ANDROID_BITMAP_FORMAT_NONE")] + NONE = ffi::AndroidBitmapFormat::ANDROID_BITMAP_FORMAT_NONE.0 as i32, + #[doc(alias = "ANDROID_BITMAP_FORMAT_RGBA_8888")] + RGBA_8888 = ffi::AndroidBitmapFormat::ANDROID_BITMAP_FORMAT_RGBA_8888.0 as i32, + #[doc(alias = "ANDROID_BITMAP_FORMAT_RGB_565")] + RGB_565 = ffi::AndroidBitmapFormat::ANDROID_BITMAP_FORMAT_RGB_565.0 as i32, + #[deprecated = "Deprecated in API level 13. Because of the poor quality of this configuration, it is advised to use ARGB_8888 instead."] + #[doc(alias = "ANDROID_BITMAP_FORMAT_RGBA_4444")] + RGBA_4444 = ffi::AndroidBitmapFormat::ANDROID_BITMAP_FORMAT_RGBA_4444.0 as i32, + #[doc(alias = "ANDROID_BITMAP_FORMAT_A_8")] + A_8 = ffi::AndroidBitmapFormat::ANDROID_BITMAP_FORMAT_A_8.0 as i32, + #[doc(alias = "ANDROID_BITMAP_FORMAT_RGBA_F16")] + RGBA_F16 = ffi::AndroidBitmapFormat::ANDROID_BITMAP_FORMAT_RGBA_F16.0 as i32, + #[doc(alias = "ANDROID_BITMAP_FORMAT_RGBA_1010102")] + RGBA_1010102 = ffi::AndroidBitmapFormat::ANDROID_BITMAP_FORMAT_RGBA_1010102.0 as i32, + + #[doc(hidden)] + #[num_enum(catch_all)] + __Unknown(i32), +} + +/// An immediate wrapper over [`android.graphics.Bitmap`] +/// +/// [`android.graphics.Bitmap`]: https://developer.android.com/reference/android/graphics/Bitmap +#[derive(Debug)] +pub struct Bitmap { + env: *mut JNIEnv, + inner: jobject, +} + +impl Bitmap { + /// Create a [`Bitmap`] wrapper from JNI pointers + /// + /// # Safety + /// This function should be called with a healthy JVM pointer and with a non-null + /// [`android.graphics.Bitmap`], which must be kept alive on the Java/Kotlin side. + /// + /// [`android.graphics.Bitmap`]: https://developer.android.com/reference/android/graphics/Bitmap + pub unsafe fn from_jni(env: *mut JNIEnv, bitmap: jobject) -> Self { + Self { env, inner: bitmap } + } + + /// Fills out and returns the [`BitmapInfo`] struct for the given Java bitmap object. + #[doc(alias = "AndroidBitmap_getInfo")] + pub fn info(&self) -> Result { + let inner = + construct(|res| unsafe { ffi::AndroidBitmap_getInfo(self.env, self.inner, res) })?; + + Ok(BitmapInfo { inner }) + } + + /// Returns the [`DataSpace`] of this [`Bitmap`]. + /// + /// Note that [`DataSpace`] only exposes a few values. This may return [`DataSpace::Unknown`], + /// even for Named ColorSpaces, if they have no corresponding [`DataSpace`]. + #[cfg(feature = "api-level-30")] + #[doc(alias = "AndroidBitmap_getDataSpace")] + pub fn data_space(&self) -> DataSpace { + let value = unsafe { ffi::AndroidBitmap_getDataSpace(self.env, self.inner) }; + value.into() + } + + /// Attempt to lock the pixel address. + /// + /// Locking will ensure that the memory for the pixels will not move until the + /// [`Bitmap::unlock_pixels()`] call, and ensure that, if the pixels had been previously purged, + /// they will have been restored. + /// + /// If this call succeeds, it must be balanced by a call to [`Bitmap::unlock_pixels()`], after + /// which time the address of the pixels should no longer be used. + #[doc(alias = "AndroidBitmap_lockPixels")] + pub fn lock_pixels(&self) -> Result<*mut std::os::raw::c_void> { + construct(|res| unsafe { ffi::AndroidBitmap_lockPixels(self.env, self.inner, res) }) + } + + /// Call this to balance a successful call to [`Bitmap::lock_pixels()`]. + #[doc(alias = "AndroidBitmap_unlockPixels")] + pub fn unlock_pixels(&self) -> Result<()> { + let status = unsafe { ffi::AndroidBitmap_unlockPixels(self.env, self.inner) }; + BitmapError::from_status(status) + } + + /// Retrieve the native object associated with an [`ffi::ANDROID_BITMAP_FLAGS_IS_HARDWARE`] + /// [`Bitmap`] (requires [`BitmapInfoFlags::is_hardware()`] on [`BitmapInfo::flags()`] to return + /// [`true`]). + /// + /// Client must not modify it while a [`Bitmap`] is wrapping it. + #[cfg(feature = "api-level-30")] + #[doc(alias = "AndroidBitmap_getHardwareBuffer")] + pub fn hardware_buffer(&self) -> Result { + unsafe { + let result = + construct(|res| ffi::AndroidBitmap_getHardwareBuffer(self.env, self.inner, res))?; + let non_null = if cfg!(debug_assertions) { + std::ptr::NonNull::new(result).expect("result should never be null") + } else { + std::ptr::NonNull::new_unchecked(result) + }; + Ok(HardwareBufferRef::from_ptr(non_null)) + } + } + + /// [Lock] the pixels in `self` and compress them as described by [`info()`]. + /// + /// Unlike [`compress_raw()`] this requires a [`Bitmap`] object (as `self`) backed by a + /// [`jobject`]. + /// + /// # Parameters + /// - `format`: [`BitmapCompressFormat`] to compress to. + /// - `quality`: Hint to the compressor, `0-100`. The value is interpreted differently + /// depending on [`BitmapCompressFormat`]. + /// - `compress_callback`: Closure that writes the compressed data. Will be called on the + /// current thread, each time the compressor has compressed more data that is ready to be + /// written. May be called more than once for each call to this method. + /// + /// [Lock]: Self::lock_pixels() + /// [`info()`]: Self::info() + /// [`compress_raw()`]: Self::compress_raw() + #[cfg(feature = "api-level-30")] + #[doc(alias = "AndroidBitmap_compress")] + pub fn compress Result<(), ()>>( + &self, + format: BitmapCompressFormat, + quality: i32, + compress_callback: F, + ) -> Result<(), BitmapCompressError> { + let info = self.info()?; + let data_space = self.data_space(); + let pixels = self.lock_pixels()?; + // SAFETY: When lock_pixels() succeeds, assume it returns a valid pointer that stays + // valid until we call unlock_pixels(). + let result = unsafe { + Self::compress_raw( + &info, + data_space, + pixels, + format, + quality, + compress_callback, + ) + }; + self.unlock_pixels()?; + result + } + + /// Compress `pixels` as described by `info`. + /// + /// Unlike [`compress()`] this takes a raw pointer to pixels and does not need a [`Bitmap`] + /// object backed by a [`jobject`]. + /// + /// # Parameters + /// - `info`: Description of the pixels to compress. + /// - `data_space`: [`DataSpace`] describing the color space of the pixels. Should _not_ be + /// [`DataSpace::Unknown`] [^1]. + /// - `pixels`: Pointer to pixels to compress. + /// - `format`: [`BitmapCompressFormat`] to compress to. + /// - `quality`: Hint to the compressor, `0-100`. The value is interpreted differently + /// depending on [`BitmapCompressFormat`]. + /// - `compress_callback`: Closure that writes the compressed data. Will be called on the + /// current thread, each time the compressor has compressed more data that is ready to be + /// written. May be called more than once for each call to this method. + /// + /// # Safety + /// `pixels` must point to a valid buffer that matches the size, stride and format in `info`. + /// + /// [`compress()`]: Self::compress() + /// [^1]: + #[cfg(feature = "api-level-30")] + #[doc(alias = "AndroidBitmap_compress")] + pub unsafe fn compress_raw Result<(), ()>>( + info: &BitmapInfo, + data_space: DataSpace, + pixels: *const std::ffi::c_void, + format: BitmapCompressFormat, + quality: i32, + compress_callback: F, + ) -> Result<(), BitmapCompressError> { + if data_space == DataSpace::Unknown { + return Err(BitmapCompressError::DataSpaceUnknown); + } + + use std::{any::Any, ffi::c_void, panic::AssertUnwindSafe}; + struct CallbackState Result<(), ()>> { + callback: F, + panic: Option>, + } + let mut cb_state = CallbackState:: { + callback: compress_callback, + panic: None, + }; + + extern "C" fn compress_cb Result<(), ()>>( + context: *mut c_void, + data: *const c_void, + size: usize, + ) -> bool { + // SAFETY: This callback will only be called serially on a single thread. Both the + // panic state and the FnMut context need to be available mutably. + let cb_state = unsafe { context.cast::>().as_mut() }.unwrap(); + let data = unsafe { std::slice::from_raw_parts(data.cast(), size) }; + let panic = std::panic::catch_unwind(AssertUnwindSafe(|| (cb_state.callback)(data))); + match panic { + Ok(r) => r.is_ok(), + Err(e) => { + cb_state.panic = Some(e); + false + } + } + } + + let status = unsafe { + ffi::AndroidBitmap_compress( + &info.inner, + data_space.into(), + pixels, + format.into(), + quality, + <*mut _>::cast(&mut cb_state), + Some(compress_cb::), + ) + }; + + if let Some(panic) = cb_state.panic { + std::panic::resume_unwind(panic) + } + + Ok(BitmapError::from_status(status)?) + } +} + +/// Possible values for [`ffi::ANDROID_BITMAP_FLAGS_ALPHA_MASK`] within [`BitmapInfoFlags`] +#[repr(u32)] +#[cfg(feature = "api-level-30")] +#[derive(Clone, Copy, Debug, IntoPrimitive, FromPrimitive)] +#[doc(alias = "ANDROID_BITMAP_FLAGS_ALPHA_MASK")] +#[non_exhaustive] +pub enum BitmapInfoFlagsAlpha { + /// Pixel components are premultiplied by alpha. + #[doc(alias = "ANDROID_BITMAP_FLAGS_ALPHA_PREMUL")] + Premultiplied = ffi::ANDROID_BITMAP_FLAGS_ALPHA_PREMUL, + /// Pixels are opaque. + #[doc(alias = "ANDROID_BITMAP_FLAGS_ALPHA_OPAQUE")] + Opaque = ffi::ANDROID_BITMAP_FLAGS_ALPHA_OPAQUE, + /// Pixel components are independent of alpha. + #[doc(alias = "ANDROID_BITMAP_FLAGS_ALPHA_UNPREMUL")] + Unpremultiplied = ffi::ANDROID_BITMAP_FLAGS_ALPHA_UNPREMUL, + + #[doc(hidden)] + #[num_enum(catch_all)] + __Unknown(u32), +} + +/// Bitfield containing information about the bitmap. +#[cfg(feature = "api-level-30")] +#[repr(transparent)] +#[derive(Clone, Copy, Hash, PartialEq, Eq)] +pub struct BitmapInfoFlags(u32); + +#[cfg(feature = "api-level-30")] +impl std::fmt::Debug for BitmapInfoFlags { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "BitmapInfoFlags({:#x}, alpha: {:?}, is_hardware: {})", + self.0, + self.alpha(), + self.is_hardware() + ) + } +} + +#[cfg(feature = "api-level-30")] +impl BitmapInfoFlags { + /// Returns the alpha value contained in the [`ffi::ANDROID_BITMAP_FLAGS_ALPHA_MASK`] bit range + #[doc(alias = "ANDROID_BITMAP_FLAGS_ALPHA_MASK")] + pub fn alpha(self) -> BitmapInfoFlagsAlpha { + // Note that ffi::ANDROID_BITMAP_FLAGS_ALPHA_SHIFT is 0 and hence irrelevant. + (self.0 & ffi::ANDROID_BITMAP_FLAGS_ALPHA_MASK).into() + } + + /// Returns [`true`] when [`ffi::ANDROID_BITMAP_FLAGS_IS_HARDWARE`] is set, meaning this + /// [`Bitmap`] uses "HARDWARE Config" and its [`HardwareBufferRef`] can be retrieved via + /// [`Bitmap::hardware_buffer()`]. + #[doc(alias = "ANDROID_BITMAP_FLAGS_IS_HARDWARE")] + pub fn is_hardware(self) -> bool { + // This constant is defined in a separate anonymous enum which bindgen treats as i32. + (self.0 & ffi::ANDROID_BITMAP_FLAGS_IS_HARDWARE as u32) != 0 + } +} + +/// A native [`AndroidBitmapInfo`] +/// +/// [`AndroidBitmapInfo`]: https://developer.android.com/ndk/reference/struct/android-bitmap-info#struct_android_bitmap_info +#[derive(Clone, Copy)] +#[doc(alias = "AndroidBitmapInfo")] +pub struct BitmapInfo { + inner: ffi::AndroidBitmapInfo, +} + +impl std::fmt::Debug for BitmapInfo { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let mut f = f.debug_struct("BitmapInfo"); + f.field("width", &self.width()) + .field("height", &self.height()) + .field("stride", &self.stride()) + .field("format", &self.format()); + + #[cfg(feature = "api-level-30")] + f.field("flags", &self.flags()); + + f.finish() + } +} + +impl BitmapInfo { + pub fn new(width: u32, height: u32, stride: u32, format: BitmapFormat) -> Self { + Self { + inner: ffi::AndroidBitmapInfo { + width, + height, + stride, + format: format.into(), + flags: 0, + }, + } + } + + #[cfg(feature = "api-level-30")] + pub fn new_with_flags( + width: u32, + height: u32, + stride: u32, + format: BitmapFormat, + flags: BitmapInfoFlags, + ) -> Self { + Self { + inner: ffi::AndroidBitmapInfo { + flags: flags.0, + ..Self::new(width, height, stride, format).inner + }, + } + } + + /// The bitmap width in pixels. + pub fn width(&self) -> u32 { + self.inner.width + } + + /// The bitmap height in pixels. + pub fn height(&self) -> u32 { + self.inner.height + } + + /// The number of byte per row. + pub fn stride(&self) -> u32 { + self.inner.stride + } + + /// Convert the internal, native [`ffi::AndroidBitmapInfo::format`] into a [`BitmapFormat`]. + pub fn format(&self) -> BitmapFormat { + self.inner.format.into() + } + + /// Bitfield containing information about the bitmap. + #[cfg(feature = "api-level-30")] + pub fn flags(&self) -> BitmapInfoFlags { + BitmapInfoFlags(self.inner.flags) + } +} + +/// Specifies the formats that can be compressed to with [`Bitmap::compress()`] and +/// [`Bitmap::compress_raw()`]. +#[cfg(feature = "api-level-30")] +#[repr(i32)] +#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, FromPrimitive, IntoPrimitive)] +#[doc(alias = "AndroidBitmapCompressFormat")] +#[non_exhaustive] +pub enum BitmapCompressFormat { + /// Compress to the JPEG format. + /// + /// quality of `0` means compress for the smallest size. `100` means compress for max visual + /// quality. + #[doc(alias = "ANDROID_BITMAP_COMPRESS_FORMAT_JPEG")] + Jpeg = ffi::AndroidBitmapCompressFormat::ANDROID_BITMAP_COMPRESS_FORMAT_JPEG.0 as i32, + /// Compress to the PNG format. + /// + /// PNG is lossless, so quality is ignored. + #[doc(alias = "ANDROID_BITMAP_COMPRESS_FORMAT_PNG")] + Png = ffi::AndroidBitmapCompressFormat::ANDROID_BITMAP_COMPRESS_FORMAT_PNG.0 as i32, + /// Compress to the WEBP lossless format. + /// + /// quality refers to how much effort to put into compression. A value of `0` means to + /// compress quickly, resulting in a relatively large file size. `100` means to spend more time + /// compressing, resulting in a smaller file. + #[doc(alias = "ANDROID_BITMAP_COMPRESS_FORMAT_WEBP_LOSSY")] + WebPLossy = + ffi::AndroidBitmapCompressFormat::ANDROID_BITMAP_COMPRESS_FORMAT_WEBP_LOSSY.0 as i32, + /// Compress to the WEBP lossy format. + /// + /// quality of `0` means compress for the smallest size. `100` means compress for max visual quality. + #[doc(alias = "ANDROID_BITMAP_COMPRESS_FORMAT_WEBP_LOSSLESS")] + WebPLossless = + ffi::AndroidBitmapCompressFormat::ANDROID_BITMAP_COMPRESS_FORMAT_WEBP_LOSSLESS.0 as i32, + + #[doc(hidden)] + #[num_enum(catch_all)] + __Unknown(i32), +} + +/// Encapsulates possible errors returned by [`Bitmap::compress()`] or [`Bitmap::compress_raw()`]. +#[cfg(feature = "api-level-30")] +#[derive(Debug, thiserror::Error)] +pub enum BitmapCompressError { + #[error(transparent)] + BitmapError(#[from] BitmapError), + /// [`Bitmap`] compression requires a known [`DataSpace`]. [`DataSpace::Unknown`] is invalid + /// even though it is typically treated as `sRGB`, for that [`DataSpace::Srgb`] has to be passed + /// explicitly. + #[error("The dataspace for this Bitmap is Unknown")] + DataSpaceUnknown, +} diff --git a/clients/android/native/vendor/ndk/src/configuration.rs b/clients/android/native/vendor/ndk/src/configuration.rs new file mode 100644 index 00000000..fcd98dda --- /dev/null +++ b/clients/android/native/vendor/ndk/src/configuration.rs @@ -0,0 +1,600 @@ +//! Bindings for [`AConfiguration`] +//! +//! See also the [NDK docs](https://developer.android.com/ndk/reference/group/configuration) for +//! [`AConfiguration`], as well as the [docs for providing +//! resources](https://developer.android.com/guide/topics/resources/providing-resources.html), +//! which explain many of the configuration values. The [`android.content.res.Configuration` +//! javadoc](https://developer.android.com/reference/android/content/res/Configuration.html) may +//! also have useful information. +//! +//! [`AConfiguration`]: https://developer.android.com/ndk/reference/group/configuration#aconfiguration + +use crate::asset::AssetManager; +use num_enum::{FromPrimitive, IntoPrimitive}; +use std::fmt; +use std::ptr::NonNull; + +/// A native [`AConfiguration *`] +/// +/// [`Configuration`] is an opaque type used to get and set various subsystem configurations. +/// +/// [`AConfiguration *`]: https://developer.android.com/ndk/reference/group/configuration#aconfiguration +pub struct Configuration { + ptr: NonNull, +} + +unsafe impl Send for Configuration {} +unsafe impl Sync for Configuration {} + +impl Drop for Configuration { + fn drop(&mut self) { + unsafe { ffi::AConfiguration_delete(self.ptr.as_ptr()) } + } +} + +impl Clone for Configuration { + fn clone(&self) -> Self { + let mut new = Self::new(); + new.copy(self); + new + } +} + +impl PartialEq for Configuration { + fn eq(&self, other: &Self) -> bool { + self.diff(other).0 == 0 + } +} +impl Eq for Configuration {} + +impl fmt::Debug for Configuration { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Configuration") + .field("mcc", &self.mcc()) + .field("mnc", &self.mnc()) + .field("lang", &self.language()) + .field("country", &self.country()) + .field("orientation", &self.orientation()) + .field("touchscreen", &self.touchscreen()) + .field("density", &self.density()) + .field("keyboard", &self.keyboard()) + .field("navigation", &self.navigation()) + .field("keys_hidden", &self.keys_hidden()) + .field("nav_hidden", &self.nav_hidden()) + .field("sdk_version", &self.sdk_version()) + .field("screen_size", &self.screen_size()) + .field("screen_long", &self.screen_long()) + .field("ui_mode_type", &self.ui_mode_type()) + .field("ui_mode_night", &self.ui_mode_night()) + .finish() + } +} + +impl Configuration { + /// Construct a `Configuration` from a pointer. + /// + /// # Safety + /// By calling this function, you assert that it is a valid pointer to a native + /// `AConfiguration`, and give ownership of it to the `Configuration` instance. + pub unsafe fn from_ptr(ptr: NonNull) -> Self { + Self { ptr } + } + + /// Create a new `Configuration`, with the same contents as the `AConfiguration` referenced by + /// the pointer. + /// + /// This is useful if you have a pointer, but not ownership of it. + /// + /// # Safety + /// By calling this function, you assert that it is a valid pointer to a native + /// `AConfiguration`. + pub unsafe fn clone_from_ptr(ptr: NonNull) -> Self { + let conf = Self::new(); + ffi::AConfiguration_copy(conf.ptr.as_ptr(), ptr.as_ptr()); + conf + } + + /// The pointer to the native `AConfiguration`. Keep in mind that the `Configuration` object + /// still has ownership, and will free it when dropped. + pub fn ptr(&self) -> NonNull { + self.ptr + } + + pub fn from_asset_manager(am: &AssetManager) -> Self { + let config = Self::new(); + unsafe { + ffi::AConfiguration_fromAssetManager(config.ptr().as_mut(), am.ptr().as_mut()); + } + config + } + + /// Create a new `Configuration`, with none of the values set. + pub fn new() -> Self { + unsafe { + Self { + ptr: NonNull::new(ffi::AConfiguration_new()).unwrap(), + } + } + } + + /// `dest.copy(&src)` copies the contents of `src` to `dest` + pub fn copy(&mut self, other: &Self) { + unsafe { ffi::AConfiguration_copy(self.ptr.as_ptr(), other.ptr.as_ptr()) } + } + + /// Information about what fields differ between the two configurations + pub fn diff(&self, other: &Self) -> DiffResult { + unsafe { + DiffResult(ffi::AConfiguration_diff(self.ptr.as_ptr(), other.ptr.as_ptr()) as u32) + } + } + + /// Returns false if anything in `self` conflicts with `requested` + pub fn matches(&self, requested: &Self) -> bool { + unsafe { ffi::AConfiguration_match(self.ptr.as_ptr(), requested.ptr.as_ptr()) != 0 } + } + + /// Returns the country code, as a [`String`] of two characters, if set + pub fn country(&self) -> Option { + let mut chars = [0u8; 2]; + unsafe { + ffi::AConfiguration_getCountry(self.ptr.as_ptr(), chars.as_mut_ptr().cast()); + } + if chars[0] == 0 { + None + } else { + Some(std::str::from_utf8(chars.as_slice()).unwrap().to_owned()) + } + } + + /// Returns the screen density in dpi. + /// + /// On some devices it can return values outside of the density enum. + pub fn density(&self) -> Option { + let density = unsafe { ffi::AConfiguration_getDensity(self.ptr.as_ptr()) as u32 }; + match density { + ffi::ACONFIGURATION_DENSITY_DEFAULT => Some(160), + ffi::ACONFIGURATION_DENSITY_ANY => None, + ffi::ACONFIGURATION_DENSITY_NONE => None, + density => Some(density), + } + } + + /// Returns the keyboard type. + pub fn keyboard(&self) -> Keyboard { + unsafe { ffi::AConfiguration_getKeyboard(self.ptr.as_ptr()).into() } + } + + /// Returns keyboard visibility/availability. + pub fn keys_hidden(&self) -> KeysHidden { + unsafe { ffi::AConfiguration_getKeysHidden(self.ptr.as_ptr()).into() } + } + + /// Returns the language, as a [`String`] of two characters, if set + pub fn language(&self) -> Option { + let mut chars = [0u8; 2]; + unsafe { + ffi::AConfiguration_getLanguage(self.ptr.as_ptr(), chars.as_mut_ptr().cast()); + } + if chars[0] == 0 { + None + } else { + Some(std::str::from_utf8(chars.as_slice()).unwrap().to_owned()) + } + } + + /// Returns the layout direction + pub fn layout_direction(&self) -> LayoutDir { + unsafe { ffi::AConfiguration_getLayoutDirection(self.ptr.as_ptr()).into() } + } + + /// Returns the mobile country code. + pub fn mcc(&self) -> i32 { + unsafe { ffi::AConfiguration_getMcc(self.ptr.as_ptr()) } + } + + /// Returns the mobile network code, if one is defined + pub fn mnc(&self) -> Option { + unsafe { + match ffi::AConfiguration_getMnc(self.ptr.as_ptr()) { + 0 => None, + x if x == ffi::ACONFIGURATION_MNC_ZERO as i32 => Some(0), + x => Some(x), + } + } + } + + pub fn nav_hidden(&self) -> NavHidden { + unsafe { ffi::AConfiguration_getNavHidden(self.ptr.as_ptr()).into() } + } + + pub fn navigation(&self) -> Navigation { + unsafe { ffi::AConfiguration_getNavigation(self.ptr.as_ptr()).into() } + } + + pub fn orientation(&self) -> Orientation { + unsafe { ffi::AConfiguration_getOrientation(self.ptr.as_ptr()).into() } + } + + pub fn screen_height_dp(&self) -> Option { + unsafe { + let height = ffi::AConfiguration_getScreenHeightDp(self.ptr.as_ptr()); + if height == ffi::ACONFIGURATION_SCREEN_HEIGHT_DP_ANY as i32 { + None + } else { + Some(height) + } + } + } + + pub fn screen_width_dp(&self) -> Option { + unsafe { + let width = ffi::AConfiguration_getScreenWidthDp(self.ptr.as_ptr()); + if width == ffi::ACONFIGURATION_SCREEN_WIDTH_DP_ANY as i32 { + None + } else { + Some(width) + } + } + } + + pub fn screen_long(&self) -> ScreenLong { + unsafe { ffi::AConfiguration_getScreenLong(self.ptr.as_ptr()).into() } + } + + #[cfg(feature = "api-level-30")] + pub fn screen_round(&self) -> ScreenRound { + unsafe { ffi::AConfiguration_getScreenRound(self.ptr.as_ptr()).into() } + } + + pub fn screen_size(&self) -> ScreenSize { + unsafe { ffi::AConfiguration_getScreenSize(self.ptr.as_ptr()).into() } + } + + pub fn sdk_version(&self) -> i32 { + unsafe { ffi::AConfiguration_getSdkVersion(self.ptr.as_ptr()) } + } + + pub fn smallest_screen_width_dp(&self) -> Option { + unsafe { + let width = ffi::AConfiguration_getSmallestScreenWidthDp(self.ptr.as_ptr()); + if width == ffi::ACONFIGURATION_SMALLEST_SCREEN_WIDTH_DP_ANY as i32 { + None + } else { + Some(width) + } + } + } + + pub fn touchscreen(&self) -> Touchscreen { + unsafe { ffi::AConfiguration_getTouchscreen(self.ptr.as_ptr()).into() } + } + + pub fn ui_mode_night(&self) -> UiModeNight { + unsafe { ffi::AConfiguration_getUiModeNight(self.ptr.as_ptr()).into() } + } + + pub fn ui_mode_type(&self) -> UiModeType { + unsafe { ffi::AConfiguration_getUiModeType(self.ptr.as_ptr()).into() } + } +} + +/// A bitfield representing the differences between two [`Configuration`]s +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub struct DiffResult(pub u32); + +impl DiffResult { + pub fn mcc(self) -> bool { + self.0 & ffi::ACONFIGURATION_MCC != 0 + } + pub fn mnc(self) -> bool { + self.0 & ffi::ACONFIGURATION_MNC != 0 + } + pub fn locale(self) -> bool { + self.0 & ffi::ACONFIGURATION_LOCALE != 0 + } + pub fn touchscreen(self) -> bool { + self.0 & ffi::ACONFIGURATION_TOUCHSCREEN != 0 + } + pub fn keyboard(self) -> bool { + self.0 & ffi::ACONFIGURATION_KEYBOARD != 0 + } + pub fn keyboard_hidden(self) -> bool { + self.0 & ffi::ACONFIGURATION_KEYBOARD_HIDDEN != 0 + } + pub fn navigation(self) -> bool { + self.0 & ffi::ACONFIGURATION_NAVIGATION != 0 + } + pub fn orientation(self) -> bool { + self.0 & ffi::ACONFIGURATION_ORIENTATION != 0 + } + pub fn density(self) -> bool { + self.0 & ffi::ACONFIGURATION_DENSITY != 0 + } + pub fn screen_size(self) -> bool { + self.0 & ffi::ACONFIGURATION_SCREEN_SIZE != 0 + } + pub fn version(self) -> bool { + self.0 & ffi::ACONFIGURATION_VERSION != 0 + } + pub fn screen_layout(self) -> bool { + self.0 & ffi::ACONFIGURATION_SCREEN_LAYOUT != 0 + } + pub fn ui_mode(self) -> bool { + self.0 & ffi::ACONFIGURATION_UI_MODE != 0 + } + pub fn smallest_screen_size(self) -> bool { + self.0 & ffi::ACONFIGURATION_SMALLEST_SCREEN_SIZE != 0 + } + pub fn layout_dir(self) -> bool { + self.0 & ffi::ACONFIGURATION_LAYOUTDIR != 0 + } + pub fn screen_round(self) -> bool { + self.0 & ffi::ACONFIGURATION_SCREEN_ROUND != 0 + } + pub fn color_mode(self) -> bool { + self.0 & ffi::ACONFIGURATION_COLOR_MODE != 0 + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, FromPrimitive, IntoPrimitive)] +#[repr(i32)] +#[non_exhaustive] +pub enum Orientation { + Any = ffi::ACONFIGURATION_ORIENTATION_ANY as i32, + Port = ffi::ACONFIGURATION_ORIENTATION_PORT as i32, + Land = ffi::ACONFIGURATION_ORIENTATION_LAND as i32, + Square = ffi::ACONFIGURATION_ORIENTATION_SQUARE as i32, + + #[doc(hidden)] + #[num_enum(catch_all)] + __Unknown(i32), +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, FromPrimitive, IntoPrimitive)] +#[repr(i32)] +#[non_exhaustive] +pub enum Touchscreen { + Any = ffi::ACONFIGURATION_TOUCHSCREEN_ANY as i32, + NoTouch = ffi::ACONFIGURATION_TOUCHSCREEN_NOTOUCH as i32, + Stylus = ffi::ACONFIGURATION_TOUCHSCREEN_STYLUS as i32, + Finger = ffi::ACONFIGURATION_TOUCHSCREEN_FINGER as i32, + + #[doc(hidden)] + #[num_enum(catch_all)] + __Unknown(i32), +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, FromPrimitive, IntoPrimitive)] +#[repr(i32)] +#[non_exhaustive] +pub enum Density { + Default = ffi::ACONFIGURATION_DENSITY_DEFAULT as i32, + Low = ffi::ACONFIGURATION_DENSITY_LOW as i32, + Medium = ffi::ACONFIGURATION_DENSITY_MEDIUM as i32, + TV = ffi::ACONFIGURATION_DENSITY_TV as i32, + High = ffi::ACONFIGURATION_DENSITY_HIGH as i32, + XHigh = ffi::ACONFIGURATION_DENSITY_XHIGH as i32, + XXHigh = ffi::ACONFIGURATION_DENSITY_XXHIGH as i32, + XXXHigh = ffi::ACONFIGURATION_DENSITY_XXXHIGH as i32, + Any = ffi::ACONFIGURATION_DENSITY_ANY as i32, + None = ffi::ACONFIGURATION_DENSITY_NONE as i32, + + #[doc(hidden)] + #[num_enum(catch_all)] + __Unknown(i32), +} + +impl Density { + /// The DPI associated with the density class. + /// See [the Android screen density + /// docs](https://developer.android.com/training/multiscreen/screendensities#TaskProvideAltBmp) + /// + /// There are some [`Density`] values that have no associated DPI; these values return [`None`]. + pub fn dpi(self) -> Option { + match self { + Self::Default => Some(160), // Or should it be None? + Self::Low => Some(120), + Self::Medium => Some(160), + Self::High => Some(240), + Self::XHigh => Some(320), + Self::XXHigh => Some(480), + Self::XXXHigh => Some(640), + Self::TV => Some(213), + Self::Any => None, + Self::None => None, + // TODO + Self::__Unknown(v) => Some(v as u32), + } + } + + /// The Hi-DPI factor associated with the density class. This is the factor by which an + /// image/resource should be scaled to match its size across devices. The baseline is a 160dpi + /// screen (i.e., Hi-DPI factor = DPI / 160). + /// See [the Android screen density + /// docs](https://developer.android.com/training/multiscreen/screendensities#TaskProvideAltBmp) + /// + /// There are some [`Density`] values that have no associated DPI; these values return [`None`]. + pub fn approx_hidpi_factor(self) -> Option { + match self { + Self::Default => Some(1.), // Or should it be None? + Self::Low => Some(0.75), + Self::Medium => Some(1.), + Self::High => Some(1.5), + Self::XHigh => Some(2.), + Self::XXHigh => Some(3.), + Self::XXXHigh => Some(4.), + Self::TV => Some(4. / 3.), + Self::Any => None, + Self::None => None, + Self::__Unknown(_) => None, + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, FromPrimitive, IntoPrimitive)] +#[repr(i32)] +#[non_exhaustive] +pub enum Keyboard { + Any = ffi::ACONFIGURATION_KEYBOARD_ANY as i32, + NoKeys = ffi::ACONFIGURATION_KEYBOARD_NOKEYS as i32, + Qwerty = ffi::ACONFIGURATION_KEYBOARD_QWERTY as i32, + TwelveKey = ffi::ACONFIGURATION_KEYBOARD_12KEY as i32, + + #[doc(hidden)] + #[num_enum(catch_all)] + __Unknown(i32), +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, FromPrimitive, IntoPrimitive)] +#[repr(i32)] +#[non_exhaustive] +pub enum Navigation { + Any = ffi::ACONFIGURATION_NAVIGATION_ANY as i32, + NoNav = ffi::ACONFIGURATION_NAVIGATION_NONAV as i32, + DPad = ffi::ACONFIGURATION_NAVIGATION_DPAD as i32, + Trackball = ffi::ACONFIGURATION_NAVIGATION_TRACKBALL as i32, + Wheel = ffi::ACONFIGURATION_NAVIGATION_WHEEL as i32, + + #[doc(hidden)] + #[num_enum(catch_all)] + __Unknown(i32), +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, FromPrimitive, IntoPrimitive)] +#[repr(i32)] +#[non_exhaustive] +pub enum KeysHidden { + Any = ffi::ACONFIGURATION_KEYSHIDDEN_ANY as i32, + No = ffi::ACONFIGURATION_KEYSHIDDEN_NO as i32, + Yes = ffi::ACONFIGURATION_KEYSHIDDEN_YES as i32, + Soft = ffi::ACONFIGURATION_KEYSHIDDEN_SOFT as i32, + + #[doc(hidden)] + #[num_enum(catch_all)] + __Unknown(i32), +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, FromPrimitive, IntoPrimitive)] +#[repr(i32)] +#[non_exhaustive] +pub enum NavHidden { + Any = ffi::ACONFIGURATION_NAVHIDDEN_ANY as i32, + No = ffi::ACONFIGURATION_NAVHIDDEN_NO as i32, + Yes = ffi::ACONFIGURATION_NAVHIDDEN_YES as i32, + + #[doc(hidden)] + #[num_enum(catch_all)] + __Unknown(i32), +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, FromPrimitive, IntoPrimitive)] +#[repr(i32)] +#[non_exhaustive] +pub enum ScreenSize { + Any = ffi::ACONFIGURATION_SCREENSIZE_ANY as i32, + Small = ffi::ACONFIGURATION_SCREENSIZE_SMALL as i32, + Normal = ffi::ACONFIGURATION_SCREENSIZE_NORMAL as i32, + Large = ffi::ACONFIGURATION_SCREENSIZE_LARGE as i32, + XLarge = ffi::ACONFIGURATION_SCREENSIZE_XLARGE as i32, + + #[doc(hidden)] + #[num_enum(catch_all)] + __Unknown(i32), +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, FromPrimitive, IntoPrimitive)] +#[repr(i32)] +#[non_exhaustive] +pub enum ScreenLong { + Any = ffi::ACONFIGURATION_SCREENLONG_ANY as i32, + No = ffi::ACONFIGURATION_SCREENLONG_NO as i32, + Yes = ffi::ACONFIGURATION_SCREENLONG_YES as i32, + + #[doc(hidden)] + #[num_enum(catch_all)] + __Unknown(i32), +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, FromPrimitive, IntoPrimitive)] +#[repr(i32)] +#[non_exhaustive] +pub enum ScreenRound { + Any = ffi::ACONFIGURATION_SCREENROUND_ANY as i32, + No = ffi::ACONFIGURATION_SCREENROUND_NO as i32, + Yes = ffi::ACONFIGURATION_SCREENROUND_YES as i32, + + #[doc(hidden)] + #[num_enum(catch_all)] + __Unknown(i32), +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, FromPrimitive, IntoPrimitive)] +#[repr(i32)] +#[non_exhaustive] +pub enum WideColorGamut { + Any = ffi::ACONFIGURATION_WIDE_COLOR_GAMUT_ANY as i32, + No = ffi::ACONFIGURATION_WIDE_COLOR_GAMUT_NO as i32, + Yes = ffi::ACONFIGURATION_WIDE_COLOR_GAMUT_YES as i32, + + #[doc(hidden)] + #[num_enum(catch_all)] + __Unknown(i32), +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, FromPrimitive, IntoPrimitive)] +#[repr(i32)] +#[non_exhaustive] +pub enum HDR { + Any = ffi::ACONFIGURATION_HDR_ANY as i32, + No = ffi::ACONFIGURATION_HDR_NO as i32, + Yes = ffi::ACONFIGURATION_HDR_YES as i32, + + #[doc(hidden)] + #[num_enum(catch_all)] + __Unknown(i32), +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, FromPrimitive, IntoPrimitive)] +#[repr(i32)] +#[non_exhaustive] +pub enum LayoutDir { + Any = ffi::ACONFIGURATION_LAYOUTDIR_ANY as i32, + Ltr = ffi::ACONFIGURATION_LAYOUTDIR_LTR as i32, + Rtl = ffi::ACONFIGURATION_LAYOUTDIR_RTL as i32, + + #[doc(hidden)] + #[num_enum(catch_all)] + __Unknown(i32), +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, FromPrimitive, IntoPrimitive)] +#[repr(i32)] +#[non_exhaustive] +pub enum UiModeType { + Any = ffi::ACONFIGURATION_UI_MODE_TYPE_ANY as i32, + Normal = ffi::ACONFIGURATION_UI_MODE_TYPE_NORMAL as i32, + Desk = ffi::ACONFIGURATION_UI_MODE_TYPE_DESK as i32, + Car = ffi::ACONFIGURATION_UI_MODE_TYPE_CAR as i32, + Television = ffi::ACONFIGURATION_UI_MODE_TYPE_TELEVISION as i32, + Applicance = ffi::ACONFIGURATION_UI_MODE_TYPE_APPLIANCE as i32, + Watch = ffi::ACONFIGURATION_UI_MODE_TYPE_WATCH as i32, + VrHeadset = ffi::ACONFIGURATION_UI_MODE_TYPE_VR_HEADSET as i32, + + #[doc(hidden)] + #[num_enum(catch_all)] + __Unknown(i32), +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, FromPrimitive, IntoPrimitive)] +#[repr(i32)] +#[non_exhaustive] +pub enum UiModeNight { + Any = ffi::ACONFIGURATION_UI_MODE_NIGHT_ANY as i32, + No = ffi::ACONFIGURATION_UI_MODE_NIGHT_NO as i32, + Yes = ffi::ACONFIGURATION_UI_MODE_NIGHT_YES as i32, + + #[doc(hidden)] + #[num_enum(catch_all)] + __Unknown(i32), +} diff --git a/clients/android/native/vendor/ndk/src/data_space.rs b/clients/android/native/vendor/ndk/src/data_space.rs new file mode 100644 index 00000000..f25691d1 --- /dev/null +++ b/clients/android/native/vendor/ndk/src/data_space.rs @@ -0,0 +1,648 @@ +//! Bindings for [`ADataSpace`] +//! +//! [`ADataSpace`]: https://developer.android.com/ndk/reference/group/a-data-space#group___a_data_space_1ga2759ad19cae46646cc5f7002758c4a1c +#![cfg(feature = "api-level-28")] + +use std::fmt; + +use num_enum::{FromPrimitive, IntoPrimitive}; + +/// Describes how to interpret colors. +/// +/// +#[repr(i32)] +#[derive(Clone, Copy, Hash, PartialEq, Eq, FromPrimitive, IntoPrimitive)] +#[doc(alias = "ADataSpace")] +#[non_exhaustive] +pub enum DataSpace { + /// Default-assumption data space, when not explicitly specified. + /// + /// It is safest to assume the buffer is an image with `sRGB` primaries and encoding ranges, + /// but the consumer and/or the producer of the data may simply be using defaults. No automatic + /// gamma transform should be expected, except for a possible display gamma transform when drawn + /// to a screen. + #[doc(alias = "ADATASPACE_UNKNOWN")] + Unknown = ffi::ADataSpace::ADATASPACE_UNKNOWN.0, + + /// Adobe RGB. + /// + /// Uses [full range], [gamma `2.2` transfer] and [Adobe RGB standard]. + /// + /// Note: Application is responsible for gamma encoding the data as a `2.2` gamma encoding is + /// not supported in HW. + /// + /// [full range]: DataSpaceRange::Full + /// [gamma `2.2` transfer]: DataSpaceTransfer::Gamma2_2 + /// [Adobe RGB standard]: DataSpaceStandard::AdobeRgb + #[doc(alias = "ADATASPACE_ADOBE_RGB")] + AdobeRgb = ffi::ADataSpace::ADATASPACE_ADOBE_RGB.0, + /// ITU-R Recommendation 2020 (`BT.2020`). + /// + /// Ultra High-definition television. + /// + /// Uses [full range], [`SMPTE 170M` transfer] and [`BT2020` standard]. + /// + /// [full range]: DataSpaceRange::Full + /// [`SMPTE 170M` transfer]: DataSpaceTransfer::Smpte170M + /// [`BT2020` standard]: DataSpaceStandard::Bt2020 + #[doc(alias = "ADATASPACE_BT2020")] + Bt2020 = ffi::ADataSpace::ADATASPACE_BT2020.0, + /// Hybrid Log Gamma encoding. + /// + /// Uses [full range], [hybrid log gamma transfer] and [`BT2020` standard]. + /// + /// [full range]: DataSpaceRange::Full + /// [hybrid log gamma transfer]: DataSpaceTransfer::HLG + /// [`BT2020` standard]: DataSpaceStandard::Bt2020 + #[doc(alias = "ADATASPACE_BT2020_HLG")] + Bt2020Hlg = ffi::ADataSpace::ADATASPACE_BT2020_HLG.0, + /// ITU Hybrid Log Gamma encoding. + /// + /// Uses [limited range], [hybrid log gamma transfer] and [`BT2020` standard]. + /// + /// [limited range]: DataSpaceRange::Limited + /// [hybrid log gamma transfer]: DataSpaceTransfer::HLG + /// [`BT2020` standard]: DataSpaceStandard::Bt2020 + #[doc(alias = "ADATASPACE_BT2020_ITU_HLG")] + Bt2020ItuHlg = ffi::ADataSpace::ADATASPACE_BT2020_ITU_HLG.0, + /// ITU-R Recommendation 2020 (`BT.2020`). + /// + /// Ultra High-definition television. + /// + /// Uses [limited range], [`SMPTE 2084 (PQ)` transfer] and [`BT2020` standard]. + /// + /// [limited range]: DataSpaceRange::Limited + /// [`SMPTE 2084 (PQ)` transfer]: DataSpaceTransfer::St2084 + /// [`BT2020` standard]: DataSpaceStandard::Bt2020 + #[doc(alias = "ADATASPACE_BT2020_ITU_PQ")] + Bt2020ItuPq = ffi::ADataSpace::ADATASPACE_BT2020_ITU_PQ.0, + /// ITU-R Recommendation 2020 (`BT.2020`). + /// + /// Ultra High-definition television. + /// + /// Uses [full range], [`SMPTE 2084 (PQ)` transfer] and [`BT2020` standard]. + /// + /// [full range]: DataSpaceRange::Full + /// [`SMPTE 2084 (PQ)` transfer]: DataSpaceTransfer::St2084 + /// [`BT2020` standard]: DataSpaceStandard::Bt2020 + #[doc(alias = "ADATASPACE_BT2020_PQ")] + Bt2020Pq = ffi::ADataSpace::ADATASPACE_BT2020_PQ.0, + /// ITU-R Recommendation 601 (`BT.601`) - 525-line. + /// + /// Standard-definition television, 525 Lines (NTSC). + /// + /// Uses [limited range], [`SMPTE 170M` transfer] and [`BT.601_525` standard]. + /// + /// [limited range]: DataSpaceRange::Limited + /// [`SMPTE 170M` transfer]: DataSpaceTransfer::Smpte170M + /// [`BT.601_525` standard]: DataSpaceStandard::Bt601_525 + #[doc(alias = "ADATASPACE_BT601_525")] + Bt601_525 = ffi::ADataSpace::ADATASPACE_BT601_525.0, + /// ITU-R Recommendation 601 (`BT.601`) - 625-line. + /// + /// Standard-definition television, 625 Lines (PAL). + /// + /// Uses [limited range], [`SMPTE 170M` transfer] and [`BT.601_625` standard]. + /// + /// [limited range]: DataSpaceRange::Limited + /// [`SMPTE 170M` transfer]: DataSpaceTransfer::Smpte170M + /// [`BT.601_625` standard]: DataSpaceStandard::Bt601_625 + #[doc(alias = "ADATASPACE_BT601_625")] + Bt601_625 = ffi::ADataSpace::ADATASPACE_BT601_625.0, + /// ITU-R Recommendation 709 (`BT.709`). + /// + /// High-definition television. + /// + /// Uses [limited range], [`SMPTE 170M` transfer] and [`BT.709` standard]. + /// + /// [limited range]: DataSpaceRange::Limited + /// [`SMPTE 170M` transfer]: DataSpaceTransfer::Smpte170M + /// [`BT.709` standard]: DataSpaceStandard::Bt709 + #[doc(alias = "ADATASPACE_BT709")] + Bt709 = ffi::ADataSpace::ADATASPACE_BT709.0, + /// `SMPTE EG 432-1` and `SMPTE RP 431-2`. + /// + /// Digital Cinema `DCI-P3`. + /// + /// Uses [full range], [gamma `2.6` transfer] and [`D65` `DCI-P3` standard]. + /// + /// Note: Application is responsible for gamma encoding the data as a `2.6` gamma encoding is + /// not supported in HW. + /// + /// [full range]: DataSpaceRange::Full + /// [gamma `2.6` transfer]: DataSpaceTransfer::Gamma2_6 + /// [`D65` `DCI-P3` standard]: DataSpaceStandard::DciP3 + #[doc(alias = "ADATASPACE_DCI_P3")] + DciP3 = ffi::ADataSpace::ADATASPACE_DCI_P3.0, + /// Display P3. + /// + /// Uses [full range], [`sRGB` transfer] and [`D65` `DCI-P3` standard]. + /// + /// [full range]: DataSpaceRange::Full + /// [`sRGB` transfer]: DataSpaceTransfer::Srgb + /// [`D65` `DCI-P3` standard]: DataSpaceStandard::DciP3 + #[doc(alias = "ADATASPACE_DISPLAY_P3")] + DisplayP3 = ffi::ADataSpace::ADATASPACE_DISPLAY_P3.0, + /// JPEG File Interchange Format (`JFIF`). + /// + /// Same model as `BT.601-625`, but all values (`Y`, `Cb`, `Cr`) range from `0` to `255`. + /// + /// Uses [full range], [`SMPTE 170M` transfer] and [`BT.601_625` standard]. + /// + /// [full range]: DataSpaceRange::Full + /// [`SMPTE 170M` transfer]: DataSpaceTransfer::Smpte170M + /// [`BT.601_625` standard]: DataSpaceStandard::Bt601_625 + #[doc(alias = "ADATASPACE_JFIF")] + Jfif = ffi::ADataSpace::ADATASPACE_JFIF.0, + /// `scRGB`. + /// + /// The `red`, `green`, and `blue` components are stored in [extended][extended range] `sRGB` + /// space, and gamma- encoded using the [`sRGB` transfer] function. + /// + /// The values are floating point. A pixel value of `1.0`, `1.0`, `1.0` corresponds to `sRGB` + /// white (`D65`) at `80` nits. Values beyond the range `[0.0 - 1.0]` would correspond to other + /// colors spaces and/or HDR content. + /// + /// Uses [extended range], [`sRGB` transfer] and [`BT.709` standard]. + /// + /// [extended range]: DataSpaceRange::Extended + /// [`sRGB` transfer]: DataSpaceTransfer::Srgb + /// [`BT.709` standard]: DataSpaceStandard::Bt709 + #[doc(alias = "ADATASPACE_SCRGB")] + Scrgb = ffi::ADataSpace::ADATASPACE_SCRGB.0, + /// `scRGB` linear encoding + /// + /// The `red`, `green`, and `blue` components are stored in [extended][extended range] `sRGB` + /// space, but are linear, not gamma-encoded. + /// + /// The values are floating point. A pixel value of `1.0`, `1.0`, `1.0` corresponds to `sRGB` + /// white (`D65`) at `80` nits. Values beyond the range `[0.0 - 1.0]` would correspond to other + /// colors spaces and/or HDR content. + /// + /// Uses [extended range], [linear transfer] and [`BT.709` standard]. + /// + /// [extended range]: DataSpaceRange::Extended + /// [linear transfer]: DataSpaceTransfer::Linear + /// [`BT.709` standard]: DataSpaceStandard::Bt709 + #[doc(alias = "ADATASPACE_SCRGB_LINEAR")] + ScrgbLinear = ffi::ADataSpace::ADATASPACE_SCRGB_LINEAR.0, + /// `sRGB` gamma encoding. + /// + /// The `red`, `green` and `blue` components are stored in `sRGB` space, and converted to linear + /// space when read, using the [`sRGB` transfer] function for each of the `R`, `G` and `B` + /// components. When written, the inverse transformation is performed. + /// + /// The `alpha` component, if present, is always stored in linear space and is left unmodified + /// when read or written. + /// + /// Uses [full range], [`sRGB` transfer] and [`BT.709` standard]. + /// + /// [full range]: DataSpaceRange::Full + /// [`sRGB` transfer]: DataSpaceTransfer::Srgb + /// [`BT.709` standard]: DataSpaceStandard::Bt709 + #[doc(alias = "ADATASPACE_SRGB")] + Srgb = ffi::ADataSpace::ADATASPACE_SRGB.0, + /// `sRGB` linear encoding. + /// + /// The `red`, `green`, and `blue` components are stored in `sRGB` space, but are linear, not + /// gamma-encoded. The `RGB` primaries and the white point are the same as [`BT.709]`. + /// + /// The values are encoded using the [full range] (`[0, 255]` for 8-bit) for all components. + /// + /// Uses [full range], [linear transfer] and [`BT.709` standard]. + /// + /// [full range]: DataSpaceRange::Full + /// [linear transfer]: DataSpaceTransfer::Linear + /// [`BT.709` standard]: DataSpaceStandard::Bt709 + #[doc(alias = "ADATASPACE_SRGB_LINEAR")] + SrgbLinear = ffi::ADataSpace::ADATASPACE_SRGB_LINEAR.0, + + /// Depth. + /// + /// This value is valid with formats [`HAL_PIXEL_FORMAT_Y16`] and [`HAL_PIXEL_FORMAT_BLOB`]. + /// + /// [`HAL_PIXEL_FORMAT_Y16`]: https://cs.android.com/android/platform/superproject/main/+/main:frameworks/native/libs/nativewindow/include/vndk/hardware_buffer.h;l=74-75;drc=45317f5c7c966fc816843217adc96a2ddea8bf29 + /// [`HAL_PIXEL_FORMAT_BLOB`]: super::hardware_buffer_format::HardwareBufferFormat::BLOB + #[doc(alias = "ADATASPACE_DEPTH")] + Depth = ffi::ADataSpace::ADATASPACE_DEPTH.0, + /// ISO `16684-1:2011(E)` Dynamic Depth. + /// + /// Embedded depth metadata following the dynamic depth specification. + #[doc(alias = "ADATASPACE_DYNAMIC_DEPTH")] + DynamicDepth = ffi::ADataSpace::ADATASPACE_DYNAMIC_DEPTH.0, + + #[doc(hidden)] + #[num_enum(catch_all)] + __Unknown(i32), +} + +impl fmt::Display for DataSpace { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(match self { + Self::Unknown => "Unknown", + Self::AdobeRgb => "AdobeRgb", + Self::Bt2020 => "Bt2020", + Self::Bt2020Hlg => "Bt2020Hlg", + Self::Bt2020ItuHlg => "Bt2020ItuHlg", + Self::Bt2020ItuPq => "Bt2020ItuPq", + Self::Bt2020Pq => "Bt2020Pq", + Self::Bt601_525 => "Bt601_525", + Self::Bt601_625 => "Bt601_625", + Self::Bt709 => "Bt709", + Self::DciP3 => "DciP3", + Self::DisplayP3 => "DisplayP3", + Self::Jfif => "Jfif", + Self::Scrgb => "Scrgb", + Self::ScrgbLinear => "ScrgbLinear", + Self::Srgb => "Srgb", + Self::SrgbLinear => "SrgbLinear", + Self::Depth => "Depth", + Self::DynamicDepth => "DynamicDepth", + Self::__Unknown(u) => { + return write!( + f, + "Unknown DataSpace({u:x?}, standard: {:?}, transfer: {:?}, range: {:?})", + self.standard(), + self.transfer(), + self.range() + ) + } + }) + } +} + +impl fmt::Debug for DataSpace { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "DataSpace({self}, standard: {:?}, transfer: {:?}, range: {:?})", + self.standard(), + self.transfer(), + self.range(), + ) + } +} + +impl DataSpace { + /// Construct a [`DataSpace`] from individual `standard`, `transfer` and `range` components. + /// + /// Together these should correspond to a single format. + pub fn from_parts( + standard: DataSpaceStandard, + transfer: DataSpaceTransfer, + range: DataSpaceRange, + ) -> Self { + Self::from(i32::from(standard) | i32::from(transfer) | i32::from(range)) + } + + /// Extracts and returns the color-description aspect from this [`DataSpace`]. + #[doc(alias = "STANDARD_MASK")] + pub fn standard(self) -> DataSpaceStandard { + let standard = i32::from(self) & ffi::ADataSpace::STANDARD_MASK.0; + standard.into() + } + + /// Extracts and returns the transfer aspect from this [`DataSpace`]. + #[doc(alias = "TRANSFER_MASK")] + pub fn transfer(self) -> DataSpaceTransfer { + let transfer = i32::from(self) & ffi::ADataSpace::TRANSFER_MASK.0; + transfer.into() + } + + /// Extracts and returns the range aspect from this [`DataSpace`]. + #[doc(alias = "RANGE_MASK")] + pub fn range(self) -> DataSpaceRange { + let range = i32::from(self) & ffi::ADataSpace::RANGE_MASK.0; + range.into() + } +} + +/// Color-description aspects. +/// +/// The following aspects define various characteristics of the color specification. These represent +/// bitfields, so that a data space value can specify each of them independently. Standard aspect +/// defines the chromaticity coordinates of the source primaries in terms of the CIE 1931 definition +/// of `x` and `y` specified in ISO 11664-1. +#[repr(i32)] +#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, FromPrimitive, IntoPrimitive)] +#[doc(alias = "STANDARD_MASK")] +#[non_exhaustive] +pub enum DataSpaceStandard { + /// Chromacity coordinates are unknown or are determined by the application. Implementations + /// shall use the following suggested standards: + /// + /// All `YCbCr` formats: [`BT.709`] if size is `720p` or larger (since most video content is + /// letterboxed this corresponds to width is `1280` or greater, or height + /// is 720 or greater). [`BT.601_625`] if size is smaller than `720p` or + /// is `JPEG`. + /// All `RGB` formats: [`BT.709`]. + /// + /// For all other formats the standard is undefined, and implementations should use an + /// appropriate standard for the data represented. + /// + /// [`BT.709`]: Self::Bt709 + /// [`BT.601_625`]: Self::Bt601_625 + #[doc(alias = "STANDARD_UNSPECIFIED")] + Unspecified = ffi::ADataSpace::STANDARD_UNSPECIFIED.0, + /// | Primaries | x | y | + /// | ----------- | ------ | ------ | + /// | green | 0.300 | 0.600 | + /// | blue | 0.150 | 0.060 | + /// | red | 0.640 | 0.330 | + /// | white (D65) | 0.3127 | 0.3290 | + /// + /// Use the unadjusted `KR = 0.2126`, `KB = 0.0722` luminance interpretation for `RGB` + /// conversion. + #[doc(alias = "STANDARD_BT709")] + Bt709 = ffi::ADataSpace::STANDARD_BT709.0, + /// | Primaries | x | y | + /// | ----------- | ------ | ------ | + /// | green | 0.290 | 0.600 | + /// | blue | 0.150 | 0.060 | + /// | red | 0.640 | 0.330 | + /// | white (D65) | 0.3127 | 0.3290 | + /// + /// `KR = 0.299`, `KB = 0.114`. This adjusts the luminance interpretation for `RGB` conversion + /// from the one purely determined by the primaries to minimize the color shift into `RGB` + /// space that uses [`BT.709`] primaries. + /// + /// [`BT.709`]: Self::Bt709 + #[doc(alias = "STANDARD_BT601_625")] + Bt601_625 = ffi::ADataSpace::STANDARD_BT601_625.0, + /// | Primaries | x | y | + /// | ----------- | ------ | ------ | + /// | green | 0.290 | 0.600 | + /// | blue | 0.150 | 0.060 | + /// | red | 0.640 | 0.330 | + /// | white (D65) | 0.3127 | 0.3290 | + /// + /// Use the unadjusted `KR = 0.222`, `KB = 0.071` luminance interpretation for `RGB` conversion. + #[doc(alias = "STANDARD_BT601_625_UNADJUSTED")] + Bt601_625Unadjusted = ffi::ADataSpace::STANDARD_BT601_625_UNADJUSTED.0, + /// | Primaries | x | y | + /// | ----------- | ------ | ------ | + /// | green | 0.310 | 0.595 | + /// | blue | 0.155 | 0.070 | + /// | red | 0.630 | 0.340 | + /// | white (D65) | 0.3127 | 0.3290 | + /// + /// `KR = 0.299`, `KB = 0.114`. This adjusts the luminance interpretation for `RGB` conversion + /// from the one purely determined by the primaries to minimize the color shift into `RGB` space + /// that uses [`BT.709`] primaries. + /// + /// [`BT.709`]: Self::Bt709 + #[doc(alias = "STANDARD_BT601_525")] + Bt601_525 = ffi::ADataSpace::STANDARD_BT601_525.0, + /// | Primaries | x | y | + /// | ----------- | ------ | ------ | + /// | green | 0.310 | 0.595 | + /// | blue | 0.155 | 0.070 | + /// | red | 0.630 | 0.340 | + /// | white (D65) | 0.3127 | 0.3290 | + /// + /// Use the unadjusted `KR = 0.212`, `KB = 0.087` luminance interpretation + /// for `RGB` conversion (as in `SMPTE 240M`). + #[doc(alias = "STANDARD_BT601_525_UNADJUSTED")] + Bt601_525Unadjusted = ffi::ADataSpace::STANDARD_BT601_525_UNADJUSTED.0, + /// | Primaries | x | y | + /// | ----------- | ------ | ------ | + /// | green | 0.170 | 0.797 | + /// | blue | 0.131 | 0.046 | + /// | red | 0.708 | 0.292 | + /// | white (D65) | 0.3127 | 0.3290 | + /// + /// Use the unadjusted `KR = 0.2627`, `KB = 0.0593` luminance interpretation for `RGB` + /// conversion. + #[doc(alias = "STANDARD_BT2020")] + Bt2020 = ffi::ADataSpace::STANDARD_BT2020.0, + /// | Primaries | x | y | + /// | ----------- | ------ | ------ | + /// | green | 0.170 | 0.797 | + /// | blue | 0.131 | 0.046 | + /// | red | 0.708 | 0.292 | + /// | white (D65) | 0.3127 | 0.3290 | + /// + /// Use the unadjusted `KR = 0.2627`, `KB = 0.0593` luminance interpretation for `RGB` + /// conversion using the linear domain. + #[doc(alias = "STANDARD_BT2020_CONSTANT_LUMINANCE")] + Bt2020ConstantLuminance = ffi::ADataSpace::STANDARD_BT2020_CONSTANT_LUMINANCE.0, + /// | Primaries | x | y | + /// | --------- | ----- | ---- | + /// | green | 0.21 |0.71 | + /// | blue | 0.14 |0.08 | + /// | red | 0.67 |0.33 | + /// | white (C) | 0.310 |0.316 | + /// + /// Use the unadjusted `KR = 0.30`, `KB = 0.11` luminance interpretation for `RGB` conversion. + #[doc(alias = "STANDARD_BT470M")] + Bt470M = ffi::ADataSpace::STANDARD_BT470M.0, + /// | Primaries | x | y | + /// | --------- | ----- | ----- | + /// | green | 0.243 | 0.692 | + /// | blue | 0.145 | 0.049 | + /// | red | 0.681 | 0.319 | + /// | white (C) | 0.310 | 0.316 | + /// + /// Use the unadjusted `KR = 0.254`, `KB = 0.068` luminance interpretation for `RGB` conversion. + #[doc(alias = "STANDARD_FILM")] + Film = ffi::ADataSpace::STANDARD_FILM.0, + /// `SMPTE EG 432-1` and `SMPTE RP 431-2`. (`DCI-P3`) + /// + /// | Primaries | x | y | + /// | ----------- | ------ | ------ | + /// | green | 0.265 | 0.690 | + /// | blue | 0.150 | 0.060 | + /// | red | 0.680 | 0.320 | + /// | white (D65) | 0.3127 | 0.3290 | + #[doc(alias = "STANDARD_DCI_P3")] + DciP3 = ffi::ADataSpace::STANDARD_DCI_P3.0, + /// Adobe RGB + /// + /// | Primaries | x | y | + /// | ----------- | ------ | ------ | + /// | green | 0.210 | 0.710 | + /// | blue | 0.150 | 0.060 | + /// | red | 0.640 | 0.330 | + /// | white (D65) | 0.3127 | 0.3290 | + #[doc(alias = "STANDARD_ADOBE_RGB")] + AdobeRgb = ffi::ADataSpace::STANDARD_ADOBE_RGB.0, + + #[doc(hidden)] + #[num_enum(catch_all)] + __Unknown(i32), +} + +/// Transfer aspect. +/// +/// Transfer characteristics are the opto-electronic transfer characteristic at the source as a +///function of linear optical intensity (luminance). +/// +/// For digital signals, `E` corresponds to the recorded value. Normally, the transfer function is +/// applied in `RGB` space to each of the `R`, `G` and `B` components independently. This may result +/// in color shift that can be minimized by applying the transfer function in `Lab` space only for +/// the `L` component. Implementation may apply the transfer function in `RGB` space for all pixel +/// formats if desired. +#[repr(i32)] +#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, FromPrimitive, IntoPrimitive)] +#[doc(alias = "TRANSFER_MASK")] +#[non_exhaustive] +pub enum DataSpaceTransfer { + /// Transfer characteristics are unknown or are determined by the application. + /// + /// Implementations should use the following transfer functions: + /// + /// - For `YCbCr` formats: use [`DataSpaceTransfer::Smpte170M`] + /// - For `RGB` formats: use [`DataSpaceTransfer::Srgb`] + /// + /// For all other formats the transfer function is undefined, and implementations should use an + /// appropriate standard for the data represented. + #[doc(alias = "TRANSFER_UNSPECIFIED")] + Unspecified = ffi::ADataSpace::TRANSFER_UNSPECIFIED.0, + + /// Linear transfer. + /// + /// Transfer characteristic curve: + /// ```ignore + /// E = L + /// ``` + /// - `L`: luminance of image `0 <= L <= 1` for conventional colorimetry + /// - `E`: corresponding electrical signal + #[doc(alias = "TRANSFER_LINEAR")] + Linear = ffi::ADataSpace::TRANSFER_LINEAR.0, + /// `sRGB` transfer. + /// + /// Transfer characteristic curve: + /// + /// ```ignore + /// E = 1.055 * L^(1/2.4) - 0.055 for 0.0031308 <= L <= 1 + /// = 12.92 * L for 0 <= L < 0.0031308 + /// ``` + /// - `L`: luminance of image `0 <= L <= 1` for conventional colorimetry + /// - `E`: corresponding electrical signal + #[doc(alias = "TRANSFER_SRGB")] + Srgb = ffi::ADataSpace::TRANSFER_SRGB.0, + /// SMPTE 170M transfer. + /// + /// Transfer characteristic curve: + /// ```ignore + /// E = 1.099 * L ^ 0.45 - 0.099 for 0.018 <= L <= 1 + /// = 4.500 * L for 0 <= L < 0.018 + /// ``` + /// - `L`: luminance of image `0 <= L <= 1` for conventional colorimetry + /// - `E`: corresponding electrical signal + #[doc(alias = "TRANSFER_SMPTE_170M")] + Smpte170M = ffi::ADataSpace::TRANSFER_SMPTE_170M.0, + /// Display gamma `2.2`. + /// + /// Transfer characteristic curve: + /// ```ignore + /// E = L ^ (1/2.2) + /// ``` + /// - `L`: luminance of image `0 <= L <= 1` for conventional colorimetry + /// - `E`: corresponding electrical signal + #[doc(alias = "TRANSFER_GAMMA2_2")] + Gamma2_2 = ffi::ADataSpace::TRANSFER_GAMMA2_2.0, + /// Display gamma `2.6`. + /// + /// Transfer characteristic curve: + /// ```ignore + /// E = L ^ (1/2.6) + /// ``` + /// - `L`: luminance of image `0 <= L <= 1` for conventional colorimetry + /// - `E`: corresponding electrical signal + #[doc(alias = "TRANSFER_GAMMA2_6")] + Gamma2_6 = ffi::ADataSpace::TRANSFER_GAMMA2_6.0, + /// Display gamma `2.8`. + /// + /// Transfer characteristic curve: + /// ```ignore + /// E = L ^ (1/2.8) + /// ``` + /// - `L`: luminance of image `0 <= L <= 1` for conventional colorimetry + /// - `E`: corresponding electrical signal + #[doc(alias = "TRANSFER_GAMMA2_8")] + Gamma2_8 = ffi::ADataSpace::TRANSFER_GAMMA2_8.0, + /// SMPTE ST 2084 (Dolby Perceptual Quantizer). + /// + /// Transfer characteristic curve: + /// ```ignore + /// E = ((c1 + c2 * L^n) / (1 + c3 * L^n)) ^ m + /// c1 = c3 - c2 + 1 = 3424 / 4096 = 0.8359375 + /// c2 = 32 * 2413 / 4096 = 18.8515625 + /// c3 = 32 * 2392 / 4096 = 18.6875 + /// m = 128 * 2523 / 4096 = 78.84375 + /// n = 0.25 * 2610 / 4096 = 0.1593017578125 + /// ``` + /// - `L`: luminance of image 0 <= L <= 1 for HDR colorimetry. + /// `L = 1` corresponds to `10000 cd/m2` + #[doc(alias = "TRANSFER_ST2084")] + St2084 = ffi::ADataSpace::TRANSFER_ST2084.0, + /// ARIB STD-B67 Hybrid Log Gamma. + /// + /// Transfer characteristic curve: + /// ```ignore + /// E = r * L^0.5 for 0 <= L <= 1 + /// = a * ln(L - b) + c for 1 < L + /// a = 0.17883277 + /// b = 0.28466892 + /// c = 0.55991073 + /// r = 0.5 + /// ``` + /// - `L`: luminance of image `0 <= L` for HDR colorimetry. + /// `L = 1` corresponds to reference white level of `100 cd/m2` + /// - `E`: corresponding electrical signal + #[doc(alias = "TRANSFER_HLG")] + HLG = ffi::ADataSpace::TRANSFER_HLG.0, + + #[doc(hidden)] + #[num_enum(catch_all)] + __Unknown(i32), +} + +/// Range aspect. +/// +/// Defines the range of values corresponding to the unit range of `0-1`. This is defined for +/// `YCbCr` only, but can be expanded to `RGB` space. +#[repr(i32)] +#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, FromPrimitive, IntoPrimitive)] +#[doc(alias = "RANGE_MASK")] +#[non_exhaustive] +pub enum DataSpaceRange { + /// Range is unknown or are determined by the application. Implementations shall use the + /// following suggested ranges: + /// + /// - All YCbCr formats: limited range. + /// - All RGB or RGBA formats (including RAW and Bayer): full range. + /// - All Y formats: full range + /// + /// For all other formats range is undefined, and implementations should use an appropriate + /// range for the data represented. + #[doc(alias = "RANGE_UNSPECIFIED")] + Unspecified = ffi::ADataSpace::RANGE_UNSPECIFIED.0, + /// Full range uses all values for `Y`, `Cb` and `Cr` from `0` to `2^b-1`, where `b` is the bit + /// depth of the color format. + #[doc(alias = "RANGE_FULL")] + Full = ffi::ADataSpace::RANGE_FULL.0, + /// Limited range uses values `16/256*2^b` to `235/256*2^b` for `Y`, and `1/16*2^b` to + /// `15/16*2^b` for `Cb`, `Cr`, `R`, `G` and `B`, where `b` is the bit depth of the color + /// format. + /// + /// E.g. For 8-bit-depth formats: Luma (`Y`) samples should range from `16` to `235`, inclusive + /// Chroma `(Cb, Cr)` samples should range from `16` to `240`, inclusive. + /// + /// For 10-bit-depth formats: Luma (`Y`) samples should range from `64` to `940`, inclusive + /// Chroma `(Cb, Cr)` samples should range from `64` to `960`, inclusive. + #[doc(alias = "RANGE_LIMITED")] + Limited = ffi::ADataSpace::RANGE_LIMITED.0, + /// Extended range is used for `scRGB`. + /// + /// Intended for use with floating point pixel formats. `[0.0 - 1.0]` is the standard `sRGB` + /// space. Values outside the range `0.0 - 1.0` can encode color outside the `sRGB` gamut. Used + /// to blend / merge multiple dataspaces on a single display. + #[doc(alias = "RANGE_EXTENDED")] + Extended = ffi::ADataSpace::RANGE_EXTENDED.0, + + #[doc(hidden)] + #[num_enum(catch_all)] + __Unknown(i32), +} diff --git a/clients/android/native/vendor/ndk/src/event.rs b/clients/android/native/vendor/ndk/src/event.rs new file mode 100644 index 00000000..ef447da4 --- /dev/null +++ b/clients/android/native/vendor/ndk/src/event.rs @@ -0,0 +1,1583 @@ +//! Bindings for [`AInputEvent`, `AKeyEvent` and `AMotionEvent`] +//! +//! Most of these operations directly wrap functions in the NDK. +//! +//! See also the Java docs for [`android.view.InputEvent`], [`android.view.MotionEvent`], and +//! [`android.view.KeyEvent`]. +//! +//! [`AInputEvent`, `AKeyEvent` and `AMotionEvent`]: https://developer.android.com/ndk/reference/group/input +//! [`android.view.InputEvent`]: https://developer.android.com/reference/android/view/InputEvent +//! [`android.view.MotionEvent`]: https://developer.android.com/reference/android/view/MotionEvent +//! [`android.view.KeyEvent`]: https://developer.android.com/reference/android/view/KeyEvent + +use std::ptr::NonNull; + +#[cfg(feature = "api-level-31")] +use jni_sys::{jobject, JNIEnv}; +use num_enum::{FromPrimitive, IntoPrimitive}; + +/// A native [`AInputEvent *`] +/// +/// [`AInputEvent *`]: https://developer.android.com/ndk/reference/group/input#ainputevent +#[derive(Debug)] +#[non_exhaustive] +pub enum InputEvent { + MotionEvent(MotionEvent), + KeyEvent(KeyEvent), +} + +/// Wraps a Java [`InputEvent`] acquired from [`KeyEvent::from_java()`] or +/// [`MotionEvent::from_java()`] with respective [`Drop`] semantics. +#[cfg(feature = "api-level-31")] +#[derive(Debug)] +pub struct InputEventJava(InputEvent); + +#[cfg(feature = "api-level-31")] +impl Drop for InputEventJava { + /// Releases interface objects created by [`KeyEvent::from_java()`] or + /// [`MotionEvent::from_java()`]. + /// + /// The underlying Java object remains valid and does not change its state. + #[doc(alias = "AInputEvent_release")] + fn drop(&mut self) { + let ptr = match self.0 { + InputEvent::MotionEvent(MotionEvent { ptr }) + | InputEvent::KeyEvent(KeyEvent { ptr }) => ptr.as_ptr().cast(), + }; + unsafe { ffi::AInputEvent_release(ptr) } + } +} + +/// An enum representing the source of an [`InputEvent`]. +#[derive(Debug, Clone, Copy, PartialEq, Eq, FromPrimitive, IntoPrimitive)] +#[repr(i32)] +#[non_exhaustive] +pub enum Source { + Unknown = ffi::AINPUT_SOURCE_UNKNOWN as i32, + Keyboard = ffi::AINPUT_SOURCE_KEYBOARD as i32, + Dpad = ffi::AINPUT_SOURCE_DPAD as i32, + Gamepad = ffi::AINPUT_SOURCE_GAMEPAD as i32, + Touchscreen = ffi::AINPUT_SOURCE_TOUCHSCREEN as i32, + Mouse = ffi::AINPUT_SOURCE_MOUSE as i32, + Stylus = ffi::AINPUT_SOURCE_STYLUS as i32, + BluetoothStylus = ffi::AINPUT_SOURCE_BLUETOOTH_STYLUS as i32, + Trackball = ffi::AINPUT_SOURCE_TRACKBALL as i32, + MouseRelative = ffi::AINPUT_SOURCE_MOUSE_RELATIVE as i32, + Touchpad = ffi::AINPUT_SOURCE_TOUCHPAD as i32, + TouchNavigation = ffi::AINPUT_SOURCE_TOUCH_NAVIGATION as i32, + Joystick = ffi::AINPUT_SOURCE_JOYSTICK as i32, + Hdmi = ffi::AINPUT_SOURCE_HDMI as i32, + Sensor = ffi::AINPUT_SOURCE_SENSOR as i32, + RotaryEncoder = ffi::AINPUT_SOURCE_ROTARY_ENCODER as i32, + Any = ffi::AINPUT_SOURCE_ANY as i32, + + #[doc(hidden)] + #[num_enum(catch_all)] + __Unknown(i32), +} + +impl Source { + pub fn class(self) -> SourceClass { + let class = i32::from(self) & ffi::AINPUT_SOURCE_CLASS_MASK as i32; + // The mask fits in a u8. + SourceClass::from_bits_retain(class as u8) + } +} + +bitflags::bitflags! { + /// Flags representing the class of an [`InputEvent`] [`Source`]. + #[derive(Debug, Clone, Copy, PartialEq, Eq)] + pub struct SourceClass : u8 { + #[doc(alias = "AINPUT_SOURCE_CLASS_BUTTON")] + const BUTTON = ffi::AINPUT_SOURCE_CLASS_BUTTON as u8; + #[doc(alias = "AINPUT_SOURCE_CLASS_POINTER")] + const POINTER = ffi::AINPUT_SOURCE_CLASS_POINTER as u8; + #[doc(alias = "AINPUT_SOURCE_CLASS_NAVIGATION")] + const NAVIGATION = ffi::AINPUT_SOURCE_CLASS_NAVIGATION as u8; + #[doc(alias = "AINPUT_SOURCE_CLASS_POSITION")] + const POSITION = ffi::AINPUT_SOURCE_CLASS_POSITION as u8; + #[doc(alias = "AINPUT_SOURCE_CLASS_JOYSTICK")] + const JOYSTICK = ffi::AINPUT_SOURCE_CLASS_JOYSTICK as u8; + + // https://docs.rs/bitflags/latest/bitflags/#externally-defined-flags + const _ = ffi::AINPUT_SOURCE_CLASS_MASK as u8; + } +} + +impl InputEvent { + /// Initialize an [`InputEvent`] from a pointer + /// + /// # Safety + /// By calling this function, you assert that the pointer is a valid pointer to a + /// native [`ffi::AInputEvent`]. + #[inline] + pub unsafe fn from_ptr(ptr: NonNull) -> Self { + match ffi::AInputEvent_getType(ptr.as_ptr()) as u32 { + ffi::AINPUT_EVENT_TYPE_KEY => InputEvent::KeyEvent(KeyEvent::from_ptr(ptr)), + ffi::AINPUT_EVENT_TYPE_MOTION => InputEvent::MotionEvent(MotionEvent::from_ptr(ptr)), + x => panic!("Bad event type received: {}", x), + } + } + + /// Returns a pointer to the native [`ffi::AInputEvent`]. + #[inline] + pub fn ptr(&self) -> NonNull { + match self { + InputEvent::MotionEvent(MotionEvent { ptr }) => *ptr, + InputEvent::KeyEvent(KeyEvent { ptr }) => *ptr, + } + } + + /// Get the source of the event. + /// + /// See [the NDK + /// docs](https://developer.android.com/ndk/reference/group/input#ainputevent_getsource) + #[inline] + pub fn source(&self) -> Source { + let source = unsafe { ffi::AInputEvent_getSource(self.ptr().as_ptr()) }; + source.into() + } + + /// Get the device id associated with the event. + /// + /// See [the NDK + /// docs](https://developer.android.com/ndk/reference/group/input#ainputevent_getdeviceid) + #[inline] + pub fn device_id(&self) -> i32 { + unsafe { ffi::AInputEvent_getDeviceId(self.ptr().as_ptr()) } + } +} + +/// A bitfield representing the state of modifier keys during an event. +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub struct MetaState(pub u32); + +impl MetaState { + #[inline] + pub fn alt_on(self) -> bool { + self.0 & ffi::AMETA_ALT_ON != 0 + } + #[inline] + pub fn alt_left_on(self) -> bool { + self.0 & ffi::AMETA_ALT_LEFT_ON != 0 + } + #[inline] + pub fn alt_right_on(self) -> bool { + self.0 & ffi::AMETA_ALT_RIGHT_ON != 0 + } + #[inline] + pub fn shift_on(self) -> bool { + self.0 & ffi::AMETA_SHIFT_ON != 0 + } + #[inline] + pub fn shift_left_on(self) -> bool { + self.0 & ffi::AMETA_SHIFT_LEFT_ON != 0 + } + #[inline] + pub fn shift_right_on(self) -> bool { + self.0 & ffi::AMETA_SHIFT_RIGHT_ON != 0 + } + #[inline] + pub fn sym_on(self) -> bool { + self.0 & ffi::AMETA_SYM_ON != 0 + } + #[inline] + pub fn function_on(self) -> bool { + self.0 & ffi::AMETA_FUNCTION_ON != 0 + } + #[inline] + pub fn ctrl_on(self) -> bool { + self.0 & ffi::AMETA_CTRL_ON != 0 + } + #[inline] + pub fn ctrl_left_on(self) -> bool { + self.0 & ffi::AMETA_CTRL_LEFT_ON != 0 + } + #[inline] + pub fn ctrl_right_on(self) -> bool { + self.0 & ffi::AMETA_CTRL_RIGHT_ON != 0 + } + #[inline] + pub fn meta_on(self) -> bool { + self.0 & ffi::AMETA_META_ON != 0 + } + #[inline] + pub fn meta_left_on(self) -> bool { + self.0 & ffi::AMETA_META_LEFT_ON != 0 + } + #[inline] + pub fn meta_right_on(self) -> bool { + self.0 & ffi::AMETA_META_RIGHT_ON != 0 + } + #[inline] + pub fn caps_lock_on(self) -> bool { + self.0 & ffi::AMETA_CAPS_LOCK_ON != 0 + } + #[inline] + pub fn num_lock_on(self) -> bool { + self.0 & ffi::AMETA_NUM_LOCK_ON != 0 + } + #[inline] + pub fn scroll_lock_on(self) -> bool { + self.0 & ffi::AMETA_SCROLL_LOCK_ON != 0 + } +} + +/// A motion event +/// +/// Wraps an [`AInputEvent *`] of the [`ffi::AINPUT_EVENT_TYPE_MOTION`] type. +/// +/// For general discussion of motion events in Android, see [the relevant +/// javadoc](https://developer.android.com/reference/android/view/MotionEvent). +/// +/// [`AInputEvent *`]: https://developer.android.com/ndk/reference/group/input#ainputevent +#[derive(Clone, Debug)] +pub struct MotionEvent { + ptr: NonNull, +} + +// TODO: thread safety? + +/// A motion action. +#[derive(Copy, Clone, Debug, PartialEq, Eq, FromPrimitive, IntoPrimitive)] +#[repr(i32)] +#[non_exhaustive] +pub enum MotionAction { + Down = ffi::AMOTION_EVENT_ACTION_DOWN as i32, + Up = ffi::AMOTION_EVENT_ACTION_UP as i32, + Move = ffi::AMOTION_EVENT_ACTION_MOVE as i32, + Cancel = ffi::AMOTION_EVENT_ACTION_CANCEL as i32, + Outside = ffi::AMOTION_EVENT_ACTION_OUTSIDE as i32, + PointerDown = ffi::AMOTION_EVENT_ACTION_POINTER_DOWN as i32, + PointerUp = ffi::AMOTION_EVENT_ACTION_POINTER_UP as i32, + HoverMove = ffi::AMOTION_EVENT_ACTION_HOVER_MOVE as i32, + Scroll = ffi::AMOTION_EVENT_ACTION_SCROLL as i32, + HoverEnter = ffi::AMOTION_EVENT_ACTION_HOVER_ENTER as i32, + HoverExit = ffi::AMOTION_EVENT_ACTION_HOVER_EXIT as i32, + ButtonPress = ffi::AMOTION_EVENT_ACTION_BUTTON_PRESS as i32, + ButtonRelease = ffi::AMOTION_EVENT_ACTION_BUTTON_RELEASE as i32, + + #[doc(hidden)] + #[num_enum(catch_all)] + __Unknown(i32), +} + +/// An axis of a motion event. +#[derive(Copy, Clone, Debug, PartialEq, Eq, FromPrimitive, IntoPrimitive)] +#[repr(i32)] +#[non_exhaustive] +pub enum Axis { + X = ffi::AMOTION_EVENT_AXIS_X as i32, + Y = ffi::AMOTION_EVENT_AXIS_Y as i32, + Pressure = ffi::AMOTION_EVENT_AXIS_PRESSURE as i32, + Size = ffi::AMOTION_EVENT_AXIS_SIZE as i32, + TouchMajor = ffi::AMOTION_EVENT_AXIS_TOUCH_MAJOR as i32, + TouchMinor = ffi::AMOTION_EVENT_AXIS_TOUCH_MINOR as i32, + ToolMajor = ffi::AMOTION_EVENT_AXIS_TOOL_MAJOR as i32, + ToolMinor = ffi::AMOTION_EVENT_AXIS_TOOL_MINOR as i32, + Orientation = ffi::AMOTION_EVENT_AXIS_ORIENTATION as i32, + Vscroll = ffi::AMOTION_EVENT_AXIS_VSCROLL as i32, + Hscroll = ffi::AMOTION_EVENT_AXIS_HSCROLL as i32, + Z = ffi::AMOTION_EVENT_AXIS_Z as i32, + Rx = ffi::AMOTION_EVENT_AXIS_RX as i32, + Ry = ffi::AMOTION_EVENT_AXIS_RY as i32, + Rz = ffi::AMOTION_EVENT_AXIS_RZ as i32, + HatX = ffi::AMOTION_EVENT_AXIS_HAT_X as i32, + HatY = ffi::AMOTION_EVENT_AXIS_HAT_Y as i32, + Ltrigger = ffi::AMOTION_EVENT_AXIS_LTRIGGER as i32, + Rtrigger = ffi::AMOTION_EVENT_AXIS_RTRIGGER as i32, + Throttle = ffi::AMOTION_EVENT_AXIS_THROTTLE as i32, + Rudder = ffi::AMOTION_EVENT_AXIS_RUDDER as i32, + Wheel = ffi::AMOTION_EVENT_AXIS_WHEEL as i32, + Gas = ffi::AMOTION_EVENT_AXIS_GAS as i32, + Brake = ffi::AMOTION_EVENT_AXIS_BRAKE as i32, + Distance = ffi::AMOTION_EVENT_AXIS_DISTANCE as i32, + Tilt = ffi::AMOTION_EVENT_AXIS_TILT as i32, + Scroll = ffi::AMOTION_EVENT_AXIS_SCROLL as i32, + RelativeX = ffi::AMOTION_EVENT_AXIS_RELATIVE_X as i32, + RelativeY = ffi::AMOTION_EVENT_AXIS_RELATIVE_Y as i32, + Generic1 = ffi::AMOTION_EVENT_AXIS_GENERIC_1 as i32, + Generic2 = ffi::AMOTION_EVENT_AXIS_GENERIC_2 as i32, + Generic3 = ffi::AMOTION_EVENT_AXIS_GENERIC_3 as i32, + Generic4 = ffi::AMOTION_EVENT_AXIS_GENERIC_4 as i32, + Generic5 = ffi::AMOTION_EVENT_AXIS_GENERIC_5 as i32, + Generic6 = ffi::AMOTION_EVENT_AXIS_GENERIC_6 as i32, + Generic7 = ffi::AMOTION_EVENT_AXIS_GENERIC_7 as i32, + Generic8 = ffi::AMOTION_EVENT_AXIS_GENERIC_8 as i32, + Generic9 = ffi::AMOTION_EVENT_AXIS_GENERIC_9 as i32, + Generic10 = ffi::AMOTION_EVENT_AXIS_GENERIC_10 as i32, + Generic11 = ffi::AMOTION_EVENT_AXIS_GENERIC_11 as i32, + Generic12 = ffi::AMOTION_EVENT_AXIS_GENERIC_12 as i32, + Generic13 = ffi::AMOTION_EVENT_AXIS_GENERIC_13 as i32, + Generic14 = ffi::AMOTION_EVENT_AXIS_GENERIC_14 as i32, + Generic15 = ffi::AMOTION_EVENT_AXIS_GENERIC_15 as i32, + Generic16 = ffi::AMOTION_EVENT_AXIS_GENERIC_16 as i32, + + #[doc(hidden)] + #[num_enum(catch_all)] + __Unknown(i32), +} + +/// The tool type of a pointer. +#[derive(Copy, Clone, Debug, PartialEq, Eq, FromPrimitive, IntoPrimitive)] +#[repr(i32)] +#[non_exhaustive] +pub enum ToolType { + Unknown = ffi::AMOTION_EVENT_TOOL_TYPE_UNKNOWN as i32, + Finger = ffi::AMOTION_EVENT_TOOL_TYPE_FINGER as i32, + Stylus = ffi::AMOTION_EVENT_TOOL_TYPE_STYLUS as i32, + Mouse = ffi::AMOTION_EVENT_TOOL_TYPE_MOUSE as i32, + Eraser = ffi::AMOTION_EVENT_TOOL_TYPE_ERASER as i32, + Palm = ffi::AMOTION_EVENT_TOOL_TYPE_PALM as i32, + + #[doc(hidden)] + #[num_enum(catch_all)] + __Unknown(i32), +} + +/// A bitfield representing the state of buttons during a motion event. +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub struct ButtonState(pub u32); + +impl ButtonState { + #[inline] + pub fn primary(self) -> bool { + self.0 & ffi::AMOTION_EVENT_BUTTON_PRIMARY != 0 + } + #[inline] + pub fn secondary(self) -> bool { + self.0 & ffi::AMOTION_EVENT_BUTTON_SECONDARY != 0 + } + #[inline] + pub fn teriary(self) -> bool { + self.0 & ffi::AMOTION_EVENT_BUTTON_TERTIARY != 0 + } + #[inline] + pub fn back(self) -> bool { + self.0 & ffi::AMOTION_EVENT_BUTTON_BACK != 0 + } + #[inline] + pub fn forward(self) -> bool { + self.0 & ffi::AMOTION_EVENT_BUTTON_FORWARD != 0 + } + #[inline] + pub fn stylus_primary(self) -> bool { + self.0 & ffi::AMOTION_EVENT_BUTTON_STYLUS_PRIMARY != 0 + } + #[inline] + pub fn stylus_secondary(self) -> bool { + self.0 & ffi::AMOTION_EVENT_BUTTON_STYLUS_SECONDARY != 0 + } +} + +/// A bitfield representing which edges were touched by a motion event. +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub struct EdgeFlags(pub u32); + +impl EdgeFlags { + #[inline] + pub fn top(self) -> bool { + self.0 & ffi::AMOTION_EVENT_EDGE_FLAG_TOP != 0 + } + #[inline] + pub fn bottom(self) -> bool { + self.0 & ffi::AMOTION_EVENT_EDGE_FLAG_BOTTOM != 0 + } + #[inline] + pub fn left(self) -> bool { + self.0 & ffi::AMOTION_EVENT_EDGE_FLAG_LEFT != 0 + } + #[inline] + pub fn right(self) -> bool { + self.0 & ffi::AMOTION_EVENT_EDGE_FLAG_RIGHT != 0 + } +} + +/// Flags associated with this [`MotionEvent`]. +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub struct MotionEventFlags(pub u32); + +impl MotionEventFlags { + #[inline] + pub fn window_is_obscured(self) -> bool { + self.0 & ffi::AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED != 0 + } +} + +impl MotionEvent { + /// Constructs a MotionEvent from a pointer to a native [`ffi::AInputEvent`] + /// + /// # Safety + /// By calling this method, you assert that the pointer is a valid, non-null pointer to a + /// native [`ffi::AInputEvent`] and that [`ffi::AInputEvent`] + /// is an `AMotionEvent`. + #[inline] + pub unsafe fn from_ptr(ptr: NonNull) -> Self { + Self { ptr } + } + + /// Creates a native [`InputEvent`] object that is a copy of the specified + /// Java [`android.view.MotionEvent`]. The result may be used with generic and + /// [`MotionEvent`]-specific functions. + /// + /// # Safety + /// + /// This function should be called with a healthy JVM pointer and with a non-null + /// [`android.view.MotionEvent`]. + /// + /// [`android.view.MotionEvent`]: https://developer.android.com/reference/android/view/MotionEvent + #[cfg(feature = "api-level-31")] + #[doc(alias = "AMotionEvent_fromJava")] + pub unsafe fn from_java(env: *mut JNIEnv, key_event: jobject) -> Option { + let ptr = unsafe { ffi::AMotionEvent_fromJava(env, key_event) }; + Some(InputEventJava(InputEvent::MotionEvent(Self::from_ptr( + NonNull::new(ptr.cast_mut())?, + )))) + } + + /// Returns a pointer to the native [`ffi::AInputEvent`]. + #[inline] + pub fn ptr(&self) -> NonNull { + self.ptr + } + + /// Get the source of the event. + /// + /// See [the NDK + /// docs](https://developer.android.com/ndk/reference/group/input#ainputevent_getsource) + #[inline] + pub fn source(&self) -> Source { + let source = unsafe { ffi::AInputEvent_getSource(self.ptr.as_ptr()) }; + source.into() + } + + /// Get the device id associated with the event. + /// + /// See [the NDK + /// docs](https://developer.android.com/ndk/reference/group/input#ainputevent_getdeviceid) + #[inline] + pub fn device_id(&self) -> i32 { + unsafe { ffi::AInputEvent_getDeviceId(self.ptr.as_ptr()) } + } + + /// Returns the motion action associated with the event. + /// + /// See [the NDK + /// docs](https://developer.android.com/ndk/reference/group/input#amotionevent_getaction) + #[inline] + pub fn action(&self) -> MotionAction { + let action = unsafe { ffi::AMotionEvent_getAction(self.ptr.as_ptr()) } + & ffi::AMOTION_EVENT_ACTION_MASK as i32; + action.into() + } + + /// Returns the pointer index of an `Up` or `Down` event. + /// + /// Pointer indices can change per motion event. For an identifier that stays the same, see + /// [`Pointer::pointer_id()`]. + /// + /// This only has a meaning when the [action][Self::action] is one of [`Up`][MotionAction::Up], + /// [`Down`][MotionAction::Down], [`PointerUp`][MotionAction::PointerUp], + /// or [`PointerDown`][MotionAction::PointerDown]. + #[inline] + pub fn pointer_index(&self) -> usize { + let action = unsafe { ffi::AMotionEvent_getAction(self.ptr.as_ptr()) as u32 }; + let index = (action & ffi::AMOTION_EVENT_ACTION_POINTER_INDEX_MASK) + >> ffi::AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT; + index as usize + } + + /* + /// Returns the pointer id associated with the given pointer index. + /// + /// See [the NDK + /// docs](https://developer.android.com/ndk/reference/group/input#amotionevent_getpointerid) + // TODO: look at output with out-of-range pointer index + // Probably -1 though + pub fn pointer_id_for(&self, pointer_index: usize) -> i32 { + unsafe { ffi::AMotionEvent_getPointerId(self.ptr.as_ptr(), pointer_index) } + } + */ + + /// Returns the number of pointers in this event + /// + /// See [the NDK + /// docs](https://developer.android.com/ndk/reference/group/input#amotionevent_getpointercount) + #[inline] + pub fn pointer_count(&self) -> usize { + unsafe { ffi::AMotionEvent_getPointerCount(self.ptr.as_ptr()) } + } + + /// An iterator over the pointers in this motion event + #[inline] + pub fn pointers(&self) -> PointersIter<'_> { + PointersIter { + event: self.ptr, + next_index: 0, + count: self.pointer_count(), + _marker: std::marker::PhantomData, + } + } + + /// The pointer at a given pointer index. Panics if the pointer index is out of bounds. + /// + /// If you need to loop over all the pointers, prefer the [`pointers()`][Self::pointers] method. + #[inline] + pub fn pointer_at_index(&self, index: usize) -> Pointer<'_> { + if index >= self.pointer_count() { + panic!("Pointer index {} is out of bounds", index); + } + Pointer { + event: self.ptr, + index, + _marker: std::marker::PhantomData, + } + } + + /// 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 { ffi::AMotionEvent_getHistorySize(self.ptr.as_ptr()) } + } + + /// An iterator over the historical events contained in this event. + #[inline] + pub fn history(&self) -> HistoricalMotionEventsIter<'_> { + HistoricalMotionEventsIter { + event: self.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 + /// docs](https://developer.android.com/ndk/reference/group/input#amotionevent_getmetastate) + #[inline] + pub fn meta_state(&self) -> MetaState { + unsafe { MetaState(ffi::AMotionEvent_getMetaState(self.ptr.as_ptr()) as u32) } + } + + /// Returns the button state during this event, as a bitfield. + /// + /// See [the NDK + /// docs](https://developer.android.com/ndk/reference/group/input#amotionevent_getbuttonstate) + #[inline] + pub fn button_state(&self) -> ButtonState { + unsafe { ButtonState(ffi::AMotionEvent_getButtonState(self.ptr.as_ptr()) as u32) } + } + + /// Returns the time of the start of this gesture, in the `java.lang.System.nanoTime()` time + /// base + /// + /// See [the NDK + /// docs](https://developer.android.com/ndk/reference/group/input#amotionevent_getdowntime) + #[inline] + pub fn down_time(&self) -> i64 { + unsafe { ffi::AMotionEvent_getDownTime(self.ptr.as_ptr()) } + } + + /// Returns a bitfield indicating which edges were touched by this event. + /// + /// See [the NDK + /// docs](https://developer.android.com/ndk/reference/group/input#amotionevent_getedgeflags) + #[inline] + pub fn edge_flags(&self) -> EdgeFlags { + unsafe { EdgeFlags(ffi::AMotionEvent_getEdgeFlags(self.ptr.as_ptr()) as u32) } + } + + /// Returns the time of this event, in the `java.lang.System.nanoTime()` time base + /// + /// See [the NDK + /// docs](https://developer.android.com/ndk/reference/group/input#amotionevent_geteventtime) + #[inline] + pub fn event_time(&self) -> i64 { + unsafe { ffi::AMotionEvent_getEventTime(self.ptr.as_ptr()) } + } + + /// The flags associated with a motion event. + /// + /// See [the NDK + /// docs](https://developer.android.com/ndk/reference/group/input#amotionevent_getflags) + #[inline] + pub fn flags(&self) -> MotionEventFlags { + unsafe { MotionEventFlags(ffi::AMotionEvent_getFlags(self.ptr.as_ptr()) as u32) } + } + + /// Returns the offset in the x direction between the coordinates and the raw coordinates + /// + /// See [the NDK + /// docs](https://developer.android.com/ndk/reference/group/input#amotionevent_getxoffset) + #[inline] + pub fn x_offset(&self) -> f32 { + unsafe { ffi::AMotionEvent_getXOffset(self.ptr.as_ptr()) } + } + + /// Returns the offset in the y direction between the coordinates and the raw coordinates + /// + /// See [the NDK + /// docs](https://developer.android.com/ndk/reference/group/input#amotionevent_getyoffset) + #[inline] + pub fn y_offset(&self) -> f32 { + unsafe { ffi::AMotionEvent_getYOffset(self.ptr.as_ptr()) } + } + + /// Returns the precision of the x value of the coordinates + /// + /// See [the NDK + /// docs](https://developer.android.com/ndk/reference/group/input#amotionevent_getxprecision) + #[inline] + pub fn x_precision(&self) -> f32 { + unsafe { ffi::AMotionEvent_getXPrecision(self.ptr.as_ptr()) } + } + + /// Returns the precision of the y value of the coordinates + /// + /// See [the NDK + /// docs](https://developer.android.com/ndk/reference/group/input#amotionevent_getyprecision) + #[inline] + pub fn y_precision(&self) -> f32 { + unsafe { ffi::AMotionEvent_getYPrecision(self.ptr.as_ptr()) } + } +} + +/// A view into the data of a specific pointer in a motion event. +#[derive(Debug)] +pub struct Pointer<'a> { + event: NonNull, + index: usize, + _marker: std::marker::PhantomData<&'a MotionEvent>, +} + +// TODO: thread safety? + +impl<'a> Pointer<'a> { + #[inline] + pub fn pointer_index(&self) -> usize { + self.index + } + + #[inline] + pub fn pointer_id(&self) -> i32 { + unsafe { ffi::AMotionEvent_getPointerId(self.event.as_ptr(), self.index) } + } + + #[inline] + pub fn axis_value(&self, axis: Axis) -> f32 { + unsafe { ffi::AMotionEvent_getAxisValue(self.event.as_ptr(), axis.into(), self.index) } + } + + #[inline] + pub fn orientation(&self) -> f32 { + unsafe { ffi::AMotionEvent_getOrientation(self.event.as_ptr(), self.index) } + } + + #[inline] + pub fn pressure(&self) -> f32 { + unsafe { ffi::AMotionEvent_getPressure(self.event.as_ptr(), self.index) } + } + + #[inline] + pub fn raw_x(&self) -> f32 { + unsafe { ffi::AMotionEvent_getRawX(self.event.as_ptr(), self.index) } + } + + #[inline] + pub fn raw_y(&self) -> f32 { + unsafe { ffi::AMotionEvent_getRawY(self.event.as_ptr(), self.index) } + } + + #[inline] + pub fn x(&self) -> f32 { + unsafe { ffi::AMotionEvent_getX(self.event.as_ptr(), self.index) } + } + + #[inline] + pub fn y(&self) -> f32 { + unsafe { ffi::AMotionEvent_getY(self.event.as_ptr(), self.index) } + } + + #[inline] + pub fn size(&self) -> f32 { + unsafe { ffi::AMotionEvent_getSize(self.event.as_ptr(), self.index) } + } + + #[inline] + pub fn tool_major(&self) -> f32 { + unsafe { ffi::AMotionEvent_getToolMajor(self.event.as_ptr(), self.index) } + } + + #[inline] + pub fn tool_minor(&self) -> f32 { + unsafe { ffi::AMotionEvent_getToolMinor(self.event.as_ptr(), self.index) } + } + + #[inline] + pub fn touch_major(&self) -> f32 { + unsafe { ffi::AMotionEvent_getTouchMajor(self.event.as_ptr(), self.index) } + } + + #[inline] + pub fn touch_minor(&self) -> f32 { + unsafe { ffi::AMotionEvent_getTouchMinor(self.event.as_ptr(), self.index) } + } + + #[inline] + pub fn tool_type(&self) -> ToolType { + let tool_type = unsafe { ffi::AMotionEvent_getToolType(self.event.as_ptr(), self.index) }; + tool_type.into() + } +} + +/// An iterator over the pointers in a [`MotionEvent`]. +#[derive(Debug)] +pub struct PointersIter<'a> { + event: NonNull, + next_index: usize, + count: usize, + _marker: std::marker::PhantomData<&'a MotionEvent>, +} + +// TODO: thread safety? + +impl<'a> Iterator for PointersIter<'a> { + type Item = Pointer<'a>; + fn next(&mut self) -> Option> { + if self.next_index < self.count { + let ptr = Pointer { + event: self.event, + index: self.next_index, + _marker: std::marker::PhantomData, + }; + self.next_index += 1; + Some(ptr) + } else { + None + } + } + + fn size_hint(&self) -> (usize, Option) { + let size = self.count - self.next_index; + (size, Some(size)) + } +} +impl<'a> ExactSizeIterator for PointersIter<'a> { + 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, + 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 { ffi::AMotionEvent_getHistoricalEventTime(self.event.as_ptr(), self.history_index) } + } + + /// 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 { ffi::AMotionEvent_getPointerCount(self.event.as_ptr()) }, + _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, + 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> { + 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) { + 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> { + 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 + } + } +} + +/// A view into a pointer at a historical moment +#[derive(Debug)] +pub struct HistoricalPointer<'a> { + event: NonNull, + pointer_index: usize, + history_index: usize, + _marker: std::marker::PhantomData<&'a MotionEvent>, +} + +// TODO: thread safety? + +impl<'a> HistoricalPointer<'a> { + #[inline] + pub fn pointer_index(&self) -> usize { + self.pointer_index + } + + #[inline] + pub fn pointer_id(&self) -> i32 { + unsafe { ffi::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 { + ffi::AMotionEvent_getHistoricalAxisValue( + self.event.as_ptr(), + axis.into(), + self.pointer_index, + self.history_index, + ) + } + } + + #[inline] + pub fn orientation(&self) -> f32 { + unsafe { + ffi::AMotionEvent_getHistoricalOrientation( + self.event.as_ptr(), + self.pointer_index, + self.history_index, + ) + } + } + + #[inline] + pub fn pressure(&self) -> f32 { + unsafe { + ffi::AMotionEvent_getHistoricalPressure( + self.event.as_ptr(), + self.pointer_index, + self.history_index, + ) + } + } + + #[inline] + pub fn raw_x(&self) -> f32 { + unsafe { + ffi::AMotionEvent_getHistoricalRawX( + self.event.as_ptr(), + self.pointer_index, + self.history_index, + ) + } + } + + #[inline] + pub fn raw_y(&self) -> f32 { + unsafe { + ffi::AMotionEvent_getHistoricalRawY( + self.event.as_ptr(), + self.pointer_index, + self.history_index, + ) + } + } + + #[inline] + pub fn x(&self) -> f32 { + unsafe { + ffi::AMotionEvent_getHistoricalX( + self.event.as_ptr(), + self.pointer_index, + self.history_index, + ) + } + } + + #[inline] + pub fn y(&self) -> f32 { + unsafe { + ffi::AMotionEvent_getHistoricalY( + self.event.as_ptr(), + self.pointer_index, + self.history_index, + ) + } + } + + #[inline] + pub fn size(&self) -> f32 { + unsafe { + ffi::AMotionEvent_getHistoricalSize( + self.event.as_ptr(), + self.pointer_index, + self.history_index, + ) + } + } + + #[inline] + pub fn tool_major(&self) -> f32 { + unsafe { + ffi::AMotionEvent_getHistoricalToolMajor( + self.event.as_ptr(), + self.pointer_index, + self.history_index, + ) + } + } + + #[inline] + pub fn tool_minor(&self) -> f32 { + unsafe { + ffi::AMotionEvent_getHistoricalToolMinor( + self.event.as_ptr(), + self.pointer_index, + self.history_index, + ) + } + } + + #[inline] + pub fn touch_major(&self) -> f32 { + unsafe { + ffi::AMotionEvent_getHistoricalTouchMajor( + self.event.as_ptr(), + self.pointer_index, + self.history_index, + ) + } + } + + #[inline] + pub fn touch_minor(&self) -> f32 { + unsafe { + ffi::AMotionEvent_getHistoricalTouchMinor( + self.event.as_ptr(), + self.pointer_index, + self.history_index, + ) + } + } +} + +/// An iterator over the pointers in a historical motion event +#[derive(Debug)] +pub struct HistoricalPointersIter<'a> { + event: NonNull, + history_index: usize, + next_pointer_index: usize, + pointer_count: usize, + _marker: std::marker::PhantomData<&'a MotionEvent>, +} + +// TODO: thread safety? + +impl<'a> Iterator for HistoricalPointersIter<'a> { + type Item = HistoricalPointer<'a>; + + fn next(&mut self) -> Option> { + 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 size_hint(&self) -> (usize, Option) { + let size = self.pointer_count - self.next_pointer_index; + (size, Some(size)) + } +} +impl ExactSizeIterator for HistoricalPointersIter<'_> { + fn len(&self) -> usize { + self.pointer_count - self.next_pointer_index + } +} + +/// A key event +/// +/// Wraps an [`AInputEvent *`] of the [`ffi::AINPUT_EVENT_TYPE_KEY`] type. +/// +/// For general discussion of key events in Android, see [the relevant +/// javadoc](https://developer.android.com/reference/android/view/KeyEvent). +/// +/// [`AInputEvent *`]: https://developer.android.com/ndk/reference/group/input#ainputevent +#[derive(Debug)] +pub struct KeyEvent { + ptr: NonNull, +} + +// TODO: thread safety? + +/// Key actions. +/// See [the NDK docs](https://developer.android.com/ndk/reference/group/input#anonymous-enum-27) +#[derive(Copy, Clone, Debug, PartialEq, Eq, FromPrimitive, IntoPrimitive)] +#[repr(i32)] +#[non_exhaustive] +pub enum KeyAction { + Down = ffi::AKEY_EVENT_ACTION_DOWN as i32, + Up = ffi::AKEY_EVENT_ACTION_UP as i32, + Multiple = ffi::AKEY_EVENT_ACTION_MULTIPLE as i32, + + #[doc(hidden)] + #[num_enum(catch_all)] + __Unknown(i32), +} + +/// Key codes. +#[derive(Copy, Clone, Debug, PartialEq, Eq, FromPrimitive, IntoPrimitive)] +#[repr(i32)] +#[non_exhaustive] +pub enum Keycode { + Unknown = ffi::AKEYCODE_UNKNOWN as i32, + SoftLeft = ffi::AKEYCODE_SOFT_LEFT as i32, + SoftRight = ffi::AKEYCODE_SOFT_RIGHT as i32, + Home = ffi::AKEYCODE_HOME as i32, + Back = ffi::AKEYCODE_BACK as i32, + Call = ffi::AKEYCODE_CALL as i32, + Endcall = ffi::AKEYCODE_ENDCALL as i32, + Keycode0 = ffi::AKEYCODE_0 as i32, + Keycode1 = ffi::AKEYCODE_1 as i32, + Keycode2 = ffi::AKEYCODE_2 as i32, + Keycode3 = ffi::AKEYCODE_3 as i32, + Keycode4 = ffi::AKEYCODE_4 as i32, + Keycode5 = ffi::AKEYCODE_5 as i32, + Keycode6 = ffi::AKEYCODE_6 as i32, + Keycode7 = ffi::AKEYCODE_7 as i32, + Keycode8 = ffi::AKEYCODE_8 as i32, + Keycode9 = ffi::AKEYCODE_9 as i32, + Star = ffi::AKEYCODE_STAR as i32, + Pound = ffi::AKEYCODE_POUND as i32, + DpadUp = ffi::AKEYCODE_DPAD_UP as i32, + DpadDown = ffi::AKEYCODE_DPAD_DOWN as i32, + DpadLeft = ffi::AKEYCODE_DPAD_LEFT as i32, + DpadRight = ffi::AKEYCODE_DPAD_RIGHT as i32, + DpadCenter = ffi::AKEYCODE_DPAD_CENTER as i32, + VolumeUp = ffi::AKEYCODE_VOLUME_UP as i32, + VolumeDown = ffi::AKEYCODE_VOLUME_DOWN as i32, + Power = ffi::AKEYCODE_POWER as i32, + Camera = ffi::AKEYCODE_CAMERA as i32, + Clear = ffi::AKEYCODE_CLEAR as i32, + A = ffi::AKEYCODE_A as i32, + B = ffi::AKEYCODE_B as i32, + C = ffi::AKEYCODE_C as i32, + D = ffi::AKEYCODE_D as i32, + E = ffi::AKEYCODE_E as i32, + F = ffi::AKEYCODE_F as i32, + G = ffi::AKEYCODE_G as i32, + H = ffi::AKEYCODE_H as i32, + I = ffi::AKEYCODE_I as i32, + J = ffi::AKEYCODE_J as i32, + K = ffi::AKEYCODE_K as i32, + L = ffi::AKEYCODE_L as i32, + M = ffi::AKEYCODE_M as i32, + N = ffi::AKEYCODE_N as i32, + O = ffi::AKEYCODE_O as i32, + P = ffi::AKEYCODE_P as i32, + Q = ffi::AKEYCODE_Q as i32, + R = ffi::AKEYCODE_R as i32, + S = ffi::AKEYCODE_S as i32, + T = ffi::AKEYCODE_T as i32, + U = ffi::AKEYCODE_U as i32, + V = ffi::AKEYCODE_V as i32, + W = ffi::AKEYCODE_W as i32, + X = ffi::AKEYCODE_X as i32, + Y = ffi::AKEYCODE_Y as i32, + Z = ffi::AKEYCODE_Z as i32, + Comma = ffi::AKEYCODE_COMMA as i32, + Period = ffi::AKEYCODE_PERIOD as i32, + AltLeft = ffi::AKEYCODE_ALT_LEFT as i32, + AltRight = ffi::AKEYCODE_ALT_RIGHT as i32, + ShiftLeft = ffi::AKEYCODE_SHIFT_LEFT as i32, + ShiftRight = ffi::AKEYCODE_SHIFT_RIGHT as i32, + Tab = ffi::AKEYCODE_TAB as i32, + Space = ffi::AKEYCODE_SPACE as i32, + Sym = ffi::AKEYCODE_SYM as i32, + Explorer = ffi::AKEYCODE_EXPLORER as i32, + Envelope = ffi::AKEYCODE_ENVELOPE as i32, + Enter = ffi::AKEYCODE_ENTER as i32, + Del = ffi::AKEYCODE_DEL as i32, + Grave = ffi::AKEYCODE_GRAVE as i32, + Minus = ffi::AKEYCODE_MINUS as i32, + Equals = ffi::AKEYCODE_EQUALS as i32, + LeftBracket = ffi::AKEYCODE_LEFT_BRACKET as i32, + RightBracket = ffi::AKEYCODE_RIGHT_BRACKET as i32, + Backslash = ffi::AKEYCODE_BACKSLASH as i32, + Semicolon = ffi::AKEYCODE_SEMICOLON as i32, + Apostrophe = ffi::AKEYCODE_APOSTROPHE as i32, + Slash = ffi::AKEYCODE_SLASH as i32, + At = ffi::AKEYCODE_AT as i32, + Num = ffi::AKEYCODE_NUM as i32, + Headsethook = ffi::AKEYCODE_HEADSETHOOK as i32, + Focus = ffi::AKEYCODE_FOCUS as i32, + Plus = ffi::AKEYCODE_PLUS as i32, + Menu = ffi::AKEYCODE_MENU as i32, + Notification = ffi::AKEYCODE_NOTIFICATION as i32, + Search = ffi::AKEYCODE_SEARCH as i32, + MediaPlayPause = ffi::AKEYCODE_MEDIA_PLAY_PAUSE as i32, + MediaStop = ffi::AKEYCODE_MEDIA_STOP as i32, + MediaNext = ffi::AKEYCODE_MEDIA_NEXT as i32, + MediaPrevious = ffi::AKEYCODE_MEDIA_PREVIOUS as i32, + MediaRewind = ffi::AKEYCODE_MEDIA_REWIND as i32, + MediaFastForward = ffi::AKEYCODE_MEDIA_FAST_FORWARD as i32, + Mute = ffi::AKEYCODE_MUTE as i32, + PageUp = ffi::AKEYCODE_PAGE_UP as i32, + PageDown = ffi::AKEYCODE_PAGE_DOWN as i32, + Pictsymbols = ffi::AKEYCODE_PICTSYMBOLS as i32, + SwitchCharset = ffi::AKEYCODE_SWITCH_CHARSET as i32, + ButtonA = ffi::AKEYCODE_BUTTON_A as i32, + ButtonB = ffi::AKEYCODE_BUTTON_B as i32, + ButtonC = ffi::AKEYCODE_BUTTON_C as i32, + ButtonX = ffi::AKEYCODE_BUTTON_X as i32, + ButtonY = ffi::AKEYCODE_BUTTON_Y as i32, + ButtonZ = ffi::AKEYCODE_BUTTON_Z as i32, + ButtonL1 = ffi::AKEYCODE_BUTTON_L1 as i32, + ButtonR1 = ffi::AKEYCODE_BUTTON_R1 as i32, + ButtonL2 = ffi::AKEYCODE_BUTTON_L2 as i32, + ButtonR2 = ffi::AKEYCODE_BUTTON_R2 as i32, + ButtonThumbl = ffi::AKEYCODE_BUTTON_THUMBL as i32, + ButtonThumbr = ffi::AKEYCODE_BUTTON_THUMBR as i32, + ButtonStart = ffi::AKEYCODE_BUTTON_START as i32, + ButtonSelect = ffi::AKEYCODE_BUTTON_SELECT as i32, + ButtonMode = ffi::AKEYCODE_BUTTON_MODE as i32, + Escape = ffi::AKEYCODE_ESCAPE as i32, + ForwardDel = ffi::AKEYCODE_FORWARD_DEL as i32, + CtrlLeft = ffi::AKEYCODE_CTRL_LEFT as i32, + CtrlRight = ffi::AKEYCODE_CTRL_RIGHT as i32, + CapsLock = ffi::AKEYCODE_CAPS_LOCK as i32, + ScrollLock = ffi::AKEYCODE_SCROLL_LOCK as i32, + MetaLeft = ffi::AKEYCODE_META_LEFT as i32, + MetaRight = ffi::AKEYCODE_META_RIGHT as i32, + Function = ffi::AKEYCODE_FUNCTION as i32, + Sysrq = ffi::AKEYCODE_SYSRQ as i32, + Break = ffi::AKEYCODE_BREAK as i32, + MoveHome = ffi::AKEYCODE_MOVE_HOME as i32, + MoveEnd = ffi::AKEYCODE_MOVE_END as i32, + Insert = ffi::AKEYCODE_INSERT as i32, + Forward = ffi::AKEYCODE_FORWARD as i32, + MediaPlay = ffi::AKEYCODE_MEDIA_PLAY as i32, + MediaPause = ffi::AKEYCODE_MEDIA_PAUSE as i32, + MediaClose = ffi::AKEYCODE_MEDIA_CLOSE as i32, + MediaEject = ffi::AKEYCODE_MEDIA_EJECT as i32, + MediaRecord = ffi::AKEYCODE_MEDIA_RECORD as i32, + F1 = ffi::AKEYCODE_F1 as i32, + F2 = ffi::AKEYCODE_F2 as i32, + F3 = ffi::AKEYCODE_F3 as i32, + F4 = ffi::AKEYCODE_F4 as i32, + F5 = ffi::AKEYCODE_F5 as i32, + F6 = ffi::AKEYCODE_F6 as i32, + F7 = ffi::AKEYCODE_F7 as i32, + F8 = ffi::AKEYCODE_F8 as i32, + F9 = ffi::AKEYCODE_F9 as i32, + F10 = ffi::AKEYCODE_F10 as i32, + F11 = ffi::AKEYCODE_F11 as i32, + F12 = ffi::AKEYCODE_F12 as i32, + NumLock = ffi::AKEYCODE_NUM_LOCK as i32, + Numpad0 = ffi::AKEYCODE_NUMPAD_0 as i32, + Numpad1 = ffi::AKEYCODE_NUMPAD_1 as i32, + Numpad2 = ffi::AKEYCODE_NUMPAD_2 as i32, + Numpad3 = ffi::AKEYCODE_NUMPAD_3 as i32, + Numpad4 = ffi::AKEYCODE_NUMPAD_4 as i32, + Numpad5 = ffi::AKEYCODE_NUMPAD_5 as i32, + Numpad6 = ffi::AKEYCODE_NUMPAD_6 as i32, + Numpad7 = ffi::AKEYCODE_NUMPAD_7 as i32, + Numpad8 = ffi::AKEYCODE_NUMPAD_8 as i32, + Numpad9 = ffi::AKEYCODE_NUMPAD_9 as i32, + NumpadDivide = ffi::AKEYCODE_NUMPAD_DIVIDE as i32, + NumpadMultiply = ffi::AKEYCODE_NUMPAD_MULTIPLY as i32, + NumpadSubtract = ffi::AKEYCODE_NUMPAD_SUBTRACT as i32, + NumpadAdd = ffi::AKEYCODE_NUMPAD_ADD as i32, + NumpadDot = ffi::AKEYCODE_NUMPAD_DOT as i32, + NumpadComma = ffi::AKEYCODE_NUMPAD_COMMA as i32, + NumpadEnter = ffi::AKEYCODE_NUMPAD_ENTER as i32, + NumpadEquals = ffi::AKEYCODE_NUMPAD_EQUALS as i32, + NumpadLeftParen = ffi::AKEYCODE_NUMPAD_LEFT_PAREN as i32, + NumpadRightParen = ffi::AKEYCODE_NUMPAD_RIGHT_PAREN as i32, + VolumeMute = ffi::AKEYCODE_VOLUME_MUTE as i32, + Info = ffi::AKEYCODE_INFO as i32, + ChannelUp = ffi::AKEYCODE_CHANNEL_UP as i32, + ChannelDown = ffi::AKEYCODE_CHANNEL_DOWN as i32, + ZoomIn = ffi::AKEYCODE_ZOOM_IN as i32, + ZoomOut = ffi::AKEYCODE_ZOOM_OUT as i32, + Tv = ffi::AKEYCODE_TV as i32, + Window = ffi::AKEYCODE_WINDOW as i32, + Guide = ffi::AKEYCODE_GUIDE as i32, + Dvr = ffi::AKEYCODE_DVR as i32, + Bookmark = ffi::AKEYCODE_BOOKMARK as i32, + Captions = ffi::AKEYCODE_CAPTIONS as i32, + Settings = ffi::AKEYCODE_SETTINGS as i32, + TvPower = ffi::AKEYCODE_TV_POWER as i32, + TvInput = ffi::AKEYCODE_TV_INPUT as i32, + StbPower = ffi::AKEYCODE_STB_POWER as i32, + StbInput = ffi::AKEYCODE_STB_INPUT as i32, + AvrPower = ffi::AKEYCODE_AVR_POWER as i32, + AvrInput = ffi::AKEYCODE_AVR_INPUT as i32, + ProgRed = ffi::AKEYCODE_PROG_RED as i32, + ProgGreen = ffi::AKEYCODE_PROG_GREEN as i32, + ProgYellow = ffi::AKEYCODE_PROG_YELLOW as i32, + ProgBlue = ffi::AKEYCODE_PROG_BLUE as i32, + AppSwitch = ffi::AKEYCODE_APP_SWITCH as i32, + Button1 = ffi::AKEYCODE_BUTTON_1 as i32, + Button2 = ffi::AKEYCODE_BUTTON_2 as i32, + Button3 = ffi::AKEYCODE_BUTTON_3 as i32, + Button4 = ffi::AKEYCODE_BUTTON_4 as i32, + Button5 = ffi::AKEYCODE_BUTTON_5 as i32, + Button6 = ffi::AKEYCODE_BUTTON_6 as i32, + Button7 = ffi::AKEYCODE_BUTTON_7 as i32, + Button8 = ffi::AKEYCODE_BUTTON_8 as i32, + Button9 = ffi::AKEYCODE_BUTTON_9 as i32, + Button10 = ffi::AKEYCODE_BUTTON_10 as i32, + Button11 = ffi::AKEYCODE_BUTTON_11 as i32, + Button12 = ffi::AKEYCODE_BUTTON_12 as i32, + Button13 = ffi::AKEYCODE_BUTTON_13 as i32, + Button14 = ffi::AKEYCODE_BUTTON_14 as i32, + Button15 = ffi::AKEYCODE_BUTTON_15 as i32, + Button16 = ffi::AKEYCODE_BUTTON_16 as i32, + LanguageSwitch = ffi::AKEYCODE_LANGUAGE_SWITCH as i32, + MannerMode = ffi::AKEYCODE_MANNER_MODE as i32, + Keycode3dMode = ffi::AKEYCODE_3D_MODE as i32, + Contacts = ffi::AKEYCODE_CONTACTS as i32, + Calendar = ffi::AKEYCODE_CALENDAR as i32, + Music = ffi::AKEYCODE_MUSIC as i32, + Calculator = ffi::AKEYCODE_CALCULATOR as i32, + ZenkakuHankaku = ffi::AKEYCODE_ZENKAKU_HANKAKU as i32, + Eisu = ffi::AKEYCODE_EISU as i32, + Muhenkan = ffi::AKEYCODE_MUHENKAN as i32, + Henkan = ffi::AKEYCODE_HENKAN as i32, + KatakanaHiragana = ffi::AKEYCODE_KATAKANA_HIRAGANA as i32, + Yen = ffi::AKEYCODE_YEN as i32, + Ro = ffi::AKEYCODE_RO as i32, + Kana = ffi::AKEYCODE_KANA as i32, + Assist = ffi::AKEYCODE_ASSIST as i32, + BrightnessDown = ffi::AKEYCODE_BRIGHTNESS_DOWN as i32, + BrightnessUp = ffi::AKEYCODE_BRIGHTNESS_UP as i32, + MediaAudioTrack = ffi::AKEYCODE_MEDIA_AUDIO_TRACK as i32, + Sleep = ffi::AKEYCODE_SLEEP as i32, + Wakeup = ffi::AKEYCODE_WAKEUP as i32, + Pairing = ffi::AKEYCODE_PAIRING as i32, + MediaTopMenu = ffi::AKEYCODE_MEDIA_TOP_MENU as i32, + Keycode11 = ffi::AKEYCODE_11 as i32, + Keycode12 = ffi::AKEYCODE_12 as i32, + LastChannel = ffi::AKEYCODE_LAST_CHANNEL as i32, + TvDataService = ffi::AKEYCODE_TV_DATA_SERVICE as i32, + VoiceAssist = ffi::AKEYCODE_VOICE_ASSIST as i32, + TvRadioService = ffi::AKEYCODE_TV_RADIO_SERVICE as i32, + TvTeletext = ffi::AKEYCODE_TV_TELETEXT as i32, + TvNumberEntry = ffi::AKEYCODE_TV_NUMBER_ENTRY as i32, + TvTerrestrialAnalog = ffi::AKEYCODE_TV_TERRESTRIAL_ANALOG as i32, + TvTerrestrialDigital = ffi::AKEYCODE_TV_TERRESTRIAL_DIGITAL as i32, + TvSatellite = ffi::AKEYCODE_TV_SATELLITE as i32, + TvSatelliteBs = ffi::AKEYCODE_TV_SATELLITE_BS as i32, + TvSatelliteCs = ffi::AKEYCODE_TV_SATELLITE_CS as i32, + TvSatelliteService = ffi::AKEYCODE_TV_SATELLITE_SERVICE as i32, + TvNetwork = ffi::AKEYCODE_TV_NETWORK as i32, + TvAntennaCable = ffi::AKEYCODE_TV_ANTENNA_CABLE as i32, + TvInputHdmi1 = ffi::AKEYCODE_TV_INPUT_HDMI_1 as i32, + TvInputHdmi2 = ffi::AKEYCODE_TV_INPUT_HDMI_2 as i32, + TvInputHdmi3 = ffi::AKEYCODE_TV_INPUT_HDMI_3 as i32, + TvInputHdmi4 = ffi::AKEYCODE_TV_INPUT_HDMI_4 as i32, + TvInputComposite1 = ffi::AKEYCODE_TV_INPUT_COMPOSITE_1 as i32, + TvInputComposite2 = ffi::AKEYCODE_TV_INPUT_COMPOSITE_2 as i32, + TvInputComponent1 = ffi::AKEYCODE_TV_INPUT_COMPONENT_1 as i32, + TvInputComponent2 = ffi::AKEYCODE_TV_INPUT_COMPONENT_2 as i32, + TvInputVga1 = ffi::AKEYCODE_TV_INPUT_VGA_1 as i32, + TvAudioDescription = ffi::AKEYCODE_TV_AUDIO_DESCRIPTION as i32, + TvAudioDescriptionMixUp = ffi::AKEYCODE_TV_AUDIO_DESCRIPTION_MIX_UP as i32, + TvAudioDescriptionMixDown = ffi::AKEYCODE_TV_AUDIO_DESCRIPTION_MIX_DOWN as i32, + TvZoomMode = ffi::AKEYCODE_TV_ZOOM_MODE as i32, + TvContentsMenu = ffi::AKEYCODE_TV_CONTENTS_MENU as i32, + TvMediaContextMenu = ffi::AKEYCODE_TV_MEDIA_CONTEXT_MENU as i32, + TvTimerProgramming = ffi::AKEYCODE_TV_TIMER_PROGRAMMING as i32, + Help = ffi::AKEYCODE_HELP as i32, + NavigatePrevious = ffi::AKEYCODE_NAVIGATE_PREVIOUS as i32, + NavigateNext = ffi::AKEYCODE_NAVIGATE_NEXT as i32, + NavigateIn = ffi::AKEYCODE_NAVIGATE_IN as i32, + NavigateOut = ffi::AKEYCODE_NAVIGATE_OUT as i32, + StemPrimary = ffi::AKEYCODE_STEM_PRIMARY as i32, + Stem1 = ffi::AKEYCODE_STEM_1 as i32, + Stem2 = ffi::AKEYCODE_STEM_2 as i32, + Stem3 = ffi::AKEYCODE_STEM_3 as i32, + DpadUpLeft = ffi::AKEYCODE_DPAD_UP_LEFT as i32, + DpadDownLeft = ffi::AKEYCODE_DPAD_DOWN_LEFT as i32, + DpadUpRight = ffi::AKEYCODE_DPAD_UP_RIGHT as i32, + DpadDownRight = ffi::AKEYCODE_DPAD_DOWN_RIGHT as i32, + MediaSkipForward = ffi::AKEYCODE_MEDIA_SKIP_FORWARD as i32, + MediaSkipBackward = ffi::AKEYCODE_MEDIA_SKIP_BACKWARD as i32, + MediaStepForward = ffi::AKEYCODE_MEDIA_STEP_FORWARD as i32, + MediaStepBackward = ffi::AKEYCODE_MEDIA_STEP_BACKWARD as i32, + SoftSleep = ffi::AKEYCODE_SOFT_SLEEP as i32, + Cut = ffi::AKEYCODE_CUT as i32, + Copy = ffi::AKEYCODE_COPY as i32, + Paste = ffi::AKEYCODE_PASTE as i32, + SystemNavigationUp = ffi::AKEYCODE_SYSTEM_NAVIGATION_UP as i32, + SystemNavigationDown = ffi::AKEYCODE_SYSTEM_NAVIGATION_DOWN as i32, + SystemNavigationLeft = ffi::AKEYCODE_SYSTEM_NAVIGATION_LEFT as i32, + SystemNavigationRight = ffi::AKEYCODE_SYSTEM_NAVIGATION_RIGHT as i32, + AllApps = ffi::AKEYCODE_ALL_APPS as i32, + Refresh = ffi::AKEYCODE_REFRESH as i32, + ThumbsUp = ffi::AKEYCODE_THUMBS_UP as i32, + ThumbsDown = ffi::AKEYCODE_THUMBS_DOWN as i32, + ProfileSwitch = ffi::AKEYCODE_PROFILE_SWITCH as i32, + + #[doc(hidden)] + #[num_enum(catch_all)] + __Unknown(i32), +} + +impl KeyEvent { + /// Constructs a KeyEvent from a pointer to a native [`ffi::AInputEvent`] + /// + /// # Safety + /// By calling this method, you assert that the pointer is a valid, non-null pointer to an + /// [`ffi::AInputEvent`], and that [`ffi::AInputEvent`] is an `AKeyEvent`. + #[inline] + pub unsafe fn from_ptr(ptr: NonNull) -> Self { + Self { ptr } + } + + /// Creates a native [`InputEvent`] object that is a copy of the specified Java + /// [`android.view.KeyEvent`]. The result may be used with generic and [`KeyEvent`]-specific + /// functions. + /// + /// # Safety + /// + /// This function should be called with a healthy JVM pointer and with a non-null + /// [`android.view.KeyEvent`]. + /// + /// [`android.view.KeyEvent`]: https://developer.android.com/reference/android/view/KeyEvent + #[cfg(feature = "api-level-31")] + #[doc(alias = "AKeyEvent_fromJava")] + pub unsafe fn from_java(env: *mut JNIEnv, key_event: jobject) -> Option { + let ptr = unsafe { ffi::AKeyEvent_fromJava(env, key_event) }; + Some(InputEventJava(InputEvent::KeyEvent(Self::from_ptr( + NonNull::new(ptr.cast_mut())?, + )))) + } + + /// Returns a pointer to the native [`ffi::AInputEvent`]. + #[inline] + pub fn ptr(&self) -> NonNull { + self.ptr + } + + /// Returns the key action represented by this event. + /// + /// See [the NDK + /// docs](https://developer.android.com/ndk/reference/group/input#akeyevent_getaction) + #[inline] + pub fn action(&self) -> KeyAction { + let action = unsafe { ffi::AKeyEvent_getAction(self.ptr.as_ptr()) }; + action.into() + } + + /// Get the source of the event. + /// + /// See [the NDK + /// docs](https://developer.android.com/ndk/reference/group/input#ainputevent_getsource) + #[inline] + pub fn source(&self) -> Source { + let source = unsafe { ffi::AInputEvent_getSource(self.ptr.as_ptr()) }; + source.into() + } + + /// Get the device id associated with the event. + /// + /// See [the NDK + /// docs](https://developer.android.com/ndk/reference/group/input#ainputevent_getdeviceid) + #[inline] + pub fn device_id(&self) -> i32 { + unsafe { ffi::AInputEvent_getDeviceId(self.ptr.as_ptr()) } + } + + /// Returns the last time the key was pressed. This is on the scale of + /// `java.lang.System.nanoTime()`, which has nanosecond precision, but no defined start time. + /// + /// See [the NDK + /// docs](https://developer.android.com/ndk/reference/group/input#akeyevent_getdowntime) + #[inline] + pub fn down_time(&self) -> i64 { + unsafe { ffi::AKeyEvent_getDownTime(self.ptr.as_ptr()) } + } + + /// Returns the time this event occured. This is on the scale of + /// `java.lang.System.nanoTime()`, which has nanosecond precision, but no defined start time. + /// + /// See [the NDK + /// docs](https://developer.android.com/ndk/reference/group/input#akeyevent_geteventtime) + #[inline] + pub fn event_time(&self) -> i64 { + unsafe { ffi::AKeyEvent_getEventTime(self.ptr.as_ptr()) } + } + + /// Returns the keycode associated with this key event + /// + /// See [the NDK + /// docs](https://developer.android.com/ndk/reference/group/input#akeyevent_getkeycode) + #[inline] + pub fn key_code(&self) -> Keycode { + let keycode = unsafe { ffi::AKeyEvent_getKeyCode(self.ptr.as_ptr()) }; + keycode.into() + } + + /// Returns the number of repeats of a key. + /// + /// See [the NDK + /// docs](https://developer.android.com/ndk/reference/group/input#akeyevent_getrepeatcount) + #[inline] + pub fn repeat_count(&self) -> i32 { + unsafe { ffi::AKeyEvent_getRepeatCount(self.ptr.as_ptr()) } + } + + /// Returns the hardware keycode of a key. This varies from device to device. + /// + /// See [the NDK + /// docs](https://developer.android.com/ndk/reference/group/input#akeyevent_getscancode) + #[inline] + pub fn scan_code(&self) -> i32 { + unsafe { ffi::AKeyEvent_getScanCode(self.ptr.as_ptr()) } + } +} + +/// Flags associated with [`KeyEvent`]. +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub struct KeyEventFlags(pub u32); + +impl KeyEventFlags { + #[inline] + pub fn cancelled(&self) -> bool { + self.0 & ffi::AKEY_EVENT_FLAG_CANCELED != 0 + } + #[inline] + pub fn cancelled_long_press(&self) -> bool { + self.0 & ffi::AKEY_EVENT_FLAG_CANCELED_LONG_PRESS != 0 + } + #[inline] + pub fn editor_action(&self) -> bool { + self.0 & ffi::AKEY_EVENT_FLAG_EDITOR_ACTION != 0 + } + #[inline] + pub fn fallback(&self) -> bool { + self.0 & ffi::AKEY_EVENT_FLAG_FALLBACK != 0 + } + #[inline] + pub fn from_system(&self) -> bool { + self.0 & ffi::AKEY_EVENT_FLAG_FROM_SYSTEM != 0 + } + #[inline] + pub fn keep_touch_mode(&self) -> bool { + self.0 & ffi::AKEY_EVENT_FLAG_KEEP_TOUCH_MODE != 0 + } + #[inline] + pub fn long_press(&self) -> bool { + self.0 & ffi::AKEY_EVENT_FLAG_LONG_PRESS != 0 + } + #[inline] + pub fn soft_keyboard(&self) -> bool { + self.0 & ffi::AKEY_EVENT_FLAG_SOFT_KEYBOARD != 0 + } + #[inline] + pub fn tracking(&self) -> bool { + self.0 & ffi::AKEY_EVENT_FLAG_TRACKING != 0 + } + #[inline] + pub fn virtual_hard_key(&self) -> bool { + self.0 & ffi::AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY != 0 + } + #[inline] + pub fn woke_here(&self) -> bool { + self.0 & ffi::AKEY_EVENT_FLAG_WOKE_HERE != 0 + } +} + +impl KeyEvent { + /// Flags associated with this [`KeyEvent`]. + /// + /// See [the NDK docs](https://developer.android.com/ndk/reference/group/input#akeyevent_getflags) + #[inline] + pub fn flags(&self) -> KeyEventFlags { + unsafe { KeyEventFlags(ffi::AKeyEvent_getFlags(self.ptr.as_ptr()) as u32) } + } + + /// Returns the state of the modifiers during this key event, represented by a bitmask. + /// + /// See [the NDK + /// docs](https://developer.android.com/ndk/reference/group/input#akeyevent_getmetastate) + #[inline] + pub fn meta_state(&self) -> MetaState { + unsafe { MetaState(ffi::AKeyEvent_getMetaState(self.ptr.as_ptr()) as u32) } + } +} diff --git a/clients/android/native/vendor/ndk/src/font.rs b/clients/android/native/vendor/ndk/src/font.rs new file mode 100644 index 00000000..70f69003 --- /dev/null +++ b/clients/android/native/vendor/ndk/src/font.rs @@ -0,0 +1,552 @@ +//! Bindings for [`AFont`], [`AFontMatcher`], and [`ASystemFontIterator`] +//! +//! [`AFont`]: https://developer.android.com/ndk/reference/group/font +//! [`AFontMatcher`]: https://developer.android.com/ndk/reference/group/font#afontmatcher_create +//! [`ASystemFontIterator`]: https://developer.android.com/ndk/reference/group/font#asystemfontiterator_open + +#![cfg(feature = "api-level-29")] + +use std::convert::TryFrom; +use std::ffi::{CStr, OsStr}; +use std::fmt::{self, Write}; +use std::os::unix::prelude::OsStrExt; +use std::path::Path; +use std::ptr::NonNull; + +use num_enum::IntoPrimitive; + +/// An integer holding a valid font weight value between 1 and 1000. +/// +/// See the [`Font::weight`] definition for more details. +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] +pub struct FontWeight(u16); + +impl FontWeight { + pub const fn new(value: u16) -> Result { + if Self::MIN.0 <= value && value <= Self::MAX.0 { + Ok(Self(value)) + } else { + Err(FontWeightValueError(())) + } + } + + pub const fn to_u16(self) -> u16 { + self.0 + } + + /// The minimum value for the font weight value. Unlike [`ffi::AFONT_WEIGHT_MIN`] being `0`, + /// [`FontWeight::MIN`] is `1` to make the `MIN..MAX` range be inclusive, keeping consistency + /// between [`FontWeight`] and other types like `std::num::NonZeroU*`. + pub const MIN: FontWeight = FontWeight(ffi::AFONT_WEIGHT_MIN as u16 + 1); + + /// A font weight value for the thin weight. + pub const THIN: FontWeight = FontWeight(ffi::AFONT_WEIGHT_THIN as u16); + + /// A font weight value for the extra-light weight. + pub const EXTRA_LIGHT: FontWeight = FontWeight(ffi::AFONT_WEIGHT_EXTRA_LIGHT as u16); + + /// A font weight value for the light weight. + pub const LIGHT: FontWeight = FontWeight(ffi::AFONT_WEIGHT_LIGHT as u16); + + /// A font weight value for the normal weight. + pub const NORMAL: FontWeight = FontWeight(ffi::AFONT_WEIGHT_NORMAL as u16); + + /// A font weight value for the medium weight. + pub const MEDIUM: FontWeight = FontWeight(ffi::AFONT_WEIGHT_MEDIUM as u16); + + /// A font weight value for the semi-bold weight. + pub const SEMI_BOLD: FontWeight = FontWeight(ffi::AFONT_WEIGHT_SEMI_BOLD as u16); + + /// A font weight value for the bold weight. + pub const BOLD: FontWeight = FontWeight(ffi::AFONT_WEIGHT_BOLD as u16); + + /// A font weight value for the extra-bold weight. + pub const EXTRA_BOLD: FontWeight = FontWeight(ffi::AFONT_WEIGHT_EXTRA_BOLD as u16); + + /// A font weight value for the black weight. + pub const BLACK: FontWeight = FontWeight(ffi::AFONT_WEIGHT_BLACK as u16); + + /// The maximum value for the font weight value. + pub const MAX: FontWeight = FontWeight(ffi::AFONT_WEIGHT_MAX as u16); +} + +impl fmt::Display for FontWeight { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(match *self { + FontWeight::THIN => "Thin", + FontWeight::EXTRA_LIGHT => "Extra Light (Ultra Light)", + FontWeight::LIGHT => "Light", + FontWeight::NORMAL => "Normal (Regular)", + FontWeight::MEDIUM => "Medium", + FontWeight::SEMI_BOLD => "Semi Bold (Demi Bold)", + FontWeight::BOLD => "Bold", + FontWeight::EXTRA_BOLD => "Extra Bold (Ultra Bold)", + FontWeight::BLACK => "Black (Heavy)", + _ => return writeln!(f, "{}", self.0), + }) + } +} + +/// The error type returned when an invalid font weight value is passed. +#[derive(Debug)] +pub struct FontWeightValueError(()); + +impl fmt::Display for FontWeightValueError { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt.write_str("font weight must be positive and less than or equal to 1000") + } +} + +impl std::error::Error for FontWeightValueError {} + +impl TryFrom for FontWeight { + type Error = FontWeightValueError; + + fn try_from(value: u16) -> Result { + FontWeight::new(value) + } +} + +/// A 4-byte integer representing an OpenType axis tag. +#[derive(Clone, Copy, PartialEq, Eq)] +pub struct AxisTag(u32); + +impl AxisTag { + /// Checks whether the given 4-byte array can construct a valid axis tag and returns + /// [`Ok(AxisTag)`] if the array is valid. + /// + /// Each byte in a tag must be in the range 0x20 to 0x7E. A space character cannot be followed + /// by a non-space character. A tag must have one to four non-space characters. See the + /// [OpenType spec] for more details. + /// + /// [OpenType spec]: https://learn.microsoft.com/en-us/typography/opentype/spec/otff#data-types + pub const fn from_be_bytes_checked(value: [u8; 4]) -> Result { + // Each byte in a tag must be in the range 0x20 to 0x7E. + macro_rules! check_byte_range { + ($($e:expr)+) => { + $( + if !(value[$e] as char).is_ascii_graphic() && value[$e] != b' ' { + return Err(AxisTagValueError::InvalidCharacter); + } + )+ + }; + } + check_byte_range!(0 1 2 3); + + if value[0] == b' ' { + return Err( + if value[1] == b' ' && value[2] == b' ' && value[3] == b' ' { + // A tag must have one to four non-space characters. + AxisTagValueError::EmptyTag + } else { + // A space character cannot be followed by a non-space character. + AxisTagValueError::InvalidSpacePadding + }, + ); + } + + macro_rules! check_if_valid { + ($e:expr ; $($f:expr)+) => { + if value[$e] == b' ' { + return if true $(&& value[$f] == b' ')+ { + Ok(Self(u32::from_be_bytes(value))) + } else { + // A space character cannot be followed by a non-space character. + Err(AxisTagValueError::InvalidSpacePadding) + }; + } + }; + } + + check_if_valid!(1; 2 3); + check_if_valid!(2; 3); + + // Whether or not value[3] is b' ', value is a valid axis tag. + Ok(Self(u32::from_be_bytes(value))) + } + + /// Checks whether the given 4-byte array can construct a valid axis tag and returns + /// [`Ok(AxisTag)`] if the array is valid. + /// + /// See [`AxisTag::from_be()`] for more details. + pub const fn from_be_checked(value: u32) -> Result { + Self::from_be_bytes_checked(value.to_be_bytes()) + } + + /// Construct an axis tag from the given 4-byte array. If the resulting axis tag is invalid, + /// this function panics. + /// + /// See [`AxisTag::from_be()`] for more details. + pub const fn from_be_bytes(value: [u8; 4]) -> Self { + Self::unwrap_result(Self::from_be_bytes_checked(value)) + } + + /// Construct an axis tag from the given 4-byte integer. If the resulting axis tag is invalid, + /// this function panics. + /// + /// See [`AxisTag::from_be()`] for more details. + pub const fn from_be(value: u32) -> Self { + Self::unwrap_result(Self::from_be_checked(value)) + } + + /// const-version of [`Result::unwrap`]. Should be removed when [`Option::unwrap`] or + /// [`Result::unwrap`] become `const`-stable. + const fn unwrap_result(result: Result) -> Self { + match result { + Ok(t) => t, + Err(e) => panic!("{}", e.as_str()), + } + } + + pub const fn to_u32(self) -> u32 { + self.0 + } + + pub const fn to_be_bytes(self) -> [u8; 4] { + self.0.to_be_bytes() + } +} + +impl fmt::Display for AxisTag { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let bytes = self.to_be_bytes(); + f.write_char(bytes[0] as char)?; + f.write_char(bytes[1] as char)?; + f.write_char(bytes[2] as char)?; + f.write_char(bytes[3] as char) + } +} + +impl fmt::Debug for AxisTag { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "AxisTag({} {:#x})", self, self.0) + } +} + +/// The error type returned when an invalid axis tag value is passed. +#[derive(Clone, Copy, Debug)] +pub enum AxisTagValueError { + /// There is a byte not in the range 0x20 to 0x7E. + InvalidCharacter, + /// There is a space character followed by a non-space character. + InvalidSpacePadding, + /// The tag only consists of space characters. + EmptyTag, +} + +impl AxisTagValueError { + pub const fn as_str(&self) -> &'static str { + match self { + Self::InvalidCharacter => "each byte in an axis tag must be in the range 0x20 to 0x7E", + Self::InvalidSpacePadding => { + "a space character cannot be followed by a non-space character" + } + Self::EmptyTag => "a tag must have one to four non-space characters", + } + } +} + +impl fmt::Display for AxisTagValueError { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt.write_str(self.as_str()) + } +} + +impl std::error::Error for AxisTagValueError {} + +/// A native [`AFont *`] +/// +/// [`AFont *`]: https://developer.android.com/ndk/reference/group/font +#[derive(Debug)] +pub struct Font { + ptr: NonNull, +} + +impl Font { + /// Assumes ownership of `ptr`. + /// + /// # Safety + /// `ptr` must be a valid owning pointer to an Android [`ffi::AFont`]. + pub unsafe fn from_ptr(ptr: NonNull) -> Self { + Self { ptr } + } + + /// Returns s the pointer to the native [`ffi::AFont`]. + pub fn ptr(&self) -> NonNull { + self.ptr + } + + /// Returns a count of font variation settings associated with the current font. + /// + /// The font variation settings are provided as multiple tag-value pairs. + /// + /// For example, bold italic font may have following font variation settings: `'wght' 700`, + /// `'slnt' -12`. In this case, [`Font::axis_count()`] returns `2` and [`Font::axis_tag_at()`] and + /// [`Font::axis_value_at()`] return those variation names and the corresponding values. + /// + /// ```no_run + /// use ndk::font::Font; + /// + /// let font: Font = todo!(); + /// for idx in 0..font.axis_count() { + /// log::debug!("{}: {}", font.axis_tag_at(idx), font.axis_value_at(idx)); + /// } + /// // Output: + /// // wght: 700 + /// // slnt: -12 + /// ``` + pub fn axis_count(&self) -> usize { + unsafe { ffi::AFont_getAxisCount(self.ptr.as_ptr()) } + } + + /// Returns an OpenType axis tag associated with the current font. + /// + /// See [`Font::axis_count()`] for more details. + pub fn axis_tag_at(&self, idx: usize) -> AxisTag { + // Android returns Axis Tag in big-endian. + // See https://cs.android.com/android/platform/superproject/+/refs/heads/master:frameworks/base/native/android/system_fonts.cpp;l=197 for details + AxisTag(unsafe { ffi::AFont_getAxisTag(self.ptr.as_ptr(), idx as u32) }) + } + + /// Returns an OpenType axis value associated with the current font. + /// + /// See [`Font::axis_count()`] for more details. + pub fn axis_value_at(&self, idx: usize) -> f32 { + unsafe { ffi::AFont_getAxisValue(self.ptr.as_ptr(), idx as u32) } + } + + /// Returns a font collection index value associated with the current font. + /// + /// In case the target font file is a font collection (e.g. `.ttc` or `.otc`), this returns a + /// non-negative value as a font offset in the collection. This always returns 0 if the target + /// font file is a regular font. + pub fn collection_index(&self) -> usize { + unsafe { ffi::AFont_getCollectionIndex(self.ptr.as_ptr()) } + } + + /// Returns an absolute path to the current font file. + /// + /// Here is a list of font formats returned by this method: + /// + /// * OpenType + /// * OpenType Font Collection + /// * TrueType + /// * TrueType Collection + /// + /// The file extension could be one of `*.otf`, `*.ttf`, `*.otc` or `*.ttc`. + /// The font file specified by the returned path is guaranteed to be openable with `O_RDONLY`. + pub fn path(&self) -> &Path { + let path = unsafe { CStr::from_ptr(ffi::AFont_getFontFilePath(self.ptr.as_ptr())) }; + OsStr::from_bytes(path.to_bytes()).as_ref() + } + + /// Returns an IETF BCP47 compliant language tag associated with the current font. + /// + /// For information about IETF BCP47, read [`Locale.forLanguageTag(java.lang.String)`]. + /// + /// [`Locale.forLanguageTag(java.lang.String)`]: https://developer.android.com/reference/java/util/Locale.html#forLanguageTag(java.lang.String) + pub fn locale(&self) -> Option<&CStr> { + let ptr = unsafe { ffi::AFont_getLocale(self.ptr.as_ptr()) }; + if ptr.is_null() { + None + } else { + Some(unsafe { CStr::from_ptr(ptr) }) + } + } + + /// Returns a weight value associated with the current font. + /// + /// The weight values are positive and less than or equal to 1000. Here are pairs of the common + /// names and their values. + /// + /// | Value | Name | NDK Definition | + /// | ----- | ------------------------- | --------------------------- | + /// | 100 | Thin | [`FontWeight::THIN`] | + /// | 200 | Extra Light (Ultra Light) | [`FontWeight::EXTRA_LIGHT`] | + /// | 300 | Light | [`FontWeight::LIGHT`] | + /// | 400 | Normal (Regular) | [`FontWeight::NORMAL`] | + /// | 500 | Medium | [`FontWeight::MEDIUM`] | + /// | 600 | Semi Bold (Demi Bold) | [`FontWeight::SEMI_BOLD`] | + /// | 700 | Bold | [`FontWeight::BOLD`] | + /// | 800 | Extra Bold (Ultra Bold) | [`FontWeight::EXTRA_BOLD`] | + /// | 900 | Black (Heavy) | [`FontWeight::BLACK`] | + pub fn weight(&self) -> FontWeight { + FontWeight(unsafe { ffi::AFont_getWeight(self.ptr.as_ptr()) }) + } + + /// Returns [`true`] if the current font is italic, otherwise returns [`false`]. + pub fn is_italic(&self) -> bool { + unsafe { ffi::AFont_isItalic(self.ptr.as_ptr()) } + } +} + +impl Drop for Font { + fn drop(&mut self) { + unsafe { ffi::AFont_close(self.ptr.as_ptr()) } + } +} + +/// Corresponds to [`AFAMILY_VARIANT_*`]. +/// +/// [`AFAMILY_VARIANT_*`]: https://developer.android.com/ndk/reference/group/font#group___font_1gga96a58e29e8dbf2b5bdeb775cba46556ea662aafc7016e35d6758da93416fc0833 +#[repr(u32)] +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, IntoPrimitive)] +#[non_exhaustive] +pub enum FamilyVariant { + /// A family variant value for the compact font family variant. + /// The compact font family has Latin-based vertical metrics. + Compact = ffi::AFAMILY_VARIANT_COMPACT, + /// A family variant value for the system default variant. + Default = ffi::AFAMILY_VARIANT_DEFAULT, + /// A family variant value for the elegant font family variant. + /// The elegant font family may have larger vertical metrics than Latin font. + Elegant = ffi::AFAMILY_VARIANT_ELEGANT, + + #[doc(hidden)] + #[num_enum(catch_all)] + __Unknown(u32), +} + +/// A native [`AFontMatcher *`] +/// +/// [`AFontMatcher *`]: https://developer.android.com/ndk/reference/group/font#afontmatcher_create +#[derive(Debug)] +pub struct FontMatcher { + ptr: NonNull, +} + +impl FontMatcher { + /// Assumes ownership of `ptr`. + /// + /// # Safety + /// `ptr` must be a valid owning pointer to an Android [`ffi::AFontMatcher`]. + pub unsafe fn from_ptr(ptr: NonNull) -> Self { + Self { ptr } + } + + /// Returns s the pointer to the native [`ffi::AFontMatcher`]. + pub fn ptr(&self) -> NonNull { + self.ptr + } + + /// Creates a new [`FontMatcher`] object. [`FontMatcher`] selects the best font from the + /// parameters set by the user. + pub fn new() -> Self { + let ptr = NonNull::new(unsafe { ffi::AFontMatcher_create() }) + .expect("AFontMatcher_create returned NULL"); + unsafe { FontMatcher::from_ptr(ptr) } + } + + /// Performs the matching from the generic font family for the text and select one font. + /// + /// For more information about generic font families, please read the + /// [W3C spec](https://www.w3.org/TR/css-fonts-4/#generic-font-families). + /// + /// Even if no font can render the given text, this function will return a non-null result for + /// drawing Tofu character. + /// + /// # Parameters + /// + /// - `family_name`: A font family name. + /// - `text`: A UTF-16 encoded text buffer to be rendered. If an empty string is given, this + /// function will panic. + /// - `run_length_out`: Set this to [`Some`] if you want to get the length of the text run with + /// the font returned. + pub fn match_font( + &mut self, + family_name: &CStr, + text: &[u16], + run_length_out: Option<&mut u32>, + ) -> Font { + if text.is_empty() { + panic!("text is empty"); + } + unsafe { + Font::from_ptr( + NonNull::new(ffi::AFontMatcher_match( + self.ptr.as_ptr(), + family_name.as_ptr(), + text.as_ptr(), + text.len() as _, + run_length_out.map_or(std::ptr::null_mut(), |u| u), + )) + .expect("AFontMatcher_match returned NULL"), + ) + } + } + + /// Sets the family variant of the font to be matched. + /// + /// If this function is not called, the match is performed with [`FamilyVariant::Default`]. + pub fn set_family_variant(&mut self, family_variant: FamilyVariant) { + unsafe { ffi::AFontMatcher_setFamilyVariant(self.ptr.as_ptr(), family_variant.into()) } + } + + /// Sets the locale of the font to be matched. + /// + /// If this function is not called, the match is performed with an empty locale list. + /// + /// # Parameters + /// + /// - `language_tags`: comma separated IETF BCP47 compliant language tags. + pub fn set_locales(&mut self, language_tags: &CStr) { + unsafe { ffi::AFontMatcher_setLocales(self.ptr.as_ptr(), language_tags.as_ptr()) } + } + + /// Sets the style of the font to be matched. + /// + /// If this function is not called, the match is performed with [`FontWeight::NORMAL`] with non-italic style. + pub fn set_style(&mut self, weight: FontWeight, italic: bool) { + unsafe { ffi::AFontMatcher_setStyle(self.ptr.as_ptr(), weight.to_u16(), italic) } + } +} + +impl Drop for FontMatcher { + fn drop(&mut self) { + unsafe { ffi::AFontMatcher_destroy(self.ptr.as_ptr()) } + } +} + +/// A native [`ASystemFontIterator *`] +/// +/// [`ASystemFontIterator *`]: https://developer.android.com/ndk/reference/group/font#asystemfontiterator_open +#[derive(Debug)] +pub struct SystemFontIterator { + ptr: NonNull, +} + +impl SystemFontIterator { + /// Assumes ownership of `ptr`. + /// + /// # Safety + /// `ptr` must be a valid owning pointer to an Android [`ffi::ASystemFontIterator`]. + pub unsafe fn from_ptr(ptr: NonNull) -> Self { + Self { ptr } + } + + /// Returns the pointer to the native [`ffi::ASystemFontIterator`]. + pub fn ptr(&self) -> NonNull { + self.ptr + } + + /// Creates a system font iterator. + pub fn new() -> Option { + NonNull::new(unsafe { ffi::ASystemFontIterator_open() }) + .map(|p| unsafe { SystemFontIterator::from_ptr(p) }) + } +} + +impl Iterator for SystemFontIterator { + type Item = Font; + + fn next(&mut self) -> Option { + NonNull::new(unsafe { ffi::ASystemFontIterator_next(self.ptr.as_ptr()) }) + .map(|p| unsafe { Font::from_ptr(p) }) + } +} + +impl Drop for SystemFontIterator { + fn drop(&mut self) { + unsafe { ffi::ASystemFontIterator_close(self.ptr.as_ptr()) } + } +} diff --git a/clients/android/native/vendor/ndk/src/hardware_buffer.rs b/clients/android/native/vendor/ndk/src/hardware_buffer.rs new file mode 100644 index 00000000..252b8bb1 --- /dev/null +++ b/clients/android/native/vendor/ndk/src/hardware_buffer.rs @@ -0,0 +1,640 @@ +//! Bindings for [`AHardwareBuffer`] +//! +//! [`AHardwareBuffer`]: https://developer.android.com/ndk/reference/group/a-hardware-buffer#ahardwarebuffer + +#![cfg(feature = "api-level-26")] + +use std::{ + io::Result, + mem::MaybeUninit, + ops::Deref, + os::{ + fd::{AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, OwnedFd}, + raw::c_void, + }, + ptr::NonNull, +}; + +use jni_sys::{jobject, JNIEnv}; + +use super::{hardware_buffer_format::HardwareBufferFormat, utils::status_to_io_result}; + +bitflags::bitflags! { + /// Buffer usage flags, specifying how the buffer will be accessed. + #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)] + #[doc(alias = "AHardwareBuffer_UsageFlags")] + pub struct HardwareBufferUsage : u64 { + /// The buffer will never be locked for direct CPU reads using the + /// [`HardwareBuffer::lock()`] function. Note that reading the buffer using OpenGL or Vulkan + /// functions or memory mappings is still allowed. + #[doc(alias = "AHARDWAREBUFFER_USAGE_CPU_READ_NEVER")] + const CPU_READ_NEVER = ffi::AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_CPU_READ_NEVER.0; + /// The buffer will sometimes be locked for direct CPU reads using the + /// [`HardwareBuffer::lock()`] function. Note that reading the buffer using OpenGL or Vulkan + /// functions or memory mappings does not require the presence of this flag. + #[doc(alias = "AHARDWAREBUFFER_USAGE_CPU_READ_RARELY")] + const CPU_READ_RARELY = ffi::AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_CPU_READ_RARELY.0; + /// The buffer will often be locked for direct CPU reads using the + /// [`HardwareBuffer::lock()`] function. Note that reading the buffer using OpenGL or Vulkan + /// functions or memory mappings does not require the presence of this flag. + #[doc(alias = "AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN")] + const CPU_READ_OFTEN = ffi::AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN.0; + /// CPU read value mask. + #[doc(alias = "AHARDWAREBUFFER_USAGE_CPU_READ_MASK")] + const CPU_READ_MASK = ffi::AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_CPU_READ_MASK.0; + + /// The buffer will never be locked for direct CPU writes using the + /// [`HardwareBuffer::lock()`] function. Note that writing the buffer using OpenGL or Vulkan + /// functions or memory mappings is still allowed. + #[doc(alias = "AHARDWAREBUFFER_USAGE_CPU_WRITE_NEVER")] + const CPU_WRITE_NEVER = ffi::AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_CPU_WRITE_NEVER.0; + /// The buffer will sometimes be locked for direct CPU writes using the + /// [`HardwareBuffer::lock()`] function. Note that writing the buffer using OpenGL or Vulkan + /// functions or memory mappings does not require the presence of this flag. + #[doc(alias = "AHARDWAREBUFFER_USAGE_CPU_WRITE_RARELY")] + const CPU_WRITE_RARELY = ffi::AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_CPU_WRITE_RARELY.0; + /// The buffer will often be locked for direct CPU writes using the + /// [`HardwareBuffer::lock()`] function. Note that writing the buffer using OpenGL or Vulkan + /// functions or memory mappings does not require the presence of this flag. + #[doc(alias = "AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN")] + const CPU_WRITE_OFTEN = ffi::AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN.0; + /// CPU write value mask. + #[doc(alias = "AHARDWAREBUFFER_USAGE_CPU_WRITE_MASK")] + const CPU_WRITE_MASK = ffi::AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_CPU_WRITE_MASK.0; + + /// The buffer will be read from by the GPU as a texture. + #[doc(alias = "AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE")] + const GPU_SAMPLED_IMAGE = ffi::AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE.0; + /// The buffer will be written to by the GPU as a framebuffer attachment. + #[doc(alias = "AHARDWAREBUFFER_USAGE_GPU_FRAMEBUFFER")] + const GPU_FRAMEBUFFER = ffi::AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_GPU_FRAMEBUFFER.0; + /// The buffer will be written to by the GPU as a framebuffer attachment. + /// + /// Note that the name of this flag is somewhat misleading: it does not imply that the + /// buffer contains a color format. A buffer with depth or stencil format that will be + /// used as a framebuffer attachment should also have this flag. Use the equivalent flag + /// [`HardwareBufferusage::GPU_FRAMEBUFFER`] to avoid this confusion. + #[doc(alias = "AHARDWAREBUFFER_USAGE_GPU_COLOR_OUTPUT")] + const GPU_COLOR_OUTPUT = ffi::AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_GPU_COLOR_OUTPUT.0; + /// The buffer will be used as a composer HAL overlay layer. + /// + /// This flag is currently only needed when using [`SurfaceTransaction::set_buffer()`] to + /// set a buffer. In all other cases, the framework adds this flag internally to buffers + /// that could be presented in a composer overlay. [`SurfaceTransaction::set_buffer()`] + /// is special because it uses buffers allocated directly through + /// [`HardwareBuffer::allocate()`] instead of buffers allocated by the framework. + #[doc(alias = "AHARDWAREBUFFER_USAGE_COMPOSER_OVERLAY")] + const COMPOSER_OVERLAY = ffi::AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_COMPOSER_OVERLAY.0; + /// The buffer is protected from direct CPU access or being read by non-secure hardware, + /// such as video encoders. + /// + /// This flag is incompatible with CPU read and write flags. It is mainly used when handling + /// DRM video. Refer to the EGL extension [`EGL_EXT_protected_content`] and GL extension + /// [`GL_EXT_protected_textures`] for more information on how these buffers are expected + /// to behave. + /// + /// [`EGL_EXT_protected_content`]: https://registry.khronos.org/EGL/extensions/EXT/EGL_EXT_protected_content.txt + /// [`GL_EXT_protected_textures`]: https://registry.khronos.org/OpenGL/extensions/EXT/EXT_protected_textures.txt + #[doc(alias = "AHARDWAREBUFFER_USAGE_PROTECTED_CONTENT")] + const PROTECTED_CONTENT = ffi::AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_PROTECTED_CONTENT.0; + /// The buffer will be read by a hardware video encoder. + #[doc(alias = "AHARDWAREBUFFER_USAGE_VIDEO_ENCODE")] + const VIDEO_ENCODE = ffi::AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_VIDEO_ENCODE.0; + /// The buffer will be used for direct writes from sensors. When this flag is present, the + /// format must be [`HardwareBufferFormat::Blob`]. + #[doc(alias = "AHARDWAREBUFFER_USAGE_SENSOR_DIRECT_DATA")] + const SENSOR_DIRECT_DATA = ffi::AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_SENSOR_DIRECT_DATA.0; + /// The buffer will be used as a shader storage or uniform buffer object. When this flag is + /// present, the format must be [`HardwareBufferFormat::Blob`]. + #[doc(alias = "AHARDWAREBUFFER_USAGE_GPU_DATA_BUFFER")] + const GPU_DATA_BUFFER = ffi::AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_GPU_DATA_BUFFER.0; + /// The buffer will be used as a cube map texture. When this flag is present, the buffer + /// must have a layer count that is a multiple of 6. Note that buffers with this flag must + /// be bound to OpenGL textures using the extension [`GL_EXT_EGL_image_storage`] instead + /// of [`GL_KHR_EGL_image`]. + /// + /// [`GL_EXT_EGL_image_storage`]: https://registry.khronos.org/OpenGL/extensions/EXT/EXT_EGL_image_storage.txt + // TODO: This extension only exists for VG. Reported at https://issuetracker.google.com/issues/300602767#comment16 + /// [`GL_KHR_EGL_image`]: https://registry.khronos.org/OpenVG/extensions/KHR/VG_KHR_EGL_image.txt + #[doc(alias = "AHARDWAREBUFFER_USAGE_GPU_CUBE_MAP")] + const GPU_CUBE_MAP = ffi::AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_GPU_CUBE_MAP.0; + /// The buffer contains a complete mipmap hierarchy. Note that buffers with this flag must + /// be bound to OpenGL textures using the extension [`GL_EXT_EGL_image_storage`] instead + /// of [`GL_KHR_EGL_image`]. + /// + /// [`GL_EXT_EGL_image_storage`]: https://registry.khronos.org/OpenGL/extensions/EXT/EXT_EGL_image_storage.txt + // TODO: This extension only exists for VG. Reported at https://issuetracker.google.com/issues/300602767#comment16 + /// [`GL_KHR_EGL_image`]: https://registry.khronos.org/OpenVG/extensions/KHR/VG_KHR_EGL_image.txt + #[doc(alias = "AHARDWAREBUFFER_USAGE_GPU_MIPMAP_COMPLETE")] + const GPU_MIPMAP_COMPLETE = ffi::AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_GPU_MIPMAP_COMPLETE.0; + + // TODO: Only available in a newer NDK + // /// Usage: The buffer is used for front-buffer rendering. When front-buffering rendering + // /// is specified, different usages may adjust their behavior as a result. For example, when + // /// used as [`HardwareBufferFormat::GPU_COLOR_OUTPUT`] the buffer will behave similar to a + // /// single-buffered window. When used with [`HardwareBufferFormat::COMPOSER_OVERLAY`], the + // /// system will try to prioritize the buffer receiving an overlay plane & avoid caching it + // /// in intermediate composition buffers. + // #[doc(alias = "AHARDWAREBUFFER_USAGE_FRONT_BUFFER")] + // const USAGE_FRONT_BUFFER = ffi::AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_FRONT_BUFFER.0; + + #[doc(alias = "AHARDWAREBUFFER_USAGE_VENDOR_0")] + const VENDOR_0 = ffi::AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_VENDOR_0.0; + #[doc(alias = "AHARDWAREBUFFER_USAGE_VENDOR_1")] + const VENDOR_1 = ffi::AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_VENDOR_1.0; + #[doc(alias = "AHARDWAREBUFFER_USAGE_VENDOR_2")] + const VENDOR_2 = ffi::AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_VENDOR_2.0; + #[doc(alias = "AHARDWAREBUFFER_USAGE_VENDOR_3")] + const VENDOR_3 = ffi::AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_VENDOR_3.0; + #[doc(alias = "AHARDWAREBUFFER_USAGE_VENDOR_4")] + const VENDOR_4 = ffi::AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_VENDOR_4.0; + #[doc(alias = "AHARDWAREBUFFER_USAGE_VENDOR_5")] + const VENDOR_5 = ffi::AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_VENDOR_5.0; + #[doc(alias = "AHARDWAREBUFFER_USAGE_VENDOR_6")] + const VENDOR_6 = ffi::AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_VENDOR_6.0; + #[doc(alias = "AHARDWAREBUFFER_USAGE_VENDOR_7")] + const VENDOR_7 = ffi::AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_VENDOR_7.0; + #[doc(alias = "AHARDWAREBUFFER_USAGE_VENDOR_8")] + const VENDOR_8 = ffi::AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_VENDOR_8.0; + #[doc(alias = "AHARDWAREBUFFER_USAGE_VENDOR_9")] + const VENDOR_9 = ffi::AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_VENDOR_9.0; + #[doc(alias = "AHARDWAREBUFFER_USAGE_VENDOR_10")] + const VENDOR_10 = ffi::AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_VENDOR_10.0; + #[doc(alias = "AHARDWAREBUFFER_USAGE_VENDOR_11")] + const VENDOR_11 = ffi::AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_VENDOR_11.0; + #[doc(alias = "AHARDWAREBUFFER_USAGE_VENDOR_12")] + const VENDOR_12 = ffi::AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_VENDOR_12.0; + #[doc(alias = "AHARDWAREBUFFER_USAGE_VENDOR_13")] + const VENDOR_13 = ffi::AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_VENDOR_13.0; + #[doc(alias = "AHARDWAREBUFFER_USAGE_VENDOR_14")] + const VENDOR_14 = ffi::AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_VENDOR_14.0; + #[doc(alias = "AHARDWAREBUFFER_USAGE_VENDOR_15")] + const VENDOR_15 = ffi::AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_VENDOR_15.0; + #[doc(alias = "AHARDWAREBUFFER_USAGE_VENDOR_16")] + const VENDOR_16 = ffi::AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_VENDOR_16.0; + #[doc(alias = "AHARDWAREBUFFER_USAGE_VENDOR_17")] + const VENDOR_17 = ffi::AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_VENDOR_17.0; + #[doc(alias = "AHARDWAREBUFFER_USAGE_VENDOR_18")] + const VENDOR_18 = ffi::AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_VENDOR_18.0; + #[doc(alias = "AHARDWAREBUFFER_USAGE_VENDOR_19")] + const VENDOR_19 = ffi::AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_VENDOR_19.0; + } +} + +impl HardwareBufferUsage { + /// Helper to read [`HardwareBufferUsage::CPU_READ_MASK`] values. + #[doc(alias = "AHARDWAREBUFFER_USAGE_CPU_READ_MASK")] + pub fn cpu_read(self) -> HardwareBufferUsage { + self.intersection(Self::CPU_READ_MASK) + } + + /// Helper to read [`HardwareBufferUsage::CPU_WRITE_MASK`] values. + #[doc(alias = "AHARDWAREBUFFER_USAGE_CPU_WRITE_MASK")] + pub fn cpu_write(self) -> HardwareBufferUsage { + self.intersection(Self::CPU_WRITE_MASK) + } +} + +pub type Rect = ffi::ARect; + +fn construct(with_ptr: impl FnOnce(*mut T) -> i32) -> Result { + let mut result = MaybeUninit::uninit(); + let status = with_ptr(result.as_mut_ptr()); + status_to_io_result(status).map(|()| unsafe { result.assume_init() }) +} + +/// A native [`AHardwareBuffer *`] +/// +/// [`HardwareBuffer`] objects represent chunks of memory that can be accessed by various hardware +/// components in the system. +/// +/// It can be easily converted to the Java counterpart [`android.hardware.HardwareBuffer`] and +/// passed between processes using Binder. All operations involving [`HardwareBuffer`] and +/// [`android.hardware.HardwareBuffer`] are zero-copy, i.e., passing [`HardwareBuffer`] to another +/// process creates a shared view of the same region of memory. +/// +/// [`HardwareBuffer`] can be bound to EGL/OpenGL and Vulkan primitives. For EGL, use the extension +/// function [`eglGetNativeClientBufferANDROID`] to obtain an `EGLClientBuffer` and pass it +/// directly to [`eglCreateImageKHR`]. Refer to the EGL extensions +/// [`EGL_ANDROID_get_native_client_buffer`] and [`EGL_ANDROID_image_native_buffer`] for more +/// information. In Vulkan, the contents of the [`HardwareBuffer`] can be accessed as [external +/// memory]. See the [`VK_ANDROID_external_memory_android_hardware_buffer`] extension for details. +/// +/// [`AHardwareBuffer *`]: https://developer.android.com/ndk/reference/group/a-hardware-buffer#ahardwarebuffer +/// [`android.hardware.HardwareBuffer`]: https://developer.android.com/reference/android/hardware/HardwareBuffer +/// [`eglGetNativeClientBufferANDROID`]: https://www.khronos.org/registry/EGL/extensions/ANDROID/EGL_ANDROID_get_native_client_buffer.txt +/// [`eglCreateImageKHR`]: https://www.khronos.org/registry/EGL/extensions/KHR/EGL_KHR_image_base.txt +/// [`EGL_ANDROID_get_native_client_buffer`]: https://www.khronos.org/registry/EGL/extensions/ANDROID/EGL_ANDROID_get_native_client_buffer.txt +/// [`EGL_ANDROID_image_native_buffer`]: https://www.khronos.org/registry/EGL/extensions/ANDROID/EGL_ANDROID_image_native_buffer.txt +/// [external memory]: https://www.khronos.org/registry/vulkan/specs/1.3-extensions/man/html/VK_KHR_external_memory.html +/// [`VK_ANDROID_external_memory_android_hardware_buffer`]: https://www.khronos.org/registry/vulkan/specs/1.3-extensions/man/html/VK_ANDROID_external_memory_android_hardware_buffer.html +#[derive(Debug)] +pub struct HardwareBuffer { + inner: NonNull, +} + +impl HardwareBuffer { + /// Create an _unowned_ [`HardwareBuffer`] from a native pointer + /// + /// To wrap a strong reference (that is `release`d on [`Drop`]), call + /// [`HardwareBufferRef::from_ptr()`] instead. + /// + /// # Safety + /// By calling this function, you assert that it is a valid pointer to an NDK + /// [`ffi::AHardwareBuffer`] that is kept alive externally, or retrieve a strong reference + /// using [`HardwareBuffer::acquire()`]. + pub unsafe fn from_ptr(ptr: NonNull) -> Self { + Self { inner: ptr } + } + + /// Returns the underlying [`ffi::AHardwareBuffer`] pointer + /// + /// See the top-level [`HardwareBuffer`] struct documentation for (graphics) APIs that accept + /// this pointer. + pub fn as_ptr(&self) -> *mut ffi::AHardwareBuffer { + self.inner.as_ptr() + } + + /// Allocates a buffer that matches the passed [`HardwareBufferDesc`]. + /// + /// If allocation succeeds, the buffer can be used according to the usage flags specified in + /// its description. If a buffer is used in ways not compatible with its usage flags, the + /// results are undefined and may include program termination. + pub fn allocate(desc: HardwareBufferDesc) -> Result { + unsafe { + let ptr = construct(|res| ffi::AHardwareBuffer_allocate(&desc.into_native(), res))?; + + Ok(HardwareBufferRef::from_ptr(NonNull::new_unchecked(ptr))) + } + } + + /// Create a [`HardwareBuffer`] from JNI pointers + /// + /// # Safety + /// By calling this function, you assert that these are valid pointers to JNI objects. + /// + /// This method does not acquire any additional reference to the AHardwareBuffer that is + /// returned. To keep the [`HardwareBuffer`] alive after the [Java `HardwareBuffer`] object + /// is closed, explicitly or by the garbage collector, be sure to retrieve a strong reference + /// using [`HardwareBuffer::acquire()`]. + /// + /// [Java `HardwareBuffer`]: https://developer.android.com/reference/android/hardware/HardwareBuffer + pub unsafe fn from_jni(env: *mut JNIEnv, hardware_buffer: jobject) -> Self { + let ptr = ffi::AHardwareBuffer_fromHardwareBuffer(env, hardware_buffer); + + Self::from_ptr(NonNull::new_unchecked(ptr)) + } + + /// # Safety + /// By calling this function, you assert that `env` is a valid pointer to a [`JNIEnv`]. + pub unsafe fn to_jni(&self, env: *mut JNIEnv) -> jobject { + ffi::AHardwareBuffer_toHardwareBuffer(env, self.as_ptr()) + } + + /// Return a description of the [`HardwareBuffer`] in the passed [`HardwareBufferDesc`] struct. + pub fn describe(&self) -> HardwareBufferDesc { + let desc = unsafe { + let mut result = MaybeUninit::uninit(); + ffi::AHardwareBuffer_describe(self.as_ptr(), result.as_mut_ptr()); + result.assume_init() + }; + + HardwareBufferDesc { + width: desc.width, + height: desc.height, + layers: desc.layers, + format: i32::try_from(desc.format) + .expect("i32->u32 overflow in HardwareBuffer::describe()") + .into(), + usage: HardwareBufferUsage::from_bits_retain(desc.usage), + stride: desc.stride, + } + } + + /// Test whether the given format and usage flag combination is allocatable. + /// + /// If this function returns [`true`], it means that a buffer with the given description can + /// be allocated on this implementation, unless resource exhaustion occurs. If this function + /// returns [`false`], it means that the allocation of the given description will never + /// succeed. + /// + /// The return value of this function may depend on all fields in the description, except + /// [`HardwareBufferDesc::stride`], which is always ignored. For example, some implementations + /// have implementation-defined limits on texture size and layer count. + #[cfg(feature = "api-level-29")] + pub fn is_supported(desc: HardwareBufferDesc) -> bool { + let res = unsafe { ffi::AHardwareBuffer_isSupported(&desc.into_native()) }; + res == 1 + } + + /// Get the system-wide unique id for this [`HardwareBuffer`]. + #[cfg(feature = "api-level-31")] + #[doc(alias = "AHardwareBuffer_getId")] + pub fn id(&self) -> Result { + construct(|res| unsafe { ffi::AHardwareBuffer_getId(self.as_ptr(), res) }) + } + + /// Lock the [`HardwareBuffer`] for direct CPU access. + /// + /// This function can lock the buffer for either reading or writing. It may block if the + /// hardware needs to finish rendering, if CPU caches need to be synchronized, or possibly for + /// other implementation-specific reasons. + /// + /// The [`HardwareBuffer`] must have one layer, otherwise the call will fail. + /// + /// If `fence` is not [`None`], it specifies a fence file descriptor on which to wait before + /// locking the buffer. If it's [`None`], the caller is responsible for ensuring that writes + /// to the buffer have completed before calling this function. Using this parameter is more + /// efficient than waiting on the fence and then calling this function. + /// + /// The `usage` parameter may only specify `HardwareBufferUsage::CPU_*`. If set, then the + /// address of the buffer in virtual memory is returned. The flags must also be compatible with + /// usage flags specified at buffer creation: if a read flag is passed, the buffer must have + /// been created with [`HardwareBufferUsage::CPU_READ_RARELY`] or + /// [`HardwareBufferUsage::CPU_READ_OFTEN`]. If a write flag is passed, it must have been + /// created with [`HardwareBufferUsage::CPU_WRITE_RARELY`] or + /// [`HardwareBufferUsage::CPU_WRITE_OFTEN`]. + /// + /// If `rect` is not [`None`], the caller promises to modify only data in the area specified by + /// `rect`. If rect is [`None`], the caller may modify the contents of the entire buffer. The + /// content of the buffer outside of the specified rect is NOT modified by this call. + /// + /// It is legal for several different threads to lock a buffer for read access; none of the + /// threads are blocked. + /// + /// Locking a buffer simultaneously for write or read/write is undefined, but will neither + /// terminate the process nor block the caller. This function may return an error or leave the + /// buffer's content in an indeterminate state. + /// + /// If the buffer has [`HardwareBufferFormat::BLOB`], it is legal lock it for reading and + /// writing in multiple threads and/or processes simultaneously, and the contents of the buffer + /// behave like shared memory. + pub fn lock( + &self, + usage: HardwareBufferUsage, + fence: Option, + rect: Option, + ) -> Result<*mut c_void> { + let fence = fence.map_or(-1, IntoRawFd::into_raw_fd); + let rect = match rect { + Some(rect) => &rect, + None => std::ptr::null(), + }; + construct(|res| unsafe { + ffi::AHardwareBuffer_lock(self.as_ptr(), usage.bits(), fence, rect, res) + }) + } + + /// Lock a [`HardwareBuffer`] for direct CPU access. + /// + /// This function is the same as the above [`lock()`][Self::lock()] function, but passes back + /// additional information about the bytes per pixel and the bytes per stride of the locked + /// buffer. If the bytes per pixel or bytes per stride are unknown or variable, or if the + /// underlying mapper implementation does not support returning additional information, then + /// this call will fail with [`std::io::Error::kind()`] = [`std::io::ErrorKind::Unsupported`]. + #[cfg(feature = "api-level-29")] + pub fn lock_and_get_info( + &self, + usage: HardwareBufferUsage, + fence: Option, + rect: Option, + ) -> Result { + let fence = fence.map_or(-1, IntoRawFd::into_raw_fd); + let rect = match rect { + Some(rect) => &rect, + None => std::ptr::null(), + }; + let mut virtual_address = MaybeUninit::uninit(); + let mut bytes_per_pixel = MaybeUninit::uninit(); + let mut bytes_per_stride = MaybeUninit::uninit(); + let status = unsafe { + ffi::AHardwareBuffer_lockAndGetInfo( + self.as_ptr(), + usage.bits(), + fence, + rect, + virtual_address.as_mut_ptr(), + bytes_per_pixel.as_mut_ptr(), + bytes_per_stride.as_mut_ptr(), + ) + }; + status_to_io_result(status).map(|()| unsafe { + LockedPlaneInfo { + virtual_address: virtual_address.assume_init(), + bytes_per_pixel: bytes_per_pixel.assume_init() as u32, + bytes_per_stride: bytes_per_stride.assume_init() as u32, + } + }) + } + + /// Lock a potentially multi-planar [`HardwareBuffer`] for direct CPU access. + /// + /// This function is similar to [`lock()`][Self::lock()], but can lock multi-planar formats. + /// Note, that multi-planar should not be confused with multi-layer images, which this locking + /// function does not support. + /// + /// YUV formats are always represented by three separate planes of data, one for each color + /// plane. The order of planes in the array is guaranteed such that plane #0 is always `Y`, + /// plane #1 is always `U` (`Cb`), and plane #2 is always `V` (`Cr`). All other formats are + /// represented by a single plane. + /// + /// Additional information always accompanies the buffers, describing the row stride and the + /// pixel stride for each plane. + /// + /// In case the buffer cannot be locked, this will return zero planes. + /// + /// See the [`lock()`][Self::lock()] documentation for all other locking semantics. + #[cfg(feature = "api-level-29")] + pub fn lock_planes( + &self, + usage: HardwareBufferUsage, + fence: Option, + rect: Option, + ) -> Result { + let fence = fence.map_or(-1, IntoRawFd::into_raw_fd); + let rect = match rect { + Some(rect) => &rect, + None => std::ptr::null(), + }; + let planes = construct(|res| unsafe { + ffi::AHardwareBuffer_lockPlanes(self.as_ptr(), usage.bits(), fence, rect, res) + })?; + + Ok(HardwareBufferPlanes { + inner: planes, + index: 0, + }) + } + + /// Unlock the [`HardwareBuffer`] from direct CPU access. + /// + /// Must be called after all changes to the buffer are completed by the caller. The function + /// will block until all work is completed. See [`unlock_async()`][Self::unlock_async()] for + /// a non-blocking variant that returns a file descriptor to be signaled on unlocking instead. + pub fn unlock(&self) -> Result<()> { + let status = unsafe { ffi::AHardwareBuffer_unlock(self.as_ptr(), std::ptr::null_mut()) }; + status_to_io_result(status) + } + + /// Unlock the [`HardwareBuffer`] from direct CPU access. + /// + /// Returns a fence file descriptor that will become signaled when unlocking is completed, or + /// [`None`] if unlocking is already finished. The caller is responsible for closing the file + /// descriptor once it's no longer needed. See [`unlock()`][Self::unlock()] for a variant that + /// blocks instead. + pub fn unlock_async(&self) -> Result> { + let fence = construct(|res| unsafe { ffi::AHardwareBuffer_unlock(self.as_ptr(), res) })?; + Ok(match fence { + -1 => None, + fence => Some(unsafe { OwnedFd::from_raw_fd(fence) }), + }) + } + + /// Receive a [`HardwareBuffer`] from an `AF_UNIX` socket. + /// + /// `AF_UNIX` sockets are wrapped by [`std::os::unix::net::UnixListener`] and + /// [`std::os::unix::net::UnixStream`] in Rust and have a corresponding + /// [`std::os::unix::io::AsFd::as_fd()`] implementation. + pub fn recv_handle_from_unix_socket(socket_fd: BorrowedFd<'_>) -> Result { + unsafe { + let ptr = construct(|res| { + ffi::AHardwareBuffer_recvHandleFromUnixSocket(socket_fd.as_raw_fd(), res) + })?; + + Ok(Self::from_ptr(NonNull::new_unchecked(ptr))) + } + } + + /// Send the [`HardwareBuffer`] to an `AF_UNIX` socket. + /// + /// `AF_UNIX` sockets are wrapped by [`std::os::unix::net::UnixListener`] and + /// [`std::os::unix::net::UnixStream`] in Rust and have a corresponding + /// [`std::os::unix::io::AsFd::as_fd()`] implementation. + pub fn send_handle_to_unix_socket(&self, socket_fd: BorrowedFd<'_>) -> Result<()> { + let status = unsafe { + ffi::AHardwareBuffer_sendHandleToUnixSocket(self.as_ptr(), socket_fd.as_raw_fd()) + }; + status_to_io_result(status) + } + + /// Acquire a reference on the given [`HardwareBuffer`] object. + /// + /// This prevents the object from being deleted until the last strong reference, represented + /// by [`HardwareBufferRef`], is [`drop()`]ped. + pub fn acquire(&self) -> HardwareBufferRef { + unsafe { + ffi::AHardwareBuffer_acquire(self.as_ptr()); + HardwareBufferRef::from_ptr(self.inner) + } + } +} + +/// A [`HardwareBuffer`] with an owned reference, that is released when dropped. +/// It behaves much like a strong [`std::rc::Rc`] reference. +#[derive(Debug)] +pub struct HardwareBufferRef { + inner: HardwareBuffer, +} + +impl HardwareBufferRef { + /// Create an _owned_ [`HardwareBuffer`] from a native pointer + /// + /// To wrap a weak reference (that is **not** `release`d on [`Drop`]), call + /// [`HardwareBuffer::from_ptr()`] instead. + /// + /// # Safety + /// By calling this function, you assert that it is a valid pointer to an NDK + /// [`ffi::AHardwareBuffer`]. + pub unsafe fn from_ptr(ptr: NonNull) -> Self { + Self { + inner: HardwareBuffer { inner: ptr }, + } + } +} + +impl Deref for HardwareBufferRef { + type Target = HardwareBuffer; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +impl Drop for HardwareBufferRef { + fn drop(&mut self) { + unsafe { ffi::AHardwareBuffer_release(self.inner.as_ptr()) } + } +} + +impl Clone for HardwareBufferRef { + fn clone(&self) -> Self { + self.acquire() + } +} + +/// Buffer description. +/// +/// Used for allocating new buffers and querying parameters of existing ones. +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub struct HardwareBufferDesc { + pub width: u32, + pub height: u32, + pub layers: u32, + pub format: HardwareBufferFormat, + pub usage: HardwareBufferUsage, + pub stride: u32, +} + +impl HardwareBufferDesc { + fn into_native(self) -> ffi::AHardwareBuffer_Desc { + ffi::AHardwareBuffer_Desc { + width: self.width, + height: self.height, + layers: self.layers, + format: i32::from(self.format) + .try_into() + .expect("i32->u32 overflow in HardwareBufferDesc::into_native()"), + usage: self.usage.bits(), + stride: self.stride, + rfu0: 0, + rfu1: 0, + } + } +} + +/// A native [`AHardwareBuffer_Plane`] +/// +/// Contains the same fields as [`ffi::AHardwareBuffer_Plane`]. +/// +/// [`AHardwareBuffer_Plane`]: https://developer.android.com/ndk/reference/group/a-hardware-buffer#ahardwarebuffer_plane +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub struct LockedPlaneInfo { + pub virtual_address: *mut c_void, + pub bytes_per_pixel: u32, + pub bytes_per_stride: u32, +} + +/// Iterator over [`ffi::AHardwareBuffer_Planes`], containing a list of [`LockedPlaneInfo`]. +#[derive(Debug)] +pub struct HardwareBufferPlanes { + inner: ffi::AHardwareBuffer_Planes, + index: u32, +} + +impl Iterator for HardwareBufferPlanes { + type Item = LockedPlaneInfo; + + fn next(&mut self) -> Option { + if self.index == self.inner.planeCount { + None + } else { + let plane = self.inner.planes[self.index as usize]; + self.index += 1; + Some(LockedPlaneInfo { + virtual_address: plane.data, + bytes_per_pixel: plane.pixelStride, + bytes_per_stride: plane.rowStride, + }) + } + } +} diff --git a/clients/android/native/vendor/ndk/src/hardware_buffer_format.rs b/clients/android/native/vendor/ndk/src/hardware_buffer_format.rs new file mode 100644 index 00000000..555669f5 --- /dev/null +++ b/clients/android/native/vendor/ndk/src/hardware_buffer_format.rs @@ -0,0 +1,106 @@ +//! Bindings for [`AHardwareBuffer_Format`] +//! +//! [`AHardwareBuffer_Format`]: https://developer.android.com/ndk/reference/group/a-hardware-buffer#ahardwarebuffer_format + +use num_enum::{FromPrimitive, IntoPrimitive}; + +/// Buffer pixel formats. +#[repr(i32)] +#[derive(Copy, Clone, Debug, PartialEq, Eq, FromPrimitive, IntoPrimitive)] +#[allow(non_camel_case_types)] +#[non_exhaustive] +pub enum HardwareBufferFormat { + /// Matches deprecated [`ffi::ANativeWindow_LegacyFormat::WINDOW_FORMAT_RGBA_8888`].0. + #[doc(alias = "AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM")] + R8G8B8A8_UNORM = ffi::AHardwareBuffer_Format::AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM.0 as i32, + /// Matches deprecated [`ffi::ANativeWindow_LegacyFormat::WINDOW_FORMAT_RGBX_8888`].0. + #[doc(alias = "AHARDWAREBUFFER_FORMAT_R8G8B8X8_UNORM")] + R8G8B8X8_UNORM = ffi::AHardwareBuffer_Format::AHARDWAREBUFFER_FORMAT_R8G8B8X8_UNORM.0 as i32, + #[cfg(feature = "api-level-26")] + #[doc(alias = "AHARDWAREBUFFER_FORMAT_R8G8B8_UNORM")] + R8G8B8_UNORM = ffi::AHardwareBuffer_Format::AHARDWAREBUFFER_FORMAT_R8G8B8_UNORM.0 as i32, + /// Matches deprecated [`ffi::ANativeWindow_LegacyFormat::WINDOW_FORMAT_RGB_565`].0. + #[doc(alias = "AHARDWAREBUFFER_FORMAT_R5G6B5_UNORM")] + R5G6B5_UNORM = ffi::AHardwareBuffer_Format::AHARDWAREBUFFER_FORMAT_R5G6B5_UNORM.0 as i32, + #[cfg(feature = "api-level-26")] + #[doc(alias = "AHARDWAREBUFFER_FORMAT_R16G16B16A16_FLOAT")] + R16G16B16A16_FLOAT = + ffi::AHardwareBuffer_Format::AHARDWAREBUFFER_FORMAT_R16G16B16A16_FLOAT.0 as i32, + #[cfg(feature = "api-level-26")] + #[doc(alias = "AHARDWAREBUFFER_FORMAT_R10G10B10A2_UNORM")] + R10G10B10A2_UNORM = + ffi::AHardwareBuffer_Format::AHARDWAREBUFFER_FORMAT_R10G10B10A2_UNORM.0 as i32, + #[cfg(feature = "api-level-26")] + #[doc(alias = "AHARDWAREBUFFER_FORMAT_BLOB")] + BLOB = ffi::AHardwareBuffer_Format::AHARDWAREBUFFER_FORMAT_BLOB.0 as i32, + #[cfg(feature = "api-level-26")] + #[doc(alias = "AHARDWAREBUFFER_FORMAT_D16_UNORM")] + D16_UNORM = ffi::AHardwareBuffer_Format::AHARDWAREBUFFER_FORMAT_D16_UNORM.0 as i32, + #[cfg(feature = "api-level-26")] + #[doc(alias = "AHARDWAREBUFFER_FORMAT_D24_UNORM")] + D24_UNORM = ffi::AHardwareBuffer_Format::AHARDWAREBUFFER_FORMAT_D24_UNORM.0 as i32, + #[cfg(feature = "api-level-26")] + #[doc(alias = "AHARDWAREBUFFER_FORMAT_D24_UNORM_S8_UINT")] + D24_UNORM_S8_UINT = + ffi::AHardwareBuffer_Format::AHARDWAREBUFFER_FORMAT_D24_UNORM_S8_UINT.0 as i32, + #[cfg(feature = "api-level-26")] + #[doc(alias = "AHARDWAREBUFFER_FORMAT_D32_FLOAT")] + D32_FLOAT = ffi::AHardwareBuffer_Format::AHARDWAREBUFFER_FORMAT_D32_FLOAT.0 as i32, + #[cfg(feature = "api-level-26")] + #[doc(alias = "AHARDWAREBUFFER_FORMAT_D32_FLOAT_S8_UINT")] + D32_FLOAT_S8_UINT = + ffi::AHardwareBuffer_Format::AHARDWAREBUFFER_FORMAT_D32_FLOAT_S8_UINT.0 as i32, + #[cfg(feature = "api-level-26")] + #[doc(alias = "AHARDWAREBUFFER_FORMAT_S8_UINT")] + S8_UINT = ffi::AHardwareBuffer_Format::AHARDWAREBUFFER_FORMAT_S8_UINT.0 as i32, + #[cfg(feature = "api-level-26")] + #[doc(alias = "AHARDWAREBUFFER_FORMAT_Y8Cb8Cr8_420")] + Y8Cb8Cr8_420 = ffi::AHardwareBuffer_Format::AHARDWAREBUFFER_FORMAT_Y8Cb8Cr8_420.0 as i32, + #[cfg(feature = "api-level-26")] + #[doc(alias = "AHARDWAREBUFFER_FORMAT_YCbCr_P010")] + YCbCr_P010 = ffi::AHardwareBuffer_Format::AHARDWAREBUFFER_FORMAT_YCbCr_P010.0 as i32, + #[cfg(feature = "api-level-26")] + R8_UNORM = ffi::AHardwareBuffer_Format::AHARDWAREBUFFER_FORMAT_R8_UNORM.0 as i32, + + #[doc(hidden)] + #[num_enum(catch_all)] + __Unknown(i32), +} + +impl HardwareBufferFormat { + /// Returns [`None`] when there is no immediate byte size available for this format, for + /// example on planar buffer formats. + pub fn bytes_per_pixel(self) -> Option { + Some(match self { + Self::R8G8B8A8_UNORM | Self::R8G8B8X8_UNORM => 4, + #[cfg(feature = "api-level-26")] + Self::R8G8B8_UNORM => 3, + Self::R5G6B5_UNORM => 2, + #[cfg(feature = "api-level-26")] + Self::R16G16B16A16_FLOAT => 8, + #[cfg(feature = "api-level-26")] + Self::R10G10B10A2_UNORM => 4, + #[cfg(feature = "api-level-26")] + Self::BLOB => 1, + #[cfg(feature = "api-level-26")] + Self::D16_UNORM => 2, + #[cfg(feature = "api-level-26")] + Self::D24_UNORM => 3, + #[cfg(feature = "api-level-26")] + Self::D24_UNORM_S8_UINT => 4, + #[cfg(feature = "api-level-26")] + Self::D32_FLOAT => 4, + #[cfg(feature = "api-level-26")] + Self::D32_FLOAT_S8_UINT => 5, + #[cfg(feature = "api-level-26")] + Self::S8_UINT => 1, + #[cfg(feature = "api-level-26")] + Self::Y8Cb8Cr8_420 => 3, + #[cfg(feature = "api-level-26")] + Self::YCbCr_P010 => return None, + #[cfg(feature = "api-level-26")] + Self::R8_UNORM => 1, + Self::__Unknown(_) => return None, + }) + } +} diff --git a/clients/android/native/vendor/ndk/src/input_queue.rs b/clients/android/native/vendor/ndk/src/input_queue.rs new file mode 100644 index 00000000..0fae5f11 --- /dev/null +++ b/clients/android/native/vendor/ndk/src/input_queue.rs @@ -0,0 +1,143 @@ +//! Bindings for [`AInputQueue`] +//! +//! [`AInputQueue`]: https://developer.android.com/ndk/reference/group/input#ainputqueue + +use std::io::Result; +use std::os::raw::c_int; +use std::ptr::{self, NonNull}; + +#[cfg(feature = "api-level-33")] +use jni_sys::{jobject, JNIEnv}; + +use crate::event::InputEvent; +#[cfg(doc)] +use crate::event::KeyEvent; +use crate::looper::ForeignLooper; +use crate::utils::status_to_io_result; + +/// A native [`AInputQueue *`] +/// +/// An input queue is the facility through which you retrieve input events. +/// +/// [`AInputQueue *`]: https://developer.android.com/ndk/reference/group/input#ainputqueue +#[derive(Debug)] +pub struct InputQueue { + ptr: NonNull, +} + +// It gets shared between threads in `ndk-glue` +unsafe impl Send for InputQueue {} +unsafe impl Sync for InputQueue {} + +impl InputQueue { + /// Construct an [`InputQueue`] from the native pointer. + /// + /// # Safety + /// By calling this function, you assert that the pointer is a valid pointer to an NDK [`ffi::AInputQueue`]. + pub unsafe fn from_ptr(ptr: NonNull) -> Self { + Self { ptr } + } + + /// Returns the [`InputQueue`] object associated with the supplied + /// [Java `InputQueue`][`android.view.InputQueue`] object. + /// + /// # Safety + /// + /// This function should be called with a healthy JVM pointer and with a non-null + /// [`android.view.InputQueue`], which must be kept alive on the Java/Kotlin side. + /// + /// The returned native object holds a weak reference to the Java object, and is only valid as + /// long as the Java object has not yet been disposed. You should ensure that there is a strong + /// reference to the Java object and that it has not been disposed before using the returned + /// object. + /// + /// [`android.view.InputQueue`]: https://developer.android.com/reference/android/view/InputQueue + #[cfg(feature = "api-level-33")] + #[doc(alias = "AInputQueue_fromJava")] + pub unsafe fn from_java(env: *mut JNIEnv, input_queue: jobject) -> Option { + let ptr = unsafe { ffi::AInputQueue_fromJava(env, input_queue) }; + Some(Self::from_ptr(NonNull::new(ptr)?)) + } + + pub fn ptr(&self) -> NonNull { + self.ptr + } + + /// Returns the next available [`InputEvent`] from the queue. + /// + /// Returns [`None`] if no event is available. + #[doc(alias = "AInputQueue_getEvent")] + pub fn event(&self) -> Result> { + let mut out_event = ptr::null_mut(); + let status = unsafe { ffi::AInputQueue_getEvent(self.ptr.as_ptr(), &mut out_event) }; + match status_to_io_result(status) { + Ok(()) => { + debug_assert!(!out_event.is_null()); + Ok(Some(unsafe { + InputEvent::from_ptr(NonNull::new_unchecked(out_event)) + })) + } + Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => Ok(None), + Err(e) => Err(e), + } + } + + /// Returns [`true`] if there are one or more events available in the input queue. + #[doc(alias = "AInputQueue_hasEvents")] + pub fn has_events(&self) -> bool { + match unsafe { ffi::AInputQueue_hasEvents(self.ptr.as_ptr()) } { + 0 => false, + 1 => true, + r => unreachable!("AInputQueue_hasEvents returned non-boolean {}", r), + } + } + + /// Sends the key for standard pre-dispatching that is, possibly deliver it to the current IME + /// to be consumed before the app. + /// + /// Returns [`Some`] if it was not pre-dispatched, meaning you can process it right now. If + /// [`None`] is returned, you must abandon the current event processing and allow the event to + /// appear again in the event queue (if it does not get consumed during pre-dispatching). + /// + /// Also returns [`None`] if `event` is not a [`KeyEvent`]. + #[doc(alias = "AInputQueue_preDispatchEvent")] + pub fn pre_dispatch(&self, event: InputEvent) -> Option { + match unsafe { ffi::AInputQueue_preDispatchEvent(self.ptr.as_ptr(), event.ptr().as_ptr()) } + { + 0 => Some(event), + _ => None, + } + } + + /// Report that dispatching has finished with the given [`InputEvent`]. + /// + /// This must be called after receiving an event with [`InputQueue::event()`]. + #[doc(alias = "AInputQueue_finishEvent")] + pub fn finish_event(&self, event: InputEvent, handled: bool) { + unsafe { + ffi::AInputQueue_finishEvent(self.ptr.as_ptr(), event.ptr().as_ptr(), handled as c_int) + } + } + + /// Add this input queue to a [`ForeignLooper`] for processing. + /// + /// See [`ForeignLooper::add_fd()`] for information on the `ident`, `callback`, and `data` params. + #[doc(alias = "AInputQueue_attachLooper")] + pub fn attach_looper(&self, looper: &ForeignLooper, id: i32) { + unsafe { + ffi::AInputQueue_attachLooper( + self.ptr.as_ptr(), + looper.ptr().as_ptr(), + id, + None, + std::ptr::null_mut(), + ) + } + } + + /// Remove this input queue from the [`ForeignLooper`] it is currently attached to. + #[doc(alias = "AInputQueue_detachLooper")] + pub fn detach_looper(&self) { + unsafe { ffi::AInputQueue_detachLooper(self.ptr.as_ptr()) } + } +} diff --git a/clients/android/native/vendor/ndk/src/lib.rs b/clients/android/native/vendor/ndk/src/lib.rs new file mode 100644 index 00000000..81e2ef61 --- /dev/null +++ b/clients/android/native/vendor/ndk/src/lib.rs @@ -0,0 +1,33 @@ +//! # Android NDK +//! +//! Bindings to the [Android NDK]. +//! +//! [Android NDK]: https://developer.android.com/ndk/reference +#![warn( + missing_debug_implementations, + rust_2018_idioms, + trivial_casts, + unused_qualifications +)] +#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] + +pub mod asset; +pub mod audio; +pub mod bitmap; +pub mod configuration; +pub mod data_space; +pub mod event; +pub mod font; +pub mod hardware_buffer; +pub mod hardware_buffer_format; +pub mod input_queue; +pub mod looper; +pub mod media; +pub mod media_error; +pub mod native_activity; +pub mod native_window; +pub mod shared_memory; +pub mod surface_texture; +pub mod sync; +pub mod trace; +mod utils; diff --git a/clients/android/native/vendor/ndk/src/looper.rs b/clients/android/native/vendor/ndk/src/looper.rs new file mode 100644 index 00000000..d8eb644b --- /dev/null +++ b/clients/android/native/vendor/ndk/src/looper.rs @@ -0,0 +1,459 @@ +//! Bindings for [`ALooper`] +//! +//! In Android, [`ALooper`]s are inherently thread-local. Due to this, there are two different +//! [`ALooper`] interfaces exposed in this module: +//! +//! * [`ThreadLooper`], which has methods for the operations performable with a looper in one's own +//! thread; and +//! * [`ForeignLooper`], which has methods for the operations performable with any thread's looper. +//! +//! [`ALooper`]: https://developer.android.com/ndk/reference/group/looper#alooper + +use std::mem::ManuallyDrop; +use std::os::{ + fd::{AsRawFd, BorrowedFd, RawFd}, + raw::c_void, +}; +use std::ptr; +use std::time::Duration; +use thiserror::Error; + +use crate::utils::abort_on_panic; + +/// A thread-local native [`ALooper *`]. This promises that there is a looper associated with the +/// current thread. +/// +/// [`ALooper *`]: https://developer.android.com/ndk/reference/group/looper#alooper +#[derive(Debug)] +pub struct ThreadLooper { + _marker: std::marker::PhantomData<*mut ()>, // Not send or sync + foreign: ForeignLooper, +} + +bitflags::bitflags! { + /// Flags for file descriptor events that a looper can monitor. + /// + /// These flag bits can be combined to monitor multiple events at once. + #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)] + pub struct FdEvent : u32 { + /// The file descriptor is available for read operations. + #[doc(alias = "ALOOPER_EVENT_INPUT")] + const INPUT = ffi::ALOOPER_EVENT_INPUT; + /// The file descriptor is available for write operations. + #[doc(alias = "ALOOPER_EVENT_OUTPUT")] + const OUTPUT = ffi::ALOOPER_EVENT_OUTPUT; + /// The file descriptor has encountered an error condition. + /// + /// The looper always sends notifications about errors; it is not necessary to specify this + /// event flag in the requested event set. + #[doc(alias = "ALOOPER_EVENT_ERROR")] + const ERROR = ffi::ALOOPER_EVENT_ERROR; + /// The file descriptor was hung up. + /// + /// For example, indicates that the remote end of a pipe or socket was closed. + /// + /// The looper always sends notifications about hangups; it is not necessary to specify this + /// event flag in the requested event set. + #[doc(alias = "ALOOPER_EVENT_HANGUP")] + const HANGUP = ffi::ALOOPER_EVENT_HANGUP; + /// The file descriptor is invalid. + /// + /// For example, the file descriptor was closed prematurely. + /// + /// The looper always sends notifications about invalid file descriptors; it is not + /// necessary to specify this event flag in the requested event set. + #[doc(alias = "ALOOPER_EVENT_INVALID")] + const INVALID = ffi::ALOOPER_EVENT_INVALID; + + // https://docs.rs/bitflags/latest/bitflags/#externally-defined-flags + const _ = !0; + } +} + +/// The poll result from a [`ThreadLooper`]. +#[derive(Debug)] +pub enum Poll<'fd> { + /// This looper was woken using [`ForeignLooper::wake()`] + Wake, + /// For [`ThreadLooper::poll_once*()`][ThreadLooper::poll_once()], an event was received and processed using a callback. + Callback, + /// For [`ThreadLooper::poll_*_timeout()`][ThreadLooper::poll_once_timeout()], the requested timeout was reached before any events. + Timeout, + /// An event was received + Event { + ident: i32, + /// # Safety + /// The caller should guarantee that this file descriptor remains open after it was added + /// via [`ForeignLooper::add_fd()`] or [`ForeignLooper::add_fd_with_callback()`]. + fd: BorrowedFd<'fd>, + events: FdEvent, + data: *mut c_void, + }, +} + +#[derive(Debug, Copy, Clone, Error)] +#[error("Android Looper error")] +pub struct LooperError; + +impl ThreadLooper { + /// Prepares a looper for the current thread and returns it + pub fn prepare() -> Self { + unsafe { + let ptr = ffi::ALooper_prepare(ffi::ALOOPER_PREPARE_ALLOW_NON_CALLBACKS as _); + let foreign = ForeignLooper::from_ptr(ptr::NonNull::new(ptr).expect("looper non null")); + Self { + _marker: std::marker::PhantomData, + foreign, + } + } + } + + /// Returns the looper associated with the current thread, if any. + pub fn for_thread() -> Option { + Some(Self { + _marker: std::marker::PhantomData, + foreign: ForeignLooper::for_thread()?, + }) + } + + /// Polls the looper, blocking on processing an event, but with a timeout in milliseconds. + /// Give a timeout of `0` to make this non-blocking. + fn poll_once_ms(&self, ms: i32) -> Result, LooperError> { + let mut fd = -1; + let mut events = -1; + let mut data: *mut c_void = ptr::null_mut(); + match unsafe { ffi::ALooper_pollOnce(ms, &mut fd, &mut events, &mut data) } { + ffi::ALOOPER_POLL_WAKE => Ok(Poll::Wake), + ffi::ALOOPER_POLL_CALLBACK => Ok(Poll::Callback), + ffi::ALOOPER_POLL_TIMEOUT => Ok(Poll::Timeout), + ffi::ALOOPER_POLL_ERROR => Err(LooperError), + ident if ident >= 0 => Ok(Poll::Event { + ident, + // SAFETY: Even though this FD at least shouldn't outlive self, a user could have + // closed it after calling add_fd or add_fd_with_callback. + fd: unsafe { BorrowedFd::borrow_raw(fd) }, + events: FdEvent::from_bits(events as u32) + .expect("poll event contains unknown bits"), + data, + }), + _ => unreachable!(), + } + } + + /// Polls the looper, blocking on processing an event. + #[inline] + pub fn poll_once(&self) -> Result, LooperError> { + self.poll_once_ms(-1) + } + + /// Polls the looper, blocking on processing an event, but with a timeout. Give a timeout of + /// [`Duration::ZERO`] to make this non-blocking. + /// + /// It panics if the timeout is larger than expressible as an [`i32`] of milliseconds (roughly 25 + /// days). + #[inline] + pub fn poll_once_timeout(&self, timeout: Duration) -> Result, LooperError> { + self.poll_once_ms( + timeout + .as_millis() + .try_into() + .expect("Supplied timeout is too large"), + ) + } + + /// Repeatedly polls the looper, blocking on processing an event, but with a timeout in + /// milliseconds. Give a timeout of `0` to make this non-blocking. + /// + /// This function will never return [`Poll::Callback`]. + fn poll_all_ms(&self, ms: i32) -> Result, LooperError> { + let mut fd = -1; + let mut events = -1; + let mut data: *mut c_void = ptr::null_mut(); + match unsafe { ffi::ALooper_pollAll(ms, &mut fd, &mut events, &mut data) } { + ffi::ALOOPER_POLL_WAKE => Ok(Poll::Wake), + ffi::ALOOPER_POLL_TIMEOUT => Ok(Poll::Timeout), + ffi::ALOOPER_POLL_ERROR => Err(LooperError), + ident if ident >= 0 => Ok(Poll::Event { + ident, + // SAFETY: Even though this FD at least shouldn't outlive self, a user could have + // closed it after calling add_fd or add_fd_with_callback. + fd: unsafe { BorrowedFd::borrow_raw(fd) }, + events: FdEvent::from_bits(events as u32) + .expect("poll event contains unknown bits"), + data, + }), + _ => unreachable!(), + } + } + + /// Repeatedly polls the looper, blocking on processing an event. + /// + /// This function will never return [`Poll::Callback`]. + #[inline] + pub fn poll_all(&self) -> Result, LooperError> { + self.poll_all_ms(-1) + } + + /// Repeatedly polls the looper, blocking on processing an event, but with a timeout. Give a + /// timeout of [`Duration::ZERO`] to make this non-blocking. + /// + /// This function will never return [`Poll::Callback`]. + /// + /// It panics if the timeout is larger than expressible as an [`i32`] of milliseconds (roughly 25 + /// days). + #[inline] + pub fn poll_all_timeout(&self, timeout: Duration) -> Result, LooperError> { + self.poll_all_ms( + timeout + .as_millis() + .try_into() + .expect("Supplied timeout is too large"), + ) + } + + /// Adds a file descriptor to be polled, with a callback that is invoked when any of the + /// [`FdEvent`]s described in `events` is triggered. + /// + /// The callback receives the file descriptor it is associated with and a bitmask of the poll + /// events that were triggered (typically [`FdEvent::INPUT`]). It should return [`true`] to + /// continue receiving callbacks, or [`false`] to have the callback unregistered. + /// + /// See also [the NDK + /// docs](https://developer.android.com/ndk/reference/group/looper.html#alooper_addfd). + /// + /// Note that this will leak a [`Box`] unless the callback returns [`false`] to unregister + /// itself. + /// + /// # Threading + /// This function will be called on the current thread when this [`ThreadLooper`] is + /// polled. A callback can also be registered from other threads via the equivalent + /// [`ForeignLooper::add_fd_with_callback()`] function, which requires a [`Send`] bound. + /// + /// # Safety + /// The caller should guarantee that this file descriptor stays open until it is removed via + /// [`remove_fd()`][ForeignLooper::remove_fd()] or by returning [`false`] from the callback, + /// and for however long the caller wishes to use this file descriptor inside and after the + /// callback. + #[doc(alias = "ALooper_addFd")] + pub fn add_fd_with_callback, FdEvent) -> bool>( + &self, + fd: BorrowedFd<'_>, + events: FdEvent, + callback: F, + ) -> Result<(), LooperError> { + unsafe { + self.foreign + .add_fd_with_callback_assume_send(fd, events, callback) + } + } + + /// Returns a reference to the [`ForeignLooper`] that is associated with the current thread. + pub fn as_foreign(&self) -> &ForeignLooper { + &self.foreign + } + + pub fn into_foreign(self) -> ForeignLooper { + self.foreign + } +} + +/// A native [`ALooper *`], not necessarily allocated with the current thread. +/// +/// [`ALooper *`]: https://developer.android.com/ndk/reference/group/looper#alooper +#[derive(Debug)] +pub struct ForeignLooper { + ptr: ptr::NonNull, +} + +unsafe impl Send for ForeignLooper {} +unsafe impl Sync for ForeignLooper {} + +impl Drop for ForeignLooper { + fn drop(&mut self) { + unsafe { ffi::ALooper_release(self.ptr.as_ptr()) } + } +} + +impl Clone for ForeignLooper { + fn clone(&self) -> Self { + unsafe { + ffi::ALooper_acquire(self.ptr.as_ptr()); + Self { ptr: self.ptr } + } + } +} + +impl ForeignLooper { + /// Returns the looper associated with the current thread, if any. + #[inline] + pub fn for_thread() -> Option { + ptr::NonNull::new(unsafe { ffi::ALooper_forThread() }) + .map(|ptr| unsafe { Self::from_ptr(ptr) }) + } + + /// Construct a [`ForeignLooper`] object from the given pointer. + /// + /// # Safety + /// By calling this function, you guarantee that the pointer is a valid, non-null pointer to an + /// NDK [`ffi::ALooper`]. + #[inline] + pub unsafe fn from_ptr(ptr: ptr::NonNull) -> Self { + ffi::ALooper_acquire(ptr.as_ptr()); + Self { ptr } + } + + /// Returns a pointer to the NDK `ALooper` object. + #[inline] + pub fn ptr(&self) -> ptr::NonNull { + self.ptr + } + + /// Wakes the looper. An event of [`Poll::Wake`] will be sent. + pub fn wake(&self) { + unsafe { ffi::ALooper_wake(self.ptr.as_ptr()) } + } + + /// Adds a file descriptor to be polled, without a callback. + /// + /// See also [the NDK + /// docs](https://developer.android.com/ndk/reference/group/looper.html#alooper_addfd). + /// + /// # Safety + /// The caller should guarantee that this file descriptor stays open until it is removed via + /// [`remove_fd()`][Self::remove_fd()], and for however long the caller wishes to use this file + /// descriptor when it is returned in [`Poll::Event::fd`]. + + // `ALooper_addFd` won't dereference `data`; it will only pass it on to the event. + // Optionally dereferencing it there already enforces `unsafe` context. + #[allow(clippy::not_unsafe_ptr_arg_deref)] + pub fn add_fd( + &self, + fd: BorrowedFd<'_>, + ident: i32, + events: FdEvent, + data: *mut c_void, + ) -> Result<(), LooperError> { + match unsafe { + ffi::ALooper_addFd( + self.ptr.as_ptr(), + fd.as_raw_fd(), + ident, + events.bits() as i32, + None, + data, + ) + } { + 1 => Ok(()), + -1 => Err(LooperError), + _ => unreachable!(), + } + } + + /// Adds a file descriptor to be polled, with a callback that is invoked when any of the + /// [`FdEvent`]s described in `events` is triggered. + /// + /// The callback receives the file descriptor it is associated with and a bitmask of the poll + /// events that were triggered (typically [`FdEvent::INPUT`]). It should return [`true`] to + /// continue receiving callbacks, or [`false`] to have the callback unregistered. + /// + /// See also [the NDK + /// docs](https://developer.android.com/ndk/reference/group/looper.html#alooper_addfd). + /// + /// Note that this will leak a [`Box`] unless the callback returns [`false`] to unregister + /// itself. + /// + /// # Threading + /// This function will be called on the looper thread where and when it is polled. + /// For registering callbacks without [`Send`] requirement, call the equivalent + /// [`ThreadLooper::add_fd_with_callback()`] function on the Looper thread. + /// + /// # Safety + /// The caller should guarantee that this file descriptor stays open until it is removed via + /// [`remove_fd()`][Self::remove_fd()] or by returning [`false`] from the callback, and for + /// however long the caller wishes to use this file descriptor inside and after the callback. + #[doc(alias = "ALooper_addFd")] + pub fn add_fd_with_callback, FdEvent) -> bool + Send>( + &self, + fd: BorrowedFd<'_>, + events: FdEvent, + callback: F, + ) -> Result<(), LooperError> { + unsafe { self.add_fd_with_callback_assume_send(fd, events, callback) } + } + + /// Private helper to deduplicate/commonize the implementation behind + /// [`ForeignLooper::add_fd_with_callback()`] and [`ThreadLooper::add_fd_with_callback()`], + /// as both have their own way of guaranteeing thread-safety. The former, [`ForeignLooper`], + /// requires the closure to be [`Send`]. The latter, [`ThreadLooper`], can only exist on the + /// thread where polling happens and where the closure will end up being invoked, and does not + /// require [`Send`]. + /// + /// # Safety + /// The caller must guarantee that `F` is [`Send`] or that `F` will only run on the current + /// thread. See the explanation above about why this function exists. + unsafe fn add_fd_with_callback_assume_send, FdEvent) -> bool>( + &self, + fd: BorrowedFd<'_>, + events: FdEvent, + callback: F, + ) -> Result<(), LooperError> { + extern "C" fn cb_handler, FdEvent) -> bool>( + fd: RawFd, + events: i32, + data: *mut c_void, + ) -> i32 { + abort_on_panic(|| unsafe { + let mut cb = ManuallyDrop::new(Box::::from_raw(data as *mut _)); + let events = FdEvent::from_bits_retain( + events.try_into().expect("Unexpected sign bit in `events`"), + ); + let keep_registered = cb(BorrowedFd::borrow_raw(fd), events); + if !keep_registered { + ManuallyDrop::into_inner(cb); + } + keep_registered as i32 + }) + } + let data = Box::into_raw(Box::new(callback)) as *mut _; + match unsafe { + ffi::ALooper_addFd( + self.ptr.as_ptr(), + fd.as_raw_fd(), + ffi::ALOOPER_POLL_CALLBACK, + events.bits() as i32, + Some(cb_handler::), + data, + ) + } { + 1 => Ok(()), + -1 => Err(LooperError), + _ => unreachable!(), + } + } + + /// Removes a previously added file descriptor from the looper. + /// + /// Returns [`true`] if the file descriptor was removed, [`false`] if it was not previously + /// registered. + /// + /// # Safety + /// When this method returns, it is safe to close the file descriptor since the looper will no + /// longer have a reference to it. However, it is possible for the callback to already be + /// running or for it to run one last time if the file descriptor was already signalled. + /// Calling code is responsible for ensuring that this case is safely handled. For example, if + /// the callback takes care of removing itself during its own execution either by returning `0` + /// or by calling this method, then it can be guaranteed to not be invoked again at any later + /// time unless registered anew. + /// + /// Note that unregistering a file descriptor with callback will leak a [`Box`] created in + /// [`add_fd_with_callback()`][Self::add_fd_with_callback()]. Consider returning [`false`] + /// from the callback instead to drop it. + pub fn remove_fd(&self, fd: BorrowedFd<'_>) -> Result { + match unsafe { ffi::ALooper_removeFd(self.ptr.as_ptr(), fd.as_raw_fd()) } { + 1 => Ok(true), + 0 => Ok(false), + -1 => Err(LooperError), + _ => unreachable!(), + } + } +} diff --git a/clients/android/native/vendor/ndk/src/media/image_reader.rs b/clients/android/native/vendor/ndk/src/media/image_reader.rs new file mode 100644 index 00000000..d4143776 --- /dev/null +++ b/clients/android/native/vendor/ndk/src/media/image_reader.rs @@ -0,0 +1,459 @@ +//! Bindings for [`AImageReader`] and [`AImage`] +//! +//! [`AImageReader`]: https://developer.android.com/ndk/reference/group/media#aimagereader +//! [`AImage`]: https://developer.android.com/ndk/reference/group/media#aimage +#![cfg(feature = "api-level-24")] + +use crate::media_error::{construct, construct_never_null, MediaError, Result}; +use crate::native_window::NativeWindow; +use crate::utils::abort_on_panic; +use num_enum::{FromPrimitive, IntoPrimitive}; +use std::{ + ffi::c_void, + fmt::{self, Debug, Formatter}, + mem::MaybeUninit, + ptr::NonNull, +}; + +#[cfg(feature = "api-level-26")] +use std::os::fd::{FromRawFd, IntoRawFd, OwnedFd}; + +#[cfg(feature = "api-level-26")] +use crate::hardware_buffer::{HardwareBuffer, HardwareBufferUsage}; + +#[repr(i32)] +#[derive(Copy, Clone, Debug, PartialEq, Eq, FromPrimitive, IntoPrimitive)] +#[allow(non_camel_case_types)] +#[non_exhaustive] +pub enum ImageFormat { + RGBA_8888 = ffi::AIMAGE_FORMATS::AIMAGE_FORMAT_RGBA_8888.0 as i32, + RGBX_8888 = ffi::AIMAGE_FORMATS::AIMAGE_FORMAT_RGBX_8888.0 as i32, + RGB_888 = ffi::AIMAGE_FORMATS::AIMAGE_FORMAT_RGB_888.0 as i32, + RGB_565 = ffi::AIMAGE_FORMATS::AIMAGE_FORMAT_RGB_565.0 as i32, + RGBA_FP16 = ffi::AIMAGE_FORMATS::AIMAGE_FORMAT_RGBA_FP16.0 as i32, + YUV_420_888 = ffi::AIMAGE_FORMATS::AIMAGE_FORMAT_YUV_420_888.0 as i32, + JPEG = ffi::AIMAGE_FORMATS::AIMAGE_FORMAT_JPEG.0 as i32, + RAW16 = ffi::AIMAGE_FORMATS::AIMAGE_FORMAT_RAW16.0 as i32, + RAW_PRIVATE = ffi::AIMAGE_FORMATS::AIMAGE_FORMAT_RAW_PRIVATE.0 as i32, + RAW10 = ffi::AIMAGE_FORMATS::AIMAGE_FORMAT_RAW10.0 as i32, + RAW12 = ffi::AIMAGE_FORMATS::AIMAGE_FORMAT_RAW12.0 as i32, + DEPTH16 = ffi::AIMAGE_FORMATS::AIMAGE_FORMAT_DEPTH16.0 as i32, + DEPTH_POINT_CLOUD = ffi::AIMAGE_FORMATS::AIMAGE_FORMAT_DEPTH_POINT_CLOUD.0 as i32, + PRIVATE = ffi::AIMAGE_FORMATS::AIMAGE_FORMAT_PRIVATE.0 as i32, + Y8 = ffi::AIMAGE_FORMATS::AIMAGE_FORMAT_Y8.0 as i32, + HEIC = ffi::AIMAGE_FORMATS::AIMAGE_FORMAT_HEIC.0 as i32, + DEPTH_JPEG = ffi::AIMAGE_FORMATS::AIMAGE_FORMAT_DEPTH_JPEG.0 as i32, + + #[doc(hidden)] + #[num_enum(catch_all)] + __Unknown(i32), +} + +pub type ImageListener = Box; + +#[cfg(feature = "api-level-26")] +pub type BufferRemovedListener = Box; + +/// Result returned by: +/// - [`ImageReader::acquire_next_image()`]` +/// - [`ImageReader::acquire_next_image_async()`]` +/// - [`ImageReader::acquire_latest_image()`]` +/// - [`ImageReader::acquire_latest_image_async()`]` +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum AcquireResult { + /// Returned if there is no buffers currently available in the reader queue. + #[doc(alias = "AMEDIA_IMGREADER_NO_BUFFER_AVAILABLE")] + NoBufferAvailable, + /// Returned if the number of concurrently acquired images has reached the limit. + #[doc(alias = "AMEDIA_IMGREADER_MAX_IMAGES_ACQUIRED")] + MaxImagesAcquired, + + /// Returned if an [`Image`] (optionally with fence) was successfully acquired. + Image(T), +} + +impl AcquireResult { + fn map(self, f: impl FnOnce(T) -> U) -> AcquireResult { + match self { + AcquireResult::Image(img) => AcquireResult::Image(f(img)), + AcquireResult::NoBufferAvailable => AcquireResult::NoBufferAvailable, + AcquireResult::MaxImagesAcquired => AcquireResult::MaxImagesAcquired, + } + } +} + +impl AcquireResult { + /// Inlined version of [`construct_never_null()`] with IMGREADER-specific result mapping. + fn construct_never_null( + with_ptr: impl FnOnce(*mut *mut ffi::AImage) -> ffi::media_status_t, + ) -> Result { + let mut result = MaybeUninit::uninit(); + let status = with_ptr(result.as_mut_ptr()); + match status { + ffi::media_status_t::AMEDIA_IMGREADER_NO_BUFFER_AVAILABLE => { + Ok(Self::NoBufferAvailable) + } + ffi::media_status_t::AMEDIA_IMGREADER_MAX_IMAGES_ACQUIRED => { + Ok(Self::MaxImagesAcquired) + } + status => MediaError::from_status(status).map(|()| { + let result = unsafe { result.assume_init() }; + Self::Image(Image { + inner: if cfg!(debug_assertions) { + NonNull::new(result).expect("result should never be null") + } else { + unsafe { NonNull::new_unchecked(result) } + }, + }) + }), + } + } +} + +/// A native [`AImageReader *`] +/// +/// [`AImageReader *`]: https://developer.android.com/ndk/reference/group/media#aimagereader +pub struct ImageReader { + inner: NonNull, + image_cb: Option>, + #[cfg(feature = "api-level-26")] + buffer_removed_cb: Option>, +} + +impl Debug for ImageReader { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.debug_struct("ImageReader") + .field("inner", &self.inner) + .field( + "image_cb", + match &self.image_cb { + Some(_) => &"Some(_)", + None => &"None", + }, + ) + .finish() + } +} + +impl ImageReader { + fn from_ptr(inner: NonNull) -> Self { + Self { + inner, + image_cb: None, + #[cfg(feature = "api-level-26")] + buffer_removed_cb: None, + } + } + + fn as_ptr(&self) -> *mut ffi::AImageReader { + self.inner.as_ptr() + } + + pub fn new(width: i32, height: i32, format: ImageFormat, max_images: i32) -> Result { + let inner = construct_never_null(|res| unsafe { + ffi::AImageReader_new(width, height, format.into(), max_images, res) + })?; + + Ok(Self::from_ptr(inner)) + } + + #[cfg(feature = "api-level-26")] + pub fn new_with_usage( + width: i32, + height: i32, + format: ImageFormat, + usage: HardwareBufferUsage, + max_images: i32, + ) -> Result { + let inner = construct_never_null(|res| unsafe { + ffi::AImageReader_newWithUsage( + width, + height, + format.into(), + usage.bits(), + max_images, + res, + ) + })?; + + Ok(Self::from_ptr(inner)) + } + + #[doc(alias = "AImageReader_setImageListener")] + pub fn set_image_listener(&mut self, listener: ImageListener) -> Result<()> { + let mut boxed = Box::new(listener); + let ptr: *mut ImageListener = &mut *boxed; + + unsafe extern "C" fn on_image_available( + context: *mut c_void, + reader: *mut ffi::AImageReader, + ) { + abort_on_panic(|| { + let reader = ImageReader::from_ptr(NonNull::new_unchecked(reader)); + let listener: *mut ImageListener = context.cast(); + (*listener)(&reader); + std::mem::forget(reader); + }) + } + + let mut listener = ffi::AImageReader_ImageListener { + context: ptr as _, + onImageAvailable: Some(on_image_available), + }; + let status = unsafe { ffi::AImageReader_setImageListener(self.as_ptr(), &mut listener) }; + + // keep listener alive until Drop or new listener is assigned + self.image_cb = Some(boxed); + + MediaError::from_status(status) + } + + #[cfg(feature = "api-level-26")] + #[doc(alias = "AImageReader_setBufferRemovedListener")] + pub fn set_buffer_removed_listener(&mut self, listener: BufferRemovedListener) -> Result<()> { + let mut boxed = Box::new(listener); + let ptr: *mut BufferRemovedListener = &mut *boxed; + + unsafe extern "C" fn on_buffer_removed( + context: *mut c_void, + reader: *mut ffi::AImageReader, + buffer: *mut ffi::AHardwareBuffer, + ) { + abort_on_panic(|| { + let reader = ImageReader::from_ptr(NonNull::new_unchecked(reader)); + let buffer = HardwareBuffer::from_ptr(NonNull::new_unchecked(buffer)); + let listener: *mut BufferRemovedListener = context.cast(); + (*listener)(&reader, &buffer); + std::mem::forget(reader); + }) + } + + let mut listener = ffi::AImageReader_BufferRemovedListener { + context: ptr as _, + onBufferRemoved: Some(on_buffer_removed), + }; + let status = + unsafe { ffi::AImageReader_setBufferRemovedListener(self.as_ptr(), &mut listener) }; + + // keep listener alive until Drop or new listener is assigned + self.buffer_removed_cb = Some(boxed); + + MediaError::from_status(status) + } + + /// Get a [`NativeWindow`] that can be used to produce [`Image`]s for this [`ImageReader`]. + /// + /// + #[doc(alias = "AImageReader_getWindow")] + pub fn window(&self) -> Result { + unsafe { + let ptr = construct_never_null(|res| ffi::AImageReader_getWindow(self.as_ptr(), res))?; + Ok(NativeWindow::clone_from_ptr(ptr)) + } + } + + #[doc(alias = "AImageReader_getWidth")] + pub fn width(&self) -> Result { + construct(|res| unsafe { ffi::AImageReader_getWidth(self.as_ptr(), res) }) + } + + #[doc(alias = "AImageReader_getHeight")] + pub fn height(&self) -> Result { + construct(|res| unsafe { ffi::AImageReader_getHeight(self.as_ptr(), res) }) + } + + #[doc(alias = "AImageReader_getFormat")] + pub fn format(&self) -> Result { + let format = construct(|res| unsafe { ffi::AImageReader_getFormat(self.as_ptr(), res) })?; + Ok(format.into()) + } + + #[doc(alias = "AImageReader_getMaxImages")] + pub fn max_images(&self) -> Result { + construct(|res| unsafe { ffi::AImageReader_getMaxImages(self.as_ptr(), res) }) + } + + #[doc(alias = "AImageReader_acquireNextImage")] + pub fn acquire_next_image(&self) -> Result> { + AcquireResult::construct_never_null(|res| unsafe { + ffi::AImageReader_acquireNextImage(self.as_ptr(), res) + }) + } + + /// Acquire the next [`Image`] from the image reader's queue asynchronously. + /// + /// # Safety + /// If the returned file descriptor is not [`None`], it must be awaited before attempting to + /// access the [`Image`] returned. + /// + /// + #[cfg(feature = "api-level-26")] + #[doc(alias = "AImageReader_acquireNextImageAsync")] + pub unsafe fn acquire_next_image_async( + &self, + ) -> Result)>> { + let mut fence = MaybeUninit::uninit(); + AcquireResult::construct_never_null(|res| { + ffi::AImageReader_acquireNextImageAsync(self.as_ptr(), res, fence.as_mut_ptr()) + }) + .map(|result| { + result.map(|image| match fence.assume_init() { + -1 => (image, None), + fence => (image, Some(unsafe { OwnedFd::from_raw_fd(fence) })), + }) + }) + } + + #[doc(alias = "AImageReader_acquireLatestImage")] + pub fn acquire_latest_image(&self) -> Result> { + AcquireResult::construct_never_null(|res| unsafe { + ffi::AImageReader_acquireLatestImage(self.as_ptr(), res) + }) + } + + /// Acquire the latest [`Image`] from the image reader's queue asynchronously, dropping older images. + /// + /// # Safety + /// If the returned file descriptor is not [`None`], it must be awaited before attempting to + /// access the [`Image`] returned. + /// + /// + #[cfg(feature = "api-level-26")] + #[doc(alias = "AImageReader_acquireLatestImageAsync")] + pub unsafe fn acquire_latest_image_async( + &self, + ) -> Result)>> { + let mut fence = MaybeUninit::uninit(); + AcquireResult::construct_never_null(|res| { + ffi::AImageReader_acquireLatestImageAsync(self.as_ptr(), res, fence.as_mut_ptr()) + }) + .map(|result| { + result.map(|image| match fence.assume_init() { + -1 => (image, None), + fence => (image, Some(unsafe { OwnedFd::from_raw_fd(fence) })), + }) + }) + } +} + +impl Drop for ImageReader { + #[doc(alias = "AImageReader_delete")] + fn drop(&mut self) { + unsafe { ffi::AImageReader_delete(self.as_ptr()) }; + } +} + +/// A native [`AImage *`] +/// +/// [`AImage *`]: https://developer.android.com/ndk/reference/group/media#aimage +#[derive(Debug)] +#[doc(alias = "AImage")] +pub struct Image { + inner: NonNull, +} + +#[doc(alias = "AImageCropRect")] +pub type CropRect = ffi::AImageCropRect; + +impl Image { + fn as_ptr(&self) -> *mut ffi::AImage { + self.inner.as_ptr() + } + + #[doc(alias = "AImage_getPlaneData")] + pub fn plane_data(&self, plane_idx: i32) -> Result<&[u8]> { + let mut result_ptr = MaybeUninit::uninit(); + let mut result_len = MaybeUninit::uninit(); + let status = unsafe { + ffi::AImage_getPlaneData( + self.as_ptr(), + plane_idx, + result_ptr.as_mut_ptr(), + result_len.as_mut_ptr(), + ) + }; + + MediaError::from_status(status).map(|()| unsafe { + std::slice::from_raw_parts(result_ptr.assume_init(), result_len.assume_init() as _) + }) + } + + #[doc(alias = "AImage_getPlanePixelStride")] + pub fn plane_pixel_stride(&self, plane_idx: i32) -> Result { + construct(|res| unsafe { ffi::AImage_getPlanePixelStride(self.as_ptr(), plane_idx, res) }) + } + + #[doc(alias = "AImage_getPlaneRowStride")] + pub fn plane_row_stride(&self, plane_idx: i32) -> Result { + construct(|res| unsafe { ffi::AImage_getPlaneRowStride(self.as_ptr(), plane_idx, res) }) + } + + #[doc(alias = "AImage_getCropRect")] + pub fn crop_rect(&self) -> Result { + construct(|res| unsafe { ffi::AImage_getCropRect(self.as_ptr(), res) }) + } + + #[doc(alias = "AImage_getWidth")] + pub fn width(&self) -> Result { + construct(|res| unsafe { ffi::AImage_getWidth(self.as_ptr(), res) }) + } + + #[doc(alias = "AImage_getHeight")] + pub fn height(&self) -> Result { + construct(|res| unsafe { ffi::AImage_getHeight(self.as_ptr(), res) }) + } + + #[doc(alias = "AImage_getFormat")] + pub fn format(&self) -> Result { + let format = construct(|res| unsafe { ffi::AImage_getFormat(self.as_ptr(), res) })?; + Ok(format.into()) + } + + #[doc(alias = "AImage_getTimestamp")] + pub fn timestamp(&self) -> Result { + construct(|res| unsafe { ffi::AImage_getTimestamp(self.as_ptr(), res) }) + } + + #[doc(alias = "AImage_getNumberOfPlanes")] + pub fn number_of_planes(&self) -> Result { + construct(|res| unsafe { ffi::AImage_getNumberOfPlanes(self.as_ptr(), res) }) + } + + /// Get the hardware buffer handle of the input image intended for GPU and/or hardware access. + /// + /// Note that no reference on the returned [`HardwareBuffer`] handle is acquired automatically. + /// Once the [`Image`] or the parent [`ImageReader`] is deleted, the [`HardwareBuffer`] handle + /// from previous [`Image::hardware_buffer()`] becomes invalid. + /// + /// If the caller ever needs to hold on a reference to the [`HardwareBuffer`] handle after the + /// [`Image`] or the parent [`ImageReader`] is deleted, it must call + /// [`HardwareBuffer::acquire()`] to acquire an extra reference, and [`drop()`] it when + /// finished using it in order to properly deallocate the underlying memory managed by + /// [`HardwareBuffer`]. If the caller has acquired an extra reference on a [`HardwareBuffer`] + /// returned from this function, it must also register a listener using + /// [`ImageReader::set_buffer_removed_listener()`] to be notified when the buffer is no longer + /// used by [`ImageReader`]. + #[cfg(feature = "api-level-26")] + #[doc(alias = "AImage_getHardwareBuffer")] + pub fn hardware_buffer(&self) -> Result { + unsafe { + let ptr = + construct_never_null(|res| ffi::AImage_getHardwareBuffer(self.as_ptr(), res))?; + Ok(HardwareBuffer::from_ptr(ptr)) + } + } + + #[cfg(feature = "api-level-26")] + #[doc(alias = "AImage_deleteAsync")] + pub fn delete_async(self, release_fence_fd: OwnedFd) { + unsafe { ffi::AImage_deleteAsync(self.as_ptr(), release_fence_fd.into_raw_fd()) }; + std::mem::forget(self); + } +} + +impl Drop for Image { + #[doc(alias = "AImage_delete")] + fn drop(&mut self) { + unsafe { ffi::AImage_delete(self.as_ptr()) }; + } +} diff --git a/clients/android/native/vendor/ndk/src/media/media_codec.rs b/clients/android/native/vendor/ndk/src/media/media_codec.rs new file mode 100644 index 00000000..1b79bea1 --- /dev/null +++ b/clients/android/native/vendor/ndk/src/media/media_codec.rs @@ -0,0 +1,634 @@ +//! Bindings for [`AMediaCodec`] +//! +//! [`AMediaCodec`]: https://developer.android.com/ndk/reference/group/media#amediacodec + +#[deprecated = "MediaFormat should be referenced directly from the media_format module"] +pub use super::media_format::MediaFormat; +use crate::media_error::{MediaError, Result}; +use crate::native_window::NativeWindow; +use crate::utils::abort_on_panic; +use std::{ + ffi::{c_char, c_void, CStr, CString}, + fmt, + mem::MaybeUninit, + pin::Pin, + ptr::{self, NonNull}, + slice, + time::Duration, +}; + +#[derive(Debug, PartialEq, Eq)] +pub enum MediaCodecDirection { + Decoder, + Encoder, +} + +/// A native [`AMediaCodec *`] +/// +/// [`AMediaCodec *`]: https://developer.android.com/ndk/reference/group/media#amediacodec +#[derive(Debug)] +pub struct MediaCodec { + inner: NonNull, + async_notify_callback: Option>>, +} + +pub struct AsyncNotifyCallback { + /// Called when an input buffer becomes available. + /// + /// The specified index is the index of the available input buffer. + pub on_input_available: Option, + + /// Called when an output buffer becomes available. + /// + /// The specified index is the index of the available output buffer. The specified + /// [`BufferInfo`] contains information regarding the available output buffer. + pub on_output_available: Option, + + /// Called when the output format has changed. + /// + /// The specified format contains the new output format. + pub on_format_changed: Option, + + /// Called when the [`MediaCodec`] encountered an error. + /// + /// The specified [`ActionCode`] indicates the possible actions that client can take, and it can + /// be checked by calling [`ActionCode::is_recoverable`] or [`ActionCode::is_transient`]. If + /// both [`ActionCode::is_recoverable`] and [`ActionCode::is_transient`] return [`false`], then + /// the codec error is fatal and the codec must be deleted. The specified detail string may + /// contain more detailed messages about this error. + pub on_error: Option, +} + +impl fmt::Debug for AsyncNotifyCallback { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("AsyncNotifyCallback") + .field( + "on_input_available", + match &self.on_input_available { + Some(_) => &"Some(_)", + None => &"None", + }, + ) + .field( + "on_output_available", + match &self.on_output_available { + Some(_) => &"Some(_)", + None => &"None", + }, + ) + .field( + "on_format_changed", + match &self.on_format_changed { + Some(_) => &"Some(_)", + None => &"None", + }, + ) + .field( + "on_error", + match &self.on_error { + Some(_) => &"Some(_)", + None => &"None", + }, + ) + .finish() + } +} + +pub type InputAvailableCallback = Box; +pub type OutputAvailableCallback = Box; +pub type FormatChangedCallback = Box; +pub type ErrorCallback = Box; + +impl MediaCodec { + /// [punktfunk vendored patch — the ONLY change to this crate] Public so callers can bind + /// `AMediaCodec_*` entry points the wrapper doesn't expose yet (here: + /// `AMediaCodec_setOnFrameRenderedCallback` for the HUD's display stage). The pointer is valid + /// for `&self`'s lifetime; callers must not delete or re-configure the codec through it. + pub fn as_ptr(&self) -> *mut ffi::AMediaCodec { + self.inner.as_ptr() + } + + pub fn from_codec_name(name: &str) -> Option { + let c_string = CString::new(name).unwrap(); + Some(Self { + inner: NonNull::new(unsafe { ffi::AMediaCodec_createCodecByName(c_string.as_ptr()) })?, + async_notify_callback: None, + }) + } + + pub fn from_decoder_type(mime_type: &str) -> Option { + let c_string = CString::new(mime_type).unwrap(); + Some(Self { + inner: NonNull::new(unsafe { + ffi::AMediaCodec_createDecoderByType(c_string.as_ptr()) + })?, + async_notify_callback: None, + }) + } + + pub fn from_encoder_type(mime_type: &str) -> Option { + let c_string = CString::new(mime_type).unwrap(); + Some(Self { + inner: NonNull::new(unsafe { + ffi::AMediaCodec_createEncoderByType(c_string.as_ptr()) + })?, + async_notify_callback: None, + }) + } + + /// Set an asynchronous callback for actionable [`MediaCodec`] events. + /// + /// When asynchronous callback is enabled, it is an error for the client to call + /// [`MediaCodec::dequeue_input_buffer()`] or [`MediaCodec::dequeue_output_buffer()`]. + /// + /// [`MediaCodec::flush()`] behaves differently in asynchronous mode. After calling + /// [`MediaCodec::flush()`], the client must call [`MediaCodec::start()`] to "resume" receiving + /// input buffers. Even if the client does not receive + /// [`AsyncNotifyCallback::on_input_available`] callbacks from video encoders configured with an + /// input surface, the client still needs to call [`MediaCodec::start()`] to resume the input + /// surface to send buffers to the encoders. + /// + /// When called with [`None`] callback, this method unregisters any previously set callback. + /// + /// Refer to the definition of [`AsyncNotifyCallback`] on how each callback function is called + /// and what are specified. + /// + /// Once the callback is unregistered or the codec is reset / released, the previously + /// registered callback will not be called. + /// + /// All callbacks are fired on one NDK internal thread. + /// [`MediaCodec::set_async_notify_callback()`] should not be called on the callback thread. No + /// heavy duty task should be performed on callback thread. + #[cfg(feature = "api-level-28")] + pub fn set_async_notify_callback( + &mut self, + callback: Option, + ) -> Result<()> { + unsafe extern "C" fn ffi_on_input_available( + _codec: *mut ffi::AMediaCodec, + user_data: *mut c_void, + index: i32, + ) { + abort_on_panic(|| { + let callback = &mut *(user_data as *mut AsyncNotifyCallback); + if let Some(f) = callback.on_input_available.as_mut() { + f(index as usize); + } + }) + } + + unsafe extern "C" fn ffi_on_output_available( + _codec: *mut ffi::AMediaCodec, + user_data: *mut c_void, + index: i32, + buffer_info: *mut ffi::AMediaCodecBufferInfo, + ) { + abort_on_panic(|| { + let callback = &mut *(user_data as *mut AsyncNotifyCallback); + if let Some(f) = callback.on_output_available.as_mut() { + let buffer_info = BufferInfo { + inner: *buffer_info, + }; + f(index as usize, &buffer_info); + } + }) + } + + unsafe extern "C" fn ffi_on_format_changed( + _codec: *mut ffi::AMediaCodec, + user_data: *mut c_void, + format: *mut ffi::AMediaFormat, + ) { + abort_on_panic(|| { + // Ownership of the format is not documented, but the implementation allocates a new instance and does + // not free it, so assume it is ok for us to do so + // https://cs.android.com/android/platform/superproject/main/+/refs/heads/main:frameworks/av/media/ndk/NdkMediaCodec.cpp;l=248-254;drc=5e15c3e22f3fa32d64e57302201123ce41589adf + let format = MediaFormat::from_ptr(NonNull::new_unchecked(format)); + + let callback = &mut *(user_data as *mut AsyncNotifyCallback); + if let Some(f) = callback.on_format_changed.as_mut() { + f(&format); + } + }) + } + + unsafe extern "C" fn ffi_on_error( + _codec: *mut ffi::AMediaCodec, + user_data: *mut c_void, + error: ffi::media_status_t, + action_code: i32, + detail: *const c_char, + ) { + abort_on_panic(|| { + let callback = &mut *(user_data as *mut AsyncNotifyCallback); + if let Some(f) = callback.on_error.as_mut() { + f( + MediaError::from_status(error).unwrap_err(), + ActionCode(action_code), + CStr::from_ptr(detail), + ); + } + }) + } + + let (callback, ffi_callback, user_data) = if let Some(callback) = callback { + // On Android 12 and earlier, due to faulty null checks, if a callback is not set, but at least one other + // callback *is* set, then it will segfault in when trying to invoke the unset callback. See for example: + // https://cs.android.com/android/platform/superproject/+/android-12.0.0_r34:frameworks/av/media/ndk/NdkMediaCodec.cpp;l=161-162;drc=ef058464777739e2d9ffad5f00d0e57b186d9a13 + // To work around this we just enable all callbacks and do nothing if the corresponding callback is not set + // in AsyncNotifyCallback + let ffi_callback = ffi::AMediaCodecOnAsyncNotifyCallback { + onAsyncInputAvailable: Some(ffi_on_input_available), + onAsyncOutputAvailable: Some(ffi_on_output_available), + onAsyncFormatChanged: Some(ffi_on_format_changed), + onAsyncError: Some(ffi_on_error), + }; + + let mut boxed = Box::pin(callback); + let ptr: *mut AsyncNotifyCallback = &mut *boxed; + + (Some(boxed), ffi_callback, ptr as *mut c_void) + } else { + let ffi_callback = ffi::AMediaCodecOnAsyncNotifyCallback { + onAsyncInputAvailable: None, + onAsyncOutputAvailable: None, + onAsyncFormatChanged: None, + onAsyncError: None, + }; + + (None, ffi_callback, ptr::null_mut()) + }; + + let status = unsafe { + ffi::AMediaCodec_setAsyncNotifyCallback(self.as_ptr(), ffi_callback, user_data) + }; + let result = MediaError::from_status(status); + + // This behavior is not documented, but the implementation always clears the callback on failure, so we must + // clear any callback that may have been previously registered + // https://cs.android.com/android/platform/superproject/main/+/main:frameworks/av/media/ndk/NdkMediaCodec.cpp;l=581-584;drc=8c4e619c7461ac1a8c20c55364643662e9185e4d + if result.is_ok() { + self.async_notify_callback = callback; + } else { + self.async_notify_callback = None; + } + + result + } + + pub fn configure( + &self, + format: &MediaFormat, + surface: Option<&NativeWindow>, + direction: MediaCodecDirection, + ) -> Result<()> { + let status = unsafe { + ffi::AMediaCodec_configure( + self.as_ptr(), + format.as_ptr(), + surface.map_or(ptr::null_mut(), |s| s.ptr().as_ptr()), + ptr::null_mut(), + if direction == MediaCodecDirection::Encoder { + ffi::AMEDIACODEC_CONFIGURE_FLAG_ENCODE as u32 + } else { + 0 + }, + ) + }; + MediaError::from_status(status) + } + + #[cfg(feature = "api-level-26")] + pub fn create_input_surface(&self) -> Result { + use crate::media_error::construct_never_null; + unsafe { + let ptr = construct_never_null(|res| { + ffi::AMediaCodec_createInputSurface(self.as_ptr(), res) + })?; + Ok(NativeWindow::from_ptr(ptr)) + } + } + + #[cfg(feature = "api-level-26")] + pub fn create_persistent_input_surface() -> Result { + use crate::media_error::construct_never_null; + unsafe { + let ptr = + construct_never_null(|res| ffi::AMediaCodec_createPersistentInputSurface(res))?; + Ok(NativeWindow::from_ptr(ptr)) + } + } + + pub fn dequeue_input_buffer(&self, timeout: Duration) -> Result> { + let result = unsafe { + ffi::AMediaCodec_dequeueInputBuffer( + self.as_ptr(), + timeout + .as_micros() + .try_into() + .expect("Supplied timeout is too large"), + ) + }; + + if result == ffi::AMEDIACODEC_INFO_TRY_AGAIN_LATER as isize { + Ok(DequeuedInputBufferResult::TryAgainLater) + } else { + let index = MediaError::from_status_if_negative(result)? as usize; + Ok(DequeuedInputBufferResult::Buffer(InputBuffer { + codec: self, + index, + })) + } + } + + pub fn dequeue_output_buffer( + &self, + timeout: Duration, + ) -> Result> { + let mut info = MaybeUninit::uninit(); + + let result = unsafe { + ffi::AMediaCodec_dequeueOutputBuffer( + self.as_ptr(), + info.as_mut_ptr(), + timeout + .as_micros() + .try_into() + .expect("Supplied timeout is too large"), + ) + }; + + if result == ffi::AMEDIACODEC_INFO_TRY_AGAIN_LATER as isize { + Ok(DequeuedOutputBufferInfoResult::TryAgainLater) + } else if result == ffi::AMEDIACODEC_INFO_OUTPUT_FORMAT_CHANGED as isize { + Ok(DequeuedOutputBufferInfoResult::OutputFormatChanged) + } else if result == ffi::AMEDIACODEC_INFO_OUTPUT_BUFFERS_CHANGED as isize { + Ok(DequeuedOutputBufferInfoResult::OutputBuffersChanged) + } else { + let index = MediaError::from_status_if_negative(result)? as usize; + Ok(DequeuedOutputBufferInfoResult::Buffer(OutputBuffer { + codec: self, + index, + info: BufferInfo { + inner: unsafe { info.assume_init() }, + }, + })) + } + } + + pub fn flush(&self) -> Result<()> { + let status = unsafe { ffi::AMediaCodec_flush(self.as_ptr()) }; + MediaError::from_status(status) + } + + pub fn input_buffer(&self, index: usize) -> Option<&mut [MaybeUninit]> { + unsafe { + let mut out_size = 0; + let buffer_ptr = ffi::AMediaCodec_getInputBuffer(self.as_ptr(), index, &mut out_size); + if buffer_ptr.is_null() { + return None; + } + Some(slice::from_raw_parts_mut(buffer_ptr.cast(), out_size)) + } + } + + pub fn output_buffer(&self, index: usize) -> Option<&[u8]> { + unsafe { + let mut out_size = 0; + let buffer_ptr = ffi::AMediaCodec_getOutputBuffer(self.as_ptr(), index, &mut out_size); + if buffer_ptr.is_null() { + return None; + } + Some(slice::from_raw_parts(buffer_ptr, out_size)) + } + } + + #[cfg(feature = "api-level-28")] + pub fn input_format(&self) -> MediaFormat { + let inner = NonNull::new(unsafe { ffi::AMediaCodec_getInputFormat(self.as_ptr()) }) + .expect("AMediaCodec_getInputFormat returned NULL"); + unsafe { MediaFormat::from_ptr(inner) } + } + + pub fn output_format(&self) -> MediaFormat { + let inner = NonNull::new(unsafe { ffi::AMediaCodec_getOutputFormat(self.as_ptr()) }) + .expect("AMediaCodec_getOutputFormat returned NULL"); + unsafe { MediaFormat::from_ptr(inner) } + } + + #[cfg(feature = "api-level-28")] + pub fn name(&self) -> Result { + use crate::media_error::construct; + unsafe { + let name_ptr = construct(|name| ffi::AMediaCodec_getName(self.as_ptr(), name))?; + let name = CStr::from_ptr(name_ptr).to_str().unwrap().to_owned(); + ffi::AMediaCodec_releaseName(self.as_ptr(), name_ptr); + + Ok(name) + } + } + + pub fn queue_input_buffer( + &self, + buffer: InputBuffer<'_>, + offset: usize, + size: usize, + time: u64, + flags: u32, + ) -> Result<()> { + debug_assert!(ptr::eq(self, buffer.codec)); + self.queue_input_buffer_by_index(buffer.index, offset, size, time, flags) + } + + pub fn queue_input_buffer_by_index( + &self, + buffer_index: usize, + offset: usize, + size: usize, + time: u64, + flags: u32, + ) -> Result<()> { + let status = unsafe { + ffi::AMediaCodec_queueInputBuffer( + self.as_ptr(), + buffer_index, + offset as ffi::off_t, + size, + time, + flags, + ) + }; + MediaError::from_status(status) + } + + pub fn release_output_buffer(&self, buffer: OutputBuffer<'_>, render: bool) -> Result<()> { + debug_assert!(ptr::eq(self, buffer.codec)); + self.release_output_buffer_by_index(buffer.index, render) + } + + pub fn release_output_buffer_by_index(&self, buffer_index: usize, render: bool) -> Result<()> { + let status = + unsafe { ffi::AMediaCodec_releaseOutputBuffer(self.as_ptr(), buffer_index, render) }; + MediaError::from_status(status) + } + + pub fn release_output_buffer_at_time( + &self, + buffer: OutputBuffer<'_>, + timestamp_ns: i64, + ) -> Result<()> { + debug_assert!(ptr::eq(self, buffer.codec)); + self.release_output_buffer_at_time_by_index(buffer.index, timestamp_ns) + } + + pub fn release_output_buffer_at_time_by_index( + &self, + buffer_index: usize, + timestamp_ns: i64, + ) -> Result<()> { + let status = unsafe { + ffi::AMediaCodec_releaseOutputBufferAtTime(self.as_ptr(), buffer_index, timestamp_ns) + }; + MediaError::from_status(status) + } + + #[cfg(feature = "api-level-26")] + pub fn set_input_surface(&self, surface: &NativeWindow) -> Result<()> { + let status = + unsafe { ffi::AMediaCodec_setInputSurface(self.as_ptr(), surface.ptr().as_ptr()) }; + MediaError::from_status(status) + } + + pub fn set_output_surface(&self, surface: &NativeWindow) -> Result<()> { + let status = + unsafe { ffi::AMediaCodec_setOutputSurface(self.as_ptr(), surface.ptr().as_ptr()) }; + MediaError::from_status(status) + } + + #[cfg(feature = "api-level-26")] + pub fn set_parameters(&self, params: MediaFormat) -> Result<()> { + let status = unsafe { ffi::AMediaCodec_setParameters(self.as_ptr(), params.as_ptr()) }; + MediaError::from_status(status) + } + + #[cfg(feature = "api-level-26")] + pub fn set_signal_end_of_input_stream(&self) -> Result<()> { + let status = unsafe { ffi::AMediaCodec_signalEndOfInputStream(self.as_ptr()) }; + MediaError::from_status(status) + } + + pub fn start(&self) -> Result<()> { + let status = unsafe { ffi::AMediaCodec_start(self.as_ptr()) }; + MediaError::from_status(status) + } + + pub fn stop(&self) -> Result<()> { + let status = unsafe { ffi::AMediaCodec_stop(self.as_ptr()) }; + MediaError::from_status(status) + } +} + +impl Drop for MediaCodec { + fn drop(&mut self) { + let status = unsafe { ffi::AMediaCodec_delete(self.as_ptr()) }; + MediaError::from_status(status).unwrap(); + } +} + +#[derive(Debug)] +pub struct InputBuffer<'a> { + codec: &'a MediaCodec, + index: usize, +} + +impl InputBuffer<'_> { + pub fn buffer_mut(&mut self) -> &mut [MaybeUninit] { + self.codec.input_buffer(self.index).unwrap_or_else(|| { + panic!( + "AMediaCodec_getInputBuffer returned NULL for index {}", + self.index + ) + }) + } +} + +#[derive(Debug)] +pub enum DequeuedInputBufferResult<'a> { + Buffer(InputBuffer<'a>), + TryAgainLater, +} + +#[derive(Debug)] +pub struct OutputBuffer<'a> { + codec: &'a MediaCodec, + index: usize, + info: BufferInfo, +} + +impl OutputBuffer<'_> { + pub fn buffer(&self) -> &[u8] { + self.codec.output_buffer(self.index).unwrap_or_else(|| { + panic!( + "AMediaCodec_getOutputBuffer returned NULL for index {}", + self.index + ) + }) + } + + #[cfg(feature = "api-level-28")] + pub fn format(&self) -> MediaFormat { + let inner = NonNull::new(unsafe { + ffi::AMediaCodec_getBufferFormat(self.codec.as_ptr(), self.index) + }) + .expect("AMediaCodec_getBufferFormat returned NULL"); + unsafe { MediaFormat::from_ptr(inner) } + } + + pub fn info(&self) -> &BufferInfo { + &self.info + } +} + +#[derive(Debug)] +pub enum DequeuedOutputBufferInfoResult<'a> { + Buffer(OutputBuffer<'a>), + TryAgainLater, + OutputFormatChanged, + OutputBuffersChanged, +} + +#[derive(Copy, Clone, Debug)] +pub struct BufferInfo { + inner: ffi::AMediaCodecBufferInfo, +} + +impl BufferInfo { + pub fn offset(&self) -> i32 { + self.inner.offset + } + + pub fn size(&self) -> i32 { + self.inner.size + } + + pub fn presentation_time_us(&self) -> i64 { + self.inner.presentationTimeUs + } + + pub fn flags(&self) -> u32 { + self.inner.flags + } +} + +#[derive(Copy, Clone, Debug)] +pub struct ActionCode(pub i32); + +impl ActionCode { + pub fn is_recoverable(self) -> bool { + unsafe { ffi::AMediaCodecActionCode_isRecoverable(self.0) } + } + + pub fn is_transient(self) -> bool { + unsafe { ffi::AMediaCodecActionCode_isTransient(self.0) } + } +} diff --git a/clients/android/native/vendor/ndk/src/media/media_format.rs b/clients/android/native/vendor/ndk/src/media/media_format.rs new file mode 100644 index 00000000..4997d522 --- /dev/null +++ b/clients/android/native/vendor/ndk/src/media/media_format.rs @@ -0,0 +1,264 @@ +//! Bindings for [`AMediaFormat`] +//! +//! [`AMediaFormat`]: https://developer.android.com/ndk/reference/group/media#amediaformat + +use std::{ + ffi::{CStr, CString}, + fmt, + ptr::{self, NonNull}, + slice, +}; + +use crate::media_error::{MediaError, Result}; + +/// A native [`AMediaFormat *`] +/// +/// [`AMediaFormat *`]: https://developer.android.com/ndk/reference/group/media#amediaformat +#[doc(alias = "AMediaFormat")] +pub struct MediaFormat { + inner: NonNull, +} + +impl fmt::Display for MediaFormat { + /// Human readable representation of the format. + #[doc(alias = "AMediaFormat_toString")] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let c_str = unsafe { CStr::from_ptr(ffi::AMediaFormat_toString(self.as_ptr())) }; + f.write_str(c_str.to_str().unwrap()) + } +} + +impl fmt::Debug for MediaFormat { + #[doc(alias = "AMediaFormat_toString")] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "MediaFormat({:?}: {})", self.inner, self) + } +} + +impl Default for MediaFormat { + #[doc(alias = "AMediaFormat_new")] + fn default() -> Self { + Self::new() + } +} + +impl MediaFormat { + /// Assumes ownership of `ptr` + /// + /// # Safety + /// `ptr` must be a valid pointer to an Android [`ffi::AMediaFormat`]. + pub unsafe fn from_ptr(ptr: NonNull) -> Self { + Self { inner: ptr } + } + + pub fn as_ptr(&self) -> *mut ffi::AMediaFormat { + self.inner.as_ptr() + } + + #[doc(alias = "AMediaFormat_new")] + pub fn new() -> Self { + Self { + inner: NonNull::new(unsafe { ffi::AMediaFormat_new() }).unwrap(), + } + } + + #[doc(alias = "AMediaFormat_getInt32")] + pub fn i32(&self, key: &str) -> Option { + let name = CString::new(key).unwrap(); + let mut out = 0; + if unsafe { ffi::AMediaFormat_getInt32(self.as_ptr(), name.as_ptr(), &mut out) } { + Some(out) + } else { + None + } + } + + #[doc(alias = "AMediaFormat_getInt64")] + pub fn i64(&self, key: &str) -> Option { + let name = CString::new(key).unwrap(); + let mut out = 0; + if unsafe { ffi::AMediaFormat_getInt64(self.as_ptr(), name.as_ptr(), &mut out) } { + Some(out) + } else { + None + } + } + + #[doc(alias = "AMediaFormat_getFloat")] + pub fn f32(&self, key: &str) -> Option { + let name = CString::new(key).unwrap(); + let mut out = 0.0; + if unsafe { ffi::AMediaFormat_getFloat(self.as_ptr(), name.as_ptr(), &mut out) } { + Some(out) + } else { + None + } + } + + #[doc(alias = "AMediaFormat_getSize")] + pub fn usize(&self, key: &str) -> Option { + let name = CString::new(key).unwrap(); + let mut out = 0; + if unsafe { ffi::AMediaFormat_getSize(self.as_ptr(), name.as_ptr(), &mut out) } { + Some(out) + } else { + None + } + } + + #[doc(alias = "AMediaFormat_getBuffer")] + pub fn buffer(&self, key: &str) -> Option<&[u8]> { + let name = CString::new(key).unwrap(); + let mut out_buffer = ptr::null_mut(); + let mut out_size = 0; + unsafe { + ffi::AMediaFormat_getBuffer( + self.as_ptr(), + name.as_ptr(), + &mut out_buffer, + &mut out_size, + ) + } + .then(|| unsafe { slice::from_raw_parts(out_buffer.cast(), out_size) }) + } + + /// The returned `&str` borrow is only valid until the next call to [`MediaFormat::str()`] for + /// the same key. + #[doc(alias = "AMediaFormat_getString")] + pub fn str(&mut self, key: &str) -> Option<&str> { + let name = CString::new(key).unwrap(); + let mut out = ptr::null(); + unsafe { ffi::AMediaFormat_getString(self.as_ptr(), name.as_ptr(), &mut out) } + .then(|| unsafe { CStr::from_ptr(out) }.to_str().unwrap()) + } + + #[doc(alias = "AMediaFormat_setInt32")] + pub fn set_i32(&mut self, key: &str, value: i32) { + let name = CString::new(key).unwrap(); + unsafe { ffi::AMediaFormat_setInt32(self.as_ptr(), name.as_ptr(), value) } + } + + #[doc(alias = "AMediaFormat_setInt64")] + pub fn set_i64(&mut self, key: &str, value: i64) { + let name = CString::new(key).unwrap(); + unsafe { ffi::AMediaFormat_setInt64(self.as_ptr(), name.as_ptr(), value) } + } + + #[doc(alias = "AMediaFormat_setFloat")] + pub fn set_f32(&mut self, key: &str, value: f32) { + let name = CString::new(key).unwrap(); + unsafe { ffi::AMediaFormat_setFloat(self.as_ptr(), name.as_ptr(), value) } + } + + #[doc(alias = "AMediaFormat_setString")] + pub fn set_str(&mut self, key: &str, value: &str) { + let name = CString::new(key).unwrap(); + let c_string = CString::new(value).unwrap(); + unsafe { ffi::AMediaFormat_setString(self.as_ptr(), name.as_ptr(), c_string.as_ptr()) } + } + + #[doc(alias = "AMediaFormat_setBuffer")] + pub fn set_buffer(&mut self, key: &str, value: &[u8]) { + let name = CString::new(key).unwrap(); + unsafe { + ffi::AMediaFormat_setBuffer( + self.as_ptr(), + name.as_ptr(), + value.as_ptr().cast(), + value.len(), + ) + } + } + + #[cfg(feature = "api-level-28")] + #[doc(alias = "AMediaFormat_getDouble")] + pub fn f64(&self, key: &str) -> Option { + let name = CString::new(key).unwrap(); + let mut out = 0.0; + if unsafe { ffi::AMediaFormat_getDouble(self.as_ptr(), name.as_ptr(), &mut out) } { + Some(out) + } else { + None + } + } + + /// Returns (left, top, right, bottom) + #[cfg(feature = "api-level-28")] + #[doc(alias = "AMediaFormat_getRect")] + pub fn rect(&self, key: &str) -> Option<(i32, i32, i32, i32)> { + let name = CString::new(key).unwrap(); + let mut left = 0; + let mut top = 0; + let mut right = 0; + let mut bottom = 0; + if unsafe { + ffi::AMediaFormat_getRect( + self.as_ptr(), + name.as_ptr(), + &mut left, + &mut top, + &mut right, + &mut bottom, + ) + } { + Some((left, top, right, bottom)) + } else { + None + } + } + + #[cfg(feature = "api-level-28")] + #[doc(alias = "AMediaFormat_setDouble")] + pub fn set_f64(&mut self, key: &str, value: f64) { + let name = CString::new(key).unwrap(); + unsafe { ffi::AMediaFormat_setDouble(self.as_ptr(), name.as_ptr(), value) } + } + + #[cfg(feature = "api-level-28")] + #[doc(alias = "AMediaFormat_setRect")] + pub fn set_rect(&mut self, key: &str, left: i32, top: i32, right: i32, bottom: i32) { + let name = CString::new(key).unwrap(); + unsafe { ffi::AMediaFormat_setRect(self.as_ptr(), name.as_ptr(), left, top, right, bottom) } + } + + #[cfg(feature = "api-level-28")] + #[doc(alias = "AMediaFormat_setSize")] + pub fn set_usize(&mut self, key: &str, value: usize) { + let name = CString::new(key).unwrap(); + unsafe { ffi::AMediaFormat_setSize(self.as_ptr(), name.as_ptr(), value) } + } + + /// Copy one [`MediaFormat`] to another. + #[cfg(feature = "api-level-29")] + #[doc(alias = "AMediaFormat_copy")] + pub fn copy(&self, to: &mut Self) -> Result<()> { + let status = unsafe { ffi::AMediaFormat_copy(to.as_ptr(), self.as_ptr()) }; + MediaError::from_status(status) + } + + /// Clones this [`MediaFormat`] into a [`MediaFormat::new()`] object using + /// [`MediaFormat::copy()`]. + #[cfg(feature = "api-level-29")] + #[doc(alias = "AMediaFormat_new")] + #[doc(alias = "AMediaFormat_copy")] + pub fn try_clone(&self) -> Result { + let mut copy = Self::new(); + self.copy(&mut copy)?; + Ok(copy) + } + + /// Remove all key/value pairs from this [`MediaFormat`]. + #[cfg(feature = "api-level-29")] + #[doc(alias = "AMediaFormat_clear")] + pub fn clear(&mut self) { + unsafe { ffi::AMediaFormat_clear(self.as_ptr()) } + } +} + +impl Drop for MediaFormat { + #[doc(alias = "AMediaFormat_delete")] + fn drop(&mut self) { + let status = unsafe { ffi::AMediaFormat_delete(self.as_ptr()) }; + MediaError::from_status(status).unwrap() + } +} diff --git a/clients/android/native/vendor/ndk/src/media/mod.rs b/clients/android/native/vendor/ndk/src/media/mod.rs new file mode 100644 index 00000000..1689a6e3 --- /dev/null +++ b/clients/android/native/vendor/ndk/src/media/mod.rs @@ -0,0 +1,8 @@ +//! Bindings for the NDK media classes. +//! +//! See also [the NDK docs](https://developer.android.com/ndk/reference/group/media) +#![cfg(feature = "media")] + +pub mod image_reader; +pub mod media_codec; +pub mod media_format; diff --git a/clients/android/native/vendor/ndk/src/media_error.rs b/clients/android/native/vendor/ndk/src/media_error.rs new file mode 100644 index 00000000..99fb0d5f --- /dev/null +++ b/clients/android/native/vendor/ndk/src/media_error.rs @@ -0,0 +1,140 @@ +//! Bindings for NDK media status codes. +//! +//! Also used outside of `libmediandk.so` in `libamidi.so` for example. +#![cfg(feature = "media")] +// The cfg(feature) bounds for some pub(crate) fn uses are non-trivial and will become even more +// complex going forward. Allow them to be unused when compiling with certain feature combinations. +#![allow(dead_code)] + +use std::{fmt, mem::MaybeUninit, ptr::NonNull}; + +use num_enum::{FromPrimitive, IntoPrimitive}; + +pub type Result = std::result::Result; + +/// Media Status codes for [`media_status_t`](https://developer.android.com/ndk/reference/group/media#group___media_1ga009a49041fe39f7bdc6d8b5cddbe760c) +#[repr(i32)] +#[derive(Copy, Clone, Debug, PartialEq, Eq, FromPrimitive, IntoPrimitive)] +#[doc(alias = "media_status_t")] +#[non_exhaustive] +pub enum MediaError { + #[doc(alias = "AMEDIACODEC_ERROR_INSUFFICIENT_RESOURCE")] + CodecErrorInsufficientResource = ffi::media_status_t::AMEDIACODEC_ERROR_INSUFFICIENT_RESOURCE.0, + #[doc(alias = "AMEDIACODEC_ERROR_RECLAIMED")] + CodecErrorReclaimed = ffi::media_status_t::AMEDIACODEC_ERROR_RECLAIMED.0, + #[doc(alias = "AMEDIA_ERROR_UNKNOWN")] + ErrorUnknown = ffi::media_status_t::AMEDIA_ERROR_UNKNOWN.0, + #[doc(alias = "AMEDIA_ERROR_MALFORMED")] + ErrorMalformed = ffi::media_status_t::AMEDIA_ERROR_MALFORMED.0, + #[doc(alias = "AMEDIA_ERROR_UNSUPPORTED")] + ErrorUnsupported = ffi::media_status_t::AMEDIA_ERROR_UNSUPPORTED.0, + #[doc(alias = "AMEDIA_ERROR_INVALID_OBJECT")] + ErrorInvalidObject = ffi::media_status_t::AMEDIA_ERROR_INVALID_OBJECT.0, + #[doc(alias = "AMEDIA_ERROR_INVALID_PARAMETER")] + ErrorInvalidParameter = ffi::media_status_t::AMEDIA_ERROR_INVALID_PARAMETER.0, + #[doc(alias = "AMEDIA_ERROR_INVALID_OPERATION")] + ErrorInvalidOperation = ffi::media_status_t::AMEDIA_ERROR_INVALID_OPERATION.0, + #[doc(alias = "AMEDIA_ERROR_END_OF_STREAM")] + ErrorEndOfStream = ffi::media_status_t::AMEDIA_ERROR_END_OF_STREAM.0, + #[doc(alias = "AMEDIA_ERROR_IO")] + ErrorIo = ffi::media_status_t::AMEDIA_ERROR_IO.0, + #[doc(alias = "AMEDIA_ERROR_WOULD_BLOCK")] + ErrorWouldBlock = ffi::media_status_t::AMEDIA_ERROR_WOULD_BLOCK.0, + #[doc(alias = "AMEDIA_DRM_ERROR_BASE")] + DrmErrorBase = ffi::media_status_t::AMEDIA_DRM_ERROR_BASE.0, + #[doc(alias = "AMEDIA_DRM_NOT_PROVISIONED")] + DrmNotProvisioned = ffi::media_status_t::AMEDIA_DRM_NOT_PROVISIONED.0, + #[doc(alias = "AMEDIA_DRM_RESOURCE_BUSY")] + DrmResourceBusy = ffi::media_status_t::AMEDIA_DRM_RESOURCE_BUSY.0, + #[doc(alias = "AMEDIA_DRM_DEVICE_REVOKED")] + DrmDeviceRevoked = ffi::media_status_t::AMEDIA_DRM_DEVICE_REVOKED.0, + #[doc(alias = "AMEDIA_DRM_SHORT_BUFFER")] + DrmShortBuffer = ffi::media_status_t::AMEDIA_DRM_SHORT_BUFFER.0, + #[doc(alias = "AMEDIA_DRM_SESSION_NOT_OPENED")] + DrmSessionNotOpened = ffi::media_status_t::AMEDIA_DRM_SESSION_NOT_OPENED.0, + #[doc(alias = "AMEDIA_DRM_TAMPER_DETECTED")] + DrmTamperDetected = ffi::media_status_t::AMEDIA_DRM_TAMPER_DETECTED.0, + #[doc(alias = "AMEDIA_DRM_VERIFY_FAILED")] + DrmVerifyFailed = ffi::media_status_t::AMEDIA_DRM_VERIFY_FAILED.0, + #[doc(alias = "AMEDIA_DRM_NEED_KEY")] + DrmNeedKey = ffi::media_status_t::AMEDIA_DRM_NEED_KEY.0, + #[doc(alias = "AMEDIA_DRM_LICENSE_EXPIRED")] + DrmLicenseExpired = ffi::media_status_t::AMEDIA_DRM_LICENSE_EXPIRED.0, + #[doc(alias = "AMEDIA_IMGREADER_ERROR_BASE")] + ImgreaderErrorBase = ffi::media_status_t::AMEDIA_IMGREADER_ERROR_BASE.0, + #[doc(alias = "AMEDIA_IMGREADER_CANNOT_LOCK_IMAGE")] + ImgreaderCannotLockImage = ffi::media_status_t::AMEDIA_IMGREADER_CANNOT_LOCK_IMAGE.0, + #[doc(alias = "AMEDIA_IMGREADER_CANNOT_UNLOCK_IMAGE")] + ImgreaderCannotUnlockImage = ffi::media_status_t::AMEDIA_IMGREADER_CANNOT_UNLOCK_IMAGE.0, + #[doc(alias = "AMEDIA_IMGREADER_IMAGE_NOT_LOCKED")] + ImgreaderImageNotLocked = ffi::media_status_t::AMEDIA_IMGREADER_IMAGE_NOT_LOCKED.0, + + /// This error code is unknown to the [`ndk`][crate] crate. Please report an issue if you + /// believe this code needs to be added to our mapping. + // Use the OK discriminant, as no-one will be able to call `as i32` and only has access to the + // constants via `From` provided by `IntoPrimitive` which reads the contained value. + // An autogenerated ` + 1` discriminant is normally fine, except that the + // previous variant is negative and `+1` would match the variant before that. + #[doc(hidden)] + #[num_enum(catch_all)] + __Unknown(i32) = ffi::media_status_t::AMEDIA_OK.0, +} + +impl fmt::Display for MediaError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{:?}", self) + } +} + +impl std::error::Error for MediaError {} + +impl MediaError { + /// Returns [`Ok`] on [`ffi::media_status_t::AMEDIA_OK`], [`Err`] otherwise (including positive + /// values). + /// + /// Note that some known error codes (currently only for `AMediaCodec`) are positive. + pub(crate) fn from_status(status: ffi::media_status_t) -> Result<()> { + match status { + ffi::media_status_t::AMEDIA_OK => Ok(()), + x => Err(Self::from(x.0)), + } + } + + /// Returns the original value in [`Ok`] if it is not negative, [`Err`] otherwise. + /// + /// Note that some [`ffi::media_status_t`] codes are positive but will never be returned as + /// [`Err`] from this function. As of writing these codes are specific to the `AMediaCodec` API + /// and should not be handled generically. + pub(crate) fn from_status_if_negative + Copy>(value: T) -> Result { + let v = value.into(); + if v >= 0 { + Ok(value) + } else { + Err(Self::from( + i32::try_from(v).expect("Error code out of bounds"), + )) + } + } +} + +/// Calls the `with_ptr` construction function with a pointer to uninitialized stack memory, +/// expecting `with_ptr` to initialize it or otherwise return an error code. +pub(crate) fn construct(with_ptr: impl FnOnce(*mut T) -> ffi::media_status_t) -> Result { + let mut result = MaybeUninit::uninit(); + let status = with_ptr(result.as_mut_ptr()); + MediaError::from_status(status).map(|()| unsafe { result.assume_init() }) +} + +/// Calls the `with_ptr` construction function with a pointer to a pointer, and expects `with_ptr` +/// to initialize the second pointer to a valid address. That address is returned in the form of a +/// [`NonNull`] object. +pub(crate) fn construct_never_null( + with_ptr: impl FnOnce(*mut *mut T) -> ffi::media_status_t, +) -> Result> { + let result = construct(with_ptr)?; + Ok(if cfg!(debug_assertions) { + NonNull::new(result).expect("result should never be null") + } else { + unsafe { NonNull::new_unchecked(result) } + }) +} diff --git a/clients/android/native/vendor/ndk/src/native_activity.rs b/clients/android/native/vendor/ndk/src/native_activity.rs new file mode 100644 index 00000000..19c8965e --- /dev/null +++ b/clients/android/native/vendor/ndk/src/native_activity.rs @@ -0,0 +1,241 @@ +//! Bindings for [`ANativeActivity`] +//! +//! [`ANativeActivity`]: https://developer.android.com/ndk/reference/group/native-activity#anativeactivity + +use super::hardware_buffer_format::HardwareBufferFormat; +use std::{ + ffi::{CStr, OsStr}, + os::{raw::c_void, unix::prelude::OsStrExt}, + path::Path, + ptr::NonNull, +}; + +bitflags::bitflags! { + /// Window flags, as per the Java API at [`android.view.WindowManager.LayoutParams`]. + /// + /// + /// + /// [`android.view.WindowManager.LayoutParams`]: https://developer.android.com/reference/android/view/WindowManager.LayoutParams + #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)] + pub struct WindowFlags : u32 { + const ALLOW_LOCK_WHILE_SCREEN_ON = ffi::AWINDOW_FLAG_ALLOW_LOCK_WHILE_SCREEN_ON; + const DIM_BEHIND = ffi::AWINDOW_FLAG_DIM_BEHIND; + #[deprecated = "Deprecated. Blurring is no longer supported."] + const BLUR_BEHIND = ffi::AWINDOW_FLAG_BLUR_BEHIND; + const NOT_FOCUSABLE = ffi::AWINDOW_FLAG_NOT_FOCUSABLE; + const NOT_TOUCHABLE = ffi::AWINDOW_FLAG_NOT_TOUCHABLE; + const NOT_TOUCH_MODAL = ffi::AWINDOW_FLAG_NOT_TOUCH_MODAL; + #[deprecated = "This constant was deprecated in API level 20. This flag has no effect."] + const TOUCHABLE_WHEN_WAKING = ffi::AWINDOW_FLAG_TOUCHABLE_WHEN_WAKING; + const KEEP_SCREEN_ON = ffi::AWINDOW_FLAG_KEEP_SCREEN_ON; + const LAYOUT_IN_SCREEN = ffi::AWINDOW_FLAG_LAYOUT_IN_SCREEN; + const LAYOUT_NO_LIMITS = ffi::AWINDOW_FLAG_LAYOUT_NO_LIMITS; + const FULLSCREEN = ffi::AWINDOW_FLAG_FULLSCREEN; + #[cfg_attr(feature = "api-level-30", deprecated = "This constant was deprecated in API level 30. This value became API \"by accident\", and shouldn't be used by 3rd party applications.")] + const FORCE_NOT_FULLSCREEN = ffi::AWINDOW_FLAG_FORCE_NOT_FULLSCREEN; + #[deprecated = "This constant was deprecated in API level 17. This flag is no longer used."] + const DITHER = ffi::AWINDOW_FLAG_DITHER; + const SECURE = ffi::AWINDOW_FLAG_SECURE; + const SCALED = ffi::AWINDOW_FLAG_SCALED; + const IGNORE_CHEEK_PRESSES = ffi::AWINDOW_FLAG_IGNORE_CHEEK_PRESSES; + const LAYOUT_INSET_DECOR = ffi::AWINDOW_FLAG_LAYOUT_INSET_DECOR; + const ALT_FOCUSABLE_IM = ffi::AWINDOW_FLAG_ALT_FOCUSABLE_IM; + const WATCH_OUTSIDE_TOUCH = ffi::AWINDOW_FLAG_WATCH_OUTSIDE_TOUCH; + const SHOW_WHEN_LOCKED = ffi::AWINDOW_FLAG_SHOW_WHEN_LOCKED; + const SHOW_WALLPAPER = ffi::AWINDOW_FLAG_SHOW_WALLPAPER; + const TURN_SCREEN_ON = ffi::AWINDOW_FLAG_TURN_SCREEN_ON; + #[cfg_attr(feature = "api-level-26", deprecated = "This constant was deprecated in API level 26. Use `SHOW_WHEN_LOCKED` instead.")] + const DISMISS_KEYGUARD = ffi::AWINDOW_FLAG_DISMISS_KEYGUARD; + const ATTACHED_IN_DECOR = 0x40000000; + + // https://docs.rs/bitflags/latest/bitflags/#externally-defined-flags + const _ = !0; + } +} + +/// A native [`ANativeActivity *`] +/// +/// This is either provided in [`ffi::ANativeActivity_onCreate()`], or accessible through +/// `ndk_glue::native_activity()`. +/// +/// [`ANativeActivity *`]: https://developer.android.com/ndk/reference/struct/a-native-activity +#[derive(Debug)] +pub struct NativeActivity { + ptr: NonNull, +} + +// It gets shared between threads in `ndk-glue` +unsafe impl Send for NativeActivity {} +unsafe impl Sync for NativeActivity {} + +impl NativeActivity { + /// Create a [`NativeActivity`] from a pointer + /// + /// # Safety + /// By calling this function, you assert that it is a valid pointer to a native + /// [`ffi::ANativeActivity`]. + pub unsafe fn from_ptr(ptr: NonNull) -> Self { + Self { ptr } + } + + /// The pointer to the native `ANativeActivity` + pub fn ptr(&self) -> NonNull { + self.ptr + } +} + +/// Methods that relate to fields of the struct itself +/// +/// The relevant NDK docs can be found +/// [here](https://developer.android.com/ndk/reference/struct/a-native-activity). +impl NativeActivity { + /// The platform's SDK version code + pub fn sdk_version(&self) -> i32 { + unsafe { self.ptr.as_ref().sdkVersion } + } + + /// Path to this application's internal data directory + pub fn internal_data_path(&self) -> &Path { + OsStr::from_bytes(unsafe { CStr::from_ptr(self.ptr.as_ref().internalDataPath) }.to_bytes()) + .as_ref() + } + + /// Path to this application's external (removable, mountable) data directory + pub fn external_data_path(&self) -> &Path { + OsStr::from_bytes(unsafe { CStr::from_ptr(self.ptr.as_ref().externalDataPath) }.to_bytes()) + .as_ref() + } + + /// This app's asset manager, which can be used to access assets from the `.apk` file. + pub fn asset_manager(&self) -> crate::asset::AssetManager { + unsafe { + crate::asset::AssetManager::from_ptr( + NonNull::new(self.ptr.as_ref().assetManager).unwrap(), + ) + } + } + + /// Instance data associated with the activity + pub fn instance(&self) -> *mut c_void { + unsafe { self.ptr.as_ref().instance } + } + + /// Set the instance data associated with the activity + /// + /// # Safety + /// This can invalidate assumptions held by `ndk-glue`, as well as cause data + /// races with concurrent access to the instance data. + pub unsafe fn set_instance(&mut self, data: *mut c_void) { + // FIXME Does this create undefined behavior by creating a mutable reference to what could + // also be accessed immutably at the same time? + // + // I think that as long as we warn the users to avoid concurrent access, and we pass along + // the `unsafe` burden, it's OK. + self.ptr.as_mut().instance = data; + } + + /// This process's `JavaVM` object. + /// + /// Usage with [__jni__](https://crates.io/crates/jni) crate: + /// ```no_run + /// # use ndk::native_activity::NativeActivity; + /// # let native_activity: NativeActivity = unimplemented!(); + /// let vm_ptr = native_activity.vm(); + /// let vm = unsafe { jni::JavaVM::from_raw(vm_ptr) }.unwrap(); + /// let env = vm.attach_current_thread(); + /// // Do JNI with env ... + /// ``` + pub fn vm(&self) -> *mut jni_sys::JavaVM { + unsafe { self.ptr.as_ref() }.vm + } + + /// The [`android.app.NativeActivity`] instance + /// + /// In the JNI, this is named `clazz`; however, as the docs say, "it should really be named + /// 'activity' instead of 'clazz', since it's a reference to the NativeActivity instance". + /// + /// [`android.app.NativeActivity`]: https://developer.android.com/reference/android/app/NativeActivity + pub fn activity(&self) -> jni_sys::jobject { + unsafe { self.ptr.as_ref() }.clazz + } + + /// Path to the directory with the application's OBB files. + /// + /// # Safety + /// Only available as of Honeycomb (Android 3.0+, API level 11+) + pub unsafe fn obb_path(&self) -> &Path { + OsStr::from_bytes(CStr::from_ptr(self.ptr.as_ref().obbPath).to_bytes()).as_ref() + } +} + +/// Methods that relate to `ANativeActivity_*` functions. +/// +/// The relevant NDK docs can be found +/// [here](https://developer.android.com/ndk/reference/group/native-activity). +impl NativeActivity { + /// Sends a destroy event to the activity and stops it. + pub fn finish(&self) { + unsafe { ffi::ANativeActivity_finish(self.ptr.as_ptr()) } + } + + /// Shows the IME (the on-screen keyboard). + /// + /// If `force` is true, the `SHOW_FORCED` flag is used; otherwise, the `SHOW_IMPLICIT` flag is + /// used. Depending on the value of this flag, the `hide_soft_input` method with behave + /// differently. See [the relevant + /// javadoc](https://developer.android.com/reference/android/view/inputmethod/InputMethodManager#constants_2) + /// for more information. + pub fn show_soft_input(&self, force: bool) { + let flag = if force { + ffi::ANATIVEACTIVITY_SHOW_SOFT_INPUT_FORCED + } else { + ffi::ANATIVEACTIVITY_SHOW_SOFT_INPUT_IMPLICIT + }; + unsafe { ffi::ANativeActivity_showSoftInput(self.ptr.as_ptr(), flag) } + } + + /// Hides the IME (the on-screen keyboard). + /// + /// If `not_always` is true, the `HIDE_NOT_ALWAYS` flag is used; otherwise, the + /// `HIDE_IMPLICIT_ONLY` flag is used. Depending on the value of this flag and the way the IME + /// was shown, it may or may not be hidden. See [the relevant + /// javadoc](https://developer.android.com/reference/android/view/inputmethod/InputMethodManager#constants_2) + /// for more information. + pub fn hide_soft_input(&self, not_always: bool) { + let flag = if not_always { + ffi::ANATIVEACTIVITY_HIDE_SOFT_INPUT_NOT_ALWAYS + } else { + ffi::ANATIVEACTIVITY_HIDE_SOFT_INPUT_IMPLICIT_ONLY + }; + unsafe { ffi::ANativeActivity_hideSoftInput(self.ptr.as_ptr(), flag) } + } + + /// Change the window format of the given activity. + /// + /// Calls [`getWindow().setFormat()`] of the given activity. 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. + /// + /// [`getWindow().setFormat()`]: https://developer.android.com/reference/android/view/Window#setFormat(int) + pub fn set_window_format(&self, format: HardwareBufferFormat) { + unsafe { ffi::ANativeActivity_setWindowFormat(self.ptr.as_ptr(), format.into()) } + } + + /// Change the window flags of the given activity. + /// + /// Calls [`getWindow().setFlags()`] of the given activity. + /// + /// 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. + /// + /// [`getWindow().setFlags()`]: https://developer.android.com/reference/android/view/Window#setFlags(int,%20int) + pub fn set_window_flags(&self, add_flags: WindowFlags, remove_flags: WindowFlags) { + unsafe { + ffi::ANativeActivity_setWindowFlags( + self.ptr.as_ptr(), + add_flags.bits(), + remove_flags.bits(), + ) + } + } +} diff --git a/clients/android/native/vendor/ndk/src/native_window.rs b/clients/android/native/vendor/ndk/src/native_window.rs new file mode 100644 index 00000000..1a339032 --- /dev/null +++ b/clients/android/native/vendor/ndk/src/native_window.rs @@ -0,0 +1,459 @@ +//! Bindings for [`ANativeWindow`] +//! +//! [`ANativeWindow`]: https://developer.android.com/ndk/reference/group/a-native-window#anativewindow + +use std::{ffi::c_void, io, mem::MaybeUninit, ptr::NonNull}; + +use jni_sys::{jobject, JNIEnv}; + +use super::{hardware_buffer_format::HardwareBufferFormat, utils::status_to_io_result}; +#[cfg(all(feature = "nativewindow", feature = "api-level-28"))] +use crate::data_space::DataSpace; + +pub type Rect = ffi::ARect; + +// [`NativeWindow`] represents the producer end of an image queue +/// +/// It is the C counterpart of the [`android.view.Surface`] object in Java, and can be converted +/// both ways. Depending on the consumer, images submitted to [`NativeWindow`] can be shown on the +/// display or sent to other consumers, such as video encoders. +/// +/// [`android.view.Surface`]: https://developer.android.com/reference/android/view/Surface +#[derive(Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub struct NativeWindow { + ptr: NonNull, +} + +unsafe impl Send for NativeWindow {} +unsafe impl Sync for NativeWindow {} + +impl Drop for NativeWindow { + fn drop(&mut self) { + unsafe { ffi::ANativeWindow_release(self.ptr.as_ptr()) } + } +} + +impl Clone for NativeWindow { + fn clone(&self) -> Self { + unsafe { ffi::ANativeWindow_acquire(self.ptr.as_ptr()) } + Self { ptr: self.ptr } + } +} + +#[cfg(feature = "rwh_04")] +unsafe impl rwh_04::HasRawWindowHandle for NativeWindow { + fn raw_window_handle(&self) -> rwh_04::RawWindowHandle { + let mut handle = rwh_04::AndroidNdkHandle::empty(); + handle.a_native_window = self.ptr.as_ptr().cast(); + rwh_04::RawWindowHandle::AndroidNdk(handle) + } +} + +#[cfg(feature = "rwh_05")] +unsafe impl rwh_05::HasRawWindowHandle for NativeWindow { + fn raw_window_handle(&self) -> rwh_05::RawWindowHandle { + let mut handle = rwh_05::AndroidNdkWindowHandle::empty(); + handle.a_native_window = self.ptr.as_ptr().cast(); + rwh_05::RawWindowHandle::AndroidNdk(handle) + } +} + +#[cfg(feature = "rwh_06")] +impl rwh_06::HasWindowHandle for NativeWindow { + fn window_handle(&self) -> Result, rwh_06::HandleError> { + let handle = rwh_06::AndroidNdkWindowHandle::new(self.ptr.cast()); + let handle = rwh_06::RawWindowHandle::AndroidNdk(handle); + // SAFETY: All fields of the "raw" `AndroidNdkWindowHandle` struct are filled out. The + // returned pointer is also kept valid by `NativeWindow` (until `Drop`), which is lifetime- + // borrowed in the returned `WindowHandle<'_>` and cannot be outlived. Its value won't + // change throughout the lifetime of this `NativeWindow`. + Ok(unsafe { rwh_06::WindowHandle::borrow_raw(handle) }) + } +} + +impl NativeWindow { + /// Assumes ownership of `ptr` + /// + /// # Safety + /// `ptr` must be a valid pointer to an Android [`ffi::ANativeWindow`]. + pub unsafe fn from_ptr(ptr: NonNull) -> Self { + Self { ptr } + } + + /// Acquires ownership of `ptr` + /// + /// # Safety + /// `ptr` must be a valid pointer to an Android [`ffi::ANativeWindow`]. + pub unsafe fn clone_from_ptr(ptr: NonNull) -> Self { + ffi::ANativeWindow_acquire(ptr.as_ptr()); + Self::from_ptr(ptr) + } + + pub fn ptr(&self) -> NonNull { + self.ptr + } + + pub fn height(&self) -> i32 { + unsafe { ffi::ANativeWindow_getHeight(self.ptr.as_ptr()) } + } + + pub fn width(&self) -> i32 { + unsafe { ffi::ANativeWindow_getWidth(self.ptr.as_ptr()) } + } + + /// Return the current pixel format ([`HardwareBufferFormat`]) of the window surface. + pub fn format(&self) -> HardwareBufferFormat { + let value = unsafe { ffi::ANativeWindow_getFormat(self.ptr.as_ptr()) }; + value.into() + } + + /// Change the format and size of the window buffers. + /// + /// The width and height control the number of pixels in the buffers, not the dimensions of the + /// window on screen. If these are different than the window's physical size, then its buffer + /// will be scaled to match that size when compositing it to the screen. The width and height + /// must be either both zero or both non-zero. + /// + /// For all of these parameters, if `0` or [`None`] is supplied then the window's base value + /// will come back in force. + pub fn set_buffers_geometry( + &self, + width: i32, + height: i32, + format: Option, + ) -> io::Result<()> { + let format = format.map_or(0i32, |f| f.into()); + let status = unsafe { + ffi::ANativeWindow_setBuffersGeometry(self.ptr.as_ptr(), width, height, format) + }; + status_to_io_result(status) + } + + /// Set a transform that will be applied to future buffers posted to the window. + #[cfg(all(feature = "nativewindow", feature = "api-level-26"))] + #[doc(alias = "ANativeWindow_setBuffersTransform")] + pub fn set_buffers_transform(&self, transform: NativeWindowTransform) -> io::Result<()> { + let status = + unsafe { ffi::ANativeWindow_setBuffersTransform(self.ptr.as_ptr(), transform.bits()) }; + status_to_io_result(status) + } + + /// All buffers queued after this call will be associated with the dataSpace parameter + /// specified. + /// + /// `data_space` specifies additional information about the buffer. For example, it can be used + /// to convey the color space of the image data in the buffer, or it can be used to indicate + /// that the buffers contain depth measurement data instead of color images. The default + /// dataSpace is `0`, [`DataSpace::Unknown`], unless it has been overridden by the producer. + #[cfg(all(feature = "nativewindow", feature = "api-level-28"))] + #[doc(alias = "ANativeWindow_setBuffersDataSpace")] + pub fn set_buffers_data_space(&self, data_space: DataSpace) -> io::Result<()> { + let status = + unsafe { ffi::ANativeWindow_setBuffersDataSpace(self.ptr.as_ptr(), data_space.into()) }; + status_to_io_result(status) + } + + /// Get the dataspace of the buffers in this [`NativeWindow`]. + #[cfg(all(feature = "nativewindow", feature = "api-level-28"))] + #[doc(alias = "ANativeWindow_getBuffersDataSpace")] + pub fn buffers_data_space(&self) -> io::Result { + let status = unsafe { ffi::ANativeWindow_getBuffersDataSpace(self.ptr.as_ptr()) }; + if status >= 0 { + Ok(status.into()) + } else { + Err(status_to_io_result(status).unwrap_err()) + } + } + + /// Sets the intended frame rate for this window. + /// + /// Same as [`set_frame_rate_with_change_strategy(window, frame_rate, compatibility, ChangeFrameRateStrategy::OnlyIfSeamless)`][`NativeWindow::set_frame_rate_with_change_strategy()`]. + /// + #[cfg_attr( + not(feature = "api-level-31"), + doc = "[`NativeWindow::set_frame_rate_with_change_strategy()`]: https://developer.android.com/ndk/reference/group/a-native-window#anativewindow_setframeratewithchangestrategy" + )] + #[cfg(all(feature = "nativewindow", feature = "api-level-30"))] + #[doc(alias = "ANativeWindow_setFrameRate")] + pub fn set_frame_rate( + &self, + frame_rate: f32, + compatibility: FrameRateCompatibility, + ) -> io::Result<()> { + let status = unsafe { + ffi::ANativeWindow_setFrameRate(self.ptr.as_ptr(), frame_rate, compatibility as i8) + }; + status_to_io_result(status) + } + + /// Sets the intended frame rate for this window. + /// + /// On devices that are capable of running the display at different refresh rates, the system + /// may choose a display refresh rate to better match this window's frame rate. Usage of this + /// API won't introduce frame rate throttling, or affect other aspects of the application's + /// frame production pipeline. However, because the system may change the display refresh rate, + /// calls to this function may result in changes to Choreographer callback timings, and changes + /// to the time interval at which the system releases buffers back to the application. + /// + /// Note that this only has an effect for windows presented on the display. If this + /// [`NativeWindow`] is consumed by something other than the system compositor, e.g. a media + /// codec, this call has no effect. + /// + /// You can register for changes in the refresh rate using + /// [`ffi::AChoreographer_registerRefreshRateCallback()`]. + /// + /// # Parameters + /// + /// - `frame_rate`: The intended frame rate of this window, in frames per second. `0` is a + /// special value that indicates the app will accept the system's choice for the display + /// frame rate, which is the default behavior if this function isn't called. The `frame_rate` + /// param does not need to be a valid refresh rate for this device's display - e.g., it's + /// fine to pass `30`fps to a device that can only run the display at `60`fps. + /// - `compatibility`: The frame rate compatibility of this window. The compatibility value may + /// influence the system's choice of display refresh rate. See the [`FrameRateCompatibility`] + /// values for more info. This parameter is ignored when `frame_rate` is `0`. + /// - `change_frame_rate_strategy`: Whether display refresh rate transitions caused by this + /// window should be seamless. A seamless transition is one that doesn't have any visual + /// interruptions, such as a black screen for a second or two. See the + /// [`ChangeFrameRateStrategy`] values. This parameter is ignored when `frame_rate` is `0`. + #[cfg(all(feature = "nativewindow", feature = "api-level-31"))] + #[doc(alias = "ANativeWindow_setFrameRateWithChangeStrategy")] + pub fn set_frame_rate_with_change_strategy( + &self, + frame_rate: f32, + compatibility: FrameRateCompatibility, + change_frame_rate_strategy: ChangeFrameRateStrategy, + ) -> io::Result<()> { + let status = unsafe { + ffi::ANativeWindow_setFrameRateWithChangeStrategy( + self.ptr.as_ptr(), + frame_rate, + compatibility as i8, + change_frame_rate_strategy as i8, + ) + }; + status_to_io_result(status) + } + + /// Provides a hint to the window that buffers should be preallocated ahead of time. + /// + /// Note that the window implementation is not guaranteed to preallocate any buffers, for + /// instance if an implementation disallows allocation of new buffers, or if there is + /// insufficient memory in the system to preallocate additional buffers + #[cfg(all(feature = "nativewindow", feature = "api-level-30"))] + pub fn try_allocate_buffers(&self) { + unsafe { ffi::ANativeWindow_tryAllocateBuffers(self.ptr.as_ptr()) } + } + + /// Return the [`NativeWindow`] associated with a JNI [`android.view.Surface`] pointer. + /// + /// # Safety + /// By calling this function, you assert that `env` is a valid pointer to a [`JNIEnv`] and + /// `surface` is a valid pointer to an [`android.view.Surface`]. + /// + /// [`android.view.Surface`]: https://developer.android.com/reference/android/view/Surface + pub unsafe fn from_surface(env: *mut JNIEnv, surface: jobject) -> Option { + let ptr = ffi::ANativeWindow_fromSurface(env, surface); + Some(Self::from_ptr(NonNull::new(ptr)?)) + } + + /// Return a JNI [`android.view.Surface`] pointer derived from this [`NativeWindow`]. + /// + /// # Safety + /// By calling this function, you assert that `env` is a valid pointer to a [`JNIEnv`]. + /// + /// [`android.view.Surface`]: https://developer.android.com/reference/android/view/Surface + #[cfg(feature = "api-level-26")] + pub unsafe fn to_surface(&self, env: *mut JNIEnv) -> jobject { + ffi::ANativeWindow_toSurface(env, self.ptr().as_ptr()) + } + + /// Lock the window's next drawing surface for writing. + /// + /// Optionally pass the region you intend to draw into `dirty_bounds`. When this function + /// returns it is updated (commonly enlarged) with the actual area the caller needs to redraw. + pub fn lock( + &self, + dirty_bounds: Option<&mut Rect>, + ) -> io::Result> { + let dirty_bounds = match dirty_bounds { + Some(dirty_bounds) => dirty_bounds, + None => std::ptr::null_mut(), + }; + let mut buffer = MaybeUninit::uninit(); + let status = unsafe { + ffi::ANativeWindow_lock(self.ptr.as_ptr(), buffer.as_mut_ptr(), dirty_bounds) + }; + status_to_io_result(status)?; + + Ok(NativeWindowBufferLockGuard { + window: self, + buffer: unsafe { buffer.assume_init() }, + }) + } +} + +/// Lock holding the next drawing surface for writing. It is unlocked and posted on [`drop()`]. +#[derive(Debug)] +pub struct NativeWindowBufferLockGuard<'a> { + window: &'a NativeWindow, + buffer: ffi::ANativeWindow_Buffer, +} + +impl<'a> NativeWindowBufferLockGuard<'a> { + /// The number of pixels that are shown horizontally. + pub fn width(&self) -> usize { + usize::try_from(self.buffer.width).unwrap() + } + + // The number of pixels that are shown vertically. + pub fn height(&self) -> usize { + usize::try_from(self.buffer.height).unwrap() + } + + /// The number of _pixels_ that a line in the buffer takes in memory. + /// + /// This may be `>= width`. + pub fn stride(&self) -> usize { + usize::try_from(self.buffer.stride).unwrap() + } + + /// The format of the buffer. One of [`HardwareBufferFormat`]. + pub fn format(&self) -> HardwareBufferFormat { + self.buffer.format.into() + } + + /// The actual bits. + /// + /// This points to a memory segment of [`stride()`][Self::stride()] * + /// [`height()`][Self::height()] * [`HardwareBufferFormat::bytes_per_pixel()`] bytes. + /// + /// Only [`width()`][Self::width()] pixels are visible for each [`stride()`][Self::stride()] + /// line of pixels in the buffer. + /// + /// See [`bytes()`][Self::bytes()] for safe access to these bytes. + pub fn bits(&mut self) -> *mut c_void { + self.buffer.bits + } + + /// Safe write access to likely uninitialized pixel buffer data. + /// + /// Returns [`None`] when there is no [`HardwareBufferFormat::bytes_per_pixel()`] size + /// available for this [`format()`][Self::format()]. + /// + /// The returned slice consists of [`stride()`][Self::stride()] * [`height()`][Self::height()] + /// \* [`HardwareBufferFormat::bytes_per_pixel()`] bytes. + /// + /// Only [`width()`][Self::width()] pixels are visible for each [`stride()`][Self::stride()] + /// line of pixels in the buffer. + pub fn bytes(&mut self) -> Option<&mut [MaybeUninit]> { + let num_pixels = self.stride() * self.height(); + let num_bytes = num_pixels * self.format().bytes_per_pixel()?; + Some(unsafe { std::slice::from_raw_parts_mut(self.bits().cast(), num_bytes) }) + } + + /// Returns a slice of bytes for each line of visible pixels in the buffer, ignoring any + /// padding pixels incurred by the stride. + /// + /// See [`bits()`][Self::bits()] and [`bytes()`][Self::bytes()] for contiguous access to the + /// underlying buffer. + pub fn lines(&mut self) -> Option]>> { + let bpp = self.format().bytes_per_pixel()?; + let scanline_bytes = bpp * self.stride(); + let width_bytes = bpp * self.width(); + let bytes = self.bytes()?; + + Some( + bytes + .chunks_exact_mut(scanline_bytes) + .map(move |scanline| &mut scanline[..width_bytes]), + ) + } +} + +impl<'a> Drop for NativeWindowBufferLockGuard<'a> { + fn drop(&mut self) { + let ret = unsafe { ffi::ANativeWindow_unlockAndPost(self.window.ptr.as_ptr()) }; + assert_eq!(ret, 0); + } +} + +#[cfg(all(feature = "nativewindow", feature = "api-level-26"))] +bitflags::bitflags! { + /// Transforms that can be applied to buffers as they are displayed to a window. + /// + /// Supported transforms are any combination of horizontal mirror, vertical mirror, and + /// clockwise 90 degree rotation, in that order. Rotations of 180 and 270 degrees are made up + /// of those basic transforms. + #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)] + #[doc(alias = "ANativeWindowTransform")] + pub struct NativeWindowTransform : i32 { + #[doc(alias = "ANATIVEWINDOW_TRANSFORM_IDENTITY")] + const IDENTITY = ffi::ANativeWindowTransform::ANATIVEWINDOW_TRANSFORM_IDENTITY.0 as i32; + #[doc(alias = "ANATIVEWINDOW_TRANSFORM_MIRROR_HORIZONTAL")] + const MIRROR_HORIZONTAL = ffi::ANativeWindowTransform::ANATIVEWINDOW_TRANSFORM_MIRROR_HORIZONTAL.0 as i32; + #[doc(alias = "ANATIVEWINDOW_TRANSFORM_MIRROR_VERTICAL")] + const MIRROR_VERTICAL = ffi::ANativeWindowTransform::ANATIVEWINDOW_TRANSFORM_MIRROR_VERTICAL.0 as i32; + #[doc(alias = "ANATIVEWINDOW_TRANSFORM_ROTATE_90")] + const ROTATE_90 = ffi::ANativeWindowTransform::ANATIVEWINDOW_TRANSFORM_ROTATE_90.0 as i32; + /// Defined as [`Self::MIRROR_HORIZONTAL`] `|` [`Self::MIRROR_VERTICAL`]. + #[doc(alias = "ANATIVEWINDOW_TRANSFORM_ROTATE_180")] + const ROTATE_180 = ffi::ANativeWindowTransform::ANATIVEWINDOW_TRANSFORM_ROTATE_180.0 as i32; + /// Defined as [`Self::ROTATE_180`] `|` [`Self::ROTATE_90`]. + #[doc(alias = "ANATIVEWINDOW_TRANSFORM_ROTATE_270")] + const ROTATE_270 = ffi::ANativeWindowTransform::ANATIVEWINDOW_TRANSFORM_ROTATE_270.0 as i32; + + // https://docs.rs/bitflags/latest/bitflags/#externally-defined-flags + const _ = !0; + } +} + +/// Compatibility value for [`NativeWindow::set_frame_rate()`] +#[cfg_attr( + feature = "api-level-31", + doc = " and [`NativeWindow::set_frame_rate_with_change_strategy()`]" +)] +/// . +#[cfg(all(feature = "nativewindow", feature = "api-level-30"))] +#[repr(i8)] +#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)] +#[doc(alias = "ANativeWindow_FrameRateCompatibility")] +#[non_exhaustive] +pub enum FrameRateCompatibility { + /// There are no inherent restrictions on the frame rate of this window. + /// + /// When the system selects a frame rate other than what the app requested, the app will be + /// able to run at the system frame rate without requiring pull down. This value should be used + /// when displaying game content, UIs, and anything that isn't video. + #[doc(alias = "ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT")] + Default = + ffi::ANativeWindow_FrameRateCompatibility::ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT.0 as i8, + /// This window is being used to display content with an inherently fixed frame rate, e.g. a + /// video that has a specific frame rate. + /// + /// When the system selects a frame rate other than what the app requested, the app will need + /// to do pull down or use some other technique to adapt to the system's frame rate. The user + /// experience is likely to be worse (e.g. more frame stuttering) than it would be if the + /// system had chosen the app's requested frame rate. This value should be used for video + /// content. + #[doc(alias = "ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_FIXED_SOURCE")] + FixedSource = ffi::ANativeWindow_FrameRateCompatibility::ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_FIXED_SOURCE.0 as i8, +} + +/// Change frame rate strategy value for [`NativeWindow::set_frame_rate_with_change_strategy()`]. +#[cfg(all(feature = "nativewindow", feature = "api-level-31"))] +#[repr(i8)] +#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)] +#[doc(alias = "ANativeWindow_ChangeFrameRateStrategy")] +#[non_exhaustive] +pub enum ChangeFrameRateStrategy { + /// Change the frame rate only if the transition is going to be seamless. + #[doc(alias = "ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS")] + OnlyIfSeamless = + ffi::ANativeWindow_ChangeFrameRateStrategy::ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS + .0 as i8, + /// Change the frame rate even if the transition is going to be non-seamless, i.e. with visual interruptions for the user. + #[doc(alias = "ANATIVEWINDOW_CHANGE_FRAME_RATE_ALWAYS")] + Always = + ffi::ANativeWindow_ChangeFrameRateStrategy::ANATIVEWINDOW_CHANGE_FRAME_RATE_ALWAYS.0 as i8, +} diff --git a/clients/android/native/vendor/ndk/src/shared_memory.rs b/clients/android/native/vendor/ndk/src/shared_memory.rs new file mode 100644 index 00000000..20182f9c --- /dev/null +++ b/clients/android/native/vendor/ndk/src/shared_memory.rs @@ -0,0 +1,153 @@ +//! Bindings for [`ASharedMemory`] +//! +//! [`ASharedMemory`]: https://developer.android.com/ndk/reference/group/memory +#![cfg(feature = "api-level-26")] + +use std::{ + ffi::CStr, + io::{Error, Result}, + // TODO: Import from std::os::fd::{} since Rust 1.66 + os::unix::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, OwnedFd, RawFd}, + ptr, +}; + +#[cfg(feature = "api-level-27")] +use jni_sys::{jobject, JNIEnv}; + +/// Enables the creation, mapping, and protection control over anonymous shared memory. +#[derive(Debug)] +#[doc(alias = "ASharedMemory")] +pub struct SharedMemory(OwnedFd); + +impl SharedMemory { + /// Create a shared memory region. + /// + /// Creates shared memory region and returns a file descriptor. The resulting file descriptor + /// can be `mmap`'ed to process memory space with `PROT_READ | PROT_WRITE | PROT_EXEC`. Access + /// to this shared memory region can be restricted with [`set_prot()`][Self::set_prot()]. + /// + /// Use [`android.os.ParcelFileDescriptor`] to pass the file descriptor to another process. + /// File descriptors may also be sent to other processes over a Unix domain socket with + /// `sendmsg` and `SCM_RIGHTS`. See `sendmsg(3)` and `cmsg(3)` man pages for more information. + /// + /// If you intend to share this file descriptor with a child process after calling `exec(3)`, + /// note that you will need to use `fcntl(2)` with `F_SETFD` to clear the `FD_CLOEXEC` flag for + /// this to work on all versions of Android. + /// + /// [`android.os.ParcelFileDescriptor`]: https://developer.android.com/reference/android/os/ParcelFileDescriptor + #[doc(alias = "ASharedMemory_create")] + pub fn create(name: Option<&CStr>, size: usize) -> Result { + let fd = + unsafe { ffi::ASharedMemory_create(name.map_or(ptr::null(), |p| p.as_ptr()), size) }; + if fd < 0 { + Err(Error::last_os_error()) + } else { + Ok(unsafe { Self::from_raw_fd(fd) }) + } + } + + /// Returns a `dup`'d FD from the given Java [`android.os.SharedMemory`] object. + /// + /// The returned file descriptor has all the same properties & capabilities as the FD returned + /// from [`create()`][Self::create()], however the protection flags will be the same as those + /// of the [`android.os.SharedMemory`] object. + /// + /// [`android.os.SharedMemory`]: https://developer.android.com/reference/android/os/SharedMemory + #[cfg(feature = "api-level-27")] + #[doc(alias = "ASharedMemory_dupFromJava")] + #[allow(clippy::not_unsafe_ptr_arg_deref)] + pub fn dup_from_java(env: *mut JNIEnv, shared_memory: jobject) -> Result { + let fd = unsafe { ffi::ASharedMemory_dupFromJava(env, shared_memory) }; + if fd < 0 { + Err(Error::last_os_error()) + } else { + Ok(unsafe { Self::from_raw_fd(fd) }) + } + } + + /// Get the size of the shared memory region. + #[doc(alias = "ASharedMemory_getSize")] + pub fn size(&self) -> usize { + unsafe { ffi::ASharedMemory_getSize(self.as_raw_fd()) } + } + + /// Restrict access of shared memory region. + /// + /// This function restricts access of a shared memory region. Access can only be removed. The + /// effect applies globally to all file descriptors in all processes across the system that + /// refer to this shared memory region. Existing memory mapped regions are not affected. + /// + /// It is a common use case to create a shared memory region, map it read/write locally to + /// initialize content, and then send the shared memory to another process with read only + /// access. Code example as below: + /// + /// ```no_run + /// # use ndk::shared_memory::SharedMemory; + /// # // TODO: Import from std::os::fd::{} since Rust 1.66 + /// # use std::os::unix::io::AsRawFd; + /// # use std::ffi::CStr; + /// # unsafe { + /// let mem = SharedMemory::create(Some(CStr::from_bytes_with_nul_unchecked(b"memory\0")), 127).unwrap(); + /// // By default it has PROT_READ | PROT_WRITE | PROT_EXEC. + /// let size = mem.size(); + /// let buffer = libc::mmap( + /// std::ptr::null_mut(), + /// size, + /// libc::PROT_READ | libc::PROT_WRITE, + /// libc::MAP_SHARED, + /// mem.as_raw_fd(), + /// 0, + /// ); + /// let buffer_slice = std::slice::from_raw_parts_mut(buffer.cast(), size); + /// + /// // trivially initialize content + /// buffer_slice[..7].copy_from_slice(b"hello!\0"); + /// + /// // Existing mappings will retain their protection flags (PROT_WRITE here) after set_prod() + /// // unless it is unmapped: + /// libc::munmap(buffer, size); + /// + /// // limit access to read only + /// mem.set_prot(libc::PROT_READ); + /// + /// // share fd with another process here and the other process can only map with PROT_READ. + /// # } + /// ``` + #[doc(alias = "ASharedMemory_setProt")] + pub fn set_prot(&self, prot: i32) -> Result<()> { + let status = unsafe { ffi::ASharedMemory_setProt(self.as_raw_fd(), prot) }; + if status < 0 { + Err(Error::last_os_error()) + } else { + Ok(()) + } + } +} + +impl AsFd for SharedMemory { + fn as_fd(&self) -> BorrowedFd<'_> { + self.0.as_fd() + } +} + +impl AsRawFd for SharedMemory { + fn as_raw_fd(&self) -> RawFd { + self.0.as_raw_fd() + } +} + +impl IntoRawFd for SharedMemory { + fn into_raw_fd(self) -> RawFd { + self.0.into_raw_fd() + } +} + +impl FromRawFd for SharedMemory { + /// # Safety + /// + /// The resource pointed to by `fd` must be open and suitable for assuming + /// ownership. The resource must not require any cleanup other than `close`. + unsafe fn from_raw_fd(fd: RawFd) -> Self { + Self(OwnedFd::from_raw_fd(fd)) + } +} diff --git a/clients/android/native/vendor/ndk/src/surface_texture.rs b/clients/android/native/vendor/ndk/src/surface_texture.rs new file mode 100644 index 00000000..d38ffd7a --- /dev/null +++ b/clients/android/native/vendor/ndk/src/surface_texture.rs @@ -0,0 +1,159 @@ +//! Bindings for [`ASurfaceTexture`] +//! +//! See for an architectural overview of +//! [`SurfaceTexture`] internals. +//! +//! [`ASurfaceTexture`]: https://developer.android.com/ndk/reference/group/surface-texture +#![cfg(feature = "api-level-28")] + +use crate::{native_window::NativeWindow, utils::status_to_io_result}; +use jni_sys::{jobject, JNIEnv}; +use std::{io::Result, ptr::NonNull, time::Duration}; + +/// An opaque type to manage [`android.graphics.SurfaceTexture`] from native code +/// +/// [`android.graphics.SurfaceTexture`]: https://developer.android.com/reference/android/graphics/SurfaceTexture +#[derive(Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub struct SurfaceTexture { + ptr: NonNull, +} + +unsafe impl Send for SurfaceTexture {} + +impl Drop for SurfaceTexture { + fn drop(&mut self) { + unsafe { ffi::ASurfaceTexture_release(self.ptr.as_ptr()) } + } +} + +impl SurfaceTexture { + /// Assumes ownership of `ptr` + /// + /// # Safety + /// `ptr` must be a valid pointer to an Android [`ffi::ASurfaceTexture`]. + pub unsafe fn from_ptr(ptr: NonNull) -> Self { + Self { ptr } + } + + /// Get a reference to the native [`SurfaceTexture`] from the corresponding Java object. + /// + /// # Safety + /// + /// This function should be called with a healthy JVM pointer and with a non-null + /// [`android.graphics.SurfaceTexture`], which must be kept alive on the Java/Kotlin side. + /// + /// The caller must keep a reference to the Java [`android.graphics.SurfaceTexture`] during the + /// lifetime of the returned [`SurfaceTexture`]. Failing to do so could result in the + /// [`SurfaceTexture`] to stop functioning properly once the Java object gets finalized. + /// However, this will not result in program termination. + /// + /// [`android.graphics.SurfaceTexture`]: https://developer.android.com/reference/android/graphics/SurfaceTexture + pub unsafe fn from_surface_texture(env: *mut JNIEnv, surface_texture: jobject) -> Option { + let a_surface_texture_ptr = ffi::ASurfaceTexture_fromSurfaceTexture(env, surface_texture); + let s = NonNull::new(a_surface_texture_ptr)?; + Some(SurfaceTexture::from_ptr(s)) + } + + /// Returns a pointer to the native [`ffi::ASurfaceTexture`]. + pub fn ptr(&self) -> NonNull { + self.ptr + } + + /// Returns a reference to a [`NativeWindow`] (i.e. the Producer) for this [`SurfaceTexture`]. + /// + /// This is equivalent to Java's: + /// ```java + /// Surface sur = new Surface(surfaceTexture); + /// ``` + pub fn acquire_native_window(&self) -> Option { + let native_window = unsafe { ffi::ASurfaceTexture_acquireANativeWindow(self.ptr.as_ptr()) }; + let n = NonNull::new(native_window)?; + Some(unsafe { NativeWindow::from_ptr(n) }) + } + + /// Attach the [`SurfaceTexture`] to the OpenGL ES context that is current on the calling + /// thread. + /// + /// A new OpenGL ES texture object is created and populated with the [`SurfaceTexture`] image + /// frame that was current at the time of the last call to + /// [`detach_from_gl_context()`][Self::detach_from_gl_context()]. This new texture is bound to + /// the `GL_TEXTURE_EXTERNAL_OES` texture target. + /// + /// This can be used to access the [`SurfaceTexture`] image contents from multiple OpenGL ES + /// contexts. Note, however, that the image contents are only accessible from one OpenGL ES + /// context at a time. + pub fn attach_to_gl_context(&self, tex_name: u32) -> Result<()> { + let status = unsafe { ffi::ASurfaceTexture_attachToGLContext(self.ptr.as_ptr(), tex_name) }; + status_to_io_result(status) + } + + /// Detach the [`SurfaceTexture`] from the OpenGL ES context that owns the OpenGL ES texture + /// object. + /// + /// This call must be made with the OpenGL ES context current on the calling thread. The OpenGL + /// ES texture object will be deleted as a result of this call. After calling this method all + /// calls to [`update_tex_image()`][Self::update_tex_image()] will fail until a successful call + /// to [`attach_to_gl_context()`][Self::attach_to_gl_context()] is made. + /// + /// This can be used to access the [`SurfaceTexture`] image contents from multiple OpenGL ES + /// contexts. Note, however, that the image contents are only accessible from one OpenGL ES + /// context at a time. + pub fn detach_from_gl_context(&self) -> Result<()> { + let status = unsafe { ffi::ASurfaceTexture_detachFromGLContext(self.ptr.as_ptr()) }; + status_to_io_result(status) + } + + /// Retrieve the 4x4 texture coordinate transform matrix associated with the texture image set + /// by the most recent call to [`update_tex_image()`][Self::update_tex_image()]. + /// + /// This transform matrix maps 2D homogeneous texture coordinates of the form `(s, t, 0, 1)` + /// with `s` and `t` in the inclusive range `[0, 1]` to the texture coordinate that should be + /// used to sample that location from the texture. Sampling the texture outside of the range of + /// this transform is undefined. + /// + /// The matrix is stored in column-major order so that it may be passed directly to OpenGL ES + /// via the [`glLoadMatrixf()`] or [`glUniformMatrix4fv()`] functions. + /// + /// [`glLoadMatrixf()`]: https://www.khronos.org/registry/OpenGL-Refpages/gl2.1/xhtml/glLoadMatrix.xml + /// [`gluniformmatrix4fv()`]: https://www.khronos.org/registry/OpenGL-Refpages/es3.1/html/glUniform.xhtml + pub fn transform_matrix(&self) -> [f32; 16] { + let mut r = [0f32; 16]; + unsafe { ffi::ASurfaceTexture_getTransformMatrix(self.ptr.as_ptr(), r.as_mut_ptr()) }; + r + } + + /// Retrieve the timestamp associated with the texture image set by the most recent call to + /// [`update_tex_image()`][Self::update_tex_image()]. + /// + /// This timestamp is in nanoseconds, and is normally monotonically increasing. The timestamp + /// should be unaffected by time-of-day adjustments, and for a camera should be strictly + /// monotonic but for a [`MediaPlayer`] may be reset when the position is set. The specific + /// meaning and zero point of the timestamp depends on the source providing images to the + /// [`SurfaceTexture`]. Unless otherwise specified by the image source, timestamps cannot + /// generally be compared across [`SurfaceTexture`] instances, or across multiple program + /// invocations. It is mostly useful for determining time offsets between subsequent frames. + /// + /// For EGL/Vulkan producers, this timestamp is the desired present time set with the + /// [`EGL_ANDROID_presentation_time`] or [`VK_GOOGLE_display_timing`] extensions. + /// + /// [`MediaPlayer`]: https://developer.android.com/reference/android/media/MediaPlayer + /// [`EGL_ANDROID_presentation_time`]: https://www.khronos.org/registry/EGL/extensions/ANDROID/EGL_ANDROID_presentation_time.txt + /// [`VK_GOOGLE_display_timing`]: https://www.khronos.org/registry/vulkan/specs/1.3-extensions/man/html/VK_GOOGLE_display_timing.html + pub fn timestamp(&self) -> Duration { + Duration::from_nanos( + unsafe { ffi::ASurfaceTexture_getTimestamp(self.ptr.as_ptr()) } + .try_into() + .unwrap(), + ) + } + + /// Update the texture image to the most recent frame from the image stream. + /// + /// This may only be called while the OpenGL ES context that owns the texture is current on the + /// calling thread. It will implicitly bind its texture to the `GL_TEXTURE_EXTERNAL_OES` + /// texture target. + pub fn update_tex_image(&self) -> Result<()> { + let status = unsafe { ffi::ASurfaceTexture_updateTexImage(self.ptr.as_ptr()) }; + status_to_io_result(status) + } +} diff --git a/clients/android/native/vendor/ndk/src/sync.rs b/clients/android/native/vendor/ndk/src/sync.rs new file mode 100644 index 00000000..b17aff4f --- /dev/null +++ b/clients/android/native/vendor/ndk/src/sync.rs @@ -0,0 +1,143 @@ +//! Bindings for [sync functions] +//! +//! [sync functions]: https://developer.android.com/ndk/reference/group/sync +#![cfg(feature = "sync")] + +use std::{ + ffi::CStr, + fmt::Debug, + // TODO: Import from std::os::fd::{} since Rust 1.66 + os::unix::io::{AsRawFd, BorrowedFd, FromRawFd, OwnedFd}, + ptr::NonNull, +}; + +#[doc(alias = "sync_file_info")] +#[repr(transparent)] +pub struct SyncFileInfo { + inner: NonNull, +} + +impl Debug for SyncFileInfo { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("SyncFileInfo") + .field("name", &self.name()) + .field("status", &self.status()) + .field("flags", &self.flags()) + .field("num_fences", &self.num_fences()) + .field("fence_info", &self.fence_info()) + .finish() + } +} + +impl SyncFileInfo { + /// Retrieve detailed information about a sync file and its fences. + #[doc(alias = "sync_file_info")] + pub fn new(fd: BorrowedFd<'_>) -> Option { + let inner = NonNull::new(unsafe { ffi::sync_file_info(fd.as_raw_fd()) })?; + Some(Self { inner }) + } + + pub fn name(&self) -> &CStr { + let inner = unsafe { self.inner.as_ref() }; + // TODO: Switch to CStr::from_bytes_until_nul (with c_char -> u8 transmute) since MSRV 1.69 + // https://github.com/ash-rs/ash/pull/746 + unsafe { CStr::from_ptr(inner.name.as_ptr()) } + } + + pub fn status(&self) -> i32 { + let inner = unsafe { self.inner.as_ref() }; + inner.status + } + + pub fn flags(&self) -> u32 { + let inner = unsafe { self.inner.as_ref() }; + inner.flags + } + + pub fn num_fences(&self) -> usize { + let inner = unsafe { self.inner.as_ref() }; + inner.num_fences as usize + } + + /// Get the array of fence infos from the sync file's info. + #[doc(alias = "sync_get_fence_info")] + pub fn fence_info(&self) -> &[SyncFenceInfo] { + let inner = unsafe { self.inner.as_ref() }; + + if inner.num_fences == 0 { + &[] + } else { + let sync_fence_info = NonNull::new(inner.sync_fence_info as *mut _) + .expect("sync_fence_info cannot be null if num_fences > 0"); + unsafe { + std::slice::from_raw_parts(sync_fence_info.as_ptr(), inner.num_fences as usize) + } + } + } +} + +impl Drop for SyncFileInfo { + /// Free a [`struct@ffi::sync_file_info`] structure. + #[doc(alias = "sync_file_info_free")] + fn drop(&mut self) { + unsafe { ffi::sync_file_info_free(self.inner.as_ptr()) } + } +} + +#[doc(alias = "sync_fence_info")] +#[repr(transparent)] +pub struct SyncFenceInfo(ffi::sync_fence_info); + +impl Debug for SyncFenceInfo { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("SyncFenceInfo") + .field("obj_name", &self.obj_name()) + .field("driver_name", &self.driver_name()) + .field("status", &self.status()) + .field("flags", &self.flags()) + .field("timestamp_ns", &self.timestamp_ns()) + .finish() + } +} + +impl SyncFenceInfo { + pub fn obj_name(&self) -> &CStr { + // TODO: Switch to CStr::from_bytes_until_nul (with c_char -> u8 transmute) since MSRV 1.69 + unsafe { CStr::from_ptr(self.0.obj_name.as_ptr()) } + } + + pub fn driver_name(&self) -> &CStr { + // TODO: Switch to CStr::from_bytes_until_nul (with c_char -> u8 transmute) since MSRV 1.69 + unsafe { CStr::from_ptr(self.0.driver_name.as_ptr()) } + } + + pub fn status(&self) -> i32 { + self.0.status + } + + pub fn flags(&self) -> u32 { + self.0.flags + } + + pub fn timestamp_ns(&self) -> u64 { + self.0.timestamp_ns + } +} + +/// Merge two sync files. +/// +/// This produces a new sync file with the given name which has the union of the two original sync +/// file's fences; redundant fences may be removed. +/// +/// If one of the input sync files is signaled or invalid, then this function may behave like +/// `dup()`: the new file descriptor refers to the valid/unsignaled sync file with its original +/// name, rather than a new sync file. +pub fn sync_merge(name: &CStr, fd1: BorrowedFd<'_>, fd2: BorrowedFd<'_>) -> OwnedFd { + unsafe { + OwnedFd::from_raw_fd(ffi::sync_merge( + name.as_ptr(), + fd1.as_raw_fd(), + fd2.as_raw_fd(), + )) + } +} diff --git a/clients/android/native/vendor/ndk/src/trace.rs b/clients/android/native/vendor/ndk/src/trace.rs new file mode 100644 index 00000000..8c6cd6fb --- /dev/null +++ b/clients/android/native/vendor/ndk/src/trace.rs @@ -0,0 +1,92 @@ +//! Bindings for the NDK tracing API. +//! +//! See also [the NDK docs](https://developer.android.com/ndk/reference/group/tracing) +#![cfg(feature = "api-level-23")] +use std::ffi::{CString, NulError}; +use std::marker::PhantomData; + +pub fn is_trace_enabled() -> bool { + unsafe { ffi::ATrace_isEnabled() } +} + +#[derive(Debug)] +pub struct Section { + // Section is !Sync and !Send + _pd: PhantomData<*mut ()>, +} + +impl Section { + pub fn new(name: &str) -> Result { + let section_name = CString::new(name)?; + unsafe { ffi::ATrace_beginSection(section_name.as_ptr()) }; + + Ok(Self { _pd: PhantomData }) + } + + pub fn end(self) { + drop(self) + } +} + +impl Drop for Section { + fn drop(&mut self) { + unsafe { ffi::ATrace_endSection() }; + } +} + +/// Unique identifier for distinguishing simultaneous events +#[derive(Debug)] +#[cfg(feature = "api-level-29")] +pub struct Cookie(pub i32); + +#[derive(Debug)] +#[cfg(feature = "api-level-29")] +pub struct AsyncSection { + section_name: CString, + cookie: Cookie, + // AsyncSection is !Sync + _pd: PhantomData<&'static ()>, +} + +#[cfg(feature = "api-level-29")] +impl AsyncSection { + pub fn new(name: &str, cookie: Cookie) -> Result { + let section_name = CString::new(name)?; + unsafe { ffi::ATrace_beginAsyncSection(section_name.as_ptr(), cookie.0) }; + + Ok(Self { + section_name, + cookie, + _pd: PhantomData, + }) + } + + pub fn end(self) { + drop(self) + } +} + +#[cfg(feature = "api-level-29")] +impl Drop for AsyncSection { + fn drop(&mut self) { + unsafe { ffi::ATrace_endAsyncSection(self.section_name.as_ptr(), self.cookie.0) }; + } +} + +#[cfg(feature = "api-level-29")] +#[derive(Debug)] +pub struct Counter { + name: CString, +} + +#[cfg(feature = "api-level-29")] +impl Counter { + pub fn new(name: &str) -> Result { + let name = CString::new(name)?; + Ok(Self { name }) + } + + pub fn set_value(&self, value: i64) { + unsafe { ffi::ATrace_setCounter(self.name.as_ptr(), value) } + } +} diff --git a/clients/android/native/vendor/ndk/src/utils.rs b/clients/android/native/vendor/ndk/src/utils.rs new file mode 100644 index 00000000..fe1ec5b6 --- /dev/null +++ b/clients/android/native/vendor/ndk/src/utils.rs @@ -0,0 +1,70 @@ +//! Internal utilities +use log::{error, log_enabled, Level}; +use std::ffi::{c_int, CStr, CString}; +use std::io::{Error, Result}; + +/// Turns standard `` status codes - typically rewrapped by Android's [`Errors.h`] - into +/// Rust's [`std::io::Error`]. +/// +/// [`Errors.h`]: https://cs.android.com/android/platform/superproject/+/master:system/core/libutils/include/utils/Errors.h +pub(crate) fn status_to_io_result(status: i32) -> Result<()> { + match status { + 0 => Ok(()), + r if r < 0 => Err(Error::from_raw_os_error(-r)), + r => unreachable!("Status is positive integer {}", r), + } +} + +pub(crate) fn android_log(level: Level, tag: &CStr, msg: &CStr) { + let prio = match level { + Level::Error => ffi::android_LogPriority::ANDROID_LOG_ERROR, + Level::Warn => ffi::android_LogPriority::ANDROID_LOG_WARN, + Level::Info => ffi::android_LogPriority::ANDROID_LOG_INFO, + Level::Debug => ffi::android_LogPriority::ANDROID_LOG_DEBUG, + Level::Trace => ffi::android_LogPriority::ANDROID_LOG_VERBOSE, + }; + unsafe { + ffi::__android_log_write(prio.0 as c_int, tag.as_ptr(), msg.as_ptr()); + } +} + +pub(crate) fn log_panic(panic: Box) { + fn log_panic(panic_str: &str) { + const RUST_PANIC_TAG: &CStr = + unsafe { CStr::from_bytes_with_nul_unchecked(b"RustPanic\0") }; + + let panic_str = CString::new(panic_str).unwrap_or_default(); + + // Use the Rust logger if installed and enabled, otherwise fall back to the Android system + // logger so there is at least some record of the panic + if log_enabled!(Level::Error) { + error!("RustPanic: {}", panic_str.to_string_lossy()); + log::logger().flush(); + } else { + android_log(Level::Error, RUST_PANIC_TAG, &panic_str); + } + } + + match panic.downcast::() { + Ok(panic_string) => log_panic(&panic_string), + Err(panic) => match panic.downcast::<&str>() { + Ok(panic_str) => log_panic(&panic_str), + Err(_) => log_panic("Unknown panic message type"), + }, + } +} + +/// Run a closure and abort the program if it panics. +/// +/// This is generally used to ensure Rust callbacks won't unwind past the FFI boundary, which leads +/// to undefined behaviour. +pub(crate) fn abort_on_panic(f: impl FnOnce() -> R) -> R { + std::panic::catch_unwind(std::panic::AssertUnwindSafe(f)).unwrap_or_else(|panic| { + // Try logging the panic before aborting + // + // Just in case our attempt to log a panic could itself cause a panic we use a + // second catch_unwind here. + let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| log_panic(panic))); + std::process::abort(); + }) +} diff --git a/docs-site/content/docs/stats.md b/docs-site/content/docs/stats.md index c9434321..410eeb53 100644 --- a/docs-site/content/docs/stats.md +++ b/docs-site/content/docs/stats.md @@ -74,7 +74,7 @@ always spelled out rather than pretending: | client | headline | why | |---|---|---| | Windows, macOS/iOS (Metal presenter), Linux | `capture→on-glass` / `capture→displayed` | present instant available (GTK measures at hand-off to the compositor, which adds about one compositor cycle after it) | -| Android | `capture→decoded` | the display hand-off happens inside MediaCodec/SurfaceView where precise present timing isn't exposed | +| Android | `capture→displayed` | MediaCodec's per-frame render callback reports SurfaceFlinger's render timestamp; on the rare window where no callback is delivered (the platform may drop them under load) the HUD falls back to `capture→decoded` | | macOS/iOS fallback presenter | `capture→received` | the system video layer hides decode and present timing entirely | A shorter chain means the number is **smaller because it measures less** — check the