Vendor dependencies for 0.3.0 release

This commit is contained in:
2025-09-27 10:29:08 -05:00
parent 0c8d39d483
commit 82ab7f317b
26803 changed files with 16134934 additions and 0 deletions

151
vendor/android-activity/src/config.rs vendored Normal file
View File

@@ -0,0 +1,151 @@
use core::fmt;
use std::sync::{Arc, RwLock};
use ndk::configuration::{
Configuration, Keyboard, KeysHidden, LayoutDir, NavHidden, Navigation, Orientation, ScreenLong,
ScreenSize, Touchscreen, UiModeNight, UiModeType,
};
/// A (cheaply clonable) reference to this application's [`ndk::configuration::Configuration`]
///
/// This provides a thread-safe way to access the latest configuration state for
/// an application without deeply copying the large [`ndk::configuration::Configuration`] struct.
///
/// If the application is notified of configuration changes then those changes
/// will become visible via pre-existing configuration references.
#[derive(Clone)]
pub struct ConfigurationRef {
config: Arc<RwLock<Configuration>>,
}
impl PartialEq for ConfigurationRef {
fn eq(&self, other: &Self) -> bool {
if Arc::ptr_eq(&self.config, &other.config) {
true
} else {
let other_guard = other.config.read().unwrap();
self.config.read().unwrap().eq(&*other_guard)
}
}
}
impl Eq for ConfigurationRef {}
unsafe impl Send for ConfigurationRef {}
unsafe impl Sync for ConfigurationRef {}
impl fmt::Debug for ConfigurationRef {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.config.read().unwrap().fmt(f)
}
}
impl ConfigurationRef {
pub(crate) fn new(config: Configuration) -> Self {
Self {
config: Arc::new(RwLock::new(config)),
}
}
pub(crate) fn replace(&self, src: Configuration) {
self.config.write().unwrap().copy(&src);
}
// Returns a deep copy of the full application configuration
pub fn copy(&self) -> Configuration {
let mut dest = Configuration::new();
dest.copy(&self.config.read().unwrap());
dest
}
/// Returns the country code, as a [`String`] of two characters, if set
pub fn country(&self) -> Option<String> {
self.config.read().unwrap().country()
}
/// Returns the screen density in dpi.
///
/// On some devices it can return values outside of the density enum.
pub fn density(&self) -> Option<u32> {
self.config.read().unwrap().density()
}
/// Returns the keyboard type.
pub fn keyboard(&self) -> Keyboard {
self.config.read().unwrap().keyboard()
}
/// Returns keyboard visibility/availability.
pub fn keys_hidden(&self) -> KeysHidden {
self.config.read().unwrap().keys_hidden()
}
/// Returns the language, as a `String` of two characters, if a language is set
pub fn language(&self) -> Option<String> {
self.config.read().unwrap().language()
}
/// Returns the layout direction
pub fn layout_direction(&self) -> LayoutDir {
self.config.read().unwrap().layout_direction()
}
/// Returns the mobile country code.
pub fn mcc(&self) -> i32 {
self.config.read().unwrap().mcc()
}
/// Returns the mobile network code, if one is defined
pub fn mnc(&self) -> Option<i32> {
self.config.read().unwrap().mnc()
}
pub fn nav_hidden(&self) -> NavHidden {
self.config.read().unwrap().nav_hidden()
}
pub fn navigation(&self) -> Navigation {
self.config.read().unwrap().navigation()
}
pub fn orientation(&self) -> Orientation {
self.config.read().unwrap().orientation()
}
pub fn screen_height_dp(&self) -> Option<i32> {
self.config.read().unwrap().screen_height_dp()
}
pub fn screen_width_dp(&self) -> Option<i32> {
self.config.read().unwrap().screen_width_dp()
}
pub fn screen_long(&self) -> ScreenLong {
self.config.read().unwrap().screen_long()
}
#[cfg(feature = "api-level-30")]
pub fn screen_round(&self) -> ScreenRound {
self.config.read().unwrap().screen_round()
}
pub fn screen_size(&self) -> ScreenSize {
self.config.read().unwrap().screen_size()
}
pub fn sdk_version(&self) -> i32 {
self.config.read().unwrap().sdk_version()
}
pub fn smallest_screen_width_dp(&self) -> Option<i32> {
self.config.read().unwrap().smallest_screen_width_dp()
}
pub fn touchscreen(&self) -> Touchscreen {
self.config.read().unwrap().touchscreen()
}
pub fn ui_mode_night(&self) -> UiModeNight {
self.config.read().unwrap().ui_mode_night()
}
pub fn ui_mode_type(&self) -> UiModeType {
self.config.read().unwrap().ui_mode_type()
}
}

58
vendor/android-activity/src/error.rs vendored Normal file
View File

@@ -0,0 +1,58 @@
use thiserror::Error;
#[derive(Error, Debug)]
pub enum AppError {
#[error("Operation only supported from the android_main() thread: {0}")]
NonMainThread(String),
#[error("Java VM or JNI error, including Java exceptions")]
JavaError(String),
#[error("Input unavailable")]
InputUnavailable,
}
pub type Result<T> = std::result::Result<T, AppError>;
// XXX: we don't want to expose jni-rs in the public API
// so we have an internal error type that we can generally
// use in the backends and then we can strip the error
// in the frontend of the API.
//
// This way we avoid exposing a public trait implementation for
// `From<jni::errors::Error>`
#[derive(Error, Debug)]
pub(crate) enum InternalAppError {
#[error("A JNI error")]
JniError(jni::errors::JniError),
#[error("A Java Exception was thrown via a JNI method call")]
JniException(String),
#[error("A Java VM error")]
JvmError(jni::errors::Error),
#[error("Input unavailable")]
InputUnavailable,
}
pub(crate) type InternalResult<T> = std::result::Result<T, InternalAppError>;
impl From<jni::errors::Error> for InternalAppError {
fn from(value: jni::errors::Error) -> Self {
InternalAppError::JvmError(value)
}
}
impl From<jni::errors::JniError> for InternalAppError {
fn from(value: jni::errors::JniError) -> Self {
InternalAppError::JniError(value)
}
}
impl From<InternalAppError> for AppError {
fn from(value: InternalAppError) -> Self {
match value {
InternalAppError::JniError(err) => AppError::JavaError(err.to_string()),
InternalAppError::JniException(msg) => AppError::JavaError(msg),
InternalAppError::JvmError(err) => AppError::JavaError(err.to_string()),
InternalAppError::InputUnavailable => AppError::InputUnavailable,
}
}
}

View File

@@ -0,0 +1,32 @@
//! The bindings are pre-generated and the right one for the platform is selected at compile time.
// Bindgen lints
#![allow(non_upper_case_globals)]
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
#![allow(improper_ctypes)]
#![allow(clippy::all)]
// Temporarily allow UB nullptr dereference in bindgen layout tests until fixed upstream:
// https://github.com/rust-lang/rust-bindgen/pull/2055
// https://github.com/rust-lang/rust-bindgen/pull/2064
#![allow(deref_nullptr)]
#![allow(dead_code)]
use jni_sys::*;
use libc::{pthread_cond_t, pthread_mutex_t, pthread_t};
use ndk_sys::{AAssetManager, AConfiguration, ALooper, ALooper_callbackFunc, ANativeWindow, ARect};
#[cfg(all(
any(target_os = "android", feature = "test"),
any(target_arch = "arm", target_arch = "armv7")
))]
include!("ffi_arm.rs");
#[cfg(all(any(target_os = "android", feature = "test"), target_arch = "aarch64"))]
include!("ffi_aarch64.rs");
#[cfg(all(any(target_os = "android", feature = "test"), target_arch = "x86"))]
include!("ffi_i686.rs");
#[cfg(all(any(target_os = "android", feature = "test"), target_arch = "x86_64"))]
include!("ffi_x86_64.rs");

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,760 @@
// GameActivity handles input events by double buffering MotionEvent and KeyEvent structs which
// essentially just mirror state from the corresponding Java objects.
//
// See also the javadocs for
// [`android.view.InputEvent`](https://developer.android.com/reference/android/view/InputEvent.html),
// [`android.view.MotionEvent`](https://developer.android.com/reference/android/view/MotionEvent.html),
// and [`android.view.KeyEvent`](https://developer.android.com/reference/android/view/KeyEvent).
//
// This code is mostly based on https://github.com/rust-windowing/android-ndk-rs/blob/master/ndk/src/event.rs
//
// The `Source` enum was defined based on the Java docs since there are a couple of source types that
// aren't exposed via the AInputQueue API
// The `Class` was also bound differently to `android-ndk-rs` considering how the class is defined
// by masking bits from the `Source`.
use crate::activity_impl::ffi::{GameActivityKeyEvent, GameActivityMotionEvent};
use crate::input::{
Axis, Button, ButtonState, EdgeFlags, KeyAction, KeyEventFlags, Keycode, MetaState,
MotionAction, MotionEventFlags, Pointer, PointersIter, Source, ToolType,
};
// Note: try to keep this wrapper API compatible with the AInputEvent API if possible
#[derive(Debug, Clone)]
#[non_exhaustive]
pub enum InputEvent<'a> {
MotionEvent(MotionEvent<'a>),
KeyEvent(KeyEvent<'a>),
TextEvent(crate::input::TextInputState),
}
/// A motion event.
///
/// For general discussion of motion events in Android, see [the relevant
/// javadoc](https://developer.android.com/reference/android/view/MotionEvent).
#[derive(Clone, Debug)]
pub struct MotionEvent<'a> {
ga_event: &'a GameActivityMotionEvent,
}
impl<'a> MotionEvent<'a> {
pub(crate) fn new(ga_event: &'a GameActivityMotionEvent) -> Self {
Self { ga_event }
}
/// Get the source of the event.
///
#[inline]
pub fn source(&self) -> Source {
let source = self.ga_event.source as u32;
source.into()
}
/// Get the device id associated with the event.
///
#[inline]
pub fn device_id(&self) -> i32 {
self.ga_event.deviceId
}
/// Returns the motion action associated with the event.
///
/// See [the MotionEvent docs](https://developer.android.com/reference/android/view/MotionEvent#getActionMasked())
#[inline]
pub fn action(&self) -> MotionAction {
let action = self.ga_event.action as u32 & ndk_sys::AMOTION_EVENT_ACTION_MASK;
action.into()
}
/// Returns which button has been modified during a press or release action.
///
/// For actions other than [`MotionAction::ButtonPress`] and
/// [`MotionAction::ButtonRelease`] the returned value is undefined.
///
/// See [the MotionEvent docs](https://developer.android.com/reference/android/view/MotionEvent#getActionButton())
#[inline]
pub fn action_button(&self) -> Button {
let action = self.ga_event.actionButton as u32;
action.into()
}
/// Returns the pointer index of an `Up` or `Down` event.
///
/// Pointer indices can change per motion event. For an identifier that stays the same, see
/// [`Pointer::pointer_id()`].
///
/// This only has a meaning when the [action](self::action) is one of [`Up`](MotionAction::Up),
/// [`Down`](MotionAction::Down), [`PointerUp`](MotionAction::PointerUp),
/// or [`PointerDown`](MotionAction::PointerDown).
#[inline]
pub fn pointer_index(&self) -> usize {
let action = self.ga_event.action as u32;
let index = (action & ndk_sys::AMOTION_EVENT_ACTION_POINTER_INDEX_MASK)
>> ndk_sys::AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;
index as usize
}
/*
/// Returns the pointer id associated with the given pointer index.
///
/// See [the NDK
/// docs](https://developer.android.com/ndk/reference/group/input#amotionevent_getpointerid)
// TODO: look at output with out-of-range pointer index
// Probably -1 though
pub fn pointer_id_for(&self.ga_event, pointer_index: usize) -> i32 {
unsafe { ndk_sys::AMotionEvent_getPointerId(self.ga_event.ptr.as_ptr(), pointer_index) }
}
*/
/// Returns the number of pointers in this event
///
/// See [the MotionEvent docs](https://developer.android.com/reference/android/view/MotionEvent#getPointerCount())
#[inline]
pub fn pointer_count(&self) -> usize {
self.ga_event.pointerCount as usize
}
/// An iterator over the pointers in this motion event
#[inline]
pub fn pointers(&self) -> PointersIter<'_> {
PointersIter {
inner: PointersIterImpl {
event: self,
next_index: 0,
count: self.pointer_count(),
},
}
}
/// The pointer at a given pointer index. Panics if the pointer index is out of bounds.
///
/// If you need to loop over all the pointers, prefer the [`pointers()`](self::pointers) method.
#[inline]
pub fn pointer_at_index(&self, index: usize) -> Pointer<'_> {
if index >= self.pointer_count() {
panic!("Pointer index {} is out of bounds", index);
}
Pointer {
inner: PointerImpl { event: self, index },
}
}
/*
/// Returns the size of the history contained in this event.
///
/// See [the NDK
/// docs](https://developer.android.com/ndk/reference/group/input#amotionevent_gethistorysize)
#[inline]
pub fn history_size(&self) -> usize {
unsafe { ndk_sys::AMotionEvent_getHistorySize(self.ga_event.ptr.as_ptr()) as usize }
}
/// An iterator over the historical events contained in this event.
#[inline]
pub fn history(&self) -> HistoricalMotionEventsIter<'_> {
HistoricalMotionEventsIter {
event: self.ga_event.ptr,
next_history_index: 0,
history_size: self.history_size(),
_marker: std::marker::PhantomData,
}
}
*/
/// Returns the state of any modifier keys that were pressed during the event.
///
/// See [the NDK
/// docs](https://developer.android.com/ndk/reference/group/input#amotionevent_getmetastate)
#[inline]
pub fn meta_state(&self) -> MetaState {
MetaState(self.ga_event.metaState as u32)
}
/// Returns the button state during this event, as a bitfield.
///
/// See [the NDK
/// docs](https://developer.android.com/ndk/reference/group/input#amotionevent_getbuttonstate)
#[inline]
pub fn button_state(&self) -> ButtonState {
ButtonState(self.ga_event.buttonState as u32)
}
/// Returns the time of the start of this gesture, in the `java.lang.System.nanoTime()` time
/// base
///
/// See [the NDK
/// docs](https://developer.android.com/ndk/reference/group/input#amotionevent_getdowntime)
#[inline]
pub fn down_time(&self) -> i64 {
self.ga_event.downTime
}
/// Returns a bitfield indicating which edges were touched by this event.
///
/// See [the NDK
/// docs](https://developer.android.com/ndk/reference/group/input#amotionevent_getedgeflags)
#[inline]
pub fn edge_flags(&self) -> EdgeFlags {
EdgeFlags(self.ga_event.edgeFlags as u32)
}
/// Returns the time of this event, in the `java.lang.System.nanoTime()` time base
///
/// See [the NDK
/// docs](https://developer.android.com/ndk/reference/group/input#amotionevent_geteventtime)
#[inline]
pub fn event_time(&self) -> i64 {
self.ga_event.eventTime
}
/// The flags associated with a motion event.
///
/// See [the NDK
/// docs](https://developer.android.com/ndk/reference/group/input#amotionevent_getflags)
#[inline]
pub fn flags(&self) -> MotionEventFlags {
MotionEventFlags(self.ga_event.flags as u32)
}
/* Missing from GameActivity currently...
/// Returns the offset in the x direction between the coordinates and the raw coordinates
///
/// See [the NDK
/// docs](https://developer.android.com/ndk/reference/group/input#amotionevent_getxoffset)
#[inline]
pub fn x_offset(&self.ga_event) -> f32 {
self.ga_event.x_offset
}
/// Returns the offset in the y direction between the coordinates and the raw coordinates
///
/// See [the NDK
/// docs](https://developer.android.com/ndk/reference/group/input#amotionevent_getyoffset)
#[inline]
pub fn y_offset(&self.ga_event) -> f32 {
self.ga_event.y_offset
}
*/
/// Returns the precision of the x value of the coordinates
///
/// See [the NDK
/// docs](https://developer.android.com/ndk/reference/group/input#amotionevent_getxprecision)
#[inline]
pub fn x_precision(&self) -> f32 {
self.ga_event.precisionX
}
/// Returns the precision of the y value of the coordinates
///
/// See [the NDK
/// docs](https://developer.android.com/ndk/reference/group/input#amotionevent_getyprecision)
#[inline]
pub fn y_precision(&self) -> f32 {
self.ga_event.precisionY
}
}
/// A view into the data of a specific pointer in a motion event.
#[derive(Debug)]
pub(crate) struct PointerImpl<'a> {
event: &'a MotionEvent<'a>,
index: usize,
}
impl<'a> PointerImpl<'a> {
#[inline]
pub fn pointer_index(&self) -> usize {
self.index
}
#[inline]
pub fn pointer_id(&self) -> i32 {
let pointer = &self.event.ga_event.pointers[self.index];
pointer.id
}
#[inline]
pub fn axis_value(&self, axis: Axis) -> f32 {
let pointer = &self.event.ga_event.pointers[self.index];
let axis: u32 = axis.into();
pointer.axisValues[axis as usize]
}
#[inline]
pub fn raw_x(&self) -> f32 {
let pointer = &self.event.ga_event.pointers[self.index];
pointer.rawX
}
#[inline]
pub fn raw_y(&self) -> f32 {
let pointer = &self.event.ga_event.pointers[self.index];
pointer.rawY
}
#[inline]
pub fn tool_type(&self) -> ToolType {
let pointer = &self.event.ga_event.pointers[self.index];
let tool_type = pointer.toolType as u32;
tool_type.into()
}
}
/// An iterator over the pointers in a [`MotionEvent`].
#[derive(Debug)]
pub(crate) struct PointersIterImpl<'a> {
event: &'a MotionEvent<'a>,
next_index: usize,
count: usize,
}
impl<'a> Iterator for PointersIterImpl<'a> {
type Item = Pointer<'a>;
fn next(&mut self) -> Option<Pointer<'a>> {
if self.next_index < self.count {
let ptr = Pointer {
inner: PointerImpl {
event: self.event,
index: self.next_index,
},
};
self.next_index += 1;
Some(ptr)
} else {
None
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
let size = self.count - self.next_index;
(size, Some(size))
}
}
impl<'a> ExactSizeIterator for PointersIterImpl<'a> {
fn len(&self) -> usize {
self.count - self.next_index
}
}
/*
/// Represents a view into a past moment of a motion event
#[derive(Debug)]
pub struct HistoricalMotionEvent<'a> {
event: NonNull<ndk_sys::AInputEvent>,
history_index: usize,
_marker: std::marker::PhantomData<&'a MotionEvent>,
}
// TODO: thread safety?
impl<'a> HistoricalMotionEvent<'a> {
/// Returns the "history index" associated with this historical event. Older events have smaller indices.
#[inline]
pub fn history_index(&self) -> usize {
self.history_index
}
/// Returns the time of the historical event, in the `java.lang.System.nanoTime()` time base
///
/// See [the NDK
/// docs](https://developer.android.com/ndk/reference/group/input#amotionevent_gethistoricaleventtime)
#[inline]
pub fn event_time(&self) -> i64 {
unsafe {
ndk_sys::AMotionEvent_getHistoricalEventTime(
self.event.as_ptr(),
self.history_index as ndk_sys::size_t,
)
}
}
/// An iterator over the pointers of this historical motion event
#[inline]
pub fn pointers(&self) -> HistoricalPointersIter<'a> {
HistoricalPointersIter {
event: self.event,
history_index: self.history_index,
next_pointer_index: 0,
pointer_count: unsafe {
ndk_sys::AMotionEvent_getPointerCount(self.event.as_ptr()) as usize
},
_marker: std::marker::PhantomData,
}
}
}
/// An iterator over all the historical moments in a [`MotionEvent`].
///
/// It iterates from oldest to newest.
#[derive(Debug)]
pub struct HistoricalMotionEventsIter<'a> {
event: NonNull<ndk_sys::AInputEvent>,
next_history_index: usize,
history_size: usize,
_marker: std::marker::PhantomData<&'a MotionEvent>,
}
// TODO: thread safety?
impl<'a> Iterator for HistoricalMotionEventsIter<'a> {
type Item = HistoricalMotionEvent<'a>;
fn next(&mut self) -> Option<HistoricalMotionEvent<'a>> {
if self.next_history_index < self.history_size {
let res = HistoricalMotionEvent {
event: self.event,
history_index: self.next_history_index,
_marker: std::marker::PhantomData,
};
self.next_history_index += 1;
Some(res)
} else {
None
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
let size = self.history_size - self.next_history_index;
(size, Some(size))
}
}
impl ExactSizeIterator for HistoricalMotionEventsIter<'_> {
fn len(&self) -> usize {
self.history_size - self.next_history_index
}
}
impl<'a> DoubleEndedIterator for HistoricalMotionEventsIter<'a> {
fn next_back(&mut self) -> Option<HistoricalMotionEvent<'a>> {
if self.next_history_index < self.history_size {
self.history_size -= 1;
Some(HistoricalMotionEvent {
event: self.event,
history_index: self.history_size,
_marker: std::marker::PhantomData,
})
} else {
None
}
}
}
/// A view into a pointer at a historical moment
#[derive(Debug)]
pub struct HistoricalPointer<'a> {
event: NonNull<ndk_sys::AInputEvent>,
pointer_index: usize,
history_index: usize,
_marker: std::marker::PhantomData<&'a MotionEvent>,
}
// TODO: thread safety?
impl<'a> HistoricalPointer<'a> {
#[inline]
pub fn pointer_index(&self) -> usize {
self.pointer_index
}
#[inline]
pub fn pointer_id(&self) -> i32 {
unsafe {
ndk_sys::AMotionEvent_getPointerId(self.event.as_ptr(), self.pointer_index as ndk_sys::size_t)
}
}
#[inline]
pub fn history_index(&self) -> usize {
self.history_index
}
#[inline]
pub fn axis_value(&self, axis: Axis) -> f32 {
unsafe {
ndk_sys::AMotionEvent_getHistoricalAxisValue(
self.event.as_ptr(),
axis as i32,
self.pointer_index as ndk_sys::size_t,
self.history_index as ndk_sys::size_t,
)
}
}
#[inline]
pub fn orientation(&self) -> f32 {
unsafe {
ndk_sys::AMotionEvent_getHistoricalOrientation(
self.event.as_ptr(),
self.pointer_index as ndk_sys::size_t,
self.history_index as ndk_sys::size_t,
)
}
}
#[inline]
pub fn pressure(&self) -> f32 {
unsafe {
ndk_sys::AMotionEvent_getHistoricalPressure(
self.event.as_ptr(),
self.pointer_index as ndk_sys::size_t,
self.history_index as ndk_sys::size_t,
)
}
}
#[inline]
pub fn raw_x(&self) -> f32 {
unsafe {
ndk_sys::AMotionEvent_getHistoricalRawX(
self.event.as_ptr(),
self.pointer_index as ndk_sys::size_t,
self.history_index as ndk_sys::size_t,
)
}
}
#[inline]
pub fn raw_y(&self) -> f32 {
unsafe {
ndk_sys::AMotionEvent_getHistoricalRawY(
self.event.as_ptr(),
self.pointer_index as ndk_sys::size_t,
self.history_index as ndk_sys::size_t,
)
}
}
#[inline]
pub fn x(&self) -> f32 {
unsafe {
ndk_sys::AMotionEvent_getHistoricalX(
self.event.as_ptr(),
self.pointer_index as ndk_sys::size_t,
self.history_index as ndk_sys::size_t,
)
}
}
#[inline]
pub fn y(&self) -> f32 {
unsafe {
ndk_sys::AMotionEvent_getHistoricalY(
self.event.as_ptr(),
self.pointer_index as ndk_sys::size_t,
self.history_index as ndk_sys::size_t,
)
}
}
#[inline]
pub fn size(&self) -> f32 {
unsafe {
ndk_sys::AMotionEvent_getHistoricalSize(
self.event.as_ptr(),
self.pointer_index as ndk_sys::size_t,
self.history_index as ndk_sys::size_t,
)
}
}
#[inline]
pub fn tool_major(&self) -> f32 {
unsafe {
ndk_sys::AMotionEvent_getHistoricalToolMajor(
self.event.as_ptr(),
self.pointer_index as ndk_sys::size_t,
self.history_index as ndk_sys::size_t,
)
}
}
#[inline]
pub fn tool_minor(&self) -> f32 {
unsafe {
ndk_sys::AMotionEvent_getHistoricalToolMinor(
self.event.as_ptr(),
self.pointer_index as ndk_sys::size_t,
self.history_index as ndk_sys::size_t,
)
}
}
#[inline]
pub fn touch_major(&self) -> f32 {
unsafe {
ndk_sys::AMotionEvent_getHistoricalTouchMajor(
self.event.as_ptr(),
self.pointer_index as ndk_sys::size_t,
self.history_index as ndk_sys::size_t,
)
}
}
#[inline]
pub fn touch_minor(&self) -> f32 {
unsafe {
ndk_sys::AMotionEvent_getHistoricalTouchMinor(
self.event.as_ptr(),
self.pointer_index as ndk_sys::size_t,
self.history_index as ndk_sys::size_t,
)
}
}
}
/// An iterator over the pointers in a historical motion event
#[derive(Debug)]
pub struct HistoricalPointersIter<'a> {
event: NonNull<ndk_sys::AInputEvent>,
history_index: usize,
next_pointer_index: usize,
pointer_count: usize,
_marker: std::marker::PhantomData<&'a MotionEvent>,
}
// TODO: thread safety?
impl<'a> Iterator for HistoricalPointersIter<'a> {
type Item = HistoricalPointer<'a>;
fn next(&mut self) -> Option<HistoricalPointer<'a>> {
if self.next_pointer_index < self.pointer_count {
let ptr = HistoricalPointer {
event: self.event,
history_index: self.history_index,
pointer_index: self.next_pointer_index,
_marker: std::marker::PhantomData,
};
self.next_pointer_index += 1;
Some(ptr)
} else {
None
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
let size = self.pointer_count - self.next_pointer_index;
(size, Some(size))
}
}
impl ExactSizeIterator for HistoricalPointersIter<'_> {
fn len(&self) -> usize {
self.pointer_count - self.next_pointer_index
}
}
*/
/// A key event.
///
/// For general discussion of key events in Android, see [the relevant
/// javadoc](https://developer.android.com/reference/android/view/KeyEvent).
#[derive(Debug, Clone)]
pub struct KeyEvent<'a> {
ga_event: &'a GameActivityKeyEvent,
}
impl<'a> KeyEvent<'a> {
pub(crate) fn new(ga_event: &'a GameActivityKeyEvent) -> Self {
Self { ga_event }
}
/// Get the source of the event.
///
#[inline]
pub fn source(&self) -> Source {
let source = self.ga_event.source as u32;
source.into()
}
/// Get the device id associated with the event.
///
#[inline]
pub fn device_id(&self) -> i32 {
self.ga_event.deviceId
}
/// Returns the key action associated with the event.
///
/// See [the KeyEvent docs](https://developer.android.com/reference/android/view/KeyEvent#getAction())
#[inline]
pub fn action(&self) -> KeyAction {
let action = self.ga_event.action as u32;
action.into()
}
#[inline]
pub fn action_button(&self) -> KeyAction {
let action = self.ga_event.action as u32;
action.into()
}
/// Returns the last time the key was pressed. This is on the scale of
/// `java.lang.System.nanoTime()`, which has nanosecond precision, but no defined start time.
///
/// See [the NDK
/// docs](https://developer.android.com/ndk/reference/group/input#akeyevent_getdowntime)
#[inline]
pub fn down_time(&self) -> i64 {
self.ga_event.downTime
}
/// Returns the time this event occured. This is on the scale of
/// `java.lang.System.nanoTime()`, which has nanosecond precision, but no defined start time.
///
/// See [the NDK
/// docs](https://developer.android.com/ndk/reference/group/input#akeyevent_geteventtime)
#[inline]
pub fn event_time(&self) -> i64 {
self.ga_event.eventTime
}
/// Returns the keycode associated with this key event
///
/// See [the NDK
/// docs](https://developer.android.com/ndk/reference/group/input#akeyevent_getkeycode)
#[inline]
pub fn key_code(&self) -> Keycode {
let keycode = self.ga_event.keyCode as u32;
keycode.into()
}
/// Returns the number of repeats of a key.
///
/// See [the NDK
/// docs](https://developer.android.com/ndk/reference/group/input#akeyevent_getrepeatcount)
#[inline]
pub fn repeat_count(&self) -> i32 {
self.ga_event.repeatCount
}
/// Returns the hardware keycode of a key. This varies from device to device.
///
/// See [the NDK
/// docs](https://developer.android.com/ndk/reference/group/input#akeyevent_getscancode)
#[inline]
pub fn scan_code(&self) -> i32 {
self.ga_event.scanCode
}
}
impl<'a> KeyEvent<'a> {
/// Flags associated with this [`KeyEvent`].
///
/// See [the NDK docs](https://developer.android.com/ndk/reference/group/input#akeyevent_getflags)
#[inline]
pub fn flags(&self) -> KeyEventFlags {
KeyEventFlags(self.ga_event.flags as u32)
}
/// Returns the state of the modifiers during this key event, represented by a bitmask.
///
/// See [the NDK
/// docs](https://developer.android.com/ndk/reference/group/input#akeyevent_getmetastate)
#[inline]
pub fn meta_state(&self) -> MetaState {
MetaState(self.ga_event.metaState as u32)
}
}

