Skip to main content

AndroidApp

Struct AndroidApp 

Source
pub struct AndroidApp { /* private fields */ }
Expand description

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.

Implementations§

Source§

impl AndroidApp

Source

pub fn native_window(&self) -> Option<NativeWindow>

Queries the current NativeWindow for the application.

This will only return Some(window) between MainEvent::InitWindow and MainEvent::TerminateWindow events.

Source

pub fn java_main_looper(&self) -> ForeignLooper

Returns a ndk::looper::ForeignLooper associated with the Java main / UI thread.

This can be used to register file descriptors that may wake up the Java main / UI thread and optionally run callbacks on that thread.

let looper = app.java_main_looper();
looper.add_fd_with_callback(todo!(), ndk::looper::FdEvent::INPUT, todo!()).unwrap();
Source

pub fn vm_as_ptr(&self) -> *mut c_void

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 could this as a JavaVM via:

let vm = unsafe { JavaVM::from_raw(app.vm_as_ptr().cast()) };
Source

pub fn activity_as_ptr(&self) -> *mut c_void

Returns an (unowned) JNI global object reference for this application’s JVM Activity as a pointer

If you use the jni crate you can cast this as a JObject reference via:

let raw_activity_global = app.activity_as_ptr() as jni::sys::jobject;
// SAFETY: The reference / pointer is valid as long as `app` is valid
let activity = unsafe { env.as_cast_raw::<Global<JObject>>(&raw_activity_global)? };
§JNI Safety

Note that the returned reference will be a JNI global reference that you do not own.

  • Don’t wrap the reference as a Global which would try to delete the reference when dropped.
  • Don’t wrap the reference in an Auto which would treat the reference like a local reference and try to delete it when dropped.

The reference is only guaranteed to be valid until you drop the AndroidApp.

Warning: Don’t assume the returned reference has a 'static lifetime since it’s possible for android_main() to run multiple times over the lifetime of an application with a new AndroidApp instance each time.

Source

pub fn poll_events<F>(&self, timeout: Option<Duration>, callback: F)
where F: FnMut(PollEvent<'_>),

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 or ALooper_pollOnce 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.

Internally this is based on ALooper_pollOnce and will only poll file descriptors once per invocation.

§Wake Events

Note that although there is an explicit PollEvent::Wake that can indicate that the main loop was explicitly woken up (E.g. via AndroidAppWaker::wake) it’s possible that there will be more-specific events that will be delivered after a wake up.

In other words you should only expect to explicitly see PollEvent::Wake events after an early wake up if there were no other, more-specific, events that could be delivered after the wake up.

Again, said another way - it’s possible that any event could effectively be delivered after an early wake up so don’t assume there is a 1:1 relationship between invoking a wake up via AndroidAppWaker::wake and the delivery of PollEvent::Wake.

§Panics

This must only be called from your android_main() thread and it may panic if called from another thread.

Source

pub fn create_waker(&self) -> AndroidAppWaker

Creates a means to wake up the main loop while it is blocked waiting for events within AndroidApp::poll_events().

Source

pub fn run_on_java_main_thread<F>(&self, f: Box<F>)
where F: FnOnce() + Send + 'static,

Runs the given closure on the Java main / UI thread.

This is useful for performing operations that must be executed on the main thread, such as interacting with Android SDK APIs that require execution on the main thread.

Any panic within the closure will be caught and logged as an error, (assuming your application is built to allow unwinding).

The thread will be attached to the JVM (for using JNI) and any un-cleared Java exceptions left over by the callback will be caught, cleared and logged as an error.

There is no built-in mechanism to propagate results back to the caller but you can use channels or other synchronization primitives that you capture.

It’s important to avoid blocking the android_main thread while waiting for any results because this could lead to deadlocks for Activity callbacks that require a synchronous response for the android_activity thread.

§Example

This example demonstrates using the jni 0.22 API to show a toast message from the Java main thread.

use android_activity::AndroidApp;
use jni::{objects::JString, refs::Global};

jni::bind_java_type! { Context => "android.content.Context" }
jni::bind_java_type! {
    Activity => "android.app.Activity",
    type_map {
        Context => "android.content.Context",
    },
    is_instance_of {
        context: Context
    },
}

jni::bind_java_type! {
    Toast => "android.widget.Toast",
    type_map {
        Context => "android.content.Context",
    },
    methods {
        static fn make_text(context: Context, text: JCharSequence, duration: i32) -> Toast,
        fn show(),
    }
}

enum ToastDuration {
    Short = 0,
    Long = 1,
}

fn send_toast(outer_app: &AndroidApp, msg: impl AsRef<str>, duration: ToastDuration) {
    let app = outer_app.clone();
    let msg = msg.as_ref().to_string();
    outer_app.run_on_java_main_thread(Box::new(move || {
        let jvm = unsafe { jni::JavaVM::from_raw(app.vm_as_ptr() as _) };
        // As an micro optimization you could use jvm.with_top_local_frame, since we know
        // we're already attached
        if let Err(err) = jvm.attach_current_thread(|env| -> jni::errors::Result<()> {
            let activity: jni::sys::jobject = app.activity_as_ptr() as _;
            let activity = unsafe { env.as_cast_raw::<Global<Activity>>(&activity)? };
            let message = JString::new(env, &msg)?;
            let toast = Toast::make_text(env, activity.as_ref(), &message, duration as i32)?;
            toast.show(env)?;
            Ok(())
        }) {
            log::error!("Failed to show toast on main thread: {err:?}");
        }
    }));
}
Source

pub fn config(&self) -> ConfigurationRef

Returns a reference to this application’s ndk::configuration::Configuration.

§Warning

The value held by this reference will change with every MainEvent::ConfigChanged event that is raised. You should not Clone this type to compare it against a “new” AndroidApp::config() when that event is raised, since both point to the same internal ndk::configuration::Configuration and will be identical.

Source

pub fn content_rect(&self) -> Rect

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.

Source

pub fn asset_manager(&self) -> AssetManager

Returns the AssetManager for the application’s Application context.

Use this to access raw files bundled in the application’s .apk file.

This is an Application-scoped asset manager, not an Activity-scoped one. In normal usage those behave the same for packaged assets, so this is usually the correct API to use.

In uncommon cases, an Activity may have a context-specific asset/resource view that differs from the Application context. If you specifically need the current Activity’s AssetManager, obtain the Activity via AndroidApp::activity_as_ptr and call getAssets() through JNI.

The returned AssetManager has a 'static lifetime and remains valid across Activity recreation, including when android_main() is re-entered.

Beware: If you consider accessing the Activity context’s AssetManager through JNI you must keep the AssetManager alive via a global reference before accessing the ndk AAssetManager and ndk::asset::AssetManager does not currently handle this for you.

Source

pub fn set_window_flags( &self, add_flags: WindowManagerFlags, remove_flags: WindowManagerFlags, )

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)>.

