1use crate::light_fidl_handler::{GroupPublisher, InfoPublisher};
6use crate::light_hardware_configuration::{DisableConditions, LightHardwareConfiguration};
7use crate::types::{LightGroup, LightInfo, LightState, LightType, LightValue};
8use anyhow::{Context, Error};
9use fidl_fuchsia_hardware_light::{Info, LightMarker, LightProxy};
10use fidl_fuchsia_settings_storage::LightGroups;
11use fuchsia_async as fasync;
12use futures::channel::mpsc::UnboundedReceiver;
13use futures::channel::oneshot::Sender;
14use futures::lock::Mutex;
15use futures::StreamExt;
16use settings_common::call_async;
17use settings_common::config::default_settings::DefaultSetting;
18use settings_common::inspect::event::{
19 ExternalEventPublisher, ResponseType, SettingValuePublisher,
20};
21use settings_common::service_context::{ExternalServiceProxy, ServiceContext};
22use settings_media_buttons::{Event, MediaButtons};
23use settings_storage::fidl_storage::{FidlStorage, FidlStorageConvertible};
24use settings_storage::storage_factory::{NoneT, StorageAccess, StorageFactory};
25use settings_storage::UpdateState;
26use std::borrow::Cow;
27use std::collections::hash_map::Entry;
28use std::collections::HashMap;
29use std::rc::Rc;
30
31pub(crate) const ARG_NAME: &str = "name";
34
35pub(crate) const DEVICE_PATH: &str = "/dev/class/light/*";
37
38impl FidlStorageConvertible for LightInfo {
39 type Storable = LightGroups;
40 type Loader = NoneT;
41 const KEY: &'static str = "light_info";
42
43 #[allow(clippy::redundant_closure)]
44 fn to_storable(self) -> Self::Storable {
45 LightGroups {
46 groups: self
47 .light_groups
48 .into_values()
49 .map(|group| fidl_fuchsia_settings::LightGroup::from(group))
50 .collect(),
51 }
52 }
53
54 fn from_storable(storable: Self::Storable) -> Self {
55 let light_groups = storable
57 .groups
58 .into_iter()
59 .map(|group| (group.name.clone().unwrap(), group.into()))
60 .collect();
61 Self { light_groups }
62 }
63}
64
65#[derive(thiserror::Error, Debug)]
66pub(crate) enum LightError {
67 #[error("Invalid input argument for Light setting: argument:{0:?} value:{1:?}")]
68 InvalidArgument(&'static str, String),
69 #[error(
70 "Call to an external dependency {0:?} for Light setting failed. \
71 Request:{1:?}: Error:{2}"
72 )]
73 ExternalFailure(&'static str, Cow<'static, str>, Cow<'static, str>),
74 #[error("Write failed for Light setting: {0:?}")]
75 WriteFailure(Error),
76 #[error("Unexpected error: {0:?}")]
77 UnexpectedError(&'static str),
78}
79
80impl From<&LightError> for ResponseType {
81 fn from(error: &LightError) -> Self {
82 match error {
83 LightError::InvalidArgument(..) => ResponseType::InvalidArgument,
84 LightError::ExternalFailure(..) => ResponseType::ExternalFailure,
85 LightError::WriteFailure(..) => ResponseType::StorageFailure,
86 LightError::UnexpectedError(..) => ResponseType::UnexpectedError,
87 }
88 }
89}
90
91pub struct LightController {
92 light_proxy: ExternalServiceProxy<LightProxy, ExternalEventPublisher>,
94
95 light_hardware_config: Option<LightHardwareConfiguration>,
99
100 data_cache: Rc<Mutex<Option<LightInfo>>>,
104
105 store: Rc<FidlStorage>,
107
108 publisher: Option<InfoPublisher>,
110
111 group_publishers: HashMap<String, GroupPublisher>,
113
114 setting_value_publisher: SettingValuePublisher<LightInfo>,
116}
117
118pub(crate) enum Request {
119 SetLightGroupValue(String, Vec<LightState>, Sender<Result<(), LightError>>),
120}
121
122impl StorageAccess for LightController {
123 type Storage = FidlStorage;
124 type Data = LightInfo;
125 const STORAGE_KEY: &'static str = LightInfo::KEY;
126}
127
128impl LightController {
130 pub(crate) async fn new<F>(
131 service_context: Rc<ServiceContext>,
132 default_setting: &mut DefaultSetting<LightHardwareConfiguration, &'static str>,
133 storage_factory: Rc<F>,
134 setting_value_publisher: SettingValuePublisher<LightInfo>,
135 external_publisher: ExternalEventPublisher,
136 ) -> Result<Self, Error>
137 where
138 F: StorageFactory<Storage = FidlStorage>,
139 {
140 let light_hardware_config =
141 default_setting.load_default_value().context("loading default value")?;
142
143 LightController::create_with_config(
144 service_context,
145 light_hardware_config,
146 &*storage_factory,
147 setting_value_publisher,
148 external_publisher,
149 )
150 .await
151 }
152
153 async fn create_with_config<F>(
155 service_context: Rc<ServiceContext>,
156 light_hardware_config: Option<LightHardwareConfiguration>,
157 storage_factory: &F,
158 setting_value_publisher: SettingValuePublisher<LightInfo>,
159 external_publisher: ExternalEventPublisher,
160 ) -> Result<Self, Error>
161 where
162 F: StorageFactory<Storage = FidlStorage>,
163 {
164 let light_proxy = service_context
165 .connect_device_path::<LightMarker, _>(DEVICE_PATH, external_publisher)
166 .await
167 .context("connecting to fuchsia.hardware.light")?;
168
169 Ok(LightController {
170 light_proxy,
171 light_hardware_config,
172 data_cache: Rc::new(Mutex::new(None)),
173 store: storage_factory.get_store().await,
174 publisher: None,
175 group_publishers: HashMap::new(),
176 setting_value_publisher,
177 })
178 }
179
180 pub(crate) fn register_publishers(
181 &mut self,
182 publisher: InfoPublisher,
183 group_publishers: HashMap<String, GroupPublisher>,
184 ) {
185 self.publisher = Some(publisher);
186 self.group_publishers = group_publishers;
187 }
188
189 fn publish(&self, info: LightInfo) {
190 let _ = self.setting_value_publisher.publish(&info);
191 let pg = info.light_groups.iter().filter_map(|(key, group)| {
192 self.group_publishers.get(key).map(|publisher| (publisher, group))
193 });
194 for (publisher, group) in pg {
195 publisher.update(|old_group| {
196 let Some(old_group) = old_group.as_mut() else {
197 *old_group = Some(group.clone());
198 return true;
199 };
200
201 if *old_group != *group {
202 *old_group = group.clone();
203 return true;
204 }
205 false
206 });
207 }
208
209 if let Some(publisher) = self.publisher.as_ref() {
210 publisher.set(info);
211 }
212 }
213
214 pub(crate) async fn handle(
215 self,
216 mut event_rx: UnboundedReceiver<(Event, super::ResultSender)>,
217 mut request_rx: UnboundedReceiver<Request>,
218 ) -> Result<fasync::Task<()>, LightError> {
219 Ok(fasync::Task::local(async move {
220 let mut next_event = event_rx.next();
221 let mut next_request = request_rx.next();
222 'request: loop {
223 futures::select! {
224 event = next_event => {
225 let Some((Event::OnButton(media_buttons), response_tx)) = event else {
226 continue;
227 };
228 next_event = event_rx.next();
229 let res = if let MediaButtons { mic_mute: Some(mic_mute), .. } = media_buttons {
230 self.on_mic_mute(mic_mute).await.map(|res| res.map(|info| self.publish(info)))
231 } else {
232 Ok(None)
233 };
234 let _ = response_tx.send(res);
235 }
236 request = next_request => {
237 let Some(Request::SetLightGroupValue(name, state, tx)) = request else {
238 continue;
239 };
240 next_request = request_rx.next();
241 for light_state in &state {
243 if !light_state.is_finite() {
244 let _ = tx.send(Err(LightError::InvalidArgument(
245 "state",
246 format!("{light_state:?}"),
247 )));
248 continue 'request;
249 }
250 }
251
252 match self.set(name, state).await {
253 Ok(info) => {
254 if let Some(info) = info {
255 self.publish(info);
256 }
257 let _ = tx.send(Ok(()));
258 }
259 Err(e) => {
260 let _ = tx.send(Err(e));
261 }
262 }
263 }
264 }
265 }
266 }))
267 }
268
269 async fn set(
270 &self,
271 name: String,
272 state: Vec<LightState>,
273 ) -> Result<Option<LightInfo>, LightError> {
274 let mut light_info = self.data_cache.lock().await;
275 if light_info.is_none() {
277 drop(light_info);
278 let _ = self.restore().await?;
279 light_info = self.data_cache.lock().await;
280 }
281
282 let current =
283 light_info.as_mut().ok_or_else(|| LightError::UnexpectedError("missing data cache"))?;
284 let mut entry = match current.light_groups.entry(name.clone()) {
285 Entry::Vacant(_) => {
286 return Err(LightError::InvalidArgument(ARG_NAME, name));
288 }
289 Entry::Occupied(entry) => entry,
290 };
291
292 let group = entry.get_mut();
293
294 if state.len() != group.lights.len() {
295 return Err(LightError::InvalidArgument("state", format!("{state:?}")));
298 }
299
300 if !state.iter().filter_map(|state| state.value.clone()).all(|value| {
301 match group.light_type {
302 LightType::Brightness => matches!(value, LightValue::Brightness(_)),
303 LightType::Rgb => matches!(value, LightValue::Rgb(_)),
304 LightType::Simple => matches!(value, LightValue::Simple(_)),
305 }
306 }) {
307 return Err(LightError::InvalidArgument("state", format!("{state:?}")));
310 }
311
312 self.write_light_group_to_hardware(group, &state).await?;
314
315 self.store
316 .write(current.clone())
317 .await
318 .map(|state| match state {
319 UpdateState::Unchanged => None,
320 UpdateState::Updated => Some(current.clone()),
321 })
322 .map_err(|e| LightError::WriteFailure(e.context("writing light on set")))
323 }
324
325 async fn write_light_group_to_hardware(
329 &self,
330 group: &mut LightGroup,
331 state: &[LightState],
332 ) -> Result<(), LightError> {
333 for (i, (light, hardware_index)) in
334 state.iter().zip(group.hardware_index.iter()).enumerate()
335 {
336 let (set_result, method_name) = match light.clone().value {
337 None => continue,
340 Some(LightValue::Brightness(brightness)) => (
341 call_async!(self.light_proxy =>
342 set_brightness_value(*hardware_index, brightness))
343 .await,
344 "set_brightness_value",
345 ),
346 Some(LightValue::Rgb(rgb)) => {
347 let value = rgb
348 .clone()
349 .try_into()
350 .map_err(|_| LightError::InvalidArgument("value", format!("{rgb:?}")))?;
351 (
352 call_async!(self.light_proxy =>
353 set_rgb_value(*hardware_index, & value))
354 .await,
355 "set_rgb_value",
356 )
357 }
358 Some(LightValue::Simple(on)) => (
359 call_async!(self.light_proxy => set_simple_value(*hardware_index, on)).await,
360 "set_simple_value",
361 ),
362 };
363 set_result
364 .map_err(|e| format!("{e:?}"))
365 .and_then(|res| res.map_err(|e| format!("{e:?}")))
366 .map_err(|e| {
367 LightError::ExternalFailure(
368 "fuchsia.hardware.light",
369 Cow::Owned(format!("{method_name} for light {hardware_index}")),
370 Cow::Owned(e),
371 )
372 })?;
373
374 group.lights[i] = light.clone();
376 }
377 Ok(())
378 }
379
380 async fn on_mic_mute(&self, mic_mute: bool) -> Result<Option<LightInfo>, LightError> {
381 let mut light_info = self.data_cache.lock().await;
382 if light_info.is_none() {
383 drop(light_info);
384 let _ = self.restore().await?;
385 light_info = self.data_cache.lock().await;
386 }
387
388 let current =
389 light_info.as_mut().ok_or_else(|| LightError::UnexpectedError("missing data cache"))?;
390 for light in current
391 .light_groups
392 .values_mut()
393 .filter(|l| l.disable_conditions.contains(&DisableConditions::MicSwitch))
394 {
395 light.enabled = mic_mute;
398 }
399
400 self.store
401 .write(current.clone())
402 .await
403 .map(|state| match state {
404 UpdateState::Unchanged => None,
405 UpdateState::Updated => Some(current.clone()),
406 })
407 .map_err(|e| LightError::WriteFailure(e.context("writing light on mic mute")))
408 }
409
410 pub(crate) async fn restore(&self) -> Result<LightInfo, LightError> {
411 let light_info = if let Some(config) = self.light_hardware_config.clone() {
412 self.restore_from_configuration(config).await
414 } else {
415 self.restore_from_hardware().await
417 }?;
418 let mut guard = self.data_cache.lock().await;
419 *guard = Some(light_info.clone());
420 Ok(light_info)
421 }
422
423 async fn restore_from_configuration(
427 &self,
428 config: LightHardwareConfiguration,
429 ) -> Result<LightInfo, LightError> {
430 let current = self.store.get::<LightInfo>().await;
431 let mut light_groups: HashMap<String, LightGroup> = HashMap::new();
432 for group_config in config.light_groups {
433 let mut light_state: Vec<LightState> = Vec::new();
434
435 for light_index in group_config.hardware_index.iter() {
438 light_state.push(
439 self.light_state_from_hardware_index(*light_index, group_config.light_type)
440 .await?,
441 );
442 }
443
444 let enabled = current
446 .light_groups
447 .get(&group_config.name)
448 .map(|found_group| found_group.enabled)
449 .unwrap_or(true);
450
451 let _ = light_groups.insert(
452 group_config.name.clone(),
453 LightGroup {
454 name: group_config.name,
455 enabled,
456 light_type: group_config.light_type,
457 lights: light_state,
458 hardware_index: group_config.hardware_index,
459 disable_conditions: group_config.disable_conditions,
460 },
461 );
462 }
463
464 Ok(LightInfo { light_groups })
465 }
466
467 async fn restore_from_hardware(&self) -> Result<LightInfo, LightError> {
472 let num_lights = call_async!(self.light_proxy => get_num_lights()).await.map_err(|e| {
473 LightError::ExternalFailure(
474 "fuchsia.hardware.light",
475 Cow::Borrowed("get_num_lights"),
476 Cow::Owned(format!("{e:?}")),
477 )
478 })?;
479
480 let mut current = self.store.get::<LightInfo>().await;
481 for i in 0..num_lights {
482 let info = call_async!(self.light_proxy => get_info(i))
483 .await
484 .map_err(|e| format!("{e:?}"))
485 .and_then(|res| res.map_err(|e| format!("{e:?}")))
486 .map_err(|e| {
487 LightError::ExternalFailure(
488 "fuchsia.hardware.light",
489 Cow::Owned(format!("get_info for light {i}")),
490 Cow::Owned(e),
491 )
492 })?;
493 let (name, group) = self.light_info_to_group(i, info).await?;
494 let _ = current.light_groups.insert(name, group);
495 }
496
497 Ok(current)
498 }
499
500 async fn light_info_to_group(
503 &self,
504 index: u32,
505 info: Info,
506 ) -> Result<(String, LightGroup), LightError> {
507 let light_type: LightType = info.capability.into();
508
509 let light_state = self.light_state_from_hardware_index(index, light_type).await?;
510
511 Ok((
512 info.name.clone(),
513 LightGroup {
514 name: info.name,
515 enabled: true,
517 light_type,
518 lights: vec![light_state],
519 hardware_index: vec![index],
520 disable_conditions: vec![],
521 },
522 ))
523 }
524
525 async fn light_state_from_hardware_index(
528 &self,
529 index: u32,
530 light_type: LightType,
531 ) -> Result<LightState, LightError> {
532 let value = match light_type {
534 LightType::Brightness => {
535 call_async!(self.light_proxy => get_current_brightness_value(index))
536 .await
537 .map_err(|e| format!("{e:?}"))
538 .and_then(|res| res.map_err(|e| format!("{e:?}")))
539 .map(LightValue::Brightness)
540 .map_err(|e| {
541 LightError::ExternalFailure(
542 "fuchsia.hardware.light",
543 Cow::Owned(format!("get_current_brightness_value for light {index}")),
544 Cow::Owned(e),
545 )
546 })?
547 }
548 LightType::Rgb => call_async!(self.light_proxy => get_current_rgb_value(index))
549 .await
550 .map_err(|e| format!("{e:?}"))
551 .and_then(|res| res.map_err(|e| format!("{e:?}")))
552 .map(LightValue::from)
553 .map_err(|e| {
554 LightError::ExternalFailure(
555 "fuchsia.hardware.light",
556 Cow::Owned(format!("get_current_rgb_value for light {index}")),
557 Cow::Owned(e),
558 )
559 })?,
560 LightType::Simple => call_async!(self.light_proxy => get_current_simple_value(index))
561 .await
562 .map_err(|e| format!("{e:?}"))
563 .and_then(|res| res.map_err(|e| format!("{e:?}")))
564 .map(LightValue::Simple)
565 .map_err(|e| {
566 LightError::ExternalFailure(
567 "fuchsia.hardware.light",
568 Cow::Owned(format!("get_current_simple_value for light {index}")),
569 Cow::Owned(e),
570 )
571 })?,
572 };
573
574 Ok(LightState { value: Some(value) })
575 }
576}
577
578#[cfg(test)]
579mod tests {
580 use futures::channel::mpsc;
581
582 use super::*;
583 use crate::light_fidl_handler::LightFidlHandler;
584 use crate::test_fakes::hardware_light_service::HardwareLightService;
585 use settings_test_common::fakes::service::ServiceRegistry;
586 use settings_test_common::storage::InMemoryFidlStorageFactory;
587
588 #[fuchsia::test()]
591 async fn test_set_before_restore() {
592 let service_registry = ServiceRegistry::create();
595 let light_service_handle = Rc::new(Mutex::new(HardwareLightService::new()));
596 service_registry.lock().await.register_service(light_service_handle.clone());
597
598 let service_context = ServiceContext::new(Some(ServiceRegistry::serve(service_registry)));
599
600 light_service_handle
602 .lock()
603 .await
604 .insert_light(0, "light_1".to_string(), LightType::Simple, LightValue::Simple(false))
605 .await;
606
607 let storage_factory = InMemoryFidlStorageFactory::new();
608 storage_factory.initialize_storage::<LightInfo>().await;
609
610 let (tx, _rx) = mpsc::unbounded();
611 let setting_value_publisher = SettingValuePublisher::new(tx);
612 let (tx, _rx) = mpsc::unbounded();
613 let event_publisher = ExternalEventPublisher::new(tx);
614
615 let mut light_controller = LightController::create_with_config(
617 Rc::new(service_context),
618 None,
619 &storage_factory,
620 setting_value_publisher,
621 event_publisher,
622 )
623 .await
624 .expect("Failed to create light controller");
625 let info = light_controller.restore().await.unwrap();
626 let (info_hanging_get, group_hanging_gets) = LightFidlHandler::build_hanging_gets(info);
627 light_controller.register_publishers(
628 info_hanging_get.new_publisher(),
629 group_hanging_gets
630 .iter()
631 .map(|(key, hanging_get)| (key.clone(), hanging_get.new_publisher()))
632 .collect(),
633 );
634
635 let _ = light_controller
637 .set("light_1".to_string(), vec![LightState { value: Some(LightValue::Simple(true)) }])
638 .await
639 .expect("Set call failed");
640
641 let _ =
643 light_controller.data_cache.lock().await.as_ref().expect("Data cache is not populated");
644 }
645
646 #[fuchsia::test()]
649 async fn test_on_mic_mute_before_restore() {
650 let service_registry = ServiceRegistry::create();
653 let light_service_handle = Rc::new(Mutex::new(HardwareLightService::new()));
654 service_registry.lock().await.register_service(light_service_handle.clone());
655
656 let service_context = ServiceContext::new(Some(ServiceRegistry::serve(service_registry)));
657
658 light_service_handle
660 .lock()
661 .await
662 .insert_light(0, "light_1".to_string(), LightType::Simple, LightValue::Simple(false))
663 .await;
664
665 let storage_factory = InMemoryFidlStorageFactory::new();
666 storage_factory.initialize_storage::<LightInfo>().await;
667
668 let (tx, _rx) = mpsc::unbounded();
669 let setting_value_publisher = SettingValuePublisher::new(tx);
670 let (tx, _rx) = mpsc::unbounded();
671 let event_publisher = ExternalEventPublisher::new(tx);
672
673 let mut light_controller = LightController::create_with_config(
675 Rc::new(service_context),
676 None,
677 &storage_factory,
678 setting_value_publisher,
679 event_publisher,
680 )
681 .await
682 .expect("Failed to create light controller");
683 let info = light_controller.restore().await.unwrap();
684 let (info_hanging_get, group_hanging_gets) = LightFidlHandler::build_hanging_gets(info);
685 light_controller.register_publishers(
686 info_hanging_get.new_publisher(),
687 group_hanging_gets
688 .iter()
689 .map(|(key, hanging_get)| (key.clone(), hanging_get.new_publisher()))
690 .collect(),
691 );
692
693 let _ = light_controller.on_mic_mute(false).await.expect("Set call failed");
695
696 let _ =
698 light_controller.data_cache.lock().await.as_ref().expect("Data cache is not populated");
699 }
700}