View File

@@ -0,0 +1,961 @@
#![cfg(feature = "game-activity")]
use std::collections::HashMap;
use std::marker::PhantomData;
use std::ops::Deref;
use std::panic::catch_unwind;
use std::ptr;
use std::ptr::NonNull;
use std::sync::Weak;
use std::sync::{Arc, Mutex, RwLock};
use std::time::Duration;
use libc::c_void;
use log::{error, trace};
use jni_sys::*;
use ndk_sys::ALooper_wake;
use ndk_sys::{ALooper, ALooper_pollAll};
use ndk::asset::AssetManager;
use ndk::configuration::Configuration;
use ndk::native_window::NativeWindow;
use crate::error::InternalResult;
use crate::input::{Axis, KeyCharacterMap, KeyCharacterMapBinding};
use crate::jni_utils::{self, CloneJavaVM};
use crate::util::{abort_on_panic, forward_stdio_to_logcat, log_panic, try_get_path_from_ptr};
use crate::{
AndroidApp, ConfigurationRef, InputStatus, MainEvent, PollEvent, Rect, WindowManagerFlags,
};
mod ffi;
pub mod input;
use crate::input::{TextInputState, TextSpan};
use input::{InputEvent, KeyEvent, MotionEvent};
// The only time it's safe to update the android_app->savedState pointer is
// while handling a SaveState event, so this API is only exposed for those
// events...
#[derive(Debug)]
pub struct StateSaver<'a> {
app: &'a AndroidAppInner,
}
impl<'a> StateSaver<'a> {
pub fn store(&self, state: &'a [u8]) {
// android_native_app_glue specifically expects savedState to have been allocated
// via libc::malloc since it will automatically handle freeing the data once it
// has been handed over to the Java Activity / main thread.
unsafe {
let app_ptr = self.app.native_app.as_ptr();
// In case the application calls store() multiple times for some reason we
// make sure to free any pre-existing state...
if !(*app_ptr).savedState.is_null() {
libc::free((*app_ptr).savedState);
(*app_ptr).savedState = ptr::null_mut();
(*app_ptr).savedStateSize = 0;
}
let buf = libc::malloc(state.len());
if buf.is_null() {
panic!("Failed to allocate save_state buffer");
}
// Since it's a byte array there's no special alignment requirement here.
//
// Since we re-define `buf` we ensure it's not possible to access the buffer
// via its original pointer for the lifetime of the slice.
{
let buf: &mut [u8] = std::slice::from_raw_parts_mut(buf.cast(), state.len());
buf.copy_from_slice(state);
}
(*app_ptr).savedState = buf;
(*app_ptr).savedStateSize = state.len() as _;
}
}
}
#[derive(Debug)]
pub struct StateLoader<'a> {
app: &'a AndroidAppInner,
}
impl<'a> StateLoader<'a> {
pub fn load(&self) -> Option<Vec<u8>> {
unsafe {
let app_ptr = self.app.native_app.as_ptr();
if !(*app_ptr).savedState.is_null() && (*app_ptr).savedStateSize > 0 {
let buf: &mut [u8] = std::slice::from_raw_parts_mut(
(*app_ptr).savedState.cast(),
(*app_ptr).savedStateSize,
);
let state = buf.to_vec();
Some(state)
} else {
None
}
}
}
}
#[derive(Clone)]
pub struct AndroidAppWaker {
// The looper pointer is owned by the android_app and effectively
// has a 'static lifetime, and the ALooper_wake C API is thread
// safe, so this can be cloned safely and is send + sync safe
looper: NonNull<ALooper>,
}
unsafe impl Send for AndroidAppWaker {}
unsafe impl Sync for AndroidAppWaker {}
impl AndroidAppWaker {
pub fn wake(&self) {
unsafe {
ALooper_wake(self.looper.as_ptr());
}
}
}
impl AndroidApp {
pub(crate) unsafe fn from_ptr(ptr: NonNull<ffi::android_app>, jvm: CloneJavaVM) -> Self {
let mut env = jvm.get_env().unwrap(); // We attach to the thread before creating the AndroidApp
let key_map_binding = match KeyCharacterMapBinding::new(&mut env) {
Ok(b) => b,
Err(err) => {
panic!("Failed to create KeyCharacterMap JNI bindings: {err:?}");
}
};
// Note: we don't use from_ptr since we don't own the android_app.config
// and need to keep in mind that the Drop handler is going to call
// AConfiguration_delete()
let config = Configuration::clone_from_ptr(NonNull::new_unchecked((*ptr.as_ptr()).config));
Self {
inner: Arc::new(RwLock::new(AndroidAppInner {
jvm,
native_app: NativeAppGlue { ptr },
config: ConfigurationRef::new(config),
native_window: Default::default(),
key_map_binding: Arc::new(key_map_binding),
key_maps: Mutex::new(HashMap::new()),
input_receiver: Mutex::new(None),
})),
}
}
}
#[derive(Debug, Clone)]
struct NativeAppGlue {
ptr: NonNull<ffi::android_app>,
}
impl Deref for NativeAppGlue {
type Target = NonNull<ffi::android_app>;
fn deref(&self) -> &Self::Target {
&self.ptr
}
}
unsafe impl Send for NativeAppGlue {}
unsafe impl Sync for NativeAppGlue {}
impl NativeAppGlue {
// TODO: move into a trait
pub fn text_input_state(&self) -> TextInputState {
unsafe {
let activity = (*self.as_ptr()).activity;
let mut out_state = TextInputState {
text: String::new(),
selection: TextSpan { start: 0, end: 0 },
compose_region: None,
};
let out_ptr = &mut out_state as *mut TextInputState;
let app_ptr = self.as_ptr();
(*app_ptr).textInputState = 0;
// NEON WARNING:
//
// It's not clearly documented but the GameActivity API over the
// GameTextInput library directly exposes _modified_ UTF8 text
// from Java so we need to be careful to convert text to and
// from UTF8
//
// GameTextInput also uses a pre-allocated, fixed-sized buffer for
// the current text state and has shared `currentState_` that
// appears to have no lock to guard access from multiple threads.
//
// There's also no locking at the GameActivity level, so I'm fairly
// certain that `GameActivity_getTextInputState` isn't thread
// safe: https://issuetracker.google.com/issues/294112477
//
// Overall this is all quite gnarly - and probably a good reminder
// of why we want to use Rust instead of C/C++.
ffi::GameActivity_getTextInputState(
activity,
Some(AndroidAppInner::map_input_state_to_text_event_callback),
out_ptr.cast(),
);
out_state
}
}
// TODO: move into a trait
pub fn set_text_input_state(&self, state: TextInputState) {
unsafe {
let activity = (*self.as_ptr()).activity;
let modified_utf8 = cesu8::to_java_cesu8(&state.text);
let text_length = modified_utf8.len() as i32;
let modified_utf8_bytes = modified_utf8.as_ptr();
let ffi_state = ffi::GameTextInputState {
text_UTF8: modified_utf8_bytes.cast(), // NB: may be signed or unsigned depending on target
text_length,
selection: ffi::GameTextInputSpan {
start: state.selection.start as i32,
end: state.selection.end as i32,
},
composingRegion: match state.compose_region {
Some(span) => {
// The GameText subclass of InputConnection only has a special case for removing the
// compose region if `start == -1` but the docs for `setComposingRegion` imply that
// the region should effectively be removed if any empty region is given (unlike for the
// selection region, it's not meaningful to maintain an empty compose region)
//
// We aim for more consistent behaviour by normalizing any empty region into `(-1, -1)`
// to remove the compose region.
//
// NB `setComposingRegion` itself is documented to clamp start/end to the text bounds
// so apart from this special-case handling in GameText's implementation of
// `setComposingRegion` then there's nothing special about `(-1, -1)` - it's just an empty
// region that should get clamped to `(0, 0)` and then get removed.
if span.start == span.end {
ffi::GameTextInputSpan { start: -1, end: -1 }
} else {
ffi::GameTextInputSpan {
start: span.start as i32,
end: span.end as i32,
}
}
}
None => ffi::GameTextInputSpan { start: -1, end: -1 },
},
};
ffi::GameActivity_setTextInputState(activity, &ffi_state as *const _);
}
}
}
#[derive(Debug)]
pub struct AndroidAppInner {
pub(crate) jvm: CloneJavaVM,
native_app: NativeAppGlue,
config: ConfigurationRef,
native_window: RwLock<Option<NativeWindow>>,
/// Shared JNI bindings for the `KeyCharacterMap` class
key_map_binding: Arc<KeyCharacterMapBinding>,
/// A table of `KeyCharacterMap`s per `InputDevice` ID
/// these are used to be able to map key presses to unicode
/// characters
key_maps: Mutex<HashMap<i32, KeyCharacterMap>>,
/// While an app is reading input events it holds an
/// InputReceiver reference which we track to ensure
/// we don't hand out more than one receiver at a time
input_receiver: Mutex<Option<Weak<InputReceiver>>>,
}
impl AndroidAppInner {
pub fn vm_as_ptr(&self) -> *mut c_void {
let app_ptr = self.native_app.as_ptr();
unsafe { (*(*app_ptr).activity).vm as _ }
}
pub fn activity_as_ptr(&self) -> *mut c_void {
let app_ptr = self.native_app.as_ptr();
unsafe { (*(*app_ptr).activity).javaGameActivity as _ }
}
pub fn native_window(&self) -> Option<NativeWindow> {
self.native_window.read().unwrap().clone()
}
pub fn poll_events<F>(&self, timeout: Option<Duration>, mut callback: F)
where
F: FnMut(PollEvent),
{
trace!("poll_events");
unsafe {
let native_app = &self.native_app;
let mut fd: i32 = 0;
let mut events: i32 = 0;
let mut source: *mut core::ffi::c_void = ptr::null_mut();
let timeout_milliseconds = if let Some(timeout) = timeout {
timeout.as_millis() as i32
} else {
-1
};
trace!("Calling ALooper_pollAll, timeout = {timeout_milliseconds}");
let id = ALooper_pollAll(
timeout_milliseconds,
&mut fd,
&mut events,
&mut source as *mut *mut core::ffi::c_void,
);
match id {
ffi::ALOOPER_POLL_WAKE => {
trace!("ALooper_pollAll returned POLL_WAKE");
if ffi::android_app_input_available_wake_up(native_app.as_ptr()) {
log::debug!("Notifying Input Available");
callback(PollEvent::Main(MainEvent::InputAvailable));
}
callback(PollEvent::Wake);
}
ffi::ALOOPER_POLL_CALLBACK => {
// ALooper_pollAll is documented to handle all callback sources internally so it should
// never return a _CALLBACK source id...
error!("Spurious ALOOPER_POLL_CALLBACK from ALopper_pollAll() (ignored)");
}
ffi::ALOOPER_POLL_TIMEOUT => {
trace!("ALooper_pollAll returned POLL_TIMEOUT");
callback(PollEvent::Timeout);
}
ffi::ALOOPER_POLL_ERROR => {
// If we have an IO error with our pipe to the main Java thread that's surely
// not something we can recover from
panic!("ALooper_pollAll returned POLL_ERROR");
}
id if id >= 0 => {
match id as u32 {
ffi::NativeAppGlueLooperId_LOOPER_ID_MAIN => {
trace!("ALooper_pollAll returned ID_MAIN");
let source: *mut ffi::android_poll_source = source.cast();
if !source.is_null() {
let cmd_i = ffi::android_app_read_cmd(native_app.as_ptr());
let cmd = match cmd_i as u32 {
//NativeAppGlueAppCmd_UNUSED_APP_CMD_INPUT_CHANGED => AndroidAppMainEvent::InputChanged,
ffi::NativeAppGlueAppCmd_APP_CMD_INIT_WINDOW => {
MainEvent::InitWindow {}
}
ffi::NativeAppGlueAppCmd_APP_CMD_TERM_WINDOW => {
MainEvent::TerminateWindow {}
}
ffi::NativeAppGlueAppCmd_APP_CMD_WINDOW_RESIZED => {
MainEvent::WindowResized {}
}
ffi::NativeAppGlueAppCmd_APP_CMD_WINDOW_REDRAW_NEEDED => {
MainEvent::RedrawNeeded {}
}
ffi::NativeAppGlueAppCmd_APP_CMD_CONTENT_RECT_CHANGED => {
MainEvent::ContentRectChanged {}
}
ffi::NativeAppGlueAppCmd_APP_CMD_GAINED_FOCUS => {
MainEvent::GainedFocus
}
ffi::NativeAppGlueAppCmd_APP_CMD_LOST_FOCUS => {
MainEvent::LostFocus
}
ffi::NativeAppGlueAppCmd_APP_CMD_CONFIG_CHANGED => {
MainEvent::ConfigChanged {}
}
ffi::NativeAppGlueAppCmd_APP_CMD_LOW_MEMORY => {
MainEvent::LowMemory
}
ffi::NativeAppGlueAppCmd_APP_CMD_START => MainEvent::Start,
ffi::NativeAppGlueAppCmd_APP_CMD_RESUME => MainEvent::Resume {
loader: StateLoader { app: self },
},
ffi::NativeAppGlueAppCmd_APP_CMD_SAVE_STATE => {
MainEvent::SaveState {
saver: StateSaver { app: self },
}
}
ffi::NativeAppGlueAppCmd_APP_CMD_PAUSE => MainEvent::Pause,
ffi::NativeAppGlueAppCmd_APP_CMD_STOP => MainEvent::Stop,
ffi::NativeAppGlueAppCmd_APP_CMD_DESTROY => MainEvent::Destroy,
ffi::NativeAppGlueAppCmd_APP_CMD_WINDOW_INSETS_CHANGED => {
MainEvent::InsetsChanged {}
}
_ => unreachable!(),
};
trace!("Read ID_MAIN command {cmd_i} = {cmd:?}");
trace!("Calling android_app_pre_exec_cmd({cmd_i})");
ffi::android_app_pre_exec_cmd(native_app.as_ptr(), cmd_i);
match cmd {
MainEvent::ConfigChanged { .. } => {
self.config.replace(Configuration::clone_from_ptr(
NonNull::new_unchecked((*native_app.as_ptr()).config),
));
}
MainEvent::InitWindow { .. } => {
let win_ptr = (*native_app.as_ptr()).window;
// It's important that we use ::clone_from_ptr() here
// because NativeWindow has a Drop implementation that
// will unconditionally _release() the native window
*self.native_window.write().unwrap() =
Some(NativeWindow::clone_from_ptr(
NonNull::new(win_ptr).unwrap(),
));
}
MainEvent::TerminateWindow { .. } => {
*self.native_window.write().unwrap() = None;
}
_ => {}
}
trace!("Invoking callback for ID_MAIN command = {:?}", cmd);
callback(PollEvent::Main(cmd));
trace!("Calling android_app_post_exec_cmd({cmd_i})");
ffi::android_app_post_exec_cmd(native_app.as_ptr(), cmd_i);
} else {
panic!("ALooper_pollAll returned ID_MAIN event with NULL android_poll_source!");
}
}
_ => {
error!("Ignoring spurious ALooper event source: id = {id}, fd = {fd}, events = {events:?}, data = {source:?}");
}
}
}
_ => {
error!("Spurious ALooper_pollAll return value {id} (ignored)");
}
}
}
}
pub fn set_window_flags(
&self,
add_flags: WindowManagerFlags,
remove_flags: WindowManagerFlags,
) {
unsafe {
let activity = (*self.native_app.as_ptr()).activity;
ffi::GameActivity_setWindowFlags(activity, add_flags.bits(), remove_flags.bits())
}
}
// TODO: move into a trait
pub fn show_soft_input(&self, show_implicit: bool) {
unsafe {
let activity = (*self.native_app.as_ptr()).activity;
let flags = if show_implicit {
ffi::ShowImeFlags_SHOW_IMPLICIT
} else {
0
};
ffi::GameActivity_showSoftInput(activity, flags);
}
}
// TODO: move into a trait
pub fn hide_soft_input(&self, hide_implicit_only: bool) {
unsafe {
let activity = (*self.native_app.as_ptr()).activity;
let flags = if hide_implicit_only {
ffi::HideImeFlags_HIDE_IMPLICIT_ONLY
} else {
0
};
ffi::GameActivity_hideSoftInput(activity, flags);
}
}
unsafe extern "C" fn map_input_state_to_text_event_callback(
context: *mut c_void,
state: *const ffi::GameTextInputState,
) {
// Java uses a modified UTF-8 format, which is a modified cesu8 format
let out_ptr: *mut TextInputState = context.cast();
let text_modified_utf8: *const u8 = (*state).text_UTF8.cast();
let text_modified_utf8 =
std::slice::from_raw_parts(text_modified_utf8, (*state).text_length as usize);
match cesu8::from_java_cesu8(text_modified_utf8) {
Ok(str) => {
let len = str.len();
(*out_ptr).text = String::from(str);
let selection_start = (*state).selection.start.clamp(0, len as i32 + 1);
let selection_end = (*state).selection.end.clamp(0, len as i32 + 1);
(*out_ptr).selection = TextSpan {
start: selection_start as usize,
end: selection_end as usize,
};
if (*state).composingRegion.start < 0 || (*state).composingRegion.end < 0 {
(*out_ptr).compose_region = None;
} else {
(*out_ptr).compose_region = Some(TextSpan {
start: (*state).composingRegion.start as usize,
end: (*state).composingRegion.end as usize,
});
}
}
Err(err) => {
log::error!("Invalid UTF8 text in TextEvent: {}", err);
}
}
}
// TODO: move into a trait
pub fn text_input_state(&self) -> TextInputState {
self.native_app.text_input_state()
}
// TODO: move into a trait
pub fn set_text_input_state(&self, state: TextInputState) {
self.native_app.set_text_input_state(state);
}
pub(crate) fn device_key_character_map(
&self,
device_id: i32,
) -> InternalResult<KeyCharacterMap> {
let mut guard = self.key_maps.lock().unwrap();
let key_map = match guard.entry(device_id) {
std::collections::hash_map::Entry::Occupied(occupied) => occupied.get().clone(),
std::collections::hash_map::Entry::Vacant(vacant) => {
let character_map = jni_utils::device_key_character_map(
self.jvm.clone(),
self.key_map_binding.clone(),
device_id,
)?;
vacant.insert(character_map.clone());
character_map
}
};
Ok(key_map)
}
pub fn enable_motion_axis(&mut self, axis: Axis) {
let axis: u32 = axis.into();
unsafe { ffi::GameActivityPointerAxes_enableAxis(axis as i32) }
}
pub fn disable_motion_axis(&mut self, axis: Axis) {
let axis: u32 = axis.into();
unsafe { ffi::GameActivityPointerAxes_disableAxis(axis as i32) }
}
pub fn create_waker(&self) -> AndroidAppWaker {
unsafe {
// From the application's pov we assume the app_ptr and looper pointer
// have static lifetimes and we can safely assume they are never NULL.
let app_ptr = self.native_app.as_ptr();
AndroidAppWaker {
looper: NonNull::new_unchecked((*app_ptr).looper),
}
}
}
pub fn config(&self) -> ConfigurationRef {
self.config.clone()
}
pub fn content_rect(&self) -> Rect {
unsafe {
let app_ptr = self.native_app.as_ptr();
Rect {
left: (*app_ptr).contentRect.left,
right: (*app_ptr).contentRect.right,
top: (*app_ptr).contentRect.top,
bottom: (*app_ptr).contentRect.bottom,
}
}
}
pub fn asset_manager(&self) -> AssetManager {
unsafe {
let app_ptr = self.native_app.as_ptr();
let am_ptr = NonNull::new_unchecked((*(*app_ptr).activity).assetManager);
AssetManager::from_ptr(am_ptr)
}
}
pub(crate) fn input_events_receiver(&self) -> InternalResult<Arc<InputReceiver>> {
let mut guard = self.input_receiver.lock().unwrap();
// Make sure we don't hand out more than one receiver at a time because
// turning the reciever into an interator will perform a swap_buffers
// for the buffered input events which shouldn't happen while we're in
// the middle of iterating events
if let Some(receiver) = &*guard {
if receiver.strong_count() > 0 {
return Err(crate::error::InternalAppError::InputUnavailable);
}
}
*guard = None;
let receiver = Arc::new(InputReceiver {
native_app: self.native_app.clone(),
});
*guard = Some(Arc::downgrade(&receiver));
Ok(receiver)
}
pub fn internal_data_path(&self) -> Option<std::path::PathBuf> {
unsafe {
let app_ptr = self.native_app.as_ptr();
try_get_path_from_ptr((*(*app_ptr).activity).internalDataPath)
}
}
pub fn external_data_path(&self) -> Option<std::path::PathBuf> {
unsafe {
let app_ptr = self.native_app.as_ptr();
try_get_path_from_ptr((*(*app_ptr).activity).externalDataPath)
}
}
pub fn obb_path(&self) -> Option<std::path::PathBuf> {
unsafe {
let app_ptr = self.native_app.as_ptr();
try_get_path_from_ptr((*(*app_ptr).activity).obbPath)
}
}
}
struct MotionEventsLendingIterator {
pos: usize,
count: usize,
}
impl MotionEventsLendingIterator {
fn new(buffer: &InputBuffer) -> Self {
Self {
pos: 0,
count: buffer.motion_events_count(),
}
}
fn next<'buf>(&mut self, buffer: &'buf InputBuffer) -> Option<MotionEvent<'buf>> {
if self.pos < self.count {
// Safety:
// - This iterator currently has exclusive access to the front buffer of events
// - We know the buffer is non-null
// - `pos` is less than the number of events stored in the buffer
let ga_event = unsafe {
(*buffer.ptr.as_ptr())
.motionEvents
.add(self.pos)
.as_ref()
.unwrap()
};
let event = MotionEvent::new(ga_event);
self.pos += 1;
Some(event)
} else {
None
}
}
}
struct KeyEventsLendingIterator {
pos: usize,
count: usize,
}
impl KeyEventsLendingIterator {
fn new(buffer: &InputBuffer) -> Self {
Self {
pos: 0,
count: buffer.key_events_count(),
}
}
fn next<'buf>(&mut self, buffer: &'buf InputBuffer) -> Option<KeyEvent<'buf>> {
if self.pos < self.count {
// Safety:
// - This iterator currently has exclusive access to the front buffer of events
// - We know the buffer is non-null
// - `pos` is less than the number of events stored in the buffer
let ga_event = unsafe {
(*buffer.ptr.as_ptr())
.keyEvents
.add(self.pos)
.as_ref()
.unwrap()
};
let event = KeyEvent::new(ga_event);
self.pos += 1;
Some(event)
} else {
None
}
}
}
struct InputBuffer<'a> {
ptr: NonNull<ffi::android_input_buffer>,
_lifetime: PhantomData<&'a ffi::android_input_buffer>,
}
impl<'a> InputBuffer<'a> {
pub(crate) fn from_ptr(ptr: NonNull<ffi::android_input_buffer>) -> InputBuffer<'a> {
Self {
ptr,
_lifetime: PhantomData,
}
}
pub fn motion_events_count(&self) -> usize {
unsafe { (*self.ptr.as_ptr()).motionEventsCount as usize }
}
pub fn key_events_count(&self) -> usize {
unsafe { (*self.ptr.as_ptr()).keyEventsCount as usize }
}
}
impl<'a> Drop for InputBuffer<'a> {
fn drop(&mut self) {
unsafe {
ffi::android_app_clear_motion_events(self.ptr.as_ptr());
ffi::android_app_clear_key_events(self.ptr.as_ptr());
}
}
}
/// Conceptually we can think of this like the receiver end of an
/// input events channel.
///
/// After being passed back to AndroidApp it gets turned into a
/// lending iterator for pending input events.
///
/// It serves two purposes:
/// 1. It represents an exclusive access to input events (the application
/// can only have one receiver at a time) and it's intended to support
/// the double-buffering design for input events in GameActivity where
/// we issue a swap_buffers before iterating events and wouldn't want
/// another swap to be possible before finishing - especially since
/// we want to borrow directly from the buffer while dispatching.
/// 2. It doesn't borrow from AndroidAppInner so we can pass it back to
/// AndroidApp which can drop its lock around AndroidAppInner and
/// it can then be turned into a lending iterator. (We wouldn't
/// be able to pass the iterator back to the application if it
/// borrowed from within the lock and we need to drop the lock
/// because otherwise the app wouldn't be able to access the AndroidApp
/// API in any way while iterating events)
#[derive(Debug)]
pub(crate) struct InputReceiver {
// Safety: the native_app effectively has a static lifetime and it
// has its own internal locking when calling
// `android_app_swap_input_buffers`
native_app: NativeAppGlue,
}
impl<'a> From<Arc<InputReceiver>> for InputIteratorInner<'a> {
fn from(receiver: Arc<InputReceiver>) -> Self {
let buffered = unsafe {
let app_ptr = receiver.native_app.as_ptr();
let input_buffer = ffi::android_app_swap_input_buffers(app_ptr);
NonNull::new(input_buffer).map(|input_buffer| {
let buffer = InputBuffer::from_ptr(input_buffer);
let keys_iter = KeyEventsLendingIterator::new(&buffer);
let motion_iter = MotionEventsLendingIterator::new(&buffer);
BufferedEvents::<'a> {
buffer,
keys_iter,
motion_iter,
}
})
};
let native_app = receiver.native_app.clone();
Self {
_receiver: receiver,
buffered,
native_app,
text_event_checked: false,
}
}
}
struct BufferedEvents<'a> {
buffer: InputBuffer<'a>,
keys_iter: KeyEventsLendingIterator,
motion_iter: MotionEventsLendingIterator,
}
pub(crate) struct InputIteratorInner<'a> {
// Held to maintain exclusive access to buffered input events
_receiver: Arc<InputReceiver>,
buffered: Option<BufferedEvents<'a>>,
native_app: NativeAppGlue,
text_event_checked: bool,
}
impl<'a> InputIteratorInner<'a> {
pub(crate) fn next<F>(&mut self, callback: F) -> bool
where
F: FnOnce(&input::InputEvent) -> InputStatus,
{
if let Some(buffered) = &mut self.buffered {
if let Some(key_event) = buffered.keys_iter.next(&buffered.buffer) {
let _ = callback(&InputEvent::KeyEvent(key_event));
return true;
}
if let Some(motion_event) = buffered.motion_iter.next(&buffered.buffer) {
let _ = callback(&InputEvent::MotionEvent(motion_event));
return true;
}
self.buffered = None;
}
if !self.text_event_checked {
self.text_event_checked = true;
unsafe {
let app_ptr = self.native_app.as_ptr();
// XXX: It looks like the GameActivity implementation should
// be using atomic ops to set this flag, and require us to
// use atomics to check and clear it too.
//
// We currently just hope that with the lack of atomic ops that
// the compiler isn't reordering code so this gets flagged
// before the java main thread really updates the state.
if (*app_ptr).textInputState != 0 {
let state = self.native_app.text_input_state(); // Will clear .textInputState
let _ = callback(&InputEvent::TextEvent(state));
return true;
}
}
}
false
}
}
// Rust doesn't give us a clean way to directly export symbols from C/C++
// so we rename the C/C++ symbols and re-export these JNI entrypoints from
// Rust...
//
// https://github.com/rust-lang/rfcs/issues/2771
extern "C" {
pub fn Java_com_google_androidgamesdk_GameActivity_initializeNativeCode_C(
env: *mut JNIEnv,
javaGameActivity: jobject,
internalDataDir: jstring,
obbDir: jstring,
externalDataDir: jstring,
jAssetMgr: jobject,
savedState: jbyteArray,
javaConfig: jobject,
) -> jlong;
pub fn GameActivity_onCreate_C(
activity: *mut ffi::GameActivity,
savedState: *mut ::std::os::raw::c_void,
savedStateSize: libc::size_t,
);
}
#[no_mangle]
pub unsafe extern "C" fn Java_com_google_androidgamesdk_GameActivity_initializeNativeCode(
env: *mut JNIEnv,
java_game_activity: jobject,
internal_data_dir: jstring,
obb_dir: jstring,
external_data_dir: jstring,
jasset_mgr: jobject,
saved_state: jbyteArray,
java_config: jobject,
) -> jni_sys::jlong {
Java_com_google_androidgamesdk_GameActivity_initializeNativeCode_C(
env,
java_game_activity,
internal_data_dir,
obb_dir,
external_data_dir,
jasset_mgr,
saved_state,
java_config,
)
}
#[no_mangle]
pub unsafe extern "C" fn GameActivity_onCreate(
activity: *mut ffi::GameActivity,
saved_state: *mut ::std::os::raw::c_void,
saved_state_size: libc::size_t,
) {
GameActivity_onCreate_C(activity, saved_state, saved_state_size);
}
extern "Rust" {
pub fn android_main(app: AndroidApp);
}
// This is a spring board between android_native_app_glue and the user's
// `app_main` function. This is run on a dedicated thread spawned
// by android_native_app_glue.
#[no_mangle]
pub unsafe extern "C" fn _rust_glue_entry(native_app: *mut ffi::android_app) {
abort_on_panic(|| {
let _join_log_forwarder = forward_stdio_to_logcat();
let jvm = unsafe {
let jvm = (*(*native_app).activity).vm;
let activity: jobject = (*(*native_app).activity).javaGameActivity;
ndk_context::initialize_android_context(jvm.cast(), activity.cast());
let jvm = CloneJavaVM::from_raw(jvm).unwrap();
// Since this is a newly spawned thread then the JVM hasn't been attached
// to the thread yet. Attach before calling the applications main function
// so they can safely make JNI calls
jvm.attach_current_thread_permanently().unwrap();
jvm
};
unsafe {
// Name thread - this needs to happen here after attaching to a JVM thread,
// since that changes the thread name to something like "Thread-2".
let thread_name = std::ffi::CStr::from_bytes_with_nul(b"android_main\0").unwrap();
libc::pthread_setname_np(libc::pthread_self(), thread_name.as_ptr());
let app = AndroidApp::from_ptr(NonNull::new(native_app).unwrap(), jvm.clone());
// We want to specifically catch any panic from the application's android_main
// so we can finish + destroy the Activity gracefully via the JVM
catch_unwind(|| {
// XXX: If we were in control of the Java Activity subclass then
// we could potentially run the android_main function via a Java native method
// springboard (e.g. call an Activity subclass method that calls a jni native
// method that then just calls android_main()) that would make sure there was
// a Java frame at the base of our call stack which would then be recognised
// when calling FindClass to lookup a suitable classLoader, instead of
// defaulting to the system loader. Without this then it's difficult for native
// code to look up non-standard Java classes.
android_main(app);
})
.unwrap_or_else(|panic| log_panic(panic));
// Let JVM know that our Activity can be destroyed before detaching from the JVM
//
// "Note that this method can be called from any thread; it will send a message
// to the main thread of the process where the Java finish call will take place"
ffi::GameActivity_finish((*native_app).activity);
// This should detach automatically but lets detach explicitly to avoid depending
// on the TLS trickery in `jni-rs`
jvm.detach_current_thread();
ndk_context::release_android_context();
}
})
}

