android_activity/input/
sdk.rs1use jni::sys::jint;
2use jni::{objects::Global, JavaVM};
3
4use crate::error::{AppError, InternalAppError, InternalResult};
5use crate::input::{Keycode, MetaState};
6use crate::jni_utils;
7
8#[derive(
21 Debug, Clone, Copy, PartialEq, Eq, Hash, num_enum::FromPrimitive, num_enum::IntoPrimitive,
22)]
23#[non_exhaustive]
24#[repr(u32)]
25pub enum KeyboardType {
26 Numeric,
32
33 Predictive,
37
38 Alpha,
44
45 Full,
51
52 SpecialFunction,
56
57 #[doc(hidden)]
58 #[num_enum(catch_all)]
59 __Unknown(u32),
60}
61
62#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
65pub enum KeyMapChar {
66 None,
67 Unicode(char),
68 CombiningAccent(char),
69}
70
71jni::bind_java_type! {
72 pub(crate) AKeyCharacterMap => "android.view.KeyCharacterMap",
73 methods {
74 priv fn _get(key_code: jint, meta_state: jint) -> jint,
75 priv static fn _get_dead_char(accent_char: jint, base_char: jint) -> jint,
76 priv fn _get_keyboard_type() -> jint,
77 }
78}
79
80impl AKeyCharacterMap<'_> {
81 pub(crate) fn get(
82 &self,
83 env: &mut jni::Env,
84 key_code: jint,
85 meta_state: jint,
86 ) -> Result<jint, InternalAppError> {
87 self._get(env, key_code, meta_state)
88 .map_err(|err| jni_utils::clear_and_map_exception_to_err(env, err))
89 }
90
91 pub(crate) fn get_dead_char(
92 env: &mut jni::Env,
93 accent_char: jint,
94 base_char: jint,
95 ) -> Result<jint, InternalAppError> {
96 Self::_get_dead_char(env, accent_char, base_char)
97 .map_err(|err| jni_utils::clear_and_map_exception_to_err(env, err))
98 }
99
100 pub(crate) fn get_keyboard_type(&self, env: &mut jni::Env) -> Result<jint, InternalAppError> {
101 self._get_keyboard_type(env)
102 .map_err(|err| jni_utils::clear_and_map_exception_to_err(env, err))
103 }
104}
105
106jni::bind_java_type! {
107 pub(crate) AInputDevice => "android.view.InputDevice",
108 type_map {
109 AKeyCharacterMap => "android.view.KeyCharacterMap",
110 },
111 methods {
112 static fn get_device(id: jint) -> AInputDevice,
113 fn get_key_character_map() -> AKeyCharacterMap,
114 }
115}
116
117#[derive(Debug)]
119pub struct KeyCharacterMap {
120 jvm: JavaVM,
121 key_map: Global<AKeyCharacterMap<'static>>,
122}
123impl Clone for KeyCharacterMap {
124 fn clone(&self) -> Self {
125 let jvm = self.jvm.clone();
126 jvm.attach_current_thread(|env| -> jni::errors::Result<_> {
127 Ok(Self {
128 jvm: jvm.clone(),
129 key_map: env.new_global_ref(&self.key_map)?,
130 })
131 })
132 .expect("Failed to attach thread to JVM and clone key map")
133 }
134}
135
136impl KeyCharacterMap {
137 pub(crate) fn new(jvm: JavaVM, key_map: Global<AKeyCharacterMap<'static>>) -> Self {
138 Self { jvm, key_map }
139 }
140
141 pub fn get(&self, key_code: Keycode, meta_state: MetaState) -> Result<KeyMapChar, AppError> {
154 let key_code: u32 = key_code.into();
155 let key_code = key_code as jni::sys::jint;
156 let meta_state: u32 = meta_state.0;
157 let meta_state = meta_state as jni::sys::jint;
158
159 let vm = self.jvm.clone();
160 vm.attach_current_thread(|env| -> InternalResult<_> {
161 let unicode = self.key_map.get(env, key_code, meta_state)?;
162 let unicode = unicode as u32;
163
164 const COMBINING_ACCENT: u32 = 0x80000000;
165 const COMBINING_ACCENT_MASK: u32 = !COMBINING_ACCENT;
166
167 if unicode == 0 {
168 Ok(KeyMapChar::None)
169 } else if unicode & COMBINING_ACCENT == COMBINING_ACCENT {
170 let accent = unicode & COMBINING_ACCENT_MASK;
171 Ok(KeyMapChar::CombiningAccent(unsafe {
173 char::from_u32_unchecked(accent)
174 }))
175 } else {
176 Ok(KeyMapChar::Unicode(unsafe {
178 char::from_u32_unchecked(unicode)
179 }))
180 }
181 })
182 .map_err(|err| {
183 let err: InternalAppError = err;
184 err.into()
185 })
186 }
187
188 pub fn get_dead_char(
197 &self,
198 accent_char: char,
199 base_char: char,
200 ) -> Result<Option<char>, AppError> {
201 let accent_char = accent_char as jni::sys::jint;
202 let base_char = base_char as jni::sys::jint;
203
204 let vm = self.jvm.clone();
205 vm.attach_current_thread(|env| -> InternalResult<_> {
206 let unicode = AKeyCharacterMap::get_dead_char(env, accent_char, base_char)?;
207 let unicode = unicode as u32;
208
209 Ok(if unicode == 0 {
211 None
212 } else {
213 Some(unsafe { char::from_u32_unchecked(unicode) })
214 })
215 })
216 .map_err(|err| {
217 let err: InternalAppError = err;
218 err.into()
219 })
220 }
221
222 pub fn get_keyboard_type(&self) -> Result<KeyboardType, AppError> {
232 let vm = self.jvm.clone();
233 vm.attach_current_thread(|env| -> InternalResult<_> {
234 let keyboard_type = self.key_map.get_keyboard_type(env)?;
235 let keyboard_type = keyboard_type as u32;
236 Ok(keyboard_type.into())
237 })
238 .map_err(|err| {
239 let err: InternalAppError = err;
240 err.into()
241 })
242 }
243}
244
245fn device_key_character_map_with_env(
246 env: &mut jni::Env<'_>,
247 device_id: i32,
248) -> jni::errors::Result<KeyCharacterMap> {
249 let device = AInputDevice::get_device(env, device_id)?;
250 if device.is_null() {
251 log::error!("No input device with id {}", device_id);
254 return Err(jni::errors::Error::WrongObjectType);
255 }
256 let character_map = device.get_key_character_map(env)?;
257 let character_map = env.new_global_ref(character_map)?;
258 let jvm = JavaVM::singleton().expect("Failed to get singleton JavaVM");
259 Ok(KeyCharacterMap::new(jvm, character_map))
260}
261
262pub(crate) fn device_key_character_map(
263 jvm: JavaVM,
264 device_id: i32,
265) -> InternalResult<KeyCharacterMap> {
266 jvm.attach_current_thread(|env| {
267 if device_id == 0 {
268 return Err(InternalAppError::JniBadArgument(
269 "Can't get key character map for non-physical device_id 0".into(),
270 ));
271 }
272 device_key_character_map_with_env(env, device_id)
273 .map_err(|err| jni_utils::clear_and_map_exception_to_err(env, err))
274 })
275}