android_activity/native_activity/
mod.rs1use std::collections::HashMap;
2use std::marker::PhantomData;
3use std::panic::AssertUnwindSafe;
4use std::ptr;
5use std::sync::{Arc, Mutex, RwLock, Weak};
6use std::time::Duration;
7
8use jni::objects::JObject;
9use jni::JavaVM;
10use libc::c_void;
11use log::{error, trace};
12use ndk::input_queue::InputQueue;
13use ndk::{asset::AssetManager, native_window::NativeWindow};
14
15use crate::error::InternalResult;
16use crate::main_callbacks::MainCallbacks;
17use crate::sdk::{Activity, Context, InputMethodManager};
18use crate::{
19 util, AndroidApp, AndroidAppWaker, ConfigurationRef, InputStatus, MainEvent, PollEvent, Rect,
20 WindowManagerFlags,
21};
22
23pub mod input;
24use crate::input::{
25 device_key_character_map, Axis, ImeOptions, InputType, KeyCharacterMap, TextInputAction,
26 TextInputState, TextSpan,
27};
28
29mod glue;
30use self::glue::NativeActivityGlue;
31
32pub const LOOPER_ID_MAIN: libc::c_int = 1;
33pub const LOOPER_ID_INPUT: libc::c_int = 2;
34#[derive(Debug)]
40pub struct StateSaver<'a> {
41 app: &'a AndroidAppInner,
42}
43
44impl<'a> StateSaver<'a> {
45 pub fn store(&self, state: &'a [u8]) {
48 self.app.native_activity.set_saved_state(state);
49 }
50}
51
52#[derive(Debug)]
56pub struct StateLoader<'a> {
57 app: &'a AndroidAppInner,
58}
59impl StateLoader<'_> {
60 pub fn load(&self) -> Option<Vec<u8>> {
62 self.app.native_activity.saved_state()
63 }
64}
65
66impl AndroidApp {
67 pub(crate) fn new(
68 jvm: JavaVM,
69 main_looper: ndk::looper::ForeignLooper,
70 main_callbacks: MainCallbacks,
71 app_asset_manager: AssetManager,
72 native_activity: NativeActivityGlue,
73 jni_activity: &JObject,
74 ) -> Self {
75 jvm.with_local_frame(10, |env| -> jni::errors::Result<_> {
76 if let Err(err) = crate::sdk::jni_init(env) {
77 panic!("Failed to init JNI bindings: {err:?}");
78 };
79
80 let looper = unsafe {
81 let ptr = ndk_sys::ALooper_prepare(
82 ndk_sys::ALOOPER_PREPARE_ALLOW_NON_CALLBACKS as libc::c_int,
83 );
84 ndk::looper::ForeignLooper::from_ptr(ptr::NonNull::new(ptr).unwrap())
85 };
86
87 let activity = env
91 .new_global_ref(jni_activity)
92 .expect("Failed to create global ref for Activity instance");
93
94 let app = Self {
95 inner: Arc::new(RwLock::new(AndroidAppInner {
96 jvm: jvm.clone(),
97 main_looper,
98 main_callbacks,
99 app_asset_manager,
100 native_activity,
101 activity,
102 looper,
103 key_maps: Mutex::new(HashMap::new()),
104 input_receiver: Mutex::new(None),
105 })),
106 };
107
108 {
109 let guard = app.inner.write().unwrap();
110
111 let main_fd = guard.native_activity.cmd_read_fd();
112 unsafe {
113 ndk_sys::ALooper_addFd(
114 guard.looper.ptr().as_ptr(),
115 main_fd,
116 LOOPER_ID_MAIN,
117 ndk_sys::ALOOPER_EVENT_INPUT as libc::c_int,
118 None,
119 ptr::null_mut(),
121 );
122 }
123 }
124
125 Ok(app)
126 })
127 .expect("Failed to create AndroidApp instance")
128 }
129}
130
131#[derive(Debug)]
132pub(crate) struct AndroidAppInner {
133 pub(crate) jvm: JavaVM,
134
135 pub(crate) native_activity: NativeActivityGlue,
136
137 activity: jni::refs::Global<jni::objects::JObject<'static>>,
138
139 main_callbacks: MainCallbacks,
140
141 looper: ndk::looper::ForeignLooper,
143
144 main_looper: ndk::looper::ForeignLooper,
147
148 key_maps: Mutex<HashMap<i32, KeyCharacterMap>>,
152
153 input_receiver: Mutex<Option<Weak<InputReceiver>>>,
157
158 app_asset_manager: AssetManager,
165}
166
167impl AndroidAppInner {
168 pub(crate) fn activity_as_ptr(&self) -> *mut c_void {
169 self.activity.as_raw() as *mut c_void
173 }
174
175 pub(crate) fn looper_as_ptr(&self) -> *mut ndk_sys::ALooper {
176 self.looper.ptr().as_ptr()
177 }
178
179 pub fn java_main_looper(&self) -> ndk::looper::ForeignLooper {
180 self.main_looper.clone()
181 }
182
183 pub fn native_window(&self) -> Option<NativeWindow> {
184 self.native_activity.mutex.lock().unwrap().window.clone()
185 }
186
187 pub fn poll_events<F>(&self, timeout: Option<Duration>, mut callback: F)
188 where
189 F: FnMut(PollEvent<'_>),
190 {
191 trace!("poll_events");
192
193 unsafe {
194 let mut fd: i32 = 0;
195 let mut events: i32 = 0;
196 let mut source: *mut c_void = ptr::null_mut();
197
198 let timeout_milliseconds = if let Some(timeout) = timeout {
199 timeout.as_millis() as i32
200 } else {
201 -1
202 };
203
204 trace!("Calling ALooper_pollOnce, timeout = {timeout_milliseconds}");
205 assert_eq!(
206 ndk_sys::ALooper_forThread(),
207 self.looper_as_ptr(),
208 "Application tried to poll events from non-main thread"
209 );
210 let id = ndk_sys::ALooper_pollOnce(
211 timeout_milliseconds,
212 &mut fd,
213 &mut events,
214 &mut source as *mut *mut c_void,
215 );
216 trace!("pollOnce id = {id}");
217 match id {
218 ndk_sys::ALOOPER_POLL_WAKE => {
219 trace!("ALooper_pollOnce returned POLL_WAKE");
220 callback(PollEvent::Wake);
221 }
222 ndk_sys::ALOOPER_POLL_CALLBACK => {
223 error!("Spurious ALOOPER_POLL_CALLBACK from ALooper_pollOnce() (ignored)");
226 }
227 ndk_sys::ALOOPER_POLL_TIMEOUT => {
228 trace!("ALooper_pollOnce returned POLL_TIMEOUT");
229 callback(PollEvent::Timeout);
230 }
231 ndk_sys::ALOOPER_POLL_ERROR => {
232 panic!("ALooper_pollOnce returned POLL_ERROR");
235 }
236 id if id >= 0 => {
237 match id {
238 LOOPER_ID_MAIN => {
239 trace!("ALooper_pollOnce returned ID_MAIN");
240 if let Some(ipc_cmd) = self.native_activity.read_cmd() {
241 let main_cmd = match ipc_cmd {
242 glue::AppCmd::InputQueueChanged => None,
246
247 glue::AppCmd::InitWindow => Some(MainEvent::InitWindow {}),
248 glue::AppCmd::TermWindow => Some(MainEvent::TerminateWindow {}),
249 glue::AppCmd::WindowResized => {
250 Some(MainEvent::WindowResized {})
251 }
252 glue::AppCmd::WindowRedrawNeeded => {
253 Some(MainEvent::RedrawNeeded {})
254 }
255 glue::AppCmd::ContentRectChanged => {
256 Some(MainEvent::ContentRectChanged {})
257 }
258 glue::AppCmd::GainedFocus => Some(MainEvent::GainedFocus),
259 glue::AppCmd::LostFocus => Some(MainEvent::LostFocus),
260 glue::AppCmd::ConfigChanged => {
261 Some(MainEvent::ConfigChanged {})
262 }
263 glue::AppCmd::LowMemory => Some(MainEvent::LowMemory),
264 glue::AppCmd::Start => Some(MainEvent::Start),
265 glue::AppCmd::Resume => Some(MainEvent::Resume {
266 loader: StateLoader { app: self },
267 }),
268 glue::AppCmd::SaveState => Some(MainEvent::SaveState {
269 saver: StateSaver { app: self },
270 }),
271 glue::AppCmd::Pause => Some(MainEvent::Pause),
272 glue::AppCmd::Stop => Some(MainEvent::Stop),
273 glue::AppCmd::Destroy => Some(MainEvent::Destroy),
274 };
275
276 trace!("Calling pre_exec_cmd({ipc_cmd:#?})");
277 self.native_activity.pre_exec_cmd(
278 ipc_cmd,
279 self.looper_as_ptr(),
280 LOOPER_ID_INPUT,
281 );
282
283 if let Some(main_cmd) = main_cmd {
284 trace!("Invoking callback for ID_MAIN command = {main_cmd:?}");
285 callback(PollEvent::Main(main_cmd));
286 }
287
288 trace!("Calling post_exec_cmd({ipc_cmd:#?})");
289 self.native_activity.post_exec_cmd(ipc_cmd);
290 }
291 }
292 LOOPER_ID_INPUT => {
293 trace!("ALooper_pollOnce returned ID_INPUT");
294
295 self.native_activity.detach_input_queue_from_looper();
300 callback(PollEvent::Main(MainEvent::InputAvailable))
301 }
302 _ => {
303 error!("Ignoring spurious ALooper event source: id = {id}, fd = {fd}, events = {events:?}, data = {source:?}");
304 }
305 }
306 }
307 _ => {
308 error!("Spurious ALooper_pollOnce return value {id} (ignored)");
309 }
310 }
311 }
312 }
313
314 pub fn create_waker(&self) -> AndroidAppWaker {
315 unsafe { AndroidAppWaker::new(self.looper_as_ptr()) }
317 }
318
319 pub fn run_on_java_main_thread<F>(&self, f: Box<F>)
320 where
321 F: FnOnce() + Send + 'static,
322 {
323 self.main_callbacks.run_on_java_main_thread(f);
324 }
325
326 pub fn config(&self) -> ConfigurationRef {
327 self.native_activity.config()
328 }
329
330 pub fn content_rect(&self) -> Rect {
331 self.native_activity.content_rect()
332 }
333
334 pub fn asset_manager(&self) -> AssetManager {
335 unsafe { AssetManager::from_ptr(self.app_asset_manager.ptr()) }
340 }
341
342 pub fn set_window_flags(
343 &self,
344 add_flags: WindowManagerFlags,
345 remove_flags: WindowManagerFlags,
346 ) {
347 let guard = self.native_activity.mutex.lock().unwrap();
348 let na = guard.activity;
349 if na.is_null() {
350 log::error!("Can't set window flags after NativeActivity has been destroyed");
351 return;
352 }
353
354 let na_mut = na;
355 unsafe {
356 ndk_sys::ANativeActivity_setWindowFlags(
357 na_mut.cast(),
358 add_flags.bits(),
359 remove_flags.bits(),
360 );
361 }
362 }
363
364 pub fn show_soft_input(&self, show_implicit: bool) {
366 let guard = self.native_activity.mutex.lock().unwrap();
367 let na = guard.activity;
368 if na.is_null() {
369 log::error!("Can't show soft input after NativeActivity has been destroyed");
370 return;
371 }
372
373 let res = self
376 .jvm
377 .attach_current_thread(|env| -> jni::errors::Result<()> {
378 let activity = env.as_cast::<Activity>(self.activity.as_ref())?;
379
380 let ims = Context::INPUT_METHOD_SERVICE(env)?;
381 let im_manager = activity.as_context().get_system_service(env, ims)?;
382 let im_manager = InputMethodManager::cast_local(env, im_manager)?;
383 let jni_window = activity.get_window(env)?;
384 let view = jni_window.get_decor_view(env)?;
385 let flags = if show_implicit {
386 ndk_sys::ANATIVEACTIVITY_SHOW_SOFT_INPUT_IMPLICIT as i32
387 } else {
388 0
389 };
390 im_manager.show_soft_input(env, view, flags)?;
391 Ok(())
392 });
393 if let Err(err) = res {
394 log::warn!("Failed to show soft input: {err:?}");
395 }
396 }
397
398 pub fn hide_soft_input(&self, hide_implicit_only: bool) {
400 let guard = self.native_activity.mutex.lock().unwrap();
401 let na = guard.activity;
402 if na.is_null() {
403 log::error!("Can't hide soft input after NativeActivity has been destroyed");
404 return;
405 }
406
407 let res = self
410 .jvm
411 .attach_current_thread(|env| -> jni::errors::Result<()> {
412 let activity = env.as_cast::<Activity>(self.activity.as_ref())?;
413
414 let ims = Context::INPUT_METHOD_SERVICE(env)?;
415 let imm_obj = activity.as_context().get_system_service(env, ims)?;
416 let imm = InputMethodManager::cast_local(env, imm_obj)?;
417
418 let window = activity.get_window(env)?;
419 let decor = window.get_decor_view(env)?;
420 let token = decor.get_window_token(env)?;
421
422 let flags = if hide_implicit_only { 1 } else { 0 };
424
425 let _hidden = imm.hide_soft_input_from_window(env, token, flags)?;
426 Ok(())
427 });
428
429 if let Err(err) = res {
430 error!("Failed to hide soft input: {err:?}");
431 }
432 }
433
434 pub fn text_input_state(&self) -> TextInputState {
436 TextInputState {
437 text: String::new(),
438 selection: TextSpan { start: 0, end: 0 },
439 compose_region: None,
440 }
441 }
442
443 pub fn set_text_input_state(&self, _state: TextInputState) {
445 }
447
448 pub fn set_ime_editor_info(
450 &self,
451 _input_type: InputType,
452 _action: TextInputAction,
453 _options: ImeOptions,
454 ) {
455 }
457
458 pub fn device_key_character_map(&self, device_id: i32) -> InternalResult<KeyCharacterMap> {
459 let mut guard = self.key_maps.lock().unwrap();
460
461 let key_map = match guard.entry(device_id) {
462 std::collections::hash_map::Entry::Occupied(occupied) => occupied.get().clone(),
463 std::collections::hash_map::Entry::Vacant(vacant) => {
464 let character_map = device_key_character_map(self.jvm.clone(), device_id)?;
465 vacant.insert(character_map.clone());
466 character_map
467 }
468 };
469
470 Ok(key_map)
471 }
472
473 pub fn enable_motion_axis(&self, _axis: Axis) {
474 }
476
477 pub fn disable_motion_axis(&self, _axis: Axis) {
478 }
480
481 pub fn input_events_receiver(&self) -> InternalResult<Arc<InputReceiver>> {
482 let mut guard = self.input_receiver.lock().unwrap();
483
484 if let Some(receiver) = &*guard {
485 if receiver.strong_count() > 0 {
486 return Err(crate::error::InternalAppError::InputUnavailable);
487 }
488 }
489 *guard = None;
490
491 let queue = self
495 .native_activity
496 .looper_attached_input_queue(self.looper_as_ptr(), LOOPER_ID_INPUT);
497
498 let receiver = Arc::new(InputReceiver { queue });
502
503 *guard = Some(Arc::downgrade(&receiver));
504 Ok(receiver)
505 }
506
507 pub fn internal_data_path(&self) -> Option<std::path::PathBuf> {
508 let guard = self.native_activity.mutex.lock().unwrap();
509 let na = guard.activity;
510 if na.is_null() {
511 log::error!("Can't get internal data path after NativeActivity has been destroyed");
512 return None;
513 }
514 unsafe { util::try_get_path_from_ptr((*na).internalDataPath) }
515 }
516
517 pub fn external_data_path(&self) -> Option<std::path::PathBuf> {
518 let guard = self.native_activity.mutex.lock().unwrap();
519 let na = guard.activity;
520 if na.is_null() {
521 log::error!("Can't get external data path after NativeActivity has been destroyed");
522 return None;
523 }
524 unsafe { util::try_get_path_from_ptr((*na).externalDataPath) }
525 }
526
527 pub fn obb_path(&self) -> Option<std::path::PathBuf> {
528 let guard = self.native_activity.mutex.lock().unwrap();
529 let na = guard.activity;
530 if na.is_null() {
531 log::error!("Can't get OBB path after NativeActivity has been destroyed");
532 return None;
533 }
534 unsafe { util::try_get_path_from_ptr((*na).obbPath) }
535 }
536}
537
538#[derive(Debug)]
539pub(crate) struct InputReceiver {
540 queue: Option<InputQueue>,
541}
542
543impl From<Arc<InputReceiver>> for InputIteratorInner<'_> {
544 fn from(receiver: Arc<InputReceiver>) -> Self {
545 Self {
546 receiver,
547 _lifetime: PhantomData,
548 }
549 }
550}
551
552pub(crate) struct InputIteratorInner<'a> {
553 receiver: Arc<InputReceiver>,
555 _lifetime: PhantomData<&'a ()>,
556}
557
558impl InputIteratorInner<'_> {
559 pub(crate) fn next<F>(&self, callback: F) -> bool
560 where
561 F: FnOnce(&input::InputEvent) -> InputStatus,
562 {
563 let Some(queue) = &self.receiver.queue else {
564 log::trace!("no queue available for events");
565 return false;
566 };
567
568 if let Ok(Some(ndk_event)) = queue.event() {
575 log::trace!("queue: got event: {ndk_event:?}");
576
577 if let Some(ndk_event) = queue.pre_dispatch(ndk_event) {
578 let event = match ndk_event {
579 ndk::event::InputEvent::MotionEvent(e) => {
580 input::InputEvent::MotionEvent(input::MotionEvent::new(e))
581 }
582 ndk::event::InputEvent::KeyEvent(e) => {
583 input::InputEvent::KeyEvent(input::KeyEvent::new(e))
584 }
585 _ => todo!("NDK added a new type"),
586 };
587
588 let result = std::panic::catch_unwind(AssertUnwindSafe(|| callback(&event)));
591
592 let ndk_event = match event {
593 input::InputEvent::MotionEvent(e) => {
594 ndk::event::InputEvent::MotionEvent(e.into_ndk_event())
595 }
596 input::InputEvent::KeyEvent(e) => {
597 ndk::event::InputEvent::KeyEvent(e.into_ndk_event())
598 }
599 _ => unreachable!(),
600 };
601
602 let handled = match result {
603 Ok(handled) => handled,
604 Err(payload) => {
605 log::error!("Calling `finish_event` after panic in input event handler, to try and avoid being killed via an ANR");
606 queue.finish_event(ndk_event, false);
607 std::panic::resume_unwind(payload);
608 }
609 };
610
611 log::trace!("queue: finishing event");
612 queue.finish_event(ndk_event, handled == InputStatus::Handled);
613 }
614
615 true
616 } else {
617 log::trace!("queue: no more events");
618 false
619 }
620 }
621}