slint-ui / slint

Slint is a declarative GUI toolkit to build native user interfaces for Rust, C++, or JavaScript apps.
https://slint.dev
Other
16.95k stars 568 forks source link

How to request permissions on Android with Rust ? #5839

Closed fredericvauchelles closed 1 month ago

fredericvauchelles commented 1 month ago

Hi,

I need access to the external storage to read image file for my application on android, thus I require the "READ_EXTERNAL_STORAGE" permission.

I already added in the $ROOT/manifest.yaml the permission:

android:
  manifest:
    package: org.fredericvauchelles.gesture_training
    uses_permission:
      - name: READ_EXTERNAL_STORAGE

But I need the AP to actually request that permission to the user, something like:

#[cfg(target_os = "android")]
#[no_mangle]
fn android_main(app: slint::android::AndroidApp) {
    // Initialize slint
    slint::android::init(app.clone()).unwrap();

    // ask permissions
    slint::spawn_local(async move {
       let user_reply = app.request_permissions(&[
          "READ_EXTERNAL_STORAGE"
       ].await.unwrap();
    }).unwrap();

    // Run the UI and the event loop
    start_app().unwrap()
}

Where the API would look like:

trait AndroidAppSupport {
    async fn request_permissions(&self, permission: &[&str]) -> Result<AskPermissionResult, Error>;
}

Do you have an alternative or a plan to have something similar ?

I tried to use the JNI to do so:

trait AndroidSupport {
    fn request_file_permissions(&self) -> anyhow::Result<()>;

    fn vm(&self) -> anyhow::Result<JavaVM>;
    fn activity(&self) -> JObject;
}

impl AndroidSupport for AndroidApp {
    fn request_file_permissions(&self) -> anyhow::Result<()> {
        let vm = self.vm()?;
        let mut env = vm.attach_current_thread()?;
        let activity = self.activity();

        let permission_class = env.find_class("android/Manifest$permission")?;

        let read_permission = env.get_static_field(&permission_class, "READ_EXTERNAL_STORAGE", "Ljava/lang/String;")?;
        let write_permission = env.get_static_field(&permission_class, "WRITE_EXTERNAL_STORAGE", "Ljava/lang/String;")?;

        {
            let string_class = env.find_class("java/lang/String")?;
            let default_string = env.new_string("")?;
            let permissions_array = env.new_object_array(2, string_class, default_string)?;
            env.set_object_array_element(&permissions_array, 0, read_permission.l()?)?;
            env.set_object_array_element(&permissions_array, 1, write_permission.l()?)?;

            env.call_method(
                activity,
                "requestPermissions",
                "([Ljava/lang/String;II)V",
                &[(&permissions_array).into(), JValue::from(0), JValue::from(0)]
            )?;
        }
        Ok(())
    }

    fn vm(&self) -> anyhow::Result<JavaVM> {
        {
            unsafe {
                JavaVM::from_raw(self.vm_as_ptr().cast())
            }.map_err(anyhow::Error::from)
        }
    }

    fn activity(&self) -> JObject {
        unsafe { JObject::from_raw(self.activity_as_ptr() as jni::sys::jobject) }
    }
}

But, it fails to find the method Landroid/app/NativeActivity;requestPermission([Ljava/lang/String;I)V, which should be available.

ogoffart commented 1 month ago

This is outside of the scope of Slint.

Your code look alright, and I don't see anything wrong with it. But I'm no expert in the topic.

There was actually already a discussion on the subject. https://github.com/slint-ui/slint/discussions/5692

But I'm closing this issue in favour of this discussion.