android_activity/native_activity/
mod.rs

1#![cfg(any(feature = "native-activity", doc))]
2
3use std::collections::HashMap;
4use std::marker::PhantomData;
5use std::panic::AssertUnwindSafe;
6use std::ptr;
7use std::ptr::NonNull;
8use std::sync::{Arc, Mutex, RwLock, Weak};
9use std::time::Duration;
10
11use libc::c_void;
12use log::{error, trace};
13use ndk::input_queue::InputQueue;
14use ndk::{asset::AssetManager, native_window::NativeWindow};
15
16use crate::error::InternalResult;
17use crate::input::{Axis, KeyCharacterMap, KeyCharacterMapBinding};
18use crate::input::{TextInputState, TextSpan};
19use crate::jni_utils::{self, CloneJavaVM};
20use crate::{
21    util, AndroidApp, ConfigurationRef, InputStatus, MainEvent, PollEvent, Rect, WindowManagerFlags,
22};
23
24pub mod input;
25
26mod glue;
27use self::glue::NativeActivityGlue;
28
29pub const LOOPER_ID_MAIN: libc::c_int = 1;
30pub const LOOPER_ID_INPUT: libc::c_int = 2;
31//pub const LOOPER_ID_USER: ::std::os::raw::c_uint = 3;
32
33/// An interface for saving application state during [MainEvent::SaveState] events
34///
35/// This interface is only available temporarily while handling a [MainEvent::SaveState] event.
36#[derive(Debug)]
37pub struct StateSaver<'a> {
38    app: &'a AndroidAppInner,
39}
40
41impl<'a> StateSaver<'a> {
42    /// Stores the given `state` such that it will be available to load the next
43    /// time that the application resumes.
44    pub fn store(&self, state: &'a [u8]) {
45        self.app.native_activity.set_saved_state(state);
46    }
47}
48
49/// An interface for loading application state during [MainEvent::Resume] events
50///
51/// This interface is only available temporarily while handling a [MainEvent::Resume] event.
52#[derive(Debug)]
53pub struct StateLoader<'a> {
54    app: &'a AndroidAppInner,
55}
56impl<'a> StateLoader<'a> {
57    /// Returns whatever state was saved during the last [MainEvent::SaveState] event or `None`
58    pub fn load(&self) -> Option<Vec<u8>> {
59        self.app.native_activity.saved_state()
60    }
61}
62
63/// A means to wake up the main thread while it is blocked waiting for I/O
64#[derive(Clone)]
65pub struct AndroidAppWaker {
66    // The looper pointer is owned by the android_app and effectively
67    // has a 'static lifetime, and the ALooper_wake C API is thread
68    // safe, so this can be cloned safely and is send + sync safe
69    looper: NonNull<ndk_sys::ALooper>,
70}
71unsafe impl Send for AndroidAppWaker {}
72unsafe impl Sync for AndroidAppWaker {}
73
74impl AndroidAppWaker {
75    /// Interrupts the main thread if it is blocked within [`AndroidApp::poll_events()`]
76    ///
77    /// If [`AndroidApp::poll_events()`] is interrupted it will invoke the poll
78    /// callback with a [PollEvent::Wake][wake_event] event.
79    ///
80    /// [wake_event]: crate::PollEvent::Wake
81    pub fn wake(&self) {
82        unsafe {
83            ndk_sys::ALooper_wake(self.looper.as_ptr());
84        }
85    }
86}
87
88impl AndroidApp {
89    pub(crate) fn new(native_activity: NativeActivityGlue, jvm: CloneJavaVM) -> Self {
90        let mut env = jvm.get_env().unwrap(); // We attach to the thread before creating the AndroidApp
91
92        let key_map_binding = match KeyCharacterMapBinding::new(&mut env) {
93            Ok(b) => b,
94            Err(err) => {
95                panic!("Failed to create KeyCharacterMap JNI bindings: {err:?}");
96            }
97        };
98
99        let app = Self {
100            inner: Arc::new(RwLock::new(AndroidAppInner {
101                jvm,
102                native_activity,
103                looper: Looper {
104                    ptr: ptr::null_mut(),
105                },
106                key_map_binding: Arc::new(key_map_binding),
107                key_maps: Mutex::new(HashMap::new()),
108                input_receiver: Mutex::new(None),
109            })),
110        };
111
112        {
113            let mut guard = app.inner.write().unwrap();
114
115            let main_fd = guard.native_activity.cmd_read_fd();
116            unsafe {
117                guard.looper.ptr = ndk_sys::ALooper_prepare(
118                    ndk_sys::ALOOPER_PREPARE_ALLOW_NON_CALLBACKS as libc::c_int,
119                );
120                ndk_sys::ALooper_addFd(
121                    guard.looper.ptr,
122                    main_fd,
123                    LOOPER_ID_MAIN,
124                    ndk_sys::ALOOPER_EVENT_INPUT as libc::c_int,
125                    None,
126                    //&mut guard.cmd_poll_source as *mut _ as *mut _);
127                    ptr::null_mut(),
128                );
129            }
130        }
131
132        app
133    }
134}
135
136#[derive(Debug)]
137struct Looper {
138    pub ptr: *mut ndk_sys::ALooper,
139}
140unsafe impl Send for Looper {}
141unsafe impl Sync for Looper {}
142
143#[derive(Debug)]
144pub(crate) struct AndroidAppInner {
145    pub(crate) jvm: CloneJavaVM,
146
147    pub(crate) native_activity: NativeActivityGlue,
148    looper: Looper,
149
150    /// Shared JNI bindings for the `KeyCharacterMap` class
151    key_map_binding: Arc<KeyCharacterMapBinding>,
152
153    /// A table of `KeyCharacterMap`s per `InputDevice` ID
154    /// these are used to be able to map key presses to unicode
155    /// characters
156    key_maps: Mutex<HashMap<i32, KeyCharacterMap>>,
157
158    /// While an app is reading input events it holds an
159    /// InputReceiver reference which we track to ensure
160    /// we don't hand out more than one receiver at a time
161    input_receiver: Mutex<Option<Weak<InputReceiver>>>,
162}
163
164impl AndroidAppInner {
165    pub(crate) fn vm_as_ptr(&self) -> *mut c_void {
166        unsafe { (*self.native_activity.activity).vm as _ }
167    }
168
169    pub(crate) fn activity_as_ptr(&self) -> *mut c_void {
170        // "clazz" is a completely bogus name; this is the _instance_ not class pointer
171        unsafe { (*self.native_activity.activity).clazz as _ }
172    }
173
174    pub(crate) fn native_activity(&self) -> *const ndk_sys::ANativeActivity {
175        self.native_activity.activity
176    }
177
178    pub(crate) fn looper(&self) -> *mut ndk_sys::ALooper {
179        self.looper.ptr
180    }
181
182    pub fn native_window(&self) -> Option<NativeWindow> {
183        self.native_activity.mutex.lock().unwrap().window.clone()
184    }
185
186    pub fn poll_events<F>(&self, timeout: Option<Duration>, mut callback: F)
187    where
188        F: FnMut(PollEvent<'_>),
189    {
190        trace!("poll_events");
191
192        unsafe {
193            let mut fd: i32 = 0;
194            let mut events: i32 = 0;
195            let mut source: *mut c_void = ptr::null_mut();
196
197            let timeout_milliseconds = if let Some(timeout) = timeout {
198                timeout.as_millis() as i32
199            } else {
200                -1
201            };
202
203            trace!("Calling ALooper_pollAll, timeout = {timeout_milliseconds}");
204            assert!(
205                !ndk_sys::ALooper_forThread().is_null(),
206                "Application tried to poll events from non-main thread"
207            );
208            let id = ndk_sys::ALooper_pollAll(
209                timeout_milliseconds,
210                &mut fd,
211                &mut events,
212                &mut source as *mut *mut c_void,
213            );
214            trace!("pollAll id = {id}");
215            match id {
216                ndk_sys::ALOOPER_POLL_WAKE => {
217                    trace!("ALooper_pollAll returned POLL_WAKE");
218                    callback(PollEvent::Wake);
219                }
220                ndk_sys::ALOOPER_POLL_CALLBACK => {
221                    // ALooper_pollAll is documented to handle all callback sources internally so it should
222                    // never return a _CALLBACK source id...
223                    error!("Spurious ALOOPER_POLL_CALLBACK from ALopper_pollAll() (ignored)");
224                }
225                ndk_sys::ALOOPER_POLL_TIMEOUT => {
226                    trace!("ALooper_pollAll returned POLL_TIMEOUT");
227                    callback(PollEvent::Timeout);
228                }
229                ndk_sys::ALOOPER_POLL_ERROR => {
230                    // If we have an IO error with our pipe to the main Java thread that's surely
231                    // not something we can recover from
232                    panic!("ALooper_pollAll returned POLL_ERROR");
233                }
234                id if id >= 0 => {
235                    match id {
236                        LOOPER_ID_MAIN => {
237                            trace!("ALooper_pollAll returned ID_MAIN");
238                            if let Some(ipc_cmd) = self.native_activity.read_cmd() {
239                                let main_cmd = match ipc_cmd {
240                                    // We don't forward info about the AInputQueue to apps since it's
241                                    // an implementation details that's also not compatible with
242                                    // GameActivity
243                                    glue::AppCmd::InputQueueChanged => None,
244
245                                    glue::AppCmd::InitWindow => Some(MainEvent::InitWindow {}),
246                                    glue::AppCmd::TermWindow => Some(MainEvent::TerminateWindow {}),
247                                    glue::AppCmd::WindowResized => {
248                                        Some(MainEvent::WindowResized {})
249                                    }
250                                    glue::AppCmd::WindowRedrawNeeded => {
251                                        Some(MainEvent::RedrawNeeded {})
252                                    }
253                                    glue::AppCmd::ContentRectChanged => {
254                                        Some(MainEvent::ContentRectChanged {})
255                                    }
256                                    glue::AppCmd::GainedFocus => Some(MainEvent::GainedFocus),
257                                    glue::AppCmd::LostFocus => Some(MainEvent::LostFocus),
258                                    glue::AppCmd::ConfigChanged => {
259                                        Some(MainEvent::ConfigChanged {})
260                                    }
261                                    glue::AppCmd::LowMemory => Some(MainEvent::LowMemory),
262                                    glue::AppCmd::Start => Some(MainEvent::Start),
263                                    glue::AppCmd::Resume => Some(MainEvent::Resume {
264                                        loader: StateLoader { app: self },
265                                    }),
266                                    glue::AppCmd::SaveState => Some(MainEvent::SaveState {
267                                        saver: StateSaver { app: self },
268                                    }),
269                                    glue::AppCmd::Pause => Some(MainEvent::Pause),
270                                    glue::AppCmd::Stop => Some(MainEvent::Stop),
271                                    glue::AppCmd::Destroy => Some(MainEvent::Destroy),
272                                };
273
274                                trace!("Calling pre_exec_cmd({ipc_cmd:#?})");
275                                self.native_activity.pre_exec_cmd(
276                                    ipc_cmd,
277                                    self.looper(),
278                                    LOOPER_ID_INPUT,
279                                );
280
281                                if let Some(main_cmd) = main_cmd {
282                                    trace!("Invoking callback for ID_MAIN command = {main_cmd:?}");
283                                    callback(PollEvent::Main(main_cmd));
284                                }
285
286                                trace!("Calling post_exec_cmd({ipc_cmd:#?})");
287                                self.native_activity.post_exec_cmd(ipc_cmd);
288                            }
289                        }
290                        LOOPER_ID_INPUT => {
291                            trace!("ALooper_pollAll returned ID_INPUT");
292
293                            // To avoid spamming the application with event loop iterations notifying them of
294                            // input events then we only send one `InputAvailable` per iteration of input
295                            // handling. We re-attach the looper when the application calls
296                            // `AndroidApp::input_events()`
297                            self.native_activity.detach_input_queue_from_looper();
298                            callback(PollEvent::Main(MainEvent::InputAvailable))
299                        }
300                        _ => {
301                            error!("Ignoring spurious ALooper event source: id = {id}, fd = {fd}, events = {events:?}, data = {source:?}");
302                        }
303                    }
304                }
305                _ => {
306                    error!("Spurious ALooper_pollAll return value {id} (ignored)");
307                }
308            }
309        }
310    }
311
312    pub fn create_waker(&self) -> AndroidAppWaker {
313        unsafe {
314            // From the application's pov we assume the looper pointer has a static
315            // lifetimes and we can safely assume it is never NULL.
316            AndroidAppWaker {
317                looper: NonNull::new_unchecked(self.looper.ptr),
318            }
319        }
320    }
321
322    pub fn config(&self) -> ConfigurationRef {
323        self.native_activity.config()
324    }
325
326    pub fn content_rect(&self) -> Rect {
327        self.native_activity.content_rect()
328    }
329
330    pub fn asset_manager(&self) -> AssetManager {
331        unsafe {
332            let activity_ptr = self.native_activity.activity;
333            let am_ptr = NonNull::new_unchecked((*activity_ptr).assetManager);
334            AssetManager::from_ptr(am_ptr)
335        }
336    }
337
338    pub fn set_window_flags(
339        &self,
340        add_flags: WindowManagerFlags,
341        remove_flags: WindowManagerFlags,
342    ) {
343        let na = self.native_activity();
344        let na_mut = na as *mut ndk_sys::ANativeActivity;
345        unsafe {
346            ndk_sys::ANativeActivity_setWindowFlags(
347                na_mut.cast(),
348                add_flags.bits(),
349                remove_flags.bits(),
350            );
351        }
352    }
353
354    // TODO: move into a trait
355    pub fn show_soft_input(&self, show_implicit: bool) {
356        let na = self.native_activity();
357        unsafe {
358            let flags = if show_implicit {
359                ndk_sys::ANATIVEACTIVITY_SHOW_SOFT_INPUT_IMPLICIT
360            } else {
361                0
362            };
363            ndk_sys::ANativeActivity_showSoftInput(na as *mut _, flags);
364        }
365    }
366
367    // TODO: move into a trait
368    pub fn hide_soft_input(&self, hide_implicit_only: bool) {
369        let na = self.native_activity();
370        unsafe {
371            let flags = if hide_implicit_only {
372                ndk_sys::ANATIVEACTIVITY_HIDE_SOFT_INPUT_IMPLICIT_ONLY
373            } else {
374                0
375            };
376            ndk_sys::ANativeActivity_hideSoftInput(na as *mut _, flags);
377        }
378    }
379
380    // TODO: move into a trait
381    pub fn text_input_state(&self) -> TextInputState {
382        TextInputState {
383            text: String::new(),
384            selection: TextSpan { start: 0, end: 0 },
385            compose_region: None,
386        }
387    }
388
389    // TODO: move into a trait
390    pub fn set_text_input_state(&self, _state: TextInputState) {
391        // NOP: Unsupported
392    }
393
394    pub fn device_key_character_map(&self, device_id: i32) -> InternalResult<KeyCharacterMap> {
395        let mut guard = self.key_maps.lock().unwrap();
396
397        let key_map = match guard.entry(device_id) {
398            std::collections::hash_map::Entry::Occupied(occupied) => occupied.get().clone(),
399            std::collections::hash_map::Entry::Vacant(vacant) => {
400                let character_map = jni_utils::device_key_character_map(
401                    self.jvm.clone(),
402                    self.key_map_binding.clone(),
403                    device_id,
404                )?;
405                vacant.insert(character_map.clone());
406                character_map
407            }
408        };
409
410        Ok(key_map)
411    }
412
413    pub fn enable_motion_axis(&self, _axis: Axis) {
414        // NOP - The InputQueue API doesn't let us optimize which axis values are read
415    }
416
417    pub fn disable_motion_axis(&self, _axis: Axis) {
418        // NOP - The InputQueue API doesn't let us optimize which axis values are read
419    }
420
421    pub fn input_events_receiver(&self) -> InternalResult<Arc<InputReceiver>> {
422        let mut guard = self.input_receiver.lock().unwrap();
423
424        if let Some(receiver) = &*guard {
425            if receiver.strong_count() > 0 {
426                return Err(crate::error::InternalAppError::InputUnavailable);
427            }
428        }
429        *guard = None;
430
431        // Get the InputQueue for the NativeActivity (if there is one) and also ensure
432        // the queue is re-attached to our event Looper (so new input events will again
433        // trigger a wake up)
434        let queue = self
435            .native_activity
436            .looper_attached_input_queue(self.looper(), LOOPER_ID_INPUT);
437
438        // Note: we don't treat it as an error if there is no queue, so if applications
439        // iterate input before a queue has been created (e.g. before onStart) then
440        // it will simply behave like there are no events available currently.
441        let receiver = Arc::new(InputReceiver { queue });
442
443        *guard = Some(Arc::downgrade(&receiver));
444        Ok(receiver)
445    }
446
447    pub fn internal_data_path(&self) -> Option<std::path::PathBuf> {
448        let na = self.native_activity();
449        unsafe { util::try_get_path_from_ptr((*na).internalDataPath) }
450    }
451
452    pub fn external_data_path(&self) -> Option<std::path::PathBuf> {
453        let na = self.native_activity();
454        unsafe { util::try_get_path_from_ptr((*na).externalDataPath) }
455    }
456
457    pub fn obb_path(&self) -> Option<std::path::PathBuf> {
458        let na = self.native_activity();
459        unsafe { util::try_get_path_from_ptr((*na).obbPath) }
460    }
461}
462
463#[derive(Debug)]
464pub(crate) struct InputReceiver {
465    queue: Option<InputQueue>,
466}
467
468impl<'a> From<Arc<InputReceiver>> for InputIteratorInner<'a> {
469    fn from(receiver: Arc<InputReceiver>) -> Self {
470        Self {
471            receiver,
472            _lifetime: PhantomData,
473        }
474    }
475}
476
477pub(crate) struct InputIteratorInner<'a> {
478    // Held to maintain exclusive access to buffered input events
479    receiver: Arc<InputReceiver>,
480    _lifetime: PhantomData<&'a ()>,
481}
482
483impl<'a> InputIteratorInner<'a> {
484    pub(crate) fn next<F>(&self, callback: F) -> bool
485    where
486        F: FnOnce(&input::InputEvent) -> InputStatus,
487    {
488        let Some(queue) = &self.receiver.queue else {
489            log::trace!("no queue available for events");
490            return false;
491        };
492
493        // Note: we basically ignore errors from event() currently. Looking at the source code for
494        // Android's InputQueue, the only error that can be returned here is 'WOULD_BLOCK', which we
495        // want to just treat as meaning the queue is empty.
496        //
497        // ref: https://github.com/aosp-mirror/platform_frameworks_base/blob/master/core/jni/android_view_InputQueue.cpp
498        //
499        if let Ok(Some(ndk_event)) = queue.event() {
500            log::trace!("queue: got event: {ndk_event:?}");
501
502            if let Some(ndk_event) = queue.pre_dispatch(ndk_event) {
503                let event = match ndk_event {
504                    ndk::event::InputEvent::MotionEvent(e) => {
505                        input::InputEvent::MotionEvent(input::MotionEvent::new(e))
506                    }
507                    ndk::event::InputEvent::KeyEvent(e) => {
508                        input::InputEvent::KeyEvent(input::KeyEvent::new(e))
509                    }
510                    _ => todo!("NDK added a new type"),
511                };
512
513                // `finish_event` needs to be called for each event otherwise
514                // the app would likely get an ANR
515                let result = std::panic::catch_unwind(AssertUnwindSafe(|| callback(&event)));
516
517                let ndk_event = match event {
518                    input::InputEvent::MotionEvent(e) => {
519                        ndk::event::InputEvent::MotionEvent(e.into_ndk_event())
520                    }
521                    input::InputEvent::KeyEvent(e) => {
522                        ndk::event::InputEvent::KeyEvent(e.into_ndk_event())
523                    }
524                    _ => unreachable!(),
525                };
526
527                let handled = match result {
528                    Ok(handled) => handled,
529                    Err(payload) => {
530                        log::error!("Calling `finish_event` after panic in input event handler, to try and avoid being killed via an ANR");
531                        queue.finish_event(ndk_event, false);
532                        std::panic::resume_unwind(payload);
533                    }
534                };
535
536                log::trace!("queue: finishing event");
537                queue.finish_event(ndk_event, handled == InputStatus::Handled);
538            }
539
540            true
541        } else {
542            log::trace!("queue: no more events");
543            false
544        }
545    }
546}