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 and so 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    pub fn on_closed<'a>(&'a self) -> impl Future<Output = ()> + FusedFuture + 'static {
85        match self {
86            Self::Active(ref connection) => connection.on_closed().fuse(),
87            _ => Fuse::terminated(),
88        }
89    }
90}
91
92impl fmt::Debug for State {
93    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
94        match self {
95            State::Inactive => write!(f, "Inactive"),
96            State::SettingUp => write!(f, "SettingUp"),
97            State::TearingDown => write!(f, "TearingDown"),
98            State::AwaitingRemote(_) => write!(f, "AwaitingRemote"),
99            State::Active(active) => write!(f, "Active({:?})", active),
100        }
101    }
102}
103
104impl std::fmt::Display for State {
105    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
106        let state = match &self {
107            State::Inactive => "Inactive",
108            State::SettingUp => "SettingUp",
109            State::TearingDown => "TearingDown",
110            State::AwaitingRemote(_) => "AwaitingRemote",
111            State::Active(_) => "Active",
112        };
113        write!(f, "{}", state)
114    }
115}
116
117impl Unit for State {
118    type Data = inspect::Node;
119    fn inspect_create(&self, parent: &inspect::Node, name: impl AsRef<str>) -> Self::Data {
120        let mut node = parent.create_child(String::from(name.as_ref()));
121        self.inspect_update(&mut node);
122        node
123    }
124
125    fn inspect_update(&self, data: &mut Self::Data) {
126        data.clear_recorded();
127        data.record_string("status", &format!("{}", self));
128        if let State::Active(active) = &self {
129            let node = active.inspect_create(data, "parameters");
130            data.record(node);
131        }
132    }
133}
134
135pub type InspectableState = IValue<State>;
136
137#[cfg(test)]
138mod tests {
139    use super::*;
140
141    use diagnostics_assertions::assert_data_tree;
142    use fuchsia_bluetooth::types::PeerId;
143    use fuchsia_inspect_derive::WithInspect;
144    use {fidl_fuchsia_bluetooth as fidl_bt, fidl_fuchsia_bluetooth_bredr as bredr};
145
146    #[fuchsia::test]
147    async fn sco_state_inspect_tree() {
148        let inspect = inspect::Inspector::default();
149
150        let mut state =
151            InspectableState::default().with_inspect(inspect.root(), "sco_connection").unwrap();
152        // Default inspect tree.
153        assert_data_tree!(inspect, root: {
154            sco_connection: {
155                status: "Inactive",
156            }
157        });
158
159        state.iset(State::SettingUp);
160        assert_data_tree!(inspect, root: {
161            sco_connection: {
162                status: "SettingUp",
163            }
164        });
165
166        state.iset(State::AwaitingRemote(Box::pin(async { Err(ConnectError::Failed) })));
167        assert_data_tree!(inspect, root: {
168            sco_connection: {
169                status: "AwaitingRemote",
170            }
171        });
172
173        let params = bredr::ScoConnectionParameters {
174            parameter_set: Some(bredr::HfpParameterSet::D1),
175            air_coding_format: Some(fidl_bt::AssignedCodingFormat::Cvsd),
176            air_frame_size: Some(60),
177            io_bandwidth: Some(16000),
178            io_coding_format: Some(fidl_bt::AssignedCodingFormat::LinearPcm),
179            io_frame_size: Some(16),
180            io_pcm_data_format: Some(fidl_fuchsia_hardware_audio::SampleFormat::PcmSigned),
181            io_pcm_sample_payload_msb_position: Some(1),
182            path: Some(bredr::DataPath::Offload),
183            ..Default::default()
184        };
185        let (sco_proxy, _sco_stream) =
186            fidl::endpoints::create_proxy_and_stream::<bredr::ScoConnectionMarker>();
187        let connection = Connection::build(PeerId(1), params, sco_proxy);
188        let vigil = Vigil::new(Active::new(&connection, None));
189        state.iset(State::Active(vigil));
190        assert_data_tree!(inspect, root: {
191            sco_connection: {
192                status: "Active",
193                parameters: {
194                    parameter_set: "D1",
195                    air_coding_format: "Cvsd",
196                    air_frame_size: 60u64,
197                    io_bandwidth: 16000u64,
198                    io_coding_format: "LinearPcm",
199                    io_frame_size: 16u64,
200                    io_pcm_data_format: "PcmSigned",
201                    io_pcm_sample_payload_msb_position: 1u64,
202                    path: "Offload",
203                },
204            }
205        });
206
207        state.iset(State::TearingDown);
208        assert_data_tree!(inspect, root: {
209            sco_connection: {
210                status: "TearingDown",
211            }
212        });
213    }
214}