diff --git a/android-activity/CHANGELOG.md b/android-activity/CHANGELOG.md index 9a69611..aebf0b5 100644 --- a/android-activity/CHANGELOG.md +++ b/android-activity/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `AndroidApp` is now `Send` + `Sync` ### Changed - *Breaking*: updates to `ndk 0.7` and `ndk-sys 0.4` +- *Breaking*: `AndroidApp::config()` now returns a clonable `ConfigurationRef` instead of a deep `Configuration` copy ## [0.1.1] - 2022-07-04 ### Changed diff --git a/android-activity/src/config.rs b/android-activity/src/config.rs new file mode 100644 index 0000000..5b12ddc --- /dev/null +++ b/android-activity/src/config.rs @@ -0,0 +1,144 @@ +use core::fmt; +use std::sync::{Arc, RwLock}; + +use ndk::configuration::{ + Configuration, Keyboard, KeysHidden, LayoutDir, NavHidden, Navigation, Orientation, ScreenLong, + ScreenSize, Touchscreen, UiModeNight, UiModeType, +}; + +#[derive(Clone)] +pub struct ConfigurationRef { + config: Arc>, +} +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 { + 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 { + 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 { + 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 { + 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 { + self.config.read().unwrap().screen_height_dp() + } + + pub fn screen_width_dp(&self) -> Option { + 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 { + 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() + } +} diff --git a/android-activity/src/game_activity/mod.rs b/android-activity/src/game_activity/mod.rs index 470d166..ce0e4b4 100644 --- a/android-activity/src/game_activity/mod.rs +++ b/android-activity/src/game_activity/mod.rs @@ -8,8 +8,7 @@ use std::ops::Deref; use std::os::raw; use std::os::unix::prelude::*; use std::ptr::NonNull; -use std::sync::Arc; -use std::sync::RwLock; +use std::sync::{Arc, RwLock}; use std::time::Duration; use std::{ptr, thread}; @@ -25,7 +24,7 @@ use ndk::configuration::Configuration; use ndk::looper::FdEvent; use ndk::native_window::NativeWindow; -use crate::{util, AndroidApp, MainEvent, NativeWindowRef, PollEvent, Rect}; +use crate::{util, AndroidApp, ConfigurationRef, MainEvent, NativeWindowRef, PollEvent, Rect}; mod ffi; @@ -121,15 +120,12 @@ impl AndroidApp { // 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() - // - // Whenever we get a ConfigChanged notification we synchronize this - // config state with a deep copy. let config = Configuration::clone_from_ptr(NonNull::new_unchecked((*ptr.as_ptr()).config)); Self { inner: Arc::new(RwLock::new(AndroidAppInner { native_app: NativeAppGlue { ptr }, - config: RwLock::new(config), + config: ConfigurationRef::new(config), native_window: Default::default(), })), } @@ -153,7 +149,7 @@ unsafe impl Sync for NativeAppGlue {} #[derive(Debug)] pub struct AndroidAppInner { native_app: NativeAppGlue, - config: RwLock, + config: ConfigurationRef, native_window: RwLock>, } @@ -254,7 +250,7 @@ impl AndroidAppInner { MainEvent::LostFocus } ffi::NativeAppGlueAppCmd_APP_CMD_CONFIG_CHANGED => { - MainEvent::ConfigChanged + MainEvent::ConfigChanged {} } ffi::NativeAppGlueAppCmd_APP_CMD_LOW_MEMORY => { MainEvent::LowMemory @@ -282,11 +278,10 @@ impl AndroidAppInner { 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.write().unwrap() = - Configuration::clone_from_ptr(NonNull::new_unchecked( - (*native_app.as_ptr()).config, - )); + 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; @@ -354,8 +349,8 @@ impl AndroidAppInner { } } - pub fn config(&self) -> Configuration { - self.config.read().unwrap().clone() + pub fn config(&self) -> ConfigurationRef { + self.config.clone() } pub fn content_rect(&self) -> Rect { diff --git a/android-activity/src/lib.rs b/android-activity/src/lib.rs index 11ed79f..4759edc 100644 --- a/android-activity/src/lib.rs +++ b/android-activity/src/lib.rs @@ -5,7 +5,6 @@ use std::time::Duration; use std::{os::unix::prelude::RawFd, sync::Arc}; use ndk::asset::AssetManager; -use ndk::configuration::Configuration; // TODO: import FdEvent and avoid depending on ndk Looper abstraction in case we want to // support using epoll directly in the future. use ndk::looper::FdEvent; @@ -36,6 +35,9 @@ 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 @@ -148,7 +150,8 @@ pub enum MainEvent<'a> { /// Command from main thread: the current device configuration has changed. /// You can get a copy of the latest [Configuration] by calling /// [`AndroidApp::config()`] - ConfigChanged, + #[non_exhaustive] + ConfigChanged {}, /// Command from main thread: the system is running low on memory. /// Try to reduce your memory use. @@ -267,8 +270,8 @@ impl AndroidApp { self.inner.read().unwrap().create_waker() } - /// Returns a deep copy of this application's [`Configuration`] - pub fn config(&self) -> Configuration { + /// Returns a (cheaply clonable) reference to this application's [`Configuration`] + pub fn config(&self) -> ConfigurationRef { self.inner.read().unwrap().config() } diff --git a/android-activity/src/native_activity/mod.rs b/android-activity/src/native_activity/mod.rs index c315ac3..24149e9 100644 --- a/android-activity/src/native_activity/mod.rs +++ b/android-activity/src/native_activity/mod.rs @@ -7,8 +7,7 @@ use std::ops::Deref; use std::os::raw; use std::os::unix::prelude::*; use std::ptr::NonNull; -use std::sync::Arc; -use std::sync::RwLock; +use std::sync::{Arc, RwLock}; use std::time::Duration; use std::{ptr, thread}; @@ -23,7 +22,7 @@ use ndk::input_queue::InputQueue; use ndk::looper::FdEvent; use ndk::native_window::NativeWindow; -use crate::{util, AndroidApp, MainEvent, NativeWindowRef, PollEvent, Rect}; +use crate::{util, AndroidApp, ConfigurationRef, MainEvent, NativeWindowRef, PollEvent, Rect}; mod ffi; @@ -123,15 +122,12 @@ impl AndroidApp { // 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() - // - // Whenever we get a ConfigChanged notification we synchronize this - // config state with a deep copy. let config = Configuration::clone_from_ptr(NonNull::new_unchecked((*ptr.as_ptr()).config)); AndroidApp { inner: Arc::new(RwLock::new(AndroidAppInner { native_app: NativeAppGlue { ptr }, - config: RwLock::new(config), + config: ConfigurationRef::new(config), native_window: Default::default(), })), } @@ -155,7 +151,7 @@ unsafe impl Sync for NativeAppGlue {} #[derive(Debug)] pub(crate) struct AndroidAppInner { native_app: NativeAppGlue, - config: RwLock, + config: ConfigurationRef, native_window: RwLock>, } @@ -253,7 +249,9 @@ impl AndroidAppInner { } ffi::APP_CMD_GAINED_FOCUS => Some(MainEvent::GainedFocus), ffi::APP_CMD_LOST_FOCUS => Some(MainEvent::LostFocus), - ffi::APP_CMD_CONFIG_CHANGED => Some(MainEvent::ConfigChanged), + ffi::APP_CMD_CONFIG_CHANGED => { + Some(MainEvent::ConfigChanged {}) + } ffi::APP_CMD_LOW_MEMORY => Some(MainEvent::LowMemory), ffi::APP_CMD_START => Some(MainEvent::Start), ffi::APP_CMD_RESUME => Some(MainEvent::Resume { @@ -276,13 +274,12 @@ impl AndroidAppInner { if let Some(cmd) = cmd { trace!("Read ID_MAIN command {cmd_i} = {cmd:?}"); match cmd { - MainEvent::ConfigChanged => { - *self.config.write().unwrap() = - Configuration::clone_from_ptr( - NonNull::new_unchecked( - (*native_app.as_ptr()).config, - ), - ); + 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; @@ -353,8 +350,8 @@ impl AndroidAppInner { } } - pub fn config(&self) -> Configuration { - self.config.read().unwrap().clone() + pub fn config(&self) -> ConfigurationRef { + self.config.clone() } pub fn content_rect(&self) -> Rect { diff --git a/examples/agdk-mainloop/src/lib.rs b/examples/agdk-mainloop/src/lib.rs index 6462b86..c45f186 100644 --- a/examples/agdk-mainloop/src/lib.rs +++ b/examples/agdk-mainloop/src/lib.rs @@ -52,6 +52,9 @@ fn android_main(app: AndroidApp) { MainEvent::InputAvailable { .. } => { redraw_pending = true; } + MainEvent::ConfigChanged { .. } => { + info!("Config Changed: {:#?}", app.config()); + } MainEvent::LowMemory => {} MainEvent::Destroy => quit = true, diff --git a/examples/na-mainloop/.idea/gradle.xml b/examples/na-mainloop/.idea/gradle.xml index 526b4c2..a2d7c21 100644 --- a/examples/na-mainloop/.idea/gradle.xml +++ b/examples/na-mainloop/.idea/gradle.xml @@ -13,7 +13,6 @@ - diff --git a/examples/na-mainloop/src/lib.rs b/examples/na-mainloop/src/lib.rs index 6462b86..c45f186 100644 --- a/examples/na-mainloop/src/lib.rs +++ b/examples/na-mainloop/src/lib.rs @@ -52,6 +52,9 @@ fn android_main(app: AndroidApp) { MainEvent::InputAvailable { .. } => { redraw_pending = true; } + MainEvent::ConfigChanged { .. } => { + info!("Config Changed: {:#?}", app.config()); + } MainEvent::LowMemory => {} MainEvent::Destroy => quit = true,