settings/display/
display_controller.rs

1// Copyright 2019 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 super::types::SetDisplayInfo;
6use crate::display::display_configuration::{
7    ConfigurationThemeMode, ConfigurationThemeType, DisplayConfiguration,
8};
9use crate::display::display_fidl_handler::Publisher;
10use crate::display::types::{DisplayInfo, LowLightMode, Theme, ThemeBuilder, ThemeMode, ThemeType};
11use anyhow::{Context, Error};
12use async_trait::async_trait;
13use fidl_fuchsia_ui_brightness::{
14    ControlMarker as BrightnessControlMarker, ControlProxy as BrightnessControlProxy,
15};
16use fuchsia_async as fasync;
17use futures::StreamExt;
18use futures::channel::mpsc::UnboundedReceiver;
19use futures::channel::oneshot::Sender;
20use serde::{Deserialize, Serialize};
21use settings_common::call;
22use settings_common::config::default_settings::DefaultSetting;
23use settings_common::inspect::event::{
24    ExternalEventPublisher, ResponseType, SettingValuePublisher,
25};
26use settings_common::service_context::{ExternalServiceProxy, ServiceContext};
27use settings_common::utils::Merge;
28use settings_storage::UpdateState;
29use settings_storage::device_storage::{DeviceStorage, DeviceStorageCompatible};
30use settings_storage::storage_factory::{DefaultLoader, NoneT, StorageAccess, StorageFactory};
31use std::rc::Rc;
32use std::sync::Mutex;
33
34pub(super) const DEFAULT_MANUAL_BRIGHTNESS_VALUE: f32 = 0.5;
35pub(super) const DEFAULT_AUTO_BRIGHTNESS_VALUE: f32 = 0.5;
36
37/// Default display used if no configuration is available.
38pub(crate) const DEFAULT_DISPLAY_INFO: DisplayInfo = DisplayInfo::new(
39    false,                           /*auto_brightness_enabled*/
40    DEFAULT_MANUAL_BRIGHTNESS_VALUE, /*manual_brightness_value*/
41    DEFAULT_AUTO_BRIGHTNESS_VALUE,   /*auto_brightness_value*/
42    true,                            /*screen_enabled*/
43    LowLightMode::Disable,           /*low_light_mode*/
44    None,                            /*theme*/
45);
46
47/// Returns a default display [`DisplayInfo`] that is derived from
48/// [`DEFAULT_DISPLAY_INFO`] with any fields specified in the
49/// display configuration set.
50pub struct DisplayInfoLoader {
51    display_configuration: Mutex<DefaultSetting<DisplayConfiguration, &'static str>>,
52}
53
54impl DisplayInfoLoader {
55    pub(crate) fn new(default_setting: DefaultSetting<DisplayConfiguration, &'static str>) -> Self {
56        Self { display_configuration: Mutex::new(default_setting) }
57    }
58}
59
60impl DefaultLoader for DisplayInfoLoader {
61    type Result = DisplayInfo;
62
63    fn default_value(&self) -> Self::Result {
64        let mut default_display_info = DEFAULT_DISPLAY_INFO;
65
66        if let Ok(Some(display_configuration)) =
67            self.display_configuration.lock().unwrap().get_cached_value()
68        {
69            default_display_info.theme = Some(Theme {
70                theme_type: Some(match display_configuration.theme.theme_type {
71                    ConfigurationThemeType::Light => ThemeType::Light,
72                }),
73                theme_mode: if display_configuration
74                    .theme
75                    .theme_mode
76                    .contains(&ConfigurationThemeMode::Auto)
77                {
78                    ThemeMode::AUTO
79                } else {
80                    ThemeMode::empty()
81                },
82            });
83        }
84
85        default_display_info
86    }
87}
88
89impl DeviceStorageCompatible for DisplayInfo {
90    type Loader = DisplayInfoLoader;
91    const KEY: &'static str = "display_info";
92
93    fn try_deserialize_from(value: &str) -> Result<Self, Error> {
94        Self::extract(value).or_else(|_| DisplayInfoV5::try_deserialize_from(value).map(Self::from))
95    }
96}
97
98impl From<DisplayInfoV5> for DisplayInfo {
99    fn from(v5: DisplayInfoV5) -> Self {
100        DisplayInfo {
101            auto_brightness: v5.auto_brightness,
102            auto_brightness_value: DEFAULT_AUTO_BRIGHTNESS_VALUE,
103            manual_brightness_value: v5.manual_brightness_value,
104            screen_enabled: v5.screen_enabled,
105            low_light_mode: v5.low_light_mode,
106            theme: v5.theme,
107        }
108    }
109}
110
111#[derive(thiserror::Error, Debug)]
112pub enum DisplayError {
113    #[error("Failed to initialize controller: {0:?}")]
114    InitFailure(Error),
115    #[error("Invalid argument: arg: {0:?}, value: {1:?}")]
116    InvalidArgument(&'static str, String),
117    #[error("External failure for Display: dependency: {0:?} request:{1:?} error:{2}")]
118    ExternalFailure(&'static str, &'static str, String),
119    #[error("Write failed for Display: {0:?}")]
120    WriteFailure(Error),
121}
122
123impl From<&DisplayError> for ResponseType {
124    fn from(error: &DisplayError) -> Self {
125        match error {
126            DisplayError::InitFailure(..) => ResponseType::InitFailure,
127            DisplayError::InvalidArgument(..) => ResponseType::InvalidArgument,
128            DisplayError::ExternalFailure(..) => ResponseType::ExternalFailure,
129            DisplayError::WriteFailure(..) => ResponseType::StorageFailure,
130        }
131    }
132}
133
134#[async_trait(?Send)]
135pub trait BrightnessManager: Sized {
136    async fn from_context(
137        service_context: &ServiceContext,
138        external_publisher: ExternalEventPublisher,
139    ) -> Result<Self, DisplayError>;
140    async fn update_brightness(
141        &self,
142        info: DisplayInfo,
143        store: &DeviceStorage,
144        // Allows overriding of the check for whether info has changed. This is necessary for
145        // the initial restore call.
146        always_send: bool,
147    ) -> Result<Option<DisplayInfo>, DisplayError>;
148}
149
150#[async_trait(?Send)]
151impl BrightnessManager for () {
152    async fn from_context(
153        _: &ServiceContext,
154        _: ExternalEventPublisher,
155    ) -> Result<Self, DisplayError> {
156        Ok(())
157    }
158
159    // This does not send the brightness value on anywhere, it simply stores it.
160    // External services will pick up the value and set it on the brightness manager.
161    async fn update_brightness(
162        &self,
163        info: DisplayInfo,
164        store: &DeviceStorage,
165        _: bool,
166    ) -> Result<Option<DisplayInfo>, DisplayError> {
167        if !info.is_finite() {
168            return Err(DisplayError::InvalidArgument("display_info", format!("{info:?}")));
169        }
170        store
171            .write(&info)
172            .await
173            .map(|state| (UpdateState::Updated == state).then_some(info))
174            .context("updating display info")
175            .map_err(DisplayError::WriteFailure)
176    }
177}
178
179pub(crate) struct ExternalBrightnessControl {
180    brightness_service: ExternalServiceProxy<BrightnessControlProxy, ExternalEventPublisher>,
181}
182
183#[async_trait(?Send)]
184impl BrightnessManager for ExternalBrightnessControl {
185    async fn from_context(
186        service_context: &ServiceContext,
187        external_publisher: ExternalEventPublisher,
188    ) -> Result<Self, DisplayError> {
189        service_context
190            .connect_with_publisher::<BrightnessControlMarker, _>(external_publisher)
191            .await
192            .map(|brightness_service| Self { brightness_service })
193            .context("connecting to brightness service")
194            .map_err(DisplayError::InitFailure)
195    }
196
197    async fn update_brightness(
198        &self,
199        info: DisplayInfo,
200        store: &DeviceStorage,
201        always_send: bool,
202    ) -> Result<Option<DisplayInfo>, DisplayError> {
203        if !info.is_finite() {
204            return Err(DisplayError::InvalidArgument("display_info", format!("{info:?}")));
205        }
206        let new_info = store
207            .write(&info)
208            .await
209            .map(|state| (UpdateState::Updated == state).then_some(info))
210            .context("updating brightness")
211            .map_err(DisplayError::WriteFailure)?;
212        if new_info.is_none() && !always_send {
213            return Ok(None);
214        }
215
216        if info.auto_brightness {
217            call!(self.brightness_service => set_auto_brightness())
218        } else {
219            call!(self.brightness_service => set_manual_brightness(info.manual_brightness_value))
220        }
221        .map(|_| new_info)
222        .map_err(|e| {
223            DisplayError::ExternalFailure(
224                "brightness_service".into(),
225                "set_brightness".into(),
226                format!("{e:?}").into(),
227            )
228        })
229    }
230}
231
232pub(crate) enum Request {
233    Set(SetDisplayInfo, Sender<Result<(), DisplayError>>),
234}
235
236pub(crate) struct DisplayController<T = ()> {
237    brightness_manager: T,
238    store: Rc<DeviceStorage>,
239    publisher: Option<Publisher>,
240    setting_value_publisher: SettingValuePublisher<DisplayInfo>,
241}
242
243impl<T> StorageAccess for DisplayController<T> {
244    type Storage = DeviceStorage;
245    type Data = DisplayInfo;
246    const STORAGE_KEY: &'static str = DisplayInfo::KEY;
247}
248
249impl<T> DisplayController<T>
250where
251    T: BrightnessManager + 'static,
252{
253    pub(crate) async fn new<F>(
254        service_context: &ServiceContext,
255        storage_factory: Rc<F>,
256        setting_value_publisher: SettingValuePublisher<DisplayInfo>,
257        external_publisher: ExternalEventPublisher,
258    ) -> Result<DisplayController<T>, DisplayError>
259    where
260        F: StorageFactory<Storage = DeviceStorage>,
261    {
262        let brightness_manager =
263            <T as BrightnessManager>::from_context(service_context, external_publisher).await?;
264        Ok(Self {
265            brightness_manager,
266            store: storage_factory.get_store().await,
267            publisher: None,
268            setting_value_publisher,
269        })
270    }
271
272    pub(crate) async fn restore(&self) -> Result<DisplayInfo, DisplayError> {
273        let display_info = self.store.get::<DisplayInfo>().await;
274        assert!(display_info.is_finite());
275
276        // Load and set value.
277        self.brightness_manager
278            .update_brightness(display_info, &self.store, true)
279            .await
280            // If there was no update to the value, just return the previously retrieved value
281            // from storage.
282            .map(|info| info.unwrap_or(display_info))
283    }
284
285    pub(crate) async fn handle(
286        self,
287        mut request_rx: UnboundedReceiver<Request>,
288    ) -> fasync::Task<()> {
289        fasync::Task::local(async move {
290            while let Some(request) = request_rx.next().await {
291                let Request::Set(mut set_display_info, tx) = request;
292                let display_info = self.store.get::<DisplayInfo>().await;
293                assert!(display_info.is_finite());
294
295                if let Some(theme) = set_display_info.theme {
296                    set_display_info.theme = self.build_theme(theme, &display_info);
297                }
298                let res = self
299                    .brightness_manager
300                    .update_brightness(display_info.merge(set_display_info), &self.store, false)
301                    .await
302                    .map(|info| {
303                        if let Some(info) = info {
304                            self.publish(info);
305                        }
306                    });
307                let _ = tx.send(res);
308            }
309        })
310    }
311
312    fn build_theme(&self, incoming_theme: Theme, display_info: &DisplayInfo) -> Option<Theme> {
313        let existing_theme_type = display_info.theme.and_then(|theme| theme.theme_type);
314        let new_theme_type = incoming_theme.theme_type.or(existing_theme_type);
315
316        ThemeBuilder::new()
317            .set_theme_type(new_theme_type)
318            .set_theme_mode(incoming_theme.theme_mode)
319            .build()
320    }
321}
322
323impl<T> DisplayController<T> {
324    pub(crate) fn register_publisher(&mut self, publisher: Publisher) {
325        self.publisher = Some(publisher);
326    }
327
328    fn publish(&self, info: DisplayInfo) {
329        let _ = self.setting_value_publisher.publish(&info);
330        if let Some(publisher) = self.publisher.as_ref() {
331            publisher.set(info);
332        }
333    }
334}
335
336/// The following struct should never be modified. It represents an old
337/// version of the display settings.
338#[derive(PartialEq, Debug, Clone, Copy, Serialize, Deserialize)]
339pub struct DisplayInfoV1 {
340    /// The last brightness value that was manually set.
341    pub manual_brightness_value: f32,
342    pub auto_brightness: bool,
343    pub low_light_mode: LowLightMode,
344}
345
346impl DisplayInfoV1 {
347    const fn new(
348        auto_brightness: bool,
349        manual_brightness_value: f32,
350        low_light_mode: LowLightMode,
351    ) -> DisplayInfoV1 {
352        DisplayInfoV1 { manual_brightness_value, auto_brightness, low_light_mode }
353    }
354}
355
356impl DeviceStorageCompatible for DisplayInfoV1 {
357    type Loader = NoneT;
358    const KEY: &'static str = "display_infoV1";
359}
360
361impl Default for DisplayInfoV1 {
362    fn default() -> Self {
363        DisplayInfoV1::new(
364            false,                           /*auto_brightness_enabled*/
365            DEFAULT_MANUAL_BRIGHTNESS_VALUE, /*brightness_value*/
366            LowLightMode::Disable,           /*low_light_mode*/
367        )
368    }
369}
370
371/// The following struct should never be modified.  It represents an old
372/// version of the display settings.
373#[derive(PartialEq, Debug, Clone, Copy, Serialize, Deserialize)]
374pub struct DisplayInfoV2 {
375    pub manual_brightness_value: f32,
376    pub auto_brightness: bool,
377    pub low_light_mode: LowLightMode,
378    pub theme_mode: ThemeModeV1,
379}
380
381impl DisplayInfoV2 {
382    const fn new(
383        auto_brightness: bool,
384        manual_brightness_value: f32,
385        low_light_mode: LowLightMode,
386        theme_mode: ThemeModeV1,
387    ) -> DisplayInfoV2 {
388        DisplayInfoV2 { manual_brightness_value, auto_brightness, low_light_mode, theme_mode }
389    }
390}
391
392impl DeviceStorageCompatible for DisplayInfoV2 {
393    type Loader = NoneT;
394    const KEY: &'static str = "display_infoV2";
395
396    fn try_deserialize_from(value: &str) -> Result<Self, Error> {
397        Self::extract(value).or_else(|_| DisplayInfoV1::try_deserialize_from(value).map(Self::from))
398    }
399}
400
401impl Default for DisplayInfoV2 {
402    fn default() -> Self {
403        DisplayInfoV2::new(
404            false,                           /*auto_brightness_enabled*/
405            DEFAULT_MANUAL_BRIGHTNESS_VALUE, /*brightness_value*/
406            LowLightMode::Disable,           /*low_light_mode*/
407            ThemeModeV1::Unknown,            /*theme_mode*/
408        )
409    }
410}
411
412impl From<DisplayInfoV1> for DisplayInfoV2 {
413    fn from(v1: DisplayInfoV1) -> Self {
414        DisplayInfoV2 {
415            auto_brightness: v1.auto_brightness,
416            manual_brightness_value: v1.manual_brightness_value,
417            low_light_mode: v1.low_light_mode,
418            theme_mode: ThemeModeV1::Unknown,
419        }
420    }
421}
422
423#[derive(PartialEq, Debug, Clone, Copy, Serialize, Deserialize)]
424pub enum ThemeModeV1 {
425    Unknown,
426    Default,
427    Light,
428    Dark,
429    /// Product can choose a theme based on ambient cues.
430    Auto,
431}
432
433impl From<ThemeModeV1> for ThemeType {
434    fn from(theme_mode_v1: ThemeModeV1) -> Self {
435        match theme_mode_v1 {
436            ThemeModeV1::Default => ThemeType::Default,
437            ThemeModeV1::Light => ThemeType::Light,
438            ThemeModeV1::Dark => ThemeType::Dark,
439            // ThemeType has removed Auto field, see https://fxbug.dev/42143417
440            ThemeModeV1::Unknown | ThemeModeV1::Auto => ThemeType::Unknown,
441        }
442    }
443}
444
445#[derive(PartialEq, Debug, Clone, Copy, Serialize, Deserialize)]
446pub struct DisplayInfoV3 {
447    /// The last brightness value that was manually set.
448    pub manual_brightness_value: f32,
449    pub auto_brightness: bool,
450    pub screen_enabled: bool,
451    pub low_light_mode: LowLightMode,
452    pub theme_mode: ThemeModeV1,
453}
454
455impl DisplayInfoV3 {
456    const fn new(
457        auto_brightness: bool,
458        manual_brightness_value: f32,
459        screen_enabled: bool,
460        low_light_mode: LowLightMode,
461        theme_mode: ThemeModeV1,
462    ) -> DisplayInfoV3 {
463        DisplayInfoV3 {
464            manual_brightness_value,
465            auto_brightness,
466            screen_enabled,
467            low_light_mode,
468            theme_mode,
469        }
470    }
471}
472
473impl DeviceStorageCompatible for DisplayInfoV3 {
474    type Loader = NoneT;
475    const KEY: &'static str = "display_info";
476
477    fn try_deserialize_from(value: &str) -> Result<Self, Error> {
478        Self::extract(value).or_else(|_| DisplayInfoV2::try_deserialize_from(value).map(Self::from))
479    }
480}
481
482impl Default for DisplayInfoV3 {
483    fn default() -> Self {
484        DisplayInfoV3::new(
485            false,                           /*auto_brightness_enabled*/
486            DEFAULT_MANUAL_BRIGHTNESS_VALUE, /*brightness_value*/
487            true,                            /*screen_enabled*/
488            LowLightMode::Disable,           /*low_light_mode*/
489            ThemeModeV1::Unknown,            /*theme_mode*/
490        )
491    }
492}
493
494impl From<DisplayInfoV2> for DisplayInfoV3 {
495    fn from(v2: DisplayInfoV2) -> Self {
496        DisplayInfoV3 {
497            auto_brightness: v2.auto_brightness,
498            manual_brightness_value: v2.manual_brightness_value,
499            screen_enabled: true,
500            low_light_mode: v2.low_light_mode,
501            theme_mode: v2.theme_mode,
502        }
503    }
504}
505
506#[derive(PartialEq, Debug, Clone, Copy, Serialize, Deserialize)]
507pub struct DisplayInfoV4 {
508    /// The last brightness value that was manually set.
509    pub manual_brightness_value: f32,
510    pub auto_brightness: bool,
511    pub screen_enabled: bool,
512    pub low_light_mode: LowLightMode,
513    pub theme_type: ThemeType,
514}
515
516impl DisplayInfoV4 {
517    const fn new(
518        auto_brightness: bool,
519        manual_brightness_value: f32,
520        screen_enabled: bool,
521        low_light_mode: LowLightMode,
522        theme_type: ThemeType,
523    ) -> DisplayInfoV4 {
524        DisplayInfoV4 {
525            manual_brightness_value,
526            auto_brightness,
527            screen_enabled,
528            low_light_mode,
529            theme_type,
530        }
531    }
532}
533
534impl From<DisplayInfoV3> for DisplayInfoV4 {
535    fn from(v3: DisplayInfoV3) -> Self {
536        DisplayInfoV4 {
537            auto_brightness: v3.auto_brightness,
538            manual_brightness_value: v3.manual_brightness_value,
539            screen_enabled: v3.screen_enabled,
540            low_light_mode: v3.low_light_mode,
541            // In v4, the field formerly known as theme_mode was renamed to
542            // theme_type.
543            theme_type: ThemeType::from(v3.theme_mode),
544        }
545    }
546}
547
548impl DeviceStorageCompatible for DisplayInfoV4 {
549    type Loader = NoneT;
550    const KEY: &'static str = "display_info";
551
552    fn try_deserialize_from(value: &str) -> Result<Self, Error> {
553        Self::extract(value).or_else(|_| DisplayInfoV3::try_deserialize_from(value).map(Self::from))
554    }
555}
556
557impl Default for DisplayInfoV4 {
558    fn default() -> Self {
559        DisplayInfoV4::new(
560            false,                           /*auto_brightness_enabled*/
561            DEFAULT_MANUAL_BRIGHTNESS_VALUE, /*brightness_value*/
562            true,                            /*screen_enabled*/
563            LowLightMode::Disable,           /*low_light_mode*/
564            ThemeType::Unknown,              /*theme_type*/
565        )
566    }
567}
568
569#[derive(PartialEq, Debug, Clone, Copy, Serialize, Deserialize)]
570#[serde(deny_unknown_fields)]
571pub struct DisplayInfoV5 {
572    /// The last brightness value that was manually set.
573    pub manual_brightness_value: f32,
574    pub auto_brightness: bool,
575    pub screen_enabled: bool,
576    pub low_light_mode: LowLightMode,
577    pub theme: Option<Theme>,
578}
579
580impl DisplayInfoV5 {
581    const fn new(
582        auto_brightness: bool,
583        manual_brightness_value: f32,
584        screen_enabled: bool,
585        low_light_mode: LowLightMode,
586        theme: Option<Theme>,
587    ) -> DisplayInfoV5 {
588        DisplayInfoV5 {
589            manual_brightness_value,
590            auto_brightness,
591            screen_enabled,
592            low_light_mode,
593            theme,
594        }
595    }
596}
597
598impl From<DisplayInfoV4> for DisplayInfoV5 {
599    fn from(v4: DisplayInfoV4) -> Self {
600        DisplayInfoV5 {
601            auto_brightness: v4.auto_brightness,
602            manual_brightness_value: v4.manual_brightness_value,
603            screen_enabled: v4.screen_enabled,
604            low_light_mode: v4.low_light_mode,
605            // Clients has migrated off auto theme_type, we should not get theme_type as Auto
606            theme: Some(Theme::new(Some(v4.theme_type), ThemeMode::empty())),
607        }
608    }
609}
610
611impl DeviceStorageCompatible for DisplayInfoV5 {
612    type Loader = NoneT;
613    const KEY: &'static str = "display_info";
614
615    fn try_deserialize_from(value: &str) -> Result<Self, Error> {
616        Self::extract(value).or_else(|_| DisplayInfoV4::try_deserialize_from(value).map(Self::from))
617    }
618}
619
620impl Default for DisplayInfoV5 {
621    fn default() -> Self {
622        DisplayInfoV5::new(
623            false,                                                          /*auto_brightness_enabled*/
624            DEFAULT_MANUAL_BRIGHTNESS_VALUE,                                /*brightness_value*/
625            true,                                                           /*screen_enabled*/
626            LowLightMode::Disable,                                          /*low_light_mode*/
627            Some(Theme::new(Some(ThemeType::Unknown), ThemeMode::empty())), /*theme_type*/
628        )
629    }
630}
631
632#[cfg(test)]
633mod tests {
634    use super::*;
635    use crate::display::build_display_default_settings;
636    use crate::display::test_fakes::brightness_service::BrightnessService;
637    use fuchsia_async::{Task, TestExecutor};
638    use fuchsia_inspect::component;
639    use futures::channel::mpsc;
640    use futures::future;
641    use futures::lock::Mutex;
642    use settings_common::inspect::config_logger::InspectConfigLogger;
643    use settings_test_common::fakes::service::ServiceRegistry;
644    use settings_test_common::storage::InMemoryStorageFactory;
645
646    #[fuchsia::test]
647    fn test_display_migration_v1_to_v2() {
648        let v1 = DisplayInfoV1 {
649            manual_brightness_value: 0.6,
650            auto_brightness: true,
651            low_light_mode: LowLightMode::Enable,
652        };
653
654        let serialized_v1 = v1.serialize_to();
655        let v2 = DisplayInfoV2::try_deserialize_from(&serialized_v1)
656            .expect("deserialization should succeed");
657
658        assert_eq!(
659            v2,
660            DisplayInfoV2 {
661                manual_brightness_value: v1.manual_brightness_value,
662                auto_brightness: v1.auto_brightness,
663                low_light_mode: v1.low_light_mode,
664                theme_mode: DisplayInfoV2::default().theme_mode,
665            }
666        );
667    }
668
669    #[fuchsia::test]
670    fn test_display_migration_v2_to_v3() {
671        let v2 = DisplayInfoV2 {
672            manual_brightness_value: 0.7,
673            auto_brightness: true,
674            low_light_mode: LowLightMode::Enable,
675            theme_mode: ThemeModeV1::Default,
676        };
677
678        let serialized_v2 = v2.serialize_to();
679        let v3 = DisplayInfoV3::try_deserialize_from(&serialized_v2)
680            .expect("deserialization should succeed");
681
682        assert_eq!(
683            v3,
684            DisplayInfoV3 {
685                manual_brightness_value: v2.manual_brightness_value,
686                auto_brightness: v2.auto_brightness,
687                screen_enabled: DisplayInfoV3::default().screen_enabled,
688                low_light_mode: v2.low_light_mode,
689                theme_mode: v2.theme_mode,
690            }
691        );
692    }
693
694    #[fuchsia::test]
695    fn test_display_migration_v3_to_v4() {
696        let v3 = DisplayInfoV3 {
697            manual_brightness_value: 0.7,
698            auto_brightness: true,
699            low_light_mode: LowLightMode::Enable,
700            theme_mode: ThemeModeV1::Light,
701            screen_enabled: false,
702        };
703
704        let serialized_v3 = v3.serialize_to();
705        let v4 = DisplayInfoV4::try_deserialize_from(&serialized_v3)
706            .expect("deserialization should succeed");
707
708        // In v4, the field formally known as theme_mode is theme_type.
709        assert_eq!(
710            v4,
711            DisplayInfoV4 {
712                manual_brightness_value: v3.manual_brightness_value,
713                auto_brightness: v3.auto_brightness,
714                low_light_mode: v3.low_light_mode,
715                theme_type: ThemeType::Light,
716                screen_enabled: v3.screen_enabled,
717            }
718        );
719    }
720
721    #[fuchsia::test]
722    fn test_display_migration_v4_to_v5() {
723        let v4 = DisplayInfoV4 {
724            manual_brightness_value: 0.7,
725            auto_brightness: true,
726            low_light_mode: LowLightMode::Enable,
727            theme_type: ThemeType::Dark,
728            screen_enabled: false,
729        };
730
731        let serialized_v4 = v4.serialize_to();
732        let v5 = DisplayInfoV5::try_deserialize_from(&serialized_v4)
733            .expect("deserialization should succeed");
734
735        assert_eq!(
736            v5,
737            DisplayInfoV5 {
738                manual_brightness_value: v4.manual_brightness_value,
739                auto_brightness: v4.auto_brightness,
740                low_light_mode: v4.low_light_mode,
741                theme: Some(Theme::new(Some(v4.theme_type), ThemeMode::empty())),
742                screen_enabled: v4.screen_enabled,
743            }
744        );
745    }
746
747    #[fuchsia::test]
748    fn test_display_migration_v1_to_current() {
749        let v1 = DisplayInfoV1 {
750            manual_brightness_value: 0.6,
751            auto_brightness: true,
752            low_light_mode: LowLightMode::Enable,
753        };
754
755        let serialized_v1 = v1.serialize_to();
756        let current = DisplayInfo::try_deserialize_from(&serialized_v1)
757            .expect("deserialization should succeed");
758
759        assert_eq!(
760            current,
761            DisplayInfo {
762                manual_brightness_value: v1.manual_brightness_value,
763                auto_brightness: v1.auto_brightness,
764                low_light_mode: v1.low_light_mode,
765                theme: Some(Theme::new(Some(ThemeType::Unknown), ThemeMode::empty())),
766                // screen_enabled was added in v3.
767                screen_enabled: DisplayInfoV3::default().screen_enabled,
768                auto_brightness_value: DEFAULT_DISPLAY_INFO.auto_brightness_value,
769            }
770        );
771    }
772
773    #[fuchsia::test]
774    fn test_display_migration_v2_to_current() {
775        let v2 = DisplayInfoV2 {
776            manual_brightness_value: 0.6,
777            auto_brightness: true,
778            low_light_mode: LowLightMode::Enable,
779            theme_mode: ThemeModeV1::Light,
780        };
781
782        let serialized_v2 = v2.serialize_to();
783        let current = DisplayInfo::try_deserialize_from(&serialized_v2)
784            .expect("deserialization should succeed");
785
786        assert_eq!(
787            current,
788            DisplayInfo {
789                manual_brightness_value: v2.manual_brightness_value,
790                auto_brightness: v2.auto_brightness,
791                low_light_mode: v2.low_light_mode,
792                theme: Some(Theme::new(Some(ThemeType::Light), ThemeMode::empty())),
793                // screen_enabled was added in v3.
794                screen_enabled: DisplayInfoV3::default().screen_enabled,
795                auto_brightness_value: DEFAULT_DISPLAY_INFO.auto_brightness_value,
796            }
797        );
798    }
799
800    #[fuchsia::test]
801    fn test_display_migration_v3_to_current() {
802        let v3 = DisplayInfoV3 {
803            manual_brightness_value: 0.6,
804            auto_brightness: true,
805            low_light_mode: LowLightMode::Enable,
806            theme_mode: ThemeModeV1::Light,
807            screen_enabled: false,
808        };
809
810        let serialized_v3 = v3.serialize_to();
811        let current = DisplayInfo::try_deserialize_from(&serialized_v3)
812            .expect("deserialization should succeed");
813
814        assert_eq!(
815            current,
816            DisplayInfo {
817                manual_brightness_value: v3.manual_brightness_value,
818                auto_brightness: v3.auto_brightness,
819                low_light_mode: v3.low_light_mode,
820                theme: Some(Theme::new(Some(ThemeType::Light), ThemeMode::empty())),
821                // screen_enabled was added in v3.
822                screen_enabled: v3.screen_enabled,
823                auto_brightness_value: DEFAULT_DISPLAY_INFO.auto_brightness_value,
824            }
825        );
826    }
827
828    #[fuchsia::test]
829    fn test_display_migration_v4_to_current() {
830        let v4 = DisplayInfoV4 {
831            manual_brightness_value: 0.6,
832            auto_brightness: true,
833            low_light_mode: LowLightMode::Enable,
834            theme_type: ThemeType::Light,
835            screen_enabled: false,
836        };
837
838        let serialized_v4 = v4.serialize_to();
839        let current = DisplayInfo::try_deserialize_from(&serialized_v4)
840            .expect("deserialization should succeed");
841
842        assert_eq!(
843            current,
844            DisplayInfo {
845                manual_brightness_value: v4.manual_brightness_value,
846                auto_brightness: v4.auto_brightness,
847                low_light_mode: v4.low_light_mode,
848                theme: Some(Theme::new(Some(ThemeType::Light), ThemeMode::empty())),
849                screen_enabled: v4.screen_enabled,
850                auto_brightness_value: DEFAULT_DISPLAY_INFO.auto_brightness_value,
851            }
852        );
853    }
854
855    #[fuchsia::test]
856    fn test_display_migration_v5_to_current() {
857        let v5 = DisplayInfoV5 {
858            manual_brightness_value: 0.6,
859            auto_brightness: true,
860            low_light_mode: LowLightMode::Enable,
861            theme: Some(Theme::new(Some(ThemeType::Light), ThemeMode::AUTO)),
862            screen_enabled: false,
863        };
864
865        let serialized_v5 = v5.serialize_to();
866        let current = DisplayInfo::try_deserialize_from(&serialized_v5)
867            .expect("deserialization should succeed");
868
869        assert_eq!(
870            current,
871            DisplayInfo {
872                manual_brightness_value: v5.manual_brightness_value,
873                auto_brightness: v5.auto_brightness,
874                low_light_mode: v5.low_light_mode,
875                theme: Some(Theme::new(Some(ThemeType::Light), ThemeMode::AUTO)),
876                screen_enabled: v5.screen_enabled,
877                auto_brightness_value: DEFAULT_DISPLAY_INFO.auto_brightness_value,
878            }
879        );
880    }
881
882    const AUTO_BRIGHTNESS_LEVEL: f32 = 0.9;
883
884    fn default_settings() -> DefaultSetting<DisplayConfiguration, &'static str> {
885        let config_logger =
886            Rc::new(std::sync::Mutex::new(InspectConfigLogger::new(component::inspector().root())));
887        build_display_default_settings(config_logger)
888    }
889
890    // Makes sure that settings are restored from storage when service comes online.
891    #[fuchsia::test(allow_stalls = false)]
892    async fn test_display_restore_with_storage_controller() {
893        // Ensure auto-brightness value is restored correctly.
894        validate_restore_with_storage_controller(
895            0.7,
896            AUTO_BRIGHTNESS_LEVEL,
897            true,
898            true,
899            LowLightMode::Enable,
900            None,
901        )
902        .await;
903
904        // Ensure manual-brightness value is restored correctly.
905        validate_restore_with_storage_controller(
906            0.9,
907            AUTO_BRIGHTNESS_LEVEL,
908            false,
909            true,
910            LowLightMode::Disable,
911            None,
912        )
913        .await;
914    }
915
916    async fn validate_restore_with_storage_controller(
917        manual_brightness: f32,
918        auto_brightness_value: f32,
919        auto_brightness: bool,
920        screen_enabled: bool,
921        low_light_mode: LowLightMode,
922        theme: Option<Theme>,
923    ) {
924        let service_registry = ServiceRegistry::create();
925        let info = DisplayInfo {
926            manual_brightness_value: manual_brightness,
927            auto_brightness_value,
928            auto_brightness,
929            screen_enabled,
930            low_light_mode,
931            theme,
932        };
933        let storage_factory = InMemoryStorageFactory::with_initial_data(&info);
934        let (tx, _) = mpsc::unbounded();
935        let setting_value_publisher = SettingValuePublisher::new(tx);
936        let (tx, _) = mpsc::unbounded();
937        let external_publisher = ExternalEventPublisher::new(tx);
938
939        storage_factory
940            .initialize_with_loader::<DisplayController, _>(DisplayInfoLoader::new(
941                default_settings(),
942            ))
943            .await
944            .expect("initializing display storage");
945
946        let display_controller = DisplayController::<()>::new(
947            &ServiceContext::new(Some(Box::new(ServiceRegistry::serve(service_registry)))),
948            Rc::new(storage_factory),
949            setting_value_publisher,
950            external_publisher,
951        )
952        .await
953        .expect("constructing display controller");
954
955        let info = display_controller.restore().await.expect("restore completed");
956        let settings = fidl_fuchsia_settings::DisplaySettings::from(info);
957
958        if auto_brightness {
959            assert_eq!(settings.auto_brightness, Some(auto_brightness));
960            assert_eq!(settings.adjusted_auto_brightness, Some(auto_brightness_value));
961        } else {
962            assert_eq!(settings.brightness_value, Some(manual_brightness));
963        }
964    }
965
966    // Makes sure that settings are restored from storage when service comes online.
967    #[fuchsia::test]
968    fn test_display_restore_with_brightness_controller() {
969        let mut exec = TestExecutor::new();
970
971        // Ensure auto-brightness value is restored correctly.
972        validate_restore_with_brightness_controller(
973            &mut exec,
974            0.7,
975            AUTO_BRIGHTNESS_LEVEL,
976            true,
977            true,
978            LowLightMode::Enable,
979            None,
980        );
981
982        // Ensure manual-brightness value is restored correctly.
983        validate_restore_with_brightness_controller(
984            &mut exec,
985            0.9,
986            AUTO_BRIGHTNESS_LEVEL,
987            false,
988            true,
989            LowLightMode::Disable,
990            None,
991        );
992    }
993
994    // Float comparisons are checking that set values are the same when retrieved.
995    #[allow(clippy::float_cmp)]
996    fn validate_restore_with_brightness_controller(
997        exec: &mut TestExecutor,
998        manual_brightness: f32,
999        auto_brightness_value: f32,
1000        auto_brightness: bool,
1001        screen_enabled: bool,
1002        low_light_mode: LowLightMode,
1003        theme: Option<Theme>,
1004    ) {
1005        let brightness_service_handle = BrightnessService::create();
1006        let brightness_service_handle_clone = brightness_service_handle.clone();
1007
1008        let _task = Task::local(async move {
1009            let service_registry = ServiceRegistry::create();
1010            service_registry
1011                .lock()
1012                .await
1013                .register_service(Rc::new(Mutex::new(brightness_service_handle_clone)));
1014            let info = DisplayInfo {
1015                manual_brightness_value: manual_brightness,
1016                auto_brightness_value,
1017                auto_brightness,
1018                screen_enabled,
1019                low_light_mode,
1020                theme,
1021            };
1022            let storage_factory = InMemoryStorageFactory::with_initial_data(&info);
1023            let (tx, _) = mpsc::unbounded();
1024            let setting_value_publisher = SettingValuePublisher::new(tx);
1025            let (tx, _) = mpsc::unbounded();
1026            let external_publisher = ExternalEventPublisher::new(tx);
1027
1028            storage_factory
1029                .initialize_with_loader::<DisplayController, _>(DisplayInfoLoader::new(
1030                    default_settings(),
1031                ))
1032                .await
1033                .expect("initializing display storage");
1034
1035            let display_controller = DisplayController::<ExternalBrightnessControl>::new(
1036                &ServiceContext::new(Some(Box::new(ServiceRegistry::serve(service_registry)))),
1037                Rc::new(storage_factory),
1038                setting_value_publisher,
1039                external_publisher,
1040            )
1041            .await
1042            .expect("constructing display controller");
1043
1044            let _ = display_controller.restore().await.expect("restore completed");
1045        });
1046
1047        let _ = exec.run_until_stalled(&mut future::pending::<()>());
1048
1049        exec.run_singlethreaded(async {
1050            if auto_brightness {
1051                let service_auto_brightness =
1052                    brightness_service_handle.get_auto_brightness().lock().await.unwrap();
1053                assert_eq!(service_auto_brightness, auto_brightness);
1054            } else {
1055                let service_manual_brightness =
1056                    brightness_service_handle.get_manual_brightness().lock().await.unwrap();
1057                assert_eq!(service_manual_brightness, manual_brightness);
1058            }
1059        });
1060    }
1061}