blob: a33802a6889d83552606007e1eeb567db71a99e3 [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::audio::default_audio_info;
use crate::audio::policy::{
PolicyId, PropertyTarget, State, StateBuilder, Transform, TransformFlags,
};
use crate::audio::types::{AudioSettingSource, AudioStream, AudioStreamType};
use crate::audio::utils::round_volume_level;
use crate::base::SettingType;
use crate::handler::device_storage::testing::InMemoryStorageFactory;
use crate::message::base::{Audience, MessengerType};
use crate::policy::response;
use crate::policy::{Payload, PolicyInfo, PolicyType, Request};
use crate::service;
use crate::tests::fakes::audio_core_service;
use crate::tests::fakes::service_registry::ServiceRegistry;
use crate::EnvironmentBuilder;
use fidl_fuchsia_settings::{self as audio_fidl, AudioMarker, AudioProxy, AudioStreamSettings};
use fidl_fuchsia_settings_policy::{
self as policy_fidl, PolicyParameters, Volume, VolumePolicyControllerMarker,
VolumePolicyControllerProxy,
};
use fuchsia_component::server::NestedEnvironment;
use std::collections::HashSet;
use std::sync::Arc;
const ENV_NAME: &str = "settings_service_privacy_test_environment";
struct TestEnvironment {
/// The nested environment itself.
nested_environment: NestedEnvironment,
/// Handle to the volume policy service.
policy_service: VolumePolicyControllerProxy,
/// Handle to the setui audio service.
setui_audio_service: AudioProxy,
}
/// Creates an environment for audio policy.
async fn create_test_environment() -> TestEnvironment {
create_test_environment_with_data(None).await
}
/// Creates an environment for audio policy with initial data in storage.
async fn create_test_environment_with_data(data: Option<&State>) -> TestEnvironment {
let storage_factory = Arc::new(match data {
None => InMemoryStorageFactory::new(),
Some(data) => InMemoryStorageFactory::with_initial_data(data),
});
let service_registry = ServiceRegistry::create();
let audio_core_service_handle = audio_core_service::Builder::new().build();
service_registry.lock().await.register_service(audio_core_service_handle.clone());
let env = EnvironmentBuilder::new(storage_factory)
.service(ServiceRegistry::serve(service_registry))
.settings(&[SettingType::Audio])
.policies(&[PolicyType::Audio])
.spawn_and_get_nested_environment(ENV_NAME)
.await
.unwrap();
let policy_service = env.connect_to_service::<VolumePolicyControllerMarker>().unwrap();
let setui_audio_service = env.connect_to_service::<AudioMarker>().unwrap();
TestEnvironment { nested_environment: env, policy_service, setui_audio_service }
}
/// Sets the given stream value using the audio service.
async fn set_stream(env: &TestEnvironment, stream: AudioStream) {
let mut audio_settings = audio_fidl::AudioSettings::EMPTY;
audio_settings.streams = Some(vec![stream.into()]);
env.setui_audio_service
.set(audio_settings)
.await
.expect("set call succeeded")
.expect("set succeeded");
}
/// Sets the stream volume to the given volume level using the audio service.
async fn set_stream_volume(env: &TestEnvironment, stream_type: AudioStreamType, volume_level: f32) {
let stream = AudioStream {
stream_type: stream_type,
source: AudioSettingSource::User,
user_volume_level: volume_level,
user_volume_muted: false,
};
set_stream(env, stream).await;
}
/// Get the volume of the given stream from the audio service in the given test environment.
///
/// Panics for an unexpected result, such as the stream or volume level not being present.
async fn get_stream_volume(env: &TestEnvironment, stream: AudioStreamType) -> f32 {
get_stream_volume_from_proxy(&env.setui_audio_service, stream).await
}
/// Get the volume of the given stream from the given audio service proxy.
///
/// Panics for an unexpected result, such as the stream or volume level not being present.
async fn get_stream_volume_from_proxy(
setui_audio_service: &AudioProxy,
stream: AudioStreamType,
) -> f32 {
let stream = get_stream_from_proxy(setui_audio_service, stream).await;
stream.user_volume.as_ref().expect("no user volume").level.expect("no volume level")
}
/// Get the stream value of the given stream type from the given audio service proxy.
///
/// Panics for an unexpected result, such as the stream not being present.
async fn get_stream_from_proxy(
setui_audio_service: &AudioProxy,
stream: AudioStreamType,
) -> AudioStreamSettings {
let audio_settings = setui_audio_service.watch().await.expect("failed to watch audio settings");
audio_settings
.streams
.expect("no streams in audio settings")
.into_iter()
.find(|stream_settings| stream_settings.stream == Some(stream.into()))
.expect("failed to find audio stream")
}
async fn add_policy(env: &TestEnvironment, target: PropertyTarget, transform: Transform) -> u32 {
env.policy_service
.add_policy(&mut policy_fidl::Target::Stream(target.into()), &mut transform.into())
.await
.expect("failed to add policy")
.expect("no policy ID found")
}
/// Adds a max volume transform with the given volume limit to the policy service, then fetches the
/// active policies to verify the added limit matches the expected value.
///
/// While the input limit and actual added limit are usually the same, rounding or clamping may
/// result in differences.
async fn add_and_verify_max_volume_policy(input_volume_limit: f32, actual_volume_limit: f32) {
let target = AudioStreamType::Media;
// Add the max volume transform.
let env = create_test_environment().await;
env.policy_service
.add_policy(
&mut policy_fidl::Target::Stream(target.into()),
&mut Transform::Max(input_volume_limit).into(),
)
.await
.expect("failed to add policy")
.expect("no policy ID found");
let properties = env.policy_service.get_properties().await.expect("failed to get properties");
let property = properties
.iter()
.find(|property| property.target == Some(target.into()))
.expect("failed to find added property");
let policy = property
.active_policies
.as_ref()
.expect("no active policies")
.first()
.expect("no active policy");
// Volume limit values are rounded to two decimal places, similar to audio setting volumes.
// When comparing the values, we use an epsilon that's smaller than the threshold for
// rounding.
let epsilon = 0.0001;
match policy.parameters.as_ref().expect("has parameters") {
PolicyParameters::Max(Volume { volume: Some(added_volume_limit), .. }) => {
assert!((added_volume_limit - actual_volume_limit).abs() <= epsilon)
}
_ => panic!("Unexpected transform found"),
}
}
async fn remove_policy(env: &TestEnvironment, policy_id: u32) {
env.policy_service
.remove_policy(policy_id)
.await
.expect("failed to remove policy")
.expect("removal error");
}
// A simple validation test to ensure the policy message hub propagates messages
// properly.
#[fuchsia_async::run_until_stalled(test)]
async fn test_policy_message_hub() {
let messenger_factory = service::message::create_hub();
let policy_handler_address = service::Address::PolicyHandler(PolicyType::Audio);
// Create messenger to send request.
let (messenger, receptor) = messenger_factory
.create(MessengerType::Unbound)
.await
.expect("unbound messenger should be present");
// Create receptor to act as policy endpoint.
let mut policy_receptor = messenger_factory
.create(MessengerType::Addressable(policy_handler_address))
.await
.expect("addressable messenger should be present")
.1;
let request_payload: service::Payload = Payload::Request(Request::Get).into();
// Send request.
let mut reply_receptor = messenger
.message(request_payload.clone(), Audience::Address(policy_handler_address))
.send();
// Wait and verify request received.
let (payload, client) = policy_receptor.next_payload().await.expect("should receive message");
assert_eq!(payload, request_payload);
assert_eq!(client.get_author(), receptor.get_signature());
let state = StateBuilder::new()
.add_property(AudioStreamType::Background, TransformFlags::TRANSFORM_MAX)
.build();
// Send response.
let reply_payload: service::Payload =
Payload::Response(Ok(response::Payload::PolicyInfo(PolicyInfo::Audio(state)))).into();
client.reply(reply_payload.clone()).send().ack();
// Verify response received.
let (result_payload, _) = reply_receptor.next_payload().await.expect("should receive result");
assert_eq!(result_payload, reply_payload);
}
// Tests that from a clean slate, the policy service returns the expected default property targets.
#[fuchsia_async::run_until_stalled(test)]
async fn test_policy_get_default() {
let expected_stream_types = default_audio_info()
.streams
.iter()
.map(|stream| stream.stream_type)
.collect::<HashSet<_>>();
let env = create_test_environment().await;
// Attempt to read properties.
let properties = env.policy_service.get_properties().await.expect("failed to get properties");
// Verify that each expected stream type is contained in the property targets.
assert_eq!(properties.len(), expected_stream_types.len());
for property in properties {
// Allow unreachable patterns so this test will still build if more Target enums are added.
#[allow(unreachable_patterns)]
match property.target.expect("no target found for property") {
policy_fidl::Target::Stream(stream) => {
assert!(expected_stream_types.contains(&stream.into()));
}
_ => panic!("unexpected target type"),
}
}
}
// Tests that adding a new policy transform returns its policy ID and is reflected by the output
// from GetProperties.
#[fuchsia_async::run_until_stalled(test)]
async fn test_policy_add_policy() {
let expected_policy_target = AudioStreamType::Media;
let policy_transform = Transform::Max(1.0);
let env = create_test_environment().await;
// Add a policy property and save the returned policy ID.
let added_policy_id = add_policy(&env, expected_policy_target, policy_transform).await;
// Read the policies.
let properties = env.policy_service.get_properties().await.expect("failed to get properties");
// Verify that the expected target has the given policy and that others are unchanged.
for property in properties {
// Allow unreachable patterns so this test will still build if more Target enums are added.
#[allow(unreachable_patterns)]
match property.target.expect("no target found for property") {
policy_fidl::Target::Stream(stream) => {
if stream == expected_policy_target.into() {
// Chosen property should have a policy added.
let added_policies = property.active_policies.unwrap();
assert_eq!(added_policies.len(), 1);
let added_policy = added_policies.first().unwrap();
assert_eq!(added_policy.policy_id.unwrap(), added_policy_id);
assert_eq!(*added_policy.parameters.as_ref().unwrap(), policy_transform.into());
} else {
// Other properties shouldn't have any policies.
assert!(property.active_policies.unwrap().is_empty());
}
}
_ => panic!("unexpected target type"),
}
}
}
// Tests that starting the service with existing transforms then adding new policies still
// unique policy IDs.
#[fuchsia_async::run_until_stalled(test)]
async fn test_policy_add_policy_ids_unique_across_restart() {
let target = AudioStreamType::Media;
let transform = Transform::Min(0.0);
// Create an initial state with several transforms added to simulate data that was already
// persisted before the service started.
let mut state = StateBuilder::new().add_property(target, TransformFlags::TRANSFORM_MIN).build();
// Keep track of all IDs that are seen.
let mut ids_seen = HashSet::new();
for _ in 0..10 {
let id = state.add_transform(target, transform).expect("failed to add transform");
// Verify the ID has not been seen before. Insert returns false if it's not a new member
// of the set.
assert!(ids_seen.insert(id));
}
let env = create_test_environment_with_data(Some(&state)).await;
// Add more policy transforms.
for _ in 0..10 {
let id = add_policy(&env, target, transform).await;
// Verify the ID has not been seen before. Insert returns false if it's not a new member
// of the set.
assert!(ids_seen.insert(PolicyId::create(id)));
}
}
// Tests that volume limits specified in transform parameters for new policies are rounded.
#[fuchsia_async::run_until_stalled(test)]
async fn test_policy_add_policy_rounds_volume_limits() {
// Test various starting volumes to make sure the actual added policy limit is rounded.
let mut input_volume_limit = 0.0;
add_and_verify_max_volume_policy(input_volume_limit, round_volume_level(input_volume_limit))
.await;
input_volume_limit = 0.999999;
add_and_verify_max_volume_policy(input_volume_limit, round_volume_level(input_volume_limit))
.await;
input_volume_limit = 0.12452;
add_and_verify_max_volume_policy(input_volume_limit, round_volume_level(input_volume_limit))
.await;
}
// Tests that volume limits specified in transform parameters for new policies are clamped to the
// [0-1] range.
#[fuchsia_async::run_until_stalled(test)]
async fn test_policy_add_policy_clamps_volume_limits() {
let min_volume = 0.0;
let max_volume = 1.0;
// Values below the minimum volume level are clamped to the minimum volume level.
add_and_verify_max_volume_policy(-0.0, min_volume).await;
add_and_verify_max_volume_policy(-0.1, min_volume).await;
add_and_verify_max_volume_policy(f32::MIN, min_volume).await;
add_and_verify_max_volume_policy(f32::NEG_INFINITY, min_volume).await;
// Values above the maximum volume level are clamped to the maximum volume level.
add_and_verify_max_volume_policy(1.1, max_volume).await;
add_and_verify_max_volume_policy(f32::MAX, max_volume).await;
add_and_verify_max_volume_policy(f32::INFINITY, max_volume).await;
}
// Tests that removing and added policy transform works and the removed policy is gone
// from GetProperties.
#[fuchsia_async::run_until_stalled(test)]
async fn test_policy_remove_policy() {
let expected_policy_target = AudioStreamType::Media;
let env = create_test_environment().await;
// Add a policy property and save the returned policy ID.
let added_policy_id = add_policy(&env, expected_policy_target, Transform::Max(1.0)).await;
// Remove the same policy using the returned ID.
remove_policy(&env, added_policy_id).await;
// Fetch the properties.
let properties = env.policy_service.get_properties().await.expect("failed to get properties");
// Verify that the expected target has the given policy and that others are unchanged.
for property in properties {
// No policies are active.
assert!(property.active_policies.unwrap().is_empty());
}
}
// Tests that adding a new min policy transform affects the audio output from the audio setting.
#[fuchsia_async::run_until_stalled(test)]
async fn test_policy_add_min_policy_adjusts_volume() {
let expected_policy_target = AudioStreamType::Media;
let expected_user_volume: f32 = 0.8;
let env = create_test_environment().await;
// Set media volume level to 0 to start with.
set_stream_volume(&env, expected_policy_target, 0.0).await;
// Add a min policy transform.
add_policy(&env, expected_policy_target, Transform::Min(expected_user_volume)).await;
assert_eq!(expected_user_volume, get_stream_volume(&env, expected_policy_target).await);
}
// Tests that adding a new min policy transform unmutes a stream.
#[fuchsia_async::run_until_stalled(test)]
async fn test_policy_add_min_policy_unmutes_stream() {
let policy_target = AudioStreamType::Media;
let starting_stream = AudioStream {
stream_type: policy_target,
source: AudioSettingSource::User,
user_volume_level: 0.5,
user_volume_muted: true,
};
let env = create_test_environment().await;
// Start with the stream muted.
set_stream(&env, starting_stream).await;
// Add a min policy transform to unmute the stream
add_policy(&env, policy_target, Transform::Min(0.1)).await;
// Verify the stream is unmuted.
let stream = get_stream_from_proxy(&env.setui_audio_service, policy_target).await;
assert_eq!(stream.user_volume.expect("no user volume").muted.expect("no muted state"), false);
}
// Tests that adding a new max policy transform does not initially affect the audio output from the
// audio setting when at max, but does take effect after removal.
#[fuchsia_async::run_until_stalled(test)]
async fn test_policy_add_max_policy_adjusts_volume_after_removal() {
let expected_policy_target = AudioStreamType::Media;
let initial_volume: f32 = 1.0;
let expected_user_volume: f32 = 0.8;
let env = create_test_environment().await;
// Set media volume level to max to start with.
set_stream_volume(&env, expected_policy_target, initial_volume).await;
// Add a max policy transform.
let added_policy_id =
add_policy(&env, expected_policy_target, Transform::Max(expected_user_volume)).await;
// Volume should stay at max from the point of view of the client.
assert_eq!(initial_volume, get_stream_volume(&env, expected_policy_target).await);
// Remove the added policy.
remove_policy(&env, added_policy_id).await;
// After the policy is removed, the volume now reflects its clamped value.
assert_eq!(expected_user_volume, get_stream_volume(&env, expected_policy_target).await);
}
// Tests that incoming sets are clamped when a min policy is present.
#[fuchsia_async::run_until_stalled(test)]
async fn test_policy_min_policy_clamps_sets() {
let expected_policy_target = AudioStreamType::Media;
let expected_user_volume: f32 = 0.2;
let env = create_test_environment().await;
// Set media volume level to 50% to start with.
set_stream_volume(&env, expected_policy_target, 0.5).await;
// Add a min policy transform.
let policy_id =
add_policy(&env, expected_policy_target, Transform::Min(expected_user_volume)).await;
// Attempt to set the volume to 0.
set_stream_volume(&env, expected_policy_target, 0.0).await;
// The volume remains at 20%, the minimum set by policy.
assert_eq!(expected_user_volume, get_stream_volume(&env, expected_policy_target).await);
// Remove the min volume policy.
remove_policy(&env, policy_id).await;
// Use a new connection to get the value. The original connection won't return the value again
// since it hasn't changed.
let audio_connection = env.nested_environment.connect_to_service::<AudioMarker>().unwrap();
// The volume remains at 20% after the policy is removed.
assert_eq!(
expected_user_volume,
get_stream_volume_from_proxy(&audio_connection, expected_policy_target).await
);
}
// Tests that incoming sets are scaled when a max policy is present.
#[fuchsia_async::run_until_stalled(test)]
async fn test_policy_max_policy_scales_sets() {
let expected_policy_target = AudioStreamType::Media;
let initial_volume: f32 = 0.4;
let max_volume_limit: f32 = 0.8;
let max_volume: f32 = 1.0;
let env = create_test_environment().await;
// Set media volume level to 40% to start with.
set_stream_volume(&env, expected_policy_target, initial_volume).await;
// Add a max policy transform.
let policy_id =
add_policy(&env, expected_policy_target, Transform::Max(max_volume_limit)).await;
// The volume is 50% when retrieved, since the initial 40% is half of the max of 80%.
assert_eq!(
initial_volume / max_volume_limit,
get_stream_volume(&env, expected_policy_target).await
);
// Attempt to set the volume to max.
set_stream_volume(&env, expected_policy_target, max_volume).await;
// The volume is 100% when retrieved since the max policy limit is transparent to clients.
assert_eq!(max_volume, get_stream_volume(&env, expected_policy_target).await);
// Remove the max volume policy.
remove_policy(&env, policy_id).await;
// The volume is 80% when retrieved after the policy is removed, since the internal volume was
// 80% due to the policy.
assert_eq!(max_volume_limit, get_stream_volume(&env, expected_policy_target).await);
}