Add support and detection for asymmetrical ASE configurations.

Detect asymmetric configuration in the set configuration and
codec capabilities files and set approriate flag.

Bug: 358194849
Test: atest BluetoothLeAudioCodecsProviderTest
Change-Id: I8103ae402aa2d1d07c2b09717e94d7a8ac384d1f
diff --git a/bluetooth/audio/utils/Android.bp b/bluetooth/audio/utils/Android.bp
index d4968a8..6d1da63 100644
--- a/bluetooth/audio/utils/Android.bp
+++ b/bluetooth/audio/utils/Android.bp
@@ -90,15 +90,15 @@
 
 cc_test {
     name: "BluetoothLeAudioCodecsProviderTest",
-    srcs: [
-        "aidl_session/BluetoothLeAudioCodecsProvider.cpp",
-        "aidl_session/BluetoothLeAudioCodecsProviderTest.cpp",
-    ],
     defaults: [
         "latest_android_hardware_audio_common_ndk_static",
         "latest_android_hardware_bluetooth_audio_ndk_static",
         "latest_android_media_audio_common_types_ndk_static",
     ],
+    srcs: [
+        "aidl_session/BluetoothLeAudioCodecsProvider.cpp",
+        "aidl_session/BluetoothLeAudioCodecsProviderTest.cpp",
+    ],
     header_libs: [
         "libxsdc-utils",
     ],
diff --git a/bluetooth/audio/utils/aidl_session/BluetoothLeAudioAseConfigurationSettingProvider.cpp b/bluetooth/audio/utils/aidl_session/BluetoothLeAudioAseConfigurationSettingProvider.cpp
index 07e4997..5909c92 100644
--- a/bluetooth/audio/utils/aidl_session/BluetoothLeAudioAseConfigurationSettingProvider.cpp
+++ b/bluetooth/audio/utils/aidl_session/BluetoothLeAudioAseConfigurationSettingProvider.cpp
@@ -47,6 +47,8 @@
 #include <aidl/android/hardware/bluetooth/audio/Phy.h>
 #include <android-base/logging.h>
 
+#include <optional>
+
 #include "flatbuffers/idl.h"
 #include "flatbuffers/util.h"
 
@@ -561,6 +563,17 @@
   if (ase_cnt == 2) directionAseConfiguration.push_back(config);
 }
 
+// Comparing if 2 AseDirectionConfiguration is equal.
+// Configuration are copied in, so we can remove some fields for comparison
+// without affecting the result.
+bool isAseConfigurationEqual(AseDirectionConfiguration cfg_a,
+                             AseDirectionConfiguration cfg_b) {
+  // Remove unneeded fields when comparing.
+  cfg_a.aseConfiguration.metadata = std::nullopt;
+  cfg_b.aseConfiguration.metadata = std::nullopt;
+  return cfg_a == cfg_b;
+}
+
 void AudioSetConfigurationProviderJson::PopulateAseConfigurationFromFlat(
     const le_audio::AudioSetConfiguration* flat_cfg,
     std::vector<const le_audio::CodecConfiguration*>* codec_cfgs,
@@ -569,7 +582,7 @@
     std::vector<std::optional<AseDirectionConfiguration>>&
         sourceAseConfiguration,
     std::vector<std::optional<AseDirectionConfiguration>>& sinkAseConfiguration,
-    ConfigurationFlags& /*configurationFlags*/) {
+    ConfigurationFlags& configurationFlags) {
   if (flat_cfg == nullptr) {
     LOG(ERROR) << "flat_cfg cannot be null";
     return;
@@ -636,17 +649,41 @@
                          sourceAseConfiguration, location);
       }
     }
