mirror of
https://github.com/rust-mobile/android-activity.git
synced 2026-07-04 05:47:26 +00:00
Expose the application Configuration by reference
This provides an API to access `Configuration` state for the application without having to make deep copies of the large `Configuration` struct. This should avoid the need for Winit to create a global static copy of the Configuration whenever it changes - and instead it can just get a `ConfigurationRef` which will always reflect the latest config for the application. Fixes: #5
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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<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()
|
||||
}
|
||||
}
|
||||
@@ -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<Configuration>,
|
||||
config: ConfigurationRef,
|
||||
native_window: RwLock<Option<NativeWindow>>,
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
|
||||
@@ -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<Configuration>,
|
||||
config: ConfigurationRef,
|
||||
native_window: RwLock<Option<NativeWindow>>,
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
|
||||
Generated
-1
@@ -13,7 +13,6 @@
|
||||
<option value="$PROJECT_DIR$/app" />
|
||||
</set>
|
||||
</option>
|
||||
<option name="resolveModulePerSourceSet" value="false" />
|
||||
</GradleProjectSettings>
|
||||
</option>
|
||||
</component>
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user