|  | 
|  | 1 | +//! Bindings for [API levels] | 
|  | 2 | +//! | 
|  | 3 | +//! Defines functions and constants for working with Android API levels. | 
|  | 4 | +//! | 
|  | 5 | +//! [API levels]: https://developer.android.com/ndk/reference/group/apilevels | 
|  | 6 | +
 | 
|  | 7 | +use num_enum::{FromPrimitive, IntoPrimitive}; | 
|  | 8 | +use thiserror::Error; | 
|  | 9 | + | 
|  | 10 | +/// Android API levels, equivalent to the constants defined in `<android/api-level.h>` and the Java | 
|  | 11 | +/// [`Build.VERSION_CODES`] constants. | 
|  | 12 | +/// | 
|  | 13 | +/// [`Build.VERSION_CODES`]: https://developer.android.com/reference/android/os/Build.VERSION_CODES | 
|  | 14 | +#[derive(Debug, Clone, Copy, PartialEq, Eq, FromPrimitive, IntoPrimitive)] | 
|  | 15 | +#[repr(u32)] | 
|  | 16 | +#[non_exhaustive] | 
|  | 17 | +pub enum ApiLevel { | 
|  | 18 | +    /// Magic version number for an Android OS build which has not yet turned into an official | 
|  | 19 | +    /// release. | 
|  | 20 | +    #[doc(alias = "__ANDROID_API_FUTURE__")] | 
|  | 21 | +    Future = ffi::__ANDROID_API_FUTURE__, | 
|  | 22 | + | 
|  | 23 | +    /// Names the Gingerbread API level (9) | 
|  | 24 | +    #[doc(alias = "__ANDROID_API_G__")] | 
|  | 25 | +    G = ffi::__ANDROID_API_G__, | 
|  | 26 | +    /// Names the Ice-Cream Sandwich API level (14) | 
|  | 27 | +    #[doc(alias = "__ANDROID_API_I__")] | 
|  | 28 | +    I = ffi::__ANDROID_API_I__, | 
|  | 29 | +    /// Names the Jellybean API level (16) | 
|  | 30 | +    #[doc(alias = "__ANDROID_API_J__")] | 
|  | 31 | +    J = ffi::__ANDROID_API_J__, | 
|  | 32 | +    /// Names the Jellybean MR1 API level (17) | 
|  | 33 | +    #[doc(alias = "__ANDROID_API_J_MR1__")] | 
|  | 34 | +    JMr1 = ffi::__ANDROID_API_J_MR1__, | 
|  | 35 | +    /// Names the Jellybean MR2 API level (18) | 
|  | 36 | +    #[doc(alias = "__ANDROID_API_J_MR2__")] | 
|  | 37 | +    JMr2 = ffi::__ANDROID_API_J_MR2__, | 
|  | 38 | +    /// Names the KitKat API level (19) | 
|  | 39 | +    #[doc(alias = "__ANDROID_API_K__")] | 
|  | 40 | +    K = ffi::__ANDROID_API_K__, | 
|  | 41 | +    /// Names the Lollipop API level (21) | 
|  | 42 | +    #[doc(alias = "__ANDROID_API_L__")] | 
|  | 43 | +    L = ffi::__ANDROID_API_L__, | 
|  | 44 | +    /// Names the Lollipop MR1 API level (22) | 
|  | 45 | +    #[doc(alias = "__ANDROID_API_L_MR1__")] | 
|  | 46 | +    LMr1 = ffi::__ANDROID_API_L_MR1__, | 
|  | 47 | +    /// Names the Marshmallow API level (23) | 
|  | 48 | +    #[doc(alias = "__ANDROID_API_M__")] | 
|  | 49 | +    M = ffi::__ANDROID_API_M__, | 
|  | 50 | +    /// Names the Nougat API level (24) | 
|  | 51 | +    #[doc(alias = "__ANDROID_API_N__")] | 
|  | 52 | +    N = ffi::__ANDROID_API_N__, | 
|  | 53 | +    /// Names the Nougat MR1 API level (25) | 
|  | 54 | +    #[doc(alias = "__ANDROID_API_N_MR1__")] | 
|  | 55 | +    NMr1 = ffi::__ANDROID_API_N_MR1__, | 
|  | 56 | +    /// Names the Oreo API level (26) | 
|  | 57 | +    #[doc(alias = "__ANDROID_API_O__")] | 
|  | 58 | +    O = ffi::__ANDROID_API_O__, | 
|  | 59 | +    /// Names the Oreo MR1 API level (27) | 
|  | 60 | +    #[doc(alias = "__ANDROID_API_O_MR1__")] | 
|  | 61 | +    OMr1 = ffi::__ANDROID_API_O_MR1__, | 
|  | 62 | +    /// Names the Pie API level (28) | 
|  | 63 | +    #[doc(alias = "__ANDROID_API_P__")] | 
|  | 64 | +    P = ffi::__ANDROID_API_P__, | 
|  | 65 | +    /// Names the Android 10 (aka "Q" or "Quince Tart") API level (29) | 
|  | 66 | +    #[doc(alias = "__ANDROID_API_Q__")] | 
|  | 67 | +    Q = ffi::__ANDROID_API_Q__, | 
|  | 68 | +    /// Names the Android 11 (aka "R" or "Red Velvet Cake") API level (30) | 
|  | 69 | +    #[doc(alias = "__ANDROID_API_R__")] | 
|  | 70 | +    R = ffi::__ANDROID_API_R__, | 
|  | 71 | +    /// Names the Android 12 (aka "S" or "Snowcone") API level (31) | 
|  | 72 | +    #[doc(alias = "__ANDROID_API_S__")] | 
|  | 73 | +    S = ffi::__ANDROID_API_S__, | 
|  | 74 | +    /// Names the Android 13 (aka "T" or "Tiramisu") API level (33) | 
|  | 75 | +    #[doc(alias = "__ANDROID_API_T__")] | 
|  | 76 | +    T = ffi::__ANDROID_API_T__, | 
|  | 77 | +    /// Names the Android 14 (aka "U" or "UpsideDownCake") API level (34) | 
|  | 78 | +    #[doc(alias = "__ANDROID_API_U__")] | 
|  | 79 | +    U = ffi::__ANDROID_API_U__, | 
|  | 80 | +    /// Names the Android 15 (aka "V" or "VanillaIceCream") API level (35) | 
|  | 81 | +    #[doc(alias = "__ANDROID_API_V__")] | 
|  | 82 | +    V = ffi::__ANDROID_API_V__, | 
|  | 83 | +    #[doc(hidden)] | 
|  | 84 | +    #[num_enum(catch_all)] | 
|  | 85 | +    __Unknown(u32), | 
|  | 86 | +} | 
|  | 87 | + | 
|  | 88 | +/// Returns the `targetSdkVersion` from `AndroidManifest.xml` of the caller, or [`ApiLevel::Future`] | 
|  | 89 | +/// if there is no known target SDK version (for code not running in the context of an app). | 
|  | 90 | +/// | 
|  | 91 | +/// See also [`device_api_level()`]. | 
|  | 92 | +#[cfg(feature = "api-level-24")] | 
|  | 93 | +pub fn application_target_sdk_version() -> ApiLevel { | 
|  | 94 | +    let version = unsafe { ffi::android_get_application_target_sdk_version() }; | 
|  | 95 | +    u32::try_from(version) | 
|  | 96 | +        // Docs suggest that it would only return `Future` | 
|  | 97 | +        .expect("Unexpected sign bit in `application_target_sdk_version()`") | 
|  | 98 | +        .into() | 
|  | 99 | +} | 
|  | 100 | + | 
|  | 101 | +/// Possible failures returned by [`device_api_level()`]. | 
|  | 102 | +#[derive(Clone, Debug, PartialEq, Eq, Error)] | 
|  | 103 | +pub enum DeviceApiLevelError { | 
|  | 104 | +    #[cfg(not(feature = "api-level-29"))] | 
|  | 105 | +    #[error( | 
|  | 106 | +        "`__system_property_get(\"ro.build.version.sdk\"` failed or string is empty, ret is {0}" | 
|  | 107 | +    )] | 
|  | 108 | +    FallbackPropertyGetFailed(i32), | 
|  | 109 | +    #[cfg(not(feature = "api-level-29"))] | 
|  | 110 | +    #[error("`ro.build.version.sdk` does not contain valid UTF-8")] | 
|  | 111 | +    FallbackUtf8Error(#[from] std::str::Utf8Error), | 
|  | 112 | +    #[cfg(not(feature = "api-level-29"))] | 
|  | 113 | +    #[error("`ro.build.version.sdk` does not contain an integer")] | 
|  | 114 | +    FallbackParseIntError(#[from] std::num::ParseIntError), | 
|  | 115 | +    #[error("device_api_level() encountered a negative version code")] | 
|  | 116 | +    TryFromIntError(#[from] std::num::TryFromIntError), | 
|  | 117 | +} | 
|  | 118 | + | 
|  | 119 | +/// Returns the API level of the device we're actually running on. | 
|  | 120 | +/// | 
|  | 121 | +/// The returned value is equivalent to the Java [`Build.VERSION.SDK_INT`] API. | 
|  | 122 | +/// | 
|  | 123 | +/// [`Build.VERSION.SDK_INT`]: https://developer.android.com/reference/android/os/Build.VERSION#SDK_INT | 
|  | 124 | +/// | 
|  | 125 | +/// Below `api-level-29` this falls back to reading the `"ro.build.version.sdk"` system property, | 
|  | 126 | +/// with the possibility to return more types of errors. | 
|  | 127 | +/// | 
|  | 128 | +/// See also [`application_target_sdk_version()`]. | 
|  | 129 | +pub fn device_api_level() -> Result<ApiLevel, DeviceApiLevelError> { | 
|  | 130 | +    #[cfg(not(feature = "api-level-29"))] | 
|  | 131 | +    let version = { | 
|  | 132 | +        use std::ffi::c_int; | 
|  | 133 | +        let mut value = [0; 92]; | 
|  | 134 | +        let ret = unsafe { | 
|  | 135 | +            // from sys/cdefs.h, also available in the `libc` crate | 
|  | 136 | +            extern "C" { | 
|  | 137 | +                fn __system_property_get(__name: *const u8, __value: *mut u8) -> c_int; | 
|  | 138 | +            } | 
|  | 139 | +            // TODO: Switch to C-string literal since MSRV 1.77 | 
|  | 140 | +            __system_property_get(b"ro.build.version.sdk\0".as_ptr(), value.as_mut_ptr()) | 
|  | 141 | +        }; | 
|  | 142 | +        if ret < 1 { | 
|  | 143 | +            return Err(DeviceApiLevelError::FallbackPropertyGetFailed(ret)); | 
|  | 144 | +        } | 
|  | 145 | +        std::str::from_utf8(&value)?.parse::<i32>()? | 
|  | 146 | +    }; | 
|  | 147 | + | 
|  | 148 | +    #[cfg(feature = "api-level-29")] | 
|  | 149 | +    let version = unsafe { ffi::android_get_device_api_level() }; | 
|  | 150 | + | 
|  | 151 | +    Ok(u32::try_from(version)?.into()) | 
|  | 152 | +} | 
0 commit comments