Handle intent results (#932)

* Handle intent results

* more

* more

* more

* more
This commit is contained in:
Logan McNaughton
2026-05-21 22:06:55 +02:00
committed by GitHub
parent b865c88d99
commit 367b8bdb5c
2 changed files with 138 additions and 6 deletions
@@ -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?) {
+133 -6
View File
@@ -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<Option<slint::android::AndroidApp>> =
std::sync::Mutex::new(None);
pub static SELECT_ROM_TX: std::sync::Mutex<
Option<tokio::sync::oneshot::Sender<Option<std::path::PathBuf>>>,
> = 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<std::path::PathBuf> {
None
pub async fn select_rom(rom_dir: slint::SharedString) -> Option<std::path::PathBuf> {
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::<Option<std::path::PathBuf>>();
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<std::path::PathBuf> {
None
select_rom(slint::SharedString::new()).await
}
pub async fn select_gb_ram(_player: i32) -> Option<std::path::PathBuf> {
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::<Global<AndroidActivity>>(&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::<AndroidIntent>(&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::<jni::errors::ThrowRuntimeExAndDefault>()
}