[bt][hfp] Transfer of HF Indicator Values procedure
Bug: 64565
Test: Added to bt-hfp-audio-gateway-tests
Change-Id: Ie3b9ac6dd74b05d456be77311974f65292525d99
Reviewed-on: https://fuchsia-review.googlesource.com/c/fuchsia/+/513287
Commit-Queue: Ani Ramakrishnan <aniramakri@google.com>
Fuchsia-Auto-Submit: Ani Ramakrishnan <aniramakri@google.com>
Reviewed-by: Jeff Belgum <belgum@google.com>
diff --git a/src/connectivity/bluetooth/profiles/bt-hfp-audio-gateway/BUILD.gn b/src/connectivity/bluetooth/profiles/bt-hfp-audio-gateway/BUILD.gn
index f0527e9..8428786 100644
--- a/src/connectivity/bluetooth/profiles/bt-hfp-audio-gateway/BUILD.gn
+++ b/src/connectivity/bluetooth/profiles/bt-hfp-audio-gateway/BUILD.gn
@@ -66,6 +66,7 @@
"src/procedure/ring.rs",
"src/procedure/slc_initialization.rs",
"src/procedure/subscriber_number_information.rs",
+ "src/procedure/transfer_hf_indicator.rs",
"src/procedure/volume_synchronization.rs",
"src/profile.rs",
"src/protocol.rs",
diff --git a/src/connectivity/bluetooth/profiles/bt-hfp-audio-gateway/src/peer/service_level_connection.rs b/src/connectivity/bluetooth/profiles/bt-hfp-audio-gateway/src/peer/service_level_connection.rs
index 9aa3295..bf9d895 100644
--- a/src/connectivity/bluetooth/profiles/bt-hfp-audio-gateway/src/peer/service_level_connection.rs
+++ b/src/connectivity/bluetooth/profiles/bt-hfp-audio-gateway/src/peer/service_level_connection.rs
@@ -679,7 +679,7 @@
/// Expects a message to be received by the peer. If provided, validates the contents
/// of the received message.
#[track_caller]
- fn expect_peer_ready(
+ pub fn expect_peer_ready(
exec: &mut fasync::Executor,
remote: &mut Channel,
expected: Option<Vec<u8>>,
@@ -710,7 +710,7 @@
/// Serializes the AT Response into a byte buffer.
#[track_caller]
- fn serialize_at_response(response: at::Response) -> Vec<u8> {
+ pub fn serialize_at_response(response: at::Response) -> Vec<u8> {
let mut buf = Vec::new();
response.serialize(&mut buf).expect("serialization is ok");
buf
diff --git a/src/connectivity/bluetooth/profiles/bt-hfp-audio-gateway/src/peer/task.rs b/src/connectivity/bluetooth/profiles/bt-hfp-audio-gateway/src/peer/task.rs
index 58f8592..28cae83 100644
--- a/src/connectivity/bluetooth/profiles/bt-hfp-audio-gateway/src/peer/task.rs
+++ b/src/connectivity/bluetooth/profiles/bt-hfp-audio-gateway/src/peer/task.rs
@@ -36,7 +36,7 @@
error::Error,
procedure::{AgUpdate, InformationRequest, ProcedureMarker},
profile::ProfileEvent,
- protocol::indicators::{AgIndicator, AgIndicators},
+ protocol::indicators::{AgIndicator, AgIndicators, HfIndicator},
};
pub(super) struct PeerTask {
@@ -233,6 +233,10 @@
self.calls.send_dtmf_code(code).await;
self.connection.receive_ag_request(marker, response()).await;
}
+ InformationRequest::SendHfIndicator { indicator, response } => {
+ self.hf_indicator_update(indicator);
+ self.connection.receive_ag_request(marker, response()).await;
+ }
InformationRequest::SetNrec { enable, response } => {
let result = if let Some(handler) = &mut self.handler {
if let Ok(Ok(())) = handler.set_nrec_mode(enable).await {
@@ -335,6 +339,22 @@
self
}
+ /// Sends an HF Indicator update to the client.
+ fn hf_indicator_update(&mut self, indicator: HfIndicator) {
+ match indicator {
+ ind @ HfIndicator::EnhancedSafety(_) => {
+ debug!("Received EnhancedSafety HF Indicator update: {:?}", ind);
+ }
+ HfIndicator::BatteryLevel(v) => {
+ if let Some(handler) = &mut self.handler {
+ if let Err(e) = handler.report_headset_battery_level(v) {
+ log::warn!("Couldn't report headset battery level: {:?}", e);
+ }
+ }
+ }
+ }
+ }
+
/// Request to send the phone `status` by initiating the Phone Status Indicator
/// procedure.
async fn ring_update(&mut self, call: Call) {
@@ -403,6 +423,7 @@
use {
super::*,
async_utils::PollExt,
+ at_commands::{self as at, SerDe},
fidl_fuchsia_bluetooth_bredr::{ProfileMarker, ProfileRequestStream},
fidl_fuchsia_bluetooth_hfp::{
CallState, PeerHandlerMarker, PeerHandlerRequest, SignalStrength,
@@ -415,10 +436,16 @@
use crate::{
peer::service_level_connection::{
- tests::{create_and_initialize_slc, expect_data_received_by_peer},
+ tests::{
+ create_and_initialize_slc, expect_data_received_by_peer, expect_peer_ready,
+ serialize_at_response,
+ },
SlcState,
},
- protocol::{features::HfFeatures, indicators::AgIndicatorsReporting},
+ protocol::{
+ features::HfFeatures,
+ indicators::{AgIndicatorsReporting, HfIndicators},
+ },
};
fn arb_signal() -> impl Strategy<Value = Option<SignalStrength>> {
@@ -788,4 +815,77 @@
// Check that the task's ringer has an active call with the expected call index.
assert!(task.ringer.ringing());
}
+
+ #[test]
+ fn incoming_hf_indicator_battery_level_is_propagated_to_peer_handler_stream() {
+ // Set up the executor, peer, and background call manager task
+ let mut exec = fasync::Executor::new().unwrap();
+
+ // Setup the peer task with the specified SlcState to enable the battery level HF indicator.
+ let mut hf_indicators = HfIndicators::default();
+ hf_indicators.enable_indicators(vec![at::BluetoothHFIndicator::BatteryLevel]);
+ let state = SlcState { hf_indicators, ..SlcState::default() };
+ let (connection, mut remote) = create_and_initialize_slc(state);
+ let (peer, mut sender, receiver, _profile) = setup_peer_task(Some(connection));
+
+ let (proxy, mut stream) =
+ fidl::endpoints::create_proxy_and_stream::<PeerHandlerMarker>().unwrap();
+ // The battery level that will be reported by the peer.
+ let expected_level = 79;
+
+ fasync::Task::local(async move {
+ // First request is always the network info.
+ match stream.next().await {
+ Some(Ok(PeerHandlerRequest::WatchNetworkInformation { responder })) => {
+ responder
+ .send(NetworkInformation::EMPTY)
+ .expect("Successfully send network information");
+ }
+ x => panic!("Expected watch network information request: {:?}", x),
+ };
+ // A vec to hold all the stream items we don't care about for this test.
+ let mut junk_drawer = vec![];
+
+ // Filter out all items that are irrelevant to this particular test, placing them in
+ // the junk_drawer.
+ let mut stream = stream.filter_map(move |item| {
+ let item = match item {
+ Ok(PeerHandlerRequest::ReportHeadsetBatteryLevel { level, .. }) => Some(level),
+ x => {
+ junk_drawer.push(x);
+ None
+ }
+ };
+ ready(item)
+ });
+ let actual_battery_level = stream.next().await.unwrap();
+ assert_eq!(actual_battery_level, expected_level);
+ // Call manager should collect all further requests, without responding.
+ stream.collect::<Vec<_>>().await;
+ })
+ .detach();
+
+ // Pass in the client end connected to the call manager
+ let result = exec.run_singlethreaded(sender.send(PeerRequest::Handle(proxy)));
+ assert!(result.is_ok());
+
+ // Run the PeerTask.
+ let run_fut = peer.run(receiver);
+ pin_mut!(run_fut);
+ assert!(exec.run_until_stalled(&mut run_fut).is_pending());
+
+ // Peer sends us a battery level HF indicator update.
+ let battery_level_cmd = at::Command::Biev {
+ anum: at::BluetoothHFIndicator::BatteryLevel,
+ value: expected_level as i64,
+ };
+ let mut buf = Vec::new();
+ battery_level_cmd.serialize(&mut buf).expect("serialization is ok");
+ remote.as_ref().write(&buf[..]).expect("channel write is ok");
+ // Run the main future - the spawned task should receive the HF indicator and report it.
+ assert!(exec.run_until_stalled(&mut run_fut).is_pending());
+
+ // Since we (the AG) received a valid HF indicator, we expect to send an OK back to the peer.
+ expect_peer_ready(&mut exec, &mut remote, Some(serialize_at_response(at::Response::Ok)));
+ }
}
diff --git a/src/connectivity/bluetooth/profiles/bt-hfp-audio-gateway/src/procedure.rs b/src/connectivity/bluetooth/profiles/bt-hfp-audio-gateway/src/procedure.rs
index 928eabe..2742f45 100644
--- a/src/connectivity/bluetooth/profiles/bt-hfp-audio-gateway/src/procedure.rs
+++ b/src/connectivity/bluetooth/profiles/bt-hfp-audio-gateway/src/procedure.rs
@@ -16,7 +16,7 @@
},
protocol::{
features::AgFeatures,
- indicators::{AgIndicator, AgIndicators, HfIndicators},
+ indicators::{AgIndicator, AgIndicators, HfIndicator, HfIndicators},
},
};
@@ -62,6 +62,9 @@
/// Defines the implementation of the Phone Status Procedures.
pub mod phone_status;
+/// Defines the implementation of the Transfer of HF Indicator Values Procedure.
+pub mod transfer_hf_indicator;
+
/// Defines the implementation of the Volume Level Synchronization Procedure.
pub mod volume_synchronization;
@@ -79,6 +82,7 @@
use ring::RingProcedure;
use slc_initialization::SlcInitProcedure;
use subscriber_number_information::{build_cnum_response, SubscriberNumberInformationProcedure};
+use transfer_hf_indicator::TransferHfIndicatorProcedure;
use volume_synchronization::VolumeSynchronizationProcedure;
const THREE_WAY_SUPPORT: &[&str] = &["0", "1", "1X", "2", "2X", "3", "4"];
@@ -172,6 +176,8 @@
Answer,
/// The Hang Up procedure as defined in HFP v1.8 Sections 4.14 - 4.15
HangUp,
+ /// The Transfer of HF Indicator Values procedure as defined in HFP v1.8 Section 4.36.1.5.
+ TransferHfIndicator,
}
impl ProcedureMarker {
@@ -197,6 +203,7 @@
Self::Ring => Box::new(RingProcedure::new()),
Self::Answer => Box::new(AnswerProcedure::new()),
Self::HangUp => Box::new(HangUpProcedure::new()),
+ Self::TransferHfIndicator => Box::new(TransferHfIndicatorProcedure::new()),
}
}
@@ -225,6 +232,7 @@
at::Command::Vts { .. } => Ok(Self::Dtmf),
at::Command::Answer { .. } => Ok(Self::Answer),
at::Command::Chup { .. } => Ok(Self::HangUp),
+ at::Command::Biev { .. } => Ok(Self::TransferHfIndicator),
_ => Err(ProcedureError::NotImplemented),
}
}
@@ -243,6 +251,8 @@
SetNrec { enable: bool, response: Box<dyn FnOnce(Result<(), ()>) -> AgUpdate> },
+ SendHfIndicator { indicator: HfIndicator, response: Box<dyn FnOnce() -> AgUpdate> },
+
SendDtmf { code: DtmfCode, response: Box<dyn FnOnce() -> AgUpdate> },
SpeakerVolumeSynchronization { level: Gain, response: Box<dyn FnOnce() -> AgUpdate> },
@@ -265,6 +275,7 @@
GetSubscriberNumberInformation { .. } => Self::SubscriberNumberInformation,
SetNrec { .. } => Self::Nrec,
SendDtmf { .. } => Self::Dtmf,
+ SendHfIndicator { .. } => Self::TransferHfIndicator,
SpeakerVolumeSynchronization { .. } | MicrophoneVolumeSynchronization { .. } => {
Self::VolumeSynchronization
}
@@ -288,6 +299,10 @@
Self::QueryCurrentCalls { .. } => "QueryCurrentCalls ",
// DTFM Code values are not displayed in Debug representation
Self::SendDtmf { .. } => "SendDtmf",
+ Self::SendHfIndicator { indicator, .. } => {
+ s = format!("SendHfIndicator({:?})", indicator);
+ &s
+ }
Self::SpeakerVolumeSynchronization { level, .. } => {
s = format!("SpeakerVolumeSynchronization({:?})", level);
&s
diff --git a/src/connectivity/bluetooth/profiles/bt-hfp-audio-gateway/src/procedure/slc_initialization.rs b/src/connectivity/bluetooth/profiles/bt-hfp-audio-gateway/src/procedure/slc_initialization.rs
index db150e9..6fa04dd 100644
--- a/src/connectivity/bluetooth/profiles/bt-hfp-audio-gateway/src/procedure/slc_initialization.rs
+++ b/src/connectivity/bluetooth/profiles/bt-hfp-audio-gateway/src/procedure/slc_initialization.rs
@@ -333,7 +333,7 @@
match update {
at::Command::Bind { indicators } => {
- state.hf_indicators.set(indicators);
+ state.hf_indicators.enable_indicators(indicators);
Box::new(HfSupportedIndicatorsReceived)
}
m => SlcErrorState::unexpected_hf(m),
diff --git a/src/connectivity/bluetooth/profiles/bt-hfp-audio-gateway/src/procedure/transfer_hf_indicator.rs b/src/connectivity/bluetooth/profiles/bt-hfp-audio-gateway/src/procedure/transfer_hf_indicator.rs
new file mode 100644
index 0000000..4f80035
--- /dev/null
+++ b/src/connectivity/bluetooth/profiles/bt-hfp-audio-gateway/src/procedure/transfer_hf_indicator.rs
@@ -0,0 +1,210 @@
+// Copyright 2021 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::{
+ AgUpdate, InformationRequest, Procedure, ProcedureError, ProcedureMarker, ProcedureRequest,
+};
+
+use crate::peer::service_level_connection::SlcState;
+use at_commands as at;
+
+/// Represents the current state of the HF request to transmit an HF Indicator value as
+/// defined in HFP v1.8 Section 4.36.1.5.
+#[derive(Debug, PartialEq, Clone, Copy)]
+enum State {
+ /// Initial state of the Procedure.
+ Start,
+ /// A request has been received from the HF to transmit the indicator via the AG.
+ SendRequest,
+ /// Terminal state of the procedure.
+ Terminated,
+}
+
+impl State {
+ /// Transition to the next state in the procedure.
+ fn transition(&mut self) {
+ match *self {
+ Self::Start => *self = Self::SendRequest,
+ Self::SendRequest => *self = Self::Terminated,
+ Self::Terminated => *self = Self::Terminated,
+ }
+ }
+}
+
+/// The Hf may send an updated HF Indicator value via this procedure. Defined in
+/// HFP v1.8 Section 4.36.1.5.
+///
+/// This procedure is implemented from the perspective of the AG. Namely, outgoing `requests`
+/// typically request information about the current state of the AG, to be sent to the remote
+/// peer acting as the HF.
+#[derive(Debug)]
+pub struct TransferHfIndicatorProcedure {
+ /// The current state of the procedure
+ state: State,
+}
+
+impl Default for TransferHfIndicatorProcedure {
+ fn default() -> Self {
+ Self { state: State::Start }
+ }
+}
+
+impl TransferHfIndicatorProcedure {
+ /// Create a new Transfer HF Indicator procedure in the Start state.
+ pub fn new() -> Self {
+ Self::default()
+ }
+}
+
+impl Procedure for TransferHfIndicatorProcedure {
+ fn marker(&self) -> ProcedureMarker {
+ ProcedureMarker::TransferHfIndicator
+ }
+
+ fn hf_update(&mut self, update: at::Command, state: &mut SlcState) -> ProcedureRequest {
+ match (self.state, update) {
+ (State::Start, at::Command::Biev { anum, value }) => {
+ self.state.transition();
+ // Per HFP v1.8 Section 4.36.1.5, we should send Error if the request `anum` is
+ // disabled, or the `value` is out of bounds.
+ if let Ok(indicator) = state.hf_indicators.update_indicator_value(anum, value) {
+ InformationRequest::SendHfIndicator {
+ indicator,
+ response: Box::new(|| AgUpdate::Ok),
+ }
+ .into()
+ } else {
+ self.state.transition();
+ AgUpdate::Error.into()
+ }
+ }
+ (_, update) => ProcedureRequest::Error(ProcedureError::UnexpectedHf(update)),
+ }
+ }
+
+ fn ag_update(&mut self, update: AgUpdate, _state: &mut SlcState) -> ProcedureRequest {
+ match (self.state, update) {
+ (State::SendRequest, update @ AgUpdate::Ok) => {
+ self.state.transition();
+ update.into()
+ }
+ (_, update) => ProcedureRequest::Error(ProcedureError::UnexpectedAg(update)),
+ }
+ }
+
+ fn is_terminated(&self) -> bool {
+ self.state == State::Terminated
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::protocol::indicators::HfIndicator;
+ use matches::assert_matches;
+
+ #[test]
+ fn correct_marker() {
+ let marker = TransferHfIndicatorProcedure::new().marker();
+ assert_eq!(marker, ProcedureMarker::TransferHfIndicator);
+ }
+
+ #[test]
+ fn procedure_handles_invalid_messages() {
+ let mut proc = TransferHfIndicatorProcedure::new();
+ let req = proc.hf_update(at::Command::CindRead {}, &mut SlcState::default());
+ assert_matches!(req, ProcedureRequest::Error(ProcedureError::UnexpectedHf(_)));
+
+ let req = proc.ag_update(AgUpdate::ThreeWaySupport, &mut SlcState::default());
+ assert_matches!(req, ProcedureRequest::Error(ProcedureError::UnexpectedAg(_)));
+ }
+
+ #[test]
+ fn procedure_with_invalid_battery_value_sends_error_message() {
+ let mut proc = TransferHfIndicatorProcedure::new();
+ let mut state = SlcState::default();
+ state.hf_indicators.enable_indicators(vec![
+ at::BluetoothHFIndicator::BatteryLevel,
+ at::BluetoothHFIndicator::EnhancedSafety,
+ ]);
+
+ // Battery level is not within the range [0,100].
+ let cmd = at::Command::Biev { anum: at::BluetoothHFIndicator::BatteryLevel, value: 164 };
+ let req = proc.hf_update(cmd, &mut state);
+ let expected = vec![at::Response::Error];
+ assert_matches!(req, ProcedureRequest::SendMessages(m) if m == expected);
+ assert!(proc.is_terminated());
+ }
+
+ #[test]
+ fn procedure_with_valid_battery_value_sends_ok() {
+ let mut proc = TransferHfIndicatorProcedure::new();
+ let mut state = SlcState::default();
+ state.hf_indicators.enable_indicators(vec![
+ at::BluetoothHFIndicator::BatteryLevel,
+ at::BluetoothHFIndicator::EnhancedSafety,
+ ]);
+
+ let cmd = at::Command::Biev { anum: at::BluetoothHFIndicator::BatteryLevel, value: 76 };
+ let req = proc.hf_update(cmd, &mut state);
+ let update = match req {
+ ProcedureRequest::Info(InformationRequest::SendHfIndicator {
+ indicator: HfIndicator::BatteryLevel(76),
+ response,
+ }) => response(),
+ x => panic!("Expected SendHFInd request but got: {:?}", x),
+ };
+
+ let req = proc.ag_update(update, &mut state);
+ assert_matches!(
+ req,
+ ProcedureRequest::SendMessages(msgs) if msgs == vec![at::Response::Ok]
+ );
+ assert!(proc.is_terminated());
+ }
+
+ #[test]
+ fn procedure_with_invalid_safety_value_sends_error_message() {
+ let mut proc = TransferHfIndicatorProcedure::new();
+ let mut state = SlcState::default();
+ state.hf_indicators.enable_indicators(vec![
+ at::BluetoothHFIndicator::BatteryLevel,
+ at::BluetoothHFIndicator::EnhancedSafety,
+ ]);
+
+ // Enhanced Safety can only be 0 or 1.
+ let cmd = at::Command::Biev { anum: at::BluetoothHFIndicator::EnhancedSafety, value: 7 };
+ let req = proc.hf_update(cmd, &mut state);
+ let expected = vec![at::Response::Error];
+ assert_matches!(req, ProcedureRequest::SendMessages(m) if m == expected);
+ assert!(proc.is_terminated());
+ }
+
+ #[test]
+ fn procedure_with_valid_safety_value_sends_ok() {
+ let mut proc = TransferHfIndicatorProcedure::new();
+ let mut state = SlcState::default();
+ state.hf_indicators.enable_indicators(vec![
+ at::BluetoothHFIndicator::BatteryLevel,
+ at::BluetoothHFIndicator::EnhancedSafety,
+ ]);
+
+ let cmd = at::Command::Biev { anum: at::BluetoothHFIndicator::EnhancedSafety, value: 1 };
+ let req = proc.hf_update(cmd, &mut state);
+ let update = match req {
+ ProcedureRequest::Info(InformationRequest::SendHfIndicator {
+ indicator: HfIndicator::EnhancedSafety(true),
+ response,
+ }) => response(),
+ x => panic!("Expected SendHFInd request but got: {:?}", x),
+ };
+
+ let req = proc.ag_update(update, &mut state);
+ assert_matches!(
+ req,
+ ProcedureRequest::SendMessages(msgs) if msgs == vec![at::Response::Ok]
+ );
+ assert!(proc.is_terminated());
+ }
+}
diff --git a/src/connectivity/bluetooth/profiles/bt-hfp-audio-gateway/src/protocol/indicators.rs b/src/connectivity/bluetooth/profiles/bt-hfp-audio-gateway/src/protocol/indicators.rs
index 8ffc78a..39582a0 100644
--- a/src/connectivity/bluetooth/profiles/bt-hfp-audio-gateway/src/protocol/indicators.rs
+++ b/src/connectivity/bluetooth/profiles/bt-hfp-audio-gateway/src/protocol/indicators.rs
@@ -16,16 +16,23 @@
pub(crate) const ROAM_INDICATOR_INDEX: usize = 6;
pub(crate) const BATT_CHG_INDICATOR_INDEX: usize = 7;
-/// A single HF Indicator status + value.
+/// The supported HF indicators.
+#[derive(Clone, Copy, Debug, PartialEq)]
+pub enum HfIndicator {
+ EnhancedSafety(bool),
+ BatteryLevel(u8),
+}
+
+/// A single indicator status + value.
#[derive(Clone, Copy, Debug)]
-pub struct HfIndicator<T: Clone + Copy + Debug> {
+struct Indicator<T: Clone + Copy + Debug> {
/// Whether this indicator is enabled or not.
enabled: bool,
/// The value of the indicator.
value: Option<T>,
}
-impl<T: Clone + Copy + Debug> Default for HfIndicator<T> {
+impl<T: Clone + Copy + Debug> Default for Indicator<T> {
fn default() -> Self {
Self { enabled: false, value: None }
}
@@ -36,14 +43,18 @@
#[derive(Clone, Copy, Debug, Default)]
pub struct HfIndicators {
/// The Enhanced Safety HF indicator. There are only two potential values (enabled, disabled).
- enhanced_safety: HfIndicator<bool>,
+ enhanced_safety: Indicator<bool>,
/// The Battery Level HF indicator. Can be any integer value between [0, 100].
- battery_level: HfIndicator<u8>,
+ battery_level: Indicator<u8>,
}
impl HfIndicators {
- /// Sets the HF indicators based on the provided AT `indicators`.
- pub fn set(&mut self, indicators: Vec<at::BluetoothHFIndicator>) {
+ /// The Maximum Battery Level value for the `battery_level` indicator.
+ /// Defined in HFP v1.8 Section 4.35.
+ const MAX_BATTERY_LEVEL: u8 = 100;
+
+ /// Enables the supported HF indicators based on the provided AT `indicators`.
+ pub fn enable_indicators(&mut self, indicators: Vec<at::BluetoothHFIndicator>) {
for ind in indicators {
if ind == at::BluetoothHFIndicator::EnhancedSafety {
self.enhanced_safety.enabled = true;
@@ -54,6 +65,39 @@
}
}
+ /// Updates the `indicator` with the provided `value`.
+ /// Returns Error if the indicator is disabled or if the `value` is out of bounds.
+ /// Returns a valid HfIndicator on success.
+ pub fn update_indicator_value(
+ &mut self,
+ indicator: at::BluetoothHFIndicator,
+ value: i64,
+ ) -> Result<HfIndicator, ()> {
+ let ind = match indicator {
+ at::BluetoothHFIndicator::EnhancedSafety if self.enhanced_safety.enabled => {
+ if value != 0 && value != 1 {
+ return Err(());
+ }
+ let v = value != 0;
+ self.enhanced_safety.value = Some(v);
+ HfIndicator::EnhancedSafety(v)
+ }
+ at::BluetoothHFIndicator::BatteryLevel if self.battery_level.enabled => {
+ if value < 0 || value > Self::MAX_BATTERY_LEVEL.into() {
+ return Err(());
+ }
+ let v = value as u8;
+ self.battery_level.value = Some(v);
+ HfIndicator::BatteryLevel(v)
+ }
+ ind => {
+ log::warn!("Received HF indicator update for disabled indicator: {:?}", ind);
+ return Err(());
+ }
+ };
+ Ok(ind)
+ }
+
/// Returns the +BIND response for the current HF indicator status.
pub fn bind_response(&self) -> Vec<at::Response> {
vec![
@@ -431,6 +475,94 @@
#[cfg(test)]
mod tests {
use super::*;
+ use matches::assert_matches;
+
+ #[test]
+ fn update_hf_indicators_with_invalid_values_is_error() {
+ let mut hf_indicators = HfIndicators::default();
+ hf_indicators.enable_indicators(vec![
+ at::BluetoothHFIndicator::BatteryLevel,
+ at::BluetoothHFIndicator::EnhancedSafety,
+ ]);
+
+ let battery_too_low = -18;
+ assert_matches!(
+ hf_indicators
+ .update_indicator_value(at::BluetoothHFIndicator::BatteryLevel, battery_too_low),
+ Err(())
+ );
+ let battery_too_high = 1243;
+ assert_matches!(
+ hf_indicators
+ .update_indicator_value(at::BluetoothHFIndicator::BatteryLevel, battery_too_high),
+ Err(())
+ );
+
+ let negative_safety = -1;
+ assert_matches!(
+ hf_indicators
+ .update_indicator_value(at::BluetoothHFIndicator::EnhancedSafety, negative_safety),
+ Err(())
+ );
+ let large_safety = 8;
+ assert_matches!(
+ hf_indicators
+ .update_indicator_value(at::BluetoothHFIndicator::EnhancedSafety, large_safety),
+ Err(())
+ );
+ }
+
+ #[test]
+ fn update_disabled_hf_indicators_with_valid_values_is_error() {
+ // Default is no indicators set. Therefore any updates are errors.
+ let mut hf_indicators = HfIndicators::default();
+
+ let valid_battery = 32;
+ assert_matches!(
+ hf_indicators
+ .update_indicator_value(at::BluetoothHFIndicator::BatteryLevel, valid_battery),
+ Err(())
+ );
+
+ let valid_safety = 0;
+ assert_matches!(
+ hf_indicators
+ .update_indicator_value(at::BluetoothHFIndicator::EnhancedSafety, valid_safety),
+ Err(())
+ );
+ }
+
+ #[test]
+ fn update_hf_indicators_with_valid_values_is_ok() {
+ let mut hf_indicators = HfIndicators::default();
+ // Default values.
+ assert_eq!(hf_indicators.enhanced_safety.value, None);
+ assert_eq!(hf_indicators.enhanced_safety.enabled, false);
+ assert_eq!(hf_indicators.battery_level.value, None);
+ assert_eq!(hf_indicators.battery_level.enabled, false);
+
+ // Enable both.
+ hf_indicators.enable_indicators(vec![
+ at::BluetoothHFIndicator::BatteryLevel,
+ at::BluetoothHFIndicator::EnhancedSafety,
+ ]);
+
+ let valid_battery = 83;
+ assert_matches!(
+ hf_indicators
+ .update_indicator_value(at::BluetoothHFIndicator::BatteryLevel, valid_battery),
+ Ok(HfIndicator::BatteryLevel(83))
+ );
+ assert_eq!(hf_indicators.battery_level.value, Some(valid_battery as u8));
+
+ let valid_safety = 0;
+ assert_matches!(
+ hf_indicators
+ .update_indicator_value(at::BluetoothHFIndicator::EnhancedSafety, valid_safety),
+ Ok(HfIndicator::EnhancedSafety(false))
+ );
+ assert_eq!(hf_indicators.enhanced_safety.value, Some(false));
+ }
#[test]
fn default_indicators_reporting_is_disabled_with_all_indicators_enabled() {
diff --git a/src/connectivity/lib/at-commands/definitions/hfp.at b/src/connectivity/lib/at-commands/definitions/hfp.at
index 5fed5cd..345b3ce 100644
--- a/src/connectivity/lib/at-commands/definitions/hfp.at
+++ b/src/connectivity/lib/at-commands/definitions/hfp.at
@@ -81,6 +81,10 @@
# HFP 1.8 4.36.1.3
response BindStatus { +BIND:anum:BluetoothHFIndicator,state:BoolAsInt }
+# Bluetooth HF Indicator value command
+# HFP 1.8 4.36.1.5
+command { AT+BIEV=anum:BluetoothHFIndicator,value:Integer}
+
# Standard Call hold read command.
# HFP 1.8 4.34.2
command { AT+CHLD=? }