Skip to main content

apk/android/
mod.rs

1//! Public resources of the Android framework (the `android:` namespace).
2//!
3//! The tables are generated at build time from `framework.txt`, which
4//! `tools/gen-framework` extracts from an SDK platform's `android.jar`, so
5//! attribute IDs, formats, and enum values match the platform exactly.
6//!
7//! ```
8//! use apk::android::{self, attr};
9//!
10//! assert_eq!(attr::SCREEN_ORIENTATION.symbol("landscape"), Some(0));
11//! assert_eq!(android::find_attr("label").unwrap().id().0, 0x0101_0001);
12//! assert!(android::find_style("Theme.Material.NoActionBar").is_some());
13//! assert!(android::find_resource(apk::res::ResourceType::Color, "white").is_some());
14//! ```
15
16/// The tables `build.rs` generates from `framework.txt`.
17mod generated {
18    use super::{AttrDef, AttrFormat, Symbol};
19    use crate::res::ResourceId;
20
21    include!(concat!(env!("OUT_DIR"), "/framework.rs"));
22}
23
24use std::fmt;
25
26use bitflags::bitflags;
27
28pub use generated::{PLATFORM_API_LEVEL, attr};
29
30use crate::res::{RefPackage, ResourceId, ResourceRef, ResourceType, Value};
31
32/// The XML namespace URI bound to the `android:` prefix.
33pub const NAMESPACE: &str = "http://schemas.android.com/apk/res/android";
34
35bitflags! {
36    /// The value formats an attribute accepts (`<attr format="...">`).
37    #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
38    pub struct AttrFormat: u32 {
39        const REFERENCE = 1 << 0;
40        const STRING = 1 << 1;
41        const INTEGER = 1 << 2;
42        const BOOLEAN = 1 << 3;
43        const COLOR = 1 << 4;
44        const FLOAT = 1 << 5;
45        const DIMENSION = 1 << 6;
46        const FRACTION = 1 << 7;
47        const ENUM = 1 << 16;
48        const FLAGS = 1 << 17;
49    }
50}
51
52impl AttrFormat {
53    /// Every simple value format, used by attributes declared without a format.
54    pub const ANY: Self = Self::from_bits_retain(0xffff);
55}
56
57/// A named value of an enum or flags attribute, e.g. `landscape = 0`.
58#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
59pub struct Symbol {
60    pub name: &'static str,
61    pub value: u32,
62}
63
64impl Symbol {
65    pub(crate) const fn new(name: &'static str, value: u32) -> Self {
66        Self { name, value }
67    }
68}
69
70/// A public framework attribute.
71#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
72pub struct AttrDef {
73    name: &'static str,
74    id: ResourceId,
75    format: AttrFormat,
76    symbols: &'static [Symbol],
77}
78
79impl AttrDef {
80    pub(crate) const fn new(
81        name: &'static str,
82        id: ResourceId,
83        format: AttrFormat,
84        symbols: &'static [Symbol],
85    ) -> Self {
86        Self { name, id, format, symbols }
87    }
88
89    /// The attribute name without its `android:` prefix.
90    pub const fn name(&self) -> &'static str {
91        self.name
92    }
93
94    pub const fn id(&self) -> ResourceId {
95        self.id
96    }
97
98    /// The accepted formats; [`AttrFormat::ANY`] when undeclared.
99    pub fn format(&self) -> AttrFormat {
100        if self.format.is_empty() { AttrFormat::ANY } else { self.format }
101    }
102
103    /// Whether `value` has a format this attribute accepts, as aapt2 would
104    /// have typed it. References, `@null`, `@empty`, and values of types
105    /// this crate does not interpret are resolved at runtime, so they are
106    /// always accepted.
107    ///
108    /// ```
109    /// use apk::android::attr;
110    /// use apk::res::Value;
111    ///
112    /// assert!(attr::HAS_CODE.accepts(&Value::Bool(false)));
113    /// assert!(!attr::HAS_CODE.accepts(&Value::String("false".into())));
114    /// assert!(attr::SCREEN_ORIENTATION.accepts(&Value::Int(0)));
115    /// ```
116    pub fn accepts(&self, value: &Value) -> bool {
117        let needed = match value {
118            Value::String(_) => AttrFormat::STRING,
119            Value::Bool(_) => AttrFormat::BOOLEAN,
120            Value::Int(_) | Value::Hex(_) => AttrFormat::INTEGER | AttrFormat::ENUM | AttrFormat::FLAGS,
121            Value::Float(_) => AttrFormat::FLOAT,
122            Value::Dimension(_) => AttrFormat::DIMENSION,
123            Value::Fraction(_) => AttrFormat::FRACTION,
124            Value::Color(_) => AttrFormat::COLOR,
125            Value::Reference(_) | Value::Attribute(_) | Value::Null | Value::Empty | Value::Raw { .. } => {
126                return true;
127            }
128        };
129        self.format().intersects(needed)
130    }
131
132    /// The enum or flag values this attribute defines.
133    pub const fn symbols(&self) -> &'static [Symbol] {
134        self.symbols
135    }
136
137    /// The value of the enum or flag named `name`.
138    pub fn symbol(&self, name: &str) -> Option<u32> {
139        self.symbols.iter().find(|s| s.name == name).map(|s| s.value)
140    }
141
142    /// The name of the enum value `value`.
143    pub fn enum_name(&self, value: u32) -> Option<&'static str> {
144        self.symbols.iter().find(|s| s.value == value).map(|s| s.name)
145    }
146
147    /// Names the flags set in `value`, if `value` is exactly a union of them.
148    pub fn flag_names(&self, value: u32) -> Option<Vec<&'static str>> {
149        let mut remaining = value;
150        let mut names = Vec::new();
151        // Prefer wide masks such as `stateAlwaysHidden` over their component bits.
152        let mut symbols: Vec<_> = self.symbols.iter().filter(|s| s.value != 0).collect();
153        symbols.sort_by_key(|s| std::cmp::Reverse(s.value.count_ones()));
154        for symbol in symbols {
155            if value & symbol.value == symbol.value && remaining & symbol.value != 0 {
156                names.push(symbol.name);
157                remaining &= !symbol.value;
158            }
159        }
160        (remaining == 0).then_some(names)
161    }
162}
163
164impl fmt::Display for AttrDef {
165    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
166        write!(f, "android:{}", self.name)
167    }
168}
169
170/// Looks up a public framework attribute by name (without `android:`).
171pub fn find_attr(name: &str) -> Option<&'static AttrDef> {
172    let attrs = &generated::ATTRS;
173    attrs.binary_search_by(|attr| attr.name.cmp(name)).ok().map(|i| &attrs[i])
174}
175
176/// Looks up a public framework attribute by resource ID.
177pub fn attr_by_id(id: ResourceId) -> Option<&'static AttrDef> {
178    let by_id = &generated::ATTRS_BY_ID;
179    let index = by_id.binary_search_by_key(&id, |&i| generated::ATTRS[usize::from(i)].id).ok()?;
180    Some(&generated::ATTRS[usize::from(by_id[index])])
181}
182
183/// Looks up a public framework resource other than an attribute, such as
184/// `(Style, "Theme.Material.NoActionBar")` or `(Drawable, "sym_def_app_icon")`.
185pub fn find_resource(ty: ResourceType, name: &str) -> Option<ResourceId> {
186    let resources = &generated::RESOURCES;
187    let index = resources.binary_search_by(|(t, n, _)| (*t, *n).cmp(&(ty.as_str(), name))).ok()?;
188    Some(resources[index].2)
189}
190
191/// Looks up a public framework style such as `Theme.Material.NoActionBar`.
192pub fn find_style(name: &str) -> Option<ResourceId> {
193    find_resource(ResourceType::Style, name)
194}
195
196/// The type and name of the public framework resource (other than an
197/// attribute) with ID `id`.
198pub fn resource_name(id: ResourceId) -> Option<(ResourceType, &'static str)> {
199    let by_id = &generated::RESOURCES_BY_ID;
200    let index = by_id.binary_search_by_key(&id, |&i| generated::RESOURCES[usize::from(i)].2).ok()?;
201    let (ty, name, _) = generated::RESOURCES[usize::from(by_id[index])];
202    Some((ty.parse().ok()?, name))
203}
204
205/// Resolves an `@android:type/name` reference to a public framework resource.
206pub fn resolve(reference: &ResourceRef) -> Option<ResourceId> {
207    match reference {
208        ResourceRef::Id(id) => Some(*id),
209        ResourceRef::Named { package: RefPackage::Android, ty: ResourceType::Attr, name } => {
210            find_attr(name.as_ref()).map(AttrDef::id)
211        }
212        ResourceRef::Named { package: RefPackage::Android, ty, name } => find_resource(*ty, name.as_ref()),
213        ResourceRef::Named { package: RefPackage::App, .. } => None,
214    }
215}
216
217#[cfg(test)]
218mod tests {
219    use super::*;
220
221    #[test]
222    fn tables_are_sorted_for_binary_search() {
223        assert!(generated::ATTRS.windows(2).all(|w| w[0].name < w[1].name));
224        assert!(generated::RESOURCES.windows(2).all(|w| (w[0].0, w[0].1) < (w[1].0, w[1].1)));
225        assert!(generated::RESOURCES.iter().all(|(ty, _, _)| ty.parse::<ResourceType>().is_ok()));
226    }
227
228    #[test]
229    fn manifest_attributes_have_platform_ids_and_values() {
230        assert_eq!(attr::LABEL.id(), ResourceId(0x0101_0001));
231        assert_eq!(attr::NAME.id(), ResourceId(0x0101_0003));
232        assert_eq!(attr::MIN_SDK_VERSION.id(), ResourceId(0x0101_020c));
233        assert!(attr::EXPORTED.format().contains(AttrFormat::BOOLEAN));
234        assert_eq!(attr::LAUNCH_MODE.symbol("singleTask"), Some(2));
235        assert_eq!(attr::CONFIG_CHANGES.symbol("orientation"), Some(0x80));
236        assert_eq!(attr::WINDOW_SOFT_INPUT_MODE.flag_names(0x11).unwrap(), ["adjustResize", "stateUnchanged"]);
237    }
238
239    #[test]
240    fn resolves_framework_references() {
241        let theme: ResourceRef = "@android:style/Theme.Material.NoActionBar".parse().unwrap();
242        assert_eq!(resolve(&theme), find_style("Theme.Material.NoActionBar"));
243        assert_eq!(resource_name(resolve(&theme).unwrap()), Some((ResourceType::Style, "Theme.Material.NoActionBar")));
244        let icon: ResourceRef = "@android:drawable/sym_def_app_icon".parse().unwrap();
245        assert_eq!(resolve(&icon), Some(ResourceId(0x0108_0093)));
246        assert_eq!(attr_by_id(attr::LABEL.id()), Some(attr::LABEL));
247        let app: ResourceRef = "@string/app_name".parse().unwrap();
248        assert_eq!(resolve(&app), None);
249    }
250}