Skip to content

Conversation

@wuwbobo2021
Copy link

Thank you for checking all these changes! Further changes are probably needed.

Multiple activities & dex insertion test based on https://github.com/wuwbobo2021/jni-min-helper/tree/perm:

Click to expand

Cargo.toml:

[package]
name = "android-simple-test"
version = "0.1.0"
edition = "2021"
publish = false

[dependencies]
log = "0.4"
jni-min-helper = { path = "..", features = ["futures"] }
android-activity = { version = "0.6", features = ["native-activity"] }
android_logger = "0.14"

[build-dependencies]
android-build = "0.1.2"

[lib]
name = "android_simple_test"
crate-type = ["cdylib"]
path = "main.rs"

[package.metadata.android]
# android_manifest_file = "AndroidManifest.xml"
package = "com.example.android_simple_test"
build_targets = [ "aarch64-linux-android" ]

[package.metadata.android.sdk]
min_sdk_version = 16
target_sdk_version = 30

[[package.metadata.android.uses_permission]]
name = "android.permission.RECORD_AUDIO"

[[package.metadata.android.uses_permission]]
name = "android.permission.ACCESS_COARSE_LOCATION"

[[package.metadata.android.application.activity]]

[[package.metadata.android.application.activity]]
"android:name" = "rust.jniminhelper.PermActivity"

build.rs:

use std::{env, fs, path::PathBuf};

use android_build::{Dexer, JavaBuild};

fn main() {
    let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap();
    let src_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()).join("java");
    let out_dir = env::var("CARGO_MANIFEST_DIR")
        .map(PathBuf::from)
        .unwrap()
        .join("target")
        .join(env::var("PROFILE").unwrap());

    // TODO: fix the possible unicode output problem.
    // Safety: this sets the variable for the current single-thread process
    // of the executable compiled from this build script.
    unsafe {
        env::set_var("JAVA_TOOL_OPTIONS", "-Duser.language=en");
    }

    if target_os == "android" {
        let sources = [
            src_dir.join("PermActivity.java"),
        ];
        let android_jar = android_build::android_jar(None);

        let out_cls_dir = out_dir.join("classes");
        if out_cls_dir.try_exists().unwrap() {
            fs::remove_dir_all(&out_cls_dir).unwrap();
        }
        fs::create_dir(&out_cls_dir).unwrap();

        let mut err_string = None;
        if android_jar.is_none() {
            err_string.replace("Failed to find android.jar.".to_string());
        } else if let Err(s) =
            compile_java_source(sources, [android_jar.clone().unwrap()], out_cls_dir.clone())
        {
            err_string.replace(s);
        } else if let Err(s) = build_dex_file(out_cls_dir.clone(), android_jar, [], out_dir.clone())
        {
            err_string.replace(s);
        };

        if let Some(s) = err_string {
            for line in s.lines() {
                println!("cargo::warning={line}");
            }
            panic!("Unable to build the dex file");
        }
    }
}

fn compile_java_source(
    source_paths: impl IntoIterator<Item = PathBuf>,
    class_paths: impl IntoIterator<Item = PathBuf>,
    output_dir: PathBuf,
) -> Result<(), String> {
    let mut java_build = JavaBuild::new();

    for java_src in source_paths {
        println!("cargo:rerun-if-changed={}", java_src.to_string_lossy());
        java_build.file(java_src);
    }

    for class_path in class_paths {
        println!("cargo:rerun-if-changed={}", class_path.to_string_lossy());
        java_build.class_path(class_path);
    }

    java_build.java_source_version(8).java_target_version(8);
    java_build.classes_out_dir(output_dir);

    // Execute the command
    let result = java_build
        .command()
        .map_err(|e| e.to_string())?
        .output()
        .map_err(|e| format!("Failed to execute javac: {e:?}"))?;
    if result.status.success() {
        Ok(())
    } else {
        Err(format!(
            "Java compilation failed: {}",
            String::from_utf8_lossy(&result.stderr)
        ))
    }
}

fn build_dex_file(
    compiled_classes_path: PathBuf,
    android_jar: Option<PathBuf>,
    jar_dependencies: impl IntoIterator<Item = PathBuf>,
    output_dir: PathBuf,
) -> Result<(), String> {
    let mut dexer = Dexer::new();
    if let Some(android_jar) = android_jar {
        dexer.android_jar(&android_jar);
    }
    let dependencies: Vec<_> = jar_dependencies.into_iter().collect();
    for dependency in dependencies.iter() {
        println!("cargo:rerun-if-changed={}", dependency.to_string_lossy());
        dexer.class_path(dependency);
    }
    dexer
        .android_min_api(20)
        .release(env::var("PROFILE").as_ref().map(|s| s.as_str()) == Ok("release"))
        .class_path(&compiled_classes_path)
        .no_desugaring(true)
        .out_dir(output_dir)
        .files(dependencies.iter())
        .collect_classes(&compiled_classes_path)
        .map_err(|e| e.to_string())?;

    // Execute the command
    let result = dexer
        .run()
        .map_err(|e| format!("Failed to execute d8.jar: {e:?}"))?;
    if result.success() {
        Ok(())
    } else {
        Err(format!("Dexer invocation failed: {result}"))
    }
}

main.rs:

use android_activity::{AndroidApp, MainEvent, PollEvent};
use jni_min_helper::*;
use std::time::Duration;
use log::info;

#[no_mangle]
fn android_main(app: AndroidApp) {
    android_logger::init_once(
        android_logger::Config::default()
            .with_max_level(log::LevelFilter::Info)
            .with_tag(android_app_name().as_bytes()),
    );

    info!("spawning...");
    std::thread::spawn(background_loop);

    let mut on_destroy = false;
    loop {
        app.poll_events(
            Some(Duration::from_secs(1)), // timeout
            |event| match event {
                PollEvent::Main(MainEvent::Start) => {
                    info!("Main Start.");
                }
                PollEvent::Main(MainEvent::Resume { loader: _, .. }) => {
                    info!("Main Resume.");
                }
                PollEvent::Main(MainEvent::Pause) => {
                    info!("Main Pause.");
                }
                PollEvent::Main(MainEvent::Stop) => {
                    info!("Main Stop.");
                }
                PollEvent::Main(MainEvent::Destroy) => {
                    info!("Main Destroy.");
                    on_destroy = true;
                }
                _ => (),
            },
        );
        if on_destroy {
            return;
        }
    }
}

fn background_loop() {
    // JniClassLoader::helper_loader().unwrap().replace_app_loader().unwrap();
    // info!("lodaer replaced...");
    let req = PermissionRequest::request("Test", [
        "android.permission.RECORD_AUDIO",
        "android.permission.ACCESS_COARSE_LOCATION",
    ])
    .unwrap();
    let Some(req) = req else {
        info!("permission request is not needed.");
        return;
    };
    info!("requesting permissions...");
    let result = req.wait();
    info!("{result:#?}");
}

dependabot bot and others added 4 commits July 6, 2025 00:31
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 3 to 4.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](actions/upload-artifact@v3...v4)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <[email protected]>
Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 3 to 4.
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](actions/download-artifact@v3...v4)

---
updated-dependencies:
- dependency-name: actions/download-artifact
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <[email protected]>
Google Play will require this starting August 31 2025.  The default of
`23` for `min_sdk_version` is retained, allowing apps to be installed
all the way back to Android 6.0.

https://developer.android.com/google/play/requirements/target-sdk
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants