From 367b8bdb5c1094a99f6be19f6eff455edb21a2d2 Mon Sep 17 00:00:00 2001 From: Logan McNaughton <848146+loganmc10@users.noreply.github.com> Date: Thu, 21 May 2026 22:06:55 +0200 Subject: [PATCH] Handle intent results (#932) * Handle intent results * more * more * more * more --- .../github/gopher64/gopher64/SlintActivity.kt | 5 + src/ui/android.rs | 139 +++++++++++++++++- 2 files changed, 138 insertions(+), 6 deletions(-) diff --git a/android-project/app/src/main/java/io/github/gopher64/gopher64/SlintActivity.kt b/android-project/app/src/main/java/io/github/gopher64/gopher64/SlintActivity.kt index 5889089..dcd2891 100644 --- a/android-project/app/src/main/java/io/github/gopher64/gopher64/SlintActivity.kt +++ b/android-project/app/src/main/java/io/github/gopher64/gopher64/SlintActivity.kt @@ -4,6 +4,11 @@ import android.app.NativeActivity import android.content.Intent class SlintActivity : NativeActivity() { + companion object { + init { + System.loadLibrary("gopher64") + } + } private external fun nativeOnActivityResult(requestCode: Int, resultCode: Int, data: Intent?) override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { diff --git a/src/ui/android.rs b/src/ui/android.rs index 58a525f..3524cf7 100644 --- a/src/ui/android.rs +++ b/src/ui/android.rs @@ -1,12 +1,27 @@ -use jni::objects::JString; +use jni::objects::{JClass, JObject, JString}; use jni::refs::Global; -use jni::{Env, JavaVM, bind_java_type}; +use jni::sys::jint; +use jni::{Env, EnvUnowned, JavaVM, bind_java_type}; use crate::ui; +pub const REQUEST_SELECT_ROM: jint = 1; + pub static ANDROID_APP: std::sync::Mutex> = std::sync::Mutex::new(None); +pub static SELECT_ROM_TX: std::sync::Mutex< + Option>>, +> = std::sync::Mutex::new(None); + +bind_java_type! { + DocumentsContract => "android.provider.DocumentsContract", + fields { + #[allow(non_snake_case)] + static EXTRA_INITIAL_URI: JString, + }, +} + bind_java_type! { AndroidActivity => "android.app.Activity", type_map = { @@ -14,6 +29,11 @@ bind_java_type! { }, methods { fn start_activity(intent: AndroidIntent) -> (), + fn start_activity_for_result(intent: AndroidIntent, request_code: jint) -> (), + }, + fields { + #[allow(non_snake_case)] + static RESULT_OK: jint, }, } @@ -26,6 +46,10 @@ bind_java_type! { #[allow(non_snake_case)] static ACTION_VIEW: JString, #[allow(non_snake_case)] + static ACTION_OPEN_DOCUMENT: JString, + #[allow(non_snake_case)] + static CATEGORY_OPENABLE: JString, + #[allow(non_snake_case)] static FLAG_ACTIVITY_NEW_TASK: jint, }, constructors { @@ -33,7 +57,11 @@ bind_java_type! { }, methods { fn set_data(uri: AndroidUri) -> AndroidIntent, + fn set_type(r#type: JString) -> AndroidIntent, + fn add_category(category: JString) -> AndroidIntent, fn add_flags(flags: jint) -> AndroidIntent, + fn get_data() -> AndroidUri, + fn put_extra(extra: JString, value: JString) -> AndroidIntent, }, } @@ -41,6 +69,7 @@ bind_java_type! { AndroidUri => "android.net.Uri", methods { static fn parse(uri_string: JString) -> AndroidUri, + fn to_string() -> JString, }, } @@ -192,16 +221,61 @@ fn open_uri_on_jvm(env: &mut Env<'_>, path: &str) -> jni::errors::Result<()> { } } -pub async fn select_rom(_rom_dir: slint::SharedString) -> Option { - None +pub async fn select_rom(rom_dir: slint::SharedString) -> Option { + let vm = if let Ok(app) = ANDROID_APP.lock() + && let Some(app) = app.as_ref() + { + unsafe { JavaVM::from_raw(app.vm_as_ptr().cast()) } + } else { + eprintln!("Android app not initialized"); + return None; + }; + if let Err(err) = vm.attach_current_thread(|env| select_rom_on_jvm(env, rom_dir.to_string())) { + eprintln!("JNI error while opening URI: {err:?}"); + return None; + } + let (tx, rx) = tokio::sync::oneshot::channel::>(); + if let Ok(mut tx_lock) = SELECT_ROM_TX.lock() { + tx_lock.replace(tx); + } else { + eprintln!("Error locking SELECT_ROM_TX"); + return None; + } + rx.await.unwrap_or(None) } pub async fn select_gb_rom(_player: i32) -> Option { - None + select_rom(slint::SharedString::new()).await } pub async fn select_gb_ram(_player: i32) -> Option { - None + select_rom(slint::SharedString::new()).await +} + +fn select_rom_on_jvm(env: &mut Env<'_>, rom_dir: String) -> jni::errors::Result<()> { + if let Ok(app) = ANDROID_APP.lock() + && let Some(app) = app.as_ref() + { + let raw_activity_global = app.activity_as_ptr() as jni::sys::jobject; + let activity = unsafe { env.as_cast_raw::>(&raw_activity_global)? }; + + let action = AndroidIntent::ACTION_OPEN_DOCUMENT(env)?; + let category = AndroidIntent::CATEGORY_OPENABLE(env)?; + let mime_type = JString::from_str(env, "*/*")?; + let mut intent = AndroidIntent::new(env, &action)? + .set_type(env, &mime_type)? + .add_category(env, &category)?; + if !rom_dir.is_empty() { + let start_dir = JString::from_str(env, rom_dir)?; + let extra_initial_uri = DocumentsContract::EXTRA_INITIAL_URI(env)?; + intent = intent.put_extra(env, &extra_initial_uri, &start_dir)?; + } + + activity.start_activity_for_result(env, &intent, REQUEST_SELECT_ROM)?; + Ok(()) + } else { + Err(jni::errors::Error::UninitializedJavaVM) + } } pub fn get_dirs() -> ui::Dirs { @@ -217,3 +291,56 @@ pub fn get_dirs() -> ui::Dirs { panic!("Android app not initialized"); } } + +#[unsafe(no_mangle)] +pub extern "system" fn Java_io_github_gopher64_gopher64_SlintActivity_nativeOnActivityResult< + 'caller, +>( + mut unowned_env: EnvUnowned<'caller>, + _class: JClass<'caller>, + request_code: jint, + result_code: jint, + intent_data: JObject<'caller>, +) { + let outcome = unowned_env.with_env(|env| -> Result<_, jni::errors::Error> { + if result_code != AndroidActivity::RESULT_OK(env)? { + if let Ok(mut tx_lock) = SELECT_ROM_TX.lock() + && let Some(tx) = tx_lock.take() + { + let _ = tx.send(None); + } + return Ok(()); // user cancelled + } + if intent_data.is_null() { + if let Ok(mut tx_lock) = SELECT_ROM_TX.lock() + && let Some(tx) = tx_lock.take() + { + let _ = tx.send(None); + } + return Ok(()); + } + let result_intent = unsafe { env.as_cast_raw::(&intent_data)? }; + match request_code { + REQUEST_SELECT_ROM => { + let uri = result_intent.as_ref().get_data(env)?; + if uri.is_null() { + if let Ok(mut tx_lock) = SELECT_ROM_TX.lock() + && let Some(tx) = tx_lock.take() + { + let _ = tx.send(None); + } + return Ok(()); + } + let path = uri.to_string(env)?; + if let Ok(mut tx_lock) = SELECT_ROM_TX.lock() + && let Some(tx) = tx_lock.take() + { + let _ = tx.send(Some(std::path::PathBuf::from(path.to_string()))); + } + } + _ => {} + } + Ok(()) + }); + outcome.resolve::() +}