input_pipeline/light_sensor/
led_watcher.rs

1// Copyright 2022 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use async_utils::hanging_get::client::HangingGetStream;
6use fidl_fuchsia_settings::{LightGroup as LightGroupFidl, LightProxy, LightValue};
7use fidl_fuchsia_ui_brightness::ControlProxy;
8use fuchsia_async as fasync;
9use fuchsia_inspect::Property;
10#[cfg(test)]
11use futures::channel::mpsc;
12use futures::channel::oneshot;
13use futures::StreamExt;
14use std::cell::RefCell;
15use std::collections::HashMap;
16use std::rc::Rc;
17
18#[derive(Debug, Clone)]
19pub struct LightGroup {
20    /// Name of the light group.
21    name: String,
22    /// None brightness implies the light is disabled.
23    brightness: Option<f32>,
24}
25
26impl LightGroup {
27    #[cfg(test)]
28    pub(crate) fn new(name: String, brightness: Option<f32>) -> Self {
29        Self { name, brightness }
30    }
31
32    pub(crate) fn name(&self) -> &String {
33        &self.name
34    }
35
36    pub(crate) fn brightness(&self) -> Option<f32> {
37        self.brightness
38    }
39
40    #[cfg(test)]
41    pub(crate) fn set_brightness_for_test(&mut self, brightness: Option<f32>) {
42        self.brightness = brightness;
43    }
44
45    fn map_from_light_groups(light_groups: Vec<LightGroupFidl>) -> HashMap<String, LightGroup> {
46        light_groups
47            .into_iter()
48            .filter_map(|light_group| {
49                let enabled = light_group.enabled;
50                let lights = light_group.lights;
51                light_group.name.and_then(|name| {
52                    if enabled.unwrap_or(false) {
53                        lights
54                            .as_ref()
55                            .and_then(|lights| lights.get(0))
56                            .and_then(|light| light.value.as_ref())
57                            .map(|value| match value {
58                                LightValue::On(true) => Some(1.0),
59                                LightValue::On(false) => Some(0.0),
60                                LightValue::Brightness(b) => Some(*b as f32),
61                                LightValue::Color(_) => None,
62                            })
63                            .map(|brightness| (name.clone(), LightGroup { name, brightness }))
64                    } else {
65                        Some((name.clone(), LightGroup { name, brightness: None }))
66                    }
67                })
68            })
69            .collect()
70    }
71}
72
73pub struct LedWatcher {
74    backlight_brightness: Rc<RefCell<f32>>,
75    light_groups: Rc<RefCell<HashMap<String, LightGroup>>>,
76    #[cfg(test)]
77    update: Option<RefCell<mpsc::Sender<Update>>>,
78}
79
80#[cfg(test)]
81enum Update {
82    Brightness,
83    LightGroups,
84}
85
86impl LedWatcher {
87    /// Create a new `LedWatcher` for the `light_groups` with the supplied calibration. Only the
88    /// [LightGroup]s that have a corresponding calibration entry in [Calibration].`leds` will be
89    /// tracked.
90    pub(crate) fn new(light_groups: Vec<LightGroupFidl>) -> Self {
91        Self {
92            backlight_brightness: Rc::new(RefCell::new(0.0)),
93            light_groups: Rc::new(RefCell::new(LightGroup::map_from_light_groups(light_groups))),
94            #[cfg(test)]
95            update: None,
96        }
97    }
98
99    #[cfg(test)]
100    fn new_with_sender(light_groups: Vec<LightGroupFidl>, update: mpsc::Sender<Update>) -> Self {
101        Self {
102            backlight_brightness: Rc::new(RefCell::new(0.0)),
103            light_groups: Rc::new(RefCell::new(LightGroup::map_from_light_groups(light_groups))),
104            update: Some(RefCell::new(update)),
105        }
106    }
107
108    /// Spawn a local task to continuously watch for light group changes. Returns a tuple with a
109    /// [LedWatcherHandle], which can be used to get the current led states, and a task, which can
110    /// be used to track completion of the spawned task.
111    pub(crate) fn handle_light_groups_and_brightness_watch(
112        self,
113        light_proxy: LightProxy,
114        brightness_proxy: ControlProxy,
115        mut cancelation_rx: oneshot::Receiver<()>,
116        light_proxy_receives_initial_response: fuchsia_inspect::BoolProperty,
117        brightness_proxy_receives_initial_response: fuchsia_inspect::BoolProperty,
118    ) -> (LedWatcherHandle, fasync::Task<()>) {
119        let light_groups = Rc::clone(&self.light_groups);
120        let backlight_brightness = Rc::clone(&self.backlight_brightness);
121        let task = fasync::Task::local(async move {
122            let mut light_groups_stream =
123                HangingGetStream::new(light_proxy, LightProxy::watch_light_groups);
124            let mut brightness_stream =
125                HangingGetStream::new(brightness_proxy, ControlProxy::watch_current_brightness);
126            let mut light_group_fut = light_groups_stream.select_next_some();
127            let mut brightness_fut = brightness_stream.select_next_some();
128            loop {
129                futures::select! {
130                    watch_result = light_group_fut => {
131                        light_proxy_receives_initial_response.set(true);
132                        match watch_result {
133                            Ok(light_groups) => self.update_light_groups(light_groups),
134                            Err(e) => log::warn!("Failed to get light group update: {:?}", e),
135                        }
136                        light_group_fut = light_groups_stream.select_next_some()
137                    }
138                    watch_result = brightness_fut => {
139                        brightness_proxy_receives_initial_response.set(true);
140                        match watch_result {
141                            Ok(brightness) => self.update_brightness(brightness),
142                            Err(e) => log::warn!("Failed to get brightness update: {:?}", e),
143                        }
144                        brightness_fut = brightness_stream.select_next_some();
145                    }
146                    _ = cancelation_rx => break,
147                    complete => break,
148                }
149            }
150        });
151
152        (LedWatcherHandle { light_groups, backlight_brightness }, task)
153    }
154
155    fn update_light_groups(&self, light_groups: Vec<LightGroupFidl>) {
156        *self.light_groups.borrow_mut() = LightGroup::map_from_light_groups(light_groups);
157        #[cfg(test)]
158        if let Some(sender) = &self.update {
159            sender.borrow_mut().try_send(Update::LightGroups).expect("Failed to send update");
160        }
161    }
162
163    fn update_brightness(&self, brightness: f32) {
164        *self.backlight_brightness.borrow_mut() = brightness;
165        #[cfg(test)]
166        if let Some(sender) = &self.update {
167            sender.borrow_mut().try_send(Update::Brightness).expect("Failed to send update");
168        }
169    }
170}
171
172#[derive(Clone, Debug)]
173pub struct LedWatcherHandle {
174    light_groups: Rc<RefCell<HashMap<String, LightGroup>>>,
175    backlight_brightness: Rc<RefCell<f32>>,
176}
177
178impl LedState for LedWatcherHandle {
179    fn light_groups(&self) -> HashMap<String, LightGroup> {
180        Clone::clone(&*self.light_groups.borrow())
181    }
182
183    fn backlight_brightness(&self) -> f32 {
184        *self.backlight_brightness.borrow()
185    }
186}
187
188pub trait LedState {
189    fn light_groups(&self) -> HashMap<String, LightGroup>;
190    fn backlight_brightness(&self) -> f32;
191}
192
193pub struct CancelableTask {
194    inner: fasync::Task<()>,
195    cancelation_tx: oneshot::Sender<()>,
196}
197
198impl CancelableTask {
199    pub(crate) fn new(cancelation_tx: oneshot::Sender<()>, task: fasync::Task<()>) -> Self {
200        Self { cancelation_tx, inner: task }
201    }
202
203    /// Detach this task so it continues running independently in the background. The task will
204    /// no longer be cancelable.
205    pub fn detach(self) {
206        self.inner.detach();
207    }
208
209    /// Submit a cancelation request and wait for the task to end.
210    pub async fn cancel(self) {
211        // If the send fails, the watcher has already ended so there's no need to worry about the
212        // result.
213        let _ = self.cancelation_tx.send(());
214        self.inner.await
215    }
216}
217
218#[cfg(test)]
219mod led_watcher_tests;