1033
vendor/android-activity/src/input.rs vendored Normal file

File diff suppressed because it is too large Load Diff

351
vendor/android-activity/src/input/sdk.rs vendored Normal file
View File

@@ -0,0 +1,351 @@
use std::sync::Arc;
use jni::{
objects::{GlobalRef, JClass, JMethodID, JObject, JStaticMethodID, JValue},
signature::{Primitive, ReturnType},
JNIEnv,
};
use jni_sys::jint;
use crate::{
input::{Keycode, MetaState},
jni_utils::CloneJavaVM,
};
use crate::{
error::{AppError, InternalAppError},
jni_utils,
};
/// An enum representing the types of keyboards that may generate key events
///
/// See [getKeyboardType() docs](https://developer.android.com/reference/android/view/KeyCharacterMap#getKeyboardType())
///
/// # Android Extensible Enum
///
/// This is a runtime [extensible enum](`crate#android-extensible-enums`) and
/// should be handled similar to a `#[non_exhaustive]` enum to maintain
/// forwards compatibility.
///
/// This implements `Into<u32>` and `From<u32>` for converting to/from Android
/// SDK integer values.
#[derive(
Debug, Clone, Copy, PartialEq, Eq, Hash, num_enum::FromPrimitive, num_enum::IntoPrimitive,
)]
#[non_exhaustive]
#[repr(u32)]
pub enum KeyboardType {
/// A numeric (12-key) keyboard.
///
/// A numeric keyboard supports text entry using a multi-tap approach. It may be necessary to tap a key multiple times to generate the desired letter or symbol.
///
/// This type of keyboard is generally designed for thumb typing.
Numeric,
/// A keyboard with all the letters, but with more than one letter per key.
///
/// This type of keyboard is generally designed for thumb typing.
Predictive,
/// A keyboard with all the letters, and maybe some numbers.
///
/// An alphabetic keyboard supports text entry directly but may have a condensed layout with a small form factor. In contrast to a full keyboard, some symbols may only be accessible using special on-screen character pickers. In addition, to improve typing speed and accuracy, the framework provides special affordances for alphabetic keyboards such as auto-capitalization and toggled / locked shift and alt keys.
///
/// This type of keyboard is generally designed for thumb typing.
Alpha,
/// A full PC-style keyboard.
///
/// A full keyboard behaves like a PC keyboard. All symbols are accessed directly by pressing keys on the keyboard without on-screen support or affordances such as auto-capitalization.
///
/// This type of keyboard is generally designed for full two hand typing.
Full,
/// A keyboard that is only used to control special functions rather than for typing.
///
/// A special function keyboard consists only of non-printing keys such as HOME and POWER that are not actually used for typing.
SpecialFunction,
#[doc(hidden)]
#[num_enum(catch_all)]
__Unknown(u32),
}
/// Either represents, a unicode character or combining accent from a
/// [`KeyCharacterMap`], or `None` for non-printable keys.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum KeyMapChar {
None,
Unicode(char),
CombiningAccent(char),
}
// I've also tried to think here about how to we could potentially automatically
// generate a binding struct like `KeyCharacterMapBinding` with a procmacro and
// so have intentionally limited the `Binding` being a very thin, un-opinionated
// wrapper based on basic JNI types.
/// Lower-level JNI binding for `KeyCharacterMap` class only holds 'static state
/// and can be shared with an `Arc` ref count.
///
/// The separation here also neatly helps us separate `InternalAppError` from
/// `AppError` for mapping JNI errors without exposing any `jni-rs` types in the
/// public API.
#[derive(Debug)]
pub(crate) struct KeyCharacterMapBinding {
//vm: JavaVM,
klass: GlobalRef,
get_method_id: JMethodID,
get_dead_char_method_id: JStaticMethodID,
get_keyboard_type_method_id: JMethodID,
}
impl KeyCharacterMapBinding {
pub(crate) fn new(env: &mut JNIEnv) -> Result<Self, InternalAppError> {
let binding = env.with_local_frame::<_, _, InternalAppError>(10, |env| {
let klass = env.find_class("android/view/KeyCharacterMap")?; // Creates a local ref
Ok(Self {
get_method_id: env.get_method_id(&klass, "get", "(II)I")?,
get_dead_char_method_id: env.get_static_method_id(
&klass,
"getDeadChar",
"(II)I",
)?,
get_keyboard_type_method_id: env.get_method_id(&klass, "getKeyboardType", "()I")?,
klass: env.new_global_ref(&klass)?,
})
})?;
Ok(binding)
}
pub fn get<'local>(
&self,
env: &'local mut JNIEnv,
key_map: impl AsRef<JObject<'local>>,
key_code: jint,
meta_state: jint,
) -> Result<jint, InternalAppError> {
let key_map = key_map.as_ref();
// Safety:
// - we know our global `key_map` reference is non-null and valid.
// - we know `get_method_id` remains valid
// - we know that the signature of KeyCharacterMap::get is `(int, int) -> int`
// - we know this won't leak any local references as a side effect
//
// We know it's ok to unwrap the `.i()` value since we explicitly
// specify the return type as `Int`
let unicode = unsafe {
env.call_method_unchecked(
key_map,
self.get_method_id,
ReturnType::Primitive(Primitive::Int),
&[
JValue::Int(key_code).as_jni(),
JValue::Int(meta_state).as_jni(),
],
)
}
.map_err(|err| jni_utils::clear_and_map_exception_to_err(env, err))?;
Ok(unicode.i().unwrap())
}
pub fn get_dead_char(
&self,
env: &mut JNIEnv,
accent_char: jint,
base_char: jint,
) -> Result<jint, InternalAppError> {
// Safety:
// - we know `get_dead_char_method_id` remains valid
// - we know that KeyCharacterMap::getDeadKey is a static method
// - we know that the signature of KeyCharacterMap::getDeadKey is `(int, int) -> int`
// - we know this won't leak any local references as a side effect
//
// We know it's ok to unwrap the `.i()` value since we explicitly
// specify the return type as `Int`
// Urgh, it's pretty terrible that there's no ergonomic/safe way to get a JClass reference from a GlobalRef
// Safety: we don't do anything that would try to delete the JClass as if it were a real local reference
let klass = unsafe { JClass::from_raw(self.klass.as_obj().as_raw()) };
let unicode = unsafe {
env.call_static_method_unchecked(
&klass,
self.get_dead_char_method_id,
ReturnType::Primitive(Primitive::Int),
&[
JValue::Int(accent_char).as_jni(),
JValue::Int(base_char).as_jni(),
],
)
}
.map_err(|err| jni_utils::clear_and_map_exception_to_err(env, err))?;
Ok(unicode.i().unwrap())
}
pub fn get_keyboard_type<'local>(
&self,
env: &'local mut JNIEnv,
key_map: impl AsRef<JObject<'local>>,
) -> Result<jint, InternalAppError> {
let key_map = key_map.as_ref();
// Safety:
// - we know our global `key_map` reference is non-null and valid.
// - we know `get_keyboard_type_method_id` remains valid
// - we know that the signature of KeyCharacterMap::getKeyboardType is `() -> int`
// - we know this won't leak any local references as a side effect
//
// We know it's ok to unwrap the `.i()` value since we explicitly
// specify the return type as `Int`
Ok(unsafe {
env.call_method_unchecked(
key_map,
self.get_keyboard_type_method_id,
ReturnType::Primitive(Primitive::Int),
&[],
)
}
.map_err(|err| jni_utils::clear_and_map_exception_to_err(env, err))?
.i()
.unwrap())
}
}
/// Describes the keys provided by a keyboard device and their associated labels.
#[derive(Clone, Debug)]
pub struct KeyCharacterMap {
jvm: CloneJavaVM,
binding: Arc<KeyCharacterMapBinding>,
key_map: GlobalRef,
}
impl KeyCharacterMap {
pub(crate) fn new(
jvm: CloneJavaVM,
binding: Arc<KeyCharacterMapBinding>,
key_map: GlobalRef,
) -> Self {
Self {
jvm,
binding,
key_map,
}
}
/// Gets the Unicode character generated by the specified [`Keycode`] and [`MetaState`] combination.
///
/// Returns [`KeyMapChar::None`] if the key is not one that is used to type Unicode characters.
///
/// Returns [`KeyMapChar::CombiningAccent`] if the key is a "dead key" that should be combined with
/// another to actually produce a character -- see [`KeyCharacterMap::get_dead_char`].
///
/// # Errors
///
/// Since this API needs to use JNI internally to call into the Android JVM it may return
/// a [`AppError::JavaError`] in case there is a spurious JNI error or an exception
/// is caught.
pub fn get(&self, key_code: Keycode, meta_state: MetaState) -> Result<KeyMapChar, AppError> {
let key_code: u32 = key_code.into();
let key_code = key_code as jni_sys::jint;
let meta_state: u32 = meta_state.0;
let meta_state = meta_state as jni_sys::jint;
// Since we expect this API to be called from the `main` thread then we expect to already be
// attached to the JVM
//
// Safety: there's no other JNIEnv in scope so this env can't be used to subvert the mutable
// borrow rules that ensure we can only add local references to the top JNI frame.
let mut env = self.jvm.get_env().map_err(|err| {
let err: InternalAppError = err.into();
err
})?;
let unicode = self
.binding
.get(&mut env, self.key_map.as_obj(), key_code, meta_state)?;
let unicode = unicode as u32;
const COMBINING_ACCENT: u32 = 0x80000000;
const COMBINING_ACCENT_MASK: u32 = !COMBINING_ACCENT;
if unicode == 0 {
Ok(KeyMapChar::None)
} else if unicode & COMBINING_ACCENT == COMBINING_ACCENT {
let accent = unicode & COMBINING_ACCENT_MASK;
// Safety: assumes Android key maps don't contain invalid unicode characters
Ok(KeyMapChar::CombiningAccent(unsafe {
char::from_u32_unchecked(accent)
}))
} else {
// Safety: assumes Android key maps don't contain invalid unicode characters
Ok(KeyMapChar::Unicode(unsafe {
char::from_u32_unchecked(unicode)
}))
}
}
/// Get the character that is produced by combining the dead key producing accent with the key producing character c.
///
/// For example, ```get_dead_char('`', 'e')``` returns 'è'. `get_dead_char('^', ' ')` returns '^' and `get_dead_char('^', '^')` returns '^'.
///
/// # Errors
///
/// Since this API needs to use JNI internally to call into the Android JVM it may return
/// a [`AppError::JavaError`] in case there is a spurious JNI error or an exception
/// is caught.
pub fn get_dead_char(
&self,
accent_char: char,
base_char: char,
) -> Result<Option<char>, AppError> {
let accent_char = accent_char as jni_sys::jint;
let base_char = base_char as jni_sys::jint;
// Since we expect this API to be called from the `main` thread then we expect to already be
// attached to the JVM
//
// Safety: there's no other JNIEnv in scope so this env can't be used to subvert the mutable
// borrow rules that ensure we can only add local references to the top JNI frame.
let mut env = self.jvm.get_env().map_err(|err| {
let err: InternalAppError = err.into();
err
})?;
let unicode = self
.binding
.get_dead_char(&mut env, accent_char, base_char)?;
let unicode = unicode as u32;
// Safety: assumes Android key maps don't contain invalid unicode characters
Ok(if unicode == 0 {
None
} else {
Some(unsafe { char::from_u32_unchecked(unicode) })
})
}
/// Gets the keyboard type.
///
/// Different keyboard types have different semantics. See [`KeyboardType`] for details.
///
/// # Errors
///
/// Since this API needs to use JNI internally to call into the Android JVM it may return
/// a [`AppError::JavaError`] in case there is a spurious JNI error or an exception
/// is caught.
pub fn get_keyboard_type(&self) -> Result<KeyboardType, AppError> {
// Since we expect this API to be called from the `main` thread then we expect to already be
// attached to the JVM
//
// Safety: there's no other JNIEnv in scope so this env can't be used to subvert the mutable
// borrow rules that ensure we can only add local references to the top JNI frame.
let mut env = self.jvm.get_env().map_err(|err| {
let err: InternalAppError = err.into();
err
})?;
let keyboard_type = self
.binding
.get_keyboard_type(&mut env, self.key_map.as_obj())?;
let keyboard_type = keyboard_type as u32;
Ok(keyboard_type.into())
}
}

