use crate::error::NdkError;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::path::Path;
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename = "manifest")]
pub struct AndroidManifest {
#[serde(rename(serialize = "@xmlns:android"))]
#[serde(default = "default_namespace")]
ns_android: String,
#[serde(rename(serialize = "@package"), default)]
pub package: String,
#[serde(
rename(serialize = "@android:sharedUserId"),
skip_serializing_if = "Option::is_none"
)]
pub shared_user_id: Option<String>,
#[serde(
rename(serialize = "@android:versionCode"),
skip_serializing_if = "Option::is_none"
)]
pub version_code: Option<u32>,
#[serde(
rename(serialize = "@android:versionName"),
skip_serializing_if = "Option::is_none"
)]
pub version_name: Option<String>,
#[serde(rename(serialize = "uses-sdk"))]
#[serde(default)]
pub sdk: Sdk,
#[serde(rename(serialize = "uses-feature"))]
#[serde(default)]
pub uses_feature: Vec<Feature>,
#[serde(rename(serialize = "uses-permission"))]
#[serde(default)]
pub uses_permission: Vec<Permission>,
#[serde(default)]
pub queries: Option<Queries>,
#[serde(default)]
pub application: Application,
}
impl Default for AndroidManifest {
fn default() -> Self {
Self {
ns_android: default_namespace(),
package: Default::default(),
shared_user_id: Default::default(),
version_code: Default::default(),
version_name: Default::default(),
sdk: Default::default(),
uses_feature: Default::default(),
uses_permission: Default::default(),
queries: Default::default(),
application: Default::default(),
}
}
}
impl AndroidManifest {
pub fn write_to(&self, dir: &Path) -> Result<(), NdkError> {
let xml = quick_xml::se::to_string(&self).map_err(NdkError::Serialize)?;
std::fs::write(dir.join("AndroidManifest.xml"), xml.as_bytes())?;
Ok(())
}
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct Application {
#[serde(
rename(serialize = "@android:debuggable"),
skip_serializing_if = "Option::is_none"
)]
pub debuggable: Option<bool>,
#[serde(
rename(serialize = "@android:theme"),
skip_serializing_if = "Option::is_none"
)]
pub theme: Option<String>,
#[serde(rename(serialize = "@android:hasCode"))]
#[serde(default)]
pub has_code: bool,
#[serde(
rename(serialize = "@android:icon"),
skip_serializing_if = "Option::is_none"
)]
pub icon: Option<String>,
#[serde(rename(serialize = "@android:label"))]
#[serde(default)]
pub label: String,
#[serde(
rename(serialize = "@android:extractNativeLibs"),
skip_serializing_if = "Option::is_none"
)]
pub extract_native_libs: Option<bool>,
#[serde(
rename(serialize = "@android:usesCleartextTraffic"),
skip_serializing_if = "Option::is_none"
)]
pub uses_cleartext_traffic: Option<bool>,
#[serde(rename(serialize = "meta-data"))]
#[serde(default)]
pub meta_data: Vec<MetaData>,
#[serde(default = "default_activities")]
#[serde(deserialize_with = "deserialize_activities")]
pub activity: Vec<Activity>,
#[serde(default)]
#[serde(deserialize_with = "deserialize_services")]
pub service: Vec<Service>,
}
impl Default for Application {
fn default() -> Self {
Self {
debuggable: None,
theme: None,
has_code: false,
icon: None,
label: String::new(),
extract_native_libs: None,
uses_cleartext_traffic: None,
meta_data: Vec::new(),
activity: default_activities(),
service: Vec::new(),
}
}
}
fn default_activities() -> Vec<Activity> {
vec![Activity::default()]
}
fn deserialize_activities<'de, D>(deserializer: D) -> Result<Vec<Activity>, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
#[serde(untagged)]
enum OneOrMany<T> {
One(T),
Many(Vec<T>),
}
match OneOrMany::<Activity>::deserialize(deserializer)? {
OneOrMany::One(activity) => Ok(vec![activity]),
OneOrMany::Many(activities) => Ok(activities),
}
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct Activity {
#[serde(
rename(serialize = "@android:configChanges"),
skip_serializing_if = "Option::is_none"
)]
#[serde(default = "default_config_changes")]
pub config_changes: Option<String>,
#[serde(
rename(serialize = "@android:label"),
skip_serializing_if = "Option::is_none"
)]
pub label: Option<String>,
#[serde(
rename(serialize = "@android:launchMode"),
skip_serializing_if = "Option::is_none"
)]
pub launch_mode: Option<String>,
#[serde(rename(serialize = "@android:name"))]
#[serde(default = "default_activity_name")]
pub name: String,
#[serde(
rename(serialize = "@android:screenOrientation"),
skip_serializing_if = "Option::is_none"
)]
pub orientation: Option<String>,
#[serde(
rename(serialize = "@android:exported"),
skip_serializing_if = "Option::is_none"
)]
pub exported: Option<bool>,
#[serde(
rename(serialize = "@android:resizeableActivity"),
skip_serializing_if = "Option::is_none"
)]
pub resizeable_activity: Option<bool>,
#[serde(
rename(serialize = "@android:alwaysRetainTaskState"),
skip_serializing_if = "Option::is_none"
)]
pub always_retain_task_state: Option<bool>,
#[serde(rename(serialize = "meta-data"))]
#[serde(default)]
pub meta_data: Vec<MetaData>,
#[serde(rename(serialize = "intent-filter"))]
#[serde(default)]
pub intent_filter: Vec<IntentFilter>,
}
impl Default for Activity {
fn default() -> Self {
Self {
config_changes: default_config_changes(),
label: None,
launch_mode: None,
name: default_activity_name(),
orientation: None,
exported: None,
resizeable_activity: None,
always_retain_task_state: None,
meta_data: Default::default(),
intent_filter: Default::default(),
}
}
}
#[derive(Clone, Debug, Deserialize, Serialize, Default)]
pub struct Service {
#[serde(rename(serialize = "@android:name"))]
pub name: String,
#[serde(
rename(serialize = "@android:exported"),
skip_serializing_if = "Option::is_none"
)]
pub exported: Option<bool>,
#[serde(
rename(serialize = "@android:foregroundServiceType"),
skip_serializing_if = "Option::is_none"
)]
pub foreground_service_type: Option<String>,
#[serde(
rename(serialize = "@android:label"),
skip_serializing_if = "Option::is_none"
)]
pub label: Option<String>,
#[serde(
rename(serialize = "@android:icon"),
skip_serializing_if = "Option::is_none"
)]
pub icon: Option<String>,
#[serde(
rename(serialize = "@android:permission"),
skip_serializing_if = "Option::is_none"
)]
pub permission: Option<String>,
#[serde(
rename(serialize = "@android:process"),
skip_serializing_if = "Option::is_none"
)]
pub process: Option<String>,
#[serde(
rename(serialize = "@android:description"),
skip_serializing_if = "Option::is_none"
)]
pub description: Option<String>,
#[serde(
rename(serialize = "@android:directBootAware"),
skip_serializing_if = "Option::is_none"
)]
pub direct_boot_aware: Option<bool>,
#[serde(rename(serialize = "meta-data"))]
#[serde(default)]
pub meta_data: Vec<MetaData>,
#[serde(rename(serialize = "intent-filter"))]
#[serde(default)]
pub intent_filter: Vec<IntentFilter>,
}
fn deserialize_services<'de, D>(deserializer: D) -> Result<Vec<Service>, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
#[serde(untagged)]
enum OneOrMany<T> {
One(T),
Many(Vec<T>),
}
match OneOrMany::<Service>::deserialize(deserializer)? {
OneOrMany::One(service) => Ok(vec![service]),
OneOrMany::Many(services) => Ok(services),
}
}
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
pub struct IntentFilter {
#[serde(serialize_with = "serialize_actions")]
#[serde(rename(serialize = "action"))]
#[serde(default)]
pub actions: Vec<String>,
#[serde(serialize_with = "serialize_catergories")]
#[serde(rename(serialize = "category"))]
#[serde(default)]
pub categories: Vec<String>,
#[serde(default)]
pub data: Vec<IntentFilterData>,
}
fn serialize_actions<S>(actions: &[String], serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
use serde::ser::SerializeSeq;
#[derive(Serialize)]
struct Action {
#[serde(rename(serialize = "@android:name"))]
name: String,
}
let mut seq = serializer.serialize_seq(Some(actions.len()))?;
for action in actions {
seq.serialize_element(&Action {
name: action.clone(),
})?;
}
seq.end()
}
fn serialize_catergories<S>(categories: &[String], serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
use serde::ser::SerializeSeq;
#[derive(Serialize)]
struct Category {
#[serde(rename(serialize = "@android:name"))]
pub name: String,
}
let mut seq = serializer.serialize_seq(Some(categories.len()))?;
for category in categories {
seq.serialize_element(&Category {
name: category.clone(),
})?;
}
seq.end()
}
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
pub struct IntentFilterData {
#[serde(
rename(serialize = "@android:scheme"),
skip_serializing_if = "Option::is_none"
)]
pub scheme: Option<String>,
#[serde(
rename(serialize = "@android:host"),
skip_serializing_if = "Option::is_none"
)]
pub host: Option<String>,
#[serde(
rename(serialize = "@android:port"),
skip_serializing_if = "Option::is_none"
)]
pub port: Option<String>,
#[serde(
rename(serialize = "@android:path"),
skip_serializing_if = "Option::is_none"
)]
pub path: Option<String>,
#[serde(
rename(serialize = "@android:pathPattern"),
skip_serializing_if = "Option::is_none"
)]
pub path_pattern: Option<String>,
#[serde(
rename(serialize = "@android:pathPrefix"),
skip_serializing_if = "Option::is_none"
)]
pub path_prefix: Option<String>,
#[serde(
rename(serialize = "@android:mimeType"),
skip_serializing_if = "Option::is_none"
)]
pub mime_type: Option<String>,
}
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
pub struct MetaData {
#[serde(rename(serialize = "@android:name"))]
pub name: String,
#[serde(rename(serialize = "@android:value"))]
pub value: String,
}
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
pub struct Feature {
#[serde(
rename(serialize = "@android:name"),
skip_serializing_if = "Option::is_none"
)]
pub name: Option<String>,
#[serde(
rename(serialize = "@android:required"),
skip_serializing_if = "Option::is_none"
)]
pub required: Option<bool>,
#[serde(
rename(serialize = "@android:version"),
skip_serializing_if = "Option::is_none"
)]
pub version: Option<u32>,
#[serde(
rename(serialize = "@android:glEsVersion"),
skip_serializing_if = "Option::is_none"
)]
#[serde(serialize_with = "serialize_opengles_version")]
pub opengles_version: Option<(u8, u8)>,
}
fn serialize_opengles_version<S>(
version: &Option<(u8, u8)>,
serializer: S,
) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match version {
Some(version) => {
let opengles_version = format!("0x{:04}{:04}", version.0, version.1);
serializer.serialize_some(&opengles_version)
}
None => serializer.serialize_none(),
}
}
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
pub struct Permission {
#[serde(rename(serialize = "@android:name"))]
pub name: String,
#[serde(
rename(serialize = "@android:maxSdkVersion"),
skip_serializing_if = "Option::is_none"
)]
pub max_sdk_version: Option<u32>,
}
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
pub struct Package {
#[serde(rename(serialize = "@android:name"))]
pub name: String,
}
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
pub struct QueryProvider {
#[serde(rename(serialize = "@android:authorities"))]
pub authorities: String,
#[serde(rename(serialize = "@android:name"))]
pub name: String,
}
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
pub struct Queries {
#[serde(default)]
pub package: Vec<Package>,
#[serde(default)]
pub intent: Vec<IntentFilter>,
#[serde(default)]
pub provider: Vec<QueryProvider>,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct Sdk {
#[serde(
rename(serialize = "@android:minSdkVersion"),
skip_serializing_if = "Option::is_none"
)]
pub min_sdk_version: Option<u32>,
#[serde(
rename(serialize = "@android:targetSdkVersion"),
skip_serializing_if = "Option::is_none"
)]
pub target_sdk_version: Option<u32>,
#[serde(
rename(serialize = "@android:maxSdkVersion"),
skip_serializing_if = "Option::is_none"
)]
pub max_sdk_version: Option<u32>,
}
impl Default for Sdk {
fn default() -> Self {
Self {
min_sdk_version: Some(23),
target_sdk_version: None,
max_sdk_version: None,
}
}
}
fn default_namespace() -> String {
"http://schemas.android.com/apk/res/android".to_string()
}
fn default_activity_name() -> String {
"android.app.NativeActivity".to_string()
}
fn default_config_changes() -> Option<String> {
Some("orientation|keyboardHidden|screenSize".to_string())
}