android-permissions 0.1.0

Android permissions
use std::env;
use std::path::{Path, PathBuf};
use std::process::Command;
fn main() {
    let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
    let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
    let java_source_dir = manifest_dir.join("java");

    println!("cargo:rerun-if-changed=java");

    let android_sdk_root = env::var("ANDROID_SDK_ROOT")
        .or_else(|_| env::var("ANDROID_HOME"))
        .expect("ANDROID_SDK_ROOT or ANDROID_HOME environment variable must be set.");
    let sdk_root_path = Path::new(&android_sdk_root);

    let build_tools_dir = sdk_root_path.join("build-tools");
    let latest_build_tools =
        find_latest_sdk_dir(&build_tools_dir).expect("No build-tools version found in SDK.");
    println!(
        "cargo:warning=Using Build Tools version: {}",
        latest_build_tools.file_name().unwrap().to_str().unwrap()
    );

    let d8_executable = if cfg!(target_os = "windows") {
        "d8.bat"
    } else {
        "d8"
    };
    let d8_path = latest_build_tools.join(d8_executable);

    let platforms_dir = sdk_root_path.join("platforms");
    let latest_platform =
        find_latest_sdk_dir(&platforms_dir).expect("No platform version found in SDK.");
    println!(
        "cargo:warning=Using Platform version: {}",
        latest_platform.file_name().unwrap().to_str().unwrap()
    );
    let android_jar = latest_platform.join("android.jar");

    if !android_jar.exists() {
        panic!("android.jar not found in {:?}.", latest_platform);
    }

    let java_classes_dir = out_dir.join("java_classes");
    std::fs::create_dir_all(&java_classes_dir).unwrap();

    let java_files: Vec<PathBuf> = walkdir::WalkDir::new(&java_source_dir)
        .into_iter()
        .filter_map(Result::ok)
        .filter(|e| e.path().extension().map_or(false, |ext| ext == "java"))
        .map(|e| e.into_path())
        .collect();

    if java_files.is_empty() {
        return;
    }

    let javac_status = Command::new("javac")
        .args(["-d", java_classes_dir.to_str().unwrap()])
        .args(["-cp", android_jar.to_str().unwrap()])
        .args(["-encoding", "UTF-8"])
        .args(["-sourcepath", java_source_dir.to_str().unwrap()])
        .args(&java_files)
        .status()
        .expect("Failed to execute javac.");

    if !javac_status.success() {
        panic!("Java compilation failed.");
    }

    let class_files: Vec<PathBuf> = walkdir::WalkDir::new(&java_classes_dir)
        .into_iter()
        .filter_map(Result::ok)
        .filter(|e| e.path().extension().map_or(false, |ext| ext == "class"))
        .map(|e| e.into_path())
        .collect();

    if class_files.is_empty() {
        return;
    }
    println!("class_files = {:?}", class_files);

    let final_dex_path = out_dir.join("permission_manager.dex");
    println!("final_dex_path = {:?}", final_dex_path);
    let temp_dex_dir = out_dir.join("temp_dex");
    std::fs::create_dir_all(&temp_dex_dir).unwrap();

    let d8_status = Command::new(&d8_path)
        .arg("--output")
        .arg(&temp_dex_dir)
        .arg("--lib")
        .arg(&android_jar)
        .arg("--min-api")
        .arg("21")
        .args(&class_files)
        .status()
        .expect("Failed to execute d8. Please ensure Android build tools are installed.");

    if !d8_status.success() {
        panic!("d8 dex conversion failed.");
    }
    let generated_dex = temp_dex_dir.join("classes.dex");
    if generated_dex.exists() {
        std::fs::rename(&generated_dex, &final_dex_path)
            .expect("Unable to move classes.dex to final location");
        println!(
            "cargo:warning=DEX file successfully generated and moved to: {:?}",
            final_dex_path
        );
    } else {
        panic!("d8 executed successfully, but expected classes.dex file was not found.");
    }
}

fn find_latest_sdk_dir(parent_dir: &Path) -> Option<PathBuf> {
    if !parent_dir.exists() {
        return None;
    }

    let mut latest_version: Option<(Vec<u32>, PathBuf)> = None;

    for entry in std::fs::read_dir(parent_dir).ok()? {
        let entry = entry.ok()?;
        let path = entry.path();
        if path.is_dir() {
            let version_str = entry.file_name().to_string_lossy().to_string();
            let version_parts: Vec<u32> = version_str
                .split('.')
                .map(|s| s.replace("android-", "").parse().unwrap_or(0))
                .collect();

            if let Some((ref latest_parts, _)) = latest_version {
                if version_parts > *latest_parts {
                    latest_version = Some((version_parts, path));
                }
            } else {
                latest_version = Some((version_parts, path));
            }
        }
    }
    latest_version.map(|(_, path)| path)
}