-  } else {
-    if (codec_cfg == nullptr) {
-      LOG(ERROR) << "No codec config matching key " << codec_config_key.c_str()
-                 << " found";
+
+    // After putting all subconfig, check if it's an asymmetric configuration
+    // and populate information for ConfigurationFlags
+    if (!sinkAseConfiguration.empty() && !sourceAseConfiguration.empty()) {
+      if (sinkAseConfiguration.size() == sourceAseConfiguration.size()) {
+        for (int i = 0; i < sinkAseConfiguration.size(); ++i) {
+          if (sinkAseConfiguration[i].has_value() !=
+              sourceAseConfiguration[i].has_value()) {
+            // Different configuration: one is not empty and other is.
+            configurationFlags.bitmask |=
+                ConfigurationFlags::ALLOW_ASYMMETRIC_CONFIGURATIONS;
+          } else if (sinkAseConfiguration[i].has_value()) {
+            // Both is not empty, comparing inner fields:
+            if (!isAseConfigurationEqual(sinkAseConfiguration[i].value(),
+                                         sourceAseConfiguration[i].value())) {
+              configurationFlags.bitmask |=
+                  ConfigurationFlags::ALLOW_ASYMMETRIC_CONFIGURATIONS;
+            }
+          }
+        }
+      } else {
+        // Different number of ASE, is a different configuration.
+        configurationFlags.bitmask |=
+            ConfigurationFlags::ALLOW_ASYMMETRIC_CONFIGURATIONS;
+      }
     } else {
-      LOG(ERROR) << "Configuration '" << flat_cfg->name()->c_str()
-                 << "' has no valid subconfigurations.";
+      if (codec_cfg == nullptr) {
+        LOG(ERROR) << "No codec config matching key "
+                   << codec_config_key.c_str() << " found";
+      } else {
+        LOG(ERROR) << "Configuration '" << flat_cfg->name()->c_str()
+                   << "' has no valid subconfigurations.";
+      }
     }
   }
-
-  // TODO: Populate information for ConfigurationFlags
 }
 
 bool AudioSetConfigurationProviderJson::LoadConfigurationsFromFiles(
diff --git a/bluetooth/audio/utils/aidl_session/BluetoothLeAudioCodecsProvider.cpp b/bluetooth/audio/utils/aidl_session/BluetoothLeAudioCodecsProvider.cpp
index 59c43a4..a96df52 100644
--- a/bluetooth/audio/utils/aidl_session/BluetoothLeAudioCodecsProvider.cpp
+++ b/bluetooth/audio/utils/aidl_session/BluetoothLeAudioCodecsProvider.cpp
@@ -14,10 +14,13 @@
  * limitations under the License.
  */
 
+#include <optional>
 #include <set>
 
 #include "aidl/android/hardware/bluetooth/audio/ChannelMode.h"
 #include "aidl/android/hardware/bluetooth/audio/CodecId.h"
+#include "aidl/android/hardware/bluetooth/audio/CodecInfo.h"
+#include "aidl/android/hardware/bluetooth/audio/ConfigurationFlags.h"
 #include "aidl_android_hardware_bluetooth_audio_setting_enums.h"
 #define LOG_TAG "BTAudioCodecsProviderAidl"
 
@@ -52,6 +55,26 @@
   return le_audio_offload_setting;
 }
 
+void add_flag(CodecInfo& codec_info, int32_t bitmask) {
+  auto& transport =
+      codec_info.transport.get<CodecInfo::Transport::Tag::leAudio>();
+  if (!transport.flags.has_value()) transport.flags = ConfigurationFlags();
+  transport.flags->bitmask |= bitmask;
+}
+
+// Compare 2 codec info to see if they are equal.
+// Currently only compare bitdepth, frameDurationUs and samplingFrequencyHz
+bool is_equal(CodecInfo& codec_info_a, CodecInfo& codec_info_b) {
+  auto& transport_a =
+      codec_info_a.transport.get<CodecInfo::Transport::Tag::leAudio>();
+  auto& transport_b =
+      codec_info_b.transport.get<CodecInfo::Transport::Tag::leAudio>();
+  return codec_info_a.name == codec_info_b.name &&
+         transport_a.bitdepth == transport_b.bitdepth &&
+         transport_a.frameDurationUs == transport_b.frameDurationUs &&
+         transport_a.samplingFrequencyHz == transport_b.samplingFrequencyHz;
+}
+
 std::unordered_map<SessionType, std::vector<CodecInfo>>
 BluetoothLeAudioCodecsProvider::GetLeAudioCodecInfo(
     const std::optional<setting::LeAudioOffloadSetting>&
@@ -111,6 +134,9 @@
     codec_info.transport =
         CodecInfo::Transport::make<CodecInfo::Transport::Tag::leAudio>();
 
+    // Add low latency support by default
+    add_flag(codec_info, ConfigurationFlags::LOW_LATENCY);
+
     // Mapping codec configuration information
     auto& transport =
         codec_info.transport.get<CodecInfo::Transport::Tag::leAudio>();
@@ -152,6 +178,25 @@
     }
   }
 
