lingxia-platform 0.18.0

Platform abstraction layer for LingXia framework (Android, iOS, HarmonyOS)
Documentation
use jni::objects::{JClass, JObject, JString, JValue};
use jni::{jni_sig, jni_str};

use crate::error::PlatformError;
use crate::traits::clipboard::{
    ClipboardContents, ClipboardKind, ClipboardReadRequest, ClipboardService, ClipboardTypes,
    ClipboardWrite, parse_native_reply,
};

use super::{Platform, with_env};

fn read_jni_string(env: &mut jni::Env, obj: JObject<'_>) -> Result<String, PlatformError> {
    if obj.is_null() {
        return Err(PlatformError::Platform(
            "Android clipboard returned null".to_string(),
        ));
    }
    let value = unsafe { JString::from_raw(env, obj.into_raw() as _) };
    value
        .try_to_string(env)
        .map_err(|e| PlatformError::Platform(e.to_string()))
}

fn call_write_or_read(
    method_write: bool,
    kind: &str,
    payload: &str,
) -> Result<String, PlatformError> {
    with_env(|env| -> Result<String, PlatformError> {
        let class: &JClass = super::get_cached_class(super::CachedClass::LxAppClipboard)
            .map_err(|e| PlatformError::Platform(e.to_string()))?;
        let kind_j = env.new_string(kind)?;
        let payload_j = env.new_string(payload)?;
        let result = if method_write {
            env.call_static_method(
                class,
                jni_str!("write"),
                jni_sig!("(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;"),
                &[JValue::Object(&kind_j), JValue::Object(&payload_j)],
            )?
        } else {
            env.call_static_method(
                class,
                jni_str!("read"),
                jni_sig!("(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;"),
                &[JValue::Object(&kind_j), JValue::Object(&payload_j)],
            )?
        };
        if env.exception_check() {
            env.exception_clear();
            return Err(PlatformError::Platform(
                "Android clipboard JNI call threw an exception".to_string(),
            ));
        }
        read_jni_string(env, result.l()?)
    })
    .map_err(|e| PlatformError::Platform(format!("Failed to call Android clipboard: {e}")))
}

fn call_clear_or_types(clear: bool) -> Result<String, PlatformError> {
    with_env(|env| -> Result<String, PlatformError> {
        let class: &JClass = super::get_cached_class(super::CachedClass::LxAppClipboard)
            .map_err(|e| PlatformError::Platform(e.to_string()))?;
        let result = if clear {
            env.call_static_method(
                class,
                jni_str!("clear"),
                jni_sig!("()Ljava/lang/String;"),
                &[],
            )?
        } else {
            env.call_static_method(
                class,
                jni_str!("types"),
                jni_sig!("()Ljava/lang/String;"),
                &[],
            )?
        };
        if env.exception_check() {
            env.exception_clear();
            return Err(PlatformError::Platform(
                "Android clipboard JNI call threw an exception".to_string(),
            ));
        }
        read_jni_string(env, result.l()?)
    })
    .map_err(|e| PlatformError::Platform(format!("Failed to call Android clipboard: {e}")))
}

impl ClipboardService for Platform {
    async fn clipboard_write(&self, item: ClipboardWrite) -> Result<(), PlatformError> {
        let payload = match &item {
            ClipboardWrite::Text(text) => call_write_or_read(true, "text", text)?,
            ClipboardWrite::Image { path } => call_write_or_read(true, "image", path)?,
        };
        parse_native_reply(&payload)?.into_unit()
    }

    async fn clipboard_read(
        &self,
        request: ClipboardReadRequest,
    ) -> Result<ClipboardContents, PlatformError> {
        let kind = request.kind.map(ClipboardKind::as_str).unwrap_or("");
        let dest = request.image_output_path.as_deref().unwrap_or("");
        let payload = call_write_or_read(false, kind, dest)?;
        parse_native_reply(&payload)?.into_contents()
    }

    async fn clipboard_clear(&self) -> Result<(), PlatformError> {
        parse_native_reply(&call_clear_or_types(true)?)?.into_unit()
    }

    async fn clipboard_types(&self) -> Result<ClipboardTypes, PlatformError> {
        parse_native_reply(&call_clear_or_types(false)?)?.into_types()
    }
}