#![allow(unsafe_code)]
use crate::android_overlay_window::{
AndroidOverlayEventQueue, AndroidOverlayEventQueueHandle, AndroidOverlayPointerAction,
AndroidOverlayWindowEvent,
};
use jni::{
objects::{JClass, JObject, JString},
sys::{jfloat, jint, jlong},
Env, EnvUnowned, JavaVM, Outcome,
};
use ndk::native_window::NativeWindow;
use std::sync::Arc;
pub(crate) fn with_android_activity_env<T, F>(
app: &android_activity::AndroidApp,
f: F,
) -> Result<T, String>
where
F: for<'local> FnOnce(&mut Env<'local>, JObject<'local>) -> Result<T, String>,
{
let vm = JavaVM::singleton()
.map_err(|error| format!("Android JavaVM is not available on android_main: {error}"))?;
vm.with_local_frame(16, |env| -> jni::errors::Result<Result<T, String>> {
let raw_activity_global = app.activity_as_ptr() as jni::sys::jobject;
let activity_global = unsafe { env.as_cast_raw::<JObject>(&raw_activity_global)? };
let activity = env.new_local_ref(activity_global.as_ref())?;
Ok(f(env, activity))
})
.map_err(|error| format!("failed to access Android JNI environment: {error}"))?
}
pub(crate) fn clear_pending_android_jni_exception(env: &mut Env<'_>) {
if env.exception_check() {
env.exception_describe();
env.exception_clear();
}
}
pub(crate) fn native_window_from_surface(
env: &mut Env<'_>,
surface: JObject<'_>,
) -> Result<NativeWindow, String> {
unsafe { NativeWindow::from_surface(env.get_raw().cast(), surface.as_raw()) }.ok_or_else(|| {
clear_pending_android_jni_exception(env);
"Android overlay Surface did not provide an ANativeWindow".to_string()
})
}
pub(crate) fn release_android_overlay_event_queue_handle(handle: AndroidOverlayEventQueueHandle) {
release_android_overlay_event_queue_handle_raw(handle.raw());
}
fn release_android_overlay_event_queue_handle_raw(handle: jlong) {
if handle == 0 {
return;
}
unsafe {
drop(Arc::from_raw(
handle as usize as *const AndroidOverlayEventQueue,
));
}
}
fn push_overlay_event_for_handle(handle: jlong, event: AndroidOverlayWindowEvent) {
if handle == 0 {
log::warn!(
"dropped Android overlay event because the Java callback carried no queue handle"
);
return;
}
let Some(queue) = (unsafe { (handle as usize as *const AndroidOverlayEventQueue).as_ref() })
else {
log::warn!("dropped Android overlay event because the queue handle was invalid");
return;
};
queue.push(event);
}
#[doc(hidden)]
#[no_mangle]
pub extern "system" fn Java_dev_cranpose_android_CranposeOverlayWindow_nativeOverlayCreateFailed<
'local,
>(
mut env: EnvUnowned<'local>,
_class: JClass<'local>,
queue_handle: jlong,
message: JString<'local>,
) {
let message = match env
.with_env(|env| -> jni::errors::Result<String> { message.try_to_string(env) })
.into_outcome()
{
Outcome::Ok(message) => message,
Outcome::Err(_) | Outcome::Panic(_) => "Android overlay window creation failed".to_string(),
};
push_overlay_event_for_handle(
queue_handle,
AndroidOverlayWindowEvent::CreateFailed(message),
);
}
#[doc(hidden)]
#[no_mangle]
pub extern "system" fn Java_dev_cranpose_android_CranposeOverlayWindow_nativeOverlaySurfaceChanged<
'local,
>(
mut env: EnvUnowned<'local>,
_class: JClass<'local>,
queue_handle: jlong,
surface: JObject<'local>,
width: jint,
height: jint,
) {
match env
.with_env(|env| -> jni::errors::Result<Result<NativeWindow, String>> {
Ok(native_window_from_surface(env, surface))
})
.into_outcome()
{
Outcome::Ok(Ok(native_window)) if width > 0 && height > 0 => {
push_overlay_event_for_handle(
queue_handle,
AndroidOverlayWindowEvent::SurfaceChanged {
native_window,
width: width as u32,
height: height as u32,
},
);
}
Outcome::Ok(Ok(_)) => {}
Outcome::Ok(Err(message)) => {
push_overlay_event_for_handle(
queue_handle,
AndroidOverlayWindowEvent::CreateFailed(message),
);
}
Outcome::Err(error) => {
push_overlay_event_for_handle(
queue_handle,
AndroidOverlayWindowEvent::CreateFailed(format!(
"failed to access Android overlay Surface: {error}"
)),
);
}
Outcome::Panic(_) => {
push_overlay_event_for_handle(
queue_handle,
AndroidOverlayWindowEvent::CreateFailed(
"failed to access Android overlay Surface".to_string(),
),
);
}
}
}
#[doc(hidden)]
#[no_mangle]
pub extern "system" fn Java_dev_cranpose_android_CranposeOverlayWindow_nativeOverlaySurfaceDestroyed(
_env: EnvUnowned<'_>,
_class: JClass<'_>,
queue_handle: jlong,
) {
push_overlay_event_for_handle(queue_handle, AndroidOverlayWindowEvent::SurfaceDestroyed);
}
#[doc(hidden)]
#[no_mangle]
pub extern "system" fn Java_dev_cranpose_android_CranposeOverlayWindow_nativeOverlayPointer(
_env: EnvUnowned<'_>,
_class: JClass<'_>,
queue_handle: jlong,
action: jint,
x: jfloat,
y: jfloat,
) {
let action = match action {
0 | 5 => AndroidOverlayPointerAction::Down,
1 | 6 => AndroidOverlayPointerAction::Up,
2 => AndroidOverlayPointerAction::Move,
3 => AndroidOverlayPointerAction::Cancel,
_ => return,
};
push_overlay_event_for_handle(
queue_handle,
AndroidOverlayWindowEvent::Pointer { action, x, y },
);
}
#[doc(hidden)]
#[no_mangle]
pub extern "system" fn Java_dev_cranpose_android_CranposeOverlayWindow_nativeOverlayReleaseQueue(
_env: EnvUnowned<'_>,
_class: JClass<'_>,
queue_handle: jlong,
) {
release_android_overlay_event_queue_handle_raw(queue_handle);
}