Skip to main content

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