1mod 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
32pub const NAMESPACE: &str = "http://schemas.android.com/apk/res/android";
34
35bitflags! {
36 #[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 pub const ANY: Self = Self::from_bits_retain(0xffff);
55}
56
57#[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#[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 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 pub fn format(&self) -> AttrFormat {
100 if self.format.is_empty() { AttrFormat::ANY } else { self.format }
101 }
102
103 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 pub const fn symbols(&self) -> &'static [Symbol] {
134 self.symbols
135 }
136
137 pub fn symbol(&self, name: &str) -> Option<u32> {
139 self.symbols.iter().find(|s| s.name == name).map(|s| s.value)
140 }
141
142 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 pub fn flag_names(&self, value: u32) -> Option<Vec<&'static str>> {
149 let mut remaining = value;
150 let mut names = Vec::new();
151 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
170pub 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
176pub 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
183pub 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
191pub fn find_style(name: &str) -> Option<ResourceId> {
193 find_resource(ResourceType::Style, name)
194}
195
196pub 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
205pub 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}