use std::time::{Duration, Instant};
pub(crate) const ACCESSIBILITY_PUBLISH_INTERVAL: Duration = Duration::from_secs(1);
pub(crate) const ACCESSIBILITY_INTERACTIVE_INTERVAL: Duration = Duration::from_millis(100);
pub(crate) const ACCESSIBILITY_INTERACTION_WINDOW: Duration = Duration::from_secs(2);
pub(crate) const ACCESSIBILITY_UNREAD_PUBLISH_INTERVAL: Duration = Duration::from_secs(4);
pub(crate) struct AccessibilityPublishPolicy {
enabled: bool,
last_publish: Option<Instant>,
pending_deadline: Option<Instant>,
interval: Duration,
interactive_until: Option<Instant>,
read_since_publish: bool,
published_once: bool,
}
impl AccessibilityPublishPolicy {
pub(crate) fn new() -> Self {
Self {
enabled: false,
last_publish: None,
pending_deadline: None,
interval: ACCESSIBILITY_PUBLISH_INTERVAL,
interactive_until: None,
read_since_publish: false,
published_once: false,
}
}
pub(crate) fn update_enabled(&mut self, enabled: bool) -> bool {
let became_enabled = enabled && !self.enabled;
if became_enabled {
self.last_publish = None;
self.interval = ACCESSIBILITY_PUBLISH_INTERVAL;
self.published_once = false;
}
if !enabled {
self.pending_deadline = None;
}
self.enabled = enabled;
became_enabled
}
pub(crate) fn note_read(&mut self, now: Instant) {
self.read_since_publish = true;
self.interval = ACCESSIBILITY_PUBLISH_INTERVAL;
self.interactive_until = Some(now + ACCESSIBILITY_INTERACTION_WINDOW);
}
pub(crate) fn try_begin_publish(&mut self, now: Instant) -> bool {
if !self.enabled {
return false;
}
let wait = if self.interactive_until.is_some_and(|until| now < until) {
ACCESSIBILITY_INTERACTIVE_INTERVAL
} else {
self.interval
};
match self.last_publish {
Some(last) if now.duration_since(last) < wait => {
self.pending_deadline = Some(last + wait);
false
}
_ => {
self.pending_deadline = None;
self.last_publish = Some(now);
true
}
}
}
pub(crate) fn published(&mut self, backs_off_unread: bool) {
let in_use = self
.interactive_until
.zip(self.last_publish)
.is_some_and(|(until, last)| last < until);
let unread = self.published_once && !self.read_since_publish;
self.interval = if backs_off_unread && unread && !in_use {
(self.interval * 2).min(ACCESSIBILITY_UNREAD_PUBLISH_INTERVAL)
} else {
ACCESSIBILITY_PUBLISH_INTERVAL
};
self.read_since_publish = false;
self.published_once = true;
}
pub(crate) fn wake_deadline(&self) -> Option<Instant> {
self.pending_deadline
}
}
#[cfg(test)]
#[path = "tests/accessibility_publish_policy_tests.rs"]
mod tests;