blob: 68387d364c8a18e57320a5d3557e1a169390c27b [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 crate::audio::types::{AudioInfo, AudioInputInfo, AudioStream, AudioStreamType};
use crate::audio::{
create_default_modified_counters, default_audio_info, ModifiedCounters, StreamVolumeControl,
};
use crate::base::SettingType;
use crate::handler::base::Request;
use crate::handler::device_storage::{DeviceStorageAccess, DeviceStorageCompatible};
use crate::handler::setting_handler::persist::{
controller as data_controller, ClientProxy, WriteResult,
};
use crate::handler::setting_handler::{
controller, ControllerError, ControllerStateResult, Event, IntoHandlerResult,
SettingHandlerResult, State,
};
use crate::input::ButtonType;
use async_trait::async_trait;
use fuchsia_async as fasync;
use futures::lock::Mutex;
use std::collections::HashMap;
use std::sync::Arc;
fn get_streams_array_from_map(
stream_map: &HashMap<AudioStreamType, StreamVolumeControl>,
) -> [AudioStream; 5] {
let mut streams: [AudioStream; 5] = default_audio_info().streams;
for i in 0..streams.len() {
if let Some(volume_control) = stream_map.get(&streams[i].stream_type) {
streams[i] = volume_control.stored_stream.clone();
}
}
streams
}
type VolumeControllerHandle = Arc<Mutex<VolumeController>>;
pub struct VolumeController {
client: ClientProxy,
audio_service_connected: bool,
stream_volume_controls: HashMap<AudioStreamType, StreamVolumeControl>,
mic_mute_state: Option<bool>,
modified_counters: ModifiedCounters,
}
enum UpdateFrom<'a> {
AudioInfo(AudioInfo),
NewStreams(&'a [AudioStream]),
}
impl VolumeController {
async fn create(client: ClientProxy) -> VolumeControllerHandle {
let handle = Arc::new(Mutex::new(Self {
client,
stream_volume_controls: HashMap::new(),
audio_service_connected: false,
mic_mute_state: None,
modified_counters: create_default_modified_counters(),
}));
handle
}
/// Restores the necessary dependencies' state on boot.
async fn restore(&mut self) -> ControllerStateResult {
self.restore_volume_state(true).await
}
/// Extracts the audio state from persistent storage and restores it on
/// the local state. Also pushes the changes to the audio core if
/// [push_to_audio_core] is true.
async fn restore_volume_state(&mut self, push_to_audio_core: bool) -> ControllerStateResult {
let audio_info = self.client.read_setting::<AudioInfo>().await;
self.update_volume_streams(UpdateFrom::AudioInfo(audio_info), push_to_audio_core).await?;
Ok(())
}
async fn get_info(&self) -> Result<AudioInfo, ControllerError> {
let mut audio_info = self.client.read_setting::<AudioInfo>().await;
// Only override the mic mute state if present.
if let Some(mic_mute_state) = self.mic_mute_state {
audio_info.input = AudioInputInfo { mic_mute: mic_mute_state };
}
audio_info.modified_counters = Some(self.modified_counters.clone());
Ok(audio_info)
}
async fn set_volume(&mut self, volume: Vec<AudioStream>) -> SettingHandlerResult {
// Update counters for changed streams.
for stream in &volume {
// We don't care what the value of the counter is, just that it is different from the
// previous value. We use wrapping_add to avoid eventual overflow of the counter.
self.modified_counters.insert(
stream.stream_type,
self.modified_counters
.get(&stream.stream_type)
.map_or(0, |flag| flag.wrapping_add(1)),
);
}
if !(self.update_volume_streams(UpdateFrom::NewStreams(&volume[..]), true).await?) {
let info = self.get_info().await?.into();
self.client.notify(Event::Changed(info)).await;
}
Ok(None)
}
async fn set_mic_mute_state(&mut self, mic_mute_state: bool) -> SettingHandlerResult {
self.mic_mute_state = Some(mic_mute_state);
let mut audio_info = self.client.read_setting::<AudioInfo>().await;
audio_info.input.mic_mute = mic_mute_state;
self.client.write_setting(audio_info.into(), false).await.into_handler_result()
}
/// Updates the state with the given streams' volume levels.
///
/// If [push_to_audio_core] is true, pushes the changes to the audio core.
/// If not, just sets it on the local stored state. Should be called with
/// true on first restore and on volume changes, and false otherwise.
/// Returns whether the change triggered a notification.
async fn update_volume_streams(
&mut self,
update_from: UpdateFrom<'_>,
push_to_audio_core: bool,
) -> Result<bool, ControllerError> {
let new_streams = match &update_from {
UpdateFrom::AudioInfo(audio_info) => &audio_info.streams[..],
UpdateFrom::NewStreams(streams) => streams,
};
if push_to_audio_core {
self.check_and_bind_volume_controls(&default_audio_info().streams.to_vec()).await?;
for stream in new_streams {
if let Some(volume_control) =
self.stream_volume_controls.get_mut(&stream.stream_type)
{
volume_control.set_volume(stream.clone()).await?;
}
}
} else {
self.check_and_bind_volume_controls(new_streams).await?;
}
let mut stored_value = match update_from {
UpdateFrom::AudioInfo(audio_info) => audio_info,
_ => self.client.read_setting::<AudioInfo>().await,
};
stored_value.streams = get_streams_array_from_map(&self.stream_volume_controls);
stored_value.modified_counters = Some(self.modified_counters.clone());
Ok(self.client.write_setting(stored_value.into(), false).await.notified())
}
/// Populates the local state with the given [streams] and binds it to the audio core service.
async fn check_and_bind_volume_controls(
&mut self,
streams: &[AudioStream],
) -> ControllerStateResult {
if self.audio_service_connected {
return Ok(());
}
let service_result = self
.client
.get_service_context()
.connect::<fidl_fuchsia_media::AudioCoreMarker>()
.await;
let audio_service = service_result.map_err(|_| {
ControllerError::ExternalFailure(
SettingType::Audio,
"fuchsia.media.audio".into(),
"connect for audio_core".into(),
)
})?;
self.audio_service_connected = true;
for stream in streams {
let client = self.client.clone();
let stream_volume_control = StreamVolumeControl::create(
&audio_service,
*stream,
Some(Arc::new(move || {
// When the StreamVolumeControl exits early, inform the
// proxy we have exited. The proxy will then cleanup this
// AudioController.
let client = client.clone();
fasync::Task::spawn(async move {
client
.notify(Event::Exited(Err(ControllerError::UnexpectedError(
"stream_volume_control exit".into(),
))))
.await;
})
.detach();
})),
None,
)
// TODO(fxbug.dev/73629) This could be bad. If this fails partway we would have
// inserted some of the volume controls but not all. The service would also think that
// the audio core service is fully connected since that setting is not reverted.
.await?;
self.stream_volume_controls.insert(stream.stream_type, stream_volume_control);
}
Ok(())
}
}
pub struct AudioController {
volume: VolumeControllerHandle,
}
impl DeviceStorageAccess for AudioController {
const STORAGE_KEYS: &'static [&'static str] = &[AudioInfo::KEY];
}
#[async_trait]
impl data_controller::Create for AudioController {
/// Creates the controller
async fn create(client: ClientProxy) -> Result<Self, ControllerError> {
Ok(AudioController { volume: VolumeController::create(client).await })
}
}
#[async_trait]
impl controller::Handle for AudioController {
async fn handle(&self, request: Request) -> Option<SettingHandlerResult> {
match request {
Request::Restore => Some(self.volume.lock().await.restore().await.map(|_| None)),
Request::SetVolume(volume) => Some(self.volume.lock().await.set_volume(volume).await),
Request::Get => {
Some(self.volume.lock().await.get_info().await.map(|info| Some(info.into())))
}
Request::OnButton(ButtonType::MicrophoneMute(state)) => {
Some(self.volume.lock().await.set_mic_mute_state(state).await)
}
_ => None,
}
}
async fn change_state(&mut self, state: State) -> Option<ControllerStateResult> {
match state {
State::Startup => {
// Restore the volume state locally but do not push to the audio core.
Some(self.volume.lock().await.restore_volume_state(false).await)
}
_ => None,
}
}
}