Add effect AIDL version compatibility test in libAudioHal

Bug: 324329730
Test: atest EffectHalVersionCompatibilityTest
Change-Id: I9d385c210d2aa6469790267f38485fccc3e07778
diff --git a/media/libaudiohal/impl/Android.bp b/media/libaudiohal/impl/Android.bp
index 0a79faf..4d81f77 100644
--- a/media/libaudiohal/impl/Android.bp
+++ b/media/libaudiohal/impl/Android.bp
@@ -41,7 +41,7 @@
     ],
     header_libs: [
         "android.hardware.audio.common.util@all-versions",
-    ]
+    ],
 }
 
 cc_defaults {
@@ -71,7 +71,7 @@
     ],
     header_libs: [
         "libaudioclient_headers",
-        "libaudiohal_headers"
+        "libaudiohal_headers",
     ],
     defaults: [
         "latest_android_media_audio_common_types_cpp_export_shared",
@@ -86,7 +86,7 @@
     name: "libaudiohal@5.0",
     defaults: [
         "libaudiohal_default",
-        "libaudiohal_hidl_default"
+        "libaudiohal_hidl_default",
     ],
     srcs: [
         ":audio_core_hal_client_sources",
@@ -105,14 +105,14 @@
         "-DMAJOR_VERSION=5",
         "-DMINOR_VERSION=0",
         "-include common/all-versions/VersionMacro.h",
-    ]
+    ],
 }
 
 cc_library_shared {
     name: "libaudiohal@6.0",
     defaults: [
         "libaudiohal_default",
-        "libaudiohal_hidl_default"
+        "libaudiohal_hidl_default",
     ],
     srcs: [
         ":audio_core_hal_client_sources",
@@ -131,14 +131,14 @@
         "-DMAJOR_VERSION=6",
         "-DMINOR_VERSION=0",
         "-include common/all-versions/VersionMacro.h",
-    ]
+    ],
 }
 
 cc_library_static {
     name: "libaudiohal.effect@7.0",
     defaults: [
         "libaudiohal_default",
-        "libaudiohal_hidl_default"
+        "libaudiohal_hidl_default",
     ],
     srcs: [
         ":audio_effect_hidl_hal_client_sources",
@@ -153,14 +153,14 @@
         "-DMAJOR_VERSION=7",
         "-DMINOR_VERSION=0",
         "-include common/all-versions/VersionMacro.h",
-    ]
+    ],
 }
 
 cc_library_shared {
     name: "libaudiohal@7.0",
     defaults: [
         "libaudiohal_default",
-        "libaudiohal_hidl_default"
+        "libaudiohal_hidl_default",
     ],
     srcs: [
         ":audio_core_hal_client_sources",
@@ -180,7 +180,7 @@
         "-DMAJOR_VERSION=7",
         "-DMINOR_VERSION=0",
         "-include common/all-versions/VersionMacro.h",
-    ]
+    ],
 }
 
 cc_library_shared {
@@ -189,7 +189,7 @@
         "latest_android_hardware_audio_core_sounddose_ndk_shared",
         "latest_android_hardware_audio_sounddose_ndk_shared",
         "libaudiohal_default",
-        "libaudiohal_hidl_default"
+        "libaudiohal_hidl_default",
     ],
     srcs: [
         ":audio_core_hal_client_sources",
@@ -216,7 +216,7 @@
         "-DCOMMON_TYPES_MINOR_VERSION=0",
         "-DCORE_TYPES_MINOR_VERSION=0",
         "-include common/all-versions/VersionMacro.h",
-    ]
+    ],
 }
 
 cc_defaults {
@@ -261,9 +261,30 @@
     ],
     srcs: [
         "DevicesFactoryHalEntry.cpp",
+        "EffectsFactoryHalEntry.cpp",
+        ":audio_effect_hal_aidl_src_files",
+        ":core_audio_hal_aidl_src_files",
+    ],
+}
+
+filegroup {
+    name: "core_audio_hal_aidl_src_files",
+    srcs: [
+        "ConversionHelperAidl.cpp",
+        "DeviceHalAidl.cpp",
+        "DevicesFactoryHalAidl.cpp",
+        "Hal2AidlMapper.cpp",
+        "StreamHalAidl.cpp",
+    ],
+}
+
+filegroup {
+    name: "audio_effect_hal_aidl_src_files",
+    srcs: [
         "EffectConversionHelperAidl.cpp",
         "EffectBufferHalAidl.cpp",
         "EffectHalAidl.cpp",
+        "EffectsFactoryHalAidl.cpp",
         "effectsAidlConversion/AidlConversionAec.cpp",
         "effectsAidlConversion/AidlConversionAgc1.cpp",
         "effectsAidlConversion/AidlConversionAgc2.cpp",
@@ -280,21 +301,7 @@
         "effectsAidlConversion/AidlConversionVendorExtension.cpp",
         "effectsAidlConversion/AidlConversionVirtualizer.cpp",
         "effectsAidlConversion/AidlConversionVisualizer.cpp",
-        "EffectsFactoryHalAidl.cpp",
-        "EffectsFactoryHalEntry.cpp",
         ":audio_effectproxy_src_files",
-        ":core_audio_hal_aidl_src_files",
-    ],
-}
-
-filegroup {
-    name: "core_audio_hal_aidl_src_files",
-    srcs: [
-        "ConversionHelperAidl.cpp",
-        "DeviceHalAidl.cpp",
-        "DevicesFactoryHalAidl.cpp",
-        "Hal2AidlMapper.cpp",
-        "StreamHalAidl.cpp",
     ],
 }
 
