settings/agent/earcons/
bluetooth_handler.rs1use crate::agent::earcons::agent::CommonEarconsParams;
6use crate::agent::earcons::sound_ids::{
7 BLUETOOTH_CONNECTED_SOUND_ID, BLUETOOTH_DISCONNECTED_SOUND_ID,
8};
9use crate::agent::earcons::utils::{connect_to_sound_player, play_sound};
10use crate::audio::types::{AudioSettingSource, AudioStreamType, SetAudioStream};
11use crate::base::{SettingInfo, SettingType};
12use crate::event::Publisher;
13use crate::handler::base::{Payload, Request};
14use crate::message::base::Audience;
15use crate::{call, service, trace};
16
17use anyhow::{format_err, Context, Error};
18use fidl::endpoints::create_request_stream;
19use fidl_fuchsia_media_sessions2::{
20 DiscoveryMarker, SessionsWatcherRequest, SessionsWatcherRequestStream, WatchOptions,
21};
22use futures::stream::TryStreamExt;
23use std::collections::HashSet;
24use {fuchsia_async as fasync, fuchsia_trace as ftrace};
25
26type SessionId = u64;
28
29const BLUETOOTH_CONNECTED_FILE_PATH: &str = "bluetooth-connected.wav";
31
32const BLUETOOTH_DISCONNECTED_FILE_PATH: &str = "bluetooth-disconnected.wav";
34
35pub(crate) const BLUETOOTH_DOMAIN: &str = "Bluetooth";
36
37#[derive(Debug)]
40pub(super) struct BluetoothHandler {
41 common_earcons_params: CommonEarconsParams,
43 publisher: Publisher,
45 active_sessions: HashSet<SessionId>,
47 messenger: service::message::Messenger,
49}
50
51enum BluetoothSoundType {
53 Connected,
54 Disconnected,
55}
56
57impl BluetoothHandler {
58 pub(super) async fn create(
59 publisher: Publisher,
60 params: CommonEarconsParams,
61 messenger: service::message::Messenger,
62 ) -> Result<(), Error> {
63 let mut handler = Self {
64 common_earcons_params: params,
65 publisher,
66 active_sessions: HashSet::<SessionId>::new(),
67 messenger,
68 };
69 handler.watch_bluetooth_connections().await
70 }
71
72 pub(super) async fn watch_bluetooth_connections(&mut self) -> Result<(), Error> {
76 let discovery_connection_result = self
78 .common_earcons_params
79 .service_context
80 .connect_with_publisher::<DiscoveryMarker>(self.publisher.clone())
81 .await
82 .context("Connecting to fuchsia.media.sessions2.Discovery");
83
84 let discovery_proxy = discovery_connection_result.map_err(|e| {
85 format_err!("Failed to connect to fuchsia.media.sessions2.Discovery: {:?}", e)
86 })?;
87
88 let (watcher_client, watcher_requests) = create_request_stream();
90
91 call!(discovery_proxy =>
92 watch_sessions(&WatchOptions::default(), watcher_client))
93 .map_err(|e| format_err!("Unable to start discovery of MediaSessions: {:?}", e))?;
94
95 self.handle_bluetooth_connections(watcher_requests);
96 Ok(())
97 }
98
99 fn handle_bluetooth_connections(&mut self, mut watcher_requests: SessionsWatcherRequestStream) {
102 let mut active_sessions_clone = self.active_sessions.clone();
103 let publisher = self.publisher.clone();
104 let common_earcons_params = self.common_earcons_params.clone();
105 let messenger = self.messenger.clone();
106
107 fasync::Task::local(async move {
108 loop {
109 let maybe_req = watcher_requests.try_next().await;
110 match maybe_req {
111 Ok(Some(req)) => {
112 match req {
113 SessionsWatcherRequest::SessionUpdated {
114 session_id: id,
115 session_info_delta: delta,
116 responder,
117 } => {
118 if let Err(e) = responder.send() {
119 log::error!("Failed to acknowledge delta from SessionWatcher: {:?}", e);
120 return;
121 }
122
123 if active_sessions_clone.contains(&id)
124 || !matches!(delta.domain, Some(name) if name == BLUETOOTH_DOMAIN)
125 {
126 continue;
127 }
128 let _ = active_sessions_clone.insert(id);
129
130 let publisher = publisher.clone();
131 let common_earcons_params = common_earcons_params.clone();
132 let messenger = messenger.clone();
133 fasync::Task::local(async move {
134 play_bluetooth_sound(
135 common_earcons_params,
136 publisher,
137 BluetoothSoundType::Connected,
138 messenger,
139 )
140 .await;
141 })
142 .detach();
143 }
144 SessionsWatcherRequest::SessionRemoved { session_id, responder } => {
145 if let Err(e) = responder.send() {
146 log::error!(
147 "Failed to acknowledge session removal from SessionWatcher: {:?}",
148 e
149 );
150 return;
151 }
152
153 if !active_sessions_clone.contains(&session_id) {
154 log::warn!(
155 "Tried to remove nonexistent media session id {:?}",
156 session_id
157 );
158 continue;
159 }
160 let _ = active_sessions_clone.remove(&session_id);
161 let publisher = publisher.clone();
162 let common_earcons_params = common_earcons_params.clone();
163 let messenger = messenger.clone();
164 fasync::Task::local(async move {
165 play_bluetooth_sound(
166 common_earcons_params,
167 publisher,
168 BluetoothSoundType::Disconnected,
169 messenger,
170 )
171 .await;
172 })
173 .detach();
174 }
175 }
176 },
177 Ok(None) => {
178 log::warn!("stream ended on fuchsia.media.sessions2.SessionsWatcher");
179 break;
180 },
181 Err(e) => {
182 log::error!("failed to watch fuchsia.media.sessions2.SessionsWatcher: {:?}", &e);
183 break;
184 },
185 }
186 }
187 })
188 .detach();
189 }
190}
191
192async fn play_bluetooth_sound(
194 common_earcons_params: CommonEarconsParams,
195 publisher: Publisher,
196 sound_type: BluetoothSoundType,
197 messenger: service::message::Messenger,
198) {
199 connect_to_sound_player(
201 publisher,
202 common_earcons_params.service_context.clone(),
203 common_earcons_params.sound_player_connection.clone(),
204 )
205 .await;
206
207 let sound_player_connection = common_earcons_params.sound_player_connection.clone();
208 let sound_player_connection_lock = sound_player_connection.lock().await;
209 let sound_player_added_files = common_earcons_params.sound_player_added_files.clone();
210
211 if let Some(sound_player_proxy) = sound_player_connection_lock.as_ref() {
212 match_background_to_media(messenger).await;
213 match sound_type {
214 BluetoothSoundType::Connected => {
215 if play_sound(
216 sound_player_proxy,
217 BLUETOOTH_CONNECTED_FILE_PATH,
218 BLUETOOTH_CONNECTED_SOUND_ID,
219 sound_player_added_files.clone(),
220 )
221 .await
222 .is_err()
223 {
224 log::error!("[bluetooth_earcons_handler] failed to play bluetooth earcon connection sound");
225 }
226 }
227 BluetoothSoundType::Disconnected => {
228 if play_sound(
229 sound_player_proxy,
230 BLUETOOTH_DISCONNECTED_FILE_PATH,
231 BLUETOOTH_DISCONNECTED_SOUND_ID,
232 sound_player_added_files.clone(),
233 )
234 .await
235 .is_err()
236 {
237 log::error!("[bluetooth_earcons_handler] failed to play bluetooth earcon disconnection sound");
238 }
239 }
240 };
241 } else {
242 log::error!("[bluetooth_earcons_handler] failed to play bluetooth earcon sound: no sound player connection");
243 }
244}
245
246async fn match_background_to_media(messenger: service::message::Messenger) {
248 let mut get_receptor = messenger.message(
250 Payload::Request(Request::Get).into(),
251 Audience::Address(service::Address::Handler(SettingType::Audio)),
252 );
253
254 let mut media_volume = 0.0;
256 let mut background_volume = 0.0;
257 if let Ok((Payload::Response(Ok(Some(SettingInfo::Audio(info)))), _)) =
258 get_receptor.next_of::<Payload>().await
259 {
260 info.streams.iter().for_each(|stream| {
261 if stream.stream_type == AudioStreamType::Media {
262 media_volume = stream.user_volume_level;
263 } else if stream.stream_type == AudioStreamType::Background {
264 background_volume = stream.user_volume_level;
265 }
266 })
267 } else {
268 log::error!("Could not extract background and media volumes")
269 };
270
271 if media_volume != background_volume {
273 let id = ftrace::Id::new();
274 trace!(id, c"bluetooth_handler set background volume");
275 let mut receptor = messenger.message(
276 Payload::Request(Request::SetVolume(
277 vec![SetAudioStream {
278 stream_type: AudioStreamType::Background,
279 source: AudioSettingSource::System,
280 user_volume_level: Some(media_volume),
281 user_volume_muted: None,
282 }],
283 id,
284 ))
285 .into(),
286 Audience::Address(service::Address::Handler(SettingType::Audio)),
287 );
288
289 if receptor.next_payload().await.is_err() {
290 log::error!(
291 "Failed to play bluetooth connection sound after waiting for message response"
292 );
293 }
294 }
295}