use anyhow::Error;
use serde::{Deserialize, Serialize};
use crate::base::{Merge, SettingInfo, SettingType};
use crate::call;
use crate::config::default_settings::DefaultSetting;
use crate::display::display_configuration::{
ConfigurationThemeMode, ConfigurationThemeType, DisplayConfiguration,
};
use crate::display::types::{DisplayInfo, LowLightMode, Theme, ThemeBuilder, ThemeMode, ThemeType};
use crate::handler::base::Request;
use crate::handler::setting_handler::persist::{controller as data_controller, ClientProxy};
use crate::handler::setting_handler::{
controller, ControllerError, IntoHandlerResult, SettingHandlerResult,
};
use crate::service_context::ExternalServiceProxy;
use async_trait::async_trait;
use fidl_fuchsia_ui_brightness::{
ControlMarker as BrightnessControlMarker, ControlProxy as BrightnessControlProxy,
};
use fuchsia_trace as ftrace;
use settings_storage::device_storage::{DeviceStorage, DeviceStorageCompatible};
use settings_storage::storage_factory::{DefaultLoader, NoneT, StorageAccess};
use settings_storage::UpdateState;
use std::sync::Mutex;
pub(super) const DEFAULT_MANUAL_BRIGHTNESS_VALUE: f32 = 0.5;
pub(super) const DEFAULT_AUTO_BRIGHTNESS_VALUE: f32 = 0.5;
pub(crate) const DEFAULT_DISPLAY_INFO: DisplayInfo = DisplayInfo::new(
false, DEFAULT_MANUAL_BRIGHTNESS_VALUE, DEFAULT_AUTO_BRIGHTNESS_VALUE, true, LowLightMode::Disable, None, );
pub struct DisplayInfoLoader {
display_configuration: Mutex<DefaultSetting<DisplayConfiguration, &'static str>>,
}
impl DisplayInfoLoader {
pub(crate) fn new(default_setting: DefaultSetting<DisplayConfiguration, &'static str>) -> Self {
Self { display_configuration: Mutex::new(default_setting) }
}
}
impl DefaultLoader for DisplayInfoLoader {
type Result = DisplayInfo;
fn default_value(&self) -> Self::Result {
let mut default_display_info = DEFAULT_DISPLAY_INFO;
if let Ok(Some(display_configuration)) =
self.display_configuration.lock().unwrap().get_cached_value()
{
default_display_info.theme = Some(Theme {
theme_type: Some(match display_configuration.theme.theme_type {
ConfigurationThemeType::Light => ThemeType::Light,
}),
theme_mode: if display_configuration
.theme
.theme_mode
.contains(&ConfigurationThemeMode::Auto)
{
ThemeMode::AUTO
} else {
ThemeMode::empty()
},
});
}
default_display_info
}
}
impl DeviceStorageCompatible for DisplayInfo {
type Loader = DisplayInfoLoader;
const KEY: &'static str = "display_info";
fn try_deserialize_from(value: &str) -> Result<Self, Error> {
Self::extract(value).or_else(|_| DisplayInfoV5::try_deserialize_from(value).map(Self::from))
}
}
impl From<DisplayInfo> for SettingInfo {
fn from(info: DisplayInfo) -> SettingInfo {
SettingInfo::Brightness(info)
}
}
impl From<DisplayInfoV5> for DisplayInfo {
fn from(v5: DisplayInfoV5) -> Self {
DisplayInfo {
auto_brightness: v5.auto_brightness,
auto_brightness_value: DEFAULT_AUTO_BRIGHTNESS_VALUE,
manual_brightness_value: v5.manual_brightness_value,
screen_enabled: v5.screen_enabled,
low_light_mode: v5.low_light_mode,
theme: v5.theme,
}
}
}
#[async_trait(?Send)]
pub(crate) trait BrightnessManager: Sized {
async fn from_client(client: &ClientProxy) -> Result<Self, ControllerError>;
async fn update_brightness(
&self,
info: DisplayInfo,
client: &ClientProxy,
always_send: bool,
) -> SettingHandlerResult;
}
#[async_trait(?Send)]
impl BrightnessManager for () {
async fn from_client(_: &ClientProxy) -> Result<Self, ControllerError> {
Ok(())
}
async fn update_brightness(
&self,
info: DisplayInfo,
client: &ClientProxy,
_: bool,
) -> SettingHandlerResult {
let id = ftrace::Id::new();
if !info.is_finite() {
return Err(ControllerError::InvalidArgument(
SettingType::Display,
"display_info".into(),
format!("{info:?}").into(),
));
}
client.write_setting(info.into(), id).await.into_handler_result()
}
}
pub(crate) struct ExternalBrightnessControl {
brightness_service: ExternalServiceProxy<BrightnessControlProxy>,
}
#[async_trait(?Send)]
impl BrightnessManager for ExternalBrightnessControl {
async fn from_client(client: &ClientProxy) -> Result<Self, ControllerError> {
client
.get_service_context()
.connect::<BrightnessControlMarker>()
.await
.map(|brightness_service| Self { brightness_service })
.map_err(|_| {
ControllerError::InitFailure("could not connect to brightness service".into())
})
}
async fn update_brightness(
&self,
info: DisplayInfo,
client: &ClientProxy,
always_send: bool,
) -> SettingHandlerResult {
let id = ftrace::Id::new();
if !info.is_finite() {
return Err(ControllerError::InvalidArgument(
SettingType::Display,
"display_info".into(),
format!("{info:?}").into(),
));
}
let update_state = client.write_setting(info.into(), id).await?;
if update_state == UpdateState::Unchanged && !always_send {
return Ok(None);
}
if info.auto_brightness {
call!(self.brightness_service => set_auto_brightness())
} else {
call!(self.brightness_service => set_manual_brightness(info.manual_brightness_value))
}
.map(|_| None)
.map_err(|e| {
ControllerError::ExternalFailure(
SettingType::Display,
"brightness_service".into(),
"set_brightness".into(),
format!("{e:?}").into(),
)
})
}
}
pub(crate) struct DisplayController<T = ()>
where
T: BrightnessManager,
{
client: ClientProxy,
brightness_manager: T,
}
impl<T> StorageAccess for DisplayController<T>
where
T: BrightnessManager,
{
type Storage = DeviceStorage;
type Data = DisplayInfo;
const STORAGE_KEY: &'static str = DisplayInfo::KEY;
}
#[async_trait(?Send)]
impl<T> data_controller::Create for DisplayController<T>
where
T: BrightnessManager,
{
async fn create(client: ClientProxy) -> Result<Self, ControllerError> {
let brightness_manager = <T as BrightnessManager>::from_client(&client).await?;
Ok(Self { client, brightness_manager })
}
}
#[async_trait(?Send)]
impl<T> controller::Handle for DisplayController<T>
where
T: BrightnessManager,
{
async fn handle(&self, request: Request) -> Option<SettingHandlerResult> {
match request {
Request::Restore => {
let display_info = self.client.read_setting::<DisplayInfo>(ftrace::Id::new()).await;
assert!(display_info.is_finite());
Some(
self.brightness_manager
.update_brightness(display_info, &self.client, true)
.await,
)
}
Request::SetDisplayInfo(mut set_display_info) => {
let display_info = self.client.read_setting::<DisplayInfo>(ftrace::Id::new()).await;
assert!(display_info.is_finite());
if let Some(theme) = set_display_info.theme {
set_display_info.theme = self.build_theme(theme, &display_info);
}
Some(
self.brightness_manager
.update_brightness(
display_info.merge(set_display_info),
&self.client,
false,
)
.await,
)
}
Request::Get => Some(
self.client
.read_setting_info::<DisplayInfo>(ftrace::Id::new())
.await
.into_handler_result(),
),
_ => None,
}
}
}
impl<T> DisplayController<T>
where
T: BrightnessManager,
{
fn build_theme(&self, incoming_theme: Theme, display_info: &DisplayInfo) -> Option<Theme> {
let existing_theme_type = display_info.theme.and_then(|theme| theme.theme_type);
let new_theme_type = incoming_theme.theme_type.or(existing_theme_type);
ThemeBuilder::new()
.set_theme_type(new_theme_type)
.set_theme_mode(incoming_theme.theme_mode)
.build()
}
}
#[derive(PartialEq, Debug, Clone, Copy, Serialize, Deserialize)]
pub struct DisplayInfoV1 {
pub manual_brightness_value: f32,
pub auto_brightness: bool,
pub low_light_mode: LowLightMode,
}
impl DisplayInfoV1 {
const fn new(
auto_brightness: bool,
manual_brightness_value: f32,
low_light_mode: LowLightMode,
) -> DisplayInfoV1 {
DisplayInfoV1 { manual_brightness_value, auto_brightness, low_light_mode }
}
}
impl DeviceStorageCompatible for DisplayInfoV1 {
type Loader = NoneT;
const KEY: &'static str = "display_infoV1";
}
impl Default for DisplayInfoV1 {
fn default() -> Self {
DisplayInfoV1::new(
false, DEFAULT_MANUAL_BRIGHTNESS_VALUE, LowLightMode::Disable, )
}
}
#[derive(PartialEq, Debug, Clone, Copy, Serialize, Deserialize)]
pub struct DisplayInfoV2 {
pub manual_brightness_value: f32,
pub auto_brightness: bool,
pub low_light_mode: LowLightMode,
pub theme_mode: ThemeModeV1,
}
impl DisplayInfoV2 {
const fn new(
auto_brightness: bool,
manual_brightness_value: f32,
low_light_mode: LowLightMode,
theme_mode: ThemeModeV1,
) -> DisplayInfoV2 {
DisplayInfoV2 { manual_brightness_value, auto_brightness, low_light_mode, theme_mode }
}
}
impl DeviceStorageCompatible for DisplayInfoV2 {
type Loader = NoneT;
const KEY: &'static str = "display_infoV2";
fn try_deserialize_from(value: &str) -> Result<Self, Error> {
Self::extract(value).or_else(|_| DisplayInfoV1::try_deserialize_from(value).map(Self::from))
}
}
impl Default for DisplayInfoV2 {
fn default() -> Self {
DisplayInfoV2::new(
false, DEFAULT_MANUAL_BRIGHTNESS_VALUE, LowLightMode::Disable, ThemeModeV1::Unknown, )
}
}
impl From<DisplayInfoV1> for DisplayInfoV2 {
fn from(v1: DisplayInfoV1) -> Self {
DisplayInfoV2 {
auto_brightness: v1.auto_brightness,
manual_brightness_value: v1.manual_brightness_value,
low_light_mode: v1.low_light_mode,
theme_mode: ThemeModeV1::Unknown,
}
}
}
#[derive(PartialEq, Debug, Clone, Copy, Serialize, Deserialize)]
pub enum ThemeModeV1 {
Unknown,
Default,
Light,
Dark,
Auto,
}
impl From<ThemeModeV1> for ThemeType {
fn from(theme_mode_v1: ThemeModeV1) -> Self {
match theme_mode_v1 {
ThemeModeV1::Default => ThemeType::Default,
ThemeModeV1::Light => ThemeType::Light,
ThemeModeV1::Dark => ThemeType::Dark,
ThemeModeV1::Unknown | ThemeModeV1::Auto => ThemeType::Unknown,
}
}
}
#[derive(PartialEq, Debug, Clone, Copy, Serialize, Deserialize)]
pub struct DisplayInfoV3 {
pub manual_brightness_value: f32,
pub auto_brightness: bool,
pub screen_enabled: bool,
pub low_light_mode: LowLightMode,
pub theme_mode: ThemeModeV1,
}
impl DisplayInfoV3 {
const fn new(
auto_brightness: bool,
manual_brightness_value: f32,
screen_enabled: bool,
low_light_mode: LowLightMode,
theme_mode: ThemeModeV1,
) -> DisplayInfoV3 {
DisplayInfoV3 {
manual_brightness_value,
auto_brightness,
screen_enabled,
low_light_mode,
theme_mode,
}
}
}
impl DeviceStorageCompatible for DisplayInfoV3 {
type Loader = NoneT;
const KEY: &'static str = "display_info";
fn try_deserialize_from(value: &str) -> Result<Self, Error> {
Self::extract(value).or_else(|_| DisplayInfoV2::try_deserialize_from(value).map(Self::from))
}
}
impl Default for DisplayInfoV3 {
fn default() -> Self {
DisplayInfoV3::new(
false, DEFAULT_MANUAL_BRIGHTNESS_VALUE, true, LowLightMode::Disable, ThemeModeV1::Unknown, )
}
}
impl From<DisplayInfoV2> for DisplayInfoV3 {
fn from(v2: DisplayInfoV2) -> Self {
DisplayInfoV3 {
auto_brightness: v2.auto_brightness,
manual_brightness_value: v2.manual_brightness_value,
screen_enabled: true,
low_light_mode: v2.low_light_mode,
theme_mode: v2.theme_mode,
}
}
}
#[derive(PartialEq, Debug, Clone, Copy, Serialize, Deserialize)]
pub struct DisplayInfoV4 {
pub manual_brightness_value: f32,
pub auto_brightness: bool,
pub screen_enabled: bool,
pub low_light_mode: LowLightMode,
pub theme_type: ThemeType,
}
impl DisplayInfoV4 {
const fn new(
auto_brightness: bool,
manual_brightness_value: f32,
screen_enabled: bool,
low_light_mode: LowLightMode,
theme_type: ThemeType,
) -> DisplayInfoV4 {
DisplayInfoV4 {
manual_brightness_value,
auto_brightness,
screen_enabled,
low_light_mode,
theme_type,
}
}
}
impl From<DisplayInfoV3> for DisplayInfoV4 {
fn from(v3: DisplayInfoV3) -> Self {
DisplayInfoV4 {
auto_brightness: v3.auto_brightness,
manual_brightness_value: v3.manual_brightness_value,
screen_enabled: v3.screen_enabled,
low_light_mode: v3.low_light_mode,
theme_type: ThemeType::from(v3.theme_mode),
}
}
}
impl DeviceStorageCompatible for DisplayInfoV4 {
type Loader = NoneT;
const KEY: &'static str = "display_info";
fn try_deserialize_from(value: &str) -> Result<Self, Error> {
Self::extract(value).or_else(|_| DisplayInfoV3::try_deserialize_from(value).map(Self::from))
}
}
impl Default for DisplayInfoV4 {
fn default() -> Self {
DisplayInfoV4::new(
false, DEFAULT_MANUAL_BRIGHTNESS_VALUE, true, LowLightMode::Disable, ThemeType::Unknown, )
}
}
#[derive(PartialEq, Debug, Clone, Copy, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct DisplayInfoV5 {
pub manual_brightness_value: f32,
pub auto_brightness: bool,
pub screen_enabled: bool,
pub low_light_mode: LowLightMode,
pub theme: Option<Theme>,
}
impl DisplayInfoV5 {
const fn new(
auto_brightness: bool,
manual_brightness_value: f32,
screen_enabled: bool,
low_light_mode: LowLightMode,
theme: Option<Theme>,
) -> DisplayInfoV5 {
DisplayInfoV5 {
manual_brightness_value,
auto_brightness,
screen_enabled,
low_light_mode,
theme,
}
}
}
impl From<DisplayInfoV4> for DisplayInfoV5 {
fn from(v4: DisplayInfoV4) -> Self {
DisplayInfoV5 {
auto_brightness: v4.auto_brightness,
manual_brightness_value: v4.manual_brightness_value,
screen_enabled: v4.screen_enabled,
low_light_mode: v4.low_light_mode,
theme: Some(Theme::new(Some(v4.theme_type), ThemeMode::empty())),
}
}
}
impl DeviceStorageCompatible for DisplayInfoV5 {
type Loader = NoneT;
const KEY: &'static str = "display_info";
fn try_deserialize_from(value: &str) -> Result<Self, Error> {
Self::extract(value).or_else(|_| DisplayInfoV4::try_deserialize_from(value).map(Self::from))
}
}
impl Default for DisplayInfoV5 {
fn default() -> Self {
DisplayInfoV5::new(
false, DEFAULT_MANUAL_BRIGHTNESS_VALUE, true, LowLightMode::Disable, Some(Theme::new(Some(ThemeType::Unknown), ThemeMode::empty())), )
}
}
#[cfg(test)]
mod tests {
use super::*;
#[fuchsia::test]
fn test_display_migration_v1_to_v2() {
let v1 = DisplayInfoV1 {
manual_brightness_value: 0.6,
auto_brightness: true,
low_light_mode: LowLightMode::Enable,
};
let serialized_v1 = v1.serialize_to();
let v2 = DisplayInfoV2::try_deserialize_from(&serialized_v1)
.expect("deserialization should succeed");
assert_eq!(
v2,
DisplayInfoV2 {
manual_brightness_value: v1.manual_brightness_value,
auto_brightness: v1.auto_brightness,
low_light_mode: v1.low_light_mode,
theme_mode: DisplayInfoV2::default().theme_mode,
}
);
}
#[fuchsia::test]
fn test_display_migration_v2_to_v3() {
let v2 = DisplayInfoV2 {
manual_brightness_value: 0.7,
auto_brightness: true,
low_light_mode: LowLightMode::Enable,
theme_mode: ThemeModeV1::Default,
};
let serialized_v2 = v2.serialize_to();
let v3 = DisplayInfoV3::try_deserialize_from(&serialized_v2)
.expect("deserialization should succeed");
assert_eq!(
v3,
DisplayInfoV3 {
manual_brightness_value: v2.manual_brightness_value,
auto_brightness: v2.auto_brightness,
screen_enabled: DisplayInfoV3::default().screen_enabled,
low_light_mode: v2.low_light_mode,
theme_mode: v2.theme_mode,
}
);
}
#[fuchsia::test]
fn test_display_migration_v3_to_v4() {
let v3 = DisplayInfoV3 {
manual_brightness_value: 0.7,
auto_brightness: true,
low_light_mode: LowLightMode::Enable,
theme_mode: ThemeModeV1::Light,
screen_enabled: false,
};
let serialized_v3 = v3.serialize_to();
let v4 = DisplayInfoV4::try_deserialize_from(&serialized_v3)
.expect("deserialization should succeed");
assert_eq!(
v4,
DisplayInfoV4 {
manual_brightness_value: v3.manual_brightness_value,
auto_brightness: v3.auto_brightness,
low_light_mode: v3.low_light_mode,
theme_type: ThemeType::Light,
screen_enabled: v3.screen_enabled,
}
);
}
#[fuchsia::test]
fn test_display_migration_v4_to_v5() {
let v4 = DisplayInfoV4 {
manual_brightness_value: 0.7,
auto_brightness: true,
low_light_mode: LowLightMode::Enable,
theme_type: ThemeType::Dark,
screen_enabled: false,
};
let serialized_v4 = v4.serialize_to();
let v5 = DisplayInfoV5::try_deserialize_from(&serialized_v4)
.expect("deserialization should succeed");
assert_eq!(
v5,
DisplayInfoV5 {
manual_brightness_value: v4.manual_brightness_value,
auto_brightness: v4.auto_brightness,
low_light_mode: v4.low_light_mode,
theme: Some(Theme::new(Some(v4.theme_type), ThemeMode::empty())),
screen_enabled: v4.screen_enabled,
}
);
}
#[fuchsia::test]
fn test_display_migration_v1_to_current() {
let v1 = DisplayInfoV1 {
manual_brightness_value: 0.6,
auto_brightness: true,
low_light_mode: LowLightMode::Enable,
};
let serialized_v1 = v1.serialize_to();
let current = DisplayInfo::try_deserialize_from(&serialized_v1)
.expect("deserialization should succeed");
assert_eq!(
current,
DisplayInfo {
manual_brightness_value: v1.manual_brightness_value,
auto_brightness: v1.auto_brightness,
low_light_mode: v1.low_light_mode,
theme: Some(Theme::new(Some(ThemeType::Unknown), ThemeMode::empty())),
screen_enabled: DisplayInfoV3::default().screen_enabled,
auto_brightness_value: DEFAULT_DISPLAY_INFO.auto_brightness_value,
}
);
}
#[fuchsia::test]
fn test_display_migration_v2_to_current() {
let v2 = DisplayInfoV2 {
manual_brightness_value: 0.6,
auto_brightness: true,
low_light_mode: LowLightMode::Enable,
theme_mode: ThemeModeV1::Light,
};
let serialized_v2 = v2.serialize_to();
let current = DisplayInfo::try_deserialize_from(&serialized_v2)
.expect("deserialization should succeed");
assert_eq!(
current,
DisplayInfo {
manual_brightness_value: v2.manual_brightness_value,
auto_brightness: v2.auto_brightness,
low_light_mode: v2.low_light_mode,
theme: Some(Theme::new(Some(ThemeType::Light), ThemeMode::empty())),
screen_enabled: DisplayInfoV3::default().screen_enabled,
auto_brightness_value: DEFAULT_DISPLAY_INFO.auto_brightness_value,
}
);
}
#[fuchsia::test]
fn test_display_migration_v3_to_current() {
let v3 = DisplayInfoV3 {
manual_brightness_value: 0.6,
auto_brightness: true,
low_light_mode: LowLightMode::Enable,
theme_mode: ThemeModeV1::Light,
screen_enabled: false,
};
let serialized_v3 = v3.serialize_to();
let current = DisplayInfo::try_deserialize_from(&serialized_v3)
.expect("deserialization should succeed");
assert_eq!(
current,
DisplayInfo {
manual_brightness_value: v3.manual_brightness_value,
auto_brightness: v3.auto_brightness,
low_light_mode: v3.low_light_mode,
theme: Some(Theme::new(Some(ThemeType::Light), ThemeMode::empty())),
screen_enabled: v3.screen_enabled,
auto_brightness_value: DEFAULT_DISPLAY_INFO.auto_brightness_value,
}
);
}
#[fuchsia::test]
fn test_display_migration_v4_to_current() {
let v4 = DisplayInfoV4 {
manual_brightness_value: 0.6,
auto_brightness: true,
low_light_mode: LowLightMode::Enable,
theme_type: ThemeType::Light,
screen_enabled: false,
};
let serialized_v4 = v4.serialize_to();
let current = DisplayInfo::try_deserialize_from(&serialized_v4)
.expect("deserialization should succeed");
assert_eq!(
current,
DisplayInfo {
manual_brightness_value: v4.manual_brightness_value,
auto_brightness: v4.auto_brightness,
low_light_mode: v4.low_light_mode,
theme: Some(Theme::new(Some(ThemeType::Light), ThemeMode::empty())),
screen_enabled: v4.screen_enabled,
auto_brightness_value: DEFAULT_DISPLAY_INFO.auto_brightness_value,
}
);
}
#[fuchsia::test]
fn test_display_migration_v5_to_current() {
let v5 = DisplayInfoV5 {
manual_brightness_value: 0.6,
auto_brightness: true,
low_light_mode: LowLightMode::Enable,
theme: Some(Theme::new(Some(ThemeType::Light), ThemeMode::AUTO)),
screen_enabled: false,
};
let serialized_v5 = v5.serialize_to();
let current = DisplayInfo::try_deserialize_from(&serialized_v5)
.expect("deserialization should succeed");
assert_eq!(
current,
DisplayInfo {
manual_brightness_value: v5.manual_brightness_value,
auto_brightness: v5.auto_brightness,
low_light_mode: v5.low_light_mode,
theme: Some(Theme::new(Some(ThemeType::Light), ThemeMode::AUTO)),
screen_enabled: v5.screen_enabled,
auto_brightness_value: DEFAULT_DISPLAY_INFO.auto_brightness_value,
}
);
}
}