LeAudio: Workaround VoIP calls not using Telecom API

When the Telecom API is not used for the VoIP call, we do not
receive the InCall mode callback. We need to figure out that this
is a VoIP call and reconfigure for the CONVERSATIONAL context. However
since Telecom is not involved, TBS does not know about the call and it
will not be able to control the call state. We should also NOT put the
TBS content control ID into the stream metadata, as there will be no
incoming call on the TBS service. Otherwise the remote device would
get confused.

Bug: 284230664
Test: atest bluetooth_le_audio_client_test
Change-Id: I4d320e71055a06657c9dd688a6f873916cf862ec
diff --git a/system/bta/include/bta_le_audio_api.h b/system/bta/include/bta_le_audio_api.h
index 38b6c2e..1d61bc3 100644
--- a/system/bta/include/bta_le_audio_api.h
+++ b/system/bta/include/bta_le_audio_api.h
@@ -63,6 +63,9 @@
       bluetooth::le_audio::btle_audio_codec_config_t output_codec_config) = 0;
   virtual void SetCcidInformation(int ccid, int context_type) = 0;
   virtual void SetInCall(bool in_call) = 0;
+  virtual bool IsInCall() = 0;
+  virtual void SetInVoipCall(bool in_call) = 0;
+  virtual bool IsInVoipCall() = 0;
   virtual void SendAudioProfilePreferences(
       const int group_id, bool is_output_preference_le_audio,
       bool is_duplex_preference_le_audio) = 0;
diff --git a/system/bta/le_audio/client.cc b/system/bta/le_audio/client.cc
index 63b304d..a4b23e1 100644
--- a/system/bta/le_audio/client.cc
+++ b/system/bta/le_audio/client.cc
@@ -251,6 +251,7 @@
         audio_receiver_state_(AudioState::IDLE),
         audio_sender_state_(AudioState::IDLE),
         in_call_(false),
+        in_voip_call_(false),
         current_source_codec_config({0, 0, 0, 0}),
         current_sink_codec_config({0, 0, 0, 0}),
         lc3_encoder_left_mem(nullptr),
@@ -304,6 +305,10 @@
   void ReconfigureAfterVbcClose() {
     LOG_DEBUG("VBC close timeout");
 
+    if (IsInVoipCall()) {
+      SetInVoipCall(false);
+    }
+
     auto group = aseGroups_.FindById(active_group_id_);
     if (!group) {
       LOG_ERROR("Invalid group: %d", active_group_id_);
@@ -917,11 +922,18 @@
       remote_contexts.source.clear();
     }
 
+    /* Do not put the TBS CCID when not using Telecom for the VoIP calls. */
+    auto ccid_contexts = remote_contexts;
+    if (IsInVoipCall() && !IsInCall()) {
+      ccid_contexts.sink.unset(LeAudioContextType::CONVERSATIONAL);
+      ccid_contexts.source.unset(LeAudioContextType::CONVERSATIONAL);
+    }
+
     BidirectionalPair<std::vector<uint8_t>> ccids = {
         .sink = ContentControlIdKeeper::GetInstance()->GetAllCcids(
-            remote_contexts.sink),
+            ccid_contexts.sink),
         .source = ContentControlIdKeeper::GetInstance()->GetAllCcids(
-            remote_contexts.source)};
+            ccid_contexts.source)};
     if (group->IsPendingConfiguration()) {
       return groupStateMachine_->ConfigureStream(
           group, configuration_context_type_, remote_contexts, ccids);
@@ -1037,6 +1049,15 @@
     in_call_ = in_call;
   }
 
+  bool IsInCall() override { return in_call_; }
+
+  void SetInVoipCall(bool in_call) override {
+    LOG_DEBUG("in_voip_call: %d", in_call);
+    in_voip_call_ = in_call;
+  }
+
+  bool IsInVoipCall() override { return in_voip_call_; }
+
   void SendAudioProfilePreferences(
       const int group_id, bool is_output_preference_le_audio,
       bool is_duplex_preference_le_audio) override {
@@ -2943,7 +2964,20 @@
       return;
     }
 
