bt_hfp/sco/
state.rs

1// Copyright 2022 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 fuchsia_bluetooth::profile::ValidScoConnectionParameters;
6use fuchsia_inspect as inspect;
7use fuchsia_inspect_derive::{IValue, Unit};
8use futures::future::{BoxFuture, Fuse, FusedFuture, Future, Shared};
9use futures::FutureExt;
10use std::fmt;
11use vigil::Vigil;
12
13use crate::a2dp;
14use crate::sco::{ConnectError, Connection};
15
16#[derive(Debug)]
17pub struct Active {
18    pub params: ValidScoConnectionParameters,
19    on_closed: Shared<BoxFuture<'static, ()>>,
20    // Stored (but unused) here to keep A2DP in a paused state.
21    _pause_token: a2dp::PauseToken,
22}
23
24impl Active {
25    pub fn new(connection: &Connection, pause_token: a2dp::PauseToken) -> Self {
26        let on_closed = connection.on_closed().boxed().shared();
27        Self { params: connection.params.clone(), on_closed, _pause_token: pause_token }
28    }
29
30    pub fn on_closed(&self) -> impl Future<Output = ()> + 'static {
31        self.on_closed.clone()
32    }
33}
34
35impl Unit for Active {
36    type Data = <Connection as Unit>::Data;
37    fn inspect_create(&self, parent: &inspect::Node, name: impl AsRef<str>) -> Self::Data {
38        self.params.inspect_create(parent, name)
39    }
40
41    fn inspect_update(&self, data: &mut Self::Data) {
42        self.params.inspect_update(data)
43    }
44}
45
46#[derive(Default)]
47pub enum State {
48    /// No call is in progress.
49    #[default]
50    Inactive,
51    /// A call has been made active, and we are negotiating codecs before setting up the SCO
52    /// connection. This state prevents a race in the AG where the call has been made active but
53    /// SCO not yet set up, and the AG peer task, seeing that the connection is not Active,
54    /// attempts to set up the SCO connection a second time,
55    SettingUp,
56    /// The HF has closed the remote SCO connection so we are waiting for the call to be set
57    /// transferred to AG. This state prevents a race in the AG where the SCO connection has been
58    /// torn down but the call not yet set to inactive by the call manager, so the peer task
59    /// attempts to mark the call as inactive a second time.
60    TearingDown,
61    /// A call is transferred to the AG and we are waiting for the HF to initiate a SCO connection.
62    AwaitingRemote(BoxFuture<'static, Result<Connection, ConnectError>>),
63    /// A call is active an dso is the SCO connection.
64    Active(Vigil<Active>),
65}
66
67impl State {
68    pub fn is_active(&self) -> bool {
69        match self {
70            Self::Active(_) => true,
71            _ => false,
72        }
73    }
74
75    pub fn on_connected<'a>(
76        &'a mut self,
77    ) -> impl Future<Output = Result<Connection, ConnectError>> + FusedFuture + 'a {
78        match self {
79            Self::AwaitingRemote(ref mut fut) => fut.fuse(),
80            _ => Fuse::terminated(),
81        }
82    }
83}
84
85impl fmt::Debug for State {
86    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
87        match self {
88            State::Inactive => write!(f, "Inactive"),
89            State::SettingUp => write!(f, "SettingUp"),
90            State::TearingDown => write!(f, "TearingDown"),
91            State::AwaitingRemote(_) => write!(f, "AwaitingRemote"),
92            State::Active(active) => write!(f, "Active({:?})", active),
93        }
94    }
95}
96
97impl std::fmt::Display for State {
98    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
99        let state = match &self {
100            State::Inactive => "Inactive",
101            State::SettingUp => "SettingUp",
102            State::TearingDown => "TearingDown",
103            State::AwaitingRemote(_) => "AwaitingRemote",
104            State::Active(_) => "Active",
105        };
106        write!(f, "{}", state)
107    }
108}
109
110impl Unit for State {
111    type Data = inspect::Node;
112    fn inspect_create(&self, parent: &inspect::Node, name: impl AsRef<str>) -> Self::Data {
113        let mut node = parent.create_child(String::from(name.as_ref()));
114        self.inspect_update(&mut node);
115        node
116    }
117
118    fn inspect_update(&self, data: &mut Self::Data) {
119        data.clear_recorded();
120        data.record_string("status", &format!("{}", self));
121        if let State::Active(active) = &self {
122            let node = active.inspect_create(data, "parameters");
123            data.record(node);
124        }
125    }
126}
127
128pub type InspectableState = IValue<State>;
129
130#[cfg(test)]
131mod tests {
132    use super::*;
133
134    use diagnostics_assertions::assert_data_tree;
135    use fuchsia_bluetooth::types::PeerId;
136    use fuchsia_inspect_derive::WithInspect;
137    use {fidl_fuchsia_bluetooth as fidl_bt, fidl_fuchsia_bluetooth_bredr as bredr};
138
139    #[fuchsia::test]
140    async fn sco_state_inspect_tree() {
141        let inspect = inspect::Inspector::default();
142
143        let mut state =
144            InspectableState::default().with_inspect(inspect.root(), "sco_connection").unwrap();
145        // Default inspect tree.
146        assert_data_tree!(inspect, root: {
147            sco_connection: {
148                status: "Inactive",
149            }
150        });
151
152        state.iset(State::SettingUp);
153        assert_data_tree!(inspect, root: {
154            sco_connection: {
155                status: "SettingUp",
156            }
157        });
158
159        state.iset(State::AwaitingRemote(Box::pin(async { Err(ConnectError::Failed) })));
160        assert_data_tree!(inspect, root: {
161            sco_connection: {
162                status: "AwaitingRemote",
163            }
164        });
165
166        let params = bredr::ScoConnectionParameters {
167            parameter_set: Some(bredr::HfpParameterSet::D1),
168            air_coding_format: Some(fidl_bt::AssignedCodingFormat::Cvsd),
169            air_frame_size: Some(60),
170            io_bandwidth: Some(16000),
171            io_coding_format: Some(fidl_bt::AssignedCodingFormat::LinearPcm),
172            io_frame_size: Some(16),
173            io_pcm_data_format: Some(fidl_fuchsia_hardware_audio::SampleFormat::PcmSigned),
174            io_pcm_sample_payload_msb_position: Some(1),
175            path: Some(bredr::DataPath::Offload),
176            ..Default::default()
177        };
178        let (sco_proxy, _sco_stream) =
179            fidl::endpoints::create_proxy_and_stream::<bredr::ScoConnectionMarker>();
180        let connection = Connection::build(PeerId(1), params, sco_proxy);
181        let vigil = Vigil::new(Active::new(&connection, None));
182        state.iset(State::Active(vigil));
183        assert_data_tree!(inspect, root: {
184            sco_connection: {
185                status: "Active",
186                parameters: {
187                    parameter_set: "D1",
188                    air_coding_format: "Cvsd",
189                    air_frame_size: 60u64,
190                    io_bandwidth: 16000u64,
191                    io_coding_format: "LinearPcm",
192                    io_frame_size: 16u64,
193                    io_pcm_data_format: "PcmSigned",
194                    io_pcm_sample_payload_msb_position: 1u64,
195                    path: "Offload",
196                },
197            }
198        });
199
200        state.iset(State::TearingDown);
201        assert_data_tree!(inspect, root: {
202            sco_connection: {
203                status: "TearingDown",
204            }
205        });
206    }
207}