Source

pub fn enable_motion_axis(&self, axis: Axis)

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.

Source

pub fn disable_motion_axis(&self, 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.

Source

pub fn show_soft_input(&self, show_implicit: bool)

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.

Source

pub fn hide_soft_input(&self, hide_implicit_only: bool)

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.

Source

pub fn text_input_state(&self) -> TextInputState

Fetch the current input text state, as updated by any active IME.

Source

pub fn set_text_input_state(&self, state: TextInputState)

Forward the given input text state to any active IME.

Source

pub fn set_ime_editor_info( &self, input_type: InputType, action: TextInputAction, options: ImeOptions, )

Specify the type of text being input, how the IME enter/action key should behave and any additional IME options.

Also see the Android SDK documentation for android.view.inputmethod.EditorInfo

Source

pub fn input_events_iter(&self) -> Result<InputIterator<'_>>

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:

match app.input_events_iter() {
    Ok(mut iter) => {
        loop {
            let read_input = iter.next(|event| {
                let handled = match event {
                    InputEvent::KeyEvent(key_event) => {
                        // Snip
                        InputStatus::Handled
                    }
                    InputEvent::MotionEvent(motion_event) => {
                        InputStatus::Unhandled
                    }
                    event => {
                        InputStatus::Unhandled
                    }
                };

                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.

Source

pub fn device_key_character_map( &self, device_id: i32, ) -> Result<KeyCharacterMap>

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:

let mut combining_accent = None;
// Snip

let combined_key_char = if let Ok(map) = app.device_key_character_map(key_event.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)) => {
                        println!("KeyEvent: Combined '{unicode}' with accent '{accent}' to give '{key}'");
                        Some(key)
                    }
                    Ok(None) => None,
                    Err(err) => {
                        eprintln!("KeyEvent: Failed to combine 'dead key' accent '{accent}' with '{unicode}': {err:?}");
                        None
                    }
                }
            } else {
                println!("KeyEvent: Pressed '{unicode}'");
                Some(unicode)
            };
            combining_accent = None;
            combined_unicode.map(|unicode| KeyMapChar::Unicode(unicode))
        }
        Ok(KeyMapChar::CombiningAccent(accent)) => {
            println!("KeyEvent: Pressed 'dead key' combining accent '{accent}'");
            combining_accent = Some(accent);
            Some(KeyMapChar::CombiningAccent(accent))
        }
        Ok(KeyMapChar::None) => {
            println!("KeyEvent: Pressed non-unicode key");
            combining_accent = None;
            None
        }
        Err(err) => {
            eprintln!("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.

This API should not be called with a device_id of 0, since that indicates a non-physical device and will result in a error::AppError::JavaError.

Source

pub fn sdk_version() -> i32

The user-visible SDK version of the framework

Also referred to as Build.VERSION_CODES

Source

pub fn internal_data_path(&self) -> Option<PathBuf>

Path to this application’s internal data directory

Source

pub fn external_data_path(&self) -> Option<PathBuf>

Path to this application’s external data directory

Source

pub fn obb_path(&self) -> Option<PathBuf>

Path to the directory containing the application’s OBB files (if any).

Trait Implementations§

Source§

impl Clone for AndroidApp

Source§

fn clone(&self) -> AndroidApp

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for AndroidApp

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Hash for AndroidApp

Source§

fn hash<H: Hasher>(&self, state: &mut H)

Feeds this value into the given Hasher. Read more
1.3.0 · Source§

fn hash_slice<H>(data: &[Self], state: &mut H)
where H: Hasher, Self: Sized,

Feeds a slice of this type into the given Hasher. Read more
Source§

impl PartialEq for AndroidApp

Source§

fn eq(&self, other: &Self) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, and should not be overridden without very good reason.
Source§

impl Eq for AndroidApp

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.