1use crate::agent::earcons::agent::CommonEarconsParams;
6use crate::agent::earcons::sound_ids::{VOLUME_CHANGED_SOUND_ID, VOLUME_MAX_SOUND_ID};
7use crate::agent::earcons::utils::{connect_to_sound_player, play_sound};
8use crate::audio::types::{
9 AUDIO_STREAM_TYPE_COUNT, AudioInfo, AudioSettingSource, AudioStream, AudioStreamType,
10 SetAudioStream,
11};
12use crate::audio::{ModifiedCounters, Request as AudioRequest, create_default_modified_counters};
13use anyhow::Error;
14use futures::StreamExt;
15use futures::channel::mpsc::{self, UnboundedSender};
16use futures::channel::oneshot;
17use futures::future::OptionFuture;
18use settings_common::inspect::event::ExternalEventPublisher;
19use settings_common::trace;
20use std::collections::{HashMap, HashSet};
21use std::rc::Rc;
22use {fuchsia_async as fasync, fuchsia_trace as ftrace};
23
24pub(super) struct VolumeChangeHandler {
26 common_earcons_params: CommonEarconsParams,
27 last_user_volumes: HashMap<AudioStreamType, f32>,
28 modified_counters: ModifiedCounters,
29 external_publisher: ExternalEventPublisher,
30 audio_request_tx: Option<UnboundedSender<AudioRequest>>,
31}
32
33const MAX_VOLUME: f32 = 1.0;
35
36const VOLUME_MAX_FILE_PATH: &str = "volume-max.wav";
38
39const VOLUME_CHANGED_FILE_PATH: &str = "volume-changed.wav";
41
42impl VolumeChangeHandler {
43 pub(super) async fn spawn(
44 audio_request_tx: Option<UnboundedSender<AudioRequest>>,
45 external_publisher: ExternalEventPublisher,
46 params: CommonEarconsParams,
47 ) -> Result<(), Error> {
48 let info = if let Some(request_tx) = audio_request_tx.as_ref() {
49 let (tx, rx) = oneshot::channel();
50 if request_tx.unbounded_send(AudioRequest::Get(ftrace::Id::new(), tx)).is_ok() {
51 rx.await.ok()
52 } else {
53 None
54 }
55 } else {
56 None
57 };
58 let last_user_volumes = info
59 .map(|info| {
60 info.streams
62 .iter()
63 .filter(|x| {
64 x.stream_type == AudioStreamType::Media
65 || x.stream_type == AudioStreamType::Interruption
66 })
67 .map(|stream| (stream.stream_type, stream.user_volume_level))
68 .collect()
69 })
70 .unwrap_or_else(|| HashMap::new());
71
72 fasync::Task::local(async move {
73 let mut handler = Self {
74 common_earcons_params: params,
75 last_user_volumes,
76 modified_counters: create_default_modified_counters(),
77 external_publisher,
78 audio_request_tx: audio_request_tx.clone(),
79 };
80
81 let mut audio_rx = audio_request_tx.as_ref().and_then(|request_tx| {
82 let (audio_tx, audio_rx) = mpsc::unbounded();
83 request_tx.unbounded_send(AudioRequest::Listen(audio_tx)).ok().map(|_| audio_rx)
84 });
85
86 let mut audio_next = OptionFuture::from(audio_rx.as_mut().map(|rx| rx.next()));
87
88 loop {
89 futures::select! {
90 audio_info = audio_next => {
91 if let Some(Some(audio_info)) = audio_info {
92 handler.on_audio_info(audio_info).await;
93 audio_next = OptionFuture::from(audio_rx.as_mut().map(|rx| rx.next()));
94 }
95 }
96 complete => break,
97 }
98 }
99 })
100 .detach();
101
102 Ok(())
103 }
104
105 fn calculate_changed_streams(
109 &mut self,
110 all_streams: [AudioStream; AUDIO_STREAM_TYPE_COUNT],
111 new_modified_counters: ModifiedCounters,
112 ) -> Vec<AudioStream> {
113 let mut changed_stream_types = HashSet::new();
114 for (stream_type, timestamp) in new_modified_counters {
115 if self.modified_counters.get(&stream_type) != Some(×tamp) {
116 let _ = changed_stream_types.insert(stream_type);
117 let _ = self.modified_counters.insert(stream_type, timestamp);
118 }
119 }
120
121 IntoIterator::into_iter(all_streams)
122 .filter(|stream| changed_stream_types.contains(&stream.stream_type))
123 .collect()
124 }
125
126 fn get_user_volume(
128 &self,
129 changed_streams: Vec<AudioStream>,
130 stream_type: AudioStreamType,
131 ) -> Option<f32> {
132 changed_streams.iter().find(|&&x| x.stream_type == stream_type).map(|x| x.user_volume_level)
133 }
134
135 fn get_change_source(
137 &self,
138 changed_streams: Vec<AudioStream>,
139 stream_type: AudioStreamType,
140 ) -> Option<AudioSettingSource> {
141 changed_streams.iter().find(|&&x| x.stream_type == stream_type).map(|x| x.source)
142 }
143
144 async fn on_audio_info_for_stream(
147 &mut self,
148 new_user_volume: f32,
149 stream_type: AudioStreamType,
150 change_source: Option<AudioSettingSource>,
151 ) {
152 let volume_is_max = new_user_volume == MAX_VOLUME;
153 let last_user_volume = self.last_user_volumes.get(&stream_type);
154
155 log::debug!(
157 "[earcons_agent] New {:?} user volume: {:?}, Last {:?} user volume: {:?}",
158 stream_type,
159 new_user_volume,
160 stream_type,
161 last_user_volume,
162 );
163
164 if last_user_volume != Some(&new_user_volume) || volume_is_max {
165 if last_user_volume.is_some() && change_source != Some(AudioSettingSource::System) {
174 let id = ftrace::Id::new();
175 trace!(id, "volume_change_handler set background");
176 let streams = vec![SetAudioStream {
177 stream_type: AudioStreamType::Background,
178 source: AudioSettingSource::System,
179 user_volume_level: Some(new_user_volume),
180 user_volume_muted: None,
181 }];
182 let success = if let Some(audio_request_tx) = self.audio_request_tx.as_ref() {
183 let (tx, rx) = oneshot::channel();
184 if audio_request_tx.unbounded_send(AudioRequest::Set(streams, id, tx)).is_ok() {
185 rx.await
186 .map_err(|e| {
187 log::error!(
188 "Failed to play sound after waiting for volume set: {e:?}"
189 )
190 })
191 .is_ok()
192 } else {
193 false
194 }
195 } else {
196 false
197 };
198 if success {
199 self.play_volume_sound(new_user_volume);
200 }
201 }
202
203 let _ = self.last_user_volumes.insert(stream_type, new_user_volume);
204 }
205 }
206
207 async fn on_audio_info(&mut self, audio_info: AudioInfo) {
210 let changed_streams = match audio_info.modified_counters {
211 None => Vec::new(),
212 Some(counters) => self.calculate_changed_streams(audio_info.streams, counters),
213 };
214
215 let media_user_volume =
216 self.get_user_volume(changed_streams.clone(), AudioStreamType::Media);
217 let interruption_user_volume =
218 self.get_user_volume(changed_streams.clone(), AudioStreamType::Interruption);
219 let media_change_source =
220 self.get_change_source(changed_streams.clone(), AudioStreamType::Media);
221
222 if let Some(media_user_volume) = media_user_volume {
223 self.on_audio_info_for_stream(
224 media_user_volume,
225 AudioStreamType::Media,
226 media_change_source,
227 )
228 .await;
229 }
230 if let Some(interruption_user_volume) = interruption_user_volume {
231 self.on_audio_info_for_stream(
232 interruption_user_volume,
233 AudioStreamType::Interruption,
234 None,
235 )
236 .await;
237 }
238 }
239
240 fn play_volume_sound(&self, volume: f32) {
242 let external_publisher = self.external_publisher.clone();
243 let common_earcons_params = self.common_earcons_params.clone();
244
245 fasync::Task::local(async move {
246 connect_to_sound_player(
248 external_publisher,
249 common_earcons_params.service_context,
250 Rc::clone(&common_earcons_params.sound_player_connection),
251 )
252 .await;
253
254 let sound_player_connection = common_earcons_params.sound_player_connection;
255 let sound_player_connection = sound_player_connection.lock().await;
256 let sound_player_added_files = common_earcons_params.sound_player_added_files;
257
258 if let (Some(sound_player_proxy), volume_level) =
259 (sound_player_connection.as_ref(), volume)
260 {
261 let play_sound_result = if volume_level >= 1.0 {
262 play_sound(
263 sound_player_proxy,
264 VOLUME_MAX_FILE_PATH,
265 VOLUME_MAX_SOUND_ID,
266 sound_player_added_files.clone(),
267 )
268 .await
269 } else if volume_level > 0.0 {
270 play_sound(
271 sound_player_proxy,
272 VOLUME_CHANGED_FILE_PATH,
273 VOLUME_CHANGED_SOUND_ID,
274 sound_player_added_files.clone(),
275 )
276 .await
277 } else {
278 Ok(())
279 };
280 if let Err(e) = play_sound_result {
281 log::warn!("Failed to play sound: {:?}", e);
282 }
283 }
284 })
285 .detach();
286 }
287}
288
289#[cfg(test)]
290mod tests {
291 use super::*;
292 use crate::audio::build_audio_default_settings;
293 use fuchsia_inspect::component;
294 use futures::lock::Mutex;
295 use settings_common::inspect::config_logger::InspectConfigLogger;
296 use settings_common::service_context::ServiceContext;
297 use std::rc::Rc;
298
299 fn fake_values() -> (
300 [AudioStream; AUDIO_STREAM_TYPE_COUNT], ModifiedCounters, ModifiedCounters, Vec<AudioStream>, ) {
305 let config_logger =
306 Rc::new(std::sync::Mutex::new(InspectConfigLogger::new(component::inspector().root())));
307 let mut settings = build_audio_default_settings(config_logger);
308 let settings = settings
309 .load_default_value()
310 .expect("config data should exist and be parseable for tests")
311 .unwrap();
312 let fake_streams = settings.streams;
313 let old_timestamps = create_default_modified_counters();
314 let new_timestamps = [
315 (AudioStreamType::Background, 0),
316 (AudioStreamType::Media, 1),
317 (AudioStreamType::Interruption, 0),
318 (AudioStreamType::SystemAgent, 2),
319 (AudioStreamType::Communication, 3),
320 (AudioStreamType::Accessibility, 4),
321 ]
322 .iter()
323 .cloned()
324 .collect();
325 let expected_changed_streams =
326 [fake_streams[1], fake_streams[3], fake_streams[4], fake_streams[5]].to_vec();
327 (fake_streams, old_timestamps, new_timestamps, expected_changed_streams)
328 }
329
330 #[fuchsia::test(allow_stalls = false)]
331 async fn test_changed_streams() {
332 let (fake_streams, old_timestamps, new_timestamps, expected_changed_streams) =
333 fake_values();
334 let (event_tx, _) = mpsc::unbounded();
335 let external_publisher = ExternalEventPublisher::new(event_tx);
336 let last_user_volumes: HashMap<_, _> =
337 [(AudioStreamType::Media, 1.0), (AudioStreamType::Interruption, 0.5)].into();
338
339 let mut handler = VolumeChangeHandler {
340 common_earcons_params: CommonEarconsParams {
341 service_context: Rc::new(ServiceContext::new(None)),
342 sound_player_added_files: Rc::new(Mutex::new(HashSet::new())),
343 sound_player_connection: Rc::new(Mutex::new(None)),
344 },
345 last_user_volumes,
346 modified_counters: old_timestamps,
347 external_publisher,
348 audio_request_tx: None,
349 };
350 let changed_streams = handler.calculate_changed_streams(fake_streams, new_timestamps);
351 assert_eq!(changed_streams, expected_changed_streams);
352 }
353}