android_activity/native_activity/
mod.rs1#![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#[derive(Debug)]
37pub struct StateSaver<'a> {
38 app: &'a AndroidAppInner,
39}
40
41impl<'a> StateSaver<'a> {
42 pub fn store(&self, state: &'a [u8]) {
45 self.app.native_activity.set_saved_state(state);
46 }
47}
48
49#[derive(Debug)]
53pub struct StateLoader<'a> {
54 app: &'a AndroidAppInner,
55}
56impl<'a> StateLoader<'a> {
57 pub fn load(&self) -> Option<Vec<u8>> {
59 self.app.native_activity.saved_state()
60 }
61}
62
63#[derive(Clone)]
65pub struct AndroidAppWaker {
66 looper: NonNull<ndk_sys::ALooper>,
70}
71unsafe impl Send for AndroidAppWaker {}
72unsafe impl Sync for AndroidAppWaker {}
73
74impl AndroidAppWaker {
75 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(); 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 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 key_map_binding: Arc<KeyCharacterMapBinding>,
152
153 key_maps: Mutex<HashMap<i32, KeyCharacterMap>>,
157
158 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 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 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 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 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 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 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 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 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 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 pub fn set_text_input_state(&self, _state: TextInputState) {
391 }
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 }
416
417 pub fn disable_motion_axis(&self, _axis: Axis) {
418 }
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 let queue = self
435 .native_activity
436 .looper_attached_input_queue(self.looper(), LOOPER_ID_INPUT);
437
438 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 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 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 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}