lib/
backlight.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 anyhow::{Context as _, Error};
6use async_trait::async_trait;
7use derivative::Derivative;
8use fidl::endpoints::ProtocolMarker;
9use fidl_fuchsia_hardware_backlight as backlight;
10
11use fidl_fuchsia_hardware_backlight::{DeviceProxy as BacklightProxy, State as BacklightCommand};
12use fidl_fuchsia_ui_display_singleton::{DisplayPowerMarker, DisplayPowerProxy, PowerMode};
13use fuchsia_async as fasync;
14use fuchsia_component::client::{Service, connect_to_protocol};
15use futures::channel::oneshot;
16use futures::lock::Mutex;
17use std::sync::Arc;
18
19/// The minimum brightness value that can be sent to the backlight service.
20const MIN_REGULATED_BRIGHTNESS: f64 = 0.0004;
21/// The maximum brightness that can be sent to the backlight service.
22const MAX_REGULATED_BRIGHTNESS: f64 = 1.0;
23
24async fn open_backlight() -> Result<BacklightProxy, Error> {
25    log::trace!("Opening backlight device");
26    let device = Service::open(backlight::ServiceMarker)
27        .context("Failed to open service")?
28        .watch_for_any()
29        .await
30        .context("Failed to find instance")?
31        .connect_to_backlight()
32        .context("Failed to connect to backlight service")?;
33    log::info!("Opening backlight");
34    Ok(device)
35}
36
37fn open_display_power_service() -> Result<DisplayPowerProxy, Error> {
38    log::info!("Opening display controller");
39    connect_to_protocol::<DisplayPowerMarker>()
40        .context("Failed to connect to display power service")
41}
42
43/// Possible combinations of backlight and display power states.
44///
45/// When powering down, the backlight must always be turned off with a delay before the DDIC is
46/// turned off.
47///
48/// When powering up, the DDIC must always be turned on with a delay before the backlight is turned
49/// on.
50#[derive(Derivative)]
51#[derivative(Debug)]
52enum PowerState {
53    /// This state should only be used as a temporary placeholder while swapping values inside
54    /// containers (e.g. Mutex).
55    Indeterminate,
56    BothOn,
57    /// The backlight is off and the DDIC is scheduled to turn off.
58    BacklightOffDisplayPoweringDown(#[derivative(Debug = "ignore")] fasync::Task<()>),
59    BothOff,
60    /// The DDIC is on and the backlight is scheduled to turn on. A sequence of backlight changes
61    /// may be queued.
62    DisplayOnBacklightPoweringUp(
63        #[derivative(Debug = "ignore")] fasync::Task<()>,
64        Vec<PendingBacklightCommand>,
65    ),
66}
67
68impl Default for PowerState {
69    fn default() -> Self {
70        Self::Indeterminate
71    }
72}
73
74/// A backlight command that is queued to be invoked after the power on delay elapses.
75#[derive(Debug)]
76struct PendingBacklightCommand {
77    command: BacklightCommand,
78    /// Will be resolved when the command is invoked (or cancelled if the command is dropped due to
79    /// a state change).
80    future_handle: oneshot::Sender<Result<(), Error>>,
81}
82
83#[derive(Debug, Clone)]
84pub struct Backlight {
85    backlight_proxy: BacklightProxy,
86    display_power: Option<DisplayPower>,
87}
88
89impl Backlight {
90    /// Creates a simple `Backlight` control, for devices on which DDIC power cannot be switched
91    /// off and on.
92    pub async fn without_display_power() -> Result<Self, Error> {
93        let backlight_proxy = open_backlight().await?;
94        Self::without_display_power_internal(backlight_proxy)
95    }
96
97    fn without_display_power_internal(backlight_proxy: BacklightProxy) -> Result<Self, Error> {
98        Ok(Backlight { backlight_proxy, display_power: None })
99    }
100
101    /// Creates a `Backlight` control that manages both the backlight brightness/power and the power
102    /// state of the DDIC.
103    #[allow(unused)]
104    pub async fn with_display_power(
105        power_off_delay_millis: u16,
106        power_on_delay_millis: u16,
107    ) -> Result<Self, Error> {
108        let backlight_proxy = open_backlight().await?;
109        let display_power_proxy = open_display_power_service()?;
110        Self::with_display_power_internal(
111            backlight_proxy,
112            display_power_proxy,
113            zx::MonotonicDuration::from_millis(power_off_delay_millis as i64),
114            zx::MonotonicDuration::from_millis(power_on_delay_millis as i64),
115        )
116        .await
117    }
118
119    async fn with_display_power_internal(
120        backlight_proxy: BacklightProxy,
121        display_power_proxy: DisplayPowerProxy,
122        power_off_delay: impl Into<zx::MonotonicDuration>,
123        power_on_delay: impl Into<zx::MonotonicDuration>,
124    ) -> Result<Self, Error> {
125        let display_power = DisplayPower::new(
126            &backlight_proxy,
127            display_power_proxy,
128            power_off_delay,
129            power_on_delay,
130        )
131        .await?;
132        Ok(Backlight { backlight_proxy, display_power: Some(display_power) })
133    }
134
135    pub async fn get_max_absolute_brightness(&self) -> Result<f64, Error> {
136        let connection = self
137            .backlight_proxy
138            .get_max_absolute_brightness()
139            .await
140            .context("Didn't connect correctly")?;
141        let max_brightness: f64 = connection
142            .map_err(zx::Status::from_raw)
143            .context("Failed to get_max_absolute_brightness")?;
144        Ok(max_brightness)
145    }
146
147    async fn get(&self) -> Result<f64, Error> {
148        let backlight_info = get_state_normalized(&self.backlight_proxy).await?;
149        assert!(backlight_info.brightness >= 0.0);
150        assert!(backlight_info.brightness <= MAX_REGULATED_BRIGHTNESS);
151        Ok(if backlight_info.backlight_on { backlight_info.brightness } else { 0.0 })
152    }
153
154    async fn set(&self, value: f64) -> Result<(), Error> {
155        // TODO(https://fxbug.dev/42111816): Handle error here as well, similar to get_brightness above. Might involve
156        let regulated_value =
157            num_traits::clamp(value, MIN_REGULATED_BRIGHTNESS, MAX_REGULATED_BRIGHTNESS);
158        let backlight_on = value > 0.0;
159
160        match self.display_power.as_ref() {
161            None => self.set_backlight_state_normalized(regulated_value, backlight_on).await,
162            Some(display_power) => {
163                self.clone().set_dual_state(display_power, regulated_value, backlight_on).await
164            }
165        }
166    }
167
168    async fn set_dual_state(
169        &self,
170        display_power: &DisplayPower,
171        regulated_value: f64,
172        backlight_on: bool,
173    ) -> Result<(), Error> {
174        let power_state_arc = display_power.power_state.clone();
175        // Note that `power_state` MUST be `drop()`ped before yielding an async value, or there will
176        // be a deadlock. Rewriting this `match` expression to not use `.await`, and hence to be
177        // able to drop the guard implicitly when it goes out of scope, would be too messy.
178        let mut power_state = power_state_arc.lock().await;
179        match &mut *power_state {
180            PowerState::BothOn => {
181                if backlight_on {
182                    // See below
183                } else {
184                    self.set_backlight_state_normalized(regulated_value, backlight_on).await?;
185                    log::info!("Turned backlight off");
186                    log::info!("DDIC power off scheduled");
187                    let task =
188                        self.clone().make_scheduled_updates_task(display_power.power_off_delay);
189                    *power_state = PowerState::BacklightOffDisplayPoweringDown(task);
190                }
191                drop(power_state);
192                self.set_backlight_state_normalized(regulated_value, backlight_on).await
193            }
194            PowerState::BacklightOffDisplayPoweringDown(_task) => {
195                if backlight_on {
196                    log::info!("DDIC power on cancelled");
197                    // Cancel the scheduled display shutdown.
198                    *power_state = PowerState::BothOn;
199                    drop(power_state);
200                    self.set_backlight_state_normalized(regulated_value, backlight_on).await
201                } else {
202                    // No-op. Already scheduled to turn off.
203                    drop(power_state);
204                    Ok(())
205                }
206            }
207            PowerState::BothOff => {
208                if backlight_on {
209                    display_power.set_display_power_and_log_errors(true).await?;
210                    let (pending_change, receiver) =
211                        Self::make_pending_change(regulated_value, backlight_on);
212                    let task =
213                        self.clone().make_scheduled_updates_task(display_power.power_on_delay);
214                    *power_state =
215                        PowerState::DisplayOnBacklightPoweringUp(task, vec![pending_change]);
216                    drop(power_state);
217                    log::info!("Backlight power on scheduled");
218                    receiver.await?
219                } else {
220                    display_power.set_display_power_and_log_errors(false).await?;
221                    drop(power_state);
222                    Ok(())
223                }
224            }
225            PowerState::DisplayOnBacklightPoweringUp(_task, ref mut pending_changes) => {
226                if backlight_on {
227                    let (pending_change, receiver) =
228                        Self::make_pending_change(regulated_value, backlight_on);
229                    pending_changes.push(pending_change);
230                    drop(power_state);
231                    receiver.await?
232                } else {
233                    log::info!("Backlight power on cancelled");
234                    // Cancel scheduled backlight power on.
235                    *power_state = PowerState::BothOff;
236                    drop(power_state);
237                    Ok(())
238                }
239            }
240            PowerState::Indeterminate => {
241                unreachable!()
242            }
243        }
244    }
245
246    fn make_scheduled_updates_task(&self, delay: zx::MonotonicDuration) -> fasync::Task<()> {
247        let time = fasync::MonotonicInstant::after(delay);
248        log::trace!("Setting timer for {:?}", &time);
249        let timer = fasync::Timer::new(time);
250        let self_ = self.clone();
251        let fut = async move {
252            log::trace!("Awaiting timer");
253            timer.await;
254            log::trace!("Timer {:?} elapsed", time);
255            self_.process_scheduled_updates().await;
256        };
257        fasync::Task::local(fut)
258    }
259
260    /// Creates a pending command that can be queued in a
261    /// [`PowerState::DisplayOnBacklightPoweringUp`] state.
262    fn make_pending_change(
263        regulated_value: f64,
264        backlight_on: bool,
265    ) -> (PendingBacklightCommand, oneshot::Receiver<Result<(), Error>>) {
266        let (sender, receiver) = oneshot::channel::<Result<(), Error>>();
267        let pending_change = PendingBacklightCommand {
268            command: BacklightCommand { backlight_on, brightness: regulated_value },
269            future_handle: sender,
270        };
271        (pending_change, receiver)
272    }
273
274    /// Process scheduled updates to the power state.
275    async fn process_scheduled_updates(&self) {
276        let self_ = self.clone();
277        match &self.display_power {
278            Some(display_power) => {
279                let power_state_arc = display_power.power_state.clone();
280                let mut power_state_guard = power_state_arc.lock().await;
281                let power_state = std::mem::take(&mut *power_state_guard);
282
283                log::debug!(
284                    "Processing scheduled updates after timer. Most recent state: {:?}",
285                    &power_state
286                );
287
288                match power_state {
289                    PowerState::BacklightOffDisplayPoweringDown(_) => {
290                        if let Ok(_) = display_power.set_display_power_and_log_errors(false).await {
291                            *power_state_guard = PowerState::BothOff;
292                        } else {
293                            // Don't get stuck in an indeterminate state, nor start a retry loop.
294                            // Subsequent calls changes to the backlight state should work normally.
295                            *power_state_guard = PowerState::BothOn;
296                        }
297                    }
298                    PowerState::DisplayOnBacklightPoweringUp(_, pending_changes) => {
299                        assert!(!pending_changes.is_empty());
300                        let mut turned_on = false;
301                        for pending_change in pending_changes.into_iter() {
302                            assert!(pending_change.command.backlight_on);
303                            let result = self_
304                                .set_backlight_state_normalized(
305                                    pending_change.command.brightness,
306                                    pending_change.command.backlight_on,
307                                )
308                                .await;
309                            // Even if a backlight command fails for some reason, we need to treat
310                            // the backlight as on. Subsequent commands should still work.
311                            *power_state_guard = PowerState::BothOn;
312                            log::debug!("Sending result for pending change {:?}", &pending_change);
313                            if let Err(e) = pending_change.future_handle.send(result) {
314                                log::warn!("Failed to send result for pending change: {:#?}", e);
315                            } else if !turned_on {
316                                turned_on = true;
317                                log::info!("Turned backlight on");
318                            }
319                        }
320                    }
321                    PowerState::Indeterminate => {
322                        unreachable!()
323                    }
324                    _ => {}
325                }
326            }
327            None => {
328                unreachable!()
329            }
330        }
331    }
332
333    async fn set_backlight_state_normalized(
334        &self,
335        regulated_value: f64,
336        backlight_on: bool,
337    ) -> Result<(), Error> {
338        log::debug!(
339            "set_state_normalized(brightness: {:.3}, backlight_on: {}",
340            regulated_value,
341            backlight_on
342        );
343        self.backlight_proxy
344            .set_state_normalized(&BacklightCommand { backlight_on, brightness: regulated_value })
345            .await?
346            .map_err(|e| zx::Status::from_raw(e))
347            .context("Failed to set backlight state")
348    }
349}
350
351/// Wrapper around [`DisplayPowerProxy`], with state management and configuration values.
352#[derive(Debug, Clone)]
353struct DisplayPower {
354    proxy: DisplayPowerProxy,
355    power_state: Arc<Mutex<PowerState>>,
356    power_off_delay: zx::MonotonicDuration,
357    power_on_delay: zx::MonotonicDuration,
358}
359
360impl DisplayPower {
361    async fn new(
362        backlight_proxy: &BacklightProxy,
363        display_power_proxy: DisplayPowerProxy,
364        power_off_delay: impl Into<zx::MonotonicDuration>,
365        power_on_delay: impl Into<zx::MonotonicDuration>,
366    ) -> Result<Self, Error> {
367        // There is no direct way to retrieve the power state of the DDIC, so we infer it from the
368        // backlight's state on startup.
369        let initial_state = if get_state_normalized(&backlight_proxy).await?.backlight_on {
370            PowerState::BothOn
371        } else {
372            PowerState::BothOff
373        };
374        log::info!("Initial power state: {:?}", &initial_state);
375
376        Ok(DisplayPower {
377            proxy: display_power_proxy,
378            power_state: Arc::new(Mutex::new(initial_state)),
379            power_off_delay: power_off_delay.into(),
380            power_on_delay: power_on_delay.into(),
381        })
382    }
383
384    async fn set_display_power_and_log_errors(&self, display_on: bool) -> Result<(), Error> {
385        let on_off = if display_on { "on" } else { "off" };
386        log::info!("Turning DDIC power {}", on_off);
387        self.proxy
388            .set_power_mode(if display_on { PowerMode::On } else { PowerMode::Off })
389            .await
390            .map_err(|fidl_error| Into::<Error>::into(fidl_error))
391            .with_context(|| format!("Failed to connect to {}", DisplayPowerMarker::DEBUG_NAME))
392            .and_then(|inner| {
393                inner.map_err(|e| {
394                    let status = zx::Status::from_raw(e);
395                    Error::from(status)
396                })
397            })
398            .with_context(|| format!("Failed to turn {on_off} display"))
399            .map_err(|e| {
400                log::error!("{:#?}", &e);
401                e
402            })?;
403        log::info!("Turned DDIC power {}", on_off);
404        Ok(())
405    }
406}
407
408async fn get_state_normalized(backlight_proxy: &BacklightProxy) -> Result<BacklightCommand, Error> {
409    backlight_proxy
410        .get_state_normalized()
411        .await?
412        .map_err(|e| zx::Status::from_raw(e))
413        .context("Failed to get_state_normalized")
414}
415
416#[async_trait]
417pub trait BacklightControl: std::fmt::Debug + Send + Sync {
418    async fn get_brightness(&self) -> Result<f64, Error>;
419    async fn set_brightness(&self, value: f64) -> Result<(), Error>;
420    async fn get_max_absolute_brightness(&self) -> Result<f64, Error>;
421}
422
423#[async_trait]
424impl BacklightControl for Backlight {
425    async fn get_brightness(&self) -> Result<f64, Error> {
426        self.get().await
427    }
428    async fn set_brightness(&self, value: f64) -> Result<(), Error> {
429        self.clone().set(value).await
430    }
431    async fn get_max_absolute_brightness(&self) -> Result<f64, Error> {
432        self.get_max_absolute_brightness().await
433    }
434}
435
436#[cfg(test)]
437mod tests {
438    use super::*;
439    use fidl::endpoints::create_proxy_and_stream;
440    use fidl_fuchsia_hardware_backlight::{
441        DeviceMarker as BacklightMarker, DeviceRequestStream as BacklightRequestStream,
442    };
443    use fuchsia_async::{self as fasync};
444    use futures::join;
445    use futures::prelude::future;
446    use futures_util::stream::StreamExt;
447
448    fn mock_backlight() -> (Arc<Backlight>, BacklightRequestStream) {
449        let (backlight_proxy, backlight_stream) = create_proxy_and_stream::<BacklightMarker>();
450
451        (
452            Arc::new(Backlight::without_display_power_internal(backlight_proxy).unwrap()),
453            backlight_stream,
454        )
455    }
456
457    async fn mock_device_set(mut reqs: BacklightRequestStream) -> BacklightCommand {
458        match reqs.next().await.unwrap() {
459            Ok(fidl_fuchsia_hardware_backlight::DeviceRequest::SetStateNormalized {
460                state: command,
461                ..
462            }) => {
463                return command;
464            }
465            request => panic!("Unexpected request: {:?}", request),
466        }
467    }
468
469    async fn mock_device_get(
470        mut reqs: BacklightRequestStream,
471        backlight_command: BacklightCommand,
472    ) {
473        match reqs.next().await.unwrap() {
474            Ok(fidl_fuchsia_hardware_backlight::DeviceRequest::GetStateNormalized {
475                responder,
476            }) => {
477                let response = backlight_command;
478                let _ = responder.send(Ok(&response));
479            }
480            Ok(fidl_fuchsia_hardware_backlight::DeviceRequest::GetMaxAbsoluteBrightness {
481                responder,
482            }) => {
483                if let Err(e) = responder.send(Ok(250.0)) {
484                    panic!("Failed to reply to GetMaxAbsoluteBrightness: {}", e);
485                }
486            }
487            request => panic!("Unexpected request: {:?}", request),
488        }
489    }
490
491    #[fasync::run_singlethreaded(test)]
492    async fn test_brightness_returns_zero_if_backlight_off() {
493        // Setup
494        let (mock, backlight_stream) = mock_backlight();
495        let backlight_fut = mock_device_get(
496            backlight_stream,
497            BacklightCommand { backlight_on: false, brightness: 0.04 },
498        );
499
500        // Act
501        let get_fut = mock.get();
502        let (brightness, _) = future::join(get_fut, backlight_fut).await;
503
504        // Assert
505        assert_eq!(brightness.unwrap(), 0.0);
506    }
507
508    #[fasync::run_singlethreaded(test)]
509    async fn test_brightness_returns_non_zero_if_backlight_on() {
510        // Setup
511        let (mock, backlight_stream) = mock_backlight();
512        let backlight_fut = mock_device_get(
513            backlight_stream,
514            BacklightCommand { backlight_on: true, brightness: 0.04 },
515        );
516
517        // Act
518        let get_fut = mock.get();
519        let (brightness, _) = future::join(get_fut, backlight_fut).await;
520
521        // Assert
522        assert_eq!(brightness.unwrap(), 0.04);
523    }
524
525    #[fasync::run_singlethreaded(test)]
526    async fn test_zero_brightness_turns_backlight_off() {
527        // Setup
528        let (mock, backlight_stream) = mock_backlight();
529        let backlight_fut = mock_device_set(backlight_stream);
530
531        // Act
532        let set_fut = mock.set(0.0);
533        let (_, backlight_command) = futures::join!(set_fut, backlight_fut);
534
535        // Assert
536        assert_eq!(backlight_command.backlight_on, false);
537    }
538
539    #[fasync::run_singlethreaded(test)]
540    async fn test_negative_brightness_turns_backlight_off() {
541        // Setup
542        let (mock, backlight_stream) = mock_backlight();
543        let backlight_fut = mock_device_set(backlight_stream);
544
545        // Act
546        let set_fut = mock.set(-0.01);
547        let (_, backlight_command) = join!(set_fut, backlight_fut);
548
549        // Assert
550        assert_eq!(backlight_command.backlight_on, false);
551    }
552
553    #[fasync::run_singlethreaded(test)]
554    async fn test_brightness_turns_backlight_on() {
555        // Setup
556        let (mock, backlight_stream) = mock_backlight();
557        let backlight_fut = mock_device_set(backlight_stream);
558
559        // Act
560        let set_fut = mock.set(0.55);
561        let (_, backlight_command) = join!(set_fut, backlight_fut);
562
563        // Assert
564        assert_eq!(backlight_command.backlight_on, true);
565        assert_eq!(backlight_command.brightness, 0.55);
566    }
567
568    #[fasync::run_singlethreaded(test)]
569    async fn test_get_max_absolute_brightness() {
570        // Setup
571        let (mock, backlight_stream) = mock_backlight();
572        let backlight_fut = mock_device_get(
573            backlight_stream,
574            BacklightCommand { backlight_on: false, brightness: 0.04 },
575        );
576
577        // Act
578        let mock_fut = mock.get_max_absolute_brightness();
579        let (max_brightness, _) = future::join(mock_fut, backlight_fut).await;
580
581        // Assert
582        assert_eq!(max_brightness.unwrap(), 250.0);
583    }
584}
585
586#[cfg(test)]
587mod dual_state_tests {
588    use super::*;
589    use assert_matches::assert_matches;
590    use fidl::endpoints::create_proxy_and_stream;
591    use fidl_fuchsia_hardware_backlight::{
592        DeviceMarker as BacklightMarker, DeviceRequestStream as BacklightRequestStream,
593    };
594    use fidl_fuchsia_ui_display_singleton::{
595        DisplayPowerRequest, DisplayPowerRequestStream, PowerMode,
596    };
597    use fuchsia_async::{self as fasync, Task};
598    use futures::prelude::future;
599    use futures::{Future, TryStreamExt};
600    use std::task::Poll;
601    use test_helpers::ResettableFuture;
602
603    #[derive(Debug, Clone)]
604    struct FakeBacklightService {
605        get_state_normalized_response: ResettableFuture<Result<BacklightCommand, i32>>,
606        set_state_normalized_response: Arc<Mutex<Result<(), i32>>>,
607    }
608
609    #[allow(dead_code)]
610    impl FakeBacklightService {
611        pub fn new() -> Self {
612            Self {
613                get_state_normalized_response: ResettableFuture::new(),
614                set_state_normalized_response: Arc::new(Mutex::new(Ok(()))),
615            }
616        }
617
618        pub fn start(&self) -> Result<(BacklightProxy, Task<()>), Error> {
619            let (proxy, stream) = create_proxy_and_stream::<BacklightMarker>();
620            let task = Task::local(self.clone().process_requests(stream));
621            Ok((proxy, task))
622        }
623
624        async fn process_requests(self, mut stream: BacklightRequestStream) {
625            use fidl_fuchsia_hardware_backlight::DeviceRequest::*;
626
627            log::debug!("FakeBacklightService::process_requests");
628            while let Ok(Some(req)) = stream.try_next().await {
629                log::debug!("FakeBacklightService: {}", req.method_name());
630                match req {
631                    GetStateNormalized { responder } => {
632                        let result = self.get_state_normalized_response.get().await;
633                        responder
634                            .send(result.as_ref().map_err(|e| *e))
635                            .expect("send GetStateNormalized");
636                    }
637                    SetStateNormalized { state, responder } => {
638                        let result = self.set_state_normalized_response.lock().await.clone();
639                        if result.is_ok() {
640                            self.set_get_state_normalized_response(Ok(state)).await;
641                        }
642                        responder.send(result).expect("send SetStateNormalized");
643                    }
644                    _ => {
645                        unimplemented!();
646                    }
647                };
648            }
649        }
650
651        pub async fn set_get_state_normalized_response(
652            &self,
653            response: Result<BacklightCommand, i32>,
654        ) {
655            self.get_state_normalized_response.set(response).await;
656        }
657
658        pub async fn clear_get_state_normalized_response(&self) {
659            self.get_state_normalized_response.clear().await;
660        }
661
662        pub async fn set_set_state_normalized_response(&self, result: Result<(), i32>) {
663            let mut guard = self.set_state_normalized_response.lock().await;
664            *guard = result;
665        }
666    }
667
668    #[derive(Debug, Clone)]
669    struct FakeDisplayPowerService {
670        set_power_mode_response: Arc<Mutex<Result<(), i32>>>,
671        last_set_power_mode_value: Arc<Mutex<Option<PowerMode>>>,
672    }
673
674    #[allow(dead_code)]
675    impl FakeDisplayPowerService {
676        pub fn new() -> Self {
677            Self {
678                set_power_mode_response: Arc::new(Mutex::new(Ok(()))),
679                last_set_power_mode_value: Arc::new(Mutex::new(None)),
680            }
681        }
682
683        pub fn start(&self) -> Result<(DisplayPowerProxy, Task<()>), Error> {
684            let (proxy, stream) = create_proxy_and_stream::<DisplayPowerMarker>();
685            let task = Task::local(self.clone().process_requests(stream));
686            Ok((proxy, task))
687        }
688
689        async fn process_requests(self, mut stream: DisplayPowerRequestStream) {
690            log::debug!("FakeDisplayPowerService::process_requests");
691            while let Ok(Some(req)) = stream.try_next().await {
692                log::debug!("FakeDisplayPowerService: {}", req.method_name());
693                match req {
694                    DisplayPowerRequest::SetPowerMode { power_mode, responder } => {
695                        let result = self.set_power_mode_response.lock().await.clone();
696                        if result.is_ok() {
697                            self.last_set_power_mode_value.lock().await.replace(power_mode);
698                        }
699                        responder.send(result).expect("send SetPowerMode");
700                    }
701                    DisplayPowerRequest::_UnknownMethod { ordinal, .. } => {
702                        panic!("Unexpected method: {}", ordinal);
703                    }
704                };
705            }
706            log::warn!("FakeDisplayPowerService stopped");
707        }
708
709        pub async fn set_set_power_mode_response(&self, response: Result<(), i32>) {
710            (*self.set_power_mode_response.lock().await) = response;
711        }
712
713        pub async fn last_set_power_mode_value(&self) -> Option<PowerMode> {
714            self.last_set_power_mode_value.lock().await.clone()
715        }
716    }
717
718    trait PollExt<T> {
719        fn into_option(self) -> Option<T>;
720        #[allow(dead_code)]
721        fn unwrap(self) -> T;
722    }
723
724    impl<T> PollExt<T> for Poll<T> {
725        fn into_option(self) -> Option<T> {
726            match self {
727                Poll::Ready(x) => Some(x),
728                Poll::Pending => None,
729            }
730        }
731
732        fn unwrap(self) -> T {
733            self.into_option().unwrap()
734        }
735    }
736
737    trait TestExecutorExt {
738        fn pin_and_run_until_stalled<F: Future>(&mut self, main_future: F) -> Option<F::Output>;
739        /// Wakes expired timers and runs any existing spawned tasks until they stall. Returns
740        /// `true` if one or more timers were awoken.
741        fn wake_timers_and_run_until_stalled(&mut self) -> bool;
742    }
743
744    impl TestExecutorExt for fasync::TestExecutor {
745        fn pin_and_run_until_stalled<F: Future>(&mut self, main_future: F) -> Option<F::Output> {
746            self.run_until_stalled(&mut Box::pin(main_future)).into_option()
747        }
748
749        fn wake_timers_and_run_until_stalled(&mut self) -> bool {
750            let did_wake_timers = self.wake_expired_timers();
751            let _ = self.run_until_stalled(&mut future::pending::<()>());
752            did_wake_timers
753        }
754    }
755
756    #[allow(dead_code)]
757    struct Handles {
758        fake_backlight_service: FakeBacklightService,
759        backlight_proxy: BacklightProxy,
760        backlight_task: Task<()>,
761
762        fake_display_power_service: FakeDisplayPowerService,
763        display_power_proxy: DisplayPowerProxy,
764        display_power_task: Task<()>,
765
766        backlight: Backlight,
767    }
768
769    impl Handles {
770        /// Note that callers need to declare a variable for the executor _before_ the handles, or
771        /// else the executor will cause a panic when it's dropped before its futures.
772        fn new(
773            power_off_delay_ms: i64,
774            power_on_delay_ms: i64,
775            initial_backlight_state: BacklightCommand,
776        ) -> (fasync::TestExecutor, Handles) {
777            let mut exec = fasync::TestExecutor::new_with_fake_time();
778            exec.set_fake_time(zx::MonotonicInstant::ZERO.into());
779
780            let fake_backlight_service = FakeBacklightService::new();
781            let (backlight_proxy, backlight_task) = fake_backlight_service.start().unwrap();
782
783            let fake_display_power_service = FakeDisplayPowerService::new();
784            let (display_power_proxy, display_power_task) =
785                fake_display_power_service.start().unwrap();
786
787            let fake_backlight_service_ = fake_backlight_service.clone();
788
789            let backlight = exec
790                .pin_and_run_until_stalled(async {
791                    fake_backlight_service_
792                        .set_get_state_normalized_response(Ok(initial_backlight_state))
793                        .await;
794
795                    Backlight::with_display_power_internal(
796                        backlight_proxy.clone(),
797                        display_power_proxy.clone(),
798                        zx::MonotonicDuration::from_millis(power_off_delay_ms),
799                        zx::MonotonicDuration::from_millis(power_on_delay_ms),
800                    )
801                    .await
802                    .unwrap()
803                })
804                .unwrap();
805
806            (
807                exec,
808                Handles {
809                    fake_backlight_service,
810                    backlight_proxy,
811                    backlight_task,
812                    fake_display_power_service,
813                    display_power_proxy,
814                    display_power_task,
815                    backlight,
816                },
817            )
818        }
819    }
820
821    #[test]
822    fn positive_brightness_changes_without_affecting_ddic() {
823        let power_off_delay_ms = 100;
824        let power_on_delay_ms = 50;
825
826        let (mut exec, h) = Handles::new(
827            power_off_delay_ms,
828            power_on_delay_ms,
829            BacklightCommand { backlight_on: true, brightness: 1.0 },
830        );
831
832        exec.pin_and_run_until_stalled(async {
833            assert_eq!(h.backlight.get().await.unwrap(), 1.0);
834
835            h.backlight.set(0.9).await.unwrap();
836            assert_eq!(h.backlight.get().await.unwrap(), 0.9);
837
838            h.backlight.set(0.5).await.unwrap();
839            assert_eq!(h.backlight.get().await.unwrap(), 0.5);
840
841            assert_eq!(h.fake_display_power_service.last_set_power_mode_value().await, None)
842        })
843        .unwrap();
844    }
845
846    #[test]
847    fn zero_brightness_turns_ddic_off() {
848        let power_off_delay_ms = 100;
849        let power_on_delay_ms = 50;
850
851        let (mut exec, h) = Handles::new(
852            power_off_delay_ms,
853            power_on_delay_ms,
854            BacklightCommand { backlight_on: true, brightness: 1.0 },
855        );
856
857        exec.pin_and_run_until_stalled(async {
858            assert_eq!(h.backlight.get().await.unwrap(), 1.0);
859            assert_eq!(h.fake_display_power_service.last_set_power_mode_value().await, None);
860
861            h.backlight.set(0.0).await.unwrap();
862            assert_eq!(h.backlight.get().await.unwrap(), 0.0);
863            assert_eq!(h.fake_display_power_service.last_set_power_mode_value().await, None);
864        })
865        .unwrap();
866
867        // Right before the power-off delay
868        exec.set_fake_time(
869            (zx::MonotonicInstant::ZERO
870                + zx::MonotonicDuration::from_millis(power_off_delay_ms - 1))
871            .into(),
872        );
873        assert_eq!(exec.wake_timers_and_run_until_stalled(), false);
874
875        exec.pin_and_run_until_stalled(async {
876            assert_eq!(h.fake_display_power_service.last_set_power_mode_value().await, None);
877        })
878        .unwrap();
879
880        // Right after the power-off delay.
881        exec.set_fake_time(
882            (zx::MonotonicInstant::ZERO
883                + zx::MonotonicDuration::from_millis(power_off_delay_ms + 1))
884            .into(),
885        );
886        assert_eq!(exec.wake_timers_and_run_until_stalled(), true);
887
888        exec.pin_and_run_until_stalled(async {
889            assert_eq!(
890                h.fake_display_power_service.last_set_power_mode_value().await,
891                Some(PowerMode::Off)
892            );
893        })
894        .unwrap();
895    }
896
897    #[test]
898    fn backlight_turns_on_after_ddic() {
899        let power_off_delay_ms = 100;
900        let power_on_delay_ms = 50;
901
902        let (mut exec, h) = Handles::new(
903            power_off_delay_ms,
904            power_on_delay_ms,
905            BacklightCommand { backlight_on: false, brightness: MIN_REGULATED_BRIGHTNESS },
906        );
907
908        exec.pin_and_run_until_stalled(async {
909            assert_eq!(h.backlight.get().await.unwrap(), 0.0);
910            assert_eq!(h.fake_display_power_service.last_set_power_mode_value().await, None);
911        })
912        .unwrap();
913
914        let backlight_ = h.backlight.clone();
915
916        let mut turn_on_backlight_fut = Box::pin(async {
917            backlight_.set(0.1).await.unwrap();
918        });
919        assert_matches!(exec.run_until_stalled(&mut turn_on_backlight_fut), Poll::Pending);
920
921        exec.pin_and_run_until_stalled(async {
922            assert_eq!(h.backlight.get().await.unwrap(), 0.0);
923            assert_eq!(
924                h.fake_display_power_service.last_set_power_mode_value().await,
925                Some(PowerMode::On)
926            );
927        })
928        .unwrap();
929
930        exec.set_fake_time(
931            (zx::MonotonicInstant::ZERO
932                + zx::MonotonicDuration::from_millis(power_on_delay_ms - 1))
933            .into(),
934        );
935        assert_eq!(exec.wake_timers_and_run_until_stalled(), false);
936        assert_matches!(exec.run_until_stalled(&mut turn_on_backlight_fut), Poll::Pending);
937        exec.pin_and_run_until_stalled(async {
938            assert_eq!(h.backlight.get().await.unwrap(), 0.0);
939        })
940        .unwrap();
941
942        exec.set_fake_time(
943            (zx::MonotonicInstant::ZERO
944                + zx::MonotonicDuration::from_millis(power_on_delay_ms + 1))
945            .into(),
946        );
947        assert_eq!(exec.wake_timers_and_run_until_stalled(), true);
948        assert_matches!(exec.run_until_stalled(&mut turn_on_backlight_fut), Poll::Ready(()));
949        exec.pin_and_run_until_stalled(async {
950            assert_eq!(h.backlight.get().await.unwrap(), 0.1);
951        })
952        .unwrap();
953    }
954
955    #[test]
956    fn repeated_backlight_off_commands_do_not_affect_ddic() {
957        let power_off_delay_ms = 100;
958        let power_on_delay_ms = 50;
959
960        let (mut exec, h) = Handles::new(
961            power_off_delay_ms,
962            power_on_delay_ms,
963            BacklightCommand { backlight_on: false, brightness: MIN_REGULATED_BRIGHTNESS },
964        );
965
966        exec.pin_and_run_until_stalled(async {
967            assert_eq!(h.backlight.get().await.unwrap(), 0.0);
968            assert_eq!(h.fake_display_power_service.last_set_power_mode_value().await, None);
969
970            h.backlight.set(0.0).await.unwrap();
971            assert_eq!(h.backlight.get().await.unwrap(), 0.0);
972            assert_eq!(
973                h.fake_display_power_service.last_set_power_mode_value().await,
974                Some(PowerMode::Off)
975            );
976        })
977        .unwrap();
978    }
979
980    #[test]
981    fn ddic_power_off_is_preempted_by_backlight_on_commands() {
982        let power_off_delay_ms = 100;
983        let power_on_delay_ms = 50;
984
985        let (mut exec, h) = Handles::new(
986            power_off_delay_ms,
987            power_on_delay_ms,
988            BacklightCommand { backlight_on: true, brightness: 1.0 },
989        );
990
991        exec.pin_and_run_until_stalled(async {
992            h.backlight.set(0.0).await.unwrap();
993            assert_eq!(h.backlight.get().await.unwrap(), 0.0);
994            assert_eq!(h.fake_display_power_service.last_set_power_mode_value().await, None);
995        })
996        .unwrap();
997
998        // Right before the power-off delay
999        exec.set_fake_time(
1000            (zx::MonotonicInstant::ZERO
1001                + zx::MonotonicDuration::from_millis(power_off_delay_ms - 10))
1002            .into(),
1003        );
1004        assert_eq!(exec.wake_timers_and_run_until_stalled(), false);
1005
1006        exec.pin_and_run_until_stalled(async {
1007            assert_eq!(h.fake_display_power_service.last_set_power_mode_value().await, None);
1008        })
1009        .unwrap();
1010
1011        exec.pin_and_run_until_stalled(async {
1012            h.backlight.set(0.5).await.unwrap();
1013            assert_eq!(h.backlight.get().await.unwrap(), 0.5);
1014            assert_eq!(h.fake_display_power_service.last_set_power_mode_value().await, None);
1015        })
1016        .unwrap();
1017
1018        // Right after the power-off delay.
1019        exec.set_fake_time(
1020            (zx::MonotonicInstant::ZERO
1021                + zx::MonotonicDuration::from_millis(power_off_delay_ms + 10))
1022            .into(),
1023        );
1024        // The timer task should have been canceled (dropped).
1025        assert_eq!(exec.wake_timers_and_run_until_stalled(), false);
1026
1027        exec.pin_and_run_until_stalled(async {
1028            assert_eq!(h.fake_display_power_service.last_set_power_mode_value().await, None);
1029        })
1030        .unwrap();
1031    }
1032
1033    #[test]
1034    fn backlight_power_on_is_preempted_by_ddic_off_commands() {
1035        let power_off_delay_ms = 100;
1036        let power_on_delay_ms = 50;
1037
1038        let (mut exec, h) = Handles::new(
1039            power_off_delay_ms,
1040            power_on_delay_ms,
1041            BacklightCommand { backlight_on: false, brightness: MIN_REGULATED_BRIGHTNESS },
1042        );
1043
1044        let mut turn_on_backlight_1_fut = Box::pin(h.backlight.set(0.1));
1045        let mut turn_on_backlight_2_fut = Box::pin(h.backlight.set(0.2));
1046        assert_matches!(exec.run_until_stalled(&mut turn_on_backlight_1_fut), Poll::Pending);
1047        assert_matches!(exec.run_until_stalled(&mut turn_on_backlight_2_fut), Poll::Pending);
1048
1049        exec.set_fake_time(
1050            (zx::MonotonicInstant::ZERO
1051                + zx::MonotonicDuration::from_millis(power_on_delay_ms - 1))
1052            .into(),
1053        );
1054        assert_eq!(exec.wake_timers_and_run_until_stalled(), false);
1055        assert_matches!(exec.run_until_stalled(&mut turn_on_backlight_1_fut), Poll::Pending);
1056        assert_matches!(exec.run_until_stalled(&mut turn_on_backlight_2_fut), Poll::Pending);
1057
1058        let turn_off_backlight_fut = Box::pin(h.backlight.set(0.0));
1059        exec.pin_and_run_until_stalled(async {
1060            assert_matches!(turn_off_backlight_fut.await, Ok(()));
1061            // The futures that would have turned on the backlight should be cancelled.
1062            assert_matches!(
1063                turn_on_backlight_1_fut.await.unwrap_err().downcast::<oneshot::Canceled>(),
1064                Ok(_)
1065            );
1066            assert_matches!(
1067                turn_on_backlight_2_fut.await.unwrap_err().downcast::<oneshot::Canceled>(),
1068                Ok(_)
1069            );
1070
1071            assert_eq!(h.backlight.get().await.unwrap(), 0.0);
1072        })
1073        .unwrap();
1074
1075        exec.set_fake_time(
1076            (zx::MonotonicInstant::ZERO
1077                + zx::MonotonicDuration::from_millis(power_on_delay_ms + 1))
1078            .into(),
1079        );
1080        assert_eq!(exec.wake_timers_and_run_until_stalled(), false);
1081
1082        exec.pin_and_run_until_stalled(async {
1083            assert_eq!(h.backlight.get().await.unwrap(), 0.0);
1084        })
1085        .unwrap();
1086    }
1087
1088    #[test]
1089    fn error_in_ddic_power_off_does_not_affect_later_backlight_commands() {
1090        let power_off_delay_ms = 100;
1091        let power_on_delay_ms = 50;
1092
1093        let (mut exec, h) = Handles::new(
1094            power_off_delay_ms,
1095            power_on_delay_ms,
1096            BacklightCommand { backlight_on: true, brightness: 1.0 },
1097        );
1098
1099        exec.pin_and_run_until_stalled(async {
1100            h.fake_display_power_service
1101                .set_set_power_mode_response(Err(zx::Status::BAD_STATE.into_raw()))
1102                .await;
1103
1104            assert_eq!(h.backlight.get().await.unwrap(), 1.0);
1105            assert_eq!(h.fake_display_power_service.last_set_power_mode_value().await, None);
1106
1107            h.backlight.set(0.0).await.unwrap();
1108            assert_eq!(h.backlight.get().await.unwrap(), 0.0);
1109            assert_eq!(h.fake_display_power_service.last_set_power_mode_value().await, None);
1110        })
1111        .unwrap();
1112
1113        exec.set_fake_time(
1114            (zx::MonotonicInstant::ZERO
1115                + zx::MonotonicDuration::from_millis(power_off_delay_ms + 1))
1116            .into(),
1117        );
1118        assert_eq!(exec.wake_timers_and_run_until_stalled(), true);
1119
1120        exec.pin_and_run_until_stalled(async {
1121            assert_eq!(h.fake_display_power_service.last_set_power_mode_value().await, None);
1122
1123            h.backlight.set(0.5).await.unwrap();
1124            assert_eq!(h.backlight.get().await.unwrap(), 0.5);
1125            assert_eq!(h.fake_display_power_service.last_set_power_mode_value().await, None);
1126        })
1127        .unwrap();
1128    }
1129
1130    #[test]
1131    fn error_in_ddic_power_on_is_recoverable() {
1132        let power_off_delay_ms = 100;
1133        let power_on_delay_ms = 50;
1134
1135        let (mut exec, h) = Handles::new(
1136            power_off_delay_ms,
1137            power_on_delay_ms,
1138            BacklightCommand { backlight_on: false, brightness: MIN_REGULATED_BRIGHTNESS },
1139        );
1140
1141        exec.pin_and_run_until_stalled(async {
1142            h.fake_display_power_service
1143                .set_set_power_mode_response(Err(zx::Status::UNAVAILABLE.into_raw()))
1144                .await;
1145
1146            assert_matches!(h.backlight.set(0.5).await, Err(_));
1147            assert_eq!(h.fake_display_power_service.last_set_power_mode_value().await, None);
1148
1149            h.fake_display_power_service.set_set_power_mode_response(Ok(())).await;
1150        })
1151        .unwrap();
1152
1153        let mut retry_fut = Box::pin(h.backlight.set(0.7));
1154        assert_matches!(exec.run_until_stalled(&mut retry_fut), Poll::Pending);
1155
1156        exec.set_fake_time(
1157            (zx::MonotonicInstant::ZERO
1158                + zx::MonotonicDuration::from_millis(power_on_delay_ms + 1))
1159            .into(),
1160        );
1161        assert_eq!(exec.wake_timers_and_run_until_stalled(), true);
1162
1163        assert_matches!(exec.run_until_stalled(&mut retry_fut), Poll::Ready(Ok(())));
1164    }
1165
1166    #[test]
1167    fn ddic_does_not_power_off_if_backlight_fails_to_power_off() {
1168        let power_off_delay_ms = 100;
1169        let power_on_delay_ms = 50;
1170
1171        let (mut exec, h) = Handles::new(
1172            power_off_delay_ms,
1173            power_on_delay_ms,
1174            BacklightCommand { backlight_on: true, brightness: 0.5 },
1175        );
1176
1177        exec.pin_and_run_until_stalled(async {
1178            h.fake_backlight_service
1179                .set_set_state_normalized_response(Err(zx::Status::NO_RESOURCES.into_raw()))
1180                .await;
1181            assert_matches!(h.backlight.set(0.0).await, Err(_));
1182        })
1183        .unwrap();
1184
1185        exec.set_fake_time(
1186            (zx::MonotonicInstant::ZERO
1187                + zx::MonotonicDuration::from_millis(power_off_delay_ms + 1))
1188            .into(),
1189        );
1190        assert_eq!(exec.wake_timers_and_run_until_stalled(), false);
1191
1192        exec.pin_and_run_until_stalled(async {
1193            assert_eq!(h.fake_display_power_service.last_set_power_mode_value().await, None);
1194        })
1195        .unwrap();
1196    }
1197}