/* * Copyright (C) 2023 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 #include #include #include #include #define LOG_TAG "VtsHalAudioCore.Config" #include #include #include #include #include #include #include #include "AudioHalBinderServiceUtil.h" #include "TestUtils.h" using namespace android; using aidl::android::hardware::audio::common::isDefaultAudioFormat; using aidl::android::hardware::audio::core::IConfig; using aidl::android::hardware::audio::core::SurroundSoundConfig; using aidl::android::media::audio::common::AudioAttributes; using aidl::android::media::audio::common::AudioFlag; using aidl::android::media::audio::common::AudioFormatDescription; using aidl::android::media::audio::common::AudioFormatType; 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; using aidl::android::media::audio::common::PcmType; class AudioCoreConfig : public testing::TestWithParam { public: void SetUp() override { ASSERT_NO_FATAL_FAILURE(ConnectToService()); } void ConnectToService() { mConfig = IConfig::fromBinder(mBinderUtil.connectToService(GetParam())); ASSERT_NE(mConfig, nullptr); } void RestartService() { ASSERT_NE(mConfig, nullptr); mEngineConfig.reset(); mSurroundSoundConfig.reset(); mConfig = IConfig::fromBinder(mBinderUtil.restartService()); ASSERT_NE(mConfig, nullptr); } void SetUpEngineConfig() { if (mEngineConfig == nullptr) { auto tempConfig = std::make_unique(); ASSERT_IS_OK(mConfig->getEngineConfig(tempConfig.get())); mEngineConfig = std::move(tempConfig); } } void SetUpSurroundSoundConfig() { if (mSurroundSoundConfig == nullptr) { auto tempConfig = std::make_unique(); ASSERT_IS_OK(mConfig->getSurroundSoundConfig(tempConfig.get())); mSurroundSoundConfig = std::move(tempConfig); } } static bool IsProductStrategyTypeReservedForSystemUse(const AudioProductStrategyType& pst) { switch (pst) { case AudioProductStrategyType::SYS_RESERVED_NONE: case AudioProductStrategyType::SYS_RESERVED_REROUTING: case AudioProductStrategyType::SYS_RESERVED_CALL_ASSISTANT: return true; default: return false; } } static bool IsStreamTypeReservedForSystemUse(const AudioStreamType& streamType) { switch (streamType) { case AudioStreamType::SYS_RESERVED_DEFAULT: case AudioStreamType::SYS_RESERVED_REROUTING: case AudioStreamType::SYS_RESERVED_PATCH: case AudioStreamType::CALL_ASSISTANT: return true; default: return false; } } static bool IsAudioUsageValid(const AudioUsage& usage) { switch (usage) { case AudioUsage::INVALID: case AudioUsage::SYS_RESERVED_NOTIFICATION_COMMUNICATION_REQUEST: case AudioUsage::SYS_RESERVED_NOTIFICATION_COMMUNICATION_INSTANT: case AudioUsage::SYS_RESERVED_NOTIFICATION_COMMUNICATION_DELAYED: return false; default: return true; } } static bool IsAudioSourceValid(const AudioSource& source) { return (source != AudioSource::SYS_RESERVED_INVALID); } static const std::unordered_set& GetSupportedAudioProductStrategyTypes() { static const std::unordered_set supportedAudioProductStrategyTypes = []() { std::unordered_set supportedStrategyTypes; for (const auto& audioProductStrategyType : ndk::enum_range()) { if (!IsProductStrategyTypeReservedForSystemUse(audioProductStrategyType)) { supportedStrategyTypes.insert(static_cast(audioProductStrategyType)); } } return supportedStrategyTypes; }(); return supportedAudioProductStrategyTypes; } static int GetSupportedAudioFlagsMask() { static const int supportedAudioFlagsMask = []() { int mask = 0; for (const auto& audioFlag : ndk::enum_range()) { mask |= static_cast(audioFlag); } return mask; }(); return supportedAudioFlagsMask; } /** * Verify streamType is not INVALID if using default engine. * Verify that streamType is a valid AudioStreamType if the associated * volumeGroup minIndex/maxIndex is INDEX_DEFERRED_TO_AUDIO_SERVICE. */ void ValidateAudioStreamType(const AudioStreamType& streamType, const AudioHalVolumeGroup& associatedVolumeGroup) { EXPECT_FALSE(IsStreamTypeReservedForSystemUse(streamType)); if (!mEngineConfig->capSpecificConfig || associatedVolumeGroup.minIndex == AudioHalVolumeGroup::INDEX_DEFERRED_TO_AUDIO_SERVICE) { EXPECT_NE(streamType, AudioStreamType::INVALID); } } /** * Verify contained enum types are valid. */ void ValidateAudioAttributes(const AudioAttributes& attributes) { // No need to check contentType; there are no INVALID or SYS_RESERVED values EXPECT_TRUE(IsAudioUsageValid(attributes.usage)); EXPECT_TRUE(IsAudioSourceValid(attributes.source)); EXPECT_EQ(attributes.flags & ~GetSupportedAudioFlagsMask(), 0); } /** * Verify volumeGroupName corresponds to an AudioHalVolumeGroup. * Validate contained types. */ void ValidateAudioHalAttributesGroup( const AudioHalAttributesGroup& attributesGroup, std::unordered_map& volumeGroupMap, std::unordered_set& volumeGroupsUsedInStrategies) { bool isVolumeGroupNameValid = volumeGroupMap.count(attributesGroup.volumeGroupName); EXPECT_TRUE(isVolumeGroupNameValid); EXPECT_NO_FATAL_FAILURE(ValidateAudioStreamType( attributesGroup.streamType, volumeGroupMap.at(attributesGroup.volumeGroupName))); if (isVolumeGroupNameValid) { volumeGroupsUsedInStrategies.insert(attributesGroup.volumeGroupName); } for (const AudioAttributes& attr : attributesGroup.attributes) { EXPECT_NO_FATAL_FAILURE(ValidateAudioAttributes(attr)); } } /** * Default engine: verify productStrategy.id is valid AudioProductStrategyType. * CAP engine: verify productStrategy.id is either valid AudioProductStrategyType * or is >= VENDOR_STRATEGY_ID_START. * Validate contained types. */ void ValidateAudioHalProductStrategy( const AudioHalProductStrategy& strategy, std::unordered_map& volumeGroupMap, std::unordered_set& volumeGroupsUsedInStrategies) { if (!mEngineConfig->capSpecificConfig || (strategy.id < AudioHalProductStrategy::VENDOR_STRATEGY_ID_START)) { EXPECT_NE(GetSupportedAudioProductStrategyTypes().find(strategy.id), GetSupportedAudioProductStrategyTypes().end()); } for (const AudioHalAttributesGroup& attributesGroup : strategy.attributesGroups) { EXPECT_NO_FATAL_FAILURE(ValidateAudioHalAttributesGroup(attributesGroup, volumeGroupMap, volumeGroupsUsedInStrategies)); } } /** * Verify curve point index is in [CurvePoint::MIN_INDEX, CurvePoint::MAX_INDEX]. */ void ValidateAudioHalVolumeCurve(const AudioHalVolumeCurve& volumeCurve) { for (const AudioHalVolumeCurve::CurvePoint& curvePoint : volumeCurve.curvePoints) { EXPECT_TRUE(curvePoint.index >= AudioHalVolumeCurve::CurvePoint::MIN_INDEX); EXPECT_TRUE(curvePoint.index <= AudioHalVolumeCurve::CurvePoint::MAX_INDEX); } } /** * Verify minIndex, maxIndex are non-negative. * Verify minIndex <= maxIndex. * Verify no two volume curves use the same device category. * Validate contained types. */ void ValidateAudioHalVolumeGroup(const AudioHalVolumeGroup& volumeGroup) { /** * Legacy volume curves in audio_policy_configuration.xsd don't use * minIndex or maxIndex. Use of audio_policy_configuration.xml still * allows, and in some cases, relies on, AudioService to provide the min * and max indices for a volumeGroup. From the VTS perspective, there is * no way to differentiate between use of audio_policy_configuration.xml * or audio_policy_engine_configuration.xml, as either one can be used * for the default audio policy engine. */ if (volumeGroup.minIndex != AudioHalVolumeGroup::INDEX_DEFERRED_TO_AUDIO_SERVICE || volumeGroup.maxIndex != AudioHalVolumeGroup::INDEX_DEFERRED_TO_AUDIO_SERVICE) { EXPECT_TRUE(volumeGroup.minIndex >= 0); EXPECT_TRUE(volumeGroup.maxIndex >= 0); } EXPECT_TRUE(volumeGroup.minIndex <= volumeGroup.maxIndex); std::unordered_set deviceCategorySet; for (const AudioHalVolumeCurve& volumeCurve : volumeGroup.volumeCurves) { EXPECT_TRUE(deviceCategorySet.insert(volumeCurve.deviceCategory).second); EXPECT_NO_FATAL_FAILURE(ValidateAudioHalVolumeCurve(volumeCurve)); } } /** * Verify defaultLiteralValue is empty for inclusive criterion. */ void ValidateAudioHalCapCriterion(const AudioHalCapCriterion& criterion, const AudioHalCapCriterionType& criterionType) { if (criterionType.isInclusive) { EXPECT_TRUE(criterion.defaultLiteralValue.empty()); } } /** * Verify values only contain alphanumeric characters. */ void ValidateAudioHalCapCriterionType(const AudioHalCapCriterionType& criterionType) { auto isNotAlnum = [](const char& c) { return !isalnum(c); }; for (const std::string& value : criterionType.values) { EXPECT_EQ(find_if(value.begin(), value.end(), isNotAlnum), value.end()); } } /** * Verify each criterionType has a unique name. * Verify each criterion has a unique name. * Verify each criterion maps to a criterionType. * Verify each criterionType is used in a criterion. * Validate contained types. */ void ValidateCapSpecificConfig(const AudioHalEngineConfig::CapSpecificConfig& capCfg) { EXPECT_FALSE(capCfg.criteria.empty()); EXPECT_FALSE(capCfg.criterionTypes.empty()); std::unordered_map criterionTypeMap; for (const AudioHalCapCriterionType& criterionType : capCfg.criterionTypes) { EXPECT_NO_FATAL_FAILURE(ValidateAudioHalCapCriterionType(criterionType)); EXPECT_TRUE(criterionTypeMap.insert({criterionType.name, criterionType}).second); } std::unordered_set criterionNameSet; for (const AudioHalCapCriterion& criterion : capCfg.criteria) { EXPECT_TRUE(criterionNameSet.insert(criterion.name).second); EXPECT_EQ(criterionTypeMap.count(criterion.criterionTypeName), 1UL); EXPECT_NO_FATAL_FAILURE(ValidateAudioHalCapCriterion( criterion, criterionTypeMap.at(criterion.criterionTypeName))); } EXPECT_EQ(criterionTypeMap.size(), criterionNameSet.size()); } /** * Verify VolumeGroups are non-empty. * Verify defaultProductStrategyId matches one of the provided productStrategies. * Otherwise, must be left uninitialized. * Verify each volumeGroup has a unique name. * Verify each productStrategy has a unique id. * Verify each volumeGroup is used in a product strategy. * CAP engine: verify productStrategies are non-empty. * Validate contained types. */ void ValidateAudioHalEngineConfig() { EXPECT_NE(mEngineConfig->volumeGroups.size(), 0UL); std::unordered_map volumeGroupMap; for (const AudioHalVolumeGroup& volumeGroup : mEngineConfig->volumeGroups) { EXPECT_TRUE(volumeGroupMap.insert({volumeGroup.name, volumeGroup}).second); EXPECT_NO_FATAL_FAILURE(ValidateAudioHalVolumeGroup(volumeGroup)); } if (!mEngineConfig->productStrategies.empty()) { std::unordered_set productStrategyIdSet; std::unordered_set volumeGroupsUsedInStrategies; for (const AudioHalProductStrategy& strategy : mEngineConfig->productStrategies) { EXPECT_TRUE(productStrategyIdSet.insert(strategy.id).second); EXPECT_NO_FATAL_FAILURE(ValidateAudioHalProductStrategy( strategy, volumeGroupMap, volumeGroupsUsedInStrategies)); } EXPECT_TRUE(productStrategyIdSet.count(mEngineConfig->defaultProductStrategyId)) << "defaultProductStrategyId doesn't match any of the provided " "productStrategies"; EXPECT_EQ(volumeGroupMap.size(), volumeGroupsUsedInStrategies.size()); } else { EXPECT_EQ(mEngineConfig->defaultProductStrategyId, static_cast(AudioProductStrategyType::SYS_RESERVED_NONE)) << "defaultProductStrategyId defined, but no productStrategies were provided"; } if (mEngineConfig->capSpecificConfig) { EXPECT_NO_FATAL_FAILURE( ValidateCapSpecificConfig(mEngineConfig->capSpecificConfig.value())); EXPECT_FALSE(mEngineConfig->productStrategies.empty()); } } void ValidateAudioFormatDescription(const AudioFormatDescription& format) { EXPECT_NE(AudioFormatType::SYS_RESERVED_INVALID, format.type); if (format.type == AudioFormatType::PCM) { EXPECT_NE(PcmType::DEFAULT, format.pcm); EXPECT_TRUE(format.encoding.empty()) << format.encoding; } else { EXPECT_FALSE(format.encoding.empty()); } } /** * Verify that the surround sound configuration is not empty. * Verify each of the formatFamilies has a non-empty primaryFormat. * Verify that each format only appears once. */ void ValidateSurroundSoundConfig() { EXPECT_FALSE(mSurroundSoundConfig->formatFamilies.empty()); std::set formatSet; for (const SurroundSoundConfig::SurroundFormatFamily& family : mSurroundSoundConfig->formatFamilies) { EXPECT_NO_FATAL_FAILURE(ValidateAudioFormatDescription(family.primaryFormat)); EXPECT_FALSE(isDefaultAudioFormat(family.primaryFormat)); EXPECT_TRUE(formatSet.insert(family.primaryFormat).second); for (const AudioFormatDescription& subformat : family.subFormats) { EXPECT_NO_FATAL_FAILURE(ValidateAudioFormatDescription(subformat)); EXPECT_FALSE(isDefaultAudioFormat(subformat)); EXPECT_TRUE(formatSet.insert(subformat).second); } } } private: std::shared_ptr mConfig; std::unique_ptr mEngineConfig; std::unique_ptr mSurroundSoundConfig; AudioHalBinderServiceUtil mBinderUtil; }; TEST_P(AudioCoreConfig, Published) { // SetUp must complete with no failures. } TEST_P(AudioCoreConfig, CanBeRestarted) { ASSERT_NO_FATAL_FAILURE(RestartService()); } TEST_P(AudioCoreConfig, GetEngineConfigIsValid) { ASSERT_NO_FATAL_FAILURE(SetUpEngineConfig()); EXPECT_NO_FATAL_FAILURE(ValidateAudioHalEngineConfig()); } TEST_P(AudioCoreConfig, GetSurroundSoundConfigIsValid) { ASSERT_NO_FATAL_FAILURE(SetUpSurroundSoundConfig()); EXPECT_NO_FATAL_FAILURE(ValidateSurroundSoundConfig()); } INSTANTIATE_TEST_SUITE_P(AudioCoreConfigTest, AudioCoreConfig, testing::ValuesIn(android::getAidlHalInstanceNames(IConfig::descriptor)), android::PrintInstanceNameToString); GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(AudioCoreConfig);