cranpose 0.1.149

Cranpose runtime and UI facade
Documentation
#![allow(unsafe_code)]
use std::sync::Arc;

use cranpose_services::{
    HostController, PlatformDirectories, set_application_id, set_host_controller,
};
use jni::{EnvUnowned, jni_sig, jni_str};

use crate::android_jni::{clear_pending_android_jni_exception, with_android_activity_env};

struct AndroidHost {
    app: android_activity::AndroidApp,
}
impl HostController for AndroidHost {
    fn set_keep_screen_on(&self, enabled: bool) {
        let _ = with_android_activity_env(&self.app, |env, activity| {
            env.call_method(
                &activity,
                jni_str!("cranposeSetKeepScreenOn"),
                jni_sig!("(Z)V"),
                &[enabled.into()],
            )
            .map(|_| ())
            .map_err(|e| {
                clear_pending_android_jni_exception(env);
                e.to_string()
            })
        });
    }
    fn platform_directories(&self) -> Option<PlatformDirectories> {
        let data = self.app.internal_data_path()?.to_path_buf();
        let sandbox = data.parent().unwrap_or(&data);
        Some(PlatformDirectories {
            data: data.clone(),
            config: data.join("config"),
            cache: sandbox.join("cache"),
            documents: Some(sandbox.join("files").join("documents")),
            temporary: sandbox.join("cache").join("temporary"),
            shared: self.app.external_data_path(),
        })
    }
    fn exit(&self) {
        let _ = crate::android_finish::finish_activity(&self.app);
    }
    fn background(&self) {
        let _ = with_android_activity_env(&self.app, |env, activity| {
            env.call_method(
                &activity,
                jni_str!("cranposeMoveToBackground"),
                jni_sig!("()V"),
                &[],
            )
            .map(|_| ())
            .map_err(|e| {
                clear_pending_android_jni_exception(env);
                e.to_string()
            })
        });
    }

    fn durable_save_deadline(&self) -> std::time::Duration {
        std::time::Duration::from_secs(2)
    }
}

fn package_name(app: &android_activity::AndroidApp) -> Option<String> {
    with_android_activity_env(app, |env, activity| {
        let value = env
            .call_method(
                &activity,
                jni_str!("getPackageName"),
                jni_sig!("()Ljava/lang/String;"),
                &[],
            )
            .and_then(|value| value.l())
            .map_err(|error| {
                clear_pending_android_jni_exception(env);
                error.to_string()
            })?;
        if value.is_null() {
            return Ok(None);
        }
        jni::objects::JString::cast_local(env, value)
            .map_err(|error| error.to_string())?
            .try_to_string(env)
            .map(Some)
            .map_err(|error| error.to_string())
    })
    .ok()
    .flatten()
}

pub(crate) fn install(app: android_activity::AndroidApp) {
    if let Some(path) = app.internal_data_path() {
        unsafe {
            std::env::set_var("XDG_DATA_HOME", path);
        }
    }
    if let Some(package) = package_name(&app)
        && let Err(error) = set_application_id(&package)
    {
        log::warn!("cranpose: the Android package name is not a usable application id: {error}");
    }
    set_host_controller(Arc::new(AndroidHost { app }));
    crate::pipeline_cache_file::publish();
}

#[unsafe(no_mangle)]
pub extern "system" fn Java_dev_cranpose_android_CranposeActivity_nativeOnBackInvoked(
    _env: EnvUnowned<'_>,
    _class: jni::objects::JClass<'_>,
) -> jni::sys::jboolean {
    if cranpose_services::back_interception_enabled() {
        cranpose_services::push_back_request();
        true
    } else {
        false
    }
}