settings/audio/
audio_controller.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use super::AudioInfoLoader;
use crate::audio::types::{AudioInfo, AudioStream, AudioStreamType, SetAudioStream};
use crate::audio::{create_default_modified_counters, ModifiedCounters, StreamVolumeControl};
use crate::base::SettingType;
use crate::handler::base::Request;
use crate::handler::setting_handler::persist::{
    controller as data_controller, ClientProxy, WriteResult,
};
use crate::handler::setting_handler::{
    controller, ControllerError, ControllerStateResult, Event, SettingHandlerResult, State,
};
use crate::{trace, trace_guard};
use async_trait::async_trait;
use futures::lock::Mutex;
use settings_storage::device_storage::{DeviceStorage, DeviceStorageCompatible};
use settings_storage::storage_factory::{DefaultLoader, StorageAccess};
use std::collections::HashMap;
use std::rc::Rc;
use {fuchsia_async as fasync, fuchsia_trace as ftrace};

type VolumeControllerHandle = Rc<Mutex<VolumeController>>;

pub(crate) struct VolumeController {
    client: ClientProxy,
    audio_service_connected: bool,
    stream_volume_controls: HashMap<AudioStreamType, StreamVolumeControl>,
    modified_counters: ModifiedCounters,
    audio_info_loader: AudioInfoLoader,
}

enum UpdateFrom {
    AudioInfo(AudioInfo),
    NewStreams(Vec<SetAudioStream>),
}

impl VolumeController {
    fn create_with(
        client: ClientProxy,
        audio_info_loader: AudioInfoLoader,
    ) -> VolumeControllerHandle {
        Rc::new(Mutex::new(Self {
            client,
            stream_volume_controls: HashMap::new(),
            audio_service_connected: false,
            modified_counters: create_default_modified_counters(),
            audio_info_loader,
        }))
    }

    /// Restores the necessary dependencies' state on boot.
    async fn restore(&mut self, id: ftrace::Id) -> ControllerStateResult {
        self.restore_volume_state(true, id).await
    }

    /// Extracts the audio state from persistent storage and restores it on
    /// the local state. Also pushes the changes to the audio core if
    /// `push_to_audio_core` is true.
    async fn restore_volume_state(
        &mut self,
        push_to_audio_core: bool,
        id: ftrace::Id,
    ) -> ControllerStateResult {
        let audio_info = self.client.read_setting::<AudioInfo>(id).await;
        // Ignore the notification triggered result.
        let _ = self
            .update_volume_streams(UpdateFrom::AudioInfo(audio_info), push_to_audio_core, id)
            .await?;
        Ok(())
    }

    async fn get_info(&self, id: ftrace::Id) -> Result<AudioInfo, ControllerError> {
        let mut audio_info = self.client.read_setting::<AudioInfo>(id).await;

        audio_info.modified_counters = Some(self.modified_counters.clone());
        Ok(audio_info)
    }

    async fn set_volume(
        &mut self,
        volume: Vec<SetAudioStream>,
        id: ftrace::Id,
    ) -> SettingHandlerResult {
        let guard = trace_guard!(id, c"set volume updating counters");
        // Update counters for changed streams.
        for stream in &volume {
            // We don't care what the value of the counter is, just that it is different from the
            // previous value. We use wrapping_add to avoid eventual overflow of the counter.
            let _ = self.modified_counters.insert(
                stream.stream_type,
                self.modified_counters
                    .get(&stream.stream_type)
                    .map_or(0, |flag| flag.wrapping_add(1)),
            );
        }
        drop(guard);

        if !(self.update_volume_streams(UpdateFrom::NewStreams(volume), true, id).await?) {
            trace!(id, c"set volume notifying");
            let info = self.get_info(id).await?.into();
            self.client.notify(Event::Changed(info)).await;
        }

        Ok(None)
    }

    async fn get_streams_array_from_map(
        &self,
        stream_map: &HashMap<AudioStreamType, StreamVolumeControl>,
    ) -> [AudioStream; 5] {
        let mut streams: [AudioStream; 5] = self.audio_info_loader.default_value().streams;
        for stream in &mut streams {
            if let Some(volume_control) = stream_map.get(&stream.stream_type) {
                *stream = volume_control.stored_stream;
            }
        }

        streams
    }

