1use accesskit::{ActionHandler, ActivationHandler, TreeUpdate};
13use jni::{
14 errors::Result,
15 objects::{GlobalRef, JClass, JObject, WeakRef},
16 sys::{jboolean, jfloat, jint, jlong, JNI_FALSE, JNI_TRUE},
17 JNIEnv, JavaVM, NativeMethod,
18};
19use log::debug;
20use std::{
21 collections::BTreeMap,
22 ffi::c_void,
23 fmt::{Debug, Formatter},
24 sync::{
25 atomic::{AtomicI64, Ordering},
26 Arc, Mutex, OnceLock, Weak,
27 },
28};
29
30use crate::{action::PlatformAction, adapter::Adapter, event::QueuedEvents};
31
32struct InnerInjectingAdapter {
33 adapter: Adapter,
34 activation_handler: Box<dyn ActivationHandler + Send>,
35 action_handler: Box<dyn ActionHandler + Send>,
36}
37
38impl Debug for InnerInjectingAdapter {
39 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
40 f.debug_struct("InnerInjectingAdapter")
41 .field("adapter", &self.adapter)
42 .field("activation_handler", &"ActivationHandler")
43 .field("action_handler", &"ActionHandler")
44 .finish()
45 }
46}
47
48impl InnerInjectingAdapter {
49 fn create_accessibility_node_info<'local>(
50 &mut self,
51 env: &mut JNIEnv<'local>,
52 host: &JObject,
53 virtual_view_id: jint,
54 ) -> JObject<'local> {
55 self.adapter.create_accessibility_node_info(
56 &mut *self.activation_handler,
57 env,
58 host,
59 virtual_view_id,
60 )
61 }
62
63 fn find_focus<'local>(
64 &mut self,
65 env: &mut JNIEnv<'local>,
66 host: &JObject,
67 focus_type: jint,
68 ) -> JObject<'local> {
69 self.adapter
70 .find_focus(&mut *self.activation_handler, env, host, focus_type)
71 }
72
73 fn perform_action(
74 &mut self,
75 virtual_view_id: jint,
76 action: &PlatformAction,
77 ) -> Option<QueuedEvents> {
78 self.adapter
79 .perform_action(&mut *self.action_handler, virtual_view_id, action)
80 }
81
82 fn on_hover_event(&mut self, action: jint, x: jfloat, y: jfloat) -> Option<QueuedEvents> {
83 self.adapter
84 .on_hover_event(&mut *self.activation_handler, action, x, y)
85 }
86}
87
88static NEXT_HANDLE: AtomicI64 = AtomicI64::new(0);
89static HANDLE_MAP: Mutex<BTreeMap<jlong, Weak<Mutex<InnerInjectingAdapter>>>> =
90 Mutex::new(BTreeMap::new());
91
92fn inner_adapter_from_handle(handle: jlong) -> Option<Arc<Mutex<InnerInjectingAdapter>>> {
93 let handle_map_guard = HANDLE_MAP.lock().unwrap();
94 handle_map_guard.get(&handle).and_then(Weak::upgrade)
95}
96
97static NEXT_CALLBACK_HANDLE: AtomicI64 = AtomicI64::new(0);
98#[allow(clippy::type_complexity)]
99static CALLBACK_MAP: Mutex<
100 BTreeMap<jlong, Box<dyn FnOnce(&mut JNIEnv, &JClass, &JObject) + Send>>,
101> = Mutex::new(BTreeMap::new());
102
103fn post_to_ui_thread(
104 env: &mut JNIEnv,
105 delegate_class: &JClass,
106 host: &JObject,
107 callback: impl FnOnce(&mut JNIEnv, &JClass, &JObject) + Send + 'static,
108) {
109 let handle = NEXT_CALLBACK_HANDLE.fetch_add(1, Ordering::Relaxed);
110 CALLBACK_MAP
111 .lock()
112 .unwrap()
113 .insert(handle, Box::new(callback));
114 let runnable = env
115 .call_static_method(
116 delegate_class,
117 "newCallback",
118 "(Landroid/view/View;J)Ljava/lang/Runnable;",
119 &[host.into(), handle.into()],
120 )
121 .unwrap()
122 .l()
123 .unwrap();
124 env.call_method(
125 host,
126 "post",
127 "(Ljava/lang/Runnable;)Z",
128 &[(&runnable).into()],
129 )
130 .unwrap();
131}
132
133extern "system" fn run_callback<'local>(
134 mut env: JNIEnv<'local>,
135 class: JClass<'local>,
136 host: JObject<'local>,
137 handle: jlong,
138) {
139 let Some(callback) = CALLBACK_MAP.lock().unwrap().remove(&handle) else {
140 return;
141 };
142 callback(&mut env, &class, &host);
143}
144
145extern "system" fn create_accessibility_node_info<'local>(
146 mut env: JNIEnv<'local>,
147 _class: JClass<'local>,
148 adapter_handle: jlong,
149 host: JObject<'local>,
150 virtual_view_id: jint,
151) -> JObject<'local> {
152 let Some(inner_adapter) = inner_adapter_from_handle(adapter_handle) else {
153 return JObject::null();
154 };
155 let mut inner_adapter = inner_adapter.lock().unwrap();
156 inner_adapter.create_accessibility_node_info(&mut env, &host, virtual_view_id)
157}
158
159extern "system" fn find_focus<'local>(
160 mut env: JNIEnv<'local>,
161 _class: JClass<'local>,
162 adapter_handle: jlong,
163 host: JObject<'local>,
164 focus_type: jint,
165) -> JObject<'local> {
166 let Some(inner_adapter) = inner_adapter_from_handle(adapter_handle) else {
167 return JObject::null();
168 };
169 let mut inner_adapter = inner_adapter.lock().unwrap();
170 inner_adapter.find_focus(&mut env, &host, focus_type)
171}
172
173extern "system" fn perform_action<'local>(
174 mut env: JNIEnv<'local>,
175 _class: JClass<'local>,
176 adapter_handle: jlong,
177 host: JObject<'local>,
178 virtual_view_id: jint,
179 action: jint,
180 arguments: JObject<'local>,
181) -> jboolean {
182 let Some(action) = PlatformAction::from_java(&mut env, action, &arguments) else {
183 return JNI_FALSE;
184 };
185 let Some(inner_adapter) = inner_adapter_from_handle(adapter_handle) else {
186 return JNI_FALSE;
187 };
188 let mut inner_adapter = inner_adapter.lock().unwrap();
189 let Some(events) = inner_adapter.perform_action(virtual_view_id, &action) else {
190 return JNI_FALSE;
191 };
192 drop(inner_adapter);
193 events.raise(&mut env, &host);
194 JNI_TRUE
195}
196
197extern "system" fn on_hover_event<'local>(
198 mut env: JNIEnv<'local>,
199 _class: JClass<'local>,
200 adapter_handle: jlong,
201 host: JObject<'local>,
202 action: jint,
203 x: jfloat,
204 y: jfloat,
205) -> jboolean {
206 let Some(inner_adapter) = inner_adapter_from_handle(adapter_handle) else {
207 return JNI_FALSE;
208 };
209 let mut inner_adapter = inner_adapter.lock().unwrap();
210 let Some(events) = inner_adapter.on_hover_event(action, x, y) else {
211 return JNI_FALSE;
212 };
213 drop(inner_adapter);
214 events.raise(&mut env, &host);
215 JNI_TRUE
216}
217
218fn delegate_class(env: &mut JNIEnv) -> &'static JClass<'static> {
219 static CLASS: OnceLock<GlobalRef> = OnceLock::new();
220 let global = CLASS.get_or_init(|| {
221 #[cfg(feature = "embedded-dex")]
222 let class = {
223 let dex_class_loader_class = env
224 .find_class("dalvik/system/InMemoryDexClassLoader")
225 .unwrap();
226 let dex_bytes = include_bytes!("../classes.dex");
227 let dex_buffer = unsafe {
228 env.new_direct_byte_buffer(dex_bytes.as_ptr() as *mut u8, dex_bytes.len())
229 }
230 .unwrap();
231 let dex_class_loader = env
232 .new_object(
233 &dex_class_loader_class,
234 "(Ljava/nio/ByteBuffer;Ljava/lang/ClassLoader;)V",
235 &[(&dex_buffer).into(), (&JObject::null()).into()],
236 )
237 .unwrap();
238 let class_name = env.new_string("dev.accesskit.android.Delegate").unwrap();
239 let class_obj = env
240 .call_method(
241 &dex_class_loader,
242 "loadClass",
243 "(Ljava/lang/String;)Ljava/lang/Class;",
244 &[(&class_name).into()],
245 )
246 .unwrap()
247 .l()
248 .unwrap();
249 JClass::from(class_obj)
250 };
251 #[cfg(not(feature = "embedded-dex"))]
252 let class = env.find_class("dev/accesskit/android/Delegate").unwrap();
253 env.register_native_methods(
254 &class,
255 &[
256 NativeMethod {
257 name: "runCallback".into(),
258 sig: "(Landroid/view/View;J)V".into(),
259 fn_ptr: run_callback as *mut c_void,
260 },
261 NativeMethod {
262 name: "createAccessibilityNodeInfo".into(),
263 sig:
264 "(JLandroid/view/View;I)Landroid/view/accessibility/AccessibilityNodeInfo;"
265 .into(),
266 fn_ptr: create_accessibility_node_info as *mut c_void,
267 },
268 NativeMethod {
269 name: "findFocus".into(),
270 sig:
271 "(JLandroid/view/View;I)Landroid/view/accessibility/AccessibilityNodeInfo;"
272 .into(),
273 fn_ptr: find_focus as *mut c_void,
274 },
275 NativeMethod {
276 name: "performAction".into(),
277 sig: "(JLandroid/view/View;IILandroid/os/Bundle;)Z".into(),
278 fn_ptr: perform_action as *mut c_void,
279 },
280 NativeMethod {
281 name: "onHoverEvent".into(),
282 sig: "(JLandroid/view/View;IFF)Z".into(),
283 fn_ptr: on_hover_event as *mut c_void,
284 },
285 ],
286 )
287 .unwrap();
288 env.new_global_ref(class).unwrap()
289 });
290 global.as_obj().into()
291}
292
293pub struct InjectingAdapter {
305 vm: JavaVM,
306 delegate_class: &'static JClass<'static>,
307 host: WeakRef,
308 handle: jlong,
309 inner: Arc<Mutex<InnerInjectingAdapter>>,
310}
311
312impl Debug for InjectingAdapter {
313 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
314 f.debug_struct("InnerInjectingAdapter")
315 .field("vm", &self.vm)
316 .field("delegate_class", &self.delegate_class)
317 .field("host", &"WeakRef")
318 .field("handle", &self.handle)
319 .field("inner", &self.inner)
320 .finish()
321 }
322}
323
324impl InjectingAdapter {
325 pub fn new(
326 env: &mut JNIEnv,
327 host: &JObject,
328 activation_handler: impl 'static + ActivationHandler + Send,
329 action_handler: impl 'static + ActionHandler + Send,
330 ) -> Self {
331 let inner = Arc::new(Mutex::new(InnerInjectingAdapter {
332 adapter: Adapter::default(),
333 activation_handler: Box::new(activation_handler),
334 action_handler: Box::new(action_handler),
335 }));
336 let handle = NEXT_HANDLE.fetch_add(1, Ordering::Relaxed);
337 HANDLE_MAP
338 .lock()
339 .unwrap()
340 .insert(handle, Arc::downgrade(&inner));
341 let delegate_class = delegate_class(env);
342 post_to_ui_thread(
343 env,
344 delegate_class,
345 host,
346 move |env, delegate_class, host| {
347 let prev_delegate = env
348 .call_method(
349 host,
350 "getAccessibilityDelegate",
351 "()Landroid/view/View$AccessibilityDelegate;",
352 &[],
353 )
354 .unwrap()
355 .l()
356 .unwrap();
357 if !prev_delegate.is_null() {
358 panic!("host already has an accessibility delegate");
359 }
360 let delegate = env
361 .new_object(delegate_class, "(J)V", &[handle.into()])
362 .unwrap();
363 env.call_method(
364 host,
365 "setAccessibilityDelegate",
366 "(Landroid/view/View$AccessibilityDelegate;)V",
367 &[(&delegate).into()],
368 )
369 .unwrap();
370 env.call_method(
371 host,
372 "setOnHoverListener",
373 "(Landroid/view/View$OnHoverListener;)V",
374 &[(&delegate).into()],
375 )
376 .unwrap();
377 },
378 );
379 Self {
380 vm: env.get_java_vm().unwrap(),
381 delegate_class,
382 host: env.new_weak_ref(host).unwrap().unwrap(),
383 handle,
384 inner,
385 }
386 }
387
388 pub fn update_if_active(&mut self, update_factory: impl FnOnce() -> TreeUpdate) {
394 let mut env = self.vm.get_env().unwrap();
395 let Some(host) = self.host.upgrade_local(&env).unwrap() else {
396 return;
397 };
398 let mut inner = self.inner.lock().unwrap();
399 let Some(events) = inner.adapter.update_if_active(update_factory) else {
400 return;
401 };
402 drop(inner);
403 post_to_ui_thread(
404 &mut env,
405 self.delegate_class,
406 &host,
407 |env, _delegate_class, host| {
408 events.raise(env, host);
409 },
410 );
411 }
412}
413
414impl Drop for InjectingAdapter {
415 fn drop(&mut self) {
416 fn drop_impl(env: &mut JNIEnv, delegate_class: &JClass, host: &WeakRef) -> Result<()> {
417 let Some(host) = host.upgrade_local(env)? else {
418 return Ok(());
419 };
420 post_to_ui_thread(env, delegate_class, &host, |env, delegate_class, host| {
421 let prev_delegate = env
422 .call_method(
423 host,
424 "getAccessibilityDelegate",
425 "()Landroid/view/View$AccessibilityDelegate;",
426 &[],
427 )
428 .unwrap()
429 .l()
430 .unwrap();
431 if prev_delegate.is_null()
432 && !env.is_instance_of(&prev_delegate, delegate_class).unwrap()
433 {
434 return;
435 }
436 let null = JObject::null();
437 env.call_method(
438 host,
439 "setAccessibilityDelegate",
440 "(Landroid/view/View$AccessibilityDelegate;)V",
441 &[(&null).into()],
442 )
443 .unwrap();
444 env.call_method(
445 host,
446 "setOnHoverListener",
447 "(Landroid/view/View$OnHoverListener;)V",
448 &[(&null).into()],
449 )
450 .unwrap();
451 });
452 Ok(())
453 }
454
455 let res = match self.vm.get_env() {
456 Ok(mut env) => drop_impl(&mut env, self.delegate_class, &self.host),
457 Err(_) => self
458 .vm
459 .attach_current_thread()
460 .and_then(|mut env| drop_impl(&mut env, self.delegate_class, &self.host)),
461 };
462
463 if let Err(err) = res {
464 debug!("error dropping InjectingAdapter: {:#?}", err);
465 }
466
467 HANDLE_MAP.lock().unwrap().remove(&self.handle);
468 }
469}