use crate::{Browser, BrowserOptions, Error, ErrorKind, Result, TargetType};
use jni::objects::{JObject, JValue};
use std::process::{Command, Stdio};
pub(super) fn open_browser_internal(
browser: Browser,
target: &TargetType,
options: &BrowserOptions,
) -> Result<()> {
let url = target.get_http_url()?;
match browser {
Browser::Default => open_browser_default(url, options),
_ => Err(Error::new(
ErrorKind::NotFound,
"only default browser supported",
)),
}
}
fn open_browser_default(url: &str, options: &BrowserOptions) -> Result<()> {
if options.dry_run {
return Ok(());
}
if try_for_termux(url, options).is_ok() {
return Ok(());
}
let ctx = ndk_context::android_context();
let vm = unsafe { jni::JavaVM::from_raw(ctx.vm() as _) }.map_err(|_| {
Error::new(
ErrorKind::NotFound,
"Expected to find JVM via ndk_context crate",
)
})?;
let activity = unsafe { jni::objects::JObject::from_raw(ctx.context() as _) };
let mut env = vm
.attach_current_thread()
.map_err(|_| Error::new(ErrorKind::Other, "Failed to attach current thread"))?;
let intent_class = env
.find_class("android/content/Intent")
.map_err(|_| Error::new(ErrorKind::NotFound, "Failed to find Intent class"))?;
let action_view = env
.get_static_field(&intent_class, "ACTION_VIEW", "Ljava/lang/String;")
.map_err(|_| Error::new(ErrorKind::NotFound, "Failed to get intent.ACTION_VIEW"))?;
let uri_class = env
.find_class("android/net/Uri")
.map_err(|_| Error::new(ErrorKind::NotFound, "Failed to find Uri class"))?;
let url = env
.new_string(url)
.map_err(|_| Error::new(ErrorKind::Other, "Failed to create JNI string"))?;
let uri = env
.call_static_method(
&uri_class,
"parse",
"(Ljava/lang/String;)Landroid/net/Uri;",
&[JValue::Object(&JObject::from(url))],
)
.map_err(|_| Error::new(ErrorKind::Other, "Failed to parse JNI Uri"))?;
let intent = env
.alloc_object(&intent_class)
.map_err(|_| Error::new(ErrorKind::Other, "Failed to allocate intent"))?;
env.call_method(
&intent,
"<init>",
"(Ljava/lang/String;Landroid/net/Uri;)V",
&[action_view.borrow(), uri.borrow()],
)
.map_err(|_| Error::new(ErrorKind::Other, "Failed to initialize intent"))?;
env.call_method(
&activity,
"startActivity",
"(Landroid/content/Intent;)V",
&[JValue::Object(&intent)],
)
.map_err(|_| Error::new(ErrorKind::Other, "Failed to start activity"))?;
Ok(())
}
fn try_for_termux(url: &str, options: &BrowserOptions) -> Result<()> {
use std::env;
if env::var("TERMUX_VERSION").is_ok() {
if options.dry_run {
return Ok(());
}
let mut cmd = Command::new("termux-open");
cmd.arg(url);
if options.suppress_output {
cmd.stdin(Stdio::null())
.stdout(Stdio::null())
.stderr(Stdio::null());
}
cmd.status().and_then(|status| {
if status.success() {
Ok(())
} else {
Err(Error::new(
ErrorKind::Other,
"command present but exited unsuccessfully",
))
}
})
} else {
Err(Error::new(ErrorKind::Other, "Not a termux environment"))
}
}