151
vendor/android-activity/src/jni_utils.rs vendored Normal file
View File

@@ -0,0 +1,151 @@
//! The JNI calls we make in this crate are often not part of a Java native
//! method implementation and so we can't assume we have a JNI local frame that
//! is going to unwind and free local references, and we also can't just leave
//! exceptions to get thrown when returning to Java.
//!
//! These utilities help us check + clear exceptions and map them into Rust Errors.
use std::{ops::Deref, sync::Arc};
use jni::{
objects::{JObject, JString},
JavaVM,
};
use crate::{
error::{InternalAppError, InternalResult},
input::{KeyCharacterMap, KeyCharacterMapBinding},
};
// TODO: JavaVM should implement Clone
#[derive(Debug)]
pub(crate) struct CloneJavaVM {
pub jvm: JavaVM,
}
impl Clone for CloneJavaVM {
fn clone(&self) -> Self {
Self {
jvm: unsafe { JavaVM::from_raw(self.jvm.get_java_vm_pointer()).unwrap() },
}
}
}
impl CloneJavaVM {
pub unsafe fn from_raw(jvm: *mut jni_sys::JavaVM) -> InternalResult<Self> {
Ok(Self {
jvm: JavaVM::from_raw(jvm)?,
})
}
}
unsafe impl Send for CloneJavaVM {}
unsafe impl Sync for CloneJavaVM {}
impl Deref for CloneJavaVM {
type Target = JavaVM;
fn deref(&self) -> &Self::Target {
&self.jvm
}
}
/// Use with `.map_err()` to map `jni::errors::Error::JavaException` into a
/// richer error based on the actual contents of the `JThrowable`
///
/// (The `jni` crate doesn't do that automatically since it's more
/// common to let the exception get thrown when returning to Java)
///
/// This will also clear the exception
pub(crate) fn clear_and_map_exception_to_err(
env: &mut jni::JNIEnv<'_>,
err: jni::errors::Error,
) -> InternalAppError {
if matches!(err, jni::errors::Error::JavaException) {
let result = env.with_local_frame::<_, _, InternalAppError>(5, |env| {
let e = env.exception_occurred()?;
assert!(!e.is_null()); // should only be called after receiving a JavaException Result
env.exception_clear()?;
let class = env.get_object_class(&e)?;
//let get_stack_trace_method = env.get_method_id(&class, "getStackTrace", "()[Ljava/lang/StackTraceElement;")?;
let get_message_method =
env.get_method_id(&class, "getMessage", "()Ljava/lang/String;")?;
let msg = unsafe {
env.call_method_unchecked(
&e,
get_message_method,
jni::signature::ReturnType::Object,
&[],
)?
.l()
.unwrap()
};
let msg = unsafe { JString::from_raw(JObject::into_raw(msg)) };
let msg = env.get_string(&msg)?;
let msg: String = msg.into();
// TODO: get Java backtrace:
/*
if let JValue::Object(elements) = env.call_method_unchecked(&e, get_stack_trace_method, jni::signature::ReturnType::Array, &[])? {
let elements = env.auto_local(elements);
}
*/
Ok(msg)
});
match result {
Ok(msg) => InternalAppError::JniException(msg),
Err(err) => InternalAppError::JniException(format!(
"UNKNOWN (Failed to query JThrowable: {err:?})"
)),
}
} else {
err.into()
}
}
pub(crate) fn device_key_character_map(
jvm: CloneJavaVM,
key_map_binding: Arc<KeyCharacterMapBinding>,
device_id: i32,
) -> InternalResult<KeyCharacterMap> {
// Don't really need to 'attach' since this should be called from the app's main thread that
// should already be attached, but the redundancy should be fine
//
// Attach 'permanently' to avoid any chance of detaching the thread from the VM
let mut env = jvm.attach_current_thread_permanently()?;
// We don't want to accidentally leak any local references while we
// aren't going to be returning from here back to the JVM, to unwind, so
// we make a local frame
let character_map = env.with_local_frame::<_, _, jni::errors::Error>(10, |env| {
let input_device_class = env.find_class("android/view/InputDevice")?; // Creates a local ref
let device = env
.call_static_method(
input_device_class,
"getDevice",
"(I)Landroid/view/InputDevice;",
&[device_id.into()],
)?
.l()?; // Creates a local ref
let character_map = env
.call_method(
&device,
"getKeyCharacterMap",
"()Landroid/view/KeyCharacterMap;",
&[],
)?
.l()?;
let character_map = env.new_global_ref(character_map)?;
Ok(character_map)
})?;
Ok(KeyCharacterMap::new(
jvm.clone(),
key_map_binding,
character_map,
))
}

869
vendor/android-activity/src/lib.rs vendored Normal file
View File

