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 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;