#![allow(dead_code)]
use std::path::{Path, PathBuf};
use std::process::Command;
use apk::sign::SigningIdentity;
pub const NATIVE_LIBRARY: &[u8] = include_bytes!("../fixtures/native/libpostcard.so");
pub const PNG: &[u8] = &[
0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00,
0x01, 0x00, 0x00, 0x00, 0x01, 0x08, 0x06, 0x00, 0x00, 0x00, 0x1f, 0x15, 0xc4, 0x89, 0x00, 0x00, 0x00, 0x0d, 0x49,
0x44, 0x41, 0x54, 0x78, 0x9c, 0x63, 0x60, 0x60, 0x60, 0xf8, 0x0f, 0x00, 0x01, 0x04, 0x01, 0x00, 0x5f, 0xe5, 0xc3,
0x4b, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82,
];
pub fn identity(name: &str) -> SigningIdentity {
SigningIdentity::from_pem_file(fixture(&format!("keys/{name}.pem"))).expect("test identity")
}
pub fn fixture(relative: &str) -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures").join(relative)
}
fn oracle(found: Option<PathBuf>, variable: &str) -> Option<PathBuf> {
let required = std::env::var_os("APK_RS_REQUIRE_ORACLES").is_some_and(|value| value != "0");
assert!(
found.is_some() || !required,
"APK_RS_REQUIRE_ORACLES is set, but neither {variable} nor ANDROID_HOME locates it"
);
found
}
fn from_sdk(directory: &str, file: &str) -> Option<PathBuf> {
let sdk = std::env::var_os("ANDROID_HOME").or_else(|| std::env::var_os("ANDROID_SDK_ROOT"))?;
newest_sdk_entry(&Path::new(&sdk).join(directory), file)
}
pub fn newest_sdk_entry(directory: &Path, file: &str) -> Option<PathBuf> {
let mut versions: Vec<_> = std::fs::read_dir(directory).ok()?.flatten().map(|e| e.path()).collect();
versions.sort_by_cached_key(|path| {
let name = path.file_name().and_then(|s| s.to_str()).unwrap_or_default();
let name = name.strip_prefix("android-").unwrap_or(name);
let (version, preview) = name.split_once('-').unwrap_or((name, ""));
let numbers = |text: &str| {
text.split(|c: char| !c.is_ascii_digit()).filter_map(|s| s.parse::<u32>().ok()).collect::<Vec<_>>()
};
(numbers(version), preview.is_empty(), numbers(preview))
});
versions.into_iter().rev().map(|dir| dir.join(file)).find(|path| path.is_file())
}
pub fn tool(name: &str) -> Option<PathBuf> {
let variable = name.to_ascii_uppercase();
let file = if cfg!(windows) {
format!("{name}.{}", if name == "apksigner" { "bat" } else { "exe" })
} else {
name.to_owned()
};
let found = std::env::var_os(&variable).map(PathBuf::from).or_else(|| from_sdk("build-tools", &file));
oracle(found, &variable)
}
pub struct Run {
pub success: bool,
pub stdout: String,
pub stderr: String,
}
impl Run {
pub fn expect_success(self, what: &str) -> Self {
assert!(self.success, "{what} failed:\n{}\n{}", self.stdout, self.stderr);
self
}
}
fn run(tool: &Path, args: &[&str], apk: &Path) -> Run {
let output = Command::new(tool).args(args).arg(apk).output().expect("run SDK tool");
Run {
success: output.status.success(),
stdout: String::from_utf8_lossy(&output.stdout).into_owned(),
stderr: String::from_utf8_lossy(&output.stderr).into_owned(),
}
}
pub fn aapt2(args: &[&str], apk: &Path) -> Option<Run> {
Some(run(&tool("aapt2")?, args, apk))
}
pub fn apksigner_verify(apk: &Path, min_sdk: u32) -> Option<Run> {
let min_sdk = min_sdk.to_string();
Some(run(&tool("apksigner")?, &["verify", "--verbose", "--min-sdk-version", &min_sdk], apk))
}
pub fn apksigner_sign(apk: &Path, identity: &SigningIdentity) -> Option<Run> {
let apksigner = tool("apksigner")?;
let dir = tempfile::tempdir().expect("temporary directory");
let (key, certificate) = (dir.path().join("key.pk8"), dir.path().join("certificate.der"));
std::fs::write(&key, identity.key().to_pkcs8_der().expect("encodable key")).expect("write key");
std::fs::write(&certificate, identity.certificate().der()).expect("write certificate");
let (key, certificate) = (key.to_str().expect("UTF-8 path"), certificate.to_str().expect("UTF-8 path"));
let args = ["sign", "--key", key, "--cert", certificate, "--v4-signing-enabled", "false"];
Some(run(&apksigner, &args, apk))
}
pub fn android_jar() -> Option<PathBuf> {
let found = std::env::var_os("ANDROID_JAR").map(PathBuf::from).or_else(|| from_sdk("platforms", "android.jar"));
oracle(found, "ANDROID_JAR")
}