From 48e531fe47cbf480ee75d9dcc126c59cec1c16c3 Mon Sep 17 00:00:00 2001 From: Marijn Suijten Date: Thu, 25 Jul 2024 10:30:27 +0200 Subject: [PATCH] ndk: Add definitions and bindings for API levels `android/api-levels.h` from the NDK defines constants for all (NDK-supported) API levels and both a getter for the "current apps' target API level" as well as the API level of the device the app is running on. Create bindings to allow users to query this information at runtime from their Rust code as well. The equivalent of the NDK's `__ANDROID_API__` define are our `api-level-xx` crate features, used to restrict APIs (and inexistant linker symbols) at build-time. (As there is no full-fledged Android build system involved, users of the `ndk` crate are expected to keep this in sync with the minimum/target API level passed to their build tool of choice.) --- ndk-sys/CHANGELOG.md | 2 + ndk-sys/generate_bindings.sh | 1 + ndk-sys/src/ffi_aarch64.rs | 1 - ndk-sys/src/ffi_arm.rs | 1 - ndk-sys/src/ffi_i686.rs | 1 - ndk-sys/src/ffi_x86_64.rs | 1 - ndk/CHANGELOG.md | 1 + ndk/src/api_level.rs | 151 +++++++++++++++++++++++++++++++++++ ndk/src/lib.rs | 1 + 9 files changed, 156 insertions(+), 4 deletions(-) create mode 100644 ndk/src/api_level.rs diff --git a/ndk-sys/CHANGELOG.md b/ndk-sys/CHANGELOG.md index 4ff63c02..57f639a5 100644 --- a/ndk-sys/CHANGELOG.md +++ b/ndk-sys/CHANGELOG.md @@ -1,5 +1,7 @@ # Unreleased +- Removed `__ANDROID_API__` constant as it is always defined to the value of `__ANDROID_API_FUTURE__`. (#479) + # 0.6.0 (2024-04-26) - Generate against upstream NDK build `11769913`. (#471) diff --git a/ndk-sys/generate_bindings.sh b/ndk-sys/generate_bindings.sh index 0978e69d..57391a1a 100755 --- a/ndk-sys/generate_bindings.sh +++ b/ndk-sys/generate_bindings.sh @@ -30,6 +30,7 @@ while read ARCH && read TARGET ; do --blocklist-item 'C?_?JNIEnv' \ --blocklist-item '_?JavaVM' \ --blocklist-item '_?j\w+' \ + --blocklist-item '__ANDROID_API__' \ --newtype-enum '\w+_(result|status)_t' \ --newtype-enum 'ACameraDevice_request_template' \ --newtype-enum 'ADataSpace' \ diff --git a/ndk-sys/src/ffi_aarch64.rs b/ndk-sys/src/ffi_aarch64.rs index b9d33b19..cd86ab94 100644 --- a/ndk-sys/src/ffi_aarch64.rs +++ b/ndk-sys/src/ffi_aarch64.rs @@ -34,7 +34,6 @@ pub const __BIONIC__: u32 = 1; pub const __WORDSIZE: u32 = 64; pub const __bos_level: u32 = 0; pub const __ANDROID_API_FUTURE__: u32 = 10000; -pub const __ANDROID_API__: u32 = 10000; pub const __ANDROID_API_G__: u32 = 9; pub const __ANDROID_API_I__: u32 = 14; pub const __ANDROID_API_J__: u32 = 16; diff --git a/ndk-sys/src/ffi_arm.rs b/ndk-sys/src/ffi_arm.rs index 64fce589..be06e98e 100644 --- a/ndk-sys/src/ffi_arm.rs +++ b/ndk-sys/src/ffi_arm.rs @@ -114,7 +114,6 @@ pub const __BIONIC__: u32 = 1; pub const __WORDSIZE: u32 = 32; pub const __bos_level: u32 = 0; pub const __ANDROID_API_FUTURE__: u32 = 10000; -pub const __ANDROID_API__: u32 = 10000; pub const __ANDROID_API_G__: u32 = 9; pub const __ANDROID_API_I__: u32 = 14; pub const __ANDROID_API_J__: u32 = 16; diff --git a/ndk-sys/src/ffi_i686.rs b/ndk-sys/src/ffi_i686.rs index d060c2e7..0f06df50 100644 --- a/ndk-sys/src/ffi_i686.rs +++ b/ndk-sys/src/ffi_i686.rs @@ -34,7 +34,6 @@ pub const __BIONIC__: u32 = 1; pub const __WORDSIZE: u32 = 32; pub const __bos_level: u32 = 0; pub const __ANDROID_API_FUTURE__: u32 = 10000; -pub const __ANDROID_API__: u32 = 10000; pub const __ANDROID_API_G__: u32 = 9; pub const __ANDROID_API_I__: u32 = 14; pub const __ANDROID_API_J__: u32 = 16; diff --git a/ndk-sys/src/ffi_x86_64.rs b/ndk-sys/src/ffi_x86_64.rs index 22fbb911..508403f1 100644 --- a/ndk-sys/src/ffi_x86_64.rs +++ b/ndk-sys/src/ffi_x86_64.rs @@ -34,7 +34,6 @@ pub const __BIONIC__: u32 = 1; pub const __WORDSIZE: u32 = 64; pub const __bos_level: u32 = 0; pub const __ANDROID_API_FUTURE__: u32 = 10000; -pub const __ANDROID_API__: u32 = 10000; pub const __ANDROID_API_G__: u32 = 9; pub const __ANDROID_API_I__: u32 = 14; pub const __ANDROID_API_J__: u32 = 16; diff --git a/ndk/CHANGELOG.md b/ndk/CHANGELOG.md index 4bc9a7e2..deee0c02 100644 --- a/ndk/CHANGELOG.md +++ b/ndk/CHANGELOG.md @@ -1,6 +1,7 @@ # Unreleased - image_reader: Add `ImageReader::new_with_data_space()` constructor and `ImageReader::data_space()` getter from API level 34. (#474) +- Add `api_level` module with definitions and bindings for Android API levels. (#479) # 0.9.0 (2024-04-26) diff --git a/ndk/src/api_level.rs b/ndk/src/api_level.rs new file mode 100644 index 00000000..b66f1b51 --- /dev/null +++ b/ndk/src/api_level.rs @@ -0,0 +1,151 @@ +//! Bindings for [API levels] +//! +//! Defines functions and constants for working with Android API levels. +//! +//! [API levels]: https://developer.android.com/ndk/reference/group/apilevels + +use num_enum::{FromPrimitive, IntoPrimitive}; +use thiserror::Error; + +/// Android API levels, equivalent to the constants defined in `` and the Java +/// [`Build.VERSION_CODES`] constants. +/// +/// [`Build.VERSION_CODES`]: https://developer.android.com/reference/android/os/Build.VERSION_CODES +#[derive(Debug, Clone, Copy, PartialEq, Eq, FromPrimitive, IntoPrimitive)] +#[repr(u32)] +#[non_exhaustive] +pub enum ApiLevel { + /// Magic version number for an Android OS build which has not yet turned into an official + /// release. + #[doc(alias = "__ANDROID_API_FUTURE__")] + Future = ffi::__ANDROID_API_FUTURE__, + + /// Names the Gingerbread API level (9) + #[doc(alias = "__ANDROID_API_G__")] + G = ffi::__ANDROID_API_G__, + /// Names the Ice-Cream Sandwich API level (14) + #[doc(alias = "__ANDROID_API_I__")] + I = ffi::__ANDROID_API_I__, + /// Names the Jellybean API level (16) + #[doc(alias = "__ANDROID_API_J__")] + J = ffi::__ANDROID_API_J__, + /// Names the Jellybean MR1 API level (17) + #[doc(alias = "__ANDROID_API_J_MR1__")] + JMr1 = ffi::__ANDROID_API_J_MR1__, + /// Names the Jellybean MR2 API level (18) + #[doc(alias = "__ANDROID_API_J_MR2__")] + JMr2 = ffi::__ANDROID_API_J_MR2__, + /// Names the KitKat API level (19) + #[doc(alias = "__ANDROID_API_K__")] + K = ffi::__ANDROID_API_K__, + /// Names the Lollipop API level (21) + #[doc(alias = "__ANDROID_API_L__")] + L = ffi::__ANDROID_API_L__, + /// Names the Lollipop MR1 API level (22) + #[doc(alias = "__ANDROID_API_L_MR1__")] + LMr1 = ffi::__ANDROID_API_L_MR1__, + /// Names the Marshmallow API level (23) + #[doc(alias = "__ANDROID_API_M__")] + M = ffi::__ANDROID_API_M__, + /// Names the Nougat API level (24) + #[doc(alias = "__ANDROID_API_N__")] + N = ffi::__ANDROID_API_N__, + /// Names the Nougat MR1 API level (25) + #[doc(alias = "__ANDROID_API_N_MR1__")] + NMr1 = ffi::__ANDROID_API_N_MR1__, + /// Names the Oreo API level (26) + #[doc(alias = "__ANDROID_API_O__")] + O = ffi::__ANDROID_API_O__, + /// Names the Oreo MR1 API level (27) + #[doc(alias = "__ANDROID_API_O_MR1__")] + OMr1 = ffi::__ANDROID_API_O_MR1__, + /// Names the Pie API level (28) + #[doc(alias = "__ANDROID_API_P__")] + P = ffi::__ANDROID_API_P__, + /// Names the Android 10 (aka "Q" or "Quince Tart") API level (29) + #[doc(alias = "__ANDROID_API_Q__")] + Q = ffi::__ANDROID_API_Q__, + /// Names the Android 11 (aka "R" or "Red Velvet Cake") API level (30) + #[doc(alias = "__ANDROID_API_R__")] + R = ffi::__ANDROID_API_R__, + /// Names the Android 12 (aka "S" or "Snowcone") API level (31) + #[doc(alias = "__ANDROID_API_S__")] + S = ffi::__ANDROID_API_S__, + /// Names the Android 13 (aka "T" or "Tiramisu") API level (33) + #[doc(alias = "__ANDROID_API_T__")] + T = ffi::__ANDROID_API_T__, + /// Names the Android 14 (aka "U" or "UpsideDownCake") API level (34) + #[doc(alias = "__ANDROID_API_U__")] + U = ffi::__ANDROID_API_U__, + /// Names the Android 15 (aka "V" or "VanillaIceCream") API level (35) + #[doc(alias = "__ANDROID_API_V__")] + V = ffi::__ANDROID_API_V__, + #[doc(hidden)] + #[num_enum(catch_all)] + __Unknown(u32), +} + +/// Returns the `targetSdkVersion` from `AndroidManifest.xml` of the caller, or [`ApiLevel::Future`] +/// if there is no known target SDK version (for code not running in the context of an app). +/// +/// See also [`device_api_level()`]. +#[cfg(feature = "api-level-24")] +pub fn application_target_sdk_version() -> ApiLevel { + let version = unsafe { ffi::android_get_application_target_sdk_version() }; + u32::try_from(version) + // Docs suggest that it would only return `Future` + .expect("Unexpected sign bit in `application_target_sdk_version()`") + .into() +} + +/// Possible failures returned by [`device_api_level()`]. +#[derive(Clone, Debug, PartialEq, Eq, Error)] +pub enum DeviceApiLevelError { + #[cfg(not(feature = "api-level-29"))] + #[error( + "`__system_property_get(\"ro.build.version.sdk\"` failed or string is empty, ret is {0}" + )] + FallbackPropertyGetFailed(i32), + #[cfg(not(feature = "api-level-29"))] + #[error("`ro.build.version.sdk` does not contain valid UTF-8")] + FallbackUtf8Error(#[from] std::str::Utf8Error), + #[cfg(not(feature = "api-level-29"))] + #[error("`ro.build.version.sdk` does not contain an integer")] + FallbackParseIntError(#[from] std::num::ParseIntError), + #[error("device_api_level() encountered a negative version code")] + TryFromIntError(#[from] std::num::TryFromIntError), +} + +/// Returns the API level of the device we're actually running on. +/// +/// The returned value is equivalent to the Java [`Build.VERSION.SDK_INT`] API. +/// +/// [`Build.VERSION.SDK_INT`]: https://developer.android.com/reference/android/os/Build.VERSION#SDK_INT +/// +/// Below `api-level-29` this falls back to reading the `"ro.build.version.sdk"` system property, +/// with the possibility to return more types of errors. +/// +/// See also [`application_target_sdk_version()`]. +pub fn device_api_level() -> Result { + #[cfg(not(feature = "api-level-29"))] + let version = { + use std::ffi::{c_char, c_int}; + let mut value = [0; 92]; + let ret = unsafe { + // from sys/cdefs.h, also available in the `libc` crate + extern "C" { + fn __system_property_get(__name: *const c_char, __value: *mut c_char) -> c_int; + } + __system_property_get(c"ro.build.version.sdk".as_ptr(), value.as_mut_ptr()) + }; + if ret < 1 { + return Err(DeviceApiLevelError::FallbackPropertyGetFailed(ret)); + } + std::str::from_utf8(&value)?.parse::()? + }; + + #[cfg(feature = "api-level-29")] + let version = unsafe { ffi::android_get_device_api_level() }; + + Ok(u32::try_from(version)?.into()) +} diff --git a/ndk/src/lib.rs b/ndk/src/lib.rs index 81e2ef61..25cba58e 100644 --- a/ndk/src/lib.rs +++ b/ndk/src/lib.rs @@ -11,6 +11,7 @@ )] #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] +pub mod api_level; pub mod asset; pub mod audio; pub mod bitmap;