+  // Goes through a list of scenarios and detect asymmetrical config using
+  // codecConfiguration name.
+  for (auto& s : supported_scenarios_) {
+    if (s.hasEncode() && s.hasDecode() &&
+        config_codec_info_map_.count(s.getEncode()) &&
+        config_codec_info_map_.count(s.getDecode())) {
+      // Check if it's actually using the different codec
+      auto& encode_codec_info = config_codec_info_map_[s.getEncode()];
+      auto& decode_codec_info = config_codec_info_map_[s.getDecode()];
+      if (!is_equal(encode_codec_info, decode_codec_info)) {
+        // Change both x and y to become asymmetrical
+        add_flag(encode_codec_info,
+                 ConfigurationFlags::ALLOW_ASYMMETRIC_CONFIGURATIONS);
+        add_flag(decode_codec_info,
+                 ConfigurationFlags::ALLOW_ASYMMETRIC_CONFIGURATIONS);
+      }
+    }
+  }
+
   // Goes through every scenario, deduplicate configuration, skip the invalid
   // config references (e.g. the "invalid" entries in the xml file).
   std::set<std::string> encoding_config, decoding_config, broadcast_config;
diff --git a/bluetooth/audio/utils/aidl_session/BluetoothLeAudioCodecsProviderTest.cpp b/bluetooth/audio/utils/aidl_session/BluetoothLeAudioCodecsProviderTest.cpp
index c47f7d5..6338e11 100644
--- a/bluetooth/audio/utils/aidl_session/BluetoothLeAudioCodecsProviderTest.cpp
+++ b/bluetooth/audio/utils/aidl_session/BluetoothLeAudioCodecsProviderTest.cpp
@@ -20,10 +20,16 @@
 #include <tuple>
 
 #include "BluetoothLeAudioCodecsProvider.h"
+#include "aidl/android/hardware/bluetooth/audio/CodecInfo.h"
+#include "aidl/android/hardware/bluetooth/audio/ConfigurationFlags.h"
+#include "aidl/android/hardware/bluetooth/audio/SessionType.h"
 
 using aidl::android::hardware::bluetooth::audio::BluetoothLeAudioCodecsProvider;
+using aidl::android::hardware::bluetooth::audio::CodecInfo;
+using aidl::android::hardware::bluetooth::audio::ConfigurationFlags;
 using aidl::android::hardware::bluetooth::audio::
     LeAudioCodecCapabilitiesSetting;
+using aidl::android::hardware::bluetooth::audio::SessionType;
 using aidl::android::hardware::bluetooth::audio::setting::AudioLocation;
 using aidl::android::hardware::bluetooth::audio::setting::CodecConfiguration;
 using aidl::android::hardware::bluetooth::audio::setting::
@@ -51,15 +57,30 @@
 static const Scenario kValidBroadcastScenario(
     std::nullopt, std::nullopt, std::make_optional("BcastStereo_16_2"));
 
+static const Scenario kValidAsymmetricScenario(
+    std::make_optional("OneChanStereo_32_1"),
+    std::make_optional("OneChanStereo_16_1"), std::nullopt);
+
 // Configuration
 static const Configuration kValidConfigOneChanStereo_16_1(
     std::make_optional("OneChanStereo_16_1"), std::make_optional("LC3_16k_1"),
     std::make_optional("STEREO_ONE_CIS_PER_DEVICE"));
+
+static const Configuration kValidConfigOneChanStereo_32_1(
+    std::make_optional("OneChanStereo_32_1"), std::make_optional("LC3_32k_1"),
+    std::make_optional("STEREO_ONE_CIS_PER_DEVICE"));
+
 // CodecConfiguration
 static const CodecConfiguration kValidCodecLC3_16k_1(
     std::make_optional("LC3_16k_1"), std::make_optional(CodecType::LC3),
     std::nullopt, std::make_optional(16000), std::make_optional(7500),
     std::make_optional(30), std::nullopt);
+
+static const CodecConfiguration kValidCodecLC3_32k_1(
+    std::make_optional("LC3_32k_1"), std::make_optional(CodecType::LC3),
+    std::nullopt, std::make_optional(32000), std::make_optional(7500),
+    std::make_optional(30), std::nullopt);
+
 // StrategyConfiguration
 static const StrategyConfiguration kValidStrategyStereoOneCis(
     std::make_optional("STEREO_ONE_CIS_PER_DEVICE"),
@@ -181,6 +202,17 @@
             kValidStrategyStereoOneCisBoth, kValidStrategyStereoTwoCisBoth,
             kValidStrategyMonoOneCisBoth, kValidStrategyBroadcastStereoBoth})};
 