@@ -0,0 +1,869 @@
//! A glue layer for building standalone, Rust applications on Android
//!
//! This crate provides a "glue" layer for building native Rust
//! applications on Android, supporting multiple [`Activity`] base classes.
//! It's comparable to [`android_native_app_glue.c`][ndk_concepts]
//! for C/C++ applications.
//!
//! Currently the crate supports two `Activity` base classes:
//! 1. [`NativeActivity`] - Built in to Android, this doesn't require compiling any Java or Kotlin code.
//! 2. [`GameActivity`] - From the Android Game Development Kit, it has more
//! sophisticated input handling support than `NativeActivity`. `GameActivity`
//! is also based on the `AndroidAppCompat` class which can help with supporting
//! a wider range of devices.
//!
//! Standalone applications based on this crate need to be built as `cdylib` libraries, like:
//! ```
//! [lib]
//! crate_type=["cdylib"]
//! ```
//!
//! and implement a `#[no_mangle]` `android_main` entry point like this:
//! ```rust
//! #[no_mangle]
//! fn android_main(app: AndroidApp) {
//!
//! }
//! ```
//!
//! Once your application's `Activity` class has loaded and it calls `onCreate` then
//! `android-activity` will spawn a dedicated thread to run your `android_main` function,
//! separate from the Java thread that created the corresponding `Activity`.
//!
//! [`AndroidApp`] provides an interface to query state for the application as
//! well as monitor events, such as lifecycle and input events, that are
//! marshalled between the Java thread that owns the `Activity` and the native
//! thread that runs the `android_main()` code.
//!
//! # Cheaply Clonable [`AndroidApp`]
//!
//! [`AndroidApp`] is intended to be something that can be cheaply passed around
//! by referenced within an application. It is reference counted and can be
//! cheaply cloned.
//!
//! # `Send` and `Sync` [`AndroidApp`]
//!
//! Although an [`AndroidApp`] implements `Send` and `Sync` you do need to take
//! into consideration that some APIs, such as [`AndroidApp::poll_events()`] are
//! explicitly documented to only be usable from your `android_main()` thread.
//!
//! # Main Thread Initialization
//!
//! Before `android_main()` is called, the following application state
//! is also initialized:
//!
//! 1. An I/O thread is spawned that will handle redirecting standard input
//! and output to the Android log, visible via `logcat`.
//! 2. A `JavaVM` and `Activity` instance will be associated with the [`ndk_context`] crate
//! so that other, independent, Rust crates are able to find a JavaVM
//! for making JNI calls.
//! 3. The `JavaVM` will be attached to the native thread
//! 4. A [Looper] is attached to the Rust native thread.
//!
//!
//! These are undone after `android_main()` returns
//!
//! # Android Extensible Enums
//!
//! There are numerous enums in the `android-activity` API which are effectively
//! bindings to enums declared in the Android SDK which need to be considered
//! _runtime_ extensible.
//!
//! Any enum variants that come from the Android SDK may be extended in future
//! versions of Android and your code could be exposed to new variants if you
//! build an application that might be installed on new versions of Android.
//!
//! This crate follows a convention of adding a hidden `__Unknown(u32)` variant
//! to these enum to ensure we can always do lossless conversions between the
//! integers from the SDK and our corresponding Rust enums. This can be
//! important in case you need to pass certain variants back to the SDK
//! regardless of whether you knew about that variants specific semantics at
//! compile time.
//!
//! You should never include this `__Unknown(u32)` variant within any exhaustive
//! pattern match and should instead treat the enums like `#[non_exhaustive]`
//! enums that require you to add a catch-all for any `unknown => {}` values.
//!
//! Any code that would exhaustively include the `__Unknown(u32)` variant when
//! pattern matching can not be guaranteed to be forwards compatible with new
//! releases of `android-activity` which may add new Rust variants to these
//! enums without requiring a breaking semver bump.
//!
//! You can (infallibly) convert these enums to and from primitive `u32` values
//! using `.into()`:
//!
//! For example, here is how you could ensure forwards compatibility with both
//! compile-time and runtime extensions of a `SomeEnum` enum:
//!
//! ```rust
//! match some_enum {
//! SomeEnum::Foo => {},
//! SomeEnum::Bar => {},
//! unhandled => {
//! let sdk_val: u32 = unhandled.into();
//! println!("Unhandled enum variant {some_enum:?} has SDK value: {sdk_val}");
//! }
//! }
//! ```
//!
//! [`Activity`]: https://developer.android.com/reference/android/app/Activity
//! [`NativeActivity`]: https://developer.android.com/reference/android/app/NativeActivity
//! [ndk_concepts]: https://developer.android.com/ndk/guides/concepts#naa
//! [`GameActivity`]: https://developer.android.com/games/agdk/integrate-game-activity
//! [Looper]: https://developer.android.com/reference/android/os/Looper
#![deny(clippy::manual_let_else)]
use std::hash::Hash;
use std::sync::Arc;
use std::sync::RwLock;
use std::time::Duration;
use input::KeyCharacterMap;
use libc::c_void;
use ndk::asset::AssetManager;
use ndk::native_window::NativeWindow;
use bitflags::bitflags;
#[cfg(not(target_os = "android"))]
compile_error!("android-activity only supports compiling for Android");
#[cfg(all(feature = "game-activity", feature = "native-activity"))]
compile_error!(
r#"The "game-activity" and "native-activity" features cannot be enabled at the same time"#
);
#[cfg(all(
not(any(feature = "game-activity", feature = "native-activity")),
not(doc)
))]
compile_error!(
r#"Either "game-activity" or "native-activity" must be enabled as features
If you have set one of these features then this error indicates that Cargo is trying to
link together multiple implementations of android-activity (with incompatible versions)
which is not supported.
Since android-activity is responsible for the `android_main` entrypoint of your application
then there can only be a single implementation of android-activity linked with your application.
You can use `cargo tree` (e.g. via `cargo ndk -t arm64-v8a tree`) to identify why multiple
versions have been resolved.
You may need to add a `[patch]` into your Cargo.toml to ensure a specific version of
android-activity is used across all of your application's crates."#
);
#[cfg_attr(any(feature = "native-activity", doc), path = "native_activity/mod.rs")]
#[cfg_attr(any(feature = "game-activity", doc), path = "game_activity/mod.rs")]
pub(crate) mod activity_impl;
pub mod error;
use error::Result;
pub mod input;
mod config;
pub use config::ConfigurationRef;
mod util;
mod jni_utils;
/// A rectangle with integer edge coordinates. Used to represent window insets, for example.
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct Rect {
pub left: i32,
pub top: i32,
pub right: i32,
pub bottom: i32,
}
impl Rect {
/// An empty `Rect` with all components set to zero.
pub fn empty() -> Self {
Self {
left: 0,
top: 0,
right: 0,
bottom: 0,
}
}
}
impl From<Rect> for ndk_sys::ARect {
fn from(rect: Rect) -> Self {
Self {
left: rect.left,
right: rect.right,
top: rect.top,
bottom: rect.bottom,
}
}
}
impl From<ndk_sys::ARect> for Rect {
fn from(arect: ndk_sys::ARect) -> Self {
Self {
left: arect.left,
right: arect.right,
top: arect.top,
bottom: arect.bottom,
}
}
}
pub use activity_impl::StateLoader;
pub use activity_impl::StateSaver;
/// An application event delivered during [`AndroidApp::poll_events`]
#[non_exhaustive]
#[derive(Debug)]
pub enum MainEvent<'a> {
/// New input events are available via [`AndroidApp::input_events_iter()`]
///
/// _Note: Even if more input is received this event will not be resent
/// until [`AndroidApp::input_events_iter()`] has been called, which enables
/// applications to batch up input processing without there being lots of
/// redundant event loop wake ups._
///
/// [`AndroidApp::input_events_iter()`]: AndroidApp::input_events_iter
InputAvailable,
/// Command from main thread: a new [`NativeWindow`] is ready for use. Upon
/// receiving this command, [`AndroidApp::native_window()`] will return the new window
#[non_exhaustive]
InitWindow {},
/// Command from main thread: the existing [`NativeWindow`] needs to be
/// terminated. Upon receiving this command, [`AndroidApp::native_window()`] still
/// returns the existing window; after returning from the [`AndroidApp::poll_events()`]
/// callback then [`AndroidApp::native_window()`] will return `None`.
#[non_exhaustive]
TerminateWindow {},
// TODO: include the prev and new size in the event
/// Command from main thread: the current [`NativeWindow`] has been resized.
/// Please redraw with its new size.
#[non_exhaustive]
WindowResized {},
/// Command from main thread: the current [`NativeWindow`] needs to be redrawn.
/// You should redraw the window before the [`AndroidApp::poll_events()`]
/// callback returns in order to avoid transient drawing glitches.
#[non_exhaustive]
RedrawNeeded {},
/// Command from main thread: the content area of the window has changed,
/// such as from the soft input window being shown or hidden. You can
/// get the new content rect by calling [`AndroidApp::content_rect()`]
#[non_exhaustive]
ContentRectChanged {},
/// Command from main thread: the app's activity window has gained
/// input focus.
GainedFocus,
/// Command from main thread: the app's activity window has lost
/// input focus.
LostFocus,
/// Command from main thread: the current device configuration has changed.
/// You can get a copy of the latest [`ndk::configuration::Configuration`] by calling
/// [`AndroidApp::config()`]
#[non_exhaustive]
ConfigChanged {},
/// Command from main thread: the system is running low on memory.
/// Try to reduce your memory use.
LowMemory,
/// Command from main thread: the app's activity has been started.
Start,
/// Command from main thread: the app's activity has been resumed.
#[non_exhaustive]
Resume { loader: StateLoader<'a> },
/// Command from main thread: the app should generate a new saved state
/// for itself, to restore from later if needed. If you have saved state,
/// allocate it with malloc and place it in android_app.savedState with
/// the size in android_app.savedStateSize. The will be freed for you
/// later.
#[non_exhaustive]
SaveState { saver: StateSaver<'a> },
/// Command from main thread: the app's activity has been paused.
Pause,
/// Command from main thread: the app's activity has been stopped.
Stop,
/// Command from main thread: the app's activity is being destroyed,
/// and waiting for the app thread to clean up and exit before proceeding.
Destroy,
/// Command from main thread: the app's insets have changed.
#[non_exhaustive]
InsetsChanged {},
}
/// An event delivered during [`AndroidApp::poll_events`]
#[derive(Debug)]
#[non_exhaustive]
pub enum PollEvent<'a> {
Wake,
Timeout,
Main(MainEvent<'a>),
}
/// Indicates whether an application has handled or ignored an event
///
/// If an event is not handled by an application then some default handling may happen.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum InputStatus {
Handled,
Unhandled,
}
use activity_impl::AndroidAppInner;
pub use activity_impl::AndroidAppWaker;
bitflags! {
/// Flags for [`AndroidApp::set_window_flags`]
/// as per the [android.view.WindowManager.LayoutParams Java API](https://developer.android.com/reference/android/view/WindowManager.LayoutParams)
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct WindowManagerFlags: u32 {
/// As long as this window is visible to the user, allow the lock
/// screen to activate while the screen is on. This can be used
/// independently, or in combination with
/// [`Self::KEEP_SCREEN_ON`] and/or [`Self::SHOW_WHEN_LOCKED`]
const ALLOW_LOCK_WHILE_SCREEN_ON = 0x00000001;
/// Everything behind this window will be dimmed. */
const DIM_BEHIND = 0x00000002;
/// Blur everything behind this window.
#[deprecated = "Blurring is no longer supported"]
const BLUR_BEHIND = 0x00000004;
/// This window won't ever get key input focus, so the
/// user can not send key or other button events to it. Those will
/// instead go to whatever focusable window is behind it. This flag
/// will also enable [`Self::NOT_TOUCH_MODAL`] whether or not
/// that is explicitly set.
///
/// Setting this flag also implies that the window will not need to
/// interact with
/// a soft input method, so it will be Z-ordered and positioned
/// independently of any active input method (typically this means it
/// gets Z-ordered on top of the input method, so it can use the full
/// screen for its content and cover the input method if needed. You
/// can use [`Self::ALT_FOCUSABLE_IM`] to modify this
/// behavior.
const NOT_FOCUSABLE = 0x00000008;
/// This window can never receive touch events.
const NOT_TOUCHABLE = 0x00000010;
/// Even when this window is focusable (if
/// [`Self::NOT_FOCUSABLE`] is not set), allow any pointer
/// events outside of the window to be sent to the windows behind it.
/// Otherwise it will consume all pointer events itself, regardless of
/// whether they are inside of the window.
const NOT_TOUCH_MODAL = 0x00000020;
/// When set, if the device is asleep when the touch
/// screen is pressed, you will receive this first touch event. Usually
/// the first touch event is consumed by the system since the user can
/// not see what they are pressing on.
#[deprecated]
const TOUCHABLE_WHEN_WAKING = 0x00000040;
/// As long as this window is visible to the user, keep
/// the device's screen turned on and bright.
const KEEP_SCREEN_ON = 0x00000080;
/// Place the window within the entire screen, ignoring
/// decorations around the border (such as the status bar). The
/// window must correctly position its contents to take the screen
/// decoration into account.
const LAYOUT_IN_SCREEN = 0x00000100;
/// Allows the window to extend outside of the screen.
const LAYOUT_NO_LIMITS = 0x00000200;
/// Hide all screen decorations (such as the status
/// bar) while this window is displayed. This allows the window to
/// use the entire display space for itself -- the status bar will
/// be hidden when an app window with this flag set is on the top
/// layer. A fullscreen window will ignore a value of
/// [`Self::SOFT_INPUT_ADJUST_RESIZE`] the window will stay
/// fullscreen and will not resize.
const FULLSCREEN = 0x00000400;
/// Override [`Self::FULLSCREEN`] and force the
/// screen decorations (such as the status bar) to be shown.
const FORCE_NOT_FULLSCREEN = 0x00000800;
/// Turn on dithering when compositing this window to
/// the screen.
#[deprecated="This flag is no longer used"]
const DITHER = 0x00001000;
/// Treat the content of the window as secure, preventing
/// it from appearing in screenshots or from being viewed on non-secure
/// displays.
const SECURE = 0x00002000;
/// A special mode where the layout parameters are used
/// to perform scaling of the surface when it is composited to the
/// screen.
const SCALED = 0x00004000;
/// Intended for windows that will often be used when the user is
/// holding the screen against their face, it will aggressively
/// filter the event stream to prevent unintended presses in this
/// situation that may not be desired for a particular window, when
/// such an event stream is detected, the application will receive
/// a `AMOTION_EVENT_ACTION_CANCEL` to indicate this so
/// applications can handle this accordingly by taking no action on
/// the event until the finger is released.
const IGNORE_CHEEK_PRESSES = 0x00008000;
/// A special option only for use in combination with
/// [`Self::LAYOUT_IN_SCREEN`]. When requesting layout in
/// the screen your window may appear on top of or behind screen decorations
/// such as the status bar. By also including this flag, the window
/// manager will report the inset rectangle needed to ensure your
/// content is not covered by screen decorations.
const LAYOUT_INSET_DECOR = 0x00010000;
/// Invert the state of [`Self::NOT_FOCUSABLE`] with
/// respect to how this window interacts with the current method.
/// That is, if [`Self::NOT_FOCUSABLE`] is set and this flag is set,
/// then the window will behave as if it needs to interact with the
/// input method and thus be placed behind/away from it; if
/// [`Self::NOT_FOCUSABLE`] is not set and this flag is set,
/// then the window will behave as if it doesn't need to interact
/// with the input method and can be placed to use more space and
/// cover the input method.
const ALT_FOCUSABLE_IM = 0x00020000;
/// If you have set [`Self::NOT_TOUCH_MODAL`], you
/// can set this flag to receive a single special MotionEvent with
/// the action
/// `AMOTION_EVENT_ACTION_OUTSIDE` for
/// touches that occur outside of your window. Note that you will not
/// receive the full down/move/up gesture, only the location of the
/// first down as an `AMOTION_EVENT_ACTION_OUTSIDE`.
const WATCH_OUTSIDE_TOUCH = 0x00040000;
/// Special flag to let windows be shown when the screen
/// is locked. This will let application windows take precedence over
/// key guard or any other lock screens. Can be used with
/// [`Self::KEEP_SCREEN_ON`] to turn screen on and display
/// windows directly before showing the key guard window. Can be used with
/// [`Self::DISMISS_KEYGUARD`] to automatically fully
/// dismiss non-secure key guards. This flag only applies to the top-most
/// full-screen window.
const SHOW_WHEN_LOCKED = 0x00080000;
/// Ask that the system wallpaper be shown behind
/// your window. The window surface must be translucent to be able
/// to actually see the wallpaper behind it; this flag just ensures
/// that the wallpaper surface will be there if this window actually
/// has translucent regions.
const SHOW_WALLPAPER = 0x00100000;
/// When set as a window is being added or made
/// visible, once the window has been shown then the system will
/// poke the power manager's user activity (as if the user had woken
/// up the device) to turn the screen on.
const TURN_SCREEN_ON = 0x00200000;
/// When set the window will cause the key guard to
/// be dismissed, only if it is not a secure lock key guard. Because such
/// a key guard is not needed for security, it will never re-appear if
/// the user navigates to another window (in contrast to
/// [`Self::SHOW_WHEN_LOCKED`], which will only temporarily
/// hide both secure and non-secure key guards but ensure they reappear
/// when the user moves to another UI that doesn't hide them).
/// If the key guard is currently active and is secure (requires an
/// unlock pattern) then the user will still need to confirm it before
/// seeing this window, unless [`Self::SHOW_WHEN_LOCKED`] has
/// also been set.
const DISMISS_KEYGUARD = 0x00400000;
}
}
/// The top-level state and interface for a native Rust application
///
/// `AndroidApp` provides an interface to query state for the application as
/// well as monitor events, such as lifecycle and input events, that are
/// marshalled between the Java thread that owns the `Activity` and the native
/// thread that runs the `android_main()` code.
///
/// # Cheaply Clonable [`AndroidApp`]
///
/// [`AndroidApp`] is intended to be something that can be cheaply passed around
/// by referenced within an application. It is reference counted and can be
/// cheaply cloned.
///
/// # `Send` and `Sync` [`AndroidApp`]
///
/// Although an [`AndroidApp`] implements `Send` and `Sync` you do need to take
/// into consideration that some APIs, such as [`AndroidApp::poll_events()`] are
/// explicitly documented to only be usable from your `android_main()` thread.
///
#[derive(Debug, Clone)]
pub struct AndroidApp {
pub(crate) inner: Arc<RwLock<AndroidAppInner>>,
}
impl PartialEq for AndroidApp {
fn eq(&self, other: &Self) -> bool {
Arc::ptr_eq(&self.inner, &other.inner)
}
}
impl Eq for AndroidApp {}
impl Hash for AndroidApp {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
Arc::as_ptr(&self.inner).hash(state);
}
}
impl AndroidApp {
/// Queries the current [`NativeWindow`] for the application.
///
/// This will only return `Some(window)` between
/// [`MainEvent::InitWindow`] and [`MainEvent::TerminateWindow`]
/// events.
pub fn native_window(&self) -> Option<NativeWindow> {
self.inner.read().unwrap().native_window()
}
/// Returns a pointer to the Java Virtual Machine, for making JNI calls
///
/// This returns a pointer to the Java Virtual Machine which can be used
/// with the [`jni`] crate (or similar crates) to make JNI calls that bridge
/// between native Rust code and Java/Kotlin code running within the JVM.
///
/// If you use the [`jni`] crate you can wrap this as a [`JavaVM`] via:
/// ```ignore
/// # use jni::JavaVM;
/// # let app: AndroidApp = todo!();
/// let vm = unsafe { JavaVM::from_raw(app.vm_as_ptr()) };
/// ```
///
/// [`jni`]: https://crates.io/crates/jni
/// [`JavaVM`]: https://docs.rs/jni/latest/jni/struct.JavaVM.html
pub fn vm_as_ptr(&self) -> *mut c_void {
self.inner.read().unwrap().vm_as_ptr()
}
/// Returns a JNI object reference for this application's JVM `Activity` as a pointer
///
/// If you use the [`jni`] crate you can wrap this as an object reference via:
/// ```ignore
/// # use jni::objects::JObject;
/// # let app: AndroidApp = todo!();
/// let activity = unsafe { JObject::from_raw(app.activity_as_ptr()) };
/// ```
///
/// # JNI Safety
///
/// Note that the object reference will be a JNI global reference, not a
/// local reference and it should not be deleted. Don't wrap the reference
/// in an [`AutoLocal`] which would try to explicitly delete the reference
/// when dropped. Similarly, don't wrap the reference as a [`GlobalRef`]
/// which would also try to explicitly delete the reference when dropped.
///
/// [`jni`]: https://crates.io/crates/jni
/// [`AutoLocal`]: https://docs.rs/jni/latest/jni/objects/struct.AutoLocal.html
/// [`GlobalRef`]: https://docs.rs/jni/latest/jni/objects/struct.GlobalRef.html
pub fn activity_as_ptr(&self) -> *mut c_void {
self.inner.read().unwrap().activity_as_ptr()
}
/// Polls for any events associated with this [AndroidApp] and processes those events
/// (such as lifecycle events) via the given `callback`.
///
/// It's important to use this API for polling, and not call [`ALooper_pollAll`] directly since
/// some events require pre- and post-processing either side of the callback. For correct
/// behavior events should be handled immediately, before returning from the callback and
/// not simply queued for batch processing later. For example the existing [`NativeWindow`]
/// is accessible during a [`MainEvent::TerminateWindow`] callback and will be
/// set to `None` once the callback returns, and this is also synchronized with the Java
/// main thread. The [`MainEvent::SaveState`] event is also synchronized with the
/// Java main thread.
///
/// # Panics
///
/// This must only be called from your `android_main()` thread and it may panic if called
/// from another thread.
///
/// [`ALooper_pollAll`]: ndk::looper::ThreadLooper::poll_all
pub fn poll_events<F>(&self, timeout: Option<Duration>, callback: F)
where
F: FnMut(PollEvent<'_>),
{
self.inner.read().unwrap().poll_events(timeout, callback);
}
/// Creates a means to wake up the main loop while it is blocked waiting for
/// events within [`AndroidApp::poll_events()`].
pub fn create_waker(&self) -> AndroidAppWaker {
self.inner.read().unwrap().create_waker()
}
/// Returns a (cheaply clonable) reference to this application's [`ndk::configuration::Configuration`]
pub fn config(&self) -> ConfigurationRef {
self.inner.read().unwrap().config()
}
/// Queries the current content rectangle of the window; this is the area where the
/// window's content should be placed to be seen by the user.
pub fn content_rect(&self) -> Rect {
self.inner.read().unwrap().content_rect()
}
/// Queries the Asset Manager instance for the application.
///
/// Use this to access binary assets bundled inside your application's .apk file.
pub fn asset_manager(&self) -> AssetManager {
self.inner.read().unwrap().asset_manager()
}
/// Change the window flags of the given activity.
///
/// Note that some flags must be set before the window decoration is created,
/// see
/// `<https://developer.android.com/reference/android/view/Window#setFlags(int,%20int)>`.
pub fn set_window_flags(
&self,
add_flags: WindowManagerFlags,
remove_flags: WindowManagerFlags,
) {
self.inner
.write()
.unwrap()
.set_window_flags(add_flags, remove_flags);
}
/// Enable additional input axis
///
/// To reduce overhead, by default only [`input::Axis::X`] and [`input::Axis::Y`] are enabled
/// and other axis should be enabled explicitly.
pub fn enable_motion_axis(&self, axis: input::Axis) {
self.inner.write().unwrap().enable_motion_axis(axis);
}
/// Disable input axis
///
/// To reduce overhead, by default only [`input::Axis::X`] and [`input::Axis::Y`] are enabled
/// and other axis should be enabled explicitly.
pub fn disable_motion_axis(&self, axis: input::Axis) {
self.inner.write().unwrap().disable_motion_axis(axis);
}
/// Explicitly request that the current input method's soft input area be
/// shown to the user, if needed.
///
/// Call this if the user interacts with your view in such a way that they
/// have expressed they would like to start performing input into it.
pub fn show_soft_input(&self, show_implicit: bool) {
self.inner.read().unwrap().show_soft_input(show_implicit);
}
/// Request to hide the soft input window from the context of the window
/// that is currently accepting input.
///
/// This should be called as a result of the user doing some action that
/// fairly explicitly requests to have the input window hidden.
pub fn hide_soft_input(&self, hide_implicit_only: bool) {
self.inner
.read()
.unwrap()
.hide_soft_input(hide_implicit_only);
}
/// Fetch the current input text state, as updated by any active IME.
pub fn text_input_state(&self) -> input::TextInputState {
self.inner.read().unwrap().text_input_state()
}
/// Forward the given input text `state` to any active IME.
pub fn set_text_input_state(&self, state: input::TextInputState) {
self.inner.read().unwrap().set_text_input_state(state);
}
/// Get an exclusive, lending iterator over buffered input events
///
/// Applications are expected to call this in-sync with their rendering or
/// in response to a [`MainEvent::InputAvailable`] event being delivered.
///
/// _**Note:** your application is will only be delivered a single
/// [`MainEvent::InputAvailable`] event between calls to this API._
///
/// To reduce overhead, by default, only [`input::Axis::X`] and [`input::Axis::Y`] are enabled
/// and other axis should be enabled explicitly via [`Self::enable_motion_axis`].
///
/// This isn't the most ergonomic iteration API since we can't return a standard `Iterator`:
/// - This API returns a lending iterator may borrow from the internal buffer
/// of pending events without copying them.
/// - For each event we want to ensure the application reports whether the
/// event was handled.
///
/// # Example
/// Code to iterate all pending input events would look something like this:
///
/// ```rust
/// match app.input_events_iter() {
/// Ok(mut iter) => {
/// loop {
/// let read_input = iter.next(|event| {
/// let handled = match event {
/// InputEvent::KeyEvent(key_event) => {
/// // Snip
/// }
/// InputEvent::MotionEvent(motion_event) => {
/// // Snip
/// }
/// event => {
/// // Snip
/// }
/// };
///
/// handled
/// });
///
/// if !read_input {
/// break;
/// }
/// }
/// }
/// Err(err) => {
/// log::error!("Failed to get input events iterator: {err:?}");
/// }
/// }
/// ```
///
/// # Panics
///
/// This must only be called from your `android_main()` thread and it may panic if called
/// from another thread.
pub fn input_events_iter(&self) -> Result<input::InputIterator> {
let receiver = {
let guard = self.inner.read().unwrap();
guard.input_events_receiver()?
};
Ok(input::InputIterator {
inner: receiver.into(),
})
}
/// Lookup the [`KeyCharacterMap`] for the given input `device_id`
///
/// Use [`KeyCharacterMap::get`] to map key codes + meta state into unicode characters
/// or dead keys that compose with the next key.
///
/// # Example
///
/// Code to handle unicode character mapping as well as combining dead keys could look some thing like:
///
/// ```rust
/// let mut combining_accent = None;
/// // Snip
///
/// let combined_key_char = if let Ok(map) = app.device_key_character_map(device_id) {
/// match map.get(key_event.key_code(), key_event.meta_state()) {
/// Ok(KeyMapChar::Unicode(unicode)) => {
/// let combined_unicode = if let Some(accent) = combining_accent {
/// match map.get_dead_char(accent, unicode) {
/// Ok(Some(key)) => {
/// info!("KeyEvent: Combined '{unicode}' with accent '{accent}' to give '{key}'");
/// Some(key)
/// }
/// Ok(None) => None,
/// Err(err) => {
/// log::error!("KeyEvent: Failed to combine 'dead key' accent '{accent}' with '{unicode}': {err:?}");
/// None
/// }
/// }
/// } else {
/// info!("KeyEvent: Pressed '{unicode}'");
/// Some(unicode)
/// };
/// combining_accent = None;
/// combined_unicode.map(|unicode| KeyMapChar::Unicode(unicode))
/// }
/// Ok(KeyMapChar::CombiningAccent(accent)) => {
/// info!("KeyEvent: Pressed 'dead key' combining accent '{accent}'");
/// combining_accent = Some(accent);
/// Some(KeyMapChar::CombiningAccent(accent))
/// }
/// Ok(KeyMapChar::None) => {
/// info!("KeyEvent: Pressed non-unicode key");
/// combining_accent = None;
/// None
/// }
/// Err(err) => {
/// log::error!("KeyEvent: Failed to get key map character: {err:?}");
/// combining_accent = None;
/// None
/// }
/// }
/// } else {
/// None
/// };
/// ```
///
/// # Errors
///
/// Since this API needs to use JNI internally to call into the Android JVM it may return
/// a [`error::AppError::JavaError`] in case there is a spurious JNI error or an exception
/// is caught.
pub fn device_key_character_map(&self, device_id: i32) -> Result<KeyCharacterMap> {
Ok(self
.inner
.read()
.unwrap()
.device_key_character_map(device_id)?)
}
/// The user-visible SDK version of the framework
///
/// Also referred to as [`Build.VERSION_CODES`](https://developer.android.com/reference/android/os/Build.VERSION_CODES)
pub fn sdk_version() -> i32 {
let mut prop = android_properties::getprop("ro.build.version.sdk");
if let Some(val) = prop.value() {
val.parse::<i32>()
.expect("Failed to parse ro.build.version.sdk property")
} else {
panic!("Couldn't read ro.build.version.sdk system property");
}
}
/// Path to this application's internal data directory
pub fn internal_data_path(&self) -> Option<std::path::PathBuf> {
self.inner.read().unwrap().internal_data_path()
}
/// Path to this application's external data directory
pub fn external_data_path(&self) -> Option<std::path::PathBuf> {
self.inner.read().unwrap().external_data_path()
}
/// Path to the directory containing the application's OBB files (if any).
pub fn obb_path(&self) -> Option<std::path::PathBuf> {
self.inner.read().unwrap().obb_path()
}
}
#[test]
fn test_app_is_send_sync() {
fn needs_send_sync<T: Send + Sync>() {}
needs_send_sync::<AndroidApp>();
}

View File

@@ -0,0 +1,920 @@
//! This 'glue' layer acts as an IPC shim between the JVM main thread and the Rust
//! main thread. Notifying Rust of lifecycle events from the JVM and handling
//! synchronization between the two threads.
use std::{
ops::Deref,
panic::catch_unwind,
ptr::{self, NonNull},
sync::{Arc, Condvar, Mutex, Weak},
};
use ndk::{configuration::Configuration, input_queue::InputQueue, native_window::NativeWindow};
use crate::{
jni_utils::CloneJavaVM,
util::{abort_on_panic, forward_stdio_to_logcat, log_panic},
ConfigurationRef,
};
use super::{AndroidApp, Rect};
#[derive(Clone, Copy, Eq, PartialEq, Debug)]
pub enum AppCmd {
InputQueueChanged = 0,
InitWindow = 1,
TermWindow = 2,
WindowResized = 3,
WindowRedrawNeeded = 4,
ContentRectChanged = 5,
GainedFocus = 6,
LostFocus = 7,
ConfigChanged = 8,
LowMemory = 9,
Start = 10,
Resume = 11,
SaveState = 12,
Pause = 13,
Stop = 14,
Destroy = 15,
}
impl TryFrom<i8> for AppCmd {
type Error = ();
fn try_from(value: i8) -> Result<Self, Self::Error> {
match value {
0 => Ok(AppCmd::InputQueueChanged),
1 => Ok(AppCmd::InitWindow),
2 => Ok(AppCmd::TermWindow),
3 => Ok(AppCmd::WindowResized),
4 => Ok(AppCmd::WindowRedrawNeeded),
5 => Ok(AppCmd::ContentRectChanged),
6 => Ok(AppCmd::GainedFocus),
7 => Ok(AppCmd::LostFocus),
8 => Ok(AppCmd::ConfigChanged),
9 => Ok(AppCmd::LowMemory),
10 => Ok(AppCmd::Start),
11 => Ok(AppCmd::Resume),
12 => Ok(AppCmd::SaveState),
13 => Ok(AppCmd::Pause),
14 => Ok(AppCmd::Stop),
15 => Ok(AppCmd::Destroy),
_ => Err(()),
}
}
}
#[derive(Clone, Copy, Eq, PartialEq, Debug)]
pub enum State {
Init,
Start,
Resume,
Pause,
Stop,
}
#[derive(Debug)]
pub struct WaitableNativeActivityState {
pub activity: *mut ndk_sys::ANativeActivity,
pub mutex: Mutex<NativeActivityState>,
pub cond: Condvar,
}
#[derive(Debug, Clone)]
pub struct NativeActivityGlue {
pub inner: Arc<WaitableNativeActivityState>,
}
unsafe impl Send for NativeActivityGlue {}
unsafe impl Sync for NativeActivityGlue {}
impl Deref for NativeActivityGlue {
type Target = WaitableNativeActivityState;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl NativeActivityGlue {
pub fn new(
activity: *mut ndk_sys::ANativeActivity,
saved_state: *const libc::c_void,
saved_state_size: libc::size_t,
) -> Self {
let glue = Self {
inner: Arc::new(WaitableNativeActivityState::new(
activity,
saved_state,
saved_state_size,
)),
};
let weak_ref = Arc::downgrade(&glue.inner);
let weak_ptr = Weak::into_raw(weak_ref);
unsafe {
(*activity).instance = weak_ptr as *mut _;
(*(*activity).callbacks).onDestroy = Some(on_destroy);
(*(*activity).callbacks).onStart = Some(on_start);
(*(*activity).callbacks).onResume = Some(on_resume);
(*(*activity).callbacks).onSaveInstanceState = Some(on_save_instance_state);
(*(*activity).callbacks).onPause = Some(on_pause);
(*(*activity).callbacks).onStop = Some(on_stop);
(*(*activity).callbacks).onConfigurationChanged = Some(on_configuration_changed);
(*(*activity).callbacks).onLowMemory = Some(on_low_memory);
(*(*activity).callbacks).onWindowFocusChanged = Some(on_window_focus_changed);
(*(*activity).callbacks).onNativeWindowCreated = Some(on_native_window_created);
(*(*activity).callbacks).onNativeWindowResized = Some(on_native_window_resized);
(*(*activity).callbacks).onNativeWindowRedrawNeeded =
Some(on_native_window_redraw_needed);
(*(*activity).callbacks).onNativeWindowDestroyed = Some(on_native_window_destroyed);
(*(*activity).callbacks).onInputQueueCreated = Some(on_input_queue_created);
(*(*activity).callbacks).onInputQueueDestroyed = Some(on_input_queue_destroyed);
(*(*activity).callbacks).onContentRectChanged = Some(on_content_rect_changed);
}
glue
}
/// Returns the file descriptor that needs to be polled by the Rust main thread
/// for events/commands from the JVM thread
pub fn cmd_read_fd(&self) -> libc::c_int {
self.mutex.lock().unwrap().msg_read
}
/// For the Rust main thread to read a single pending command sent from the JVM main thread
pub fn read_cmd(&self) -> Option<AppCmd> {
self.inner.mutex.lock().unwrap().read_cmd()
}
/// For the Rust main thread to get an [`InputQueue`] that wraps the AInputQueue pointer
/// we have and at the same time ensure that the input queue is attached to the given looper.
///
/// NB: it's expected that the input queue is detached as soon as we know there is new
/// input (knowing the app will be notified) and only re-attached when the application
/// reads the input (to avoid lots of redundant wake ups)
pub fn looper_attached_input_queue(
&self,
looper: *mut ndk_sys::ALooper,
ident: libc::c_int,
) -> Option<InputQueue> {
let mut guard = self.mutex.lock().unwrap();
if guard.input_queue.is_null() {
return None;
}
unsafe {
// Reattach the input queue to the looper so future input will again deliver an
// `InputAvailable` event.
guard.attach_input_queue_to_looper(looper, ident);
Some(InputQueue::from_ptr(NonNull::new_unchecked(
guard.input_queue,
)))
}
}
pub fn detach_input_queue_from_looper(&self) {
unsafe {
self.inner
.mutex
.lock()
.unwrap()
.detach_input_queue_from_looper();
}
}
pub fn config(&self) -> ConfigurationRef {
self.mutex.lock().unwrap().config.clone()
}
pub fn content_rect(&self) -> Rect {
self.mutex.lock().unwrap().content_rect.into()
}
}
/// The status of the native thread that's created to run
/// `android_main`
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum NativeThreadState {
/// The `android_main` thread hasn't been created yet
Init,
/// The `android_main` thread has been spawned and started running
Running,
/// The `android_main` thread has finished
Stopped,
}
#[derive(Debug)]
pub struct NativeActivityState {
pub msg_read: libc::c_int,
pub msg_write: libc::c_int,
pub config: ConfigurationRef,
pub saved_state: Vec<u8>,
pub input_queue: *mut ndk_sys::AInputQueue,
pub window: Option<NativeWindow>,
pub content_rect: ndk_sys::ARect,
pub activity_state: State,
pub destroy_requested: bool,
pub thread_state: NativeThreadState,
pub app_has_saved_state: bool,
/// Set as soon as the Java main thread notifies us of an
/// `onDestroyed` callback.
pub destroyed: bool,
pub redraw_needed: bool,
pub pending_input_queue: *mut ndk_sys::AInputQueue,
pub pending_window: Option<NativeWindow>,
}
impl NativeActivityState {
pub fn read_cmd(&mut self) -> Option<AppCmd> {
let mut cmd_i: i8 = 0;
loop {
match unsafe { libc::read(self.msg_read, &mut cmd_i as *mut _ as *mut _, 1) } {
1 => {
let cmd = AppCmd::try_from(cmd_i);
return match cmd {
Ok(cmd) => Some(cmd),
Err(_) => {
log::error!("Spurious, unknown NativeActivityGlue cmd: {}", cmd_i);
None
}
};
}
-1 => {
let err = std::io::Error::last_os_error();
if err.kind() != std::io::ErrorKind::Interrupted {
log::error!("Failure reading NativeActivityGlue cmd: {}", err);
return None;
}
}
count => {
log::error!(
"Spurious read of {count} bytes while reading NativeActivityGlue cmd"
);
return None;
}
}
}
}
fn write_cmd(&mut self, cmd: AppCmd) {
let cmd = cmd as i8;
loop {
match unsafe { libc::write(self.msg_write, &cmd as *const _ as *const _, 1) } {
1 => break,
-1 => {
let err = std::io::Error::last_os_error();
if err.kind() != std::io::ErrorKind::Interrupted {
log::error!("Failure writing NativeActivityGlue cmd: {}", err);
return;
}
}
count => {
log::error!(
"Spurious write of {count} bytes while writing NativeActivityGlue cmd"
);
return;
}
}
}
}
pub unsafe fn attach_input_queue_to_looper(
&mut self,
looper: *mut ndk_sys::ALooper,
ident: libc::c_int,
) {
if !self.input_queue.is_null() {
log::trace!("Attaching input queue to looper");
ndk_sys::AInputQueue_attachLooper(
self.input_queue,
looper,
ident,
None,
ptr::null_mut(),
);
}
}
pub unsafe fn detach_input_queue_from_looper(&mut self) {
if !self.input_queue.is_null() {
log::trace!("Detaching input queue from looper");
ndk_sys::AInputQueue_detachLooper(self.input_queue);
}
}
}
impl Drop for WaitableNativeActivityState {
fn drop(&mut self) {
log::debug!("WaitableNativeActivityState::drop!");
unsafe {
let mut guard = self.mutex.lock().unwrap();
guard.detach_input_queue_from_looper();
}
}
}
impl WaitableNativeActivityState {
///////////////////////////////
// Java-side callback handling
///////////////////////////////
pub fn new(
activity: *mut ndk_sys::ANativeActivity,
saved_state_in: *const libc::c_void,
saved_state_size: libc::size_t,
) -> Self {
let mut msgpipe: [libc::c_int; 2] = [-1, -1];
unsafe {
if libc::pipe(msgpipe.as_mut_ptr()) != 0 {
panic!(
"could not create Rust <-> Java IPC pipe: {}",
std::io::Error::last_os_error()
);
}
}
let saved_state = if saved_state_in.is_null() {
Vec::new()
} else {
unsafe { std::slice::from_raw_parts(saved_state_in as *const u8, saved_state_size) }
.to_vec()
};
let config = unsafe {
let config = ndk_sys::AConfiguration_new();
ndk_sys::AConfiguration_fromAssetManager(config, (*activity).assetManager);
let config = super::ConfigurationRef::new(Configuration::from_ptr(
NonNull::new_unchecked(config),
));
log::trace!("Config: {:#?}", config);
config
};
Self {
activity,
mutex: Mutex::new(NativeActivityState {
msg_read: msgpipe[0],
msg_write: msgpipe[1],
config,
saved_state,
input_queue: ptr::null_mut(),
window: None,
content_rect: Rect::empty().into(),
activity_state: State::Init,
destroy_requested: false,
thread_state: NativeThreadState::Init,
app_has_saved_state: false,
destroyed: false,
redraw_needed: false,
pending_input_queue: ptr::null_mut(),
pending_window: None,
}),
cond: Condvar::new(),
}
}
pub fn notify_destroyed(&self) {
let mut guard = self.mutex.lock().unwrap();
guard.destroyed = true;
unsafe {
guard.write_cmd(AppCmd::Destroy);
while guard.thread_state != NativeThreadState::Stopped {
guard = self.cond.wait(guard).unwrap();
}
libc::close(guard.msg_read);
guard.msg_read = -1;
libc::close(guard.msg_write);
guard.msg_write = -1;
}
}
pub fn notify_config_changed(&self) {
let mut guard = self.mutex.lock().unwrap();
guard.write_cmd(AppCmd::ConfigChanged);
}
pub fn notify_low_memory(&self) {
let mut guard = self.mutex.lock().unwrap();
guard.write_cmd(AppCmd::LowMemory);
}
pub fn notify_focus_changed(&self, focused: bool) {
let mut guard = self.mutex.lock().unwrap();
guard.write_cmd(if focused {
AppCmd::GainedFocus
} else {
AppCmd::LostFocus
});
}
pub fn notify_window_resized(&self, native_window: *mut ndk_sys::ANativeWindow) {
let mut guard = self.mutex.lock().unwrap();
// set_window always syncs .pending_window back to .window before returning. This callback
// from Android can never arrive at an interim state, and validates that Android:
// 1. Only provides resizes in between onNativeWindowCreated and onNativeWindowDestroyed;
// 2. Doesn't call it on a bogus window pointer that we don't know about.
debug_assert_eq!(guard.window.as_ref().unwrap().ptr().as_ptr(), native_window);
guard.write_cmd(AppCmd::WindowResized);
}
pub fn notify_window_redraw_needed(&self, native_window: *mut ndk_sys::ANativeWindow) {
let mut guard = self.mutex.lock().unwrap();
// set_window always syncs .pending_window back to .window before returning. This callback
// from Android can never arrive at an interim state, and validates that Android:
// 1. Only provides resizes in between onNativeWindowCreated and onNativeWindowDestroyed;
// 2. Doesn't call it on a bogus window pointer that we don't know about.
debug_assert_eq!(guard.window.as_ref().unwrap().ptr().as_ptr(), native_window);
guard.write_cmd(AppCmd::WindowRedrawNeeded);
}
unsafe fn set_input(&self, input_queue: *mut ndk_sys::AInputQueue) {
let mut guard = self.mutex.lock().unwrap();
// The pending_input_queue state should only be set while in this method, and since
// it doesn't allow re-entrance and is cleared before returning then we expect
// this to be null
debug_assert!(
guard.pending_input_queue.is_null(),
"InputQueue update clash"
);
guard.pending_input_queue = input_queue;
guard.write_cmd(AppCmd::InputQueueChanged);
while guard.input_queue != guard.pending_input_queue {
guard = self.cond.wait(guard).unwrap();
}
guard.pending_input_queue = ptr::null_mut();
}
unsafe fn set_window(&self, window: Option<NativeWindow>) {
let mut guard = self.mutex.lock().unwrap();
// The pending_window state should only be set while in this method, and since
// it doesn't allow re-entrance and is cleared before returning then we expect
// this to be None
debug_assert!(guard.pending_window.is_none(), "NativeWindow update clash");
if guard.window.is_some() {
guard.write_cmd(AppCmd::TermWindow);
}
guard.pending_window = window;
if guard.pending_window.is_some() {
guard.write_cmd(AppCmd::InitWindow);
}
while guard.window != guard.pending_window {
guard = self.cond.wait(guard).unwrap();
}
guard.pending_window = None;
}
unsafe fn set_content_rect(&self, rect: *const ndk_sys::ARect) {
let mut guard = self.mutex.lock().unwrap();
guard.content_rect = *rect;
guard.write_cmd(AppCmd::ContentRectChanged);
}
unsafe fn set_activity_state(&self, state: State) {
let mut guard = self.mutex.lock().unwrap();
let cmd = match state {
State::Init => panic!("Can't explicitly transition into 'init' state"),
State::Start => AppCmd::Start,
State::Resume => AppCmd::Resume,
State::Pause => AppCmd::Pause,
State::Stop => AppCmd::Stop,
};
guard.write_cmd(cmd);
while guard.activity_state != state {
guard = self.cond.wait(guard).unwrap();
}
}
fn request_save_state(&self) -> (*mut libc::c_void, libc::size_t) {
let mut guard = self.mutex.lock().unwrap();
// The state_saved flag should only be set while in this method, and since
// it doesn't allow re-entrance and is cleared before returning then we expect
// this to be None
debug_assert!(!guard.app_has_saved_state, "SaveState request clash");
guard.write_cmd(AppCmd::SaveState);
while !guard.app_has_saved_state {
guard = self.cond.wait(guard).unwrap();
}
guard.app_has_saved_state = false;
// `ANativeActivity` explicitly documents that it expects save state to be
// given via a `malloc()` allocated pointer since it will automatically
// `free()` the state after it has been converted to a buffer for the JVM.
if !guard.saved_state.is_empty() {
let saved_state_size = guard.saved_state.len();
let saved_state_src_ptr = guard.saved_state.as_ptr();
unsafe {
let saved_state = libc::malloc(saved_state_size);
assert!(
!saved_state.is_null(),
"Failed to allocate {} bytes for restoring saved application state",
saved_state_size
);
libc::memcpy(saved_state, saved_state_src_ptr as _, saved_state_size);
(saved_state, saved_state_size)
}
} else {
(ptr::null_mut(), 0)
}
}
pub fn saved_state(&self) -> Option<Vec<u8>> {
let guard = self.mutex.lock().unwrap();
if !guard.saved_state.is_empty() {
Some(guard.saved_state.clone())
} else {
None
}
}
pub fn set_saved_state(&self, state: &[u8]) {
let mut guard = self.mutex.lock().unwrap();
guard.saved_state.clear();
guard.saved_state.extend_from_slice(state);
}
////////////////////////////
// Rust-side event loop
////////////////////////////
pub fn notify_main_thread_running(&self) {
let mut guard = self.mutex.lock().unwrap();
guard.thread_state = NativeThreadState::Running;
self.cond.notify_one();
}
pub fn notify_main_thread_stopped_running(&self) {
let mut guard = self.mutex.lock().unwrap();
guard.thread_state = NativeThreadState::Stopped;
self.cond.notify_one();
}
pub unsafe fn pre_exec_cmd(
&self,
cmd: AppCmd,
looper: *mut ndk_sys::ALooper,
input_queue_ident: libc::c_int,
) {
log::trace!("Pre: AppCmd::{:#?}", cmd);
match cmd {
AppCmd::InputQueueChanged => {
let mut guard = self.mutex.lock().unwrap();
guard.detach_input_queue_from_looper();
guard.input_queue = guard.pending_input_queue;
if !guard.input_queue.is_null() {
guard.attach_input_queue_to_looper(looper, input_queue_ident);
}
self.cond.notify_one();
}
AppCmd::InitWindow => {
let mut guard = self.mutex.lock().unwrap();
guard.window = guard.pending_window.clone();
self.cond.notify_one();
}
AppCmd::Resume | AppCmd::Start | AppCmd::Pause | AppCmd::Stop => {
let mut guard = self.mutex.lock().unwrap();
guard.activity_state = match cmd {
AppCmd::Start => State::Start,
AppCmd::Pause => State::Pause,
AppCmd::Resume => State::Resume,
AppCmd::Stop => State::Stop,
_ => unreachable!(),
};
self.cond.notify_one();
}
AppCmd::ConfigChanged => {
let guard = self.mutex.lock().unwrap();
let config = ndk_sys::AConfiguration_new();
ndk_sys::AConfiguration_fromAssetManager(config, (*self.activity).assetManager);
let config = Configuration::from_ptr(NonNull::new_unchecked(config));
guard.config.replace(config);
log::debug!("Config: {:#?}", guard.config);
}
AppCmd::Destroy => {
let mut guard = self.mutex.lock().unwrap();
guard.destroy_requested = true;
}
_ => {}
}
}
pub unsafe fn post_exec_cmd(&self, cmd: AppCmd) {
log::trace!("Post: AppCmd::{:#?}", cmd);
match cmd {
AppCmd::TermWindow => {
let mut guard = self.mutex.lock().unwrap();
guard.window = None;
self.cond.notify_one();
}
AppCmd::SaveState => {
let mut guard = self.mutex.lock().unwrap();
guard.app_has_saved_state = true;
self.cond.notify_one();
}
_ => {}
}
}
}
extern "Rust" {
pub fn android_main(app: AndroidApp);
}
unsafe fn try_with_waitable_activity_ref(
activity: *mut ndk_sys::ANativeActivity,
closure: impl FnOnce(Arc<WaitableNativeActivityState>),
) {
assert!(!(*activity).instance.is_null());
let weak_ptr: *const WaitableNativeActivityState = (*activity).instance.cast();
let weak_ref = Weak::from_raw(weak_ptr);
if let Some(waitable_activity) = weak_ref.upgrade() {
closure(waitable_activity);
} else {
log::error!("Ignoring spurious JVM callback after last activity reference was dropped!")
}
let _ = weak_ref.into_raw();
}
unsafe extern "C" fn on_destroy(activity: *mut ndk_sys::ANativeActivity) {
abort_on_panic(|| {
log::debug!("Destroy: {:p}\n", activity);
try_with_waitable_activity_ref(activity, |waitable_activity| {
waitable_activity.notify_destroyed()
});
})
}
unsafe extern "C" fn on_start(activity: *mut ndk_sys::ANativeActivity) {
abort_on_panic(|| {
log::debug!("Start: {:p}\n", activity);
try_with_waitable_activity_ref(activity, |waitable_activity| {
waitable_activity.set_activity_state(State::Start);
});
})
}
unsafe extern "C" fn on_resume(activity: *mut ndk_sys::ANativeActivity) {
abort_on_panic(|| {
log::debug!("Resume: {:p}\n", activity);
try_with_waitable_activity_ref(activity, |waitable_activity| {
waitable_activity.set_activity_state(State::Resume);
});
})
}
unsafe extern "C" fn on_save_instance_state(
activity: *mut ndk_sys::ANativeActivity,
out_len: *mut usize,
) -> *mut libc::c_void {
abort_on_panic(|| {
log::debug!("SaveInstanceState: {:p}\n", activity);
*out_len = 0;
let mut ret = ptr::null_mut();
try_with_waitable_activity_ref(activity, |waitable_activity| {
let (state, len) = waitable_activity.request_save_state();
*out_len = len;
ret = state
});
log::debug!("Saved state = {:p}, len = {}", ret, *out_len);
ret
})
}
unsafe extern "C" fn on_pause(activity: *mut ndk_sys::ANativeActivity) {
abort_on_panic(|| {
log::debug!("Pause: {:p}\n", activity);
try_with_waitable_activity_ref(activity, |waitable_activity| {
waitable_activity.set_activity_state(State::Pause);
});
})
}
unsafe extern "C" fn on_stop(activity: *mut ndk_sys::ANativeActivity) {
abort_on_panic(|| {
log::debug!("Stop: {:p}\n", activity);
try_with_waitable_activity_ref(activity, |waitable_activity| {
waitable_activity.set_activity_state(State::Stop);
});
})
}
unsafe extern "C" fn on_configuration_changed(activity: *mut ndk_sys::ANativeActivity) {
abort_on_panic(|| {
log::debug!("ConfigurationChanged: {:p}\n", activity);
try_with_waitable_activity_ref(activity, |waitable_activity| {
waitable_activity.notify_config_changed();
});
})
}
unsafe extern "C" fn on_low_memory(activity: *mut ndk_sys::ANativeActivity) {
abort_on_panic(|| {
log::debug!("LowMemory: {:p}\n", activity);
try_with_waitable_activity_ref(activity, |waitable_activity| {
waitable_activity.notify_low_memory();
});
})
}
unsafe extern "C" fn on_window_focus_changed(
activity: *mut ndk_sys::ANativeActivity,
focused: libc::c_int,
) {
abort_on_panic(|| {
log::debug!("WindowFocusChanged: {:p} -- {}\n", activity, focused);
try_with_waitable_activity_ref(activity, |waitable_activity| {
waitable_activity.notify_focus_changed(focused != 0);
});
})
}
unsafe extern "C" fn on_native_window_created(
activity: *mut ndk_sys::ANativeActivity,
window: *mut ndk_sys::ANativeWindow,
) {
abort_on_panic(|| {
log::debug!("NativeWindowCreated: {:p} -- {:p}\n", activity, window);
try_with_waitable_activity_ref(activity, |waitable_activity| {
// Use clone_from_ptr to acquire additional ownership on the NativeWindow,
// which will unconditionally be _release()'d on Drop.
let window = NativeWindow::clone_from_ptr(NonNull::new_unchecked(window));
waitable_activity.set_window(Some(window));
});
})
}
unsafe extern "C" fn on_native_window_resized(
activity: *mut ndk_sys::ANativeActivity,
window: *mut ndk_sys::ANativeWindow,
) {
log::debug!("NativeWindowResized: {:p} -- {:p}\n", activity, window);
try_with_waitable_activity_ref(activity, |waitable_activity| {
waitable_activity.notify_window_resized(window);
});
}
unsafe extern "C" fn on_native_window_redraw_needed(
activity: *mut ndk_sys::ANativeActivity,
window: *mut ndk_sys::ANativeWindow,
) {
log::debug!("NativeWindowRedrawNeeded: {:p} -- {:p}\n", activity, window);
try_with_waitable_activity_ref(activity, |waitable_activity| {
waitable_activity.notify_window_redraw_needed(window)
});
}
unsafe extern "C" fn on_native_window_destroyed(
activity: *mut ndk_sys::ANativeActivity,
window: *mut ndk_sys::ANativeWindow,
) {
abort_on_panic(|| {
log::debug!("NativeWindowDestroyed: {:p} -- {:p}\n", activity, window);
try_with_waitable_activity_ref(activity, |waitable_activity| {
waitable_activity.set_window(None);
});
})
}
unsafe extern "C" fn on_input_queue_created(
activity: *mut ndk_sys::ANativeActivity,
queue: *mut ndk_sys::AInputQueue,
) {
abort_on_panic(|| {
log::debug!("InputQueueCreated: {:p} -- {:p}\n", activity, queue);
try_with_waitable_activity_ref(activity, |waitable_activity| {
waitable_activity.set_input(queue);
});
})
}
unsafe extern "C" fn on_input_queue_destroyed(
activity: *mut ndk_sys::ANativeActivity,
queue: *mut ndk_sys::AInputQueue,
) {
abort_on_panic(|| {
log::debug!("InputQueueDestroyed: {:p} -- {:p}\n", activity, queue);
try_with_waitable_activity_ref(activity, |waitable_activity| {
waitable_activity.set_input(ptr::null_mut());
});
})
}
unsafe extern "C" fn on_content_rect_changed(
activity: *mut ndk_sys::ANativeActivity,
rect: *const ndk_sys::ARect,
) {
log::debug!("ContentRectChanged: {:p} -- {:p}\n", activity, rect);
try_with_waitable_activity_ref(activity, |waitable_activity| {
waitable_activity.set_content_rect(rect)
});
}
/// This is the native entrypoint for our cdylib library that `ANativeActivity` will look for via `dlsym`
#[no_mangle]
extern "C" fn ANativeActivity_onCreate(
activity: *mut ndk_sys::ANativeActivity,
saved_state: *const libc::c_void,
saved_state_size: libc::size_t,
) {
abort_on_panic(|| {
let _join_log_forwarder = forward_stdio_to_logcat();
log::trace!(
"Creating: {:p}, saved_state = {:p}, save_state_size = {}",
activity,
saved_state,
saved_state_size
);
// Conceptually we associate a glue reference with the JVM main thread, and another
// reference with the Rust main thread
let jvm_glue = NativeActivityGlue::new(activity, saved_state, saved_state_size);
let rust_glue = jvm_glue.clone();
// Let us Send the NativeActivity pointer to the Rust main() thread without a wrapper type
let activity_ptr: libc::intptr_t = activity as _;
// Note: we drop the thread handle which will detach the thread
std::thread::spawn(move || {
let activity: *mut ndk_sys::ANativeActivity = activity_ptr as *mut _;
let jvm = abort_on_panic(|| unsafe {
let na = activity;
let jvm: *mut jni_sys::JavaVM = (*na).vm;
let activity = (*na).clazz; // Completely bogus name; this is the _instance_ not class pointer
ndk_context::initialize_android_context(jvm.cast(), activity.cast());
let jvm = CloneJavaVM::from_raw(jvm).unwrap();
// Since this is a newly spawned thread then the JVM hasn't been attached
// to the thread yet. Attach before calling the applications main function
// so they can safely make JNI calls
jvm.attach_current_thread_permanently().unwrap();
jvm
});
let app = AndroidApp::new(rust_glue.clone(), jvm.clone());
rust_glue.notify_main_thread_running();
unsafe {
// Name thread - this needs to happen here after attaching to a JVM thread,
// since that changes the thread name to something like "Thread-2".
let thread_name = std::ffi::CStr::from_bytes_with_nul(b"android_main\0").unwrap();
libc::pthread_setname_np(libc::pthread_self(), thread_name.as_ptr());
// We want to specifically catch any panic from the application's android_main
// so we can finish + destroy the Activity gracefully via the JVM
catch_unwind(|| {
// XXX: If we were in control of the Java Activity subclass then
// we could potentially run the android_main function via a Java native method
// springboard (e.g. call an Activity subclass method that calls a jni native
// method that then just calls android_main()) that would make sure there was
// a Java frame at the base of our call stack which would then be recognised
// when calling FindClass to lookup a suitable classLoader, instead of
// defaulting to the system loader. Without this then it's difficult for native
// code to look up non-standard Java classes.
android_main(app);
})
.unwrap_or_else(log_panic);
// Let JVM know that our Activity can be destroyed before detaching from the JVM
//
// "Note that this method can be called from any thread; it will send a message
// to the main thread of the process where the Java finish call will take place"
ndk_sys::ANativeActivity_finish(activity);
// This should detach automatically but lets detach explicitly to avoid depending
// on the TLS trickery in `jni-rs`
jvm.detach_current_thread();
ndk_context::release_android_context();
}
rust_glue.notify_main_thread_stopped_running();
});
// Wait for thread to start.
let mut guard = jvm_glue.mutex.lock().unwrap();
// Don't specifically wait for `Running` just in case `android_main` returns
// immediately and the state is set to `Stopped`
while guard.thread_state == NativeThreadState::Init {
guard = jvm_glue.cond.wait(guard).unwrap();
}
})
}

View File

@@ -0,0 +1,437 @@
use std::marker::PhantomData;
use crate::input::{
Axis, Button, ButtonState, EdgeFlags, KeyAction, Keycode, MetaState, MotionAction,
MotionEventFlags, Pointer, PointersIter, Source, ToolType,
};
/// A motion event
///
/// For general discussion of motion events in Android, see [the relevant
/// javadoc](https://developer.android.com/reference/android/view/MotionEvent).
#[derive(Debug)]
#[repr(transparent)]
pub struct MotionEvent<'a> {
ndk_event: ndk::event::MotionEvent,
_lifetime: PhantomData<&'a ndk::event::MotionEvent>,
}
impl<'a> MotionEvent<'a> {
pub(crate) fn new(ndk_event: ndk::event::MotionEvent) -> Self {
Self {
ndk_event,
_lifetime: PhantomData,
}
}
pub(crate) fn into_ndk_event(self) -> ndk::event::MotionEvent {
self.ndk_event
}
/// Get the source of the event.
///
#[inline]
pub fn source(&self) -> Source {
// XXX: we use `AInputEvent_getSource` directly (instead of calling
// ndk_event.source()) since we have our own `Source` enum that we
// share between backends, which may also capture unknown variants
// added in new versions of Android.
let source =
unsafe { ndk_sys::AInputEvent_getSource(self.ndk_event.ptr().as_ptr()) as u32 };
source.into()
}
/// Get the device id associated with the event.
///
#[inline]
pub fn device_id(&self) -> i32 {
self.ndk_event.device_id()
}
/// Returns the motion action associated with the event.
///
/// See [the MotionEvent docs](https://developer.android.com/reference/android/view/MotionEvent#getActionMasked())
#[inline]
pub fn action(&self) -> MotionAction {
// XXX: we use `AMotionEvent_getAction` directly since we have our own
// `MotionAction` enum that we share between backends, which may also
// capture unknown variants added in new versions of Android.
let action =
unsafe { ndk_sys::AMotionEvent_getAction(self.ndk_event.ptr().as_ptr()) as u32 }
& ndk_sys::AMOTION_EVENT_ACTION_MASK;
action.into()
}
/// Returns which button has been modified during a press or release action.
///
/// For actions other than [`MotionAction::ButtonPress`] and
/// [`MotionAction::ButtonRelease`] the returned value is undefined.
///
/// See [the MotionEvent docs](https://developer.android.com/reference/android/view/MotionEvent#getActionButton())
#[inline]
pub fn action_button(&self) -> Button {
let action_button =
unsafe { ndk_sys::AMotionEvent_getActionButton(self.ndk_event.ptr().as_ptr()) as u32 };
action_button.into()
}
/// Returns the pointer index of an `Up` or `Down` event.
///
/// Pointer indices can change per motion event. For an identifier that stays the same, see
/// [`Pointer::pointer_id()`].
///
/// This only has a meaning when the [action](Self::action) is one of [`Up`](MotionAction::Up),
/// [`Down`](MotionAction::Down), [`PointerUp`](MotionAction::PointerUp),
/// or [`PointerDown`](MotionAction::PointerDown).
#[inline]
pub fn pointer_index(&self) -> usize {
self.ndk_event.pointer_index()
}
/*
/// Returns the pointer id associated with the given pointer index.
///
/// See [the NDK
/// docs](https://developer.android.com/ndk/reference/group/input#amotionevent_getpointerid)
// TODO: look at output with out-of-range pointer index
// Probably -1 though
pub fn pointer_id_for(&self, pointer_index: usize) -> i32 {
unsafe { ndk_sys::AMotionEvent_getPointerId(self.ndk_event.ptr.as_ptr(), pointer_index) }
}
*/
/// Returns the number of pointers in this event
///
/// See [the MotionEvent docs](https://developer.android.com/reference/android/view/MotionEvent#getPointerCount())
#[inline]
pub fn pointer_count(&self) -> usize {
self.ndk_event.pointer_count()
}
/// An iterator over the pointers in this motion event
#[inline]
pub fn pointers(&self) -> PointersIter<'_> {
PointersIter {
inner: PointersIterImpl {
ndk_pointers_iter: self.ndk_event.pointers(),
},
}
}
/// The pointer at a given pointer index. Panics if the pointer index is out of bounds.
///
/// If you need to loop over all the pointers, prefer the [`pointers()`](Self::pointers) method.
#[inline]
pub fn pointer_at_index(&self, index: usize) -> Pointer<'_> {
Pointer {
inner: PointerImpl {
ndk_pointer: self.ndk_event.pointer_at_index(index),
},
}
}
/*
XXX: Not currently supported with GameActivity so we don't currently expose for NativeActivity
either, for consistency.
/// Returns the size of the history contained in this event.
///
/// See [the NDK
/// docs](https://developer.android.com/ndk/reference/group/input#amotionevent_gethistorysize)
#[inline]
pub fn history_size(&self) -> usize {
self.ndk_event.history_size()
}
/// An iterator over the historical events contained in this event.
#[inline]
pub fn history(&self) -> HistoricalMotionEventsIter<'_> {
self.ndk_event.history()
}
*/
/// Returns the state of any modifier keys that were pressed during the event.
///
/// See [the NDK
/// docs](https://developer.android.com/ndk/reference/group/input#amotionevent_getmetastate)
#[inline]
pub fn meta_state(&self) -> MetaState {
self.ndk_event.meta_state().into()
}
/// Returns the button state during this event, as a bitfield.
///
/// See [the NDK
/// docs](https://developer.android.com/ndk/reference/group/input#amotionevent_getbuttonstate)
#[inline]
pub fn button_state(&self) -> ButtonState {
self.ndk_event.button_state().into()
}
/// Returns the time of the start of this gesture, in the `java.lang.System.nanoTime()` time
/// base
///
/// See [the NDK
/// docs](https://developer.android.com/ndk/reference/group/input#amotionevent_getdowntime)
#[inline]
pub fn down_time(&self) -> i64 {
self.ndk_event.down_time()
}
/// Returns a bitfield indicating which edges were touched by this event.
///
/// See [the NDK
/// docs](https://developer.android.com/ndk/reference/group/input#amotionevent_getedgeflags)
#[inline]
pub fn edge_flags(&self) -> EdgeFlags {
self.ndk_event.edge_flags().into()
}
/// Returns the time of this event, in the `java.lang.System.nanoTime()` time base
///
/// See [the NDK
/// docs](https://developer.android.com/ndk/reference/group/input#amotionevent_geteventtime)
#[inline]
pub fn event_time(&self) -> i64 {
self.ndk_event.event_time()
}
/// The flags associated with a motion event.
///
/// See [the NDK
/// docs](https://developer.android.com/ndk/reference/group/input#amotionevent_getflags)
#[inline]
pub fn flags(&self) -> MotionEventFlags {
self.ndk_event.flags().into()
}
/* Missing from GameActivity currently...
/// Returns the offset in the x direction between the coordinates and the raw coordinates
///
/// See [the NDK
/// docs](https://developer.android.com/ndk/reference/group/input#amotionevent_getxoffset)
#[inline]
pub fn x_offset(&self) -> f32 {
self.ndk_event.x_offset()
}
/// Returns the offset in the y direction between the coordinates and the raw coordinates
///
/// See [the NDK
/// docs](https://developer.android.com/ndk/reference/group/input#amotionevent_getyoffset)
#[inline]
pub fn y_offset(&self) -> f32 {
self.ndk_event.y_offset()
}
*/
/// Returns the precision of the x value of the coordinates
///
/// See [the NDK
/// docs](https://developer.android.com/ndk/reference/group/input#amotionevent_getxprecision)
#[inline]
pub fn x_precision(&self) -> f32 {
self.ndk_event.x_precision()
}
/// Returns the precision of the y value of the coordinates
///
/// See [the NDK
/// docs](https://developer.android.com/ndk/reference/group/input#amotionevent_getyprecision)
#[inline]
pub fn y_precision(&self) -> f32 {
self.ndk_event.y_precision()
}
}
/// A view into the data of a specific pointer in a motion event.
#[derive(Debug)]
pub(crate) struct PointerImpl<'a> {
ndk_pointer: ndk::event::Pointer<'a>,
}
impl<'a> PointerImpl<'a> {
#[inline]
pub fn pointer_index(&self) -> usize {
self.ndk_pointer.pointer_index()
}
#[inline]
pub fn pointer_id(&self) -> i32 {
self.ndk_pointer.pointer_id()
}
#[inline]
pub fn axis_value(&self, axis: Axis) -> f32 {
let value: u32 = axis.into();
let value = value as i32;
self.ndk_pointer.axis_value(value.into())
}
#[inline]
pub fn raw_x(&self) -> f32 {
self.ndk_pointer.raw_x()
}
#[inline]
pub fn raw_y(&self) -> f32 {
self.ndk_pointer.raw_y()
}
#[inline]
pub fn tool_type(&self) -> ToolType {
let value: i32 = self.ndk_pointer.tool_type().into();
let value = value as u32;
value.into()
}
}
/// An iterator over the pointers in a [`MotionEvent`].
#[derive(Debug)]
pub(crate) struct PointersIterImpl<'a> {
ndk_pointers_iter: ndk::event::PointersIter<'a>,
}
impl<'a> Iterator for PointersIterImpl<'a> {
type Item = Pointer<'a>;
fn next(&mut self) -> Option<Pointer<'a>> {
self.ndk_pointers_iter.next().map(|ndk_pointer| Pointer {
inner: PointerImpl { ndk_pointer },
})
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.ndk_pointers_iter.size_hint()
}
}
impl<'a> ExactSizeIterator for PointersIterImpl<'a> {
fn len(&self) -> usize {
self.ndk_pointers_iter.len()
}
}
/// A key event
///
/// For general discussion of key events in Android, see [the relevant
/// javadoc](https://developer.android.com/reference/android/view/KeyEvent).
#[derive(Debug)]
#[repr(transparent)]
pub struct KeyEvent<'a> {
ndk_event: ndk::event::KeyEvent,
_lifetime: PhantomData<&'a ndk::event::KeyEvent>,
}
impl<'a> KeyEvent<'a> {
pub(crate) fn new(ndk_event: ndk::event::KeyEvent) -> Self {
Self {
ndk_event,
_lifetime: PhantomData,
}
}
pub(crate) fn into_ndk_event(self) -> ndk::event::KeyEvent {
self.ndk_event
}
/// Get the source of the event.
///
#[inline]
pub fn source(&self) -> Source {
// XXX: we use `AInputEvent_getSource` directly (instead of calling
// ndk_event.source()) since we have our own `Source` enum that we
// share between backends, which may also capture unknown variants
// added in new versions of Android.
let source =
unsafe { ndk_sys::AInputEvent_getSource(self.ndk_event.ptr().as_ptr()) as u32 };
source.into()
}
/// Get the device id associated with the event.
///
#[inline]
pub fn device_id(&self) -> i32 {
self.ndk_event.device_id()
}
/// Returns the key action associated with the event.
///
/// See [the KeyEvent docs](https://developer.android.com/reference/android/view/KeyEvent#getAction())
#[inline]
pub fn action(&self) -> KeyAction {
// XXX: we use `AInputEvent_getAction` directly since we have our own
// `KeyAction` enum that we share between backends, which may also
// capture unknown variants added in new versions of Android.
let action = unsafe { ndk_sys::AKeyEvent_getAction(self.ndk_event.ptr().as_ptr()) as u32 };
action.into()
}
/// Returns the last time the key was pressed. This is on the scale of
/// `java.lang.System.nanoTime()`, which has nanosecond precision, but no defined start time.
///
/// See [the NDK
/// docs](https://developer.android.com/ndk/reference/group/input#akeyevent_getdowntime)
#[inline]
pub fn down_time(&self) -> i64 {
self.ndk_event.down_time()
}
/// Returns the time this event occured. This is on the scale of
/// `java.lang.System.nanoTime()`, which has nanosecond precision, but no defined start time.
///
/// See [the NDK
/// docs](https://developer.android.com/ndk/reference/group/input#akeyevent_geteventtime)
#[inline]
pub fn event_time(&self) -> i64 {
self.ndk_event.event_time()
}
/// Returns the keycode associated with this key event
///
/// See [the NDK
/// docs](https://developer.android.com/ndk/reference/group/input#akeyevent_getkeycode)
#[inline]
pub fn key_code(&self) -> Keycode {
// XXX: we use `AInputEvent_getKeyCode` directly since we have our own
// `Keycode` enum that we share between backends, which may also
// capture unknown variants added in new versions of Android.
let keycode =
unsafe { ndk_sys::AKeyEvent_getKeyCode(self.ndk_event.ptr().as_ptr()) as u32 };
keycode.into()
}
/// Returns the number of repeats of a key.
///
/// See [the NDK
/// docs](https://developer.android.com/ndk/reference/group/input#akeyevent_getrepeatcount)
#[inline]
pub fn repeat_count(&self) -> i32 {
self.ndk_event.repeat_count()
}
/// Returns the hardware keycode of a key. This varies from device to device.
///
/// See [the NDK
/// docs](https://developer.android.com/ndk/reference/group/input#akeyevent_getscancode)
#[inline]
pub fn scan_code(&self) -> i32 {
self.ndk_event.scan_code()
}
/// Returns the state of the modifiers during this key event, represented by a bitmask.
///
/// See [the NDK
/// docs](https://developer.android.com/ndk/reference/group/input#akeyevent_getmetastate)
#[inline]
pub fn meta_state(&self) -> MetaState {
self.ndk_event.meta_state().into()
}
}
// We use our own wrapper type for input events to have better consistency
// with GameActivity and ensure the enum can be extended without needing a
// semver bump
/// Enum of possible input events
#[derive(Debug)]
#[non_exhaustive]
pub enum InputEvent<'a> {
MotionEvent(self::MotionEvent<'a>),
KeyEvent(self::KeyEvent<'a>),
TextEvent(crate::input::TextInputState),
}

View File

@@ -0,0 +1,546 @@
#![cfg(any(feature = "native-activity", doc))]
use std::collections::HashMap;
use std::marker::PhantomData;
use std::panic::AssertUnwindSafe;
use std::ptr;
use std::ptr::NonNull;
use std::sync::{Arc, Mutex, RwLock, Weak};
use std::time::Duration;
use libc::c_void;
use log::{error, trace};
use ndk::input_queue::InputQueue;
use ndk::{asset::AssetManager, native_window::NativeWindow};
use crate::error::InternalResult;
use crate::input::{Axis, KeyCharacterMap, KeyCharacterMapBinding};
use crate::input::{TextInputState, TextSpan};
use crate::jni_utils::{self, CloneJavaVM};
use crate::{
util, AndroidApp, ConfigurationRef, InputStatus, MainEvent, PollEvent, Rect, WindowManagerFlags,
};
pub mod input;
mod glue;
use self::glue::NativeActivityGlue;
pub const LOOPER_ID_MAIN: libc::c_int = 1;
pub const LOOPER_ID_INPUT: libc::c_int = 2;
//pub const LOOPER_ID_USER: ::std::os::raw::c_uint = 3;
/// An interface for saving application state during [MainEvent::SaveState] events
///
/// This interface is only available temporarily while handling a [MainEvent::SaveState] event.
#[derive(Debug)]
pub struct StateSaver<'a> {
app: &'a AndroidAppInner,
}
impl<'a> StateSaver<'a> {
/// Stores the given `state` such that it will be available to load the next
/// time that the application resumes.
pub fn store(&self, state: &'a [u8]) {
self.app.native_activity.set_saved_state(state);
}
}
/// An interface for loading application state during [MainEvent::Resume] events
///
/// This interface is only available temporarily while handling a [MainEvent::Resume] event.
#[derive(Debug)]
pub struct StateLoader<'a> {
app: &'a AndroidAppInner,
}
impl<'a> StateLoader<'a> {
/// Returns whatever state was saved during the last [MainEvent::SaveState] event or `None`
pub fn load(&self) -> Option<Vec<u8>> {
self.app.native_activity.saved_state()
}
}
/// A means to wake up the main thread while it is blocked waiting for I/O
#[derive(Clone)]
pub struct AndroidAppWaker {
// The looper pointer is owned by the android_app and effectively
// has a 'static lifetime, and the ALooper_wake C API is thread
// safe, so this can be cloned safely and is send + sync safe
looper: NonNull<ndk_sys::ALooper>,
}
unsafe impl Send for AndroidAppWaker {}
unsafe impl Sync for AndroidAppWaker {}
impl AndroidAppWaker {
/// Interrupts the main thread if it is blocked within [`AndroidApp::poll_events()`]
///
/// If [`AndroidApp::poll_events()`] is interrupted it will invoke the poll
/// callback with a [PollEvent::Wake][wake_event] event.
///
/// [wake_event]: crate::PollEvent::Wake
pub fn wake(&self) {
unsafe {
ndk_sys::ALooper_wake(self.looper.as_ptr());
}
}
}
impl AndroidApp {
pub(crate) fn new(native_activity: NativeActivityGlue, jvm: CloneJavaVM) -> Self {
let mut env = jvm.get_env().unwrap(); // We attach to the thread before creating the AndroidApp
let key_map_binding = match KeyCharacterMapBinding::new(&mut env) {
Ok(b) => b,
Err(err) => {
panic!("Failed to create KeyCharacterMap JNI bindings: {err:?}");
}
};
let app = Self {
inner: Arc::new(RwLock::new(AndroidAppInner {
jvm,
native_activity,
looper: Looper {
ptr: ptr::null_mut(),
},
key_map_binding: Arc::new(key_map_binding),
key_maps: Mutex::new(HashMap::new()),
input_receiver: Mutex::new(None),
})),
};
{
let mut guard = app.inner.write().unwrap();
let main_fd = guard.native_activity.cmd_read_fd();
unsafe {
guard.looper.ptr = ndk_sys::ALooper_prepare(
ndk_sys::ALOOPER_PREPARE_ALLOW_NON_CALLBACKS as libc::c_int,
);
ndk_sys::ALooper_addFd(
guard.looper.ptr,
main_fd,
LOOPER_ID_MAIN,
ndk_sys::ALOOPER_EVENT_INPUT as libc::c_int,
None,
//&mut guard.cmd_poll_source as *mut _ as *mut _);
ptr::null_mut(),
);
}
}
app
}
}
#[derive(Debug)]
struct Looper {
pub ptr: *mut ndk_sys::ALooper,
}
unsafe impl Send for Looper {}
unsafe impl Sync for Looper {}
#[derive(Debug)]
pub(crate) struct AndroidAppInner {
pub(crate) jvm: CloneJavaVM,
pub(crate) native_activity: NativeActivityGlue,
looper: Looper,
/// Shared JNI bindings for the `KeyCharacterMap` class
key_map_binding: Arc<KeyCharacterMapBinding>,
/// A table of `KeyCharacterMap`s per `InputDevice` ID
/// these are used to be able to map key presses to unicode
/// characters
key_maps: Mutex<HashMap<i32, KeyCharacterMap>>,
/// While an app is reading input events it holds an
/// InputReceiver reference which we track to ensure
/// we don't hand out more than one receiver at a time
input_receiver: Mutex<Option<Weak<InputReceiver>>>,
}
impl AndroidAppInner {
pub(crate) fn vm_as_ptr(&self) -> *mut c_void {
unsafe { (*self.native_activity.activity).vm as _ }
}
pub(crate) fn activity_as_ptr(&self) -> *mut c_void {
// "clazz" is a completely bogus name; this is the _instance_ not class pointer
unsafe { (*self.native_activity.activity).clazz as _ }
}
pub(crate) fn native_activity(&self) -> *const ndk_sys::ANativeActivity {
self.native_activity.activity
}
pub(crate) fn looper(&self) -> *mut ndk_sys::ALooper {
self.looper.ptr
}
pub fn native_window(&self) -> Option<NativeWindow> {
self.native_activity.mutex.lock().unwrap().window.clone()
}
pub fn poll_events<F>(&self, timeout: Option<Duration>, mut callback: F)
where
F: FnMut(PollEvent<'_>),
{
trace!("poll_events");
unsafe {
let mut fd: i32 = 0;
let mut events: i32 = 0;
let mut source: *mut c_void = ptr::null_mut();
let timeout_milliseconds = if let Some(timeout) = timeout {
timeout.as_millis() as i32
} else {
-1
};
trace!("Calling ALooper_pollAll, timeout = {timeout_milliseconds}");
assert!(
!ndk_sys::ALooper_forThread().is_null(),
"Application tried to poll events from non-main thread"
);
let id = ndk_sys::ALooper_pollAll(
timeout_milliseconds,
&mut fd,
&mut events,
&mut source as *mut *mut c_void,
);
trace!("pollAll id = {id}");
match id {
ndk_sys::ALOOPER_POLL_WAKE => {
trace!("ALooper_pollAll returned POLL_WAKE");
callback(PollEvent::Wake);
}
ndk_sys::ALOOPER_POLL_CALLBACK => {
// ALooper_pollAll is documented to handle all callback sources internally so it should
// never return a _CALLBACK source id...
error!("Spurious ALOOPER_POLL_CALLBACK from ALopper_pollAll() (ignored)");
}
ndk_sys::ALOOPER_POLL_TIMEOUT => {
trace!("ALooper_pollAll returned POLL_TIMEOUT");
callback(PollEvent::Timeout);
}
ndk_sys::ALOOPER_POLL_ERROR => {
// If we have an IO error with our pipe to the main Java thread that's surely
// not something we can recover from
panic!("ALooper_pollAll returned POLL_ERROR");
}
id if id >= 0 => {
match id {
LOOPER_ID_MAIN => {
trace!("ALooper_pollAll returned ID_MAIN");
if let Some(ipc_cmd) = self.native_activity.read_cmd() {
let main_cmd = match ipc_cmd {
// We don't forward info about the AInputQueue to apps since it's
// an implementation details that's also not compatible with
// GameActivity
glue::AppCmd::InputQueueChanged => None,
glue::AppCmd::InitWindow => Some(MainEvent::InitWindow {}),
glue::AppCmd::TermWindow => Some(MainEvent::TerminateWindow {}),
glue::AppCmd::WindowResized => {
Some(MainEvent::WindowResized {})
}
glue::AppCmd::WindowRedrawNeeded => {
Some(MainEvent::RedrawNeeded {})
}
glue::AppCmd::ContentRectChanged => {
Some(MainEvent::ContentRectChanged {})
}
glue::AppCmd::GainedFocus => Some(MainEvent::GainedFocus),
glue::AppCmd::LostFocus => Some(MainEvent::LostFocus),
glue::AppCmd::ConfigChanged => {
Some(MainEvent::ConfigChanged {})
}
glue::AppCmd::LowMemory => Some(MainEvent::LowMemory),
glue::AppCmd::Start => Some(MainEvent::Start),
glue::AppCmd::Resume => Some(MainEvent::Resume {
loader: StateLoader { app: self },
}),
glue::AppCmd::SaveState => Some(MainEvent::SaveState {
saver: StateSaver { app: self },
}),
glue::AppCmd::Pause => Some(MainEvent::Pause),
glue::AppCmd::Stop => Some(MainEvent::Stop),
glue::AppCmd::Destroy => Some(MainEvent::Destroy),
};
trace!("Calling pre_exec_cmd({ipc_cmd:#?})");
self.native_activity.pre_exec_cmd(
ipc_cmd,
self.looper(),
LOOPER_ID_INPUT,
);
if let Some(main_cmd) = main_cmd {
trace!("Invoking callback for ID_MAIN command = {main_cmd:?}");
callback(PollEvent::Main(main_cmd));
}
trace!("Calling post_exec_cmd({ipc_cmd:#?})");
self.native_activity.post_exec_cmd(ipc_cmd);
}
}
LOOPER_ID_INPUT => {
trace!("ALooper_pollAll returned ID_INPUT");
// To avoid spamming the application with event loop iterations notifying them of
// input events then we only send one `InputAvailable` per iteration of input
// handling. We re-attach the looper when the application calls
// `AndroidApp::input_events()`
self.native_activity.detach_input_queue_from_looper();
callback(PollEvent::Main(MainEvent::InputAvailable))
}
_ => {
error!("Ignoring spurious ALooper event source: id = {id}, fd = {fd}, events = {events:?}, data = {source:?}");
}
}
}
_ => {
error!("Spurious ALooper_pollAll return value {id} (ignored)");
}
}
}
}
pub fn create_waker(&self) -> AndroidAppWaker {
unsafe {
// From the application's pov we assume the looper pointer has a static
// lifetimes and we can safely assume it is never NULL.
AndroidAppWaker {
looper: NonNull::new_unchecked(self.looper.ptr),
}
}
}
pub fn config(&self) -> ConfigurationRef {
self.native_activity.config()
}
pub fn content_rect(&self) -> Rect {
self.native_activity.content_rect()
}
pub fn asset_manager(&self) -> AssetManager {
unsafe {
let activity_ptr = self.native_activity.activity;
let am_ptr = NonNull::new_unchecked((*activity_ptr).assetManager);
AssetManager::from_ptr(am_ptr)
}
}
pub fn set_window_flags(
&self,
add_flags: WindowManagerFlags,
remove_flags: WindowManagerFlags,
) {
let na = self.native_activity();
let na_mut = na as *mut ndk_sys::ANativeActivity;
unsafe {
ndk_sys::ANativeActivity_setWindowFlags(
na_mut.cast(),
add_flags.bits(),
remove_flags.bits(),
);
}
}
// TODO: move into a trait
pub fn show_soft_input(&self, show_implicit: bool) {
let na = self.native_activity();
unsafe {
let flags = if show_implicit {
ndk_sys::ANATIVEACTIVITY_SHOW_SOFT_INPUT_IMPLICIT
} else {
0
};
ndk_sys::ANativeActivity_showSoftInput(na as *mut _, flags);
}
}
// TODO: move into a trait
pub fn hide_soft_input(&self, hide_implicit_only: bool) {
let na = self.native_activity();
unsafe {
let flags = if hide_implicit_only {
ndk_sys::ANATIVEACTIVITY_HIDE_SOFT_INPUT_IMPLICIT_ONLY
} else {
0
};
ndk_sys::ANativeActivity_hideSoftInput(na as *mut _, flags);
}
}
// TODO: move into a trait
pub fn text_input_state(&self) -> TextInputState {
TextInputState {
text: String::new(),
selection: TextSpan { start: 0, end: 0 },
compose_region: None,
}
}
// TODO: move into a trait
pub fn set_text_input_state(&self, _state: TextInputState) {
// NOP: Unsupported
}
pub fn device_key_character_map(&self, device_id: i32) -> InternalResult<KeyCharacterMap> {
let mut guard = self.key_maps.lock().unwrap();
let key_map = match guard.entry(device_id) {
std::collections::hash_map::Entry::Occupied(occupied) => occupied.get().clone(),
std::collections::hash_map::Entry::Vacant(vacant) => {
let character_map = jni_utils::device_key_character_map(
self.jvm.clone(),
self.key_map_binding.clone(),
device_id,
)?;
vacant.insert(character_map.clone());
character_map
}
};
Ok(key_map)
}
pub fn enable_motion_axis(&self, _axis: Axis) {
// NOP - The InputQueue API doesn't let us optimize which axis values are read
}
pub fn disable_motion_axis(&self, _axis: Axis) {
// NOP - The InputQueue API doesn't let us optimize which axis values are read
}
pub fn input_events_receiver(&self) -> InternalResult<Arc<InputReceiver>> {
let mut guard = self.input_receiver.lock().unwrap();
if let Some(receiver) = &*guard {
if receiver.strong_count() > 0 {
return Err(crate::error::InternalAppError::InputUnavailable);
}
}
*guard = None;
// Get the InputQueue for the NativeActivity (if there is one) and also ensure
// the queue is re-attached to our event Looper (so new input events will again
// trigger a wake up)
let queue = self
.native_activity
.looper_attached_input_queue(self.looper(), LOOPER_ID_INPUT);
// Note: we don't treat it as an error if there is no queue, so if applications
// iterate input before a queue has been created (e.g. before onStart) then
// it will simply behave like there are no events available currently.
let receiver = Arc::new(InputReceiver { queue });
*guard = Some(Arc::downgrade(&receiver));
Ok(receiver)
}
pub fn internal_data_path(&self) -> Option<std::path::PathBuf> {
let na = self.native_activity();
unsafe { util::try_get_path_from_ptr((*na).internalDataPath) }
}
pub fn external_data_path(&self) -> Option<std::path::PathBuf> {
let na = self.native_activity();
unsafe { util::try_get_path_from_ptr((*na).externalDataPath) }
}
pub fn obb_path(&self) -> Option<std::path::PathBuf> {
let na = self.native_activity();
unsafe { util::try_get_path_from_ptr((*na).obbPath) }
}
}
#[derive(Debug)]
pub(crate) struct InputReceiver {
queue: Option<InputQueue>,
}
impl<'a> From<Arc<InputReceiver>> for InputIteratorInner<'a> {
fn from(receiver: Arc<InputReceiver>) -> Self {
Self {
receiver,
_lifetime: PhantomData,
}
}
}
pub(crate) struct InputIteratorInner<'a> {
// Held to maintain exclusive access to buffered input events
receiver: Arc<InputReceiver>,
_lifetime: PhantomData<&'a ()>,
}
impl<'a> InputIteratorInner<'a> {
pub(crate) fn next<F>(&self, callback: F) -> bool
where
F: FnOnce(&input::InputEvent) -> InputStatus,
{
let Some(queue) = &self.receiver.queue else {
log::trace!("no queue available for events");
return false;
};
// Note: we basically ignore errors from event() currently. Looking at the source code for
// Android's InputQueue, the only error that can be returned here is 'WOULD_BLOCK', which we
// want to just treat as meaning the queue is empty.
//
// ref: https://github.com/aosp-mirror/platform_frameworks_base/blob/master/core/jni/android_view_InputQueue.cpp
//
if let Ok(Some(ndk_event)) = queue.event() {
log::trace!("queue: got event: {ndk_event:?}");
if let Some(ndk_event) = queue.pre_dispatch(ndk_event) {
let event = match ndk_event {
ndk::event::InputEvent::MotionEvent(e) => {
input::InputEvent::MotionEvent(input::MotionEvent::new(e))
}
ndk::event::InputEvent::KeyEvent(e) => {
input::InputEvent::KeyEvent(input::KeyEvent::new(e))
}
_ => todo!("NDK added a new type"),
};
// `finish_event` needs to be called for each event otherwise
// the app would likely get an ANR
let result = std::panic::catch_unwind(AssertUnwindSafe(|| callback(&event)));
let ndk_event = match event {
input::InputEvent::MotionEvent(e) => {
ndk::event::InputEvent::MotionEvent(e.into_ndk_event())
}
input::InputEvent::KeyEvent(e) => {
ndk::event::InputEvent::KeyEvent(e.into_ndk_event())
}
_ => unreachable!(),
};
let handled = match result {
Ok(handled) => handled,
Err(payload) => {
log::error!("Calling `finish_event` after panic in input event handler, to try and avoid being killed via an ANR");
queue.finish_event(ndk_event, false);
std::panic::resume_unwind(payload);
}
};
log::trace!("queue: finishing event");
queue.finish_event(ndk_event, handled == InputStatus::Handled);
}
true
} else {
log::trace!("queue: no more events");
false
}
}
}

