use crate::{error::PropParseError, proto};
use ini::Properties;
use std::{
collections::HashMap,
ops::{Deref, DerefMut},
};
macro_rules! str {
($a:expr, $b:literal) => {
$a.get($b).map(|a| a.to_string())
};
}
macro_rules! num {
($a:expr, $b:literal) => {
$a.get($b).and_then(|a| a.parse().ok())
};
}
macro_rules! list {
($a:expr, $b:literal) => {
$a.get($b)
.map(|a| a.split(",").map(|a| a.to_string()).collect::<Vec<_>>())
.unwrap_or_default()
};
}
macro_rules! prop_boo {
($a:expr, $b:literal) => {
$a.get($b).map(|a| a.to_lowercase() == "true")
};
}
#[derive(Debug)]
pub struct DeviceProperties {
pub device_config: proto::DeviceConfigurationProto,
pub android_checkin: proto::AndroidCheckinProto,
pub extra_info: HashMap<String, String>,
}
#[derive(Debug)]
pub struct PropsMap(HashMap<String, DeviceProperties>);
impl Deref for PropsMap {
type Target = HashMap<String, DeviceProperties>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for PropsMap {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl PropsMap {
pub fn parse_aurora_config<A: AsRef<str>>(aurora_config: A) -> Result<Self, PropParseError> {
let string_config = aurora_config.as_ref();
let props = ini::Ini::load_from_str(string_config)?;
let map = props
.into_iter()
.filter_map(|(name, props)| name.map(|name| (name, parse_device(&props))))
.collect();
Ok(Self(map))
}
}
fn parse_device(pro: &Properties) -> DeviceProperties {
let extra_info: HashMap<_, _> = [
"Build.ID",
"Vending.versionString",
"Vending.version",
"Build.VERSION.RELEASE",
"SimOperator",
]
.iter()
.map(|a| (a.to_string(), pro.get(a).unwrap_or_default().to_string()))
.collect();
let android_build = proto::AndroidBuildProto {
sdk_version: num!(pro, "Build.VERSION.SDK_INT"),
google_services: num!(pro, "GSF.version"),
manufacturer: str!(pro, "Build.MANUFACTURER"),
build_product: str!(pro, "Build.PRODUCT"),
bootloader: str!(pro, "Build.BOOTLOADER"),
product: str!(pro, "Build.HARDWARE"),
device: str!(pro, "Build.DEVICE"),
id: str!(pro, "Build.FINGERPRINT"),
carrier: str!(pro, "Build.BRAND"),
model: str!(pro, "Build.MODEL"),
radio: str!(pro, "Build.RADIO"),
client: str!(pro, "Client"),
ota_installed: Some(false),
..Default::default()
};
let android_checkin = proto::AndroidCheckinProto {
cell_operator: str!(pro, "CellOperator"),
sim_operator: str!(pro, "SimOperator"),
roaming: str!(pro, "Roaming"),
build: Some(android_build),
last_checkin_msec: Some(0),
user_number: Some(0),
..Default::default()
};
let device_config = proto::DeviceConfigurationProto {
has_five_way_navigation: prop_boo!(pro, "HasFiveWayNavigation"),
has_hard_keyboard: prop_boo!(pro, "HasHardKeyboard"),
system_shared_library: list!(pro, "SharedLibraries"),
system_available_feature: list!(pro, "Features"),
system_supported_locale: list!(pro, "Locales"),
native_platform: list!(pro, "Platforms"),
gl_extension: list!(pro, "GL.Extensions"),
screen_density: num!(pro, "Screen.Density"),
screen_height: num!(pro, "Screen.Height"),
screen_layout: num!(pro, "ScreenLayout"),
screen_width: num!(pro, "Screen.Width"),
touch_screen: num!(pro, "TouchScreen"),
gl_es_version: num!(pro, "GL.Version"),
navigation: num!(pro, "Navigation"),
keyboard: num!(pro, "Keyboard"),
device_feature: pro
.get("Features")
.map(|a| {
a.split(',')
.map(|a| proto::DeviceFeature {
name: Some(a.to_string()),
value: Some(0),
})
.collect::<Vec<_>>()
})
.unwrap_or_default(),
..Default::default()
};
DeviceProperties {
device_config,
android_checkin,
extra_info,
}
}