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