blob: 9c9aa0e9278c543617bb90bc469dfc13cc0dd3af [file] [log] [blame]
// Copyright 2020 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use crate::common_utils::common::macros::{fx_err_and_bail, with_line};
use anyhow::Error;
use fidl_fuchsia_power as fpower;
use fidl_fuchsia_power_test as spower;
use fuchsia_component::client::connect_to_service;
use fuchsia_syslog::macros::{fx_log_err, fx_log_warn};
use fuchsia_zircon::Status;
use parking_lot::RwLock;
use serde_json::Value;
use std::time::Duration;
#[derive(Debug)]
struct InnerBatterySimulatorFacade {
proxy: Option<spower::BatterySimulatorProxy>,
}
#[derive(Debug)]
pub struct BatterySimulatorFacade {
inner: RwLock<InnerBatterySimulatorFacade>,
}
static TAG: &str = "BatterySimulatorFacade";
impl BatterySimulatorFacade {
pub fn new() -> Self {
BatterySimulatorFacade { inner: RwLock::new(InnerBatterySimulatorFacade { proxy: None }) }
}
/// Initialize proxy to perform changes
/// # Arguments
/// * 'given_proxy' - An optional proxy for testing purposes
pub fn init_proxy(
&self,
given_proxy: Option<spower::BatterySimulatorProxy>,
) -> Result<(), Error> {
if given_proxy.is_none() {
let proxy = self.create_proxy()?;
self.inner.write().proxy = Some(proxy);
} else {
self.inner.write().proxy = given_proxy;
}
Ok(())
}
/// Create proxy to BatterySimulator to perform changes
pub fn create_proxy(&self) -> Result<spower::BatterySimulatorProxy, Error> {
match connect_to_service::<spower::BatterySimulatorMarker>() {
Ok(service) => Ok(service),
Err(err) => fx_err_and_bail!(
&with_line!(TAG),
format_err!("Failed to create battery simulator proxy: {:?}", err)
),
}
}
/// Checks if the Facade proxy has been initialized
pub fn check_proxy(&self) -> Result<(), Error> {
if self.inner.read().proxy.as_ref().is_none() {
bail!("Facade proxy has not been initialized");
}
Ok(())
}
/// Set Time Remaining to given value represented in seconds
/// # Arguments
/// * 'time_remaining' - A json object with 'message' as the key and an integer as a value
/// representing time in seconds.
// TODO(fxbug.dev/48702): Check type conversion
pub fn set_time_remaining(&self, time_remaining: Value) -> Result<(), Error> {
self.check_proxy()?;
let seconds: u64 = match time_remaining["message"].as_u64() {
Some(v) => v,
None => bail!("Unable to get seconds"),
};
let microseconds = 0;
let duration = Duration::new(seconds, microseconds);
match self.inner.read().proxy.clone() {
Some(p) => p.set_time_remaining(duration.as_nanos() as i64)?,
None => bail!("Proxy not set"),
};
Ok(())
}
/// Set Battery Status to given value
/// # Arguments
/// * 'battery_status' - A json object with 'message' as the key and an string as a value
/// representing the battery status value
pub fn set_battery_status(&self, battery_status: Value) -> Result<(), Error> {
self.check_proxy()?;
let value: &str = match battery_status["message"].as_str() {
Some(v) => &v,
None => bail!("Unable to get battery status"),
};
let res = match value {
"UNKNOWN" => fpower::BatteryStatus::Unknown,
"OK" => fpower::BatteryStatus::Ok,
"NOT_AVAILABLE" => fpower::BatteryStatus::NotAvailable,
"NOT_PRESENT" => fpower::BatteryStatus::NotPresent,
_ => fx_err_and_bail!(&with_line!(TAG), format_err!("Battery Status not valid")),
};
match self.inner.read().proxy.clone() {
Some(p) => p.set_battery_status(res)?,
None => bail!("Proxy not set"),
};
Ok(())
}
/// Set Charge Status to given value represented as a string
/// # Arguments
/// * 'charge_status' - A json object with 'message' as the key and an string as a value
/// representing the charge status value
pub fn set_charge_status(&self, charge_status: Value) -> Result<(), Error> {
self.check_proxy()?;
let value: &str = match charge_status["message"].as_str() {
Some(v) => &v,
None => bail!("Unable to get charge status"),
};
let res = match value {
"UNKNOWN" => fpower::ChargeStatus::Unknown,
"NOT_CHARGING" => fpower::ChargeStatus::NotCharging,
"CHARGING" => fpower::ChargeStatus::Charging,
"DISCHARGING" => fpower::ChargeStatus::Discharging,
"FULL" => fpower::ChargeStatus::Full,
_ => fx_err_and_bail!(&with_line!(TAG), format_err!("Charge Status not valid")),
};
match self.inner.read().proxy.clone() {
Some(p) => p.set_charge_status(res)?,
None => bail!("Proxy not set"),
};
Ok(())
}
/// Set Level Status to given value represented as a string
/// # Arguments
/// * 'level_status' - A json object with 'message' as the key and an string as a value
/// representing the level status value
pub fn set_level_status(&self, level_status: Value) -> Result<(), Error> {
self.check_proxy()?;
let value: &str = match level_status["message"].as_str() {
Some(v) => &v,
None => bail!("Unable to get level status"),
};
let res = match value {
"UNKNOWN" => fpower::LevelStatus::Unknown,
"OK" => fpower::LevelStatus::Ok,
"WARNING" => fpower::LevelStatus::Warning,
"LOW" => fpower::LevelStatus::Low,
"CRITICAL" => fpower::LevelStatus::Critical,
_ => fx_err_and_bail!(&with_line!(TAG), format_err!("Level Status not valid")),
};
match self.inner.read().proxy.clone() {
Some(p) => p.set_level_status(res)?,
None => bail!("Proxy not set"),
};
Ok(())
}
/// Set Charge Source to given value represented
/// # Arguments
/// * 'charge_source' - A json object with 'message' as the key and an string as a value
/// representing the charge source value
pub fn set_charge_source(&self, charge_source: Value) -> Result<(), Error> {
self.check_proxy()?;
let value: &str = match &charge_source["message"].as_str() {
Some(v) => &v,
None => bail!("Unable to get charge source"),
};
let res = match value {
"UNKNOWN" => fpower::ChargeSource::Unknown,
"NONE" => fpower::ChargeSource::None,
"AC_ADAPTER" => fpower::ChargeSource::AcAdapter,
"USB" => fpower::ChargeSource::Usb,
"WIRELESS" => fpower::ChargeSource::Wireless,
_ => fx_err_and_bail!(&with_line!(TAG), format_err!("Charge Source not valid")),
};
match self.inner.read().proxy.clone() {
Some(p) => p.set_charge_source(res)?,
None => bail!("Proxy not set"),
};
Ok(())
}
/// Set Battery Percentage to given value
/// # Arguments
/// * 'battery_percentage' - A json object with 'message' as the key and an integer as a value
/// representing the battery percentage
pub fn set_battery_percentage(&self, battery_percentage: Value) -> Result<(), Error> {
self.check_proxy()?;
let percent: f32 = match battery_percentage["message"].to_string().parse() {
Ok(v) => v,
Err(e) => bail!("Unable to get battery percentage {}", e),
};
let battery_percentage_lower_bound = 0.0;
let battery_percentage_upper_bound = 100.0;
if percent < battery_percentage_lower_bound || percent > battery_percentage_upper_bound {
fx_err_and_bail!(
&with_line!(TAG),
format_err!("Battery Percentage not between 0 and 100")
)
}
match self.inner.read().proxy.clone() {
Some(p) => p.set_battery_percentage(percent)?,
None => bail!("Proxy not set"),
};
Ok(())
}
/// Disconnect Real Battery
pub fn disconnect_real_battery(&self) -> Result<(), Error> {
self.check_proxy()?;
match self.inner.read().proxy.clone() {
Some(p) => p.disconnect_real_battery()?,
None => bail!("Proxy not set"),
};
Ok(())
}
/// Reconnect Real Battery
pub fn reconnect_real_battery(&self) -> Result<(), Error> {
self.check_proxy()?;
match self.inner.read().proxy.clone() {
Some(p) => p.reconnect_real_battery()?,
None => bail!("Proxy not set"),
};
Ok(())
}
/// Returns the simulated battery info
pub async fn get_simulated_battery_info(&self) -> Result<Option<fpower::BatteryInfo>, Error> {
self.check_proxy()?;
match self.inner.read().proxy.clone() {
Some(p) => match p.get_battery_info().await {
Ok(battery_info) => Ok(Some(battery_info)),
Err(fidl::Error::ClientChannelClosed { status: Status::PEER_CLOSED, .. }) => {
fx_log_warn!("Battery Simulator not available.");
Ok(None)
}
Err(e) => fx_err_and_bail!(
&with_line!(TAG),
format_err!("Couldn't get BatteryInfo {}", e)
),
},
None => bail!("Proxy not set"),
}
}
/// Returns a boolean value indicating if the device is simulating the battery state
pub async fn get_simulating_state(&self) -> Result<Option<bool>, Error> {
self.check_proxy()?;
match self.inner.read().proxy.clone() {
Some(p) => match p.is_simulating().await {
Ok(simulation_state) => Ok(Some(simulation_state)),
Err(fidl::Error::ClientChannelClosed { status: Status::PEER_CLOSED, .. }) => {
fx_log_warn!("Battery Simulator not available.");
Ok(None)
}
Err(e) => fx_err_and_bail!(
&with_line!(TAG),
format_err!("Couldn't get simulation state {}", e)
),
},
None => bail!("Proxy not set"),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use fuchsia_component::client::{launch, launcher};
use serde_json::json;
#[fuchsia_async::run_singlethreaded(test)]
async fn test_disconnect() {
// Launch battery manager
const BM_URL: &str = "fuchsia-pkg://fuchsia.com/battery-manager#meta/battery_manager.cmx";
let launcher = launcher().unwrap();
let app = launch(&launcher, BM_URL.to_string(), None).unwrap();
// Get Proxy and create Facade
let proxy = app.connect_to_service::<spower::BatterySimulatorMarker>().unwrap();
let facade = BatterySimulatorFacade::new();
let init_result = facade.init_proxy(Some(proxy));
assert!(init_result.is_ok(), "Failed to initialize proxy");
// Disconnect
let res = facade.disconnect_real_battery();
assert!(res.is_ok(), "Failed to disconnect");
// Check if simulating
let simulation_state = facade.get_simulating_state().await;
// When getting the state back, note that the DUT may not include
// battery support, so it may be empty. This is not a test failure.
match simulation_state.unwrap() {
Some(state) => {
assert_eq!(state, true);
// Reconnect real battery
let res = facade.reconnect_real_battery();
assert!(res.is_ok(), "Failed to reconnect");
}
None => fx_log_warn!("No battery state provided, skipping check"),
}
}
#[fuchsia_async::run_singlethreaded(test)]
async fn test_set_battery_percentage() {
// Launch battery manager
const BM_URL: &str = "fuchsia-pkg://fuchsia.com/battery-manager#meta/battery_manager.cmx";
let launcher = launcher().unwrap();
let app = launch(&launcher, BM_URL.to_string(), None).unwrap();
// Get Proxy and create Facade
let proxy = app.connect_to_service::<spower::BatterySimulatorMarker>().unwrap();
let facade = BatterySimulatorFacade::new();
let init_result = facade.init_proxy(Some(proxy));
assert!(init_result.is_ok(), "Failed to initialize proxy");
// Disconnect
let res = facade.disconnect_real_battery();
assert!(res.is_ok(), "Failed to disconnect");
// Set Battery Percentage
let res = facade.set_battery_percentage(json!({"message": 12}));
assert!(res.is_ok(), "Failed to set battery percentage");
// Get BatteryInfo
let battery_info = facade.get_simulated_battery_info().await;
assert!(battery_info.is_ok(), "Failed to get battery info");
// When getting the battery info back, note that the DUT may not include
// battery support, so info may be empty. This is not a test failure.
match battery_info.unwrap() {
Some(info) => {
assert_eq!(info.level_percent.unwrap(), 12.0);
// Reconnect real battery
let res = facade.reconnect_real_battery();
assert!(res.is_ok(), "Failed to reconnect");
}
None => fx_log_warn!("No battery info provided, skipping check"),
}
}
#[fuchsia_async::run_singlethreaded(test)]
async fn test_set_charge_source() {
// Launch battery manager
const BM_URL: &str = "fuchsia-pkg://fuchsia.com/battery-manager#meta/battery_manager.cmx";
let launcher = launcher().unwrap();
let app = launch(&launcher, BM_URL.to_string(), None).unwrap();
// Get Proxy and create Facade
let proxy = app.connect_to_service::<spower::BatterySimulatorMarker>().unwrap();
let facade = BatterySimulatorFacade::new();
let init_result = facade.init_proxy(Some(proxy));
assert!(init_result.is_ok(), "Failed to initialize proxy");
// Disconnect
let res = facade.disconnect_real_battery();
assert!(res.is_ok(), "Failed to disconnect");
// Set Charge Source
let res = facade.set_charge_source(json!({"message": "WIRELESS"}));
assert!(res.is_ok(), "Failed to set battery percentage");
// Get BatteryInfo
let battery_info = facade.get_simulated_battery_info().await;
assert!(battery_info.is_ok(), "Failed to get battery info");
match battery_info.unwrap() {
Some(info) => {
assert_eq!(info.charge_source.unwrap(), fpower::ChargeSource::Wireless);
// Reconnect real battery
let res = facade.reconnect_real_battery();
assert!(res.is_ok(), "Failed to reconnect");
}
None => fx_log_warn!("No battery info provided, skipping check"),
}
}
}