[bt][hfp] Add Cmer handling to AG Indicators procedure.

Updated procedure marker command matching to take the service level
connection initialization state into account.

Test: Added unit tests
Bug: 74312

Change-Id: I13dd223ce8d9b7a4362bc1d6688b0b5781daedd7
Reviewed-on: https://fuchsia-review.googlesource.com/c/fuchsia/+/514490
Commit-Queue: Auto-Submit <auto-submit@fuchsia-infra.iam.gserviceaccount.com>
Fuchsia-Auto-Submit: Jeff Belgum <belgum@google.com>
Reviewed-by: Ani Ramakrishnan <aniramakri@google.com>
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 bf9d895..ba6bcc7 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
@@ -473,7 +473,7 @@
 
         // Otherwise, try to match it to a procedure - it must be a non SLCI command since
         // the channel has already been initialized.
-        match ProcedureMarker::match_command(command) {
+        match ProcedureMarker::match_command(command, self.initialized()) {
             Ok(ProcedureMarker::SlcInitialization) => {
                 warn!("Received unexpected SLCI command after SLC initialization: {:?}", command);
                 Err(command.into())
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 c23da1d..d29f358 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
@@ -207,18 +207,21 @@
         }
     }
 
-    /// Matches the HF `command` to a procedure. Returns an error if the command is
-    /// unable to be matched.
-    pub fn match_command(command: &at::Command) -> Result<Self, ProcedureError> {
+    /// Matches the HF `command` to a procedure. `initialized` represents the initialization state
+    /// of the Service Level Connection.
+    ///
+    /// Returns an error if the command is unable to be matched.
+    pub fn match_command(command: &at::Command, initialized: bool) -> Result<Self, ProcedureError> {
         match command {
             at::Command::Brsf { .. }
             | at::Command::Bac { .. }
             | at::Command::CindTest { .. }
             | at::Command::CindRead { .. }
-            | at::Command::Cmer { .. }
             | at::Command::ChldTest { .. }
             | at::Command::BindTest { .. }
             | at::Command::BindRead { .. } => Ok(Self::SlcInitialization),
+            at::Command::Cmer { .. } if initialized => Ok(Self::Indicators),
+            at::Command::Cmer { .. } => Ok(Self::SlcInitialization),
             at::Command::Nrec { .. } => Ok(Self::Nrec),
             at::Command::Cops { .. } | at::Command::CopsRead { .. } => {
                 Ok(Self::QueryOperatorSelection)
@@ -530,4 +533,15 @@
                 if messages == vec![at::Response::Ok, at::Response::Error]
         );
     }
+
+    #[test]
+    fn match_conditional_commands_based_on_slci() {
+        let command = at::Command::Cmer { mode: 3, keyp: 0, disp: 0, ind: 1 };
+        let marker = ProcedureMarker::match_command(&command, false).expect("command to match");
+        assert_eq!(marker, ProcedureMarker::SlcInitialization);
+
+        let command = at::Command::Cmer { mode: 3, keyp: 0, disp: 0, ind: 1 };
+        let marker = ProcedureMarker::match_command(&command, true).expect("command to match");
+        assert_eq!(marker, ProcedureMarker::Indicators);
+    }
 }
diff --git a/src/connectivity/bluetooth/profiles/bt-hfp-audio-gateway/src/procedure/indicators_activation.rs b/src/connectivity/bluetooth/profiles/bt-hfp-audio-gateway/src/procedure/indicators_activation.rs
index 2d6c2427..3741ec3 100644
--- a/src/connectivity/bluetooth/profiles/bt-hfp-audio-gateway/src/procedure/indicators_activation.rs
+++ b/src/connectivity/bluetooth/profiles/bt-hfp-audio-gateway/src/procedure/indicators_activation.rs
@@ -4,7 +4,9 @@
 
 use super::{AgUpdate, Procedure, ProcedureError, ProcedureMarker, ProcedureRequest};
 
-use crate::peer::service_level_connection::SlcState;
+use crate::{
+    peer::service_level_connection::SlcState, protocol::indicators::AgIndicatorsReporting,
+};
 use at_commands as at;
 
 /// Converts the indicator activeness flags (represented as Strings) to a vector of
@@ -61,6 +63,16 @@
                     AgUpdate::Error.into()
                 }
             }
+            (false, at::Command::Cmer { mode, ind, .. }) => {
+                self.terminated = true;
+                if mode == AgIndicatorsReporting::EVENT_REPORTING_MODE
+                    && state.ag_indicator_events_reporting.set_reporting_status(ind).is_ok()
+                {
+                    AgUpdate::Ok.into()
+                } else {
+                    AgUpdate::Error.into()
+                }
+            }
             (_, update) => ProcedureRequest::Error(ProcedureError::UnexpectedHf(update)),
         }
     }
@@ -73,7 +85,6 @@
 #[cfg(test)]
 mod tests {
     use super::*;
-    use crate::protocol::indicators::AgIndicatorsReporting;
     use matches::assert_matches;
 
     #[test]
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 38d3331..ea3e0ac 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
@@ -11,18 +11,12 @@
     peer::service_level_connection::SlcState,
     protocol::{
         features::{AgFeatures, HfFeatures},
-        indicators::AgIndicators,
+        indicators::{AgIndicators, AgIndicatorsReporting},
     },
 };
 
 use {at_commands as at, num_traits::FromPrimitive};
 
