use crate::compat::atomic::{AtomicI32, AtomicU64, AtomicU8, Ordering};
use crate::platform::types::{MobileLifecycleState, ScreenOrientation};
const UNKNOWN: u8 = 0xFF;
const ACTIVE: u8 = 0;
const INACTIVE: u8 = 1;
const BACKGROUND: u8 = 2;
static STATE: AtomicU8 = AtomicU8::new(UNKNOWN);
static GENERATION: AtomicU64 = AtomicU64::new(0);
static ROTATION: AtomicI32 = AtomicI32::new(-1);
static ROTATION_GENERATION: AtomicU64 = AtomicU64::new(0);
fn encode(state: MobileLifecycleState) -> u8 {
match state {
MobileLifecycleState::Active => ACTIVE,
MobileLifecycleState::Inactive => INACTIVE,
MobileLifecycleState::Background => BACKGROUND,
}
}
fn decode(value: u8) -> Option<MobileLifecycleState> {
match value {
ACTIVE => Some(MobileLifecycleState::Active),
INACTIVE => Some(MobileLifecycleState::Inactive),
BACKGROUND => Some(MobileLifecycleState::Background),
_ => None,
}
}
pub fn set_state(state: MobileLifecycleState) -> bool {
let previous = STATE.swap(encode(state), Ordering::SeqCst);
let current = encode(state);
if previous != current {
GENERATION.fetch_add(1, Ordering::SeqCst);
crate::set_animation_suspended(current != ACTIVE);
}
true
}
pub fn lifecycle_state() -> Option<MobileLifecycleState> {
decode(STATE.load(Ordering::SeqCst))
}
pub fn generation() -> u64 {
GENERATION.load(Ordering::SeqCst)
}
pub fn set_rotation(rotation: i32) -> bool {
if !(-1..=3).contains(&rotation) {
return false;
}
let previous = ROTATION.swap(rotation, Ordering::SeqCst);
if previous != rotation {
ROTATION_GENERATION.fetch_add(1, Ordering::SeqCst);
}
true
}
pub fn orientation() -> Option<ScreenOrientation> {
match ROTATION.load(Ordering::SeqCst) {
0 => Some(ScreenOrientation::Portrait),
1 => Some(ScreenOrientation::LandscapeLeft),
2 => Some(ScreenOrientation::PortraitUpsideDown),
3 => Some(ScreenOrientation::LandscapeRight),
_ => None,
}
}
pub fn set_orientation(orientation: ScreenOrientation) -> bool {
let rotation = match orientation {
ScreenOrientation::Portrait => 0,
ScreenOrientation::LandscapeLeft => 1,
ScreenOrientation::PortraitUpsideDown => 2,
ScreenOrientation::LandscapeRight => 3,
};
set_rotation(rotation)
}
pub fn rotation_generation() -> u64 {
ROTATION_GENERATION.load(Ordering::SeqCst)
}
pub fn set_axis_orientation(axis: i32) -> bool {
match axis {
1 => set_orientation(ScreenOrientation::Portrait),
2 => set_orientation(ScreenOrientation::LandscapeLeft),
_ => false,
}
}
#[cfg(test)]
pub(crate) static TEST_LOCK: crate::compat::Mutex<()> = crate::compat::Mutex::new(());
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_reported_phase_is_read_back_and_bumps_once() {
let _guard = crate::compat::lock(&TEST_LOCK);
assert!(set_state(MobileLifecycleState::Background));
assert_eq!(lifecycle_state(), Some(MobileLifecycleState::Background));
let before = generation();
assert!(set_state(MobileLifecycleState::Background));
assert_eq!(generation(), before, "a repeated phase is not a transition");
assert!(set_state(MobileLifecycleState::Active));
assert_eq!(generation(), before + 1);
assert!(!crate::animation_suspended());
}
#[test]
fn a_rotation_maps_to_the_four_orientations() {
let _guard = crate::compat::lock(&TEST_LOCK);
assert!(set_rotation(-1));
assert_eq!(orientation(), None, "unknown before a rotation is reported");
assert!(set_rotation(0));
assert_eq!(orientation(), Some(ScreenOrientation::Portrait));
assert!(set_rotation(1));
assert_eq!(orientation(), Some(ScreenOrientation::LandscapeLeft));
assert!(set_rotation(2));
assert_eq!(orientation(), Some(ScreenOrientation::PortraitUpsideDown));
assert!(set_rotation(3));
assert_eq!(orientation(), Some(ScreenOrientation::LandscapeRight));
assert!(!set_rotation(4), "an out-of-range rotation is refused");
}
#[test]
fn an_axis_report_names_only_the_axis() {
let _guard = crate::compat::lock(&TEST_LOCK);
assert!(set_axis_orientation(2));
assert_eq!(orientation(), Some(ScreenOrientation::LandscapeLeft));
assert!(!set_axis_orientation(3));
}
#[test]
fn suspension_follows_the_phase() {
let _guard = crate::compat::lock(&TEST_LOCK);
set_state(MobileLifecycleState::Inactive);
#[cfg(not(alloc_frugal))]
assert!(crate::animation_suspended());
#[cfg(alloc_frugal)]
assert!(!crate::animation_suspended(), "a sized profile has no animation clock to suspend");
set_state(MobileLifecycleState::Active);
assert!(!crate::animation_suspended());
}
}