blob: 84a719937e972b08492441fe34fc450997d6a820 [file] [log] [blame]
// Copyright 2019 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 anyhow::{format_err, Error};
use serde_json::{from_value, to_value, Value};
use crate::setui::types::{IntlInfo, NetworkType, SetUiResult};
use fidl_fuchsia_media::AudioRenderUsage;
use fidl_fuchsia_settings::{
self as fsettings, AudioMarker, AudioStreamSettingSource, AudioStreamSettings,
ConfigurationInterfaces, DisplayMarker, DisplaySettings, IntlMarker, SetupMarker,
SetupSettings, Volume,
};
use fuchsia_component::client::connect_to_service;
use fuchsia_syslog::macros::fx_log_info;
/// Facade providing access to SetUi interfaces.
#[derive(Debug)]
pub struct SetUiFacade {
/// Audio proxy that may be optionally provided for testing. The proxy is not cached during
/// normal operation.
audio_proxy: Option<fsettings::AudioProxy>,
/// Optional Display proxy for testing, similar to `audio_proxy`.
display_proxy: Option<fsettings::DisplayProxy>,
}
impl SetUiFacade {
pub fn new() -> SetUiFacade {
SetUiFacade { audio_proxy: None, display_proxy: None }
}
/// Sets network option used by device setup.
/// Same action as choosing "Setup over Ethernet [enabled|disabled]" in "Developer options"
///
/// args: accepted args are "ethernet" or "wifi". ex: {"params": "ethernet"}
pub async fn set_network(&self, args: Value) -> Result<Value, Error> {
let network_type: NetworkType = from_value(args)?;
fx_log_info!("set_network input {:?}", network_type);
let setup_service_proxy = match connect_to_service::<SetupMarker>() {
Ok(proxy) => proxy,
Err(e) => bail!("Failed to connect to Setup service {:?}.", e),
};
let mut settings = SetupSettings::EMPTY;
match network_type {
NetworkType::Ethernet => {
settings.enabled_configuration_interfaces = Some(ConfigurationInterfaces::Ethernet);
}
NetworkType::Wifi => {
settings.enabled_configuration_interfaces = Some(ConfigurationInterfaces::Wifi);
}
_ => return Err(format_err!("Network type must either be ethernet or wifi.")),
}
// Update network configuration without automatic device reboot.
// For changes to take effect, either restart basemgr component or reboot device.
match setup_service_proxy.set2(settings, false).await? {
Ok(_) => Ok(to_value(SetUiResult::Success)?),
Err(err) => Err(format_err!("Update network settings failed with err {:?}", err)),
}
}
/// Reports the network option used for setup
///
/// Returns either "ethernet", "wifi" or "unknown".
pub async fn get_network_setting(&self) -> Result<Value, Error> {
let setup_service_proxy = match connect_to_service::<SetupMarker>() {
Ok(proxy) => proxy,
Err(e) => bail!("Failed to connect to Setup service {:?}.", e),
};
let setting = setup_service_proxy.watch().await?;
match setting.enabled_configuration_interfaces {
Some(ConfigurationInterfaces::Ethernet) => Ok(to_value(NetworkType::Ethernet)?),
Some(ConfigurationInterfaces::Wifi) => Ok(to_value(NetworkType::Wifi)?),
_ => Ok(to_value(NetworkType::Unknown)?),
}
}
/// Sets Internationalization values.
///
/// Input should is expected to be type IntlInfo in json.
/// Example:
/// {
/// "hour_cycle":"H12",
/// "locales":[{"id":"he-FR"}],
/// "temperature_unit":"Celsius",
/// "time_zone_id":"UTC"
/// }
pub async fn set_intl_setting(&self, args: Value) -> Result<Value, Error> {
let intl_info: IntlInfo = from_value(args)?;
fx_log_info!("Received Intl Settings Request {:?}", intl_info);
let intl_service_proxy = match connect_to_service::<IntlMarker>() {
Ok(proxy) => proxy,
Err(e) => bail!("Failed to connect to Intl service {:?}.", e),
};
match intl_service_proxy.set(intl_info.into()).await? {
Ok(_) => Ok(to_value(SetUiResult::Success)?),
Err(err) => Err(format_err!("Update intl settings failed with err {:?}", err)),
}
}
/// Reads the Internationalization setting.
///
/// Returns IntlInfo in json.
pub async fn get_intl_setting(&self) -> Result<Value, Error> {
let intl_service_proxy = match connect_to_service::<IntlMarker>() {
Ok(proxy) => proxy,
Err(e) => bail!("Failed to connect to Intl service {:?}.", e),
};
let intl_info: IntlInfo = intl_service_proxy.watch().await?.into();
return Ok(to_value(&intl_info)?);
}
/// Reports the AudioInput (mic muted) state.
///
/// Returns true if mic is muted or false if mic is unmuted.
pub async fn is_mic_muted(&self) -> Result<Value, Error> {
let audio_proxy = match connect_to_service::<AudioMarker>() {
Ok(proxy) => proxy,
Err(e) => bail!("Failed to connect to Setup Audio service {:?}.", e),
};
match audio_proxy.watch().await?.input {
Some(audio_input) => Ok(to_value(audio_input.muted)?),
_ => Err(format_err!("Cannot read audio input.")),
}
}
/// Sets the display brightness via `fuchsia.settings.Display.Set`.
///
/// # Arguments
/// * `args`: JSON value containing the desired brightness level as f32.
pub async fn set_brightness(&self, args: Value) -> Result<Value, Error> {
let brightness: f32 = from_value(args)?;
// Use the test proxy if one was provided, otherwise connect to the discoverable Display
// service.
let display_proxy = match self.display_proxy.as_ref() {
Some(proxy) => proxy.clone(),
None => match connect_to_service::<DisplayMarker>() {
Ok(proxy) => proxy,
Err(e) => bail!("Failed to connect to Display service {:?}.", e),
},
};
let settings = DisplaySettings {
auto_brightness: Some(false),
brightness_value: Some(brightness),
..DisplaySettings::EMPTY
};
match display_proxy.set(settings).await? {
Ok(_) => Ok(to_value(SetUiResult::Success)?),
Err(e) => Err(format_err!("SetBrightness failed with err {:?}", e)),
}
}
/// Sets the media volume level via `fuchsia.settings.Audio.Set`.
///
/// # Arguments
/// * `args`: JSON value containing the desired volume level as f32.
pub async fn set_media_volume(&self, args: Value) -> Result<Value, Error> {
let volume: f32 = from_value(args)?;
// Use the test proxy if one was provided, otherwise connect to the discoverable Audio
// service.
let audio_proxy = match self.audio_proxy.as_ref() {
Some(proxy) => proxy.clone(),
None => match connect_to_service::<AudioMarker>() {
Ok(proxy) => proxy,
Err(e) => bail!("Failed to connect to Display service {:?}.", e),
},
};
let stream_settings = AudioStreamSettings {
stream: Some(AudioRenderUsage::Media),
source: Some(AudioStreamSettingSource::User),
user_volume: Some(Volume { level: Some(volume), muted: Some(false), ..Volume::EMPTY }),
..AudioStreamSettings::EMPTY
};
let settings = fsettings::AudioSettings {
streams: Some(vec![stream_settings]),
input: None,
..fsettings::AudioSettings::EMPTY
};
match audio_proxy.set(settings).await? {
Ok(_) => Ok(to_value(SetUiResult::Success)?),
Err(e) => Err(format_err!("SetVolume failed with err {:?}", e)),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::common_utils::test::assert_value_round_trips_as;
use crate::setui::types::{HourCycle, IntlInfo, LocaleId, TemperatureUnit};
use fidl::endpoints::create_proxy_and_stream;
use fuchsia_async as fasync;
use futures::TryStreamExt;
use serde_json::json;
fn make_intl_info() -> IntlInfo {
return IntlInfo {
locales: Some(vec![LocaleId { id: "en-US".into() }]),
temperature_unit: Some(TemperatureUnit::Celsius),
time_zone_id: Some("UTC".into()),
hour_cycle: Some(HourCycle::H12),
};
}
#[test]
fn serde_intl_set() {
let intl_request = make_intl_info();
assert_value_round_trips_as(
intl_request,
json!(
{
"locales": [{"id": "en-US"}],
"temperature_unit":"Celsius",
"time_zone_id": "UTC",
"hour_cycle": "H12",
}),
);
}
// Tests that `set_brightness` correctly sends a request to the Display service.
#[fasync::run_singlethreaded(test)]
async fn test_set_brightness() {
let brightness = 0.5f32;
let (proxy, mut stream) = create_proxy_and_stream::<DisplayMarker>().unwrap();
// Create a facade future that sends a request to `proxy`.
let facade = SetUiFacade { audio_proxy: None, display_proxy: Some(proxy) };
let facade_fut = async move {
assert_eq!(
facade.set_brightness(to_value(brightness).unwrap()).await.unwrap(),
to_value(SetUiResult::Success).unwrap()
);
};
// Create a future to service the request stream.
let stream_fut = async move {
match stream.try_next().await {
Ok(Some(fsettings::DisplayRequest::Set { settings, responder })) => {
assert_eq!(
settings,
DisplaySettings {
auto_brightness: Some(false),
brightness_value: Some(brightness),
..DisplaySettings::EMPTY
}
);
responder.send(&mut Ok(())).unwrap();
}
other => panic!("Unexpected stream item: {:?}", other),
}
};
futures::future::join(facade_fut, stream_fut).await;
}
// Tests that `set_media_volume` correctly sends a request to the Audio service.
#[fasync::run_singlethreaded(test)]
async fn test_set_media_volume() {
let volume = 0.5f32;
let (proxy, mut stream) = create_proxy_and_stream::<AudioMarker>().unwrap();
// Create a facade future that sends a request to `proxy`.
let facade = SetUiFacade { audio_proxy: Some(proxy), display_proxy: None };
let facade_fut = async move {
assert_eq!(
facade.set_media_volume(to_value(volume).unwrap()).await.unwrap(),
to_value(SetUiResult::Success).unwrap()
);
};
// Create a future to service the request stream.
let stream_fut = async move {
match stream.try_next().await {
Ok(Some(fsettings::AudioRequest::Set { settings, responder })) => {
let mut streams = settings.streams.unwrap();
assert_eq!(1, streams.len());
assert_eq!(
streams.pop().unwrap(),
AudioStreamSettings {
stream: Some(AudioRenderUsage::Media),
source: Some(AudioStreamSettingSource::User),
user_volume: Some(Volume {
level: Some(volume),
muted: Some(false),
..Volume::EMPTY
}),
..AudioStreamSettings::EMPTY
}
);
responder.send(&mut Ok(())).unwrap();
}
other => panic!("Unexpected stream item: {:?}", other),
}
};
futures::future::join(facade_fut, stream_fut).await;
}
}