81 lines
2.8 KiB
Rust
81 lines
2.8 KiB
Rust
use crate::platform::OpenUrl;
|
|
use android_view::{
|
|
View,
|
|
jni::{JNIEnv, objects::JValue},
|
|
};
|
|
|
|
use super::view::HasAndroidUiState;
|
|
|
|
/// Android's URL opener. Like `FocusHost::focus_gained`'s keyboard, the
|
|
/// real work is a JNI call and this runs deep inside the sensor dispatch
|
|
/// with no `CallbackCtx` in reach -- so it raises a flag that
|
|
/// `IrisViewPeer::after_input` consumes, exactly as
|
|
/// `pending_show_keyboard` does.
|
|
///
|
|
/// Last request wins: two links cannot be tapped in one frame, and a URL
|
|
/// left queued from a frame that somehow never reached `after_input`
|
|
/// would open at some unrelated later tap, which is worse than dropping
|
|
/// it.
|
|
impl<T: HasAndroidUiState> OpenUrl for T {
|
|
fn open_url(&mut self, url: &str) {
|
|
self.android_state_mut().pending_open_url = Some(url.to_string());
|
|
}
|
|
}
|
|
|
|
/// `FLAG_ACTIVITY_NEW_TASK` because the context here is the view's, which
|
|
/// may be an application context rather than the activity's -- Android
|
|
/// throws `AndroidRuntimeException` for a non-activity context without it,
|
|
/// and it is harmless when the context *is* an activity's.
|
|
pub(super) fn open_url<'local>(env: &mut JNIEnv<'local>, view: &View<'local>, url: &str) {
|
|
match try_open_url(env, view, url) {
|
|
Ok(()) => {}
|
|
Err(e) => {
|
|
// A pending Java exception makes every later JNI call fail in
|
|
// ways nowhere near here, so it is cleared at the boundary.
|
|
let _ = env.exception_clear();
|
|
log::warn!("could not open {url}: {e}");
|
|
}
|
|
}
|
|
}
|
|
|
|
fn try_open_url<'local>(
|
|
env: &mut JNIEnv<'local>,
|
|
view: &View<'local>,
|
|
url: &str,
|
|
) -> Result<(), android_view::jni::errors::Error> {
|
|
let context = env
|
|
.call_method(&view.0, "getContext", "()Landroid/content/Context;", &[])?
|
|
.l()?;
|
|
let jurl = env.new_string(url)?;
|
|
let uri = env.call_static_method(
|
|
"android/net/Uri",
|
|
"parse",
|
|
"(Ljava/lang/String;)Landroid/net/Uri;",
|
|
&[JValue::Object(jurl.as_ref())],
|
|
)?;
|
|
let action = env.new_string("android.intent.action.VIEW")?;
|
|
let intent = env.new_object(
|
|
"android/content/Intent",
|
|
"(Ljava/lang/String;Landroid/net/Uri;)V",
|
|
&[JValue::Object(action.as_ref()), JValue::Object(&uri.l()?)],
|
|
)?;
|
|
env.call_method(
|
|
&intent,
|
|
"addFlags",
|
|
"(I)Landroid/content/Intent;",
|
|
&[JValue::Int(FLAG_ACTIVITY_NEW_TASK)],
|
|
)?;
|
|
env.call_method(
|
|
&context,
|
|
"startActivity",
|
|
"(Landroid/content/Intent;)V",
|
|
&[JValue::Object(&intent)],
|
|
)?;
|
|
Ok(())
|
|
}
|
|
|
|
/// `android.content.Intent.FLAG_ACTIVITY_NEW_TASK`. A constant rather than
|
|
/// a static-field read: it is part of the platform's stable ABI and
|
|
/// reading it costs two more JNI calls that can each fail.
|
|
const FLAG_ACTIVITY_NEW_TASK: i32 = 0x1000_0000;
|