use anyhow::{Context as _, Error};
use async_trait::async_trait;
use derivative::Derivative;
use fidl::endpoints::ProtocolMarker;
use fidl_fuchsia_hardware_backlight::{
DeviceMarker as BacklightMarker, DeviceProxy as BacklightProxy, State as BacklightCommand,
};
use fidl_fuchsia_ui_display_internal::{DisplayPowerMarker, DisplayPowerProxy};
use fuchsia_async as fasync;
use fuchsia_component::client::connect_to_protocol;
use futures::channel::oneshot;
use futures::lock::Mutex;
use std::sync::Arc;
const MIN_REGULATED_BRIGHTNESS: f64 = 0.0004;
const MAX_REGULATED_BRIGHTNESS: f64 = 1.0;
fn open_backlight() -> Result<BacklightProxy, Error> {
log::info!("Opening backlight");
let (proxy, server) = fidl::endpoints::create_proxy::<BacklightMarker>();
fdio::service_connect("/dev/class/backlight/000", server.into_channel())
.context("Failed to connect built-in service")?;
Ok(proxy)
}
fn open_display_power_service() -> Result<DisplayPowerProxy, Error> {
log::info!("Opening display controller");
connect_to_protocol::<DisplayPowerMarker>()
.context("Failed to connect to display power service")
}
#[derive(Derivative)]
#[derivative(Debug)]
enum PowerState {
Indeterminate,
BothOn,
BacklightOffDisplayPoweringDown(#[derivative(Debug = "ignore")] fasync::Task<()>),
BothOff,
DisplayOnBacklightPoweringUp(
#[derivative(Debug = "ignore")] fasync::Task<()>,
Vec<PendingBacklightCommand>,
),
}
impl Default for PowerState {
fn default() -> Self {
Self::Indeterminate
}
}
#[derive(Debug)]
struct PendingBacklightCommand {
command: BacklightCommand,
future_handle: oneshot::Sender<Result<(), Error>>,
}
#[derive(Debug, Clone)]
pub struct Backlight {
backlight_proxy: BacklightProxy,
display_power: Option<DisplayPower>,
}
impl Backlight {
pub fn without_display_power() -> Result<Self, Error> {
let backlight_proxy = open_backlight()?;
Self::without_display_power_internal(backlight_proxy)
}
fn without_display_power_internal(backlight_proxy: BacklightProxy) -> Result<Self, Error> {
Ok(Backlight { backlight_proxy, display_power: None })
}
#[allow(unused)]
pub async fn with_display_power(
power_off_delay_millis: u16,
power_on_delay_millis: u16,
) -> Result<Self, Error> {
let backlight_proxy = open_backlight()?;
let display_power_proxy = open_display_power_service()?;
Self::with_display_power_internal(
backlight_proxy,
display_power_proxy,
zx::MonotonicDuration::from_millis(power_off_delay_millis as i64),
zx::MonotonicDuration::from_millis(power_on_delay_millis as i64),
)
.await
}
async fn with_display_power_internal(
backlight_proxy: BacklightProxy,
display_power_proxy: DisplayPowerProxy,
power_off_delay: impl Into<zx::MonotonicDuration>,
power_on_delay: impl Into<zx::MonotonicDuration>,
) -> Result<Self, Error> {
let display_power = DisplayPower::new(
&backlight_proxy,
display_power_proxy,
power_off_delay,
power_on_delay,
)
.await?;
Ok(Backlight { backlight_proxy, display_power: Some(display_power) })
}
pub async fn get_max_absolute_brightness(&self) -> Result<f64, Error> {
let connection = self
.backlight_proxy
.get_max_absolute_brightness()
.await
.context("Didn't connect correctly")?;
let max_brightness: f64 = connection
.map_err(zx::Status::from_raw)
.context("Failed to get_max_absolute_brightness")?;
Ok(max_brightness)
}
async fn get(&self) -> Result<f64, Error> {
let backlight_info = get_state_normalized(&self.backlight_proxy).await?;
assert!(backlight_info.brightness >= 0.0);
assert!(backlight_info.brightness <= MAX_REGULATED_BRIGHTNESS);
Ok(if backlight_info.backlight_on { backlight_info.brightness } else { 0.0 })
}
async fn set(&self, value: f64) -> Result<(), Error> {
let regulated_value =
num_traits::clamp(value, MIN_REGULATED_BRIGHTNESS, MAX_REGULATED_BRIGHTNESS);
let backlight_on = value > 0.0;
match self.display_power.as_ref() {
None => self.set_backlight_state_normalized(regulated_value, backlight_on).await,
Some(display_power) => {
self.clone().set_dual_state(display_power, regulated_value, backlight_on).await
}
}
}
async fn set_dual_state(
&self,
display_power: &DisplayPower,
regulated_value: f64,
backlight_on: bool,
) -> Result<(), Error> {
let power_state_arc = display_power.power_state.clone();
let mut power_state = power_state_arc.lock().await;
match &mut *power_state {
PowerState::BothOn => {
if backlight_on {
} else {
self.set_backlight_state_normalized(regulated_value, backlight_on).await?;
log::info!("Turned backlight off");
log::info!("DDIC power off scheduled");
let task =
self.clone().make_scheduled_updates_task(display_power.power_off_delay);
*power_state = PowerState::BacklightOffDisplayPoweringDown(task);
}
drop(power_state);
self.set_backlight_state_normalized(regulated_value, backlight_on).await
}
PowerState::BacklightOffDisplayPoweringDown(_task) => {
if backlight_on {
log::info!("DDIC power on cancelled");
*power_state = PowerState::BothOn;
drop(power_state);
self.set_backlight_state_normalized(regulated_value, backlight_on).await
} else {
drop(power_state);
Ok(())
}
}
PowerState::BothOff => {
if backlight_on {
display_power.set_display_power_and_log_errors(true).await?;
let (pending_change, receiver) =
Self::make_pending_change(regulated_value, backlight_on);
let task =
self.clone().make_scheduled_updates_task(display_power.power_on_delay);
*power_state =
PowerState::DisplayOnBacklightPoweringUp(task, vec![pending_change]);
drop(power_state);
log::info!("Backlight power on scheduled");
receiver.await?
} else {
display_power.set_display_power_and_log_errors(false).await?;
drop(power_state);
Ok(())
}
}
PowerState::DisplayOnBacklightPoweringUp(_task, ref mut pending_changes) => {
if backlight_on {
let (pending_change, receiver) =
Self::make_pending_change(regulated_value, backlight_on);
pending_changes.push(pending_change);
drop(power_state);
receiver.await?
} else {
log::info!("Backlight power on cancelled");
*power_state = PowerState::BothOff;
drop(power_state);
Ok(())
}
}
PowerState::Indeterminate => {
unreachable!()
}
}
}
fn make_scheduled_updates_task(&self, delay: zx::MonotonicDuration) -> fasync::Task<()> {
let time = fasync::MonotonicInstant::after(delay);
log::trace!("Setting timer for {:?}", &time);
let timer = fasync::Timer::new(time);
let self_ = self.clone();
let fut = async move {
log::trace!("Awaiting timer");
timer.await;
log::trace!("Timer {:?} elapsed", time);
self_.process_scheduled_updates().await;
};
fasync::Task::local(fut)
}
fn make_pending_change(
regulated_value: f64,
backlight_on: bool,
) -> (PendingBacklightCommand, oneshot::Receiver<Result<(), Error>>) {
let (sender, receiver) = oneshot::channel::<Result<(), Error>>();
let pending_change = PendingBacklightCommand {
command: BacklightCommand { backlight_on, brightness: regulated_value },
future_handle: sender,
};
(pending_change, receiver)
}
async fn process_scheduled_updates(&self) {
let self_ = self.clone();
match &self.display_power {
Some(display_power) => {
let power_state_arc = display_power.power_state.clone();
let mut power_state_guard = power_state_arc.lock().await;
let power_state = std::mem::take(&mut *power_state_guard);
log::debug!(
"Processing scheduled updates after timer. Most recent state: {:?}",
&power_state
);
match power_state {
PowerState::BacklightOffDisplayPoweringDown(_) => {
if let Ok(_) = display_power.set_display_power_and_log_errors(false).await {
*power_state_guard = PowerState::BothOff;
} else {
*power_state_guard = PowerState::BothOn;
}
}
PowerState::DisplayOnBacklightPoweringUp(_, pending_changes) => {
assert!(!pending_changes.is_empty());
let mut turned_on = false;
for pending_change in pending_changes.into_iter() {
assert!(pending_change.command.backlight_on);
let result = self_
.set_backlight_state_normalized(
pending_change.command.brightness,
pending_change.command.backlight_on,
)
.await;
*power_state_guard = PowerState::BothOn;
log::debug!("Sending result for pending change {:?}", &pending_change);
if let Err(e) = pending_change.future_handle.send(result) {
log::warn!("Failed to send result for pending change: {:#?}", e);
} else if !turned_on {
turned_on = true;
log::info!("Turned backlight on");
}
}
}
PowerState::Indeterminate => {
unreachable!()
}
_ => {}
}
}
None => {
unreachable!()
}
}
}
async fn set_backlight_state_normalized(
&self,
regulated_value: f64,
backlight_on: bool,
) -> Result<(), Error> {
log::debug!(
"set_state_normalized(brightness: {:.3}, backlight_on: {}",
regulated_value,
backlight_on
);
self.backlight_proxy
.set_state_normalized(&BacklightCommand { backlight_on, brightness: regulated_value })
.await?
.map_err(|e| zx::Status::from_raw(e))
.context("Failed to set backlight state")
}
}
#[derive(Debug, Clone)]
struct DisplayPower {
proxy: DisplayPowerProxy,
power_state: Arc<Mutex<PowerState>>,
power_off_delay: zx::MonotonicDuration,
power_on_delay: zx::MonotonicDuration,
}
impl DisplayPower {
async fn new(
backlight_proxy: &BacklightProxy,
display_power_proxy: DisplayPowerProxy,
power_off_delay: impl Into<zx::MonotonicDuration>,
power_on_delay: impl Into<zx::MonotonicDuration>,
) -> Result<Self, Error> {
let initial_state = if get_state_normalized(&backlight_proxy).await?.backlight_on {
PowerState::BothOn
} else {
PowerState::BothOff
};
log::info!("Initial power state: {:?}", &initial_state);
Ok(DisplayPower {
proxy: display_power_proxy,
power_state: Arc::new(Mutex::new(initial_state)),
power_off_delay: power_off_delay.into(),
power_on_delay: power_on_delay.into(),
})
}
async fn set_display_power_and_log_errors(&self, display_on: bool) -> Result<(), Error> {
let on_off = if display_on { "on" } else { "off" };
log::info!("Turning DDIC power {}", on_off);
self.proxy
.set_display_power(display_on)
.await
.map_err(|fidl_error| Into::<Error>::into(fidl_error))
.with_context(|| format!("Failed to connect to {}", DisplayPowerMarker::DEBUG_NAME))
.and_then(|inner| {
inner.map_err(|e| {
let status = zx::Status::from_raw(e);
Error::from(status)
})
})
.with_context(|| format!("Failed to turn {on_off} display"))
.map_err(|e| {
log::error!("{:#?}", &e);
e
})?;
log::info!("Turned DDIC power {}", on_off);
Ok(())
}
}
async fn get_state_normalized(backlight_proxy: &BacklightProxy) -> Result<BacklightCommand, Error> {
backlight_proxy
.get_state_normalized()
.await?
.map_err(|e| zx::Status::from_raw(e))
.context("Failed to get_state_normalized")
}
#[async_trait]
pub trait BacklightControl: std::fmt::Debug + Send + Sync {
async fn get_brightness(&self) -> Result<f64, Error>;
async fn set_brightness(&self, value: f64) -> Result<(), Error>;
async fn get_max_absolute_brightness(&self) -> Result<f64, Error>;
}
#[async_trait]
impl BacklightControl for Backlight {
async fn get_brightness(&self) -> Result<f64, Error> {
self.get().await
}
async fn set_brightness(&self, value: f64) -> Result<(), Error> {
self.clone().set(value).await
}
async fn get_max_absolute_brightness(&self) -> Result<f64, Error> {
self.get_max_absolute_brightness().await
}
}
#[cfg(test)]
mod tests {
use super::*;
use fidl::endpoints::create_proxy_and_stream;
use fidl_fuchsia_hardware_backlight::DeviceRequestStream as BacklightRequestStream;
use fuchsia_async::{self as fasync};
use futures::join;
use futures::prelude::future;
use futures_util::stream::StreamExt;
fn mock_backlight() -> (Arc<Backlight>, BacklightRequestStream) {
let (backlight_proxy, backlight_stream) = create_proxy_and_stream::<BacklightMarker>();
(
Arc::new(Backlight::without_display_power_internal(backlight_proxy).unwrap()),
backlight_stream,
)
}
async fn mock_device_set(mut reqs: BacklightRequestStream) -> BacklightCommand {
match reqs.next().await.unwrap() {
Ok(fidl_fuchsia_hardware_backlight::DeviceRequest::SetStateNormalized {
state: command,
..
}) => {
return command;
}
request => panic!("Unexpected request: {:?}", request),
}
}
async fn mock_device_get(
mut reqs: BacklightRequestStream,
backlight_command: BacklightCommand,
) {
match reqs.next().await.unwrap() {
Ok(fidl_fuchsia_hardware_backlight::DeviceRequest::GetStateNormalized {
responder,
}) => {
let response = backlight_command;
let _ = responder.send(Ok(&response));
}
Ok(fidl_fuchsia_hardware_backlight::DeviceRequest::GetMaxAbsoluteBrightness {
responder,
}) => {
if let Err(e) = responder.send(Ok(250.0)) {
panic!("Failed to reply to GetMaxAbsoluteBrightness: {}", e);
}
}
request => panic!("Unexpected request: {:?}", request),
}
}
#[fasync::run_singlethreaded(test)]
async fn test_brightness_returns_zero_if_backlight_off() {
let (mock, backlight_stream) = mock_backlight();
let backlight_fut = mock_device_get(
backlight_stream,
BacklightCommand { backlight_on: false, brightness: 0.04 },
);
let get_fut = mock.get();
let (brightness, _) = future::join(get_fut, backlight_fut).await;
assert_eq!(brightness.unwrap(), 0.0);
}
#[fasync::run_singlethreaded(test)]
async fn test_brightness_returns_non_zero_if_backlight_on() {
let (mock, backlight_stream) = mock_backlight();
let backlight_fut = mock_device_get(
backlight_stream,
BacklightCommand { backlight_on: true, brightness: 0.04 },
);
let get_fut = mock.get();
let (brightness, _) = future::join(get_fut, backlight_fut).await;
assert_eq!(brightness.unwrap(), 0.04);
}
#[fasync::run_singlethreaded(test)]
async fn test_zero_brightness_turns_backlight_off() {
let (mock, backlight_stream) = mock_backlight();
let backlight_fut = mock_device_set(backlight_stream);
let set_fut = mock.set(0.0);
let (_, backlight_command) = futures::join!(set_fut, backlight_fut);
assert_eq!(backlight_command.backlight_on, false);
}
#[fasync::run_singlethreaded(test)]
async fn test_negative_brightness_turns_backlight_off() {
let (mock, backlight_stream) = mock_backlight();
let backlight_fut = mock_device_set(backlight_stream);
let set_fut = mock.set(-0.01);
let (_, backlight_command) = join!(set_fut, backlight_fut);
assert_eq!(backlight_command.backlight_on, false);
}
#[fasync::run_singlethreaded(test)]
async fn test_brightness_turns_backlight_on() {
let (mock, backlight_stream) = mock_backlight();
let backlight_fut = mock_device_set(backlight_stream);
let set_fut = mock.set(0.55);
let (_, backlight_command) = join!(set_fut, backlight_fut);
assert_eq!(backlight_command.backlight_on, true);
assert_eq!(backlight_command.brightness, 0.55);
}
#[fasync::run_singlethreaded(test)]
async fn test_get_max_absolute_brightness() {
let (mock, backlight_stream) = mock_backlight();
let backlight_fut = mock_device_get(
backlight_stream,
BacklightCommand { backlight_on: false, brightness: 0.04 },
);
let mock_fut = mock.get_max_absolute_brightness();
let (max_brightness, _) = future::join(mock_fut, backlight_fut).await;
assert_eq!(max_brightness.unwrap(), 250.0);
}
}
#[cfg(test)]
mod dual_state_tests {
use super::*;
use assert_matches::assert_matches;
use fidl::endpoints::create_proxy_and_stream;
use fidl_fuchsia_hardware_backlight::DeviceRequestStream as BacklightRequestStream;
use fidl_fuchsia_ui_display_internal::{DisplayPowerRequest, DisplayPowerRequestStream};
use fuchsia_async::{self as fasync, Task};
use futures::prelude::future;
use futures::{Future, TryStreamExt};
use std::task::Poll;
use test_helpers::ResettableFuture;
#[derive(Debug, Clone)]
struct FakeBacklightService {
get_state_normalized_response: ResettableFuture<Result<BacklightCommand, i32>>,
set_state_normalized_response: Arc<Mutex<Result<(), i32>>>,
}
#[allow(dead_code)]
impl FakeBacklightService {
pub fn new() -> Self {
Self {
get_state_normalized_response: ResettableFuture::new(),
set_state_normalized_response: Arc::new(Mutex::new(Ok(()))),
}
}
pub fn start(&self) -> Result<(BacklightProxy, Task<()>), Error> {
let (proxy, stream) = create_proxy_and_stream::<BacklightMarker>();
let task = Task::local(self.clone().process_requests(stream));
Ok((proxy, task))
}
async fn process_requests(self, mut stream: BacklightRequestStream) {
use fidl_fuchsia_hardware_backlight::DeviceRequest::*;
log::debug!("FakeBacklightService::process_requests");
while let Ok(Some(req)) = stream.try_next().await {
log::debug!("FakeBacklightService: {}", req.method_name());
match req {
GetStateNormalized { responder } => {
let result = self.get_state_normalized_response.get().await;
responder
.send(result.as_ref().map_err(|e| *e))
.expect("send GetStateNormalized");
}
SetStateNormalized { state, responder } => {
let result = self.set_state_normalized_response.lock().await.clone();
if result.is_ok() {
self.set_get_state_normalized_response(Ok(state)).await;
}
responder.send(result).expect("send SetStateNormalized");
}
_ => {
unimplemented!();
}
};
}
}
pub async fn set_get_state_normalized_response(
&self,
response: Result<BacklightCommand, i32>,
) {
self.get_state_normalized_response.set(response).await;
}
pub async fn clear_get_state_normalized_response(&self) {
self.get_state_normalized_response.clear().await;
}
pub async fn set_set_state_normalized_response(&self, result: Result<(), i32>) {
let mut guard = self.set_state_normalized_response.lock().await;
*guard = result;
}
}
#[derive(Debug, Clone)]
struct FakeDisplayPowerService {
set_display_power_response: Arc<Mutex<Result<(), i32>>>,
last_set_display_power_value: Arc<Mutex<Option<bool>>>,
}
#[allow(dead_code)]
impl FakeDisplayPowerService {
pub fn new() -> Self {
Self {
set_display_power_response: Arc::new(Mutex::new(Ok(()))),
last_set_display_power_value: Arc::new(Mutex::new(None)),
}
}
pub fn start(&self) -> Result<(DisplayPowerProxy, Task<()>), Error> {
let (proxy, stream) = create_proxy_and_stream::<DisplayPowerMarker>();
let task = Task::local(self.clone().process_requests(stream));
Ok((proxy, task))
}
async fn process_requests(self, mut stream: DisplayPowerRequestStream) {
log::debug!("FakeDisplayPowerService::process_requests");
while let Ok(Some(req)) = stream.try_next().await {
log::debug!("FakeDisplayPowerService: {}", req.method_name());
match req {
DisplayPowerRequest::SetDisplayPower { power_on, responder } => {
let result = self.set_display_power_response.lock().await.clone();
if result.is_ok() {
self.last_set_display_power_value.lock().await.replace(power_on);
}
responder.send(result).expect("send SetDisplayPower");
}
};
}
log::warn!("FakeDisplayPowerService stopped");
}
pub async fn set_set_display_power_response(&self, response: Result<(), i32>) {
(*self.set_display_power_response.lock().await) = response;
}
pub async fn last_set_display_power_value(&self) -> Option<bool> {
self.last_set_display_power_value.lock().await.clone()
}
}
trait PollExt<T> {
fn into_option(self) -> Option<T>;
#[allow(dead_code)]
fn unwrap(self) -> T;
}
impl<T> PollExt<T> for Poll<T> {
fn into_option(self) -> Option<T> {
match self {
Poll::Ready(x) => Some(x),
Poll::Pending => None,
}
}
fn unwrap(self) -> T {
self.into_option().unwrap()
}
}
trait TestExecutorExt {
fn pin_and_run_until_stalled<F: Future>(&mut self, main_future: F) -> Option<F::Output>;
fn wake_timers_and_run_until_stalled(&mut self) -> bool;
}
impl TestExecutorExt for fasync::TestExecutor {
fn pin_and_run_until_stalled<F: Future>(&mut self, main_future: F) -> Option<F::Output> {
self.run_until_stalled(&mut Box::pin(main_future)).into_option()
}
fn wake_timers_and_run_until_stalled(&mut self) -> bool {
let did_wake_timers = self.wake_expired_timers();
let _ = self.run_until_stalled(&mut future::pending::<()>());
did_wake_timers
}
}
#[allow(dead_code)]
struct Handles {
fake_backlight_service: FakeBacklightService,
backlight_proxy: BacklightProxy,
backlight_task: Task<()>,
fake_display_power_service: FakeDisplayPowerService,
display_power_proxy: DisplayPowerProxy,
display_power_task: Task<()>,
backlight: Backlight,
}
impl Handles {
fn new(
power_off_delay_ms: i64,
power_on_delay_ms: i64,
initial_backlight_state: BacklightCommand,
) -> (fasync::TestExecutor, Handles) {
let mut exec = fasync::TestExecutor::new_with_fake_time();
exec.set_fake_time(zx::MonotonicInstant::ZERO.into());
let fake_backlight_service = FakeBacklightService::new();
let (backlight_proxy, backlight_task) = fake_backlight_service.start().unwrap();
let fake_display_power_service = FakeDisplayPowerService::new();
let (display_power_proxy, display_power_task) =
fake_display_power_service.start().unwrap();
let fake_backlight_service_ = fake_backlight_service.clone();
let backlight = exec
.pin_and_run_until_stalled(async {
fake_backlight_service_
.set_get_state_normalized_response(Ok(initial_backlight_state))
.await;
Backlight::with_display_power_internal(
backlight_proxy.clone(),
display_power_proxy.clone(),
zx::MonotonicDuration::from_millis(power_off_delay_ms),
zx::MonotonicDuration::from_millis(power_on_delay_ms),
)
.await
.unwrap()
})
.unwrap();
(
exec,
Handles {
fake_backlight_service,
backlight_proxy,
backlight_task,
fake_display_power_service,
display_power_proxy,
display_power_task,
backlight,
},
)
}
}
#[test]
fn positive_brightness_changes_without_affecting_ddic() {
let power_off_delay_ms = 100;
let power_on_delay_ms = 50;
let (mut exec, h) = Handles::new(
power_off_delay_ms,
power_on_delay_ms,
BacklightCommand { backlight_on: true, brightness: 1.0 },
);
exec.pin_and_run_until_stalled(async {
assert_eq!(h.backlight.get().await.unwrap(), 1.0);
h.backlight.set(0.9).await.unwrap();
assert_eq!(h.backlight.get().await.unwrap(), 0.9);
h.backlight.set(0.5).await.unwrap();
assert_eq!(h.backlight.get().await.unwrap(), 0.5);
assert_eq!(h.fake_display_power_service.last_set_display_power_value().await, None)
})
.unwrap();
}
#[test]
fn zero_brightness_turns_ddic_off() {
let power_off_delay_ms = 100;
let power_on_delay_ms = 50;
let (mut exec, h) = Handles::new(
power_off_delay_ms,
power_on_delay_ms,
BacklightCommand { backlight_on: true, brightness: 1.0 },
);
exec.pin_and_run_until_stalled(async {
assert_eq!(h.backlight.get().await.unwrap(), 1.0);
assert_eq!(h.fake_display_power_service.last_set_display_power_value().await, None);
h.backlight.set(0.0).await.unwrap();
assert_eq!(h.backlight.get().await.unwrap(), 0.0);
assert_eq!(h.fake_display_power_service.last_set_display_power_value().await, None);
})
.unwrap();
exec.set_fake_time(
(zx::MonotonicInstant::ZERO
+ zx::MonotonicDuration::from_millis(power_off_delay_ms - 1))
.into(),
);
assert_eq!(exec.wake_timers_and_run_until_stalled(), false);
exec.pin_and_run_until_stalled(async {
assert_eq!(h.fake_display_power_service.last_set_display_power_value().await, None);
})
.unwrap();
exec.set_fake_time(
(zx::MonotonicInstant::ZERO
+ zx::MonotonicDuration::from_millis(power_off_delay_ms + 1))
.into(),
);
assert_eq!(exec.wake_timers_and_run_until_stalled(), true);
exec.pin_and_run_until_stalled(async {
assert_eq!(
h.fake_display_power_service.last_set_display_power_value().await,
Some(false)
);
})
.unwrap();
}
#[test]
fn backlight_turns_on_after_ddic() {
let power_off_delay_ms = 100;
let power_on_delay_ms = 50;
let (mut exec, h) = Handles::new(
power_off_delay_ms,
power_on_delay_ms,
BacklightCommand { backlight_on: false, brightness: MIN_REGULATED_BRIGHTNESS },
);
exec.pin_and_run_until_stalled(async {
assert_eq!(h.backlight.get().await.unwrap(), 0.0);
assert_eq!(h.fake_display_power_service.last_set_display_power_value().await, None);
})
.unwrap();
let backlight_ = h.backlight.clone();
let mut turn_on_backlight_fut = Box::pin(async {
backlight_.set(0.1).await.unwrap();
});
assert_matches!(exec.run_until_stalled(&mut turn_on_backlight_fut), Poll::Pending);
exec.pin_and_run_until_stalled(async {
assert_eq!(h.backlight.get().await.unwrap(), 0.0);
assert_eq!(
h.fake_display_power_service.last_set_display_power_value().await,
Some(true)
);
})
.unwrap();
exec.set_fake_time(
(zx::MonotonicInstant::ZERO
+ zx::MonotonicDuration::from_millis(power_on_delay_ms - 1))
.into(),
);
assert_eq!(exec.wake_timers_and_run_until_stalled(), false);
assert_matches!(exec.run_until_stalled(&mut turn_on_backlight_fut), Poll::Pending);
exec.pin_and_run_until_stalled(async {
assert_eq!(h.backlight.get().await.unwrap(), 0.0);
})
.unwrap();
exec.set_fake_time(
(zx::MonotonicInstant::ZERO
+ zx::MonotonicDuration::from_millis(power_on_delay_ms + 1))
.into(),
);
assert_eq!(exec.wake_timers_and_run_until_stalled(), true);
assert_matches!(exec.run_until_stalled(&mut turn_on_backlight_fut), Poll::Ready(()));
exec.pin_and_run_until_stalled(async {
assert_eq!(h.backlight.get().await.unwrap(), 0.1);
})
.unwrap();
}
#[test]
fn repeated_backlight_off_commands_do_not_affect_ddic() {
let power_off_delay_ms = 100;
let power_on_delay_ms = 50;
let (mut exec, h) = Handles::new(
power_off_delay_ms,
power_on_delay_ms,
BacklightCommand { backlight_on: false, brightness: MIN_REGULATED_BRIGHTNESS },
);
exec.pin_and_run_until_stalled(async {
assert_eq!(h.backlight.get().await.unwrap(), 0.0);
assert_eq!(h.fake_display_power_service.last_set_display_power_value().await, None);
h.backlight.set(0.0).await.unwrap();
assert_eq!(h.backlight.get().await.unwrap(), 0.0);
assert_eq!(
h.fake_display_power_service.last_set_display_power_value().await,
Some(false)
);
})
.unwrap();
}
#[test]
fn ddic_power_off_is_preempted_by_backlight_on_commands() {
let power_off_delay_ms = 100;
let power_on_delay_ms = 50;
let (mut exec, h) = Handles::new(
power_off_delay_ms,
power_on_delay_ms,
BacklightCommand { backlight_on: true, brightness: 1.0 },
);
exec.pin_and_run_until_stalled(async {
h.backlight.set(0.0).await.unwrap();
assert_eq!(h.backlight.get().await.unwrap(), 0.0);
assert_eq!(h.fake_display_power_service.last_set_display_power_value().await, None);
})
.unwrap();
exec.set_fake_time(
(zx::MonotonicInstant::ZERO
+ zx::MonotonicDuration::from_millis(power_off_delay_ms - 10))
.into(),
);
assert_eq!(exec.wake_timers_and_run_until_stalled(), false);
exec.pin_and_run_until_stalled(async {
assert_eq!(h.fake_display_power_service.last_set_display_power_value().await, None);
})
.unwrap();
exec.pin_and_run_until_stalled(async {
h.backlight.set(0.5).await.unwrap();
assert_eq!(h.backlight.get().await.unwrap(), 0.5);
assert_eq!(h.fake_display_power_service.last_set_display_power_value().await, None);
})
.unwrap();
exec.set_fake_time(
(zx::MonotonicInstant::ZERO
+ zx::MonotonicDuration::from_millis(power_off_delay_ms + 10))
.into(),
);
assert_eq!(exec.wake_timers_and_run_until_stalled(), false);
exec.pin_and_run_until_stalled(async {
assert_eq!(h.fake_display_power_service.last_set_display_power_value().await, None);
})
.unwrap();
}
#[test]
fn backlight_power_on_is_preempted_by_ddic_off_commands() {
let power_off_delay_ms = 100;
let power_on_delay_ms = 50;
let (mut exec, h) = Handles::new(
power_off_delay_ms,
power_on_delay_ms,
BacklightCommand { backlight_on: false, brightness: MIN_REGULATED_BRIGHTNESS },
);
let mut turn_on_backlight_1_fut = Box::pin(h.backlight.set(0.1));
let mut turn_on_backlight_2_fut = Box::pin(h.backlight.set(0.2));
assert_matches!(exec.run_until_stalled(&mut turn_on_backlight_1_fut), Poll::Pending);
assert_matches!(exec.run_until_stalled(&mut turn_on_backlight_2_fut), Poll::Pending);
exec.set_fake_time(
(zx::MonotonicInstant::ZERO
+ zx::MonotonicDuration::from_millis(power_on_delay_ms - 1))
.into(),
);
assert_eq!(exec.wake_timers_and_run_until_stalled(), false);
assert_matches!(exec.run_until_stalled(&mut turn_on_backlight_1_fut), Poll::Pending);
assert_matches!(exec.run_until_stalled(&mut turn_on_backlight_2_fut), Poll::Pending);
let turn_off_backlight_fut = Box::pin(h.backlight.set(0.0));
exec.pin_and_run_until_stalled(async {
assert_matches!(turn_off_backlight_fut.await, Ok(()));
assert_matches!(
turn_on_backlight_1_fut.await.unwrap_err().downcast::<oneshot::Canceled>(),
Ok(_)
);
assert_matches!(
turn_on_backlight_2_fut.await.unwrap_err().downcast::<oneshot::Canceled>(),
Ok(_)
);
assert_eq!(h.backlight.get().await.unwrap(), 0.0);
})
.unwrap();
exec.set_fake_time(
(zx::MonotonicInstant::ZERO
+ zx::MonotonicDuration::from_millis(power_on_delay_ms + 1))
.into(),
);
assert_eq!(exec.wake_timers_and_run_until_stalled(), false);
exec.pin_and_run_until_stalled(async {
assert_eq!(h.backlight.get().await.unwrap(), 0.0);
})
.unwrap();
}
#[test]
fn error_in_ddic_power_off_does_not_affect_later_backlight_commands() {
let power_off_delay_ms = 100;
let power_on_delay_ms = 50;
let (mut exec, h) = Handles::new(
power_off_delay_ms,
power_on_delay_ms,
BacklightCommand { backlight_on: true, brightness: 1.0 },
);
exec.pin_and_run_until_stalled(async {
h.fake_display_power_service
.set_set_display_power_response(Err(zx::Status::BAD_STATE.into_raw()))
.await;
assert_eq!(h.backlight.get().await.unwrap(), 1.0);
assert_eq!(h.fake_display_power_service.last_set_display_power_value().await, None);
h.backlight.set(0.0).await.unwrap();
assert_eq!(h.backlight.get().await.unwrap(), 0.0);
assert_eq!(h.fake_display_power_service.last_set_display_power_value().await, None);
})
.unwrap();
exec.set_fake_time(
(zx::MonotonicInstant::ZERO
+ zx::MonotonicDuration::from_millis(power_off_delay_ms + 1))
.into(),
);
assert_eq!(exec.wake_timers_and_run_until_stalled(), true);
exec.pin_and_run_until_stalled(async {
assert_eq!(h.fake_display_power_service.last_set_display_power_value().await, None);
h.backlight.set(0.5).await.unwrap();
assert_eq!(h.backlight.get().await.unwrap(), 0.5);
assert_eq!(h.fake_display_power_service.last_set_display_power_value().await, None);
})
.unwrap();
}
#[test]
fn error_in_ddic_power_on_is_recoverable() {
let power_off_delay_ms = 100;
let power_on_delay_ms = 50;
let (mut exec, h) = Handles::new(
power_off_delay_ms,
power_on_delay_ms,
BacklightCommand { backlight_on: false, brightness: MIN_REGULATED_BRIGHTNESS },
);
exec.pin_and_run_until_stalled(async {
h.fake_display_power_service
.set_set_display_power_response(Err(zx::Status::UNAVAILABLE.into_raw()))
.await;
assert_matches!(h.backlight.set(0.5).await, Err(_));
assert_eq!(h.fake_display_power_service.last_set_display_power_value().await, None);
h.fake_display_power_service.set_set_display_power_response(Ok(())).await;
})
.unwrap();
let mut retry_fut = Box::pin(h.backlight.set(0.7));
assert_matches!(exec.run_until_stalled(&mut retry_fut), Poll::Pending);
exec.set_fake_time(
(zx::MonotonicInstant::ZERO
+ zx::MonotonicDuration::from_millis(power_on_delay_ms + 1))
.into(),
);
assert_eq!(exec.wake_timers_and_run_until_stalled(), true);
assert_matches!(exec.run_until_stalled(&mut retry_fut), Poll::Ready(Ok(())));
}
#[test]
fn ddic_does_not_power_off_if_backlight_fails_to_power_off() {
let power_off_delay_ms = 100;
let power_on_delay_ms = 50;
let (mut exec, h) = Handles::new(
power_off_delay_ms,
power_on_delay_ms,
BacklightCommand { backlight_on: true, brightness: 0.5 },
);
exec.pin_and_run_until_stalled(async {
h.fake_backlight_service
.set_set_state_normalized_response(Err(zx::Status::NO_RESOURCES.into_raw()))
.await;
assert_matches!(h.backlight.set(0.0).await, Err(_));
})
.unwrap();
exec.set_fake_time(
(zx::MonotonicInstant::ZERO
+ zx::MonotonicDuration::from_millis(power_off_delay_ms + 1))
.into(),
);
assert_eq!(exec.wake_timers_and_run_until_stalled(), false);
exec.pin_and_run_until_stalled(async {
assert_eq!(h.fake_display_power_service.last_set_display_power_value().await, None);
})
.unwrap();
}
}