use crate::agent::earcons::agent::CommonEarconsParams;
use crate::agent::earcons::sound_ids::{
BLUETOOTH_CONNECTED_SOUND_ID, BLUETOOTH_DISCONNECTED_SOUND_ID,
};
use crate::agent::earcons::utils::{connect_to_sound_player, play_sound};
use crate::audio::types::{AudioSettingSource, AudioStreamType, SetAudioStream};
use crate::base::{SettingInfo, SettingType};
use crate::event::Publisher;
use crate::handler::base::{Payload, Request};
use crate::message::base::Audience;
use crate::{call, service, trace};
use anyhow::{format_err, Context, Error};
use fidl::endpoints::create_request_stream;
use fidl_fuchsia_media_sessions2::{
DiscoveryMarker, SessionsWatcherRequest, SessionsWatcherRequestStream, WatchOptions,
};
use futures::stream::TryStreamExt;
use std::collections::HashSet;
use {fuchsia_async as fasync, fuchsia_trace as ftrace};
type SessionId = u64;
const BLUETOOTH_CONNECTED_FILE_PATH: &str = "bluetooth-connected.wav";
const BLUETOOTH_DISCONNECTED_FILE_PATH: &str = "bluetooth-disconnected.wav";
pub(crate) const BLUETOOTH_DOMAIN: &str = "Bluetooth";
#[derive(Debug)]
pub(super) struct BluetoothHandler {
common_earcons_params: CommonEarconsParams,
publisher: Publisher,
active_sessions: HashSet<SessionId>,
messenger: service::message::Messenger,
}
enum BluetoothSoundType {
Connected,
Disconnected,
}
impl BluetoothHandler {
pub(super) async fn create(
publisher: Publisher,
params: CommonEarconsParams,
messenger: service::message::Messenger,
) -> Result<(), Error> {
let mut handler = Self {
common_earcons_params: params,
publisher,
active_sessions: HashSet::<SessionId>::new(),
messenger,
};
handler.watch_bluetooth_connections().await
}
pub(super) async fn watch_bluetooth_connections(&mut self) -> Result<(), Error> {
let discovery_connection_result = self
.common_earcons_params
.service_context
.connect_with_publisher::<DiscoveryMarker>(self.publisher.clone())
.await
.context("Connecting to fuchsia.media.sessions2.Discovery");
let discovery_proxy = discovery_connection_result.map_err(|e| {
format_err!("Failed to connect to fuchsia.media.sessions2.Discovery: {:?}", e)
})?;
let (watcher_client, watcher_requests) = create_request_stream();
call!(discovery_proxy =>
watch_sessions(&WatchOptions::default(), watcher_client))
.map_err(|e| format_err!("Unable to start discovery of MediaSessions: {:?}", e))?;
self.handle_bluetooth_connections(watcher_requests);
Ok(())
}
fn handle_bluetooth_connections(&mut self, mut watcher_requests: SessionsWatcherRequestStream) {
let mut active_sessions_clone = self.active_sessions.clone();
let publisher = self.publisher.clone();
let common_earcons_params = self.common_earcons_params.clone();
let messenger = self.messenger.clone();
fasync::Task::local(async move {
loop {
let maybe_req = watcher_requests.try_next().await;
match maybe_req {
Ok(Some(req)) => {
match req {
SessionsWatcherRequest::SessionUpdated {
session_id: id,
session_info_delta: delta,
responder,
} => {
if let Err(e) = responder.send() {
tracing::error!("Failed to acknowledge delta from SessionWatcher: {:?}", e);
return;
}
if active_sessions_clone.contains(&id)
|| !matches!(delta.domain, Some(name) if name == BLUETOOTH_DOMAIN)
{
continue;
}
let _ = active_sessions_clone.insert(id);
let publisher = publisher.clone();
let common_earcons_params = common_earcons_params.clone();
let messenger = messenger.clone();
fasync::Task::local(async move {
play_bluetooth_sound(
common_earcons_params,
publisher,
BluetoothSoundType::Connected,
messenger,
)
.await;
})
.detach();
}
SessionsWatcherRequest::SessionRemoved { session_id, responder } => {
if let Err(e) = responder.send() {
tracing::error!(
"Failed to acknowledge session removal from SessionWatcher: {:?}",
e
);
return;
}
if !active_sessions_clone.contains(&session_id) {
tracing::warn!(
"Tried to remove nonexistent media session id {:?}",
session_id
);
continue;
}
let _ = active_sessions_clone.remove(&session_id);
let publisher = publisher.clone();
let common_earcons_params = common_earcons_params.clone();
let messenger = messenger.clone();
fasync::Task::local(async move {
play_bluetooth_sound(
common_earcons_params,
publisher,
BluetoothSoundType::Disconnected,
messenger,
)
.await;
})
.detach();
}
}
},
Ok(None) => {
tracing::warn!("stream ended on fuchsia.media.sessions2.SessionsWatcher");
break;
},
Err(e) => {
tracing::error!("failed to watch fuchsia.media.sessions2.SessionsWatcher: {:?}", &e);
break;
},
}
}
})
.detach();
}
}
async fn play_bluetooth_sound(
common_earcons_params: CommonEarconsParams,
publisher: Publisher,
sound_type: BluetoothSoundType,
messenger: service::message::Messenger,
) {
connect_to_sound_player(
publisher,
common_earcons_params.service_context.clone(),
common_earcons_params.sound_player_connection.clone(),
)
.await;
let sound_player_connection = common_earcons_params.sound_player_connection.clone();
let sound_player_connection_lock = sound_player_connection.lock().await;
let sound_player_added_files = common_earcons_params.sound_player_added_files.clone();
if let Some(sound_player_proxy) = sound_player_connection_lock.as_ref() {
match_background_to_media(messenger).await;
match sound_type {
BluetoothSoundType::Connected => {
if play_sound(
sound_player_proxy,
BLUETOOTH_CONNECTED_FILE_PATH,
BLUETOOTH_CONNECTED_SOUND_ID,
sound_player_added_files.clone(),
)
.await
.is_err()
{
tracing::error!("[bluetooth_earcons_handler] failed to play bluetooth earcon connection sound");
}
}
BluetoothSoundType::Disconnected => {
if play_sound(
sound_player_proxy,
BLUETOOTH_DISCONNECTED_FILE_PATH,
BLUETOOTH_DISCONNECTED_SOUND_ID,
sound_player_added_files.clone(),
)
.await
.is_err()
{
tracing::error!("[bluetooth_earcons_handler] failed to play bluetooth earcon disconnection sound");
}
}
};
} else {
tracing::error!("[bluetooth_earcons_handler] failed to play bluetooth earcon sound: no sound player connection");
}
}
async fn match_background_to_media(messenger: service::message::Messenger) {
let mut get_receptor = messenger.message(
Payload::Request(Request::Get).into(),
Audience::Address(service::Address::Handler(SettingType::Audio)),
);
let mut media_volume = 0.0;
let mut background_volume = 0.0;
if let Ok((Payload::Response(Ok(Some(SettingInfo::Audio(info)))), _)) =
get_receptor.next_of::<Payload>().await
{
info.streams.iter().for_each(|stream| {
if stream.stream_type == AudioStreamType::Media {
media_volume = stream.user_volume_level;
} else if stream.stream_type == AudioStreamType::Background {
background_volume = stream.user_volume_level;
}
})
} else {
tracing::error!("Could not extract background and media volumes")
};
if media_volume != background_volume {
let id = ftrace::Id::new();
trace!(id, c"bluetooth_handler set background volume");
let mut receptor = messenger.message(
Payload::Request(Request::SetVolume(
vec![SetAudioStream {
stream_type: AudioStreamType::Background,
source: AudioSettingSource::System,
user_volume_level: Some(media_volume),
user_volume_muted: None,
}],
id,
))
.into(),
Audience::Address(service::Address::Handler(SettingType::Audio)),
);
if receptor.next_payload().await.is_err() {
tracing::error!(
"Failed to play bluetooth connection sound after waiting for message response"
);
}
}
}