113
vendor/android-activity/src/util.rs vendored Normal file
View File

@@ -0,0 +1,113 @@
use log::{error, Level};
use std::{
ffi::{CStr, CString},
fs::File,
io::{BufRead as _, BufReader, Result},
os::{
fd::{FromRawFd as _, RawFd},
raw::c_char,
},
};
pub fn try_get_path_from_ptr(path: *const c_char) -> Option<std::path::PathBuf> {
if path.is_null() {
return None;
}
let cstr = unsafe {
let cstr_slice = CStr::from_ptr(path.cast());
cstr_slice.to_str().ok()?
};
if cstr.is_empty() {
return None;
}
Some(std::path::PathBuf::from(cstr))
}
pub(crate) fn android_log(level: Level, tag: &CStr, msg: &CStr) {
let prio = match level {
Level::Error => ndk_sys::android_LogPriority::ANDROID_LOG_ERROR,
Level::Warn => ndk_sys::android_LogPriority::ANDROID_LOG_WARN,
Level::Info => ndk_sys::android_LogPriority::ANDROID_LOG_INFO,
Level::Debug => ndk_sys::android_LogPriority::ANDROID_LOG_DEBUG,
Level::Trace => ndk_sys::android_LogPriority::ANDROID_LOG_VERBOSE,
};
unsafe {
ndk_sys::__android_log_write(prio.0 as libc::c_int, tag.as_ptr(), msg.as_ptr());
}
}
pub(crate) fn forward_stdio_to_logcat() -> std::thread::JoinHandle<Result<()>> {
// XXX: make this stdout/stderr redirection an optional / opt-in feature?...
let file = unsafe {
let mut logpipe: [RawFd; 2] = Default::default();
libc::pipe2(logpipe.as_mut_ptr(), libc::O_CLOEXEC);
libc::dup2(logpipe[1], libc::STDOUT_FILENO);
libc::dup2(logpipe[1], libc::STDERR_FILENO);
libc::close(logpipe[1]);
File::from_raw_fd(logpipe[0])
};
std::thread::Builder::new()
.name("stdio-to-logcat".to_string())
.spawn(move || -> Result<()> {
let tag = CStr::from_bytes_with_nul(b"RustStdoutStderr\0").unwrap();
let mut reader = BufReader::new(file);
let mut buffer = String::new();
loop {
buffer.clear();
let len = match reader.read_line(&mut buffer) {
Ok(len) => len,
Err(e) => {
error!("Logcat forwarder failed to read stdin/stderr: {e:?}");
break Err(e);
}
};
if len == 0 {
break Ok(());
} else if let Ok(msg) = CString::new(buffer.clone()) {
android_log(Level::Info, tag, &msg);
}
}
})
.expect("Failed to start stdout/stderr to logcat forwarder thread")
}
pub(crate) fn log_panic(panic: Box<dyn std::any::Any + Send>) {
let rust_panic = unsafe { CStr::from_bytes_with_nul_unchecked(b"RustPanic\0") };
if let Some(panic) = panic.downcast_ref::<String>() {
if let Ok(msg) = CString::new(panic.clone()) {
android_log(Level::Error, rust_panic, &msg);
}
} else if let Ok(panic) = panic.downcast::<&str>() {
if let Ok(msg) = CString::new(*panic) {
android_log(Level::Error, rust_panic, &msg);
}
} else {
let unknown_panic = unsafe { CStr::from_bytes_with_nul_unchecked(b"UnknownPanic\0") };
android_log(Level::Error, unknown_panic, unsafe {
CStr::from_bytes_with_nul_unchecked(b"\0")
});
}
}
/// Run a closure and abort the program if it panics.
///
/// This is generally used to ensure Rust callbacks won't unwind past the JNI boundary, which leads
/// to undefined behaviour.
///
/// TODO(rib): throw a Java exception instead of aborting. An Android Activity does not necessarily
/// own the entire process because other application Services (or even Activities) may run in
/// threads within the same process, and so we're tearing down too much by aborting the process.
pub(crate) fn abort_on_panic<R>(f: impl FnOnce() -> R) -> R {
std::panic::catch_unwind(std::panic::AssertUnwindSafe(f)).unwrap_or_else(|panic| {
// Try logging the panic before aborting
//
// Just in case our attempt to log a panic could itself cause a panic we use a
// second catch_unwind here.
let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| log_panic(panic)));
std::process::abort();
})
}