Add missing Cap Settings aidl support and helper

Bug: 307310023
Test: atest AudioCoreConfig

Change-Id: I08c71ce9a67f065df36f71c0602e2c9e4e97ae6b
Signed-off-by: François Gaffie <francois.gaffie@renault.com>
diff --git a/audio/aidl/default/CapEngineConfigXmlConverter.cpp b/audio/aidl/default/CapEngineConfigXmlConverter.cpp
new file mode 100644
index 0000000..8210664
--- /dev/null
+++ b/audio/aidl/default/CapEngineConfigXmlConverter.cpp
@@ -0,0 +1,386 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "AHAL_Config"
+
+#include <aidl/android/media/audio/common/AudioProductStrategyType.h>
+#include <android-base/logging.h>
+#include <media/AidlConversionCppNdk.h>
+#include <media/TypeConverter.h>
+#include <media/convert.h>
+#include <utils/FastStrcmp.h>
+
+#include "core-impl/CapEngineConfigXmlConverter.h"
+#include "core-impl/XsdcConversion.h"
+
+using aidl::android::hardware::audio::common::iequals;
+using aidl::android::media::audio::common::AudioDeviceAddress;
+using aidl::android::media::audio::common::AudioDeviceDescription;
+using aidl::android::media::audio::common::AudioHalCapConfiguration;
+using aidl::android::media::audio::common::AudioHalCapCriterionV2;
+using aidl::android::media::audio::common::AudioHalCapDomain;
+using aidl::android::media::audio::common::AudioHalCapParameter;
+using aidl::android::media::audio::common::AudioHalCapRule;
+using aidl::android::media::audio::common::AudioPolicyForceUse;
+using aidl::android::media::audio::common::AudioSource;
+using aidl::android::media::audio::common::AudioStreamType;
+
+using ::android::BAD_VALUE;
+using ::android::base::unexpected;
+using ::android::utilities::convertTo;
+
+namespace eng_xsd = android::audio::policy::capengine::configuration;
+
+namespace aidl::android::hardware::audio::core::internal {
+
+static constexpr const char* gStrategiesParameter = "product_strategies";
+static constexpr const char* gInputSourcesParameter = "input_sources";
+static constexpr const char* gStreamsParameter = "streams";
+static constexpr const char* gOutputDevicesParameter = "selected_output_devices";
+static constexpr const char* gOutputDeviceAddressParameter = "device_address";
+static constexpr const char* gStrategyPrefix = "vx_";
+static constexpr const char* gLegacyOutputDevicePrefix = "AUDIO_DEVICE_OUT_";
+static constexpr const char* gLegacyInputDevicePrefix = "AUDIO_DEVICE_IN_";
+static constexpr const char* gLegacyStreamPrefix = "AUDIO_STREAM_";
+static constexpr const char* gLegacySourcePrefix = "AUDIO_SOURCE_";
+
+std::optional<std::vector<std::optional<AudioHalCapDomain>>>&
+CapEngineConfigXmlConverter::getAidlCapEngineConfig() {
+    return mAidlCapDomains;
+}
+
+ConversionResult<AudioHalCapRule::CriterionRule> convertCriterionRuleToAidl(
+        const eng_xsd::SelectionCriterionRuleType& xsdcRule) {
+    using Tag = AudioHalCapCriterionV2::Tag;
+    AudioHalCapRule::CriterionRule rule{};
+    std::string criterionName = xsdcRule.getSelectionCriterion();
+    std::string criterionValue = xsdcRule.getValue();
+    if (iequals(criterionName, toString(Tag::availableInputDevices))) {
+        rule.criterion = AudioHalCapCriterionV2::make<Tag::availableInputDevices>();
+        rule.criterionTypeValue =
+                VALUE_OR_RETURN(convertDeviceTypeToAidl(gLegacyInputDevicePrefix + criterionValue));
+    } else if (iequals(criterionName, toString(Tag::availableOutputDevices))) {
+        rule.criterion = AudioHalCapCriterionV2::make<Tag::availableOutputDevices>();
+        rule.criterionTypeValue = VALUE_OR_RETURN(
+                convertDeviceTypeToAidl(gLegacyOutputDevicePrefix + criterionValue));
+    } else if (iequals(criterionName, toString(Tag::availableInputDevicesAddresses))) {
+        rule.criterion = AudioHalCapCriterionV2::make<Tag::availableInputDevicesAddresses>();
+        rule.criterionTypeValue =
+                AudioDeviceAddress::make<AudioDeviceAddress::Tag::id>(criterionValue);
+    } else if (iequals(criterionName, toString(Tag::availableOutputDevicesAddresses))) {
+        rule.criterion = AudioHalCapCriterionV2::make<Tag::availableOutputDevicesAddresses>();
+        rule.criterionTypeValue =
+                AudioDeviceAddress::make<AudioDeviceAddress::Tag::id>(criterionValue);
+    } else if (iequals(criterionName, toString(Tag::telephonyMode))) {
+        rule.criterion = AudioHalCapCriterionV2::make<Tag::telephonyMode>();
+        rule.criterionTypeValue = VALUE_OR_RETURN(convertTelephonyModeToAidl(criterionValue));
+    } else if (!fastcmp<strncmp>(criterionName.c_str(), kXsdcForceConfigForUse,
+            strlen(kXsdcForceConfigForUse))) {
+        rule.criterion = AudioHalCapCriterionV2::make<Tag::forceConfigForUse>(
+                VALUE_OR_RETURN(convertForceUseCriterionToAidl(criterionName)));
+        rule.criterionTypeValue = VALUE_OR_RETURN(convertForcedConfigToAidl(criterionValue));
+    } else {
+        LOG(ERROR) << __func__ << " unrecognized criterion " << criterionName;
+        return unexpected(BAD_VALUE);
+    }
+    if (xsdcRule.getMatchesWhen() == eng_xsd::MatchesWhenEnum::Excludes) {
+        rule.matchingRule = AudioHalCapRule::MatchingRule::EXCLUDES;
+    } else if (xsdcRule.getMatchesWhen() == eng_xsd::MatchesWhenEnum::Includes) {
+        rule.matchingRule = AudioHalCapRule::MatchingRule::INCLUDES;
+    } else if (xsdcRule.getMatchesWhen() == eng_xsd::MatchesWhenEnum::Is) {
+        rule.matchingRule = AudioHalCapRule::MatchingRule::IS;
+    } else if (xsdcRule.getMatchesWhen() == eng_xsd::MatchesWhenEnum::IsNot) {
+        rule.matchingRule = AudioHalCapRule::MatchingRule::IS_NOT;
+    } else {
+        LOG(ERROR) << "Unsupported match when rule.";
+        return unexpected(BAD_VALUE);
+    }
+    return rule;
+}
+
+ConversionResult<AudioHalCapRule> convertRule(const eng_xsd::CompoundRuleType& xsdcCompoundRule) {
+    AudioHalCapRule rule{};
+    bool isPreviousCompoundRule = true;
+    if (xsdcCompoundRule.getType() == eng_xsd::TypeEnum::Any) {
+        rule.compoundRule = AudioHalCapRule::CompoundRule::ANY;
+    } else if (xsdcCompoundRule.getType() == eng_xsd::TypeEnum::All) {
+        rule.compoundRule = AudioHalCapRule::CompoundRule::ALL;
+    } else {
+        LOG(ERROR) << "Unsupported compound rule type.";
+        return unexpected(BAD_VALUE);
+    }
+    for (const auto& childXsdcCoumpoundRule : xsdcCompoundRule.getCompoundRule_optional()) {
+        if (childXsdcCoumpoundRule.hasCompoundRule_optional()) {
+            rule.nestedRules.push_back(VALUE_OR_FATAL(convertRule(childXsdcCoumpoundRule)));
+        } else if (childXsdcCoumpoundRule.hasSelectionCriterionRule_optional()) {
+            rule.nestedRules.push_back(VALUE_OR_FATAL(convertRule(childXsdcCoumpoundRule)));
+        }
+    }
+    if (xsdcCompoundRule.hasSelectionCriterionRule_optional()) {
+        for (const auto& xsdcRule : xsdcCompoundRule.getSelectionCriterionRule_optional()) {
+            rule.criterionRules.push_back(VALUE_OR_FATAL(convertCriterionRuleToAidl(xsdcRule)));
+        }
+    }
+    return rule;
+}
+
+ConversionResult<int> getAudioProductStrategyId(const std::string& path) {
+    std::vector<std::string> strings;
+    std::istringstream pathStream(path);
+    std::string stringToken;
+    while (getline(pathStream, stringToken, '/')) {
+        std::size_t pos = stringToken.find(gStrategyPrefix);
+        if (pos != std::string::npos) {
+            std::string strategyIdLiteral = stringToken.substr(pos + std::strlen(gStrategyPrefix));
+            int strategyId;
+            if (!convertTo(strategyIdLiteral, strategyId)) {
+                LOG(ERROR) << "Invalid strategy " << stringToken << " from path " << path;
+                return unexpected(BAD_VALUE);
+            }
+            return strategyId;
+        }
+    }
+    return unexpected(BAD_VALUE);
+}
+
+ConversionResult<AudioSource> getAudioSource(const std::string& path) {
+    std::vector<std::string> strings;
+    std::istringstream pathStream(path);
+    std::string stringToken;
+    while (getline(pathStream, stringToken, '/')) {
+        if (stringToken.find(gInputSourcesParameter) != std::string::npos) {
+            getline(pathStream, stringToken, '/');
+            std::transform(stringToken.begin(), stringToken.end(), stringToken.begin(),
+                           [](char c) { return std::toupper(c); });
+            std::string legacySourceLiteral = "AUDIO_SOURCE_" + stringToken;
+            audio_source_t legacySource;
+            if (!::android::SourceTypeConverter::fromString(legacySourceLiteral, legacySource)) {
+                LOG(ERROR) << "Invalid source " << stringToken << " from path " << path;
+                return unexpected(BAD_VALUE);
+            }
+            return legacy2aidl_audio_source_t_AudioSource(legacySource);
+        }
+    }
+    return unexpected(BAD_VALUE);
+}
+
+ConversionResult<AudioStreamType> getAudioStreamType(const std::string& path) {
+    std::vector<std::string> strings;
+    std::istringstream pathStream(path);
+    std::string stringToken;
+
+    while (getline(pathStream, stringToken, '/')) {
+        if (stringToken.find(gStreamsParameter) != std::string::npos) {
+            getline(pathStream, stringToken, '/');
+            std::transform(stringToken.begin(), stringToken.end(), stringToken.begin(),
+                           [](char c) { return std::toupper(c); });
+            std::string legacyStreamLiteral = std::string(gLegacyStreamPrefix) + stringToken;
+            audio_stream_type_t legacyStream;
+            if (!::android::StreamTypeConverter::fromString(legacyStreamLiteral, legacyStream)) {
+                LOG(ERROR) << "Invalid stream " << stringToken << " from path " << path;
+                return unexpected(BAD_VALUE);
+            }
+            return legacy2aidl_audio_stream_type_t_AudioStreamType(legacyStream);
+        }
+    }
+    return unexpected(BAD_VALUE);
+}
+
+ConversionResult<std::string> toUpperAndAppendPrefix(const std::string& capName,
+                                                     const std::string& legacyPrefix) {
+    std::string legacyName = capName;
+    std::transform(legacyName.begin(), legacyName.end(), legacyName.begin(),
+                   [](char c) { return std::toupper(c); });
+    return legacyPrefix + legacyName;
+}
+
+ConversionResult<AudioHalCapParameter> CapEngineConfigXmlConverter::convertParamToAidl(
+        const eng_xsd::ConfigurableElementSettingsType& element) {
+    const auto& path = element.getPath();
+
+    AudioHalCapParameter parameterSetting;
+    if (path.find(gStrategiesParameter) != std::string::npos) {
+        int strategyId = VALUE_OR_FATAL(getAudioProductStrategyId(path));
+        if (path.find(gOutputDevicesParameter) != std::string::npos) {
+            // Value is 1 or 0
+            if (!element.hasBitParameter_optional()) {
+                LOG(ERROR) << "Invalid strategy value type";
+                return unexpected(BAD_VALUE);
+            }
+            // Convert name to output device type
+            const auto* xsdcParam = element.getFirstBitParameter_optional();
+            std::string outputDevice = VALUE_OR_FATAL(toUpperAndAppendPrefix(
+                    eng_xsd::toString(xsdcParam->getName()), gLegacyOutputDevicePrefix));
+            audio_devices_t legacyType;
+            if (!::android::OutputDeviceConverter::fromString(outputDevice, legacyType)) {
+                LOG(ERROR) << "Invalid strategy device type " << outputDevice;
+                return unexpected(BAD_VALUE);
+            }
+            AudioDeviceDescription aidlDevice =
+                    VALUE_OR_FATAL(legacy2aidl_audio_devices_t_AudioDeviceDescription(legacyType));
+            bool isSelected;
+            if (!convertTo(xsdcParam->getValue(), isSelected)) {
+                LOG(ERROR) << "Invalid strategy device selection value " << xsdcParam->getValue();
+                return unexpected(BAD_VALUE);
+            }
+            parameterSetting =
+                    AudioHalCapParameter::StrategyDevice(aidlDevice, strategyId, isSelected);
+        } else if (path.find(gOutputDeviceAddressParameter) != std::string::npos) {
+            // Value is the address
+            if (!element.hasStringParameter_optional()) {
+                return unexpected(BAD_VALUE);
+            }
+            std::string address = element.getFirstStringParameter_optional()->getValue();
+            parameterSetting = AudioHalCapParameter::StrategyDeviceAddress(
+                    AudioDeviceAddress(address), strategyId);
+        }
+    } else if (path.find(gInputSourcesParameter) != std::string::npos) {
+        // Value is 1 or 0
+        if (!element.hasBitParameter_optional()) {
+            LOG(ERROR) << "Invalid source value type";
+            return unexpected(BAD_VALUE);
+        }
+        AudioSource audioSourceAidl = VALUE_OR_FATAL(getAudioSource(path));
+        const auto* xsdcParam = element.getFirstBitParameter_optional();
+        std::string inputDeviceLiteral = VALUE_OR_FATAL(toUpperAndAppendPrefix(
+                eng_xsd::toString(xsdcParam->getName()), gLegacyInputDevicePrefix));
+        audio_devices_t inputDeviceType;
+        if (!::android::InputDeviceConverter::fromString(inputDeviceLiteral, inputDeviceType)) {
+            LOG(ERROR) << "Invalid source device type " << inputDeviceLiteral;
+            return unexpected(BAD_VALUE);
+        }
+        AudioDeviceDescription aidlDevice =
+                VALUE_OR_FATAL(legacy2aidl_audio_devices_t_AudioDeviceDescription(inputDeviceType));
+
+        bool isSelected;
+        if (!convertTo(xsdcParam->getValue(), isSelected)) {
+            LOG(ERROR) << "Invalid source value type " << xsdcParam->getValue();
+            return unexpected(BAD_VALUE);
+        }
+        parameterSetting =
+                AudioHalCapParameter::InputSourceDevice(aidlDevice, audioSourceAidl, isSelected);
+    } else if (path.find(gStreamsParameter) != std::string::npos) {
+        AudioStreamType audioStreamAidl = VALUE_OR_FATAL(getAudioStreamType(path));
+        if (!element.hasEnumParameter_optional()) {
+            LOG(ERROR) << "Invalid stream value type";
+            return unexpected(BAD_VALUE);
+        }
+        const auto* xsdcParam = element.getFirstEnumParameter_optional();
+        std::string profileLiteral =
+                VALUE_OR_FATAL(toUpperAndAppendPrefix(xsdcParam->getValue(), gLegacyStreamPrefix));
+        audio_stream_type_t profileLegacyStream;
+        if (!::android::StreamTypeConverter::fromString(profileLiteral, profileLegacyStream)) {
+            LOG(ERROR) << "Invalid stream value " << profileLiteral;
+            return unexpected(BAD_VALUE);
+        }
+        AudioStreamType profileStreamAidl = VALUE_OR_FATAL(
+                legacy2aidl_audio_stream_type_t_AudioStreamType(profileLegacyStream));
+        parameterSetting =
+                AudioHalCapParameter::StreamVolumeProfile(audioStreamAidl, profileStreamAidl);
+    }
+    return parameterSetting;
+}
+
+ConversionResult<std::vector<AudioHalCapParameter>>
+CapEngineConfigXmlConverter::convertSettingToAidl(
+        const eng_xsd::SettingsType::Configuration& xsdcSetting) {
+    std::vector<AudioHalCapParameter> aidlCapParameterSettings;
+    for (const auto& element : xsdcSetting.getConfigurableElement()) {
+        aidlCapParameterSettings.push_back(VALUE_OR_FATAL(convertParamToAidl(element)));
+    }
+    return aidlCapParameterSettings;
+}
+
+ConversionResult<AudioHalCapConfiguration> CapEngineConfigXmlConverter::convertConfigurationToAidl(
+        const eng_xsd::ConfigurationsType::Configuration& xsdcConfiguration,
+        const eng_xsd::SettingsType::Configuration& xsdcSettingConfiguration) {
+    AudioHalCapConfiguration aidlCapConfiguration;
+    aidlCapConfiguration.name = xsdcConfiguration.getName();
+    if (xsdcConfiguration.hasCompoundRule()) {
+        if (xsdcConfiguration.getCompoundRule().size() != 1) {
+            return unexpected(BAD_VALUE);
+        }
+        aidlCapConfiguration.rule =
+                VALUE_OR_FATAL(convertRule(xsdcConfiguration.getCompoundRule()[0]));
+        aidlCapConfiguration.parameterSettings =
+                VALUE_OR_FATAL(convertSettingToAidl(xsdcSettingConfiguration));
+    }
+    return aidlCapConfiguration;
+}
+
+ConversionResult<eng_xsd::SettingsType::Configuration> getConfigurationByName(
+        const std::string& name, const std::vector<eng_xsd::SettingsType>& xsdcSettingsVec) {
+    for (const auto& xsdcSettings : xsdcSettingsVec) {
+        for (const auto& xsdcConfiguration : xsdcSettings.getConfiguration()) {
+            if (xsdcConfiguration.getName() == name) {
+                return xsdcConfiguration;
+            }
+        }
+    }
+    LOG(ERROR) << __func__ << " failed to find configuration " << name;
+    return unexpected(BAD_VALUE);
+}
+
+ConversionResult<std::vector<AudioHalCapConfiguration>>
+CapEngineConfigXmlConverter::convertConfigurationsToAidl(
+        const std::vector<eng_xsd::ConfigurationsType>& xsdcConfigurationsVec,
+        const std::vector<eng_xsd::SettingsType>& xsdcSettingsVec) {
+    if (xsdcConfigurationsVec.empty() || xsdcSettingsVec.empty()) {
+        LOG(ERROR) << __func__ << " empty configurations/settings";
+        return unexpected(BAD_VALUE);
+    }
+    std::vector<AudioHalCapConfiguration> aidlConfigurations;
+    for (const auto& xsdcConfigurations : xsdcConfigurationsVec) {
+        for (const auto& xsdcConfiguration : xsdcConfigurations.getConfiguration()) {
+            auto xsdcSettingConfiguration = VALUE_OR_FATAL(
+                    getConfigurationByName(xsdcConfiguration.getName(), xsdcSettingsVec));
+            aidlConfigurations.push_back(VALUE_OR_FATAL(
+                    convertConfigurationToAidl(xsdcConfiguration, xsdcSettingConfiguration)));
+        }
+    }
+    return aidlConfigurations;
+}
+
+ConversionResult<AudioHalCapDomain> CapEngineConfigXmlConverter::convertConfigurableDomainToAidl(
+        const eng_xsd::ConfigurableDomainType& xsdcConfigurableDomain) {
+    AudioHalCapDomain aidlConfigurableDomain;
+
+    aidlConfigurableDomain.name = xsdcConfigurableDomain.getName();
+    if (xsdcConfigurableDomain.hasSequenceAware() && xsdcConfigurableDomain.getSequenceAware()) {
+        LOG(ERROR) << "sequence aware not supported.";
+        return unexpected(BAD_VALUE);
+    }
+    if (xsdcConfigurableDomain.hasConfigurations() && xsdcConfigurableDomain.hasSettings()) {
+        aidlConfigurableDomain.configurations = VALUE_OR_FATAL(convertConfigurationsToAidl(
+                xsdcConfigurableDomain.getConfigurations(), xsdcConfigurableDomain.getSettings()));
+    }
+    return aidlConfigurableDomain;
+}
+
+void CapEngineConfigXmlConverter::init() {
+    if (getXsdcConfig()->hasConfigurableDomain()) {
+        mAidlCapDomains = std::make_optional<>(VALUE_OR_FATAL(
+                (convertCollectionToAidlOptionalValues<eng_xsd::ConfigurableDomainType,
+                                                       AudioHalCapDomain>(
+                        getXsdcConfig()->getConfigurableDomain(),
+                        std::bind(&CapEngineConfigXmlConverter::convertConfigurableDomainToAidl,
+                                  this, std::placeholders::_1)))));
+    } else {
+        mAidlCapDomains = std::nullopt;
+    }
+}
+
+}  // namespace aidl::android::hardware::audio::core::internal