use super::*;
pub fn ensure_lxapp(appid: &str, release_type: Channel) -> Result<Arc<LxApp>, LxAppError> {
let manager = super::runtime_registry::get_lxapps_manager()
.ok_or_else(|| LxAppError::Runtime("LxApps manager not initialized".to_string()))?;
manager.ensure_lxapp(appid.to_string(), release_type)
}
#[doc(hidden)]
pub fn ensure_control_lxapp(
authority: &crate::NativeControlPlaneAuthority,
appid: &str,
release_type: Channel,
) -> Result<Arc<LxApp>, LxAppError> {
if !authority.validate() {
return Err(LxAppError::UnsupportedOperation(
"control app bootstrap requires the live native host authority".to_string(),
));
}
let manager = super::runtime_registry::get_lxapps_manager()
.ok_or_else(|| LxAppError::Runtime("LxApps manager not initialized".to_string()))?;
manager.ensure_lxapp_for_native_control(appid.to_string(), release_type)
}
#[doc(hidden)]
pub fn ensure_control_surface_lxapp(
authority: &crate::NativeControlPlaneAuthority,
appid: &str,
release_type: Channel,
) -> Result<Arc<LxApp>, LxAppError> {
if !authority.validate() {
return Err(LxAppError::UnsupportedOperation(
"control surface bootstrap requires the live native host authority".to_string(),
));
}
let manager = super::runtime_registry::get_lxapps_manager()
.ok_or_else(|| LxAppError::Runtime("LxApps manager not initialized".to_string()))?;
manager.ensure_lxapp_for_control_surface(appid.to_string(), release_type)
}
pub fn ensure_builtin_lxapp(appid: &str) -> Result<Arc<LxApp>, LxAppError> {
let manager = super::runtime_registry::get_lxapps_manager()
.ok_or_else(|| LxAppError::Runtime("LxApps manager not initialized".to_string()))?;
manager.ensure_builtin_lxapp(appid)
}
pub fn ensure_host_surface_owner() -> Result<Arc<LxApp>, LxAppError> {
register_synthetic_lxapp(HOST_SURFACE_OWNER_APP_ID);
ensure_builtin_lxapp(HOST_SURFACE_OWNER_APP_ID)
}
pub fn open_lxapp(appid: &str, options: LxAppStartupOptions) -> Result<Arc<LxApp>, LxAppError> {
let manager = super::runtime_registry::get_lxapps_manager()
.ok_or_else(|| LxAppError::Runtime("LxApps manager not initialized".to_string()))?;
let app = manager.ensure_lxapp(appid.to_string(), options.release_type)?;
app.open(options)?;
Ok(app)
}
#[doc(hidden)]
pub fn open_control_lxapp_page(
authority: &crate::NativeControlPlaneAuthority,
appid: &str,
options: LxAppStartupOptions,
) -> Result<Arc<LxApp>, LxAppError> {
if !authority.validate() {
return Err(LxAppError::UnsupportedOperation(
"control app bootstrap requires the live native host authority".to_string(),
));
}
let expected = lingxia_app_context::home_app_id().ok_or_else(|| {
LxAppError::Runtime("control app identity is not initialized".to_string())
})?;
if appid != expected {
return Err(LxAppError::InvalidParameter(format!(
"control app identity mismatch: expected {expected}, got {appid}"
)));
}
let app = ensure_control_lxapp(authority, appid, options.release_type)?;
if !app.is_control_app() {
return Err(LxAppError::Runtime(format!(
"current app session is not ControlApp: {appid}"
)));
}
app.open(options)?;
let current = super::runtime_registry::try_get(appid).ok_or_else(|| {
LxAppError::ResourceNotFound(format!("current control app session not found: {appid}"))
})?;
if !current.is_control_app() {
return Err(LxAppError::Runtime(format!(
"current app session is not ControlApp: {appid}"
)));
}
Ok(current)
}
pub fn list_lxapps() -> Vec<LxAppRuntimeInfo> {
let Some(manager) = super::runtime_registry::get_lxapps_manager() else {
return Vec::new();
};
let mut apps: Vec<LxAppRuntimeInfo> = manager
.lxapps
.iter()
.filter(|entry| entry.key().as_str() != HOST_SURFACE_OWNER_APP_ID)
.map(|entry| entry.value().runtime_info())
.collect();
apps.sort_by(|a, b| a.appid.cmp(&b.appid));
apps
}
pub fn refresh_auto_appearances() {
super::host_appearance::refresh_host_appearance_system();
let Some(manager) = super::runtime_registry::get_lxapps_manager() else {
return;
};
let apps: Vec<_> = manager
.lxapps
.iter()
.filter_map(|entry| {
let app = entry.value().clone();
(app.appearance_state().preference == AppearancePreference::Auto).then_some(app)
})
.collect();
for app in apps {
std::mem::drop(crate::executor::spawn(async move {
let _ = app.refresh_appearance().await;
}));
}
}
pub fn close_lxapp(appid: &str) -> Result<(), LxAppError> {
let app = super::runtime_registry::try_get(appid)
.ok_or_else(|| LxAppError::ResourceNotFound(appid.to_string()))?;
let session_id = app.session_id();
if !app.begin_programmatic_close(session_id) {
return Ok(());
}
if let Some(manager) = super::runtime_registry::get_lxapps_manager() {
manager.remove_from_stack(appid);
}
app.shutdown()?;
app.complete_programmatic_close(session_id);
Ok(())
}
pub fn terminate_lxapp(
appid: &str,
) -> Result<impl std::future::Future<Output = Result<(), LxAppError>> + Send + 'static, LxAppError>
{
let manager = super::runtime_registry::get_lxapps_manager()
.ok_or_else(|| LxAppError::ResourceNotFound(appid.to_string()))?;
let app = manager.retire_lxapp(appid)?;
Ok(async move {
let mut stopped = app.logic_contexts.subscribe();
tokio::time::timeout(
std::time::Duration::from_secs(5),
stopped.wait_for(|count| *count == 0),
)
.await
.map_err(|_| LxAppError::Runtime(format!("Timed out terminating Logic for {}", app.appid)))?
.map_err(|_| {
LxAppError::Runtime(format!(
"Logic termination channel closed for {}",
app.appid
))
})?;
Ok(())
})
}
pub fn restart_lxapp(appid: &str) -> Result<(), LxAppError> {
let app = super::runtime_registry::try_get(appid)
.ok_or_else(|| LxAppError::ResourceNotFound(appid.to_string()))?;
app.restart()
}
pub fn uninstall_lxapp(appid: &str) -> Result<(), LxAppError> {
let manager = super::runtime_registry::get_lxapps_manager()
.ok_or_else(|| LxAppError::Runtime("LxApps manager not initialized".to_string()))?;
let app = if let Some(app) = super::runtime_registry::try_get(appid) {
manager.destroy_lxapp_with_options(appid, true);
app
} else {
manager
.lxapps
.iter()
.next()
.map(|entry| entry.value().clone())
.ok_or_else(|| LxAppError::Runtime("No LxApp runtime available".to_string()))?
};
let updater = UpdateManager::new(app);
updater.uninstall_all(appid)
}
pub fn installed_lxapp_path(appid: &str, release_type: Channel) -> Option<String> {
metadata::get(appid, release_type)
.ok()
.flatten()
.map(|record| record.install_path)
}
pub fn touch_page_instance_by_id(id: &str) -> Result<(), LxAppError> {
let id = PageInstanceId::parse(id.to_string()).ok_or_else(|| {
LxAppError::InvalidParameter("page instance id must not be empty".to_string())
})?;
let page = super::runtime_registry::find_page_by_instance_id(id.as_str())
.ok_or_else(|| LxAppError::ResourceNotFound(format!("page instance id: {}", id)))?;
let app = super::runtime_registry::try_get(&page.appid())
.ok_or_else(|| LxAppError::ResourceNotFound(page.appid()))?;
app.refresh_page_instance_dispose_ttl(&id)
}
pub fn create_page_instance(
req: CreatePageInstanceRequest,
) -> Result<CreatedPageInstance, LxAppError> {
let app = super::runtime_registry::try_get(&req.appid)
.ok_or_else(|| LxAppError::ResourceNotFound(req.appid.clone()))?;
app.create_page_instance(req.owner, req.target, req.query, req.surface, None)
}
pub fn notify_page_instance(
id: &PageInstanceId,
event: PageInstanceEvent,
) -> Result<(), LxAppError> {
let page = super::runtime_registry::find_page_by_instance_id(id.as_str())
.ok_or_else(|| LxAppError::ResourceNotFound(format!("page instance id: {}", id)))?;
let app = super::runtime_registry::try_get(&page.appid())
.ok_or_else(|| LxAppError::ResourceNotFound(page.appid()))?;
app.notify_page_instance(id, event)
}
pub fn notify_page_instance_by_id(id: &str, event: PageInstanceEvent) -> Result<(), LxAppError> {
let id = PageInstanceId::parse(id.to_string()).ok_or_else(|| {
LxAppError::InvalidParameter("page instance id must not be empty".to_string())
})?;
notify_page_instance(&id, event)
}
pub fn dispose_page_instance(id: &PageInstanceId, reason: CloseReason) -> Result<(), LxAppError> {
let page = super::runtime_registry::find_page_by_instance_id(id.as_str())
.ok_or_else(|| LxAppError::ResourceNotFound(format!("page instance id: {}", id)))?;
let app = super::runtime_registry::try_get(&page.appid())
.ok_or_else(|| LxAppError::ResourceNotFound(page.appid()))?;
app.dispose_page_instance(id, reason)
}
pub fn dispose_page_instance_by_id(id: &str, reason: CloseReason) -> Result<(), LxAppError> {
let id = PageInstanceId::parse(id.to_string()).ok_or_else(|| {
LxAppError::InvalidParameter("page instance id must not be empty".to_string())
})?;
dispose_page_instance(&id, reason)
}
pub fn on_low_memory() {
info!("on_low_memory: discarding hidden-main tab WebViews, then evicting an unused lxapp");
super::page_discard::enforce_page_webview_budget_with_limit(0);
if let Some(manager) = super::runtime_registry::get_lxapps_manager() {
manager.evict_lru_lxapp();
}
}
pub fn get_current_lxapp() -> (String, String, u64) {
if let Some(manager) = super::runtime_registry::get_lxapps_manager()
&& let Some(current_appid) = manager.peek_lxapp_stack()
&& let Some(lxapp) = manager.lxapps.get(¤t_appid)
{
let current_path = lxapp.peek_current_page_path().unwrap_or_default();
let current_session = lxapp.session_id();
info!(
"Peek {}:{} (session={}) from lxapp stack",
current_appid, current_path, current_session
);
return (current_appid, current_path, current_session);
}
(String::new(), String::new(), 0)
}
pub fn mark_lxapp_active(appid: &str) -> bool {
let Some(manager) = super::runtime_registry::get_lxapps_manager() else {
return false;
};
if !manager.lxapps.contains_key(appid) {
return false;
}
manager.remove_from_stack(appid);
manager.push_lxapp_stack(appid.to_string());
true
}
pub fn notify_lxapp_host_visibility(appid: &str, visible: bool) -> Result<(), LxAppError> {
let app = super::runtime_registry::try_get(appid)
.ok_or_else(|| LxAppError::ResourceNotFound(appid.to_string()))?;
if matches!(
app.status(),
LxAppSessionStatus::Closing | LxAppSessionStatus::Closed
) {
return Ok(());
}
let args = crate::lifecycle::AppServiceEventArgs {
source: crate::lifecycle::AppServiceEventSource::Host,
reason: if visible {
crate::lifecycle::AppServiceEventReason::Foreground
} else {
crate::lifecycle::AppServiceEventReason::Background
},
}
.to_json_string();
app.appservice_notify(
if visible {
crate::lifecycle::AppServiceEvent::OnShow
} else {
crate::lifecycle::AppServiceEvent::OnHide
},
Some(args),
)
}
pub fn notify_page_host_visibility(
appid: &str,
path: &str,
visible: bool,
) -> Result<(), LxAppError> {
let app = super::runtime_registry::try_get(appid)
.ok_or_else(|| LxAppError::ResourceNotFound(appid.to_string()))?;
if matches!(
app.status(),
LxAppSessionStatus::Closing | LxAppSessionStatus::Closed
) {
return Ok(());
}
let page = app.require_page(path)?;
page.dispatch_lifecycle_event(if visible {
crate::lifecycle::PageLifecycleEvent::OnShow
} else {
crate::lifecycle::PageLifecycleEvent::OnHide
});
if visible {
page.mark_active();
}
Ok(())
}
pub fn is_pull_down_refresh_enabled(appid: &str, path: &str) -> bool {
super::runtime_registry::try_get(appid)
.map(|lxapp| lxapp.is_pull_down_refresh_enabled(path))
.unwrap_or(false)
}
pub fn is_lxapp_open(lxappid: &str) -> bool {
if let Some(manager) = super::runtime_registry::get_lxapps_manager()
&& let Some(app) = manager.lxapps.get(lxappid)
{
return app.is_opened();
}
false
}