use jni::objects::{JClass, JObject, JString};
use jni::strings::JNIString;
use jni::sys::{jboolean, jint, jlong};
use jni::{
Env, EnvUnowned, JavaVM,
errors::{LogErrorAndDefault, ThrowRuntimeExAndDefault},
jni_sig, jni_str,
};
use lingxia_messaging::invoke_callback;
use lingxia_platform::CachedClass;
use log::{error, info, warn};
use lxapp::{
AppServiceEvent, AppServiceEventArgs, AppServiceEventReason, AppServiceEventSource,
LxAppDelegate, LxAppUiEventType, OrientationConfig, PageOrientation,
};
fn parse_color_to_i32(color_str: &str, default_color: i32) -> i32 {
if color_str.eq_ignore_ascii_case("transparent") {
return 0x00000000;
}
if color_str.starts_with('#')
&& color_str.len() == 7
&& let Ok(rgb) = i32::from_str_radix(&color_str[1..], 16)
{
return (0xFF000000u32 as i32) | rgb; }
default_color
}
fn init_cached_java_class(env: &mut Env<'_>, class: CachedClass) {
match env.find_class(JNIString::new(class.class_path())) {
Ok(local_class) => match env.new_global_ref(local_class) {
Ok(global_class) => lingxia_platform::init_cached_class(class, global_class),
Err(e) => warn!(
"Failed to create global ref for cached class {}: {:?}",
class.class_path(),
e
),
},
Err(e) => {
let _ = env.exception_clear();
warn!(
"Failed to find cached class {} (will retry later): {:?}",
class.class_path(),
e
);
}
}
}
fn init_cached_java_classes(env: &mut Env<'_>) {
let classes = [
CachedClass::LxApp,
CachedClass::PreviewMediaPayload,
CachedClass::LxAppMedia,
CachedClass::LxAppDevice,
CachedClass::LxAppLocation,
CachedClass::LxAppPopup,
CachedClass::LxAppToast,
CachedClass::LxAppModal,
CachedClass::LxAppActionSheet,
CachedClass::LxAppPicker,
CachedClass::LxAppDocument,
CachedClass::ComponentRouter,
CachedClass::LxAppPullToRefresh,
CachedClass::UpdateManager,
CachedClass::LxAppCapsule,
CachedClass::LxAppWifi,
CachedClass::LxAppNetwork,
];
for class in classes {
init_cached_java_class(env, class);
}
}
#[unsafe(no_mangle)]
#[allow(improper_ctypes_definitions)]
pub extern "system" fn JNI_OnLoad(vm: JavaVM, _: *mut std::os::raw::c_void) -> jint {
crate::logging::init();
lingxia_webview::platform::android::initialize_jni(vm);
info!("Rust library loaded successfully");
jni::sys::JNI_VERSION_1_6
}
#[unsafe(no_mangle)]
pub extern "system" fn Java_com_lingxia_lxapp_NativeApi_lingxiaInit<'a>(
mut env: EnvUnowned<'a>,
_class: JClass<'a>,
data_dir: JString<'a>,
cache_dir: JString<'a>,
asset_manager: JObject<'a>,
locale: JString<'a>,
) -> JString<'a> {
env.with_env(|env| -> Result<JString, jni::errors::Error> {
init_cached_java_classes(env);
let data_dir_str: String = data_dir.try_to_string(env)?;
let cache_dir_str: String = cache_dir.try_to_string(env)?;
let locale_str: String = locale.try_to_string(env)?;
log::info!(
"Initializing Lingxia SDK with data_dir: {}, cache_dir: {}, locale: {}",
data_dir_str,
cache_dir_str,
locale_str
);
let platform = unsafe {
lingxia_platform::Platform::from_java(
env,
asset_manager.as_raw(),
data_dir_str,
cache_dir_str,
locale_str,
)
}
.map_err(|_| jni::errors::Error::JniCall(jni::errors::JniError::Unknown))?;
let home_app_id = crate::init_with_platform(platform);
match home_app_id {
Some(appid) => {
let java_string = env.new_string(&appid)?;
Ok(java_string)
}
None => {
error!("Failed to obtain LxApp home app details during initialization.");
Ok(JString::null())
}
}
})
.resolve::<LogErrorAndDefault>()
}
#[unsafe(no_mangle)]
pub extern "system" fn Java_com_lingxia_lxapp_NativeApi_onPageShow(
mut env: EnvUnowned,
_class: JClass,
appid: JString,
path: JString,
) {
env.with_env(|env| -> Result<(), jni::errors::Error> {
let appid: String = appid.try_to_string(env)?;
let path: String = path.try_to_string(env)?;
if let Some(lxapp) = lxapp::try_get(&appid) {
lxapp.on_page_show(path);
}
Ok(())
})
.resolve::<ThrowRuntimeExAndDefault>()
}
#[unsafe(no_mangle)]
pub extern "system" fn Java_com_lingxia_lxapp_NativeApi_findWebView<'a>(
mut env: EnvUnowned<'a>,
_class: JClass<'a>,
appid: JString<'a>,
path: JString<'a>,
session_id: jlong,
) -> JObject<'a> {
env.with_env(|env| -> Result<JObject, jni::errors::Error> {
let appid: String = appid.try_to_string(env)?;
let path: String = path.try_to_string(env)?;
if session_id <= 0 {
warn!(
"findWebView called without valid session_id for {}:{}",
appid, path
);
return Ok(JObject::null());
}
let session = Some(session_id as u64);
let webtag = lingxia_webview::WebTag::new(&appid, &path, session);
if let Some(webview) = lingxia_webview::runtime::find_webview(&webtag) {
match env.new_local_ref(webview.get_java_webview()) {
Ok(local_ref) => Ok(unsafe { JObject::from_raw(env, local_ref.into_raw()) }),
Err(e) => {
error!("Failed to create local reference to WebView: {:?}", e);
Ok(JObject::null())
}
}
} else {
error!(
"💥 Not found webview for {}-{} (session={}, key={})",
appid,
path,
session_id,
webtag.key()
);
Ok(JObject::null())
}
})
.resolve::<ThrowRuntimeExAndDefault>()
}
#[unsafe(no_mangle)]
pub extern "system" fn Java_com_lingxia_lxapp_NativeApi_onLxAppClosed(
mut env: EnvUnowned,
_class: JClass,
appid: JString,
session_id: jlong,
) -> jboolean {
env.with_env(|env| -> Result<jboolean, jni::errors::Error> {
let appid: String = appid.try_to_string(env)?;
let Some(lxapp) = lxapp::try_get(&appid) else {
warn!("Received close event for unknown lxapp: {}", appid);
return Ok(false);
};
if session_id <= 0 {
warn!("Ignoring close event with invalid session_id for {}", appid);
return Ok(false);
}
let session_id = session_id as u64;
if session_id != lxapp.session_id() {
return Ok(false);
}
lxapp.on_lxapp_closed(session_id);
Ok(true)
})
.resolve::<ThrowRuntimeExAndDefault>()
}
#[unsafe(no_mangle)]
pub extern "system" fn Java_com_lingxia_lxapp_NativeApi_getNavigationBarState<'a>(
mut env: EnvUnowned<'a>,
_class: JClass<'a>,
appid: JString<'a>,
path: JString<'a>,
) -> JObject<'a> {
env.with_env(|env| -> Result<JObject, jni::errors::Error> {
let appid: String = appid.try_to_string(env)?;
let path: String = path.try_to_string(env)?;
let Some(lxapp) = lxapp::try_get(&appid) else {
return Ok(JObject::null());
};
let nav_state = lxapp
.get_page(&path)
.and_then(|page| page.get_navbar_state())
.unwrap_or_default();
let nav_bar_class = env.find_class(jni_str!("com/lingxia/lxapp/NavigationBarState"))?;
let bg_color_int = parse_color_to_i32(
&nav_state.navigationBarBackgroundColor,
0xFFFFFFFFu32 as i32,
);
let title_text = env.new_string(&nav_state.navigationBarTitleText)?;
let text_style = env.new_string(&nav_state.navigationBarTextStyle)?;
let obj = env.new_object(
nav_bar_class,
jni_sig!("(ILjava/lang/String;Ljava/lang/String;ZZZ)V"),
&[
(bg_color_int as jint).into(),
(&text_style).into(),
(&title_text).into(),
(nav_state.show_navbar as jboolean).into(),
(nav_state.show_back_button as jboolean).into(),
(nav_state.show_home_button as jboolean).into(),
],
)?;
Ok(obj)
})
.resolve::<ThrowRuntimeExAndDefault>()
}
#[unsafe(no_mangle)]
pub extern "system" fn Java_com_lingxia_lxapp_NativeApi_isPullDownRefreshEnabled<'a>(
mut env: EnvUnowned<'a>,
_class: JClass<'a>,
appid: JString<'a>,
path: JString<'a>,
) -> jboolean {
env.with_env(|env| -> Result<jboolean, jni::errors::Error> {
let appid: String = appid.try_to_string(env)?;
let path: String = path.try_to_string(env)?;
if lxapp::is_pull_down_refresh_enabled(&appid, &path) {
Ok(true)
} else {
Ok(false)
}
})
.resolve::<ThrowRuntimeExAndDefault>()
}
#[unsafe(no_mangle)]
pub extern "system" fn Java_com_lingxia_lxapp_NativeApi_getPageOrientation<'a>(
mut env: EnvUnowned<'a>,
_class: JClass<'a>,
appid: JString<'a>,
path: JString<'a>,
) -> jint {
env.with_env(|env| -> Result<jint, jni::errors::Error> {
let appid: String = appid.try_to_string(env)?;
let path: String = path.try_to_string(env)?;
let Some(lxapp_instance) = lxapp::try_get(&appid) else {
return Ok(0);
};
let orientation = lxapp_instance.get_page_orientation(&path);
Ok(orientation_to_android_value(orientation))
})
.resolve::<ThrowRuntimeExAndDefault>()
}
fn orientation_to_android_value(orientation: OrientationConfig) -> jint {
match (orientation.mode, orientation.rotation) {
(PageOrientation::Auto, _) => 0,
(PageOrientation::Portrait, 180) => 3,
(PageOrientation::Portrait, _) => 1,
(PageOrientation::Landscape, 180) => 4,
(PageOrientation::Landscape, _) => 2,
}
}
#[unsafe(no_mangle)]
pub extern "C" fn Java_com_lingxia_lxapp_NativeApi_onLxappEvent(
mut env: EnvUnowned,
_class: JClass,
appid: JString,
event_type: jint,
data: JString,
) -> jint {
env.with_env(|env| -> Result<jint, jni::errors::Error> {
let appid: String = appid.try_to_string(env)?;
let data_str: String = data.try_to_string(env)?;
let ui_event_type = match event_type {
0 => LxAppUiEventType::TabBarClick,
1 => LxAppUiEventType::CapsuleClick,
2 => LxAppUiEventType::NavigationClick,
3 => LxAppUiEventType::BackPress,
4 => LxAppUiEventType::PullDownRefresh,
_ => {
error!("Unknown UI event type: {}", event_type);
return Ok(0);
}
};
let Some(lxapp) = lxapp::try_get(&appid) else {
return Ok(0);
};
if lxapp.on_lxapp_event(ui_event_type, data_str) {
Ok(1)
} else {
Ok(0)
}
})
.resolve::<ThrowRuntimeExAndDefault>()
}
#[unsafe(no_mangle)]
pub extern "C" fn Java_com_lingxia_lxapp_NativeApi_onKeyEvent(
mut env: EnvUnowned,
_class: JClass,
appid: JString,
event_type: jint,
payload_json: JString,
) -> jboolean {
env.with_env(|env| -> Result<jboolean, jni::errors::Error> {
let appid: String = appid.try_to_string(env)?;
let payload: String = payload_json.try_to_string(env)?;
let Some(lxapp) = lxapp::try_get(&appid) else {
return Ok(false);
};
let session_id = lxapp.session_id();
const KEY_EVENT_DOWN: jint = 0;
const KEY_EVENT_UP: jint = 1;
let should_dispatch = match event_type {
KEY_EVENT_DOWN => lxapp::lifecycle::key_events::has_key_down(&appid, session_id),
KEY_EVENT_UP => lxapp::lifecycle::key_events::has_key_up(&appid, session_id),
_ => false,
};
if !should_dispatch {
return Ok(false);
}
let event_name = if event_type == KEY_EVENT_DOWN {
"KeyDown"
} else {
"KeyUp"
};
if lxapp::publish_app_event(&appid, event_name, Some(payload)) {
Ok(true)
} else {
Ok(false)
}
})
.resolve::<ThrowRuntimeExAndDefault>()
}
#[unsafe(no_mangle)]
pub extern "C" fn Java_com_lingxia_lxapp_NativeApi_onDeviceOrientationChanged(
mut env: EnvUnowned,
_class: JClass,
appid: JString,
session_id: jlong,
value: JString,
) -> jboolean {
env.with_env(|env| -> Result<jboolean, jni::errors::Error> {
let appid: String = appid.try_to_string(env)?;
let value: String = value.try_to_string(env)?;
let Some(lxapp) = lxapp::try_get(&appid) else {
return Ok(false);
};
if session_id <= 0 {
return Ok(false);
}
if lxapp.session_id() != session_id as u64 {
return Ok(false);
}
let normalized = match value.as_str() {
"portrait" => "portrait",
"landscape" => "landscape",
_ => return Ok(false),
};
let payload = format!(r#"{{"value":"{}"}}"#, normalized);
if lxapp::publish_app_event(&appid, "DeviceOrientationChange", Some(payload)) {
Ok(true)
} else {
Ok(false)
}
})
.resolve::<ThrowRuntimeExAndDefault>()
}
#[unsafe(no_mangle)]
pub extern "system" fn Java_com_lingxia_lxapp_NativeApi_onLxAppOpened<'a>(
mut env: EnvUnowned<'a>,
_class: JClass<'a>,
appid: JString<'a>,
path: JString<'a>,
session_id: jlong,
) -> JString<'a> {
env.with_env(|env| -> Result<JString, jni::errors::Error> {
let appid: String = appid.try_to_string(env)?;
let path: String = path.try_to_string(env)?;
if session_id <= 0 {
warn!(
"onLxAppOpened called without valid session_id for {}",
appid
);
return env.new_string("");
}
let resolved_path = lxapp::try_get(&appid)
.map(|lxapp| lxapp.on_lxapp_opened(path, session_id as u64))
.unwrap_or_default();
match env.new_string(&resolved_path) {
Ok(jstring) => Ok(jstring),
Err(_) => {
env.new_string("").or_else(|_| {
Ok(JString::null())
})
}
}
})
.resolve::<ThrowRuntimeExAndDefault>()
}
#[unsafe(no_mangle)]
pub extern "system" fn Java_com_lingxia_lxapp_NativeApi_getLxAppInfo<'a>(
mut env: EnvUnowned<'a>,
_class: JClass<'a>,
appid: JString<'a>,
) -> JObject<'a> {
env.with_env(|env| -> Result<JObject, jni::errors::Error> {
let appid: String = appid.try_to_string(env)?;
let Some(lxapp) = lxapp::try_get(&appid) else {
return Ok(JObject::null());
};
let lxapp_info = lxapp.get_lxapp_info();
let lxapp_info_class = env.find_class(jni_str!("com/lingxia/lxapp/LxAppInfo"))?;
let app_name_str = env.new_string(&lxapp_info.app_name)?;
let version_str = env.new_string(&lxapp_info.version)?;
let release_type_str = env.new_string(&lxapp_info.release_type)?;
let cache_dir_str = env.new_string(lxapp.user_cache_dir.to_string_lossy().into_owned())?;
let obj = env.new_object(
lxapp_info_class,
jni_sig!("(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V"),
&[
(&app_name_str).into(),
(&version_str).into(),
(&release_type_str).into(),
(&cache_dir_str).into(),
],
)?;
Ok(obj)
})
.resolve::<ThrowRuntimeExAndDefault>()
}
#[unsafe(no_mangle)]
pub extern "system" fn Java_com_lingxia_lxapp_NativeApi_getTabBarState<'a>(
mut env: EnvUnowned<'a>,
_class: JClass<'a>,
appid: JString<'a>,
) -> JObject<'a> {
env.with_env(|env| -> Result<JObject, jni::errors::Error> {
let appid: String = appid.try_to_string(env)?;
let tab_bar_config = match lxapp::try_get(&appid).and_then(|lxapp| lxapp.get_tabbar()) {
Some(config) => config,
None => {
return Ok(JObject::null());
}
};
let tab_bar_class = env.find_class(jni_str!("com/lingxia/lxapp/TabBarState"))?;
let background_color =
parse_color_to_i32(&tab_bar_config.backgroundColor, 0xFFFFFFFFu32 as i32);
let selected_color =
parse_color_to_i32(&tab_bar_config.selectedColor, 0xFF1677FFu32 as i32);
let color = parse_color_to_i32(&tab_bar_config.color, 0xFF666666u32 as i32);
let border_style = parse_color_to_i32(&tab_bar_config.borderStyle, 0xFFF0F0F0u32 as i32);
let dimension = tab_bar_config.dimension;
let position_int = tab_bar_config.position.to_i32();
let array_list_class = env.find_class(jni_str!("java/util/ArrayList"))?;
let tab_items_list = env.new_object(array_list_class, jni_sig!("()V"), &[])?;
for item in tab_bar_config.list.iter() {
if let Some(tab_item) = create_tab_bar_item(env, item) {
let _ = env.call_method(
&tab_items_list,
jni_str!("add"),
jni_sig!("(Ljava/lang/Object;)Z"),
&[(&tab_item).into()],
);
} else {
log::warn!(
"[Android] Failed to create TabBar item in getTabBarState for {}",
&item.pagePath
);
}
}
let position_class = env.find_class(jni_str!("com/lingxia/lxapp/TabBarState$Position"))?;
let position_enum = match position_int {
1 => env.get_static_field(
position_class,
jni_str!("LEFT"),
jni_sig!("Lcom/lingxia/lxapp/TabBarState$Position;"),
)?,
2 => env.get_static_field(
position_class,
jni_str!("RIGHT"),
jni_sig!("Lcom/lingxia/lxapp/TabBarState$Position;"),
)?,
_ => env.get_static_field(
position_class,
jni_str!("BOTTOM"),
jni_sig!("Lcom/lingxia/lxapp/TabBarState$Position;"),
)?,
};
let obj = env.new_object(
tab_bar_class,
jni_sig!("(IIIIILcom/lingxia/lxapp/TabBarState$Position;Ljava/util/List;ZI)V"),
&[
background_color.into(),
selected_color.into(),
color.into(),
border_style.into(),
dimension.into(),
(&position_enum).into(),
(&tab_items_list).into(),
tab_bar_config.is_visible.into(),
tab_bar_config.selected_index.into(),
],
)?;
Ok(obj)
})
.resolve::<ThrowRuntimeExAndDefault>()
}
fn create_tab_bar_item<'a>(
env: &mut Env<'a>,
item: &lxapp::tabbar::TabBarItem,
) -> Option<JObject<'a>> {
let tab_item_class = match env.find_class(jni_str!("com/lingxia/lxapp/TabBarItem")) {
Ok(c) => c,
Err(_) => return None,
};
let group_int = match &item.group {
Some(lxapp::tabbar::TabItemGroup::Start) => 1,
Some(lxapp::tabbar::TabItemGroup::End) => 2,
None => 0,
};
let page_path = match env.new_string(&item.pagePath) {
Ok(s) => s,
Err(_) => return None,
};
let text = match env.new_string(item.text.as_deref().unwrap_or("")) {
Ok(s) => s,
Err(_) => return None,
};
let icon_path = match env.new_string(item.iconPath.as_deref().unwrap_or("")) {
Ok(s) => s,
Err(_) => return None,
};
let selected_icon_path = match env.new_string(item.selectedIconPath.as_deref().unwrap_or("")) {
Ok(s) => s,
Err(_) => return None,
};
let badge_jstring = match &item.badge {
Some(badge) => match env.new_string(badge) {
Ok(s) => s.into(),
Err(_) => JObject::null(),
},
None => JObject::null(),
};
env
.new_object(
tab_item_class,
jni_sig!("(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ZILjava/lang/String;Z)V"),
&[
(&page_path).into(),
(&text).into(),
(&icon_path).into(),
(&selected_icon_path).into(),
item.selected.into(),
group_int.into(),
(&badge_jstring).into(),
item.has_red_dot.into(), ],
)
.ok()
}
#[unsafe(no_mangle)]
pub extern "system" fn Java_com_lingxia_lxapp_NativeApi_onAppLinkReceived(
mut env: EnvUnowned,
_class: JClass,
applink_url: JString,
) -> jint {
env.with_env(|env| -> Result<jint, jni::errors::Error> {
let url: String = applink_url.try_to_string(env)?;
log::info!("[Android] AppLink received: {}", url);
Ok(0)
})
.resolve::<ThrowRuntimeExAndDefault>()
}
#[unsafe(no_mangle)]
pub extern "system" fn Java_com_lingxia_lxapp_NativeApi_getCurrentLxApp<'a>(
mut env: EnvUnowned<'a>,
_class: JClass<'a>,
) -> JObject<'a> {
env.with_env(|env| -> Result<JObject, jni::errors::Error> {
let (current_appid, current_path, current_session_id) = lxapp::get_current_lxapp();
let current_lxapp_class = env.find_class(jni_str!("com/lingxia/lxapp/CurrentLxApp"))?;
let appid_str = env.new_string(¤t_appid)?;
let path_str = env.new_string(¤t_path)?;
let obj = env.new_object(
current_lxapp_class,
jni_sig!("(Ljava/lang/String;Ljava/lang/String;J)V"),
&[
(&appid_str).into(),
(&path_str).into(),
(current_session_id as jlong).into(),
],
)?;
Ok(obj)
})
.resolve::<ThrowRuntimeExAndDefault>()
}
#[unsafe(no_mangle)]
pub extern "system" fn Java_com_lingxia_lxapp_NativeApi_getLxAppSessionId<'a>(
mut env: EnvUnowned<'a>,
_class: JClass<'a>,
appid: JString<'a>,
) -> jlong {
env.with_env(|env| -> Result<jlong, jni::errors::Error> {
let appid: String = appid.try_to_string(env)?;
let session_id = lxapp::try_get(&appid)
.map(|lxapp| lxapp.session_id() as jlong)
.unwrap_or(0);
Ok(session_id)
})
.resolve::<ThrowRuntimeExAndDefault>()
}
#[unsafe(no_mangle)]
pub extern "system" fn Java_com_lingxia_lxapp_NativeApi_onCallback(
mut env: EnvUnowned,
_class: JClass,
id: jlong,
success: jboolean,
data: JString,
) -> jboolean {
env.with_env(|env| -> Result<jboolean, jni::errors::Error> {
let id = id as u64;
let success = success;
let data_str: String = match data.try_to_string(env) {
Ok(s) => s.to_string(),
Err(e) => {
error!("[Android] Failed to get data string: {}", e);
let _ = invoke_callback(id, Err(1000));
return Ok(false);
}
};
let result = if success {
Ok(data_str)
} else {
Err(data_str.parse::<u32>().unwrap_or(1000))
};
if invoke_callback(id, result) {
Ok(true)
} else {
warn!("[Android] Callback not found for id={}", id);
Ok(false)
}
})
.resolve::<ThrowRuntimeExAndDefault>()
}
#[unsafe(no_mangle)]
pub extern "system" fn Java_com_lingxia_lxapp_NativeApi_onNativeComponentEvent<'a>(
mut env: EnvUnowned<'a>,
_class: JClass<'a>,
appid: JString<'a>,
path: JString<'a>,
component_id: JString<'a>,
event_name: JString<'a>,
payload_json: JString<'a>,
bindings_json: JString<'a>,
) -> jboolean {
env.with_env(|env| -> Result<jboolean, jni::errors::Error> {
let appid: String = appid.try_to_string(env)?;
let path: String = path.try_to_string(env)?;
let component_id: String = component_id.try_to_string(env)?;
let event_name: String = event_name.try_to_string(env)?;
let payload_json: String = payload_json.try_to_string(env)?;
let bindings_json: String = bindings_json.try_to_string(env)?;
let accepted = lxapp::on_native_component_event(
&appid,
&path,
&component_id,
&event_name,
&payload_json,
&bindings_json,
);
Ok(if accepted {
true as jboolean
} else {
false as jboolean
})
})
.resolve::<ThrowRuntimeExAndDefault>()
}
#[unsafe(no_mangle)]
pub extern "system" fn Java_com_lingxia_lxapp_NativeApi_onAppShow(
mut env: EnvUnowned,
_class: JClass,
lxappid: JString,
) {
env.with_env(|env| -> Result<(), jni::errors::Error> {
let lxappid: String = match lxappid.try_to_string(env) {
Ok(s) => s.to_string(),
Err(e) => {
error!(
"[Android] Failed to get lxappid string for onAppShow: {}",
e
);
return Err(e);
}
};
if let Some(lxapp) = lxapp::try_get(&lxappid) {
let args = AppServiceEventArgs {
source: AppServiceEventSource::Host,
reason: AppServiceEventReason::Foreground,
}
.to_json_string();
let _ = lxapp.appservice_notify(AppServiceEvent::OnShow, Some(args));
}
Ok(())
})
.resolve::<ThrowRuntimeExAndDefault>()
}
#[unsafe(no_mangle)]
pub extern "system" fn Java_com_lingxia_lxapp_NativeApi_onAppHide(
mut env: EnvUnowned,
_class: JClass,
lxappid: JString,
) {
env.with_env(|env| -> Result<(), jni::errors::Error> {
let lxappid: String = match lxappid.try_to_string(env) {
Ok(s) => s.to_string(),
Err(e) => {
error!(
"[Android] Failed to get lxappid string for onAppHide: {}",
e
);
return Err(e);
}
};
if let Some(lxapp) = lxapp::try_get(&lxappid) {
let args = AppServiceEventArgs {
source: AppServiceEventSource::Host,
reason: AppServiceEventReason::Background,
}
.to_json_string();
let _ = lxapp.appservice_notify(AppServiceEvent::OnHide, Some(args));
}
Ok(())
})
.resolve::<ThrowRuntimeExAndDefault>()
}
#[unsafe(no_mangle)]
pub extern "system" fn Java_com_lingxia_lxapp_NativeApi_resolveLxUri<'a>(
mut env: EnvUnowned<'a>,
_class: JClass<'a>,
appid: JString<'a>,
input: JString<'a>,
) -> JString<'a> {
env.with_env(|env| -> Result<JString, jni::errors::Error> {
let appid: String = match appid.try_to_string(env) {
Ok(s) => s.to_string(),
Err(_) => return Ok(JString::null()),
};
let input: String = match input.try_to_string(env) {
Ok(s) => s.to_string(),
Err(_) => return Ok(JString::null()),
};
let trimmed = input.trim();
if trimmed.is_empty() {
return Ok(JString::null());
}
if trimmed.starts_with("http://") || trimmed.starts_with("https://") {
return env.new_string(trimmed).or_else(|_| Ok(JString::null()));
}
let Some(lxapp) = lxapp::try_get(&appid) else {
return Ok(JString::null());
};
let resolved = if let Some(path) = trimmed.strip_prefix("file://") {
lxapp.resolve_accessible_path(path).ok()
} else {
lxapp.resolve_accessible_path(trimmed).ok()
};
let Some(resolved) = resolved else {
return Ok(JString::null());
};
let resolved_str = resolved.to_string_lossy();
env.new_string(format!("file://{}", resolved_str))
.or_else(|_| Ok(JString::null()))
})
.resolve::<ThrowRuntimeExAndDefault>()
}
#[unsafe(no_mangle)]
pub extern "system" fn Java_com_lingxia_lxapp_NativeApi_openBrowserTab<'a>(
mut env: EnvUnowned<'a>,
_class: JClass<'a>,
appid: JString<'a>,
session_id: jlong,
url: JString<'a>,
) -> JString<'a> {
env.with_env(|env| -> Result<JString, jni::errors::Error> {
let appid: String = match appid.try_to_string(env) {
Ok(s) => s.to_string(),
Err(_) => return Ok(JString::null()),
};
let url: String = match url.try_to_string(env) {
Ok(s) => s.to_string(),
Err(_) => return Ok(JString::null()),
};
if session_id <= 0 {
return Ok(JString::null());
}
let tab_id = match crate::browser::open_for_app(&appid, session_id as u64, &url, None) {
Ok(tab_id) => tab_id,
Err(e) => {
error!("[Android] openBrowserTab failed: {}", e);
return Ok(JString::null());
}
};
env.new_string(tab_id).or_else(|_| Ok(JString::null()))
})
.resolve::<ThrowRuntimeExAndDefault>()
}
#[unsafe(no_mangle)]
pub extern "system" fn Java_com_lingxia_lxapp_NativeApi_browserTabClose(
mut env: EnvUnowned,
_class: JClass,
tab_id: JString,
) -> jboolean {
env.with_env(|env| -> Result<jboolean, jni::errors::Error> {
let tab_id: String = match tab_id.try_to_string(env) {
Ok(s) => s.to_string(),
Err(_) => return Ok(false),
};
Ok(crate::browser::close(&tab_id).is_ok())
})
.resolve::<ThrowRuntimeExAndDefault>()
}
#[unsafe(no_mangle)]
pub extern "system" fn Java_com_lingxia_lxapp_NativeApi_getBuiltinBrowserAppId<'a>(
mut env: EnvUnowned<'a>,
_class: JClass<'a>,
) -> JString<'a> {
env.with_env(|env| -> Result<JString, jni::errors::Error> {
env.new_string(crate::browser::APP_ID)
.or_else(|_| Ok(JString::null()))
})
.resolve::<ThrowRuntimeExAndDefault>()
}
#[unsafe(no_mangle)]
pub extern "system" fn Java_com_lingxia_lxapp_NativeApi_browserTabPathForId<'a>(
mut env: EnvUnowned<'a>,
_class: JClass<'a>,
tab_id: JString<'a>,
) -> JString<'a> {
env.with_env(|env| -> Result<JString, jni::errors::Error> {
let tab_id: String = match tab_id.try_to_string(env) {
Ok(s) => s.to_string(),
Err(_) => return Ok(JString::null()),
};
let path = crate::browser::tab_path(&tab_id);
env.new_string(path).or_else(|_| Ok(JString::null()))
})
.resolve::<ThrowRuntimeExAndDefault>()
}
#[unsafe(no_mangle)]
pub extern "system" fn Java_com_lingxia_lxapp_NativeApi_handleBrowserAddressInput<'a>(
mut env: EnvUnowned<'a>,
_class: JClass<'a>,
request_json: JString<'a>,
) -> JString<'a> {
env.with_env(|env| -> Result<JString, jni::errors::Error> {
let request_json: String = match request_json.try_to_string(env) {
Ok(s) => s.to_string(),
Err(_) => return Ok(JString::null()),
};
let Some(response_json) = crate::browser::resolve_input_json(&request_json) else {
return Ok(JString::null());
};
env.new_string(response_json)
.or_else(|_| Ok(JString::null()))
})
.resolve::<ThrowRuntimeExAndDefault>()
}
#[unsafe(no_mangle)]
pub extern "system" fn Java_com_lingxia_lxapp_NativeApi_handleBrowserNavigationPolicy<'a>(
mut env: EnvUnowned<'a>,
_class: JClass<'a>,
request_json: JString<'a>,
) -> JString<'a> {
env.with_env(|env| -> Result<JString, jni::errors::Error> {
let request_json: String = match request_json.try_to_string(env) {
Ok(s) => s.to_string(),
Err(_) => return Ok(JString::null()),
};
let Some(response_json) = crate::browser::classify_navigation_json(&request_json) else {
return Ok(JString::null());
};
env.new_string(response_json)
.or_else(|_| Ok(JString::null()))
})
.resolve::<ThrowRuntimeExAndDefault>()
}
#[unsafe(no_mangle)]
pub extern "system" fn Java_com_lingxia_lxapp_NativeApi_getAppCapabilities(
_env: EnvUnowned,
_class: JClass,
) -> jint {
crate::browser::app_capabilities() as jint
}