use jni::sys::jint;
use jni::{objects::Global, JavaVM};
use crate::error::{AppError, InternalAppError, InternalResult};
use crate::input::{Keycode, MetaState};
use crate::jni_utils;
#[derive(
Debug, Clone, Copy, PartialEq, Eq, Hash, num_enum::FromPrimitive, num_enum::IntoPrimitive,
)]
#[non_exhaustive]
#[repr(u32)]
pub enum KeyboardType {
Numeric,
Predictive,
Alpha,
Full,
SpecialFunction,
#[doc(hidden)]
#[num_enum(catch_all)]
__Unknown(u32),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum KeyMapChar {
None,
Unicode(char),
CombiningAccent(char),
}
jni::bind_java_type! {
pub(crate) AKeyCharacterMap => "android.view.KeyCharacterMap",
methods {
priv fn _get(key_code: jint, meta_state: jint) -> jint,
priv static fn _get_dead_char(accent_char: jint, base_char: jint) -> jint,
priv fn _get_keyboard_type() -> jint,
}
}
impl AKeyCharacterMap<'_> {
pub(crate) fn get(
&self,
env: &mut jni::Env,
key_code: jint,
meta_state: jint,
) -> Result<jint, InternalAppError> {
self._get(env, key_code, meta_state)
.map_err(|err| jni_utils::clear_and_map_exception_to_err(env, err))
}
pub(crate) fn get_dead_char(
env: &mut jni::Env,
accent_char: jint,
base_char: jint,
) -> Result<jint, InternalAppError> {
Self::_get_dead_char(env, accent_char, base_char)
.map_err(|err| jni_utils::clear_and_map_exception_to_err(env, err))
}
pub(crate) fn get_keyboard_type(&self, env: &mut jni::Env) -> Result<jint, InternalAppError> {
self._get_keyboard_type(env)
.map_err(|err| jni_utils::clear_and_map_exception_to_err(env, err))
}
}
jni::bind_java_type! {
pub(crate) AInputDevice => "android.view.InputDevice",
type_map {
AKeyCharacterMap => "android.view.KeyCharacterMap",
},
methods {
static fn get_device(id: jint) -> AInputDevice,
fn get_key_character_map() -> AKeyCharacterMap,
}
}
#[derive(Debug)]
pub struct KeyCharacterMap {
jvm: JavaVM,
key_map: Global<AKeyCharacterMap<'static>>,
}
impl Clone for KeyCharacterMap {
fn clone(&self) -> Self {
let jvm = self.jvm.clone();
jvm.attach_current_thread(|env| -> jni::errors::Result<_> {
Ok(Self {
jvm: jvm.clone(),
key_map: env.new_global_ref(&self.key_map)?,
})
})
.expect("Failed to attach thread to JVM and clone key map")
}
}
impl KeyCharacterMap {
pub(crate) fn new(jvm: JavaVM, key_map: Global<AKeyCharacterMap<'static>>) -> Self {
Self { jvm, key_map }
}
pub fn get(&self, key_code: Keycode, meta_state: MetaState) -> Result<KeyMapChar, AppError> {
let key_code: u32 = key_code.into();
let key_code = key_code as jni::sys::jint;
let meta_state: u32 = meta_state.0;
let meta_state = meta_state as jni::sys::jint;
let vm = self.jvm.clone();
vm.attach_current_thread(|env| -> InternalResult<_> {
let unicode = self.key_map.get(env, key_code, meta_state)?;
let unicode = unicode as u32;
const COMBINING_ACCENT: u32 = 0x80000000;
const COMBINING_ACCENT_MASK: u32 = !COMBINING_ACCENT;
if unicode == 0 {
Ok(KeyMapChar::None)
} else if unicode & COMBINING_ACCENT == COMBINING_ACCENT {
let accent = unicode & COMBINING_ACCENT_MASK;
Ok(KeyMapChar::CombiningAccent(unsafe {
char::from_u32_unchecked(accent)
}))
} else {
Ok(KeyMapChar::Unicode(unsafe {
char::from_u32_unchecked(unicode)
}))
}
})
.map_err(|err| {
let err: InternalAppError = err;
err.into()
})
}
pub fn get_dead_char(
&self,
accent_char: char,
base_char: char,
) -> Result<Option<char>, AppError> {
let accent_char = accent_char as jni::sys::jint;
let base_char = base_char as jni::sys::jint;
let vm = self.jvm.clone();
vm.attach_current_thread(|env| -> InternalResult<_> {
let unicode = AKeyCharacterMap::get_dead_char(env, accent_char, base_char)?;
let unicode = unicode as u32;
Ok(if unicode == 0 {
None
} else {
Some(unsafe { char::from_u32_unchecked(unicode) })
})
})
.map_err(|err| {
let err: InternalAppError = err;
err.into()
})
}
pub fn get_keyboard_type(&self) -> Result<KeyboardType, AppError> {
let vm = self.jvm.clone();
vm.attach_current_thread(|env| -> InternalResult<_> {
let keyboard_type = self.key_map.get_keyboard_type(env)?;
let keyboard_type = keyboard_type as u32;
Ok(keyboard_type.into())
})
.map_err(|err| {
let err: InternalAppError = err;
err.into()
})
}
}
fn device_key_character_map_with_env(
env: &mut jni::Env<'_>,
device_id: i32,
) -> jni::errors::Result<KeyCharacterMap> {
let device = AInputDevice::get_device(env, device_id)?;
if device.is_null() {
log::error!("No input device with id {}", device_id);
return Err(jni::errors::Error::WrongObjectType);
}
let character_map = device.get_key_character_map(env)?;
let character_map = env.new_global_ref(character_map)?;
let jvm = JavaVM::singleton().expect("Failed to get singleton JavaVM");
Ok(KeyCharacterMap::new(jvm, character_map))
}
pub(crate) fn device_key_character_map(
jvm: JavaVM,
device_id: i32,
) -> InternalResult<KeyCharacterMap> {
jvm.attach_current_thread(|env| {
if device_id == 0 {
return Err(InternalAppError::JniBadArgument(
"Can't get key character map for non-physical device_id 0".into(),
));
}
device_key_character_map_with_env(env, device_id)
.map_err(|err| jni_utils::clear_and_map_exception_to_err(env, err))
})
}