1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283
use std::hash::Hash;
use std::sync::Arc;
use std::sync::RwLock;
use std::time::Duration;
use ndk::asset::AssetManager;
use ndk::native_window::NativeWindow;
#[cfg(not(target_os = "android"))]
compile_error!("android-activity only supports compiling for Android");
#[cfg(all(feature = "game-activity", feature = "native-activity"))]
compile_error!(
"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!("Either \"game-activity\" or \"native-activity\" must be enabled as features");
#[cfg(any(feature = "native-activity", doc))]
mod native_activity;
#[cfg(any(feature = "native-activity", doc))]
use native_activity as activity_impl;
#[cfg(feature = "game-activity")]
mod game_activity;
#[cfg(feature = "game-activity")]
use game_activity as activity_impl;
pub use activity_impl::input;
mod config;
pub use config::ConfigurationRef;
mod util;
// Note: unlike in ndk-glue this has signed components (consistent
// with Android's ARect) which generally allows for representing
// rectangles with a negative/off-screen origin. Even though this
// is currently just used to represent the content rect (that probably
// wouldn't have any negative components) we keep the generality
// since this is a primitive type that could potentially be used
// for more things in the future.
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct Rect {
pub left: i32,
pub top: i32,
pub right: i32,
pub bottom: i32,
}
pub type StateSaver<'a> = activity_impl::StateSaver<'a>;
pub type StateLoader<'a> = activity_impl::StateLoader<'a>;
#[non_exhaustive]
#[derive(Debug)]
pub enum MainEvent<'a> {
/// New input events are available via [`AndroidApp::input_events()`]
///
/// _Note: Even if more input is received this event will not be resent
/// until [`AndroidApp::input_events()`] has been called, which enables
/// applications to batch up input processing without there being lots of
/// redundant event loop wake ups._
///
/// [`AndroidApp::input_events()`]: AndroidApp::input_events
InputAvailable,
/// Command from main thread: a new [`NativeWindow`] is ready for use. Upon
/// receiving this command, [`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, [`native_window()`] still
/// returns the existing window; after returning from the [`AndroidApp::poll_events()`]
/// callback then [`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 [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 {},
}
#[derive(Debug)]
#[non_exhaustive]
pub enum PollEvent<'a> {
Wake,
Timeout,
Main(MainEvent<'a>),
}
use activity_impl::AndroidAppInner;
pub use activity_impl::AndroidAppWaker;
#[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 {
#[cfg_attr(docsrs, doc(cfg(feature = "native-activity")))]
#[cfg(feature = "native-activity")]
pub(crate) fn native_activity(&self) -> *const ndk_sys::ANativeActivity {
self.inner.read().unwrap().native_activity()
}
/// Queries the current [`NativeWindow`] for the application.
///
/// This will only return `Some(window)` between
/// [`AndroidAppMainEvent::InitWindow`] and [`AndroidAppMainEvent::TerminateWindow`]
/// events.
pub fn native_window<'a>(&self) -> Option<NativeWindow> {
self.inner.read().unwrap().native_window()
}
/// 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.
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 [`poll_events()`].
pub fn create_waker(&self) -> activity_impl::AndroidAppWaker {
self.inner.read().unwrap().create_waker()
}
/// Returns a (cheaply clonable) reference to this application's [`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()
}
pub fn enable_motion_axis(&self, axis: input::Axis) {
self.inner.write().unwrap().enable_motion_axis(axis);
}
pub fn disable_motion_axis(&self, axis: input::Axis) {
self.inner.write().unwrap().disable_motion_axis(axis);
}
pub fn input_events<'b, F>(&self, callback: F)
where
F: FnMut(&input::InputEvent),
{
self.inner.read().unwrap().input_events(callback);
}
/// 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() {
i32::from_str_radix(&val, 10).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>();
}