settings/agent/earcons/
volume_change_handler.rs

1// Copyright 2020 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use std::collections::{HashMap, HashSet};
6
7use crate::trace;
8use anyhow::Error;
9use futures::StreamExt;
10use {fuchsia_async as fasync, fuchsia_trace as ftrace};
11
12use crate::agent::earcons::agent::CommonEarconsParams;
13use crate::agent::earcons::sound_ids::{VOLUME_CHANGED_SOUND_ID, VOLUME_MAX_SOUND_ID};
14use crate::agent::earcons::utils::{connect_to_sound_player, play_sound};
15use crate::audio::types::{
16    AudioInfo, AudioSettingSource, AudioStream, AudioStreamType, SetAudioStream,
17    AUDIO_STREAM_TYPE_COUNT,
18};
19use crate::audio::{create_default_modified_counters, ModifiedCounters};
20use crate::base::{SettingInfo, SettingType};
21use crate::handler::base::{Payload, Request};
22use crate::message::base::Audience;
23use crate::message::receptor::extract_payload;
24use crate::{event, service};
25
26/// The `VolumeChangeHandler` takes care of the earcons functionality on volume change.
27pub(super) struct VolumeChangeHandler {
28    common_earcons_params: CommonEarconsParams,
29    last_user_volumes: HashMap<AudioStreamType, f32>,
30    modified_counters: ModifiedCounters,
31    publisher: event::Publisher,
32    messenger: service::message::Messenger,
33}
34
35/// The maximum volume level.
36const MAX_VOLUME: f32 = 1.0;
37
38/// The file path for the earcon to be played for max sound level.
39const VOLUME_MAX_FILE_PATH: &str = "volume-max.wav";
40
41/// The file path for the earcon to be played for volume changes below max volume level.
42const VOLUME_CHANGED_FILE_PATH: &str = "volume-changed.wav";
43
44impl VolumeChangeHandler {
45    pub(super) async fn create(
46        publisher: event::Publisher,
47        params: CommonEarconsParams,
48        messenger: service::message::Messenger,
49    ) -> Result<(), Error> {
50        let mut receptor = messenger.message(
51            Payload::Request(Request::Get).into(),
52            Audience::Address(service::Address::Handler(SettingType::Audio)),
53        );
54
55        // Get initial user media volume level.
56        let last_user_volumes =
57            if let Ok((Payload::Response(Ok(Some(SettingInfo::Audio(info)))), _)) =
58                receptor.next_of::<Payload>().await
59            {
60                // Create map from stream type to user volume levels for each stream.
61                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            } else {
70                // Could not extract info from response, default to empty volumes.
71                HashMap::new()
72            };
73
74        fasync::Task::local(async move {
75            let mut handler = Self {
76                common_earcons_params: params,
77                last_user_volumes,
78                modified_counters: create_default_modified_counters(),
79                publisher,
80                messenger: messenger.clone(),
81            };
82
83            let listen_receptor = messenger
84                .message(
85                    Payload::Request(Request::Listen).into(),
86                    Audience::Address(service::Address::Handler(SettingType::Audio)),
87                )
88                .fuse();
89            futures::pin_mut!(listen_receptor);
90
91            loop {
92                futures::select! {
93                    volume_change_event = listen_receptor.next() => {
94                        if let Some(
95                            service::Payload::Setting(Payload::Response(Ok(Some(
96                                SettingInfo::Audio(audio_info)))))
97                        ) = extract_payload(volume_change_event) {
98                            handler.on_audio_info(audio_info).await;
99                        }
100                    }
101                    complete => break,
102                }
103            }
104        })
105        .detach();
106
107        Ok(())
108    }
109
110    /// Calculates and returns the streams that were changed based on
111    /// their timestamps, updating them in the stored timestamps if
112    /// they were changed.
113    fn calculate_changed_streams(
114        &mut self,
115        all_streams: [AudioStream; AUDIO_STREAM_TYPE_COUNT],
116        new_modified_counters: ModifiedCounters,
117    ) -> Vec<AudioStream> {
118        let mut changed_stream_types = HashSet::new();
119        for (stream_type, timestamp) in new_modified_counters {
120            if self.modified_counters.get(&stream_type) != Some(&timestamp) {
121                let _ = changed_stream_types.insert(stream_type);
122                let _ = self.modified_counters.insert(stream_type, timestamp);
123            }
124        }
125
126        IntoIterator::into_iter(all_streams)
127            .filter(|stream| changed_stream_types.contains(&stream.stream_type))
128            .collect()
129    }
130
131    /// Retrieve a user volume of the specified `stream_type` from the given `changed_streams`.
132    fn get_user_volume(
133        &self,
134        changed_streams: Vec<AudioStream>,
135        stream_type: AudioStreamType,
136    ) -> Option<f32> {
137        changed_streams.iter().find(|&&x| x.stream_type == stream_type).map(|x| x.user_volume_level)
138    }
139
140    /// Retrieve the change source of the specified `stream_type` from the given `changed_streams`.
141    fn get_change_source(
142        &self,
143        changed_streams: Vec<AudioStream>,
144        stream_type: AudioStreamType,
145    ) -> Option<AudioSettingSource> {
146        changed_streams.iter().find(|&&x| x.stream_type == stream_type).map(|x| x.source)
147    }
148
149    /// Helper for on_audio_info. Handles the changes for a specific AudioStreamType.
150    /// Enables separate handling of earcons on different streams.
151    async fn on_audio_info_for_stream(
152        &mut self,
153        new_user_volume: f32,
154        stream_type: AudioStreamType,
155        change_source: Option<AudioSettingSource>,
156    ) {
157        let volume_is_max = new_user_volume == MAX_VOLUME;
158        let last_user_volume = self.last_user_volumes.get(&stream_type);
159
160        // Logging for debugging volume changes.
161        log::debug!(
162            "[earcons_agent] New {:?} user volume: {:?}, Last {:?} user volume: {:?}",
163            stream_type,
164            new_user_volume,
165            stream_type,
166            last_user_volume,
167        );
168
169        if last_user_volume != Some(&new_user_volume) || volume_is_max {
170            // On restore, the last media user volume is set for the first time, and registers
171            // as different from the last seen volume, because it is initially None. Don't play
172            // the earcons sound on that set.
173            //
174            // If the change_source is System, do not play a sound since the user doesn't need
175            // to hear feedback for such changes. For system sounds that need to play earcons,
176            // the source should be AudioSettingSource::SystemWithFeedback. An example
177            // of a system change is when the volume is reset after night mode deactivates.
178            if last_user_volume.is_some() && change_source != Some(AudioSettingSource::System) {
179                let id = ftrace::Id::new();
180                trace!(id, c"volume_change_handler set background");
181                let mut receptor = self.messenger.message(
182                    Payload::Request(Request::SetVolume(
183                        vec![SetAudioStream {
184                            stream_type: AudioStreamType::Background,
185                            source: AudioSettingSource::System,
186                            user_volume_level: Some(new_user_volume),
187                            user_volume_muted: None,
188                        }],
189                        id,
190                    ))
191                    .into(),
192                    Audience::Address(service::Address::Handler(SettingType::Audio)),
193                );
194                if let Err(e) = receptor.next_payload().await {
195                    log::error!("Failed to play sound after waiting for message response: {e:?}");
196                } else {
197                    self.play_volume_sound(new_user_volume);
198                }
199            }
200
201            let _ = self.last_user_volumes.insert(stream_type, new_user_volume);
202        }
203    }
204
205    /// Invoked when a new `AudioInfo` is retrieved. Determines whether an
206    /// earcon should be played and plays sound if necessary.
207    async fn on_audio_info(&mut self, audio_info: AudioInfo) {
208        let changed_streams = match audio_info.modified_counters {
209            None => Vec::new(),
210            Some(counters) => self.calculate_changed_streams(audio_info.streams, counters),
211        };
212
213        let media_user_volume =
214            self.get_user_volume(changed_streams.clone(), AudioStreamType::Media);
215        let interruption_user_volume =
216            self.get_user_volume(changed_streams.clone(), AudioStreamType::Interruption);
217        let media_change_source =
218            self.get_change_source(changed_streams.clone(), AudioStreamType::Media);
219
220        if let Some(media_user_volume) = media_user_volume {
221            self.on_audio_info_for_stream(
222                media_user_volume,
223                AudioStreamType::Media,
224                media_change_source,
225            )
226            .await;
227        }
228        if let Some(interruption_user_volume) = interruption_user_volume {
229            self.on_audio_info_for_stream(
230                interruption_user_volume,
231                AudioStreamType::Interruption,
232                None,
233            )
234            .await;
235        }
236    }
237
238    /// Play the earcons sound given the changed volume streams.
239    fn play_volume_sound(&self, volume: f32) {
240        let common_earcons_params = self.common_earcons_params.clone();
241
242        let publisher = self.publisher.clone();
243        fasync::Task::local(async move {
244            // Connect to the SoundPlayer if not already connected.
245            connect_to_sound_player(
246                publisher,
247                common_earcons_params.service_context.clone(),
248                common_earcons_params.sound_player_connection.clone(),
249            )
250            .await;
251
252            let sound_player_connection_clone =
253                common_earcons_params.sound_player_connection.clone();
254            let sound_player_connection = sound_player_connection_clone.lock().await;
255            let sound_player_added_files = common_earcons_params.sound_player_added_files;
256
257            if let (Some(sound_player_proxy), volume_level) =
258                (sound_player_connection.as_ref(), volume)
259            {
260                let play_sound_result = if volume_level >= 1.0 {
261                    play_sound(
262                        sound_player_proxy,
263                        VOLUME_MAX_FILE_PATH,
264                        VOLUME_MAX_SOUND_ID,
265                        sound_player_added_files.clone(),
266                    )
267                    .await
268                } else if volume_level > 0.0 {
269                    play_sound(
270                        sound_player_proxy,
271                        VOLUME_CHANGED_FILE_PATH,
272                        VOLUME_CHANGED_SOUND_ID,
273                        sound_player_added_files.clone(),
274                    )
275                    .await
276                } else {
277                    Ok(())
278                };
279                if let Err(e) = play_sound_result {
280                    log::warn!("Failed to play sound: {:?}", e);
281                }
282            }
283        })
284        .detach();
285    }
286}
287
288#[cfg(test)]
289mod tests {
290    use fuchsia_inspect::component;
291    use futures::lock::Mutex;
292    use std::rc::Rc;
293
294    use crate::audio::build_audio_default_settings;
295    use crate::inspect::config_logger::InspectConfigLogger;
296    use crate::message::base::MessengerType;
297    use crate::service_context::ServiceContext;
298
299    use super::*;
300
301    fn fake_values() -> (
302        [AudioStream; AUDIO_STREAM_TYPE_COUNT], // fake_streams
303        ModifiedCounters,                       // old_counters
304        ModifiedCounters,                       // new_counters
305        Vec<AudioStream>,                       // expected_changed_streams
306    ) {
307        let config_logger =
308            Rc::new(std::sync::Mutex::new(InspectConfigLogger::new(component::inspector().root())));
309        let mut settings = build_audio_default_settings(config_logger);
310        let settings = settings
311            .load_default_value()
312            .expect("config data should exist and be parseable for tests")
313            .unwrap();
314        let fake_streams = settings.streams;
315        let old_timestamps = create_default_modified_counters();
316        let new_timestamps = [
317            (AudioStreamType::Background, 0),
318            (AudioStreamType::Media, 1),
319            (AudioStreamType::Interruption, 0),
320            (AudioStreamType::SystemAgent, 2),
321            (AudioStreamType::Communication, 3),
322            (AudioStreamType::Accessibility, 4),
323        ]
324        .iter()
325        .cloned()
326        .collect();
327        let expected_changed_streams =
328            [fake_streams[1], fake_streams[3], fake_streams[4], fake_streams[5]].to_vec();
329        (fake_streams, old_timestamps, new_timestamps, expected_changed_streams)
330    }
331
332    #[fuchsia::test(allow_stalls = false)]
333    async fn test_changed_streams() {
334        let (fake_streams, old_timestamps, new_timestamps, expected_changed_streams) =
335            fake_values();
336        let delegate = service::MessageHub::create_hub();
337        let (messenger, _) = delegate.create(MessengerType::Unbound).await.expect("messenger");
338        let publisher = event::Publisher::create(&delegate, MessengerType::Unbound).await;
339        let last_user_volumes: HashMap<_, _> =
340            [(AudioStreamType::Media, 1.0), (AudioStreamType::Interruption, 0.5)].into();
341
342        let mut handler = VolumeChangeHandler {
343            common_earcons_params: CommonEarconsParams {
344                service_context: Rc::new(ServiceContext::new(None, None)),
345                sound_player_added_files: Rc::new(Mutex::new(HashSet::new())),
346                sound_player_connection: Rc::new(Mutex::new(None)),
347            },
348            last_user_volumes,
349            modified_counters: old_timestamps,
350            publisher,
351            messenger,
352        };
353        let changed_streams = handler.calculate_changed_streams(fake_streams, new_timestamps);
354        assert_eq!(changed_streams, expected_changed_streams);
355    }
356}