    /// Updates the state with the given streams' volume levels.
    ///
    /// If `push_to_audio_core` is true, pushes the changes to the audio core.
    /// If not, just sets it on the local stored state. Should be called with
    /// true on first restore and on volume changes, and false otherwise.
    /// Returns whether the change triggered a notification.
    async fn update_volume_streams(
        &mut self,
        update_from: UpdateFrom,
        push_to_audio_core: bool,
        id: ftrace::Id,
    ) -> Result<bool, ControllerError> {
        let mut new_vec = vec![];
        trace!(id, c"update volume streams");
        let calculating_guard = trace_guard!(id, c"check and bind");
        let (stored_value, new_streams) = match &update_from {
            UpdateFrom::AudioInfo(audio_info) => (None, audio_info.streams.iter()),
            UpdateFrom::NewStreams(streams) => {
                trace!(id, c"reading setting");
                let stored_value = self.client.read_setting::<AudioInfo>(id).await;
                for set_stream in streams.iter() {
                    let stored_stream = stored_value
                        .streams
                        .iter()
                        .find(|stream| stream.stream_type == set_stream.stream_type)
                        .ok_or_else(|| {
                            ControllerError::InvalidArgument(
                                SettingType::Audio,
                                "stream".into(),
                                format!("{set_stream:?}").into(),
                            )
                        })?;
                    new_vec.push(AudioStream {
                        stream_type: stored_stream.stream_type,
                        source: set_stream.source,
                        user_volume_level: set_stream
                            .user_volume_level
                            .unwrap_or(stored_stream.user_volume_level),
                        user_volume_muted: set_stream
                            .user_volume_muted
                            .unwrap_or(stored_stream.user_volume_muted),
                    });
                }
                (Some(stored_value), new_vec.iter())
            }
        };

        if push_to_audio_core {
            let guard = trace_guard!(id, c"push to core");
            self.check_and_bind_volume_controls(
                id,
                self.audio_info_loader.default_value().streams.iter(),
            )
            .await?;
            drop(guard);

            trace!(id, c"setting core");
            for stream in new_streams {
                if let Some(volume_control) =
                    self.stream_volume_controls.get_mut(&stream.stream_type)
                {
                    volume_control.set_volume(id, *stream).await?;
                }
            }
        } else {
            trace!(id, c"without push to core");
            self.check_and_bind_volume_controls(id, new_streams).await?;
        }
        drop(calculating_guard);

        if let Some(mut stored_value) = stored_value {
            let guard = trace_guard!(id, c"updating streams and counters");
            stored_value.streams =
                self.get_streams_array_from_map(&self.stream_volume_controls).await;
            stored_value.modified_counters = Some(self.modified_counters.clone());
            drop(guard);

            let guard = trace_guard!(id, c"writing setting");
            let write_result = self.client.write_setting(stored_value.into(), id).await;
            drop(guard);
            Ok(write_result.notified())
        } else {
            Ok(false)
        }
    }

    /// Populates the local state with the given `streams` and binds it to the audio core service.
    async fn check_and_bind_volume_controls(
        &mut self,
        id: ftrace::Id,
        streams: impl Iterator<Item = &AudioStream>,
    ) -> ControllerStateResult {
        trace!(id, c"check and bind fn");
        if self.audio_service_connected {
            return Ok(());
        }

        let guard = trace_guard!(id, c"connecting to service");
        let service_result = self
            .client
            .get_service_context()
            .connect::<fidl_fuchsia_media::AudioCoreMarker>()
            .await;

        let audio_service = service_result.map_err(|e| {
            ControllerError::ExternalFailure(
                SettingType::Audio,
                "fuchsia.media.audio".into(),
                "connect for audio_core".into(),
                format!("{e:?}").into(),
            )
        })?;

        // The stream_volume_controls are generated in two steps instead of
        // one so that if one of the bindings fails during the first loop,
        // none of the streams are modified.
        drop(guard);
        let mut stream_tuples = Vec::new();
        for stream in streams {
            trace!(id, c"create stream volume control");
            let client = self.client.clone();

            // Generate a tuple with stream type and StreamVolumeControl.
            stream_tuples.push((
                stream.stream_type,
                StreamVolumeControl::create(
                    id,
                    &audio_service,
                    *stream,
                    Some(Rc::new(move || {
                        // When the StreamVolumeControl exits early, inform the
                        // proxy we have exited. The proxy will then cleanup this
                        // AudioController.
                        let client = client.clone();
                        fasync::Task::local(async move {
                            trace!(id, c"stream exit");
                            client
                                .notify(Event::Exited(Err(ControllerError::UnexpectedError(
                                    "stream_volume_control exit".into(),
                                ))))
                                .await;
                        })
                        .detach();
                    })),
                    None,
                )
                .await?,
            ));
        }

        stream_tuples.into_iter().for_each(|(stream_type, stream_volume_control)| {
            // Ignore the previous value, if any.
            let _ = self.stream_volume_controls.insert(stream_type, stream_volume_control);
        });
        self.audio_service_connected = true;

        Ok(())
    }
}

pub(crate) struct AudioController {
    volume: VolumeControllerHandle,
}

impl StorageAccess for AudioController {
    type Storage = DeviceStorage;
    type Data = AudioInfo;
    const STORAGE_KEY: &'static str = AudioInfo::KEY;
}

impl data_controller::CreateWith for AudioController {
    type Data = AudioInfoLoader;
    fn create_with(client: ClientProxy, data: Self::Data) -> Result<Self, ControllerError> {
        Ok(AudioController { volume: VolumeController::create_with(client, data) })
    }
}

#[async_trait(?Send)]
impl controller::Handle for AudioController {
    async fn handle(&self, request: Request) -> Option<SettingHandlerResult> {
        match request {
            Request::Restore => Some({
                let id = ftrace::Id::new();
                trace!(id, c"controller restore");
                self.volume.lock().await.restore(id).await.map(|_| None)
            }),
            Request::SetVolume(volume, id) => {
                trace!(id, c"controller set");
                // Validate volume contains valid volume level numbers.
                for audio_stream in &volume {
                    if !audio_stream.has_valid_volume_level() {
                        return Some(Err(ControllerError::InvalidArgument(
                            SettingType::Audio,
                            "stream".into(),
                            format!("{audio_stream:?}").into(),
                        )));
                    }
                }
                Some(self.volume.lock().await.set_volume(volume, id).await)
            }
            Request::Get => {
                let id = ftrace::Id::new();
                Some(self.volume.lock().await.get_info(id).await.map(|info| Some(info.into())))
            }
            _ => None,
        }
    }

    async fn change_state(&mut self, state: State) -> Option<ControllerStateResult> {
        match state {
            State::Startup => {
                // Restore the volume state locally but do not push to the audio core.
                Some({
                    let id = ftrace::Id::new();
                    trace!(id, c"controller startup");
                    self.volume.lock().await.restore_volume_state(false, id).await
                })
            }
            _ => None,
        }
    }
}