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
636    #[fuchsia::test]
637    fn test_display_migration_v1_to_v2() {
638        let v1 = DisplayInfoV1 {
639            manual_brightness_value: 0.6,
640            auto_brightness: true,
641            low_light_mode: LowLightMode::Enable,
642        };
643
644        let serialized_v1 = v1.serialize_to();
645        let v2 = DisplayInfoV2::try_deserialize_from(&serialized_v1)
646            .expect("deserialization should succeed");
647
648        assert_eq!(
649            v2,
650            DisplayInfoV2 {
651                manual_brightness_value: v1.manual_brightness_value,
652                auto_brightness: v1.auto_brightness,
653                low_light_mode: v1.low_light_mode,
654                theme_mode: DisplayInfoV2::default().theme_mode,
655            }
656        );
657    }
658
659    #[fuchsia::test]
660    fn test_display_migration_v2_to_v3() {
661        let v2 = DisplayInfoV2 {
662            manual_brightness_value: 0.7,
663            auto_brightness: true,
664            low_light_mode: LowLightMode::Enable,
665            theme_mode: ThemeModeV1::Default,
666        };
667
668        let serialized_v2 = v2.serialize_to();
669        let v3 = DisplayInfoV3::try_deserialize_from(&serialized_v2)
670            .expect("deserialization should succeed");
671
672        assert_eq!(
673            v3,
674            DisplayInfoV3 {
675                manual_brightness_value: v2.manual_brightness_value,
676                auto_brightness: v2.auto_brightness,
677                screen_enabled: DisplayInfoV3::default().screen_enabled,
678                low_light_mode: v2.low_light_mode,
679                theme_mode: v2.theme_mode,
680            }
681        );
682    }
683
684    #[fuchsia::test]
685    fn test_display_migration_v3_to_v4() {
686        let v3 = DisplayInfoV3 {
687            manual_brightness_value: 0.7,
688            auto_brightness: true,
689            low_light_mode: LowLightMode::Enable,
690            theme_mode: ThemeModeV1::Light,
691            screen_enabled: false,
692        };
693
694        let serialized_v3 = v3.serialize_to();
695        let v4 = DisplayInfoV4::try_deserialize_from(&serialized_v3)
696            .expect("deserialization should succeed");
697
698        // In v4, the field formally known as theme_mode is theme_type.
699        assert_eq!(
700            v4,
701            DisplayInfoV4 {
702                manual_brightness_value: v3.manual_brightness_value,
703                auto_brightness: v3.auto_brightness,
704                low_light_mode: v3.low_light_mode,
705                theme_type: ThemeType::Light,
706                screen_enabled: v3.screen_enabled,
707            }
708        );
709    }
710
711    #[fuchsia::test]
712    fn test_display_migration_v4_to_v5() {
713        let v4 = DisplayInfoV4 {
714            manual_brightness_value: 0.7,
715            auto_brightness: true,
716            low_light_mode: LowLightMode::Enable,
717            theme_type: ThemeType::Dark,
718            screen_enabled: false,
719        };
720
721        let serialized_v4 = v4.serialize_to();
722        let v5 = DisplayInfoV5::try_deserialize_from(&serialized_v4)
723            .expect("deserialization should succeed");
724
725        assert_eq!(
726            v5,
727            DisplayInfoV5 {
728                manual_brightness_value: v4.manual_brightness_value,
729                auto_brightness: v4.auto_brightness,
730                low_light_mode: v4.low_light_mode,
731                theme: Some(Theme::new(Some(v4.theme_type), ThemeMode::empty())),
732                screen_enabled: v4.screen_enabled,
733            }
734        );
735    }
736
737    #[fuchsia::test]
738    fn test_display_migration_v1_to_current() {
739        let v1 = DisplayInfoV1 {
740            manual_brightness_value: 0.6,
741            auto_brightness: true,
742            low_light_mode: LowLightMode::Enable,
743        };
744
745        let serialized_v1 = v1.serialize_to();
746        let current = DisplayInfo::try_deserialize_from(&serialized_v1)
747            .expect("deserialization should succeed");
748
749        assert_eq!(
750            current,
751            DisplayInfo {
752                manual_brightness_value: v1.manual_brightness_value,
753                auto_brightness: v1.auto_brightness,
754                low_light_mode: v1.low_light_mode,
755                theme: Some(Theme::new(Some(ThemeType::Unknown), ThemeMode::empty())),
756                // screen_enabled was added in v3.
757                screen_enabled: DisplayInfoV3::default().screen_enabled,
758                auto_brightness_value: DEFAULT_DISPLAY_INFO.auto_brightness_value,
759            }
760        );
761    }
762
763    #[fuchsia::test]
764    fn test_display_migration_v2_to_current() {
765        let v2 = DisplayInfoV2 {
766            manual_brightness_value: 0.6,
767            auto_brightness: true,
768            low_light_mode: LowLightMode::Enable,
769            theme_mode: ThemeModeV1::Light,
770        };
771
772        let serialized_v2 = v2.serialize_to();
773        let current = DisplayInfo::try_deserialize_from(&serialized_v2)
774            .expect("deserialization should succeed");
775
776        assert_eq!(
777            current,
778            DisplayInfo {
779                manual_brightness_value: v2.manual_brightness_value,
780                auto_brightness: v2.auto_brightness,
781                low_light_mode: v2.low_light_mode,
782                theme: Some(Theme::new(Some(ThemeType::Light), ThemeMode::empty())),
783                // screen_enabled was added in v3.
784                screen_enabled: DisplayInfoV3::default().screen_enabled,
785                auto_brightness_value: DEFAULT_DISPLAY_INFO.auto_brightness_value,
786            }
787        );
788    }
789
790    #[fuchsia::test]
791    fn test_display_migration_v3_to_current() {
792        let v3 = DisplayInfoV3 {
793            manual_brightness_value: 0.6,
794            auto_brightness: true,
795            low_light_mode: LowLightMode::Enable,
796            theme_mode: ThemeModeV1::Light,
797            screen_enabled: false,
798        };
799
800        let serialized_v3 = v3.serialize_to();
801        let current = DisplayInfo::try_deserialize_from(&serialized_v3)
802            .expect("deserialization should succeed");
803
804        assert_eq!(
805            current,
806            DisplayInfo {
807                manual_brightness_value: v3.manual_brightness_value,
808                auto_brightness: v3.auto_brightness,
809                low_light_mode: v3.low_light_mode,
810                theme: Some(Theme::new(Some(ThemeType::Light), ThemeMode::empty())),
811                // screen_enabled was added in v3.
812                screen_enabled: v3.screen_enabled,
813                auto_brightness_value: DEFAULT_DISPLAY_INFO.auto_brightness_value,
814            }
815        );
816    }
817
818    #[fuchsia::test]
819    fn test_display_migration_v4_to_current() {
820        let v4 = DisplayInfoV4 {
821            manual_brightness_value: 0.6,
822            auto_brightness: true,
823            low_light_mode: LowLightMode::Enable,
824            theme_type: ThemeType::Light,
825            screen_enabled: false,
826        };
827
828        let serialized_v4 = v4.serialize_to();
829        let current = DisplayInfo::try_deserialize_from(&serialized_v4)
830            .expect("deserialization should succeed");
831
832        assert_eq!(
833            current,
834            DisplayInfo {
835                manual_brightness_value: v4.manual_brightness_value,
836                auto_brightness: v4.auto_brightness,
837                low_light_mode: v4.low_light_mode,
838                theme: Some(Theme::new(Some(ThemeType::Light), ThemeMode::empty())),
839                screen_enabled: v4.screen_enabled,
840                auto_brightness_value: DEFAULT_DISPLAY_INFO.auto_brightness_value,
841            }
842        );
843    }
844
845    #[fuchsia::test]
846    fn test_display_migration_v5_to_current() {
847        let v5 = DisplayInfoV5 {
848            manual_brightness_value: 0.6,
849            auto_brightness: true,
850            low_light_mode: LowLightMode::Enable,
851            theme: Some(Theme::new(Some(ThemeType::Light), ThemeMode::AUTO)),
852            screen_enabled: false,
853        };
854
855        let serialized_v5 = v5.serialize_to();
856        let current = DisplayInfo::try_deserialize_from(&serialized_v5)
857            .expect("deserialization should succeed");
858
859        assert_eq!(
860            current,
861            DisplayInfo {
862                manual_brightness_value: v5.manual_brightness_value,
863                auto_brightness: v5.auto_brightness,
864                low_light_mode: v5.low_light_mode,
865                theme: Some(Theme::new(Some(ThemeType::Light), ThemeMode::AUTO)),
866                screen_enabled: v5.screen_enabled,
867                auto_brightness_value: DEFAULT_DISPLAY_INFO.auto_brightness_value,
868            }
869        );
870    }
871}