settings/audio/
stream_volume_control.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::rc::Rc;
6
7use crate::audio::types::{AudioError, AudioStream};
8use crate::audio::utils::round_volume_level;
9#[cfg(test)]
10use crate::clock;
11use crate::{trace, trace_guard};
12use fidl::endpoints::create_proxy;
13use fidl_fuchsia_media::Usage2;
14use fidl_fuchsia_media_audio::VolumeControlProxy;
15use futures::TryStreamExt;
16#[cfg(test)]
17use futures::channel::mpsc::UnboundedSender;
18use futures::channel::oneshot::Sender;
19use settings_common::call;
20use settings_common::inspect::event::ExternalEventPublisher;
21#[cfg(test)]
22use settings_common::service_context::ExternalServiceEvent;
23use settings_common::service_context::ExternalServiceProxy;
24use {fuchsia_async as fasync, fuchsia_trace as ftrace};
25
26#[cfg(test)]
27const PUBLISHER_EVENT_NAME: &str = "volume_control_events";
28const CONTROLLER_ERROR_DEPENDENCY: &str = "fuchsia.media.audio";
29#[cfg(test)]
30const UNKNOWN_INSPECT_STRING: &str = "unknown";
31
32/// Closure definition for an action that can be triggered by ActionFuse.
33pub(crate) type ExitAction = Rc<dyn Fn()>;
34
35// Stores an AudioStream and a VolumeControl proxy bound to the AudioCore
36// service for |stored_stream|'s stream type. |proxy| is set to None if it
37// fails to bind to the AudioCore service. |early_exit_action| specifies a
38// closure to be run if the StreamVolumeControl exits prematurely.
39pub struct StreamVolumeControl {
40    pub stored_stream: AudioStream,
41    proxy: Option<VolumeControlProxy>,
42    audio_service: ExternalServiceProxy<fidl_fuchsia_media::AudioCoreProxy, ExternalEventPublisher>,
43    early_exit_action: Option<ExitAction>,
44    #[cfg(test)]
45    publisher: Option<UnboundedSender<ExternalServiceEvent>>,
46    listen_exit_tx: Option<Sender<()>>,
47}
48
49impl Drop for StreamVolumeControl {
50    fn drop(&mut self) {
51        if let Some(exit_tx) = self.listen_exit_tx.take() {
52            // Do not signal exit if receiver is already closed.
53            if exit_tx.is_canceled() {
54                return;
55            }
56
57            // Consider panic! is likely to be abort in the drop method, only log info for
58            // unbounded_send failure.
59            exit_tx.send(()).unwrap_or_else(|_| {
60                log::warn!("StreamVolumeControl::drop, exit_tx failed to send exit signal")
61            });
62        }
63    }
64}
65
66impl StreamVolumeControl {
67    pub(crate) async fn create(
68        id: ftrace::Id,
69        audio_service: ExternalServiceProxy<
70            fidl_fuchsia_media::AudioCoreProxy,
71            ExternalEventPublisher,
72        >,
73        stream: AudioStream,
74        early_exit_action: Option<ExitAction>,
75        #[cfg(test)] publisher: Option<UnboundedSender<ExternalServiceEvent>>,
76    ) -> Result<Self, AudioError> {
77        // Stream input should be valid. Input comes from restore should be valid
78        // and from set request has the validation.
79        assert!(stream.has_valid_volume_level());
80
81        trace!(id, c"StreamVolumeControl ctor");
82        let mut control = StreamVolumeControl {
83            stored_stream: stream,
84            proxy: None,
85            audio_service: audio_service,
86            listen_exit_tx: None,
87            early_exit_action,
88            #[cfg(test)]
89            publisher,
90        };
91
92        control.bind_volume_control(id).await?;
93        Ok(control)
94    }
95
96    pub(crate) async fn set_volume(
97        &mut self,
98        id: ftrace::Id,
99        stream: AudioStream,
100    ) -> Result<(), AudioError> {
101        assert_eq!(self.stored_stream.stream_type, stream.stream_type);
102        // Stream input should be valid. Input comes from restore should be valid
103        // and from set request has the validation.
104        assert!(stream.has_valid_volume_level());
105
106        // Try to create and bind a new VolumeControl.
107        if self.proxy.is_none() {
108            self.bind_volume_control(id).await?;
109        }
110
111        // Round volume level from user input.
112        let mut new_stream_value = stream;
113        new_stream_value.user_volume_level = round_volume_level(stream.user_volume_level);
114
115        let proxy = self.proxy.as_ref().expect("no volume control proxy");
116
117        if (self.stored_stream.user_volume_level - new_stream_value.user_volume_level).abs()
118            > f32::EPSILON
119        {
120            if let Err(e) = proxy.set_volume(new_stream_value.user_volume_level) {
121                self.stored_stream = new_stream_value;
122                return Err(AudioError::ExternalFailure(
123                    CONTROLLER_ERROR_DEPENDENCY,
124                    "set volume".into(),
125                    format!("{e:?}"),
126                ));
127            }
128        }
129
130        if self.stored_stream.user_volume_muted != new_stream_value.user_volume_muted {
131            if let Err(e) = proxy.set_mute(stream.user_volume_muted) {
132                self.stored_stream = new_stream_value;
133                return Err(AudioError::ExternalFailure(
134                    CONTROLLER_ERROR_DEPENDENCY,
135                    "set mute".into(),
136                    format!("{e:?}"),
137                ));
138            }
139        }
140
141        self.stored_stream = new_stream_value;
142        Ok(())
143    }
144
145    async fn bind_volume_control(&mut self, id: ftrace::Id) -> Result<(), AudioError> {
146        trace!(id, c"bind volume control");
147        if self.proxy.is_some() {
148            return Ok(());
149        }
150
151        let (vol_control_proxy, server_end) = create_proxy();
152        let stream_type = self.stored_stream.stream_type;
153        let usage = Usage2::RenderUsage(stream_type.into());
154
155        let guard = trace_guard!(id, c"bind usage volume control");
156        if let Err(e) = call!(self.audio_service => bind_usage_volume_control2(&usage, server_end))
157        {
158            return Err(AudioError::ExternalFailure(
159                CONTROLLER_ERROR_DEPENDENCY,
160                format!("bind_usage_volume_control2 for audio_core {usage:?}").into(),
161                format!("{e:?}"),
162            ));
163        }
164        drop(guard);
165
166        let guard = trace_guard!(id, c"set values");
167        // Once the volume control is bound, apply the persisted audio settings to it.
168        if let Err(e) = vol_control_proxy.set_volume(self.stored_stream.user_volume_level) {
169            return Err(AudioError::ExternalFailure(
170                CONTROLLER_ERROR_DEPENDENCY,
171                format!("set_volume for vol_control {stream_type:?}").into(),
172                format!("{e:?}"),
173            ));
174        }
175
176        if let Err(e) = vol_control_proxy.set_mute(self.stored_stream.user_volume_muted) {
177            return Err(AudioError::ExternalFailure(
178                CONTROLLER_ERROR_DEPENDENCY,
179                "set_mute for vol_control".into(),
180                format!("{e:?}"),
181            ));
182        }
183        drop(guard);
184
185        if let Some(exit_tx) = self.listen_exit_tx.take() {
186            // exit_rx needs this signal to end leftover spawn.
187            exit_tx.send(()).expect(
188                "StreamVolumeControl::bind_volume_control, listen_exit_tx failed to send exit \
189                signal",
190            );
191        }
192
193        trace!(id, c"setup listener");
194
195        let (exit_tx, mut exit_rx) = futures::channel::oneshot::channel::<()>();
196        let mut volume_events = vol_control_proxy.take_event_stream();
197        let early_exit_action = self.early_exit_action.clone();
198        fasync::Task::local({
199            #[cfg(test)]
200            let publisher = self.publisher.clone();
201            async move {
202                let id = ftrace::Id::new();
203                trace!(id, c"bind volume handler");
204                loop {
205                    futures::select! {
206                        _ = exit_rx => {
207                            trace!(id, c"exit");
208                            #[cfg(test)]
209                            {
210                                // Send UNKNOWN_INSPECT_STRING for request-related args because it
211                                // can't be tied back to the event that caused the proxy to close.
212                                if let Some(publisher) = publisher {
213                                    let _ = publisher.unbounded_send(
214                                        ExternalServiceEvent::Closed(
215                                            PUBLISHER_EVENT_NAME,
216                                            UNKNOWN_INSPECT_STRING.into(),
217                                            UNKNOWN_INSPECT_STRING.into(),
218                                            clock::inspect_format_now().into(),
219                                        )
220                                    );
221                                }
222                            }
223                            return;
224                        }
225                        volume_event = volume_events.try_next() => {
226                            trace!(id, c"volume_event");
227                            if let Err(_) | Ok(None) = volume_event {
228                                if let Some(action) = early_exit_action {
229                                    (action)();
230                                }
231                                return;
232                            }
233                        }
234                    }
235                }
236            }
237        })
238        .detach();
239
240        self.listen_exit_tx = Some(exit_tx);
241        self.proxy = Some(vol_control_proxy);
242        Ok(())
243    }
244}