use crate::base::{SettingInfo, SettingType};
use crate::config::default_settings::DefaultSetting;
use crate::handler::base::Request;
use crate::handler::setting_handler::persist::{controller as data_controller, ClientProxy};
use crate::handler::setting_handler::{
controller, ControllerError, ControllerStateResult, SettingHandlerResult,
};
use crate::input::MediaButtons;
use crate::light::light_hardware_configuration::DisableConditions;
use crate::light::types::{LightGroup, LightInfo, LightState, LightType, LightValue};
use crate::service_context::ExternalServiceProxy;
use crate::{call_async, LightHardwareConfiguration};
use async_trait::async_trait;
use fidl_fuchsia_hardware_light::{Info, LightMarker, LightProxy};
use fidl_fuchsia_settings_storage::LightGroups;
use futures::lock::Mutex;
use settings_storage::fidl_storage::{FidlStorage, FidlStorageConvertible};
use settings_storage::storage_factory::{NoneT, StorageAccess};
use std::collections::hash_map::Entry;
use std::collections::HashMap;
use std::rc::Rc;
pub(crate) const ARG_NAME: &str = "name";
pub(crate) const DEVICE_PATH: &str = "/dev/class/light/*";
impl FidlStorageConvertible for LightInfo {
type Storable = LightGroups;
type Loader = NoneT;
const KEY: &'static str = "light_info";
#[allow(clippy::redundant_closure)]
fn to_storable(self) -> Self::Storable {
LightGroups {
groups: self
.light_groups
.into_values()
.map(|group| fidl_fuchsia_settings::LightGroup::from(group))
.collect(),
}
}
fn from_storable(storable: Self::Storable) -> Self {
let light_groups = storable
.groups
.into_iter()
.map(|group| (group.name.clone().unwrap(), group.into()))
.collect();
Self { light_groups }
}
}
impl From<LightInfo> for SettingInfo {
fn from(info: LightInfo) -> SettingInfo {
SettingInfo::Light(info)
}
}
pub struct LightController {
client: ClientProxy,
light_proxy: ExternalServiceProxy<LightProxy>,
light_hardware_config: Option<LightHardwareConfiguration>,
data_cache: Rc<Mutex<Option<LightInfo>>>,
}
impl StorageAccess for LightController {
type Storage = FidlStorage;
type Data = LightInfo;
const STORAGE_KEY: &'static str = LightInfo::KEY;
}
#[async_trait(?Send)]
impl data_controller::CreateWithAsync for LightController {
type Data = Rc<Mutex<DefaultSetting<LightHardwareConfiguration, &'static str>>>;
async fn create_with(client: ClientProxy, data: Self::Data) -> Result<Self, ControllerError> {
let light_hardware_config = data.lock().await.load_default_value().map_err(|_| {
ControllerError::InitFailure("Invalid default light hardware config".into())
})?;
LightController::create_with_config(client, light_hardware_config).await
}
}
#[async_trait(?Send)]
impl controller::Handle for LightController {
async fn handle(&self, request: Request) -> Option<SettingHandlerResult> {
match request {
Request::Restore => {
Some(self.restore().await.map(|light_info| Some(SettingInfo::Light(light_info))))
}
Request::OnButton(MediaButtons { mic_mute: Some(mic_mute), .. }) => {
Some(self.on_mic_mute(mic_mute).await)
}
Request::SetLightGroupValue(name, state) => {
for light_state in &state {
if !light_state.is_finite() {
return Some(Err(ControllerError::InvalidArgument(
SettingType::Light,
"state".into(),
format!("{light_state:?}").into(),
)));
}
}
Some(self.set(name, state).await)
}
Request::Get => {
Some(self.restore().await.map(|light_info| Some(SettingInfo::Light(light_info))))
}
_ => None,
}
}
}
impl LightController {
pub(crate) async fn create_with_config(
client: ClientProxy,
light_hardware_config: Option<LightHardwareConfiguration>,
) -> Result<Self, ControllerError> {
let light_proxy = client
.get_service_context()
.connect_device_path::<LightMarker>(DEVICE_PATH)
.await
.map_err(|e| {
ControllerError::InitFailure(
format!("failed to connect to fuchsia.hardware.light with error: {e:?}").into(),
)
})?;
Ok(LightController {
client,
light_proxy,
light_hardware_config,
data_cache: Rc::new(Mutex::new(None)),
})
}
async fn set(&self, name: String, state: Vec<LightState>) -> SettingHandlerResult {
let id = fuchsia_trace::Id::new();
let mut light_info = self.data_cache.lock().await;
if light_info.is_none() {
drop(light_info);
let _ = self.restore().await?;
light_info = self.data_cache.lock().await;
}
let current = light_info
.as_mut()
.ok_or_else(|| ControllerError::UnexpectedError("missing data cache".into()))?;
let mut entry = match current.light_groups.entry(name.clone()) {
Entry::Vacant(_) => {
return Err(ControllerError::InvalidArgument(
SettingType::Light,
ARG_NAME.into(),
name.into(),
));
}
Entry::Occupied(entry) => entry,
};
let group = entry.get_mut();
if state.len() != group.lights.len() {
return Err(ControllerError::InvalidArgument(
SettingType::Light,
"state".into(),
format!("{state:?}").into(),
));
}
if !state.iter().filter_map(|state| state.value.clone()).all(|value| {
match group.light_type {
LightType::Brightness => matches!(value, LightValue::Brightness(_)),
LightType::Rgb => matches!(value, LightValue::Rgb(_)),
LightType::Simple => matches!(value, LightValue::Simple(_)),
}
}) {
return Err(ControllerError::InvalidArgument(
SettingType::Light,
"state".into(),
format!("{state:?}").into(),
));
}
self.write_light_group_to_hardware(group, &state).await?;
let _ = self.client.write_setting(current.clone().into(), id).await?;
Ok(Some(current.clone().into()))
}
async fn write_light_group_to_hardware(
&self,
group: &mut LightGroup,
state: &[LightState],
) -> ControllerStateResult {
for (i, (light, hardware_index)) in
state.iter().zip(group.hardware_index.iter()).enumerate()
{
let (set_result, method_name) = match light.clone().value {
None => continue,
Some(LightValue::Brightness(brightness)) => (
call_async!(self.light_proxy =>
set_brightness_value(*hardware_index, brightness))
.await,
"set_brightness_value",
),
Some(LightValue::Rgb(rgb)) => {
let value = rgb.clone().try_into().map_err(|_| {
ControllerError::InvalidArgument(
SettingType::Light,
"value".into(),
format!("{rgb:?}").into(),
)
})?;
(
call_async!(self.light_proxy =>
set_rgb_value(*hardware_index, & value))
.await,
"set_rgb_value",
)
}
Some(LightValue::Simple(on)) => (
call_async!(self.light_proxy => set_simple_value(*hardware_index, on)).await,
"set_simple_value",
),
};
set_result
.map_err(|e| format!("{e:?}"))
.and_then(|res| res.map_err(|e| format!("{e:?}")))
.map_err(|e| {
ControllerError::ExternalFailure(
SettingType::Light,
"fuchsia.hardware.light".into(),
format!("{method_name} for light {hardware_index}").into(),
e.into(),
)
})?;
group.lights[i] = light.clone();
}
Ok(())
}
async fn on_mic_mute(&self, mic_mute: bool) -> SettingHandlerResult {
let id = fuchsia_trace::Id::new();
let mut light_info = self.data_cache.lock().await;
if light_info.is_none() {
drop(light_info);
let _ = self.restore().await?;
light_info = self.data_cache.lock().await;
}
let current = light_info
.as_mut()
.ok_or_else(|| ControllerError::UnexpectedError("missing data cache".into()))?;
for light in current
.light_groups
.values_mut()
.filter(|l| l.disable_conditions.contains(&DisableConditions::MicSwitch))
{
light.enabled = mic_mute;
}
let _ = self.client.write_setting(current.clone().into(), id).await?;
Ok(Some(current.clone().into()))
}
async fn restore(&self) -> Result<LightInfo, ControllerError> {
let light_info = if let Some(config) = self.light_hardware_config.clone() {
self.restore_from_configuration(config).await
} else {
self.restore_from_hardware().await
}?;
let mut guard = self.data_cache.lock().await;
*guard = Some(light_info.clone());
Ok(light_info)
}
async fn restore_from_configuration(
&self,
config: LightHardwareConfiguration,
) -> Result<LightInfo, ControllerError> {
let id = fuchsia_trace::Id::new();
let current = self.client.read_setting::<LightInfo>(id).await;
let mut light_groups: HashMap<String, LightGroup> = HashMap::new();
for group_config in config.light_groups {
let mut light_state: Vec<LightState> = Vec::new();
for light_index in group_config.hardware_index.iter() {
light_state.push(
self.light_state_from_hardware_index(*light_index, group_config.light_type)
.await?,
);
}
let enabled = current
.light_groups
.get(&group_config.name)
.map(|found_group| found_group.enabled)
.unwrap_or(true);
let _ = light_groups.insert(
group_config.name.clone(),
LightGroup {
name: group_config.name,
enabled,
light_type: group_config.light_type,
lights: light_state,
hardware_index: group_config.hardware_index,
disable_conditions: group_config.disable_conditions,
},
);
}
Ok(LightInfo { light_groups })
}
async fn restore_from_hardware(&self) -> Result<LightInfo, ControllerError> {
let num_lights = call_async!(self.light_proxy => get_num_lights()).await.map_err(|e| {
ControllerError::ExternalFailure(
SettingType::Light,
"fuchsia.hardware.light".into(),
"get_num_lights".into(),
format!("{e:?}").into(),
)
})?;
let id = fuchsia_trace::Id::new();
let mut current = self.client.read_setting::<LightInfo>(id).await;
for i in 0..num_lights {
let info = call_async!(self.light_proxy => get_info(i))
.await
.map_err(|e| format!("{e:?}"))
.and_then(|res| res.map_err(|e| format!("{e:?}")))
.map_err(|e| {
ControllerError::ExternalFailure(
SettingType::Light,
"fuchsia.hardware.light".into(),
format!("get_info for light {i}").into(),
e.into(),
)
})?;
let (name, group) = self.light_info_to_group(i, info).await?;
let _ = current.light_groups.insert(name, group);
}
Ok(current)
}
async fn light_info_to_group(
&self,
index: u32,
info: Info,
) -> Result<(String, LightGroup), ControllerError> {
let light_type: LightType = info.capability.into();
let light_state = self.light_state_from_hardware_index(index, light_type).await?;
Ok((
info.name.clone(),
LightGroup {
name: info.name,
enabled: true,
light_type,
lights: vec![light_state],
hardware_index: vec![index],
disable_conditions: vec![],
},
))
}
async fn light_state_from_hardware_index(
&self,
index: u32,
light_type: LightType,
) -> Result<LightState, ControllerError> {
let value = match light_type {
LightType::Brightness => {
call_async!(self.light_proxy => get_current_brightness_value(index))
.await
.map_err(|e| format!("{e:?}"))
.and_then(|res| res.map_err(|e| format!("{e:?}")))
.map(LightValue::Brightness)
.map_err(|e| {
ControllerError::ExternalFailure(
SettingType::Light,
"fuchsia.hardware.light".into(),
format!("get_current_brightness_value for light {index}").into(),
e.into(),
)
})?
}
LightType::Rgb => call_async!(self.light_proxy => get_current_rgb_value(index))
.await
.map_err(|e| format!("{e:?}"))
.and_then(|res| res.map_err(|e| format!("{e:?}")))
.map(LightValue::from)
.map_err(|e| {
ControllerError::ExternalFailure(
SettingType::Light,
"fuchsia.hardware.light".into(),
format!("get_current_rgb_value for light {index}").into(),
e.into(),
)
})?,
LightType::Simple => call_async!(self.light_proxy => get_current_simple_value(index))
.await
.map_err(|e| format!("{e:?}"))
.and_then(|res| res.map_err(|e| format!("{e:?}")))
.map(LightValue::Simple)
.map_err(|e| {
ControllerError::ExternalFailure(
SettingType::Light,
"fuchsia.hardware.light".into(),
format!("get_current_simple_value for light {index}").into(),
e.into(),
)
})?,
};
Ok(LightState { value: Some(value) })
}
}
#[cfg(test)]
mod tests {
use crate::handler::setting_handler::persist::ClientProxy;
use crate::handler::setting_handler::ClientImpl;
use crate::light::types::{LightInfo, LightState, LightType, LightValue};
use crate::message::base::MessengerType;
use crate::storage::{Payload as StoragePayload, StorageRequest, StorageResponse};
use crate::tests::fakes::hardware_light_service::HardwareLightService;
use crate::tests::fakes::service_registry::ServiceRegistry;
use crate::{service, Address, LightController, ServiceContext, SettingType};
use futures::lock::Mutex;
use settings_storage::UpdateState;
use std::rc::Rc;
#[fuchsia::test(allow_stalls = false)]
async fn test_set_before_restore() {
let message_hub = service::MessageHub::create_hub();
let (controller_messenger, _) = message_hub
.create(MessengerType::Unbound)
.await
.expect("Unable to create agent messenger");
let service_registry = ServiceRegistry::create();
let light_service_handle = Rc::new(Mutex::new(HardwareLightService::new()));
service_registry.lock().await.register_service(light_service_handle.clone());
let service_context =
ServiceContext::new(Some(ServiceRegistry::serve(service_registry)), None);
light_service_handle
.lock()
.await
.insert_light(0, "light_1".to_string(), LightType::Simple, LightValue::Simple(false))
.await;
let signature = controller_messenger.get_signature();
let base_proxy = ClientImpl::for_test(
Default::default(),
controller_messenger,
signature,
Rc::new(service_context),
SettingType::Light,
);
let (_, mut storage_receptor) = message_hub
.create(MessengerType::Addressable(Address::Storage))
.await
.expect("Unable to create agent messenger");
fuchsia_async::Task::local(async move {
loop {
if let Ok((payload, message_client)) = storage_receptor.next_payload().await {
if let Ok(StoragePayload::Request(storage_request)) =
StoragePayload::try_from(payload)
{
match storage_request {
StorageRequest::Read(_, _) => {
let _ = message_client.reply(service::Payload::Storage(
StoragePayload::Response(StorageResponse::Read(
LightInfo::default().into(),
)),
));
}
StorageRequest::Write(_, _) => {
let _ = message_client.reply(service::Payload::Storage(
StoragePayload::Response(StorageResponse::Write(Ok(
UpdateState::Unchanged,
))),
));
}
}
}
}
}
})
.detach();
let client_proxy = ClientProxy::new(Rc::new(base_proxy), SettingType::Light).await;
let light_controller = LightController::create_with_config(client_proxy, None)
.await
.expect("Failed to create light controller");
let _ = light_controller
.set("light_1".to_string(), vec![LightState { value: Some(LightValue::Simple(true)) }])
.await
.expect("Set call failed");
let _ =
light_controller.data_cache.lock().await.as_ref().expect("Data cache is not populated");
}
#[fuchsia::test(allow_stalls = false)]
async fn test_on_mic_mute_before_restore() {
let message_hub = service::MessageHub::create_hub();
let (controller_messenger, _) = message_hub
.create(MessengerType::Unbound)
.await
.expect("Unable to create agent messenger");
let service_registry = ServiceRegistry::create();
let light_service_handle = Rc::new(Mutex::new(HardwareLightService::new()));
service_registry.lock().await.register_service(light_service_handle.clone());
let service_context =
ServiceContext::new(Some(ServiceRegistry::serve(service_registry)), None);
light_service_handle
.lock()
.await
.insert_light(0, "light_1".to_string(), LightType::Simple, LightValue::Simple(false))
.await;
let signature = controller_messenger.get_signature();
let base_proxy = ClientImpl::for_test(
Default::default(),
controller_messenger,
signature,
Rc::new(service_context),
SettingType::Light,
);
let (_, mut storage_receptor) = message_hub
.create(MessengerType::Addressable(Address::Storage))
.await
.expect("Unable to create agent messenger");
fuchsia_async::Task::local(async move {
loop {
if let Ok((payload, message_client)) = storage_receptor.next_payload().await {
if let Ok(StoragePayload::Request(storage_request)) =
StoragePayload::try_from(payload)
{
match storage_request {
StorageRequest::Read(_, _) => {
let _ = message_client.reply(service::Payload::Storage(
StoragePayload::Response(StorageResponse::Read(
LightInfo::default().into(),
)),
));
}
StorageRequest::Write(_, _) => {
let _ = message_client.reply(service::Payload::Storage(
StoragePayload::Response(StorageResponse::Write(Ok(
UpdateState::Unchanged,
))),
));
}
}
}
}
}
})
.detach();
let client_proxy = ClientProxy::new(Rc::new(base_proxy), SettingType::Light).await;
let light_controller = LightController::create_with_config(client_proxy, None)
.await
.expect("Failed to create light controller");
let _ = light_controller.on_mic_mute(false).await.expect("Set call failed");
let _ =
light_controller.data_cache.lock().await.as_ref().expect("Data cache is not populated");
}
}