-    if (!groupStateMachine_->AttachToStream(group, leAudioDevice)) {
+    /* Do not put the TBS CCID when not using Telecom for the VoIP calls. */
+    auto ccid_contexts = group->GetMetadataContexts();
+    if (IsInVoipCall() && !IsInCall()) {
+      ccid_contexts.sink.unset(LeAudioContextType::CONVERSATIONAL);
+      ccid_contexts.source.unset(LeAudioContextType::CONVERSATIONAL);
+    }
+    BidirectionalPair<std::vector<uint8_t>> ccids = {
+        .sink = ContentControlIdKeeper::GetInstance()->GetAllCcids(
+            ccid_contexts.sink),
+        .source = ContentControlIdKeeper::GetInstance()->GetAllCcids(
+            ccid_contexts.source)};
+
+    if (!groupStateMachine_->AttachToStream(group, leAudioDevice,
+                                            std::move(ccids))) {
       LOG_WARN("Could not add device %s to the group %d streaming. ",
                ADDRESS_TO_LOGGABLE_CSTR(leAudioDevice->address_),
                group->group_id_);
@@ -3898,7 +3932,7 @@
     }
 
     /* Check if the device resume is expected */
-    if (!group->GetCachedCodecConfigurationByDirection(
+    if (!group->GetCodecConfigurationByDirection(
             configuration_context_type_,
             le_audio::types::kLeAudioDirectionSink)) {
       LOG(ERROR) << __func__ << ", invalid resume request for context type: "
@@ -4324,7 +4358,7 @@
               bluetooth::common::ToString(available_remote_contexts).c_str(),
               bluetooth::common::ToString(configuration_context_type_).c_str());
 
-    if (in_call_) {
+    if (IsInCall()) {
       LOG_DEBUG(" In Call preference used.");
       return LeAudioContextType::CONVERSATIONAL;
     }
@@ -4336,10 +4370,10 @@
       LeAudioContextType context_priority_list[] = {
           /* Highest priority first */
           LeAudioContextType::CONVERSATIONAL,
-          /* Skip the RINGTONE to avoid reconfigurations when adjusting
-           * call volume slider while not in a call.
-           * LeAudioContextType::RINGTONE,
+          /* Handling RINGTONE will cause the ringtone volume slider to trigger
+           * reconfiguration. This will be fixed in b/283349711.
            */
+          LeAudioContextType::RINGTONE,
           LeAudioContextType::LIVE,
           LeAudioContextType::VOICEASSISTANTS,
           LeAudioContextType::GAME,
@@ -4600,11 +4634,29 @@
 
   BidirectionalPair<AudioContexts> DirectionalRealignMetadataAudioContexts(
       LeAudioDeviceGroup* group, int remote_direction) {
+    // Inject conversational when ringtone is played - this is required for all
+    // the VoIP applications which are not using the telecom API.
+    if ((remote_direction == le_audio::types::kLeAudioDirectionSink) &&
+        local_metadata_context_types_.source.test(
+            LeAudioContextType::RINGTONE)) {
+      /* Simulate, we are already in the call. Sending RINGTONE when there is
+       * no incoming call to accept or reject on TBS could confuse the remote
+       * device and interrupt the stream establish procedure.
+       */
+      if (!IsInCall()) {
+        SetInVoipCall(true);
+      }
+    } else if (IsInVoipCall()) {
+      SetInVoipCall(false);
+    }
+
     /* Make sure we have CONVERSATIONAL when in a call and it is not mixed
      * with any other bidirectional context
      */
-    if (in_call_) {
-      LOG_DEBUG(" In Call preference used.");
+    if (IsInCall() || IsInVoipCall()) {
+      LOG_DEBUG(" In Call preference used: %s, voip call: %s",
+                (IsInCall() ? "true" : "false"),
+                (IsInVoipCall() ? "true" : "false"));
       local_metadata_context_types_.sink.unset_all(kLeAudioContextAllBidir);
       local_metadata_context_types_.source.unset_all(kLeAudioContextAllBidir);
       local_metadata_context_types_.sink.set(
@@ -4634,6 +4686,12 @@
     BidirectionalPair<AudioContexts> remote_metadata = {
         .sink = local_metadata_context_types_.source,
         .source = local_metadata_context_types_.sink};
+
+    if (IsInVoipCall()) {
+      LOG_DEBUG("Unsetting RINGTONE from remote sink ");
+      remote_metadata.sink.unset(LeAudioContextType::RINGTONE);
+    }
+
     LOG_DEBUG("local_metadata_context_types_.source= %s",
               ToString(local_metadata_context_types_.source).c_str());
     LOG_DEBUG("local_metadata_context_types_.sink= %s",
@@ -5135,11 +5193,13 @@
                                false)) {
       return;
     }
+
     /* If group is inactive, phone is in call and Group is not having CIS
      * connected, notify upper layer about it, so it can decide to create SCO if
      * it is in the handover case
      */
-    if (in_call_ && active_group_id_ == bluetooth::groups::kGroupUnknown) {
+    if ((IsInCall() || IsInVoipCall()) &&
+        active_group_id_ == bluetooth::groups::kGroupUnknown) {
       callbacks_->OnGroupStatus(group_id, GroupStatus::TURNED_IDLE_DURING_CALL);
     }
   }
@@ -5313,6 +5373,7 @@
   AudioState audio_sender_state_;
   /* Keep in call state. */
   bool in_call_;
+  bool in_voip_call_;
 
   /* Reconnection mode */
   tBTM_BLE_CONN_TYPE reconnection_mode_;
diff --git a/system/bta/le_audio/le_audio_client_test.cc b/system/bta/le_audio/le_audio_client_test.cc
index 0b4a43f..7b28780 100644
--- a/system/bta/le_audio/le_audio_client_test.cc
+++ b/system/bta/le_audio/le_audio_client_test.cc
@@ -739,16 +739,18 @@
               return true;
             });
 
-    ON_CALL(mock_state_machine_, AttachToStream(_, _))
+    ON_CALL(mock_state_machine_, AttachToStream(_, _, _))
         .WillByDefault([](LeAudioDeviceGroup* group,
-                          LeAudioDevice* leAudioDevice) {
+                          LeAudioDevice* leAudioDevice,
+                          types::BidirectionalPair<std::vector<uint8_t>>
+                              ccids) {
           if (group->GetState() !=
               types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) {
             return false;
           }
 
           group->Configure(group->GetConfigurationContextType(),
-                           group->GetMetadataContexts());
+                           group->GetMetadataContexts(), ccids);
           if (!group->CigAssignCisIds(leAudioDevice)) return false;
           group->CigAssignCisConnHandlesToAses(leAudioDevice);
 
diff --git a/system/bta/le_audio/mock_state_machine.h b/system/bta/le_audio/mock_state_machine.h
index 02bad5f..5a2be11 100644
--- a/system/bta/le_audio/mock_state_machine.h
+++ b/system/bta/le_audio/mock_state_machine.h
@@ -33,7 +33,8 @@
       (override));
   MOCK_METHOD((bool), AttachToStream,
               (le_audio::LeAudioDeviceGroup * group,
-               le_audio::LeAudioDevice* leAudioDevice),
+               le_audio::LeAudioDevice* leAudioDevice,
+               le_audio::types::BidirectionalPair<std::vector<uint8_t>> ccids),
               (override));
   MOCK_METHOD((void), SuspendStream, (le_audio::LeAudioDeviceGroup * group),
               (override));
diff --git a/system/bta/le_audio/state_machine.cc b/system/bta/le_audio/state_machine.cc
index 3ead35f..8a20bc4 100644
--- a/system/bta/le_audio/state_machine.cc
+++ b/system/bta/le_audio/state_machine.cc
@@ -137,8 +137,8 @@
     log_history_ = nullptr;
   }
 
-  bool AttachToStream(LeAudioDeviceGroup* group,
-                      LeAudioDevice* leAudioDevice) override {
+  bool AttachToStream(LeAudioDeviceGroup* group, LeAudioDevice* leAudioDevice,
+                      BidirectionalPair<std::vector<uint8_t>> ccids) override {
     LOG(INFO) << __func__ << " group id: " << group->group_id_
               << " device: " << ADDRESS_TO_LOGGABLE_STR(leAudioDevice->address_);
 
@@ -155,11 +155,6 @@
       return false;
     }
 
-    BidirectionalPair<std::vector<uint8_t>> ccids = {
-        .sink = le_audio::ContentControlIdKeeper::GetInstance()->GetAllCcids(
-            group->GetMetadataContexts().sink),
-        .source = le_audio::ContentControlIdKeeper::GetInstance()->GetAllCcids(
-            group->GetMetadataContexts().source)};
     if (!group->Configure(group->GetConfigurationContextType(),
                           group->GetMetadataContexts(), ccids)) {
       LOG_ERROR(" failed to set ASE configuration");
diff --git a/system/bta/le_audio/state_machine.h b/system/bta/le_audio/state_machine.h
index e4d92e8..44a4d54 100644
--- a/system/bta/le_audio/state_machine.h
+++ b/system/bta/le_audio/state_machine.h
@@ -45,8 +45,9 @@
   static void Cleanup(void);
   static LeAudioGroupStateMachine* Get(void);
 
-  virtual bool AttachToStream(LeAudioDeviceGroup* group,
-                              LeAudioDevice* leAudioDevice) = 0;
+  virtual bool AttachToStream(
+      LeAudioDeviceGroup* group, LeAudioDevice* leAudioDevice,
+      types::BidirectionalPair<std::vector<uint8_t>> ccids) = 0;
   virtual bool StartStream(
       LeAudioDeviceGroup* group, types::LeAudioContextType context_type,
       const types::BidirectionalPair<types::AudioContexts>&
diff --git a/system/bta/le_audio/state_machine_test.cc b/system/bta/le_audio/state_machine_test.cc
index 89cf100..d83a907 100644
--- a/system/bta/le_audio/state_machine_test.cc
+++ b/system/bta/le_audio/state_machine_test.cc
@@ -3323,7 +3323,8 @@
 
   EXPECT_CALL(*mock_iso_manager_, EstablishCis(_)).Times(1);
   EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(1);
-  LeAudioGroupStateMachine::Get()->AttachToStream(group, lastDevice);
+  LeAudioGroupStateMachine::Get()->AttachToStream(
+      group, lastDevice, {.sink = {media_ccid}, .source = {}});
 
   // Check if group keeps streaming
   ASSERT_EQ(group->GetState(),
@@ -3404,8 +3405,8 @@
   LeAudioGroupStateMachine::Get()->StopStream(group);
   testing::Mock::VerifyAndClearExpectations(&mock_callbacks_);
 
-  ASSERT_FALSE(
-      LeAudioGroupStateMachine::Get()->AttachToStream(group, lastDevice));
+  ASSERT_FALSE(LeAudioGroupStateMachine::Get()->AttachToStream(
+      group, lastDevice, {.sink = {}, .source = {}}));
 }
 
 TEST_F(StateMachineTest, testReconfigureAfterLateDeviceAttached) {
@@ -3664,7 +3665,8 @@
 
   EXPECT_CALL(*mock_iso_manager_, EstablishCis(_)).Times(1);
   EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(2);
-  LeAudioGroupStateMachine::Get()->AttachToStream(group, lastDevice);
+  LeAudioGroupStateMachine::Get()->AttachToStream(
+      group, lastDevice, {.sink = {call_ccid}, .source = {call_ccid}});
 
   // Check if group keeps streaming
   ASSERT_EQ(group->GetState(),
@@ -4754,7 +4756,8 @@
 
   EXPECT_CALL(*mock_iso_manager_, EstablishCis(_)).Times(1);
   EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(0);
-  LeAudioGroupStateMachine::Get()->AttachToStream(group, lastDevice);
+  LeAudioGroupStateMachine::Get()->AttachToStream(
+      group, lastDevice, {.sink = {media_ccid}, .source = {}});
 
   // Check if group keeps streaming
   ASSERT_EQ(group->GetState(),