+// Define some valid asymmetric scenario list
+static const std::vector<ScenarioList> kValidAsymmetricScenarioList = {
+    ScenarioList(std::vector<Scenario>{kValidAsymmetricScenario})};
+static const std::vector<ConfigurationList> kValidAsymmetricConfigurationList =
+    {ConfigurationList(std::vector<Configuration>{
+        kValidConfigOneChanStereo_16_1, kValidConfigOneChanStereo_32_1})};
+static const std::vector<CodecConfigurationList>
+    kValidAsymmetricCodecConfigurationList = {
+        CodecConfigurationList(std::vector<CodecConfiguration>{
+            kValidCodecLC3_16k_1, kValidCodecLC3_32k_1})};
+
 class BluetoothLeAudioCodecsProviderTest
     : public ::testing::TestWithParam<OffloadSetting> {
  public:
@@ -227,6 +259,19 @@
     return le_audio_codec_capabilities;
   }
 
+  std::unordered_map<SessionType, std::vector<CodecInfo>>
+  RunCodecInfoTestCase() {
+    auto& [scenario_lists, configuration_lists, codec_configuration_lists,
+           strategy_configuration_lists] = GetParam();
+    LeAudioOffloadSetting le_audio_offload_setting(
+        scenario_lists, configuration_lists, codec_configuration_lists,
+        strategy_configuration_lists);
+    auto le_audio_codec_capabilities =
+        BluetoothLeAudioCodecsProvider::GetLeAudioCodecInfo(
+            std::make_optional(le_audio_offload_setting));
+    return le_audio_codec_capabilities;
+  }
+
  private:
   static inline OffloadSetting CreateTestCase(
       const ScenarioList& scenario_list,
@@ -392,6 +437,39 @@
   ASSERT_TRUE(!le_audio_codec_capabilities.empty());
 }
 
+class ComposeLeAudioAymmetricCodecInfoTest
+    : public BluetoothLeAudioCodecsProviderTest {
+ public:
+};
+
+TEST_P(ComposeLeAudioAymmetricCodecInfoTest, AsymmetricCodecInfoNotEmpty) {
+  Initialize();
+  auto le_audio_codec_info_map = RunCodecInfoTestCase();
+  ASSERT_TRUE(!le_audio_codec_info_map.empty());
+  // Check true asymmetric codec info
+  ASSERT_TRUE(!le_audio_codec_info_map
+                   [SessionType::LE_AUDIO_HARDWARE_OFFLOAD_ENCODING_DATAPATH]
+                       .empty());
+  ASSERT_TRUE(!le_audio_codec_info_map
+                   [SessionType::LE_AUDIO_HARDWARE_OFFLOAD_DECODING_DATAPATH]
+                       .empty());
+  auto required_flag = ConfigurationFlags();
+  required_flag.bitmask |= ConfigurationFlags::ALLOW_ASYMMETRIC_CONFIGURATIONS;
+
+  auto codec_info = le_audio_codec_info_map
+      [SessionType::LE_AUDIO_HARDWARE_OFFLOAD_ENCODING_DATAPATH][0];
+  ASSERT_EQ(codec_info.transport.getTag(), CodecInfo::Transport::Tag::leAudio);
+  auto& transport =
+      codec_info.transport.get<CodecInfo::Transport::Tag::leAudio>();
+  ASSERT_EQ(transport.flags, std::make_optional(required_flag));
+
+  codec_info = le_audio_codec_info_map
+      [SessionType::LE_AUDIO_HARDWARE_OFFLOAD_DECODING_DATAPATH][0];
+  ASSERT_EQ(codec_info.transport.getTag(), CodecInfo::Transport::Tag::leAudio);
+  transport = codec_info.transport.get<CodecInfo::Transport::Tag::leAudio>();
+  ASSERT_EQ(transport.flags, std::make_optional(required_flag));
+}
+
 GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(GetScenariosTest);
 INSTANTIATE_TEST_SUITE_P(
     BluetoothLeAudioCodecsProviderTest, GetScenariosTest,
@@ -434,6 +512,15 @@
         kValidScenarioList, kValidConfigurationList,
         kValidCodecConfigurationList, kValidStrategyConfigurationList)));
 
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(
+    ComposeLeAudioAymmetricCodecInfoTest);
+INSTANTIATE_TEST_SUITE_P(
+    BluetoothLeAudioCodecsProviderTest, ComposeLeAudioAymmetricCodecInfoTest,
+    ::testing::ValuesIn(BluetoothLeAudioCodecsProviderTest::CreateTestCases(
+        kValidAsymmetricScenarioList, kValidAsymmetricConfigurationList,
+        kValidAsymmetricCodecConfigurationList,
+        kValidStrategyConfigurationList)));
+
 int main(int argc, char** argv) {
   ::testing::InitGoogleTest(&argc, argv);
   return RUN_ALL_TESTS();