| /* |
| * Copyright (C) 2022 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. |
| */ |
| |
| #include <fcntl.h> |
| #include <inttypes.h> |
| #include <unistd.h> |
| #include <functional> |
| #include <unordered_map> |
| |
| #include <aidl/android/media/audio/common/AudioFlag.h> |
| #include <aidl/android/media/audio/common/AudioHalEngineConfig.h> |
| #include <aidl/android/media/audio/common/AudioProductStrategyType.h> |
| |
| #include "core-impl/EngineConfigXmlConverter.h" |
| |
| using aidl::android::media::audio::common::AudioAttributes; |
| using aidl::android::media::audio::common::AudioContentType; |
| using aidl::android::media::audio::common::AudioFlag; |
| using aidl::android::media::audio::common::AudioHalAttributesGroup; |
| using aidl::android::media::audio::common::AudioHalCapCriterion; |
| using aidl::android::media::audio::common::AudioHalCapCriterionType; |
| using aidl::android::media::audio::common::AudioHalEngineConfig; |
| using aidl::android::media::audio::common::AudioHalProductStrategy; |
| using aidl::android::media::audio::common::AudioHalVolumeCurve; |
| using aidl::android::media::audio::common::AudioHalVolumeGroup; |
| using aidl::android::media::audio::common::AudioProductStrategyType; |
| using aidl::android::media::audio::common::AudioSource; |
| using aidl::android::media::audio::common::AudioStreamType; |
| using aidl::android::media::audio::common::AudioUsage; |
| |
| namespace xsd = android::audio::policy::engine::configuration; |
| |
| namespace aidl::android::hardware::audio::core::internal { |
| |
| /** |
| * Valid curve points take the form "<index>,<attenuationMb>", where the index |
| * must be in the range [0,100]. kInvalidCurvePointIndex is used to indicate |
| * that a point was formatted incorrectly (e.g. if a vendor accidentally typed a |
| * '.' instead of a ',' in their XML)-- using such a curve point will result in |
| * failed VTS tests. |
| */ |
| static const int8_t kInvalidCurvePointIndex = -1; |
| |
| void EngineConfigXmlConverter::initProductStrategyMap() { |
| #define STRATEGY_ENTRY(name) {"STRATEGY_" #name, static_cast<int>(AudioProductStrategyType::name)} |
| |
| mProductStrategyMap = {STRATEGY_ENTRY(MEDIA), |
| STRATEGY_ENTRY(PHONE), |
| STRATEGY_ENTRY(SONIFICATION), |
| STRATEGY_ENTRY(SONIFICATION_RESPECTFUL), |
| STRATEGY_ENTRY(DTMF), |
| STRATEGY_ENTRY(ENFORCED_AUDIBLE), |
| STRATEGY_ENTRY(TRANSMITTED_THROUGH_SPEAKER), |
| STRATEGY_ENTRY(ACCESSIBILITY)}; |
| #undef STRATEGY_ENTRY |
| } |
| |
| int EngineConfigXmlConverter::convertProductStrategyNameToAidl( |
| const std::string& xsdcProductStrategyName) { |
| const auto [it, success] = mProductStrategyMap.insert( |
| std::make_pair(xsdcProductStrategyName, mNextVendorStrategy)); |
| if (success) { |
| mNextVendorStrategy++; |
| } |
| return it->second; |
| } |
| |
| bool isDefaultAudioAttributes(const AudioAttributes& attributes) { |
| return ((attributes.contentType == AudioContentType::UNKNOWN) && |
| (attributes.usage == AudioUsage::UNKNOWN) && |
| (attributes.source == AudioSource::DEFAULT) && (attributes.flags == 0) && |
| (attributes.tags.empty())); |
| } |
| |
| AudioAttributes EngineConfigXmlConverter::convertAudioAttributesToAidl( |
| const xsd::AttributesType& xsdcAudioAttributes) { |
| if (xsdcAudioAttributes.hasAttributesRef()) { |
| if (mAttributesReferenceMap.empty()) { |
| mAttributesReferenceMap = |
| generateReferenceMap<xsd::AttributesRef, xsd::AttributesRefType>( |
| getXsdcConfig()->getAttributesRef()); |
| } |
| return convertAudioAttributesToAidl( |
| *(mAttributesReferenceMap.at(xsdcAudioAttributes.getAttributesRef()) |
| .getFirstAttributes())); |
| } |
| AudioAttributes aidlAudioAttributes; |
| if (xsdcAudioAttributes.hasContentType()) { |
| aidlAudioAttributes.contentType = static_cast<AudioContentType>( |
| xsdcAudioAttributes.getFirstContentType()->getValue()); |
| } |
| if (xsdcAudioAttributes.hasUsage()) { |
| aidlAudioAttributes.usage = |
| static_cast<AudioUsage>(xsdcAudioAttributes.getFirstUsage()->getValue()); |
| } |
| if (xsdcAudioAttributes.hasSource()) { |
| aidlAudioAttributes.source = |
| static_cast<AudioSource>(xsdcAudioAttributes.getFirstSource()->getValue()); |
| } |
| if (xsdcAudioAttributes.hasFlags()) { |
| std::vector<xsd::FlagType> xsdcFlagTypeVec = |
| xsdcAudioAttributes.getFirstFlags()->getValue(); |
| for (const xsd::FlagType& xsdcFlagType : xsdcFlagTypeVec) { |
| if (xsdcFlagType != xsd::FlagType::AUDIO_FLAG_NONE) { |
| aidlAudioAttributes.flags |= 1 << (static_cast<int>(xsdcFlagType) - 1); |
| } |
| } |
| } |
| if (xsdcAudioAttributes.hasBundle()) { |
| const xsd::BundleType* xsdcBundle = xsdcAudioAttributes.getFirstBundle(); |
| aidlAudioAttributes.tags[0] = xsdcBundle->getKey() + "=" + xsdcBundle->getValue(); |
| } |
| if (isDefaultAudioAttributes(aidlAudioAttributes)) { |
| mDefaultProductStrategyId = std::optional<int>{-1}; |
| } |
| return aidlAudioAttributes; |
| } |
| |
| AudioHalAttributesGroup EngineConfigXmlConverter::convertAttributesGroupToAidl( |
| const xsd::AttributesGroup& xsdcAttributesGroup) { |
| AudioHalAttributesGroup aidlAttributesGroup; |
| static const int kStreamTypeEnumOffset = |
| static_cast<int>(xsd::Stream::AUDIO_STREAM_VOICE_CALL) - |
| static_cast<int>(AudioStreamType::VOICE_CALL); |
| aidlAttributesGroup.streamType = static_cast<AudioStreamType>( |
| static_cast<int>(xsdcAttributesGroup.getStreamType()) - kStreamTypeEnumOffset); |
| aidlAttributesGroup.volumeGroupName = xsdcAttributesGroup.getVolumeGroup(); |
| if (xsdcAttributesGroup.hasAttributes_optional()) { |
| aidlAttributesGroup.attributes = |
| convertCollectionToAidl<xsd::AttributesType, AudioAttributes>( |
| xsdcAttributesGroup.getAttributes_optional(), |
| std::bind(&EngineConfigXmlConverter::convertAudioAttributesToAidl, this, |
| std::placeholders::_1)); |
| } else if (xsdcAttributesGroup.hasContentType_optional() || |
| xsdcAttributesGroup.hasUsage_optional() || |
| xsdcAttributesGroup.hasSource_optional() || |
| xsdcAttributesGroup.hasFlags_optional() || |
| xsdcAttributesGroup.hasBundle_optional()) { |
| aidlAttributesGroup.attributes.push_back(convertAudioAttributesToAidl(xsd::AttributesType( |
| xsdcAttributesGroup.getContentType_optional(), |
| xsdcAttributesGroup.getUsage_optional(), xsdcAttributesGroup.getSource_optional(), |
| xsdcAttributesGroup.getFlags_optional(), xsdcAttributesGroup.getBundle_optional(), |
| std::nullopt))); |
| |
| } else { |
| // do nothing; |
| // TODO: check if this is valid or if we should treat as an error. |
| // Currently, attributes are not mandatory in schema, but an AttributesGroup |
| // without attributes does not make much sense. |
| } |
| return aidlAttributesGroup; |
| } |
| |
| AudioHalProductStrategy EngineConfigXmlConverter::convertProductStrategyToAidl( |
| const xsd::ProductStrategies::ProductStrategy& xsdcProductStrategy) { |
| AudioHalProductStrategy aidlProductStrategy; |
| |
| aidlProductStrategy.id = convertProductStrategyNameToAidl(xsdcProductStrategy.getName()); |
| |
| if (xsdcProductStrategy.hasAttributesGroup()) { |
| aidlProductStrategy.attributesGroups = |
| convertCollectionToAidl<xsd::AttributesGroup, AudioHalAttributesGroup>( |
| xsdcProductStrategy.getAttributesGroup(), |
| std::bind(&EngineConfigXmlConverter::convertAttributesGroupToAidl, this, |
| std::placeholders::_1)); |
| } |
| if ((mDefaultProductStrategyId != std::nullopt) && (mDefaultProductStrategyId.value() == -1)) { |
| mDefaultProductStrategyId = aidlProductStrategy.id; |
| } |
| return aidlProductStrategy; |
| } |
| |
| AudioHalVolumeCurve::CurvePoint EngineConfigXmlConverter::convertCurvePointToAidl( |
| const std::string& xsdcCurvePoint) { |
| AudioHalVolumeCurve::CurvePoint aidlCurvePoint{}; |
| if (sscanf(xsdcCurvePoint.c_str(), "%" SCNd8 ",%d", &aidlCurvePoint.index, |
| &aidlCurvePoint.attenuationMb) != 2) { |
| aidlCurvePoint.index = kInvalidCurvePointIndex; |
| } |
| return aidlCurvePoint; |
| } |
| |
| AudioHalVolumeCurve EngineConfigXmlConverter::convertVolumeCurveToAidl( |
| const xsd::Volume& xsdcVolumeCurve) { |
| AudioHalVolumeCurve aidlVolumeCurve; |
| aidlVolumeCurve.deviceCategory = |
| static_cast<AudioHalVolumeCurve::DeviceCategory>(xsdcVolumeCurve.getDeviceCategory()); |
| if (xsdcVolumeCurve.hasRef()) { |
| if (mVolumesReferenceMap.empty()) { |
| mVolumesReferenceMap = generateReferenceMap<xsd::VolumesType, xsd::VolumeRef>( |
| getXsdcConfig()->getVolumes()); |
| } |
| aidlVolumeCurve.curvePoints = |
| convertCollectionToAidl<std::string, AudioHalVolumeCurve::CurvePoint>( |
| mVolumesReferenceMap.at(xsdcVolumeCurve.getRef()).getPoint(), |
| std::bind(&EngineConfigXmlConverter::convertCurvePointToAidl, this, |
| std::placeholders::_1)); |
| } else { |
| aidlVolumeCurve.curvePoints = |
| convertCollectionToAidl<std::string, AudioHalVolumeCurve::CurvePoint>( |
| xsdcVolumeCurve.getPoint(), |
| std::bind(&EngineConfigXmlConverter::convertCurvePointToAidl, this, |
| std::placeholders::_1)); |
| } |
| return aidlVolumeCurve; |
| } |
| |
| AudioHalVolumeGroup EngineConfigXmlConverter::convertVolumeGroupToAidl( |
| const xsd::VolumeGroupsType::VolumeGroup& xsdcVolumeGroup) { |
| AudioHalVolumeGroup aidlVolumeGroup; |
| aidlVolumeGroup.name = xsdcVolumeGroup.getName(); |
| aidlVolumeGroup.minIndex = xsdcVolumeGroup.getIndexMin(); |
| aidlVolumeGroup.maxIndex = xsdcVolumeGroup.getIndexMax(); |
| aidlVolumeGroup.volumeCurves = convertCollectionToAidl<xsd::Volume, AudioHalVolumeCurve>( |
| xsdcVolumeGroup.getVolume(), |
| std::bind(&EngineConfigXmlConverter::convertVolumeCurveToAidl, this, |
| std::placeholders::_1)); |
| return aidlVolumeGroup; |
| } |
| |
| AudioHalCapCriterion EngineConfigXmlConverter::convertCapCriterionToAidl( |
| const xsd::CriterionType& xsdcCriterion) { |
| AudioHalCapCriterion aidlCapCriterion; |
| aidlCapCriterion.name = xsdcCriterion.getName(); |
| aidlCapCriterion.criterionTypeName = xsdcCriterion.getType(); |
| aidlCapCriterion.defaultLiteralValue = xsdcCriterion.get_default(); |
| return aidlCapCriterion; |
| } |
| |
| std::string EngineConfigXmlConverter::convertCriterionTypeValueToAidl( |
| const xsd::ValueType& xsdcCriterionTypeValue) { |
| return xsdcCriterionTypeValue.getLiteral(); |
| } |
| |
| AudioHalCapCriterionType EngineConfigXmlConverter::convertCapCriterionTypeToAidl( |
| const xsd::CriterionTypeType& xsdcCriterionType) { |
| AudioHalCapCriterionType aidlCapCriterionType; |
| aidlCapCriterionType.name = xsdcCriterionType.getName(); |
| aidlCapCriterionType.isInclusive = !(static_cast<bool>(xsdcCriterionType.getType())); |
| aidlCapCriterionType.values = |
| convertWrappedCollectionToAidl<xsd::ValuesType, xsd::ValueType, std::string>( |
| xsdcCriterionType.getValues(), &xsd::ValuesType::getValue, |
| std::bind(&EngineConfigXmlConverter::convertCriterionTypeValueToAidl, this, |
| std::placeholders::_1)); |
| return aidlCapCriterionType; |
| } |
| |
| AudioHalEngineConfig& EngineConfigXmlConverter::getAidlEngineConfig() { |
| return mAidlEngineConfig; |
| } |
| |
| void EngineConfigXmlConverter::init() { |
| initProductStrategyMap(); |
| if (getXsdcConfig()->hasProductStrategies()) { |
| mAidlEngineConfig.productStrategies = |
| convertWrappedCollectionToAidl<xsd::ProductStrategies, |
| xsd::ProductStrategies::ProductStrategy, |
| AudioHalProductStrategy>( |
| getXsdcConfig()->getProductStrategies(), |
| &xsd::ProductStrategies::getProductStrategy, |
| std::bind(&EngineConfigXmlConverter::convertProductStrategyToAidl, this, |
| std::placeholders::_1)); |
| if (mDefaultProductStrategyId) { |
| mAidlEngineConfig.defaultProductStrategyId = mDefaultProductStrategyId.value(); |
| } |
| } |
| if (getXsdcConfig()->hasVolumeGroups()) { |
| mAidlEngineConfig.volumeGroups = convertWrappedCollectionToAidl< |
| xsd::VolumeGroupsType, xsd::VolumeGroupsType::VolumeGroup, AudioHalVolumeGroup>( |
| getXsdcConfig()->getVolumeGroups(), &xsd::VolumeGroupsType::getVolumeGroup, |
| std::bind(&EngineConfigXmlConverter::convertVolumeGroupToAidl, this, |
| std::placeholders::_1)); |
| } |
| if (getXsdcConfig()->hasCriteria() && getXsdcConfig()->hasCriterion_types()) { |
| AudioHalEngineConfig::CapSpecificConfig capSpecificConfig; |
| capSpecificConfig.criteria = |
| convertWrappedCollectionToAidl<xsd::CriteriaType, xsd::CriterionType, |
| AudioHalCapCriterion>( |
| getXsdcConfig()->getCriteria(), &xsd::CriteriaType::getCriterion, |
| std::bind(&EngineConfigXmlConverter::convertCapCriterionToAidl, this, |
| std::placeholders::_1)); |
| capSpecificConfig.criterionTypes = |
| convertWrappedCollectionToAidl<xsd::CriterionTypesType, xsd::CriterionTypeType, |
| AudioHalCapCriterionType>( |
| getXsdcConfig()->getCriterion_types(), |
| &xsd::CriterionTypesType::getCriterion_type, |
| std::bind(&EngineConfigXmlConverter::convertCapCriterionTypeToAidl, this, |
| std::placeholders::_1)); |
| mAidlEngineConfig.capSpecificConfig = capSpecificConfig; |
| } |
| } |
| } // namespace aidl::android::hardware::audio::core::internal |