Skip to main content

cranpose_services/
background.rs

1//! Background execution: ask the OS to keep running briefly after the app is
2//! backgrounded, so in-flight work (e.g. draining a recognition queue) can
3//! finish instead of being suspended immediately.
4//!
5//! The platform backend installs an implementation via
6//! [`set_platform_background_activity`] (iOS `beginBackgroundTask`, Android a
7//! foreground service). No default: [`background_activity`] returns `None` where
8//! unsupported, and the app simply runs only while foregrounded.
9
10use std::sync::Arc;
11use std::sync::Mutex;
12use std::sync::OnceLock;
13
14/// Marks periods of important work so the platform grants background running
15/// time. Implementations are `Send + Sync` (the app toggles activity from its
16/// worker threads).
17pub trait BackgroundActivity: Send + Sync {
18    /// `true` when important work starts, `false` when it finishes. Calls are
19    /// balanced by the app but implementations must tolerate repeats.
20    fn set_active(&self, active: bool);
21}
22
23pub type BackgroundActivityRef = Arc<dyn BackgroundActivity>;
24
25fn slot() -> &'static Mutex<Option<BackgroundActivityRef>> {
26    static SLOT: OnceLock<Mutex<Option<BackgroundActivityRef>>> = OnceLock::new();
27    SLOT.get_or_init(|| Mutex::new(None))
28}
29
30/// Installs the platform background-activity handler, replacing any previous one.
31pub fn set_platform_background_activity(activity: BackgroundActivityRef) {
32    if let Ok(mut s) = slot().lock() {
33        *s = Some(activity);
34    }
35}
36
37/// Removes any registered handler (tests/teardown).
38pub fn clear_platform_background_activity() {
39    if let Ok(mut s) = slot().lock() {
40        *s = None;
41    }
42}
43
44/// The registered background-activity handler, or `None` where unsupported.
45pub fn background_activity() -> Option<BackgroundActivityRef> {
46    slot().lock().ok().and_then(|s| s.clone())
47}
48
49/// Convenience: mark background work active/inactive (no-op where unsupported).
50pub fn set_background_active(active: bool) {
51    if let Some(activity) = background_activity() {
52        activity.set_active(active);
53    }
54}
55
56#[cfg(test)]
57mod tests {
58    use super::*;
59    use std::sync::atomic::{AtomicBool, Ordering};
60
61    #[test]
62    fn registration_round_trips() {
63        clear_platform_background_activity();
64        set_background_active(true); // no-op, no panic
65        struct Rec(AtomicBool);
66        impl BackgroundActivity for Rec {
67            fn set_active(&self, active: bool) {
68                self.0.store(active, Ordering::SeqCst);
69            }
70        }
71        let rec = Arc::new(Rec(AtomicBool::new(false)));
72        set_platform_background_activity(rec.clone());
73        set_background_active(true);
74        assert!(rec.0.load(Ordering::SeqCst));
75        clear_platform_background_activity();
76    }
77}