settings/audio/
audio_fidl_handler.rs

1// Copyright 2019 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::hash_map::DefaultHasher;
6use std::hash::{Hash, Hasher};
7
8use fidl::prelude::*;
9use fidl_fuchsia_media::{AudioRenderUsage, AudioRenderUsage2};
10use fidl_fuchsia_settings::{
11    AudioMarker, AudioRequest, AudioSet2Responder, AudioSet2Result, AudioSetResponder,
12    AudioSetResult, AudioSettings, AudioSettings2, AudioStreamSettingSource, AudioStreamSettings,
13    AudioStreamSettings2, AudioWatch2Responder, AudioWatchResponder, Volume,
14};
15use fuchsia_trace as ftrace;
16
17use crate::audio::types::{AudioSettingSource, AudioStream, AudioStreamType, SetAudioStream};
18use crate::base::{SettingInfo, SettingType};
19use crate::handler::base::Request;
20use crate::ingress::{request, watch, Scoped};
21use crate::job::source::{Error as JobError, ErrorResponder};
22use crate::job::Job;
23use crate::{trace, trace_guard};
24
25/// Custom responder that wraps the real FIDL responder plus a tracing guard. The guard is stored
26/// here so that it's active until a response is sent and this responder is dropped.
27struct AudioSetTraceResponder {
28    responder: AudioSetResponder,
29    _guard: Option<ftrace::AsyncScope>,
30}
31impl request::Responder<Scoped<AudioSetResult>> for AudioSetTraceResponder {
32    fn respond(self, Scoped(response): Scoped<AudioSetResult>) {
33        let _ = self.responder.send(response);
34    }
35}
36impl ErrorResponder for AudioSetTraceResponder {
37    fn id(&self) -> &'static str {
38        "Audio_Set"
39    }
40
41    fn respond(self: Box<Self>, error: fidl_fuchsia_settings::Error) -> Result<(), fidl::Error> {
42        self.responder.send(Err(error))
43    }
44}
45impl request::Responder<Scoped<AudioSetResult>> for AudioSetResponder {
46    fn respond(self, Scoped(response): Scoped<AudioSetResult>) {
47        let _ = self.send(response);
48    }
49}
50
51impl watch::Responder<AudioSettings, zx::Status> for AudioWatchResponder {
52    fn respond(self, response: Result<AudioSettings, zx::Status>) {
53        match response {
54            Ok(settings) => {
55                let _ = self.send(&settings);
56            }
57            Err(error) => {
58                self.control_handle().shutdown_with_epitaph(error);
59            }
60        }
61    }
62}
63
64// Set2 and Watch2 implementations are derived directly from the corresponding Set and Watch
65// counterparts, using AudioSettings2 instead of AudioSettings.
66struct AudioSet2TraceResponder {
67    responder: AudioSet2Responder,
68    _guard: Option<ftrace::AsyncScope>,
69}
70impl request::Responder<Scoped<AudioSet2Result>> for AudioSet2TraceResponder {
71    fn respond(self, Scoped(response): Scoped<AudioSet2Result>) {
72        let _ = self.responder.send(response);
73    }
74}
75impl ErrorResponder for AudioSet2TraceResponder {
76    fn id(&self) -> &'static str {
77        "Audio_Set2"
78    }
79
80    fn respond(self: Box<Self>, error: fidl_fuchsia_settings::Error) -> Result<(), fidl::Error> {
81        self.responder.send(Err(error))
82    }
83}
84impl request::Responder<Scoped<AudioSet2Result>> for AudioSet2Responder {
85    fn respond(self, Scoped(response): Scoped<AudioSet2Result>) {
86        let _ = self.send(response);
87    }
88}
89
90impl watch::Responder<AudioSettings2, zx::Status> for AudioWatch2Responder {
91    fn respond(self, response: Result<AudioSettings2, zx::Status>) {
92        match response {
93            Ok(settings) => {
94                let _ = self.send(&settings);
95            }
96            Err(error) => {
97                self.control_handle().shutdown_with_epitaph(error);
98            }
99        }
100    }
101}
102
103impl TryFrom<AudioRequest> for Job {
104    type Error = JobError;
105
106    fn try_from(item: AudioRequest) -> Result<Self, Self::Error> {
107        #[allow(unreachable_patterns)]
108        match item {
109            AudioRequest::Set { settings, responder } => {
110                let id = ftrace::Id::new();
111                let guard = trace_guard!(id, c"audio fidl handler set");
112                let responder = AudioSetTraceResponder { responder, _guard: guard };
113                match to_request(settings, id) {
114                    Ok(request) => {
115                        Ok(request::Work::new(SettingType::Audio, request, responder).into())
116                    }
117                    Err(err) => {
118                        log::error!(
119                            "{}: Failed to process 'set' request: {:?}",
120                            AudioMarker::DEBUG_NAME,
121                            err
122                        );
123                        Err(JobError::InvalidInput(Box::new(responder)))
124                    }
125                }
126            }
127            AudioRequest::Set2 { settings, responder } => {
128                let id = ftrace::Id::new();
129                let guard = trace_guard!(id, c"audio fidl handler set2");
130                let responder = AudioSet2TraceResponder { responder, _guard: guard };
131                match to_request2(settings, id) {
132                    Ok(request) => {
133                        Ok(request::Work::new(SettingType::Audio, request, responder).into())
134                    }
135                    Err(err) => {
136                        log::error!(
137                            "{}: Failed2 to process 'set2' request: {:?}",
138                            AudioMarker::DEBUG_NAME,
139                            err
140                        );
141                        Err(JobError::InvalidInput(Box::new(responder)))
142                    }
143                }
144            }
145            AudioRequest::Watch { responder } => {
146                let mut hasher = DefaultHasher::new();
147                "audio_watch".hash(&mut hasher);
148                // Because we increment the modification counters stored in AudioInfo for every
149                // change, clients would be notified for every change, even if the streams are
150                // identical. A custom change function is used here so only stream CHANGES
151                // (and only in "legacy" stream types) trigger the Watch() notification.
152                Ok(watch::Work::new_job_with_change_function(
153                    SettingType::Audio,
154                    responder,
155                    watch::ChangeFunction::new(
156                        hasher.finish(),
157                        Box::new(move |old: &SettingInfo, new: &SettingInfo| match (old, new) {
158                            (SettingInfo::Audio(old_info), SettingInfo::Audio(new_info)) => {
159                                let mut old_streams = old_info.streams.iter();
160                                let new_streams = new_info.streams.iter();
161                                for new_stream in new_streams {
162                                    let old_stream = old_streams
163                                        .find(|stream| stream.stream_type == new_stream.stream_type)
164                                        .expect("stream type should be found in existing streams");
165                                    // Watch() notifies upon changes to "legacy" stream types.
166                                    if (old_stream != new_stream)
167                                        && new_stream.stream_type.is_legacy()
168                                    {
169                                        return true;
170                                    }
171                                }
172                                false
173                            }
174                            _ => false,
175                        }),
176                    ),
177                ))
178            }
179            AudioRequest::Watch2 { responder } => {
180                let mut hasher = DefaultHasher::new();
181                "audio_watch2".hash(&mut hasher);
182                // Because we increment the modification counters stored in AudioInfo for every
183                // change, clients would be notified for every change, even if the streams are
184                // identical. A custom change function is used here so only stream CHANGES
185                // trigger the Watch2() notification.
186                Ok(watch::Work::new_job_with_change_function(
187                    SettingType::Audio,
188                    responder,
189                    watch::ChangeFunction::new(
190                        hasher.finish(),
191                        Box::new(move |old: &SettingInfo, new: &SettingInfo| match (old, new) {
192                            (SettingInfo::Audio(old_info), SettingInfo::Audio(new_info)) => {
193                                let mut old_streams = old_info.streams.iter();
194                                let new_streams = new_info.streams.iter();
195                                for new_stream in new_streams {
196                                    let old_stream = old_streams
197                                        .find(|stream| stream.stream_type == new_stream.stream_type)
198                                        .expect("stream type should be found in existing streams");
199                                    // Watch2() notifies upon changes to any stream type.
200                                    if old_stream != new_stream {
201                                        return true;
202                                    }
203                                }
204                                false
205                            }
206                            _ => false,
207                        }),
208                    ),
209                ))
210            }
211            _ => {
212                log::warn!("Received a call to an unsupported API: {:?}", item);
213                Err(JobError::Unsupported)
214            }
215        }
216    }
217}
218
219impl From<SettingInfo> for AudioSettings {
220    fn from(response: SettingInfo) -> Self {
221        if let SettingInfo::Audio(info) = response {
222            let mut streams = Vec::new();
223            for stream in &info.streams {
224                let stream_settings = AudioStreamSettings::try_from(*stream);
225                if stream_settings.is_ok() {
226                    streams.push(stream_settings.unwrap());
227                }
228            }
229
230            AudioSettings { streams: Some(streams), ..Default::default() }
231        } else {
232            panic!("incorrect value sent to audio");
233        }
234    }
235}
236
237impl From<SettingInfo> for AudioSettings2 {
238    fn from(response: SettingInfo) -> Self {
239        if let SettingInfo::Audio(info) = response {
240            let mut streams = Vec::new();
241            for stream in &info.streams {
242                streams.push(AudioStreamSettings2::from(*stream));
243            }
244
245            AudioSettings2 { streams: Some(streams), ..Default::default() }
246        } else {
247            panic!("incorrect value sent to audio");
248        }
249    }
250}
251
252impl TryFrom<AudioStream> for AudioStreamSettings {
253    type Error = ();
254
255    fn try_from(stream: AudioStream) -> Result<Self, ()> {
256        match AudioRenderUsage::try_from(stream.stream_type) {
257            Err(_) => Err(()),
258            Ok(stream_type) => Ok(AudioStreamSettings {
259                stream: Some(stream_type),
260                source: Some(AudioStreamSettingSource::from(stream.source)),
261                user_volume: Some(Volume {
262                    level: Some(stream.user_volume_level),
263                    muted: Some(stream.user_volume_muted),
264                    ..Default::default()
265                }),
266                ..Default::default()
267            }),
268        }
269    }
270}
271
272impl From<AudioStream> for AudioStreamSettings2 {
273    fn from(stream: AudioStream) -> Self {
274        AudioStreamSettings2 {
275            stream: Some(AudioRenderUsage2::from(stream.stream_type)),
276            source: Some(AudioStreamSettingSource::from(stream.source)),
277            user_volume: Some(Volume {
278                level: Some(stream.user_volume_level),
279                muted: Some(stream.user_volume_muted),
280                ..Default::default()
281            }),
282            ..Default::default()
283        }
284    }
285}
286
287impl From<AudioRenderUsage> for AudioStreamType {
288    fn from(usage: AudioRenderUsage) -> Self {
289        match usage {
290            AudioRenderUsage::Background => AudioStreamType::Background,
291            AudioRenderUsage::Communication => AudioStreamType::Communication,
292            AudioRenderUsage::Interruption => AudioStreamType::Interruption,
293            AudioRenderUsage::Media => AudioStreamType::Media,
294            AudioRenderUsage::SystemAgent => AudioStreamType::SystemAgent,
295        }
296    }
297}
298
299impl TryFrom<AudioStreamType> for AudioRenderUsage {
300    type Error = ();
301    fn try_from(usage: AudioStreamType) -> Result<Self, Self::Error> {
302        match usage {
303            AudioStreamType::Accessibility => Err(()),
304            AudioStreamType::Background => Ok(AudioRenderUsage::Background),
305            AudioStreamType::Communication => Ok(AudioRenderUsage::Communication),
306            AudioStreamType::Interruption => Ok(AudioRenderUsage::Interruption),
307            AudioStreamType::Media => Ok(AudioRenderUsage::Media),
308            AudioStreamType::SystemAgent => Ok(AudioRenderUsage::SystemAgent),
309        }
310    }
311}
312
313impl From<AudioStreamType> for AudioRenderUsage2 {
314    fn from(usage: AudioStreamType) -> Self {
315        match usage {
316            AudioStreamType::Accessibility => AudioRenderUsage2::Accessibility,
317            AudioStreamType::Background => AudioRenderUsage2::Background,
318            AudioStreamType::Communication => AudioRenderUsage2::Communication,
319            AudioStreamType::Interruption => AudioRenderUsage2::Interruption,
320            AudioStreamType::Media => AudioRenderUsage2::Media,
321            AudioStreamType::SystemAgent => AudioRenderUsage2::SystemAgent,
322        }
323    }
324}
325
326impl TryFrom<AudioRenderUsage2> for AudioStreamType {
327    type Error = ();
328    fn try_from(usage: AudioRenderUsage2) -> Result<Self, Self::Error> {
329        match usage {
330            AudioRenderUsage2::Accessibility => Ok(AudioStreamType::Accessibility),
331            AudioRenderUsage2::Background => Ok(AudioStreamType::Background),
332            AudioRenderUsage2::Communication => Ok(AudioStreamType::Communication),
333            AudioRenderUsage2::Interruption => Ok(AudioStreamType::Interruption),
334            AudioRenderUsage2::Media => Ok(AudioStreamType::Media),
335            AudioRenderUsage2::SystemAgent => Ok(AudioStreamType::SystemAgent),
336            _ => Err(()),
337        }
338    }
339}
340
341impl From<AudioStreamSettingSource> for AudioSettingSource {
342    fn from(source: AudioStreamSettingSource) -> Self {
343        match source {
344            AudioStreamSettingSource::User => AudioSettingSource::User,
345            AudioStreamSettingSource::System => AudioSettingSource::System,
346            AudioStreamSettingSource::SystemWithFeedback => AudioSettingSource::SystemWithFeedback,
347        }
348    }
349}
350
351impl From<AudioSettingSource> for AudioStreamSettingSource {
352    fn from(source: AudioSettingSource) -> Self {
353        match source {
354            AudioSettingSource::User => AudioStreamSettingSource::User,
355            AudioSettingSource::System => AudioStreamSettingSource::System,
356            AudioSettingSource::SystemWithFeedback => AudioStreamSettingSource::SystemWithFeedback,
357        }
358    }
359}
360
361// Clippy warns about all variants starting with the same prefix `No`.
362#[allow(clippy::enum_variant_names)]
363#[derive(thiserror::Error, Debug, PartialEq)]
364enum Error {
365    #[error("request has no streams")]
366    NoStreams,
367    #[error("missing user_volume at stream {0}")]
368    NoUserVolume(usize),
369    #[error("missing user_volume.level and user_volume.muted at stream {0}")]
370    MissingVolumeAndMuted(usize),
371    #[error("missing stream at stream {0}")]
372    NoStreamType(usize),
373    #[error("missing source at stream {0}")]
374    NoSource(usize),
375    #[error("request has an unknown stream type")]
376    UnrecognizedStreamType,
377}
378
379fn to_request(settings: AudioSettings, id: ftrace::Id) -> Result<Request, Error> {
380    trace!(id, c"to_request");
381    settings
382        .streams
383        .map(|streams| {
384            streams
385                .into_iter()
386                .enumerate()
387                .map(|(i, stream)| {
388                    let user_volume = stream.user_volume.ok_or(Error::NoUserVolume(i))?;
389                    let user_volume_level = user_volume.level;
390                    let user_volume_muted = user_volume.muted;
391                    let stream_type = stream.stream.ok_or(Error::NoStreamType(i))?.into();
392                    let source = stream.source.ok_or(Error::NoSource(i))?.into();
393                    let request = SetAudioStream {
394                        stream_type,
395                        source,
396                        user_volume_level,
397                        user_volume_muted,
398                    };
399                    if request.is_valid_payload() {
400                        Ok(request)
401                    } else {
402                        Err(Error::MissingVolumeAndMuted(i))
403                    }
404                })
405                .collect::<Result<Vec<_>, _>>()
406                .map(|streams| Request::SetVolume(streams, id))
407        })
408        .unwrap_or(Err(Error::NoStreams))
409}
410
411fn to_request2(settings: AudioSettings2, id: ftrace::Id) -> Result<Request, Error> {
412    trace!(id, c"to_request2");
413    settings
414        .streams
415        .map(|streams| {
416            streams
417                .into_iter()
418                .enumerate()
419                .map(|(i, stream)| {
420                    let user_volume = stream.user_volume.ok_or(Error::NoUserVolume(i))?;
421                    let user_volume_level = user_volume.level;
422                    let user_volume_muted = user_volume.muted;
423                    let stream_type = match stream.stream.ok_or(Error::NoStreamType(i))?.try_into()
424                    {
425                        Ok(stream_type) => Ok(stream_type),
426                        Err(_) => Err(Error::UnrecognizedStreamType),
427                    }?;
428                    let source = stream.source.ok_or(Error::NoSource(i))?.into();
429                    let request = SetAudioStream {
430                        stream_type,
431                        source,
432                        user_volume_level,
433                        user_volume_muted,
434                    };
435                    if request.is_valid_payload() {
436                        Ok(request)
437                    } else {
438                        Err(Error::MissingVolumeAndMuted(i))
439                    }
440                })
441                .collect::<Result<Vec<_>, _>>()
442                .map(|streams| Request::SetVolume(streams, id))
443        })
444        .unwrap_or(Err(Error::NoStreams))
445}
446
447#[cfg(test)]
448mod tests {
449    use super::*;
450
451    fn test_stream() -> AudioStreamSettings {
452        AudioStreamSettings {
453            stream: Some(fidl_fuchsia_media::AudioRenderUsage::Media),
454            source: Some(AudioStreamSettingSource::User),
455            user_volume: Some(Volume {
456                level: Some(0.6),
457                muted: Some(false),
458                ..Default::default()
459            }),
460            ..Default::default()
461        }
462    }
463
464    fn test_stream2() -> AudioStreamSettings2 {
465        AudioStreamSettings2 {
466            stream: Some(fidl_fuchsia_media::AudioRenderUsage2::Media),
467            source: Some(AudioStreamSettingSource::User),
468            user_volume: Some(Volume {
469                level: Some(0.6),
470                muted: Some(false),
471                ..Default::default()
472            }),
473            ..Default::default()
474        }
475    }
476
477    // Verifies that an entirely empty settings request results in an appropriate error.
478    #[fuchsia::test]
479    fn test_request_from_settings_empty() {
480        let id = ftrace::Id::new();
481        let request = to_request(AudioSettings::default(), id);
482
483        assert_eq!(request, Err(Error::NoStreams));
484    }
485
486    // Verifies that an entirely empty settings request2 results in an appropriate error.
487    #[fuchsia::test]
488    fn test_request2_from_settings_empty() {
489        let id = ftrace::Id::new();
490        let request = to_request2(AudioSettings2::default(), id);
491
492        assert_eq!(request, Err(Error::NoStreams));
493    }
494
495    // Verifies that a settings request missing user volume info results in an appropriate error.
496    #[fuchsia::test]
497    fn test_request_missing_user_volume() {
498        let mut stream = test_stream();
499        stream.user_volume = None;
500
501        let audio_settings = AudioSettings { streams: Some(vec![stream]), ..Default::default() };
502
503        let id = ftrace::Id::new();
504        let request = to_request(audio_settings, id);
505
506        assert_eq!(request, Err(Error::NoUserVolume(0)));
507    }
508
509    // Verifies that a settings request2 missing user volume info results in an appropriate error.
510    #[fuchsia::test]
511    fn test_request2_missing_user_volume() {
512        let mut stream = test_stream2();
513        stream.user_volume = None;
514
515        let audio_settings = AudioSettings2 { streams: Some(vec![stream]), ..Default::default() };
516
517        let id = ftrace::Id::new();
518        let request = to_request2(audio_settings, id);
519
520        assert_eq!(request, Err(Error::NoUserVolume(0)));
521    }
522
523    // Verifies that a settings request missing the stream type results in an appropriate error.
524    #[fuchsia::test]
525    fn test_request_missing_stream_type() {
526        let mut stream = test_stream();
527        stream.stream = None;
528
529        let audio_settings = AudioSettings { streams: Some(vec![stream]), ..Default::default() };
530
531        let id = ftrace::Id::new();
532        let request = to_request(audio_settings, id);
533
534        assert_eq!(request, Err(Error::NoStreamType(0)));
535    }
536
537    // Verifies that a settings request2 missing the stream type results in an appropriate error.
538    #[fuchsia::test]
539    fn test_request2_missing_stream_type() {
540        let mut stream = test_stream2();
541        stream.stream = None;
542
543        let audio_settings = AudioSettings2 { streams: Some(vec![stream]), ..Default::default() };
544
545        let id = ftrace::Id::new();
546        let request = to_request2(audio_settings, id);
547
548        assert_eq!(request, Err(Error::NoStreamType(0)));
549    }
550
551    // Verifies that a settings request missing the source results in an appropriate error.
552    #[fuchsia::test]
553    fn test_request_missing_source() {
554        let mut stream = test_stream();
555        stream.source = None;
556
557        let audio_settings = AudioSettings { streams: Some(vec![stream]), ..Default::default() };
558
559        let id = ftrace::Id::new();
560        let request = to_request(audio_settings, id);
561
562        assert_eq!(request, Err(Error::NoSource(0)));
563    }
564
565    // Verifies that a settings request2 missing the source results in an appropriate error.
566    #[fuchsia::test]
567    fn test_request2_missing_source() {
568        let mut stream = test_stream2();
569        stream.source = None;
570
571        let audio_settings = AudioSettings2 { streams: Some(vec![stream]), ..Default::default() };
572
573        let id = ftrace::Id::new();
574        let request = to_request2(audio_settings, id);
575
576        assert_eq!(request, Err(Error::NoSource(0)));
577    }
578
579    // Verifies that a settings request missing both the user volume level and mute state results in
580    // an appropriate error.
581    #[fuchsia::test]
582    fn test_request_missing_user_volume_level_and_muted() {
583        let mut stream = test_stream();
584        stream.user_volume = Some(Volume { level: None, muted: None, ..Default::default() });
585
586        let audio_settings = AudioSettings { streams: Some(vec![stream]), ..Default::default() };
587
588        let id = ftrace::Id::new();
589        let request = to_request(audio_settings, id);
590
591        assert_eq!(request, Err(Error::MissingVolumeAndMuted(0)));
592    }
593
594    // Verifies that a settings request2 missing both the user volume level and mute state results in
595    // an appropriate error.
596    #[fuchsia::test]
597    fn test_request2_missing_user_volume_level_and_muted() {
598        let mut stream = test_stream2();
599        stream.user_volume = Some(Volume { level: None, muted: None, ..Default::default() });
600
601        let audio_settings = AudioSettings2 { streams: Some(vec![stream]), ..Default::default() };
602
603        let id = ftrace::Id::new();
604        let request = to_request2(audio_settings, id);
605
606        assert_eq!(request, Err(Error::MissingVolumeAndMuted(0)));
607    }
608}