diff --git a/media/libaudiohal/tests/Android.bp b/media/libaudiohal/tests/Android.bp
index 8f011c8..97510d6 100644
--- a/media/libaudiohal/tests/Android.bp
+++ b/media/libaudiohal/tests/Android.bp
@@ -63,3 +63,14 @@
     ],
     header_libs: ["libaudiohalimpl_headers"],
 }
+
+cc_test {
+    name: "EffectHalVersionCompatibilityTest",
+    srcs: [
+        "EffectHalVersionCompatibility_test.cpp",
+        ":audio_effect_hal_aidl_src_files",
+    ],
+    defaults: ["libaudiohal_aidl_test_default"],
+    header_libs: ["libaudiohalimpl_headers"],
+    static_libs: ["libgmock"],
+}
diff --git a/media/libaudiohal/tests/EffectHalVersionCompatibility_test.cpp b/media/libaudiohal/tests/EffectHalVersionCompatibility_test.cpp
new file mode 100644
index 0000000..e8731ea
--- /dev/null
+++ b/media/libaudiohal/tests/EffectHalVersionCompatibility_test.cpp
@@ -0,0 +1,319 @@
+/*
+ * Copyright 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.
+ */
+
+#include <cstddef>
+#include <unordered_map>
+#define LOG_TAG "EffectHalVersionCompatibilityTest"
+
+#include <EffectHalAidl.h>
+#include <aidl/android/hardware/audio/effect/IEffect.h>
+#include <aidl/android/hardware/audio/effect/IFactory.h>
+#include <android-base/logging.h>
+#include <android/binder_manager.h>
+#include <android/binder_process.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <media/audiohal/EffectsFactoryHalInterface.h>
+#include <system/audio_aidl_utils.h>
+#include <system/audio_config.h>
+#include <system/audio_effects/audio_effects_utils.h>
+#include <system/audio_effects/effect_uuid.h>
+#include <utils/Log.h>
+
+using aidl::android::hardware::audio::effect::CommandId;
+using aidl::android::hardware::audio::effect::Descriptor;
+using aidl::android::hardware::audio::effect::IEffect;
+using aidl::android::hardware::audio::effect::IFactory;
+using aidl::android::hardware::audio::effect::kReopenSupportedVersion;
+using aidl::android::hardware::audio::effect::Parameter;
+using aidl::android::hardware::audio::effect::Processing;
+using aidl::android::hardware::audio::effect::State;
+using aidl::android::media::audio::common::AudioUuid;
+using android::OK;
+using android::sp;
+using android::effect::EffectHalAidl;
+using testing::_;
+using testing::Eq;
+
+namespace {
+
+/**
+ * Maps of parameter and the version it was introduced.
+ */
+// parameters defined directly in the Parameter union, except Parameter::specific (defined in
+// kParamIdEffectVersionMap).
+static const std::unordered_map<Parameter::Tag, int /* version */> kParamTagVersionMap = {
+        {Parameter::common, 1},         {Parameter::deviceDescription, 1},
+        {Parameter::mode, 1},           {Parameter::source, 1},
+        {Parameter::offload, 1},        {Parameter::volumeStereo, 1},
+        {Parameter::sourceMetadata, 2}, {Parameter::sinkMetadata, 2},
+};
+
+// Map of the version a specific effect type introduction
+// Id tags defined Parameter::Id union, except Parameter::Id::commonTag (defined in
+// kParamTagVersionMap).
+static const std::unordered_map<Parameter::Id::Tag, int /* version */> kParamIdEffectVersionMap = {
+        {Parameter::Id::vendorEffectTag, 1},
+        {Parameter::Id::acousticEchoCancelerTag, 1},
+        {Parameter::Id::automaticGainControlV1Tag, 1},
+        {Parameter::Id::automaticGainControlV2Tag, 1},
+        {Parameter::Id::bassBoostTag, 1},
+        {Parameter::Id::downmixTag, 1},
+        {Parameter::Id::dynamicsProcessingTag, 1},
+        {Parameter::Id::environmentalReverbTag, 1},
+        {Parameter::Id::equalizerTag, 1},
+        {Parameter::Id::hapticGeneratorTag, 1},
+        {Parameter::Id::loudnessEnhancerTag, 1},
+        {Parameter::Id::noiseSuppressionTag, 1},
+        {Parameter::Id::presetReverbTag, 1},
+        {Parameter::Id::virtualizerTag, 1},
+        {Parameter::Id::visualizerTag, 1},
+        {Parameter::Id::volumeTag, 1},
+        {Parameter::Id::spatializerTag, 2},
+};
+// Tags defined Parameter::Specific union.
+static const std::unordered_map<Parameter::Specific::Tag, int /* version */>
+        kParamEffectVersionMap = {
+                {Parameter::Specific::vendorEffect, 1},
+                {Parameter::Specific::acousticEchoCanceler, 1},
+                {Parameter::Specific::automaticGainControlV1, 1},
+                {Parameter::Specific::automaticGainControlV2, 1},
+                {Parameter::Specific::bassBoost, 1},
+                {Parameter::Specific::downmix, 1},
+                {Parameter::Specific::dynamicsProcessing, 1},
+                {Parameter::Specific::environmentalReverb, 1},
+                {Parameter::Specific::equalizer, 1},
+                {Parameter::Specific::hapticGenerator, 1},
+                {Parameter::Specific::loudnessEnhancer, 1},
+                {Parameter::Specific::noiseSuppression, 1},
+                {Parameter::Specific::presetReverb, 1},
+                {Parameter::Specific::virtualizer, 1},
+                {Parameter::Specific::visualizer, 1},
+                {Parameter::Specific::volume, 1},
+                {Parameter::Specific::spatializer, 2},
+};
+
+class MockFactory : public IFactory {
+  public:
+    explicit MockFactory(int version) : IFactory(), mVersion(version) {}
+    MOCK_METHOD(ndk::ScopedAStatus, queryEffects,
+                (const std::optional<AudioUuid>& in_type_uuid,
+                 const std::optional<AudioUuid>& in_impl_uuid,
+                 const std::optional<AudioUuid>& in_proxy_uuid,
+                 std::vector<Descriptor>* _aidl_return),
+                (override));
+
+    MOCK_METHOD(ndk::ScopedAStatus, queryProcessing,
+                (const std::optional<Processing::Type>& in_type,
+                 std::vector<Processing>* _aidl_return),
+                (override));
+
+    MOCK_METHOD(ndk::ScopedAStatus, createEffect,
+                (const AudioUuid& in_impl_uuid, std::shared_ptr<IEffect>* _aidl_return),
+                (override));
+
+    MOCK_METHOD(ndk::ScopedAStatus, destroyEffect, (const std::shared_ptr<IEffect>& in_handle),
+                (override));
+
+    ndk::ScopedAStatus getInterfaceVersion(int32_t* _aidl_return) {
+        *_aidl_return = mVersion;
+        return ndk::ScopedAStatus::ok();
+    }
+
+    // these must be implemented but won't be used in this testing
+    ::ndk::SpAIBinder asBinder() { return ::ndk::SpAIBinder(); }
+    bool isRemote() { return false; }
+    ::ndk::ScopedAStatus getInterfaceHash(std::string*) { return ndk::ScopedAStatus::ok(); }
+
+  private:
+    const int mVersion;
+};
+
+class MockEffect : public IEffect {
+  public:
+    explicit MockEffect(int version) : IEffect(), mVersion(version) {}
+    MOCK_METHOD(ndk::ScopedAStatus, open,
+                (const Parameter::Common& common,
+                 const std::optional<Parameter::Specific>& specific,
+                 IEffect::OpenEffectReturn* ret),
+                (override));
+    MOCK_METHOD(ndk::ScopedAStatus, close, (), (override));
+    MOCK_METHOD(binder_status_t, dump, (int fd, const char** args, uint32_t numArgs), (override));
+    MOCK_METHOD(ndk::ScopedAStatus, command, (CommandId id), (override));
+    MOCK_METHOD(ndk::ScopedAStatus, getState, (State * state), (override));
+    MOCK_METHOD(ndk::ScopedAStatus, getDescriptor, (Descriptor * desc), (override));
+    MOCK_METHOD(ndk::ScopedAStatus, destroy, (), ());
+
+    // reopen introduced in version kReopenSupportedVersion
+    ndk::ScopedAStatus reopen(IEffect::OpenEffectReturn*) override {
+        return mVersion < kReopenSupportedVersion
+                       ? ndk::ScopedAStatus::fromStatus(STATUS_UNKNOWN_TRANSACTION)
+                       : ndk::ScopedAStatus::ok();
+    }
+
+    // for all parameters introduced
+    ndk::ScopedAStatus setParameter(const Parameter& param) override {
+        const auto paramTag = param.getTag();
+        switch (paramTag) {
+            case Parameter::common:
+            case Parameter::deviceDescription:
+            case Parameter::mode:
+            case Parameter::source:
+            case Parameter::offload:
+            case Parameter::volumeStereo:
+            case Parameter::sinkMetadata:
+                FALLTHROUGH_INTENDED;
+            case Parameter::sourceMetadata: {
+                if (kParamTagVersionMap.find(paramTag) != kParamTagVersionMap.end() &&
+                    kParamTagVersionMap.at(paramTag) >= mVersion) {
+                    return ndk::ScopedAStatus::ok();
+                }
+                break;
+            }
+            case Parameter::specific: {
+                // TODO
+                break;
+            }
+        }
+        return ndk::ScopedAStatus::fromStatus(STATUS_BAD_VALUE);
+    }
+
+    /**
+     * Only care about version compatibility here:
+     * @return BAD_VALUE if a tag is not supported by current AIDL version.
+     * @return OK if a tag is supported by current AIDL version.
+     */
+    ndk::ScopedAStatus getParameter(const Parameter::Id& id, Parameter*) override {
+        const auto idTag = id.getTag();
+        switch (idTag) {
+            case Parameter::Id::commonTag: {
+                const auto paramTag = id.get<Parameter::Id::commonTag>();
+                if (kParamTagVersionMap.find(paramTag) != kParamTagVersionMap.end() &&
+                    kParamTagVersionMap.at(paramTag) >= mVersion) {
+                    return ndk::ScopedAStatus::ok();
+                }
+                break;
+            }
+            case Parameter::Id::vendorEffectTag:
+            case Parameter::Id::acousticEchoCancelerTag:
+            case Parameter::Id::automaticGainControlV1Tag:
+            case Parameter::Id::automaticGainControlV2Tag:
+            case Parameter::Id::bassBoostTag:
+            case Parameter::Id::downmixTag:
+            case Parameter::Id::dynamicsProcessingTag:
+            case Parameter::Id::environmentalReverbTag:
+            case Parameter::Id::equalizerTag:
+            case Parameter::Id::hapticGeneratorTag:
+            case Parameter::Id::loudnessEnhancerTag:
+            case Parameter::Id::noiseSuppressionTag:
+            case Parameter::Id::presetReverbTag:
+            case Parameter::Id::virtualizerTag:
+            case Parameter::Id::visualizerTag:
+            case Parameter::Id::volumeTag:
+                FALLTHROUGH_INTENDED;
+            case Parameter::Id::spatializerTag: {
+                if (kParamIdEffectVersionMap.find(idTag) != kParamIdEffectVersionMap.end() &&
+                    kParamIdEffectVersionMap.at(idTag) >= mVersion) {
+                    return ndk::ScopedAStatus::ok();
+                }
+                break;
+            }
+        }
+        return ndk::ScopedAStatus::fromStatus(STATUS_BAD_VALUE);
+    }
+
+    ndk::ScopedAStatus getInterfaceVersion(int32_t* _aidl_return) {
+        *_aidl_return = mVersion;
+        return ndk::ScopedAStatus::ok();
+    }
+
+    // these must be implemented but won't be used in this testing
+    ::ndk::SpAIBinder asBinder() { return ::ndk::SpAIBinder(); }
+    bool isRemote() { return false; }
+    ::ndk::ScopedAStatus getInterfaceHash(std::string*) { return ndk::ScopedAStatus::ok(); }
+
+  private:
+    const int mVersion;
+};
+
+static const std::vector<AudioUuid> kTestParamUUIDs = {
+        ::aidl::android::hardware::audio::effect::getEffectTypeUuidAcousticEchoCanceler(),
+        ::aidl::android::hardware::audio::effect::getEffectTypeUuidAutomaticGainControlV1(),
+        ::aidl::android::hardware::audio::effect::getEffectTypeUuidAutomaticGainControlV2(),
+        ::aidl::android::hardware::audio::effect::getEffectTypeUuidBassBoost(),
+        ::aidl::android::hardware::audio::effect::getEffectTypeUuidDownmix(),
+        ::aidl::android::hardware::audio::effect::getEffectTypeUuidDynamicsProcessing(),
+        ::aidl::android::hardware::audio::effect::getEffectTypeUuidEnvReverb(),
+        ::aidl::android::hardware::audio::effect::getEffectTypeUuidEqualizer(),
+        ::aidl::android::hardware::audio::effect::getEffectTypeUuidHapticGenerator(),
+        ::aidl::android::hardware::audio::effect::getEffectTypeUuidLoudnessEnhancer(),
+        ::aidl::android::hardware::audio::effect::getEffectTypeUuidNoiseSuppression(),
+        ::aidl::android::hardware::audio::effect::getEffectTypeUuidPresetReverb(),
+        ::aidl::android::hardware::audio::effect::getEffectTypeUuidSpatializer(),
+        ::aidl::android::hardware::audio::effect::getEffectTypeUuidVirtualizer(),
+        ::aidl::android::hardware::audio::effect::getEffectTypeUuidVisualizer(),
+        ::aidl::android::hardware::audio::effect::getEffectUuidNull(),
+};
+static const std::vector<int> kTestParamVersion = {1, 2};  // Effect AIDL HAL versions to test
+
+enum ParamName { UUID, VERSION };
+using TestParam = std::tuple<AudioUuid, int /* version */>;
+
+class EffectHalVersionCompatibilityTest : public ::testing::TestWithParam<TestParam> {
+  public:
+    void SetUp() override {
+        mMockFactory = ndk::SharedRefBase::make<MockFactory>(mVersion);
+        ASSERT_NE(mMockFactory, nullptr);
+        mMockEffect = ndk::SharedRefBase::make<MockEffect>(mVersion);
+        ASSERT_NE(mMockEffect, nullptr);
+        mEffectHalAidl = sp<EffectHalAidl>::make(mMockFactory, mMockEffect, 0, 0, mDesc, false);
+        ASSERT_NE(mEffectHalAidl, nullptr);
+    }
+
+    void TearDown() override {
+        EXPECT_CALL(*mMockFactory, destroyEffect(_));
+        mEffectHalAidl.clear();
+        mMockEffect.reset();
+        mMockFactory.reset();
+    }
+
+  protected:
+    const int mVersion = std::get<VERSION>(GetParam());
+    const AudioUuid mTypeUuid = std::get<UUID>(GetParam());
+    const Descriptor mDesc = {.common.id.type = mTypeUuid};
+    std::shared_ptr<MockFactory> mMockFactory = nullptr;
+    std::shared_ptr<MockEffect> mMockEffect = nullptr;
+    sp<EffectHalAidl> mEffectHalAidl = nullptr;
+};
+
+TEST_P(EffectHalVersionCompatibilityTest, testEffectAidlHalCreateDestroy) {
+    // do nothing
+}
+
+INSTANTIATE_TEST_SUITE_P(
+        EffectHalVersionCompatibilityTestWithVersion, EffectHalVersionCompatibilityTest,
+        ::testing::Combine(testing::ValuesIn(kTestParamUUIDs),
+                           testing::ValuesIn(kTestParamVersion)),
+        [](const testing::TestParamInfo<EffectHalVersionCompatibilityTest::ParamType>& info) {
+            auto version = std::to_string(std::get<VERSION>(info.param));
+            auto uuid = android::audio::utils::toString(std::get<UUID>(info.param));
+            std::string name = "EffectHalVersionCompatibilityTest_V" + version + "_" + uuid;
+            std::replace_if(
+                    name.begin(), name.end(), [](const char c) { return !std::isalnum(c); }, '_');
+            return name;
+        });
+
+}  // namespace
\ No newline at end of file