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>();
}