-/// This mode's behavior is to forward unsolicited result codes directly per
-/// 3GPP TS 27.007 version 6.8.0, Section 8.10.
-/// This is the only supported mode in the Event Reporting Enabling AT command (AT+CMER).
-/// Defined in HFP v1.8 Section 4.34.2.
-const SUPPORTED_EVENT_REPORTING_MODE: i64 = 3;
-
 /// A singular state within the SLC Initialization Procedure.
 pub trait SlcProcedureState {
     /// Returns the next state in the procedure based on the current state and the given
@@ -258,17 +252,13 @@
         // Ensure that the requested `mode` and `ind` values are valid per HFP v1.8 Section 4.34.2.
         match update {
             at::Command::Cmer { mode, ind, .. } => {
-                let is_valid = mode == SUPPORTED_EVENT_REPORTING_MODE && (ind == 0 || ind == 1);
-                if !is_valid {
-                    return SlcErrorState::invalid_hf_argument(update.clone());
-                }
-                if ind != 0 {
-                    state.ag_indicator_events_reporting.enable();
+                if mode == AgIndicatorsReporting::EVENT_REPORTING_MODE
+                    && state.ag_indicator_events_reporting.set_reporting_status(ind).is_ok()
+                {
+                    Box::new(AgIndicatorStatusEnableReceived { state: state.clone() })
                 } else {
-                    state.ag_indicator_events_reporting.disable();
+                    SlcErrorState::invalid_hf_argument(update.clone())
                 }
-
-                Box::new(AgIndicatorStatusEnableReceived { state: state.clone() })
             }
             m => SlcErrorState::unexpected_hf(m),
         }
@@ -546,8 +536,12 @@
             SlcInitProcedure::new_at_state(AgIndicatorStatusReceived { state: state.clone() });
 
         // `ind` = 9 is invalid.
-        let invalid_enable =
-            at::Command::Cmer { mode: SUPPORTED_EVENT_REPORTING_MODE, keyp: 0, disp: 0, ind: 9 };
+        let invalid_enable = at::Command::Cmer {
+            mode: AgIndicatorsReporting::EVENT_REPORTING_MODE,
+            keyp: 0,
+            disp: 0,
+            ind: 9,
+        };
         assert_matches!(
             procedure.hf_update(invalid_enable, &mut state),
             ProcedureRequest::Error(Error::InvalidHfArgument(_))
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 22ee44f..f5aa041 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
@@ -230,6 +230,24 @@
 }
 
 impl AgIndicatorsReporting {
+    /// This mode's behavior is to forward unsolicited result codes directly per
+    /// 3GPP TS 27.007 version 6.8.0, Section 8.10.
+    /// This is the only supported mode in the Event Reporting Enabling AT command (AT+CMER).
+    /// Defined in HFP v1.8 Section 4.34.2.
+    pub const EVENT_REPORTING_MODE: i64 = 3;
+
+    /// Enables or disables the indicators reporting state while maintaining current indicator
+    /// flags. Valid status values are 0 for disabled and 1 for enabled. Any other value returns an
+    /// UnsupportedReportingStatus error. See HFP v1.8 Section 4.34.2 AT+CMER.
+    pub fn set_reporting_status(&mut self, status: i64) -> Result<(), UnsupportedReportingStatus> {
+        match status {
+            0 => self.is_enabled = false,
+            1 => self.is_enabled = true,
+            _ => return Err(UnsupportedReportingStatus(status)),
+        }
+        Ok(())
+    }
+
     #[cfg(test)]
     pub fn set_signal(&mut self, toggle: bool) {
         self.signal = toggle;
@@ -254,18 +272,6 @@
         Self { is_enabled: false, service: true, signal: true, roam: true, batt_chg: true }
     }
 
-    /// Sets the indicators reporting state to enabled while maintaining current indicator
-    /// flags.
-    pub fn enable(&mut self) {
-        self.is_enabled = true;
-    }
-
-    /// Sets the indicators reporting state to disabled while maintaining the current
-    /// indicator flags.
-    pub fn disable(&mut self) {
-        self.is_enabled = false;
-    }
-
     /// Updates the indicators with any indicators specified in `flags`.
     pub fn update_from_flags(&mut self, flags: Vec<Option<bool>>) {
         for (idx, flag) in flags.into_iter().enumerate() {
@@ -308,6 +314,10 @@
     }
 }
 
+#[derive(Debug)]
+/// An error representing an unsupported reporting status value.
+pub struct UnsupportedReportingStatus(i64);
+
 /// The Call Indicator as specified in HFP v1.8, Section 4.10.1
 #[derive(PartialEq, Clone, Copy, Debug)]
 pub enum Call {
@@ -647,12 +657,12 @@
 
         // Toggling indicators reporting should preserve indicator values.
         let expected1 = AgIndicatorsReporting { is_enabled: false, ..status.clone() };
-        status.disable();
+        status.set_reporting_status(0).unwrap();
         assert_eq!(status, expected1);
-        status.disable();
+        status.set_reporting_status(0).unwrap();
         assert_eq!(status, expected1);
 
-        status.enable();
+        status.set_reporting_status(1).unwrap();
         let expected2 = AgIndicatorsReporting { is_enabled: true, ..expected1.clone() };
         assert_eq!(status, expected2);
         assert!(status.is_enabled);