mod generated {
use super::{AttrDef, AttrFormat, Symbol};
use crate::res::ResourceId;
include!(concat!(env!("OUT_DIR"), "/framework.rs"));
}
use std::fmt;
use bitflags::bitflags;
pub use generated::{PLATFORM_API_LEVEL, attr};
use crate::res::{RefPackage, ResourceId, ResourceRef, ResourceType, Value};
pub const NAMESPACE: &str = "http://schemas.android.com/apk/res/android";
bitflags! {
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct AttrFormat: u32 {
const REFERENCE = 1 << 0;
const STRING = 1 << 1;
const INTEGER = 1 << 2;
const BOOLEAN = 1 << 3;
const COLOR = 1 << 4;
const FLOAT = 1 << 5;
const DIMENSION = 1 << 6;
const FRACTION = 1 << 7;
const ENUM = 1 << 16;
const FLAGS = 1 << 17;
}
}
impl AttrFormat {
pub const ANY: Self = Self::from_bits_retain(0xffff);
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct Symbol {
pub name: &'static str,
pub value: u32,
}
impl Symbol {
pub(crate) const fn new(name: &'static str, value: u32) -> Self {
Self { name, value }
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct AttrDef {
name: &'static str,
id: ResourceId,
format: AttrFormat,
symbols: &'static [Symbol],
}
impl AttrDef {
pub(crate) const fn new(
name: &'static str,
id: ResourceId,
format: AttrFormat,
symbols: &'static [Symbol],
) -> Self {
Self { name, id, format, symbols }
}
pub const fn name(&self) -> &'static str {
self.name
}
pub const fn id(&self) -> ResourceId {
self.id
}
pub fn format(&self) -> AttrFormat {
if self.format.is_empty() { AttrFormat::ANY } else { self.format }
}
pub fn accepts(&self, value: &Value) -> bool {
let needed = match value {
Value::String(_) => AttrFormat::STRING,
Value::Bool(_) => AttrFormat::BOOLEAN,
Value::Int(_) | Value::Hex(_) => AttrFormat::INTEGER | AttrFormat::ENUM | AttrFormat::FLAGS,
Value::Float(_) => AttrFormat::FLOAT,
Value::Dimension(_) => AttrFormat::DIMENSION,
Value::Fraction(_) => AttrFormat::FRACTION,
Value::Color(_) => AttrFormat::COLOR,
Value::Reference(_) | Value::Attribute(_) | Value::Null | Value::Empty | Value::Raw { .. } => {
return true;
}
};
self.format().intersects(needed)
}
pub const fn symbols(&self) -> &'static [Symbol] {
self.symbols
}
pub fn symbol(&self, name: &str) -> Option<u32> {
self.symbols.iter().find(|s| s.name == name).map(|s| s.value)
}
pub fn enum_name(&self, value: u32) -> Option<&'static str> {
self.symbols.iter().find(|s| s.value == value).map(|s| s.name)
}
pub fn flag_names(&self, value: u32) -> Option<Vec<&'static str>> {
let mut remaining = value;
let mut names = Vec::new();
let mut symbols: Vec<_> = self.symbols.iter().filter(|s| s.value != 0).collect();
symbols.sort_by_key(|s| std::cmp::Reverse(s.value.count_ones()));
for symbol in symbols {
if value & symbol.value == symbol.value && remaining & symbol.value != 0 {
names.push(symbol.name);
remaining &= !symbol.value;
}
}
(remaining == 0).then_some(names)
}
}
impl fmt::Display for AttrDef {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "android:{}", self.name)
}
}
pub fn find_attr(name: &str) -> Option<&'static AttrDef> {
let attrs = &generated::ATTRS;
attrs.binary_search_by(|attr| attr.name.cmp(name)).ok().map(|i| &attrs[i])
}
pub fn attr_by_id(id: ResourceId) -> Option<&'static AttrDef> {
let by_id = &generated::ATTRS_BY_ID;
let index = by_id.binary_search_by_key(&id, |&i| generated::ATTRS[usize::from(i)].id).ok()?;
Some(&generated::ATTRS[usize::from(by_id[index])])
}
pub fn find_resource(ty: ResourceType, name: &str) -> Option<ResourceId> {
let resources = &generated::RESOURCES;
let index = resources.binary_search_by(|(t, n, _)| (*t, *n).cmp(&(ty.as_str(), name))).ok()?;
Some(resources[index].2)
}
pub fn find_style(name: &str) -> Option<ResourceId> {
find_resource(ResourceType::Style, name)
}
pub fn resource_name(id: ResourceId) -> Option<(ResourceType, &'static str)> {
let by_id = &generated::RESOURCES_BY_ID;
let index = by_id.binary_search_by_key(&id, |&i| generated::RESOURCES[usize::from(i)].2).ok()?;
let (ty, name, _) = generated::RESOURCES[usize::from(by_id[index])];
Some((ty.parse().ok()?, name))
}
pub fn resolve(reference: &ResourceRef) -> Option<ResourceId> {
match reference {
ResourceRef::Id(id) => Some(*id),
ResourceRef::Named { package: RefPackage::Android, ty: ResourceType::Attr, name } => {
find_attr(name.as_ref()).map(AttrDef::id)
}
ResourceRef::Named { package: RefPackage::Android, ty, name } => find_resource(*ty, name.as_ref()),
ResourceRef::Named { package: RefPackage::App, .. } => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn tables_are_sorted_for_binary_search() {
assert!(generated::ATTRS.windows(2).all(|w| w[0].name < w[1].name));
assert!(generated::RESOURCES.windows(2).all(|w| (w[0].0, w[0].1) < (w[1].0, w[1].1)));
assert!(generated::RESOURCES.iter().all(|(ty, _, _)| ty.parse::<ResourceType>().is_ok()));
}
#[test]
fn manifest_attributes_have_platform_ids_and_values() {
assert_eq!(attr::LABEL.id(), ResourceId(0x0101_0001));
assert_eq!(attr::NAME.id(), ResourceId(0x0101_0003));
assert_eq!(attr::MIN_SDK_VERSION.id(), ResourceId(0x0101_020c));
assert!(attr::EXPORTED.format().contains(AttrFormat::BOOLEAN));
assert_eq!(attr::LAUNCH_MODE.symbol("singleTask"), Some(2));
assert_eq!(attr::CONFIG_CHANGES.symbol("orientation"), Some(0x80));
assert_eq!(attr::WINDOW_SOFT_INPUT_MODE.flag_names(0x11).unwrap(), ["adjustResize", "stateUnchanged"]);
}
#[test]
fn resolves_framework_references() {
let theme: ResourceRef = "@android:style/Theme.Material.NoActionBar".parse().unwrap();
assert_eq!(resolve(&theme), find_style("Theme.Material.NoActionBar"));
assert_eq!(resource_name(resolve(&theme).unwrap()), Some((ResourceType::Style, "Theme.Material.NoActionBar")));
let icon: ResourceRef = "@android:drawable/sym_def_app_icon".parse().unwrap();
assert_eq!(resolve(&icon), Some(ResourceId(0x0108_0093)));
assert_eq!(attr_by_id(attr::LABEL.id()), Some(attr::LABEL));
let app: ResourceRef = "@string/app_name".parse().unwrap();
assert_eq!(resolve(&app), None);
}
}