use std::sync::Mutex;
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum SystemUiOverlay {
Top,
Bottom,
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum SystemUiMode {
EdgeToEdge,
Immersive,
ImmersiveSticky,
LeanBack,
Manual {
top: bool,
bottom: bool,
},
}
struct SystemUiSlot {
mode: SystemUiMode,
generation: u64,
}
static SYSTEM_UI: Mutex<SystemUiSlot> = Mutex::new(SystemUiSlot {
mode: SystemUiMode::EdgeToEdge,
generation: 0,
});
pub fn set_system_ui_mode(mode: SystemUiMode) {
let mut slot = SYSTEM_UI.lock().unwrap_or_else(|e| e.into_inner());
slot.mode = mode;
slot.generation += 1;
}
pub fn current_system_ui_mode() -> (u64, SystemUiMode) {
let slot = SYSTEM_UI.lock().unwrap_or_else(|e| e.into_inner());
(slot.generation, slot.mode)
}
pub fn encoded_state() -> u64 {
let (generation, mode) = current_system_ui_mode();
let low: u64 = match mode {
SystemUiMode::EdgeToEdge => 0,
SystemUiMode::Immersive => 1,
SystemUiMode::ImmersiveSticky => 2,
SystemUiMode::LeanBack => 3,
SystemUiMode::Manual { top, bottom } => 4 | ((top as u64) << 4) | ((bottom as u64) << 5),
};
(generation << 8) | low
}
#[derive(Debug, Default)]
pub struct SystemUiWatcher {
last_generation: u64,
}
impl SystemUiWatcher {
pub fn new() -> Self {
Self { last_generation: 0 }
}
pub fn poll(&mut self) -> Option<SystemUiMode> {
let slot = SYSTEM_UI.lock().unwrap_or_else(|e| e.into_inner());
if slot.generation == self.last_generation {
return None;
}
self.last_generation = slot.generation;
Some(slot.mode)
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::Mutex as StdMutex;
use std::thread;
static TEST_LOCK: StdMutex<()> = StdMutex::new(());
fn reset_slot() {
let mut slot = SYSTEM_UI.lock().unwrap_or_else(|e| e.into_inner());
slot.mode = SystemUiMode::EdgeToEdge;
slot.generation = 0;
}
#[test]
fn set_system_ui_mode_bumps_generation_and_watcher_observes_it_once() {
let _guard = TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
reset_slot();
let mut watcher = SystemUiWatcher::new();
assert_eq!(watcher.poll(), None);
set_system_ui_mode(SystemUiMode::Immersive);
let observed = watcher.poll();
assert_eq!(observed, Some(SystemUiMode::Immersive));
assert_eq!(watcher.poll(), None);
}
#[test]
fn independent_watchers_each_see_the_change_once() {
let _guard = TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
reset_slot();
let mut a = SystemUiWatcher::new();
let mut b = SystemUiWatcher::new();
set_system_ui_mode(SystemUiMode::LeanBack);
assert_eq!(a.poll(), Some(SystemUiMode::LeanBack));
assert_eq!(b.poll(), Some(SystemUiMode::LeanBack));
assert_eq!(a.poll(), None);
assert_eq!(b.poll(), None);
}
#[test]
fn never_calling_the_api_leaves_a_fresh_watcher_silent() {
let _guard = TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
reset_slot();
let mut watcher = SystemUiWatcher::new();
assert_eq!(watcher.poll(), None);
assert_eq!(watcher.poll(), None);
}
#[test]
fn set_from_a_spawned_thread_is_observed_on_the_polling_thread() {
let _guard = TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
reset_slot();
let mut watcher = SystemUiWatcher::new();
assert_eq!(watcher.poll(), None);
thread::spawn(|| {
set_system_ui_mode(SystemUiMode::ImmersiveSticky);
})
.join()
.unwrap();
assert_eq!(watcher.poll(), Some(SystemUiMode::ImmersiveSticky));
}
#[test]
fn encoded_state_packs_generation_and_each_mode() {
let _guard = TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
reset_slot();
assert_eq!(encoded_state(), 0);
set_system_ui_mode(SystemUiMode::EdgeToEdge);
assert_eq!(encoded_state(), 1u64 << 8);
set_system_ui_mode(SystemUiMode::Immersive);
assert_eq!(encoded_state(), (2u64 << 8) | 1);
set_system_ui_mode(SystemUiMode::ImmersiveSticky);
assert_eq!(encoded_state(), (3u64 << 8) | 2);
set_system_ui_mode(SystemUiMode::LeanBack);
assert_eq!(encoded_state(), (4u64 << 8) | 3);
}
#[test]
fn encoded_state_packs_manual_flag_combinations() {
let _guard = TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
reset_slot();
set_system_ui_mode(SystemUiMode::Manual {
top: false,
bottom: false,
});
assert_eq!(encoded_state(), (1u64 << 8) | 4);
set_system_ui_mode(SystemUiMode::Manual {
top: true,
bottom: false,
});
assert_eq!(encoded_state(), (2u64 << 8) | 4 | (1 << 4));
set_system_ui_mode(SystemUiMode::Manual {
top: false,
bottom: true,
});
assert_eq!(encoded_state(), (3u64 << 8) | 4 | (1 << 5));
set_system_ui_mode(SystemUiMode::Manual {
top: true,
bottom: true,
});
assert_eq!(encoded_state(), (4u64 << 8) | 4 | (1 << 4) | (1 << 5));
}
#[test]
fn current_system_ui_mode_peeks_without_consuming_a_watcher_cursor() {
let _guard = TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
reset_slot();
assert_eq!(current_system_ui_mode(), (0, SystemUiMode::EdgeToEdge));
set_system_ui_mode(SystemUiMode::Immersive);
assert_eq!(current_system_ui_mode(), (1, SystemUiMode::Immersive));
assert_eq!(current_system_ui_mode(), (1, SystemUiMode::Immersive));
}
}