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