AIDL effect: Add EffectProxy implementation and test

Bug: 261129656
Test: Enable AIDL in libaudiohal, Build
Test: atest EffectsProxyTest
Test: Enable AIDL and test on Pixel with YTM
Change-Id: I655d8671f64e5b237d4626be376bc9a014d5927e
diff --git a/media/libaudiohal/impl/Android.bp b/media/libaudiohal/impl/Android.bp
index d5f6598..9901fc0 100644
--- a/media/libaudiohal/impl/Android.bp
+++ b/media/libaudiohal/impl/Android.bp
@@ -274,6 +274,7 @@
         "EffectsFactoryHalAidl.cpp",
         "EffectsFactoryHalEntry.cpp",
         "StreamHalAidl.cpp",
+        ":audio_effectproxy_src_files"
     ],
     static_libs: [
         "android.hardware.common-V2-ndk",
@@ -297,3 +298,8 @@
         "-DBACKEND_CPP_NDK",
     ],
 }
+
+filegroup {
+    name: "audio_effectproxy_src_files",
+    srcs: ["EffectProxy.cpp"],
+}
diff --git a/media/libaudiohal/impl/EffectConversionHelperAidl.cpp b/media/libaudiohal/impl/EffectConversionHelperAidl.cpp
index 519b871..5ab7c84 100644
--- a/media/libaudiohal/impl/EffectConversionHelperAidl.cpp
+++ b/media/libaudiohal/impl/EffectConversionHelperAidl.cpp
@@ -29,6 +29,7 @@
 #include <utils/Log.h>
 
 #include "EffectConversionHelperAidl.h"
+#include "EffectProxy.h"
 
 namespace android {
 namespace effect {
@@ -37,7 +38,9 @@
 using ::aidl::android::hardware::audio::effect::CommandId;
 using ::aidl::android::hardware::audio::effect::Descriptor;
 using ::aidl::android::hardware::audio::effect::Flags;
+using ::aidl::android::hardware::audio::effect::IEffect;
 using ::aidl::android::hardware::audio::effect::Parameter;
+using ::aidl::android::hardware::audio::effect::State;
 using ::aidl::android::media::audio::common::AudioDeviceDescription;
 using ::aidl::android::media::audio::common::AudioMode;
 using ::aidl::android::media::audio::common::AudioSource;
@@ -72,7 +75,9 @@
       mIoId(ioId),
       mDesc(desc),
       mEffect(std::move(effect)),
-      mIsInputStream(mDesc.common.flags.type == Flags::Type::PRE_PROC) {
+      mIsInputStream(mDesc.common.flags.type == Flags::Type::PRE_PROC),
+      mIsProxyEffect(mDesc.common.id.proxy.has_value() &&
+                     mDesc.common.id.proxy.value() == mDesc.common.id.uuid) {
     mCommon.session = sessionId;
     mCommon.ioHandle = ioId;
     mCommon.input = mCommon.output = kDefaultAudioConfig;
@@ -96,8 +101,8 @@
         return BAD_VALUE;
     }
 
-    return *(status_t*)pReplyData =
-                   statusTFromBinderStatus(mEffect->open(mCommon, std::nullopt, &mOpenReturn));
+    // Do nothing for EFFECT_CMD_INIT, call IEffect.open() with EFFECT_CMD_SET_CONFIG
+    return *(status_t*)pReplyData = OK;
 }
 
 status_t EffectConversionHelperAidl::handleSetParameter(uint32_t cmdSize, const void* pCmdData,
@@ -154,22 +159,55 @@
     }
 
     effect_config_t* config = (effect_config_t*)pCmdData;
-    Parameter::Common aidlCommon = {
-            .session = mSessionId,
-            .ioHandle = mIoId,
-            .input = {.base = VALUE_OR_RETURN_STATUS(
-                              ::aidl::android::legacy2aidl_buffer_config_t_AudioConfigBase(
-                                      config->inputCfg, mIsInputStream))},
-            .output = {.base = VALUE_OR_RETURN_STATUS(
-                               ::aidl::android::legacy2aidl_buffer_config_t_AudioConfigBase(
-                                       config->outputCfg, mIsInputStream))}};
+    Parameter::Common common = {
+            .input =
+                    VALUE_OR_RETURN_STATUS(::aidl::android::legacy2aidl_buffer_config_t_AudioConfig(
+                            config->inputCfg, mIsInputStream)),
+            .output =
+                    VALUE_OR_RETURN_STATUS(::aidl::android::legacy2aidl_buffer_config_t_AudioConfig(
+                            config->outputCfg, mIsInputStream)),
+            .session = mCommon.session,
+            .ioHandle = mCommon.ioHandle};
 
-    Parameter aidlParam = UNION_MAKE(Parameter, common, aidlCommon);
+    State state;
+    RETURN_STATUS_IF_ERROR(statusTFromBinderStatus(mEffect->getState(&state)));
+    // in case of buffer/ioHandle re-configure for an opened effect, close it and re-open
+    if (state != State::INIT && mCommon != common) {
+        ALOGI("%s at state %s, closing effect", __func__,
+              android::internal::ToString(state).c_str());
+        RETURN_STATUS_IF_ERROR(statusTFromBinderStatus(mEffect->close()));
+        RETURN_STATUS_IF_ERROR(statusTFromBinderStatus(mEffect->getState(&state)));
+        mStatusQ.reset();
+        mInputQ.reset();
+        mOutputQ.reset();
+    }
 
-    status_t ret = statusTFromBinderStatus(mEffect->setParameter(aidlParam));
-    EffectParamWriter writer(*(effect_param_t*)pReplyData);
-    writer.setStatus(ret);
-    return ret;
+    if (state == State::INIT) {
+        ALOGI("%s at state %s, opening effect", __func__,
+              android::internal::ToString(state).c_str());
+        IEffect::OpenEffectReturn openReturn;
+        RETURN_STATUS_IF_ERROR(
+                statusTFromBinderStatus(mEffect->open(common, std::nullopt, &openReturn)));
+
+        if (mIsProxyEffect) {
+            const auto& ret =
+                    std::static_pointer_cast<EffectProxy>(mEffect)->getEffectReturnParam();
+            mStatusQ = std::make_shared<StatusMQ>(ret->statusMQ);
+            mInputQ = std::make_shared<DataMQ>(ret->inputDataMQ);
+            mOutputQ = std::make_shared<DataMQ>(ret->outputDataMQ);
+        } else {
+            mStatusQ = std::make_shared<StatusMQ>(openReturn.statusMQ);
+            mInputQ = std::make_shared<DataMQ>(openReturn.inputDataMQ);
+            mOutputQ = std::make_shared<DataMQ>(openReturn.outputDataMQ);
+        }
+        mCommon = common;
+    } else if (mCommon != common) {
+        ALOGI("%s at state %s, setParameter", __func__, android::internal::ToString(state).c_str());
+        Parameter aidlParam = UNION_MAKE(Parameter, common, mCommon);
+        RETURN_STATUS_IF_ERROR(statusTFromBinderStatus(mEffect->setParameter(aidlParam)));
+    }
+
+    return *static_cast<int32_t*>(pReplyData) = OK;
 }
 
 status_t EffectConversionHelperAidl::handleGetConfig(uint32_t cmdSize __unused,
@@ -187,11 +225,9 @@
     const auto& common = param.get<Parameter::common>();
     effect_config_t* pConfig = (effect_config_t*)pReplyData;
     pConfig->inputCfg = VALUE_OR_RETURN_STATUS(
-            ::aidl::android::aidl2legacy_AudioConfigBase_buffer_config_t(common.input.base, true));
-    pConfig->outputCfg =
-            VALUE_OR_RETURN_STATUS(::aidl::android::aidl2legacy_AudioConfigBase_buffer_config_t(
-                    common.output.base, false));
-    mCommon = common;
+            ::aidl::android::aidl2legacy_AudioConfig_buffer_config_t(common.input, true));
+    pConfig->outputCfg = VALUE_OR_RETURN_STATUS(
+            ::aidl::android::aidl2legacy_AudioConfig_buffer_config_t(common.output, false));
     return OK;
 }
 
@@ -294,7 +330,20 @@
               pReplyData);
         return BAD_VALUE;
     }
-    // TODO: handle this after effectproxy implemented in libaudiohal
+    effect_offload_param_t* offload = (effect_offload_param_t*)pCmdData;
+    // send to proxy to update active sub-effect
+    if (mIsProxyEffect) {
+        ALOGI("%s offload param offload %s ioHandle %d", __func__,
+              offload->isOffload ? "true" : "false", offload->ioHandle);
+        mCommon.ioHandle = offload->ioHandle;
+        RETURN_STATUS_IF_ERROR(statusTFromBinderStatus(
+                std::static_pointer_cast<EffectProxy>(mEffect)->setOffloadParam(offload)));
+        // update FMQs
+        const auto& ret = std::static_pointer_cast<EffectProxy>(mEffect)->getEffectReturnParam();
+        mStatusQ = std::make_shared<StatusMQ>(ret->statusMQ);
+        mInputQ = std::make_shared<DataMQ>(ret->inputDataMQ);
+        mOutputQ = std::make_shared<DataMQ>(ret->outputDataMQ);
+    }
     return *static_cast<int32_t*>(pReplyData) = OK;
 }
 
diff --git a/media/libaudiohal/impl/EffectConversionHelperAidl.h b/media/libaudiohal/impl/EffectConversionHelperAidl.h
index 54df1b8..1200264 100644
--- a/media/libaudiohal/impl/EffectConversionHelperAidl.h
+++ b/media/libaudiohal/impl/EffectConversionHelperAidl.h
@@ -19,6 +19,7 @@
 #include <utils/Errors.h>
 
 #include <aidl/android/hardware/audio/effect/BpEffect.h>
+#include <fmq/AidlMessageQueue.h>
 #include <system/audio_effect.h>
 #include <system/audio_effects/audio_effects_utils.h>
 
@@ -30,10 +31,15 @@
     status_t handleCommand(uint32_t cmdCode, uint32_t cmdSize, void* pCmdData, uint32_t* replySize,
                            void* pReplyData);
     virtual ~EffectConversionHelperAidl() {}
-    const ::aidl::android::hardware::audio::effect::IEffect::OpenEffectReturn&
-    getEffectReturnParam() const {
-        return mOpenReturn;
-    }
+
+    using StatusMQ = ::android::AidlMessageQueue<
+            ::aidl::android::hardware::audio::effect::IEffect::Status,
+            ::aidl::android::hardware::common::fmq::SynchronizedReadWrite>;
+    using DataMQ = ::android::AidlMessageQueue<
+            float, ::aidl::android::hardware::common::fmq::SynchronizedReadWrite>;
+    std::shared_ptr<StatusMQ> getStatusMQ() { return mStatusQ; }
+    std::shared_ptr<DataMQ> getInputMQ() { return mInputQ; }
+    std::shared_ptr<DataMQ> getOutputMQ() { return mOutputQ; }
 
   protected:
     const int32_t mSessionId;
@@ -42,7 +48,6 @@
     const std::shared_ptr<::aidl::android::hardware::audio::effect::IEffect> mEffect;
     // whether the effect is instantiated on an input stream
     const bool mIsInputStream;
-    ::aidl::android::hardware::audio::effect::IEffect::OpenEffectReturn mOpenReturn;
     ::aidl::android::hardware::audio::effect::Parameter::Common mCommon;
 
     EffectConversionHelperAidl(
@@ -59,6 +64,7 @@
     const aidl::android::media::audio::common::AudioFormatDescription kDefaultFormatDescription = {
             .type = aidl::android::media::audio::common::AudioFormatType::PCM,
             .pcm = aidl::android::media::audio::common::PcmType::FLOAT_32_BIT};
+    const bool mIsProxyEffect;
 
     static constexpr int kDefaultframeCount = 0x100;
 
@@ -75,6 +81,9 @@
                                                                    uint32_t* /* replySize */,
                                                                    void* /* pReplyData */);
     static const std::map<uint32_t /* effect_command_e */, CommandHandler> mCommandHandlerMap;
+    // data and status FMQ
+    std::shared_ptr<StatusMQ> mStatusQ = nullptr;
+    std::shared_ptr<DataMQ> mInputQ = nullptr, mOutputQ = nullptr;
 
     status_t handleInit(uint32_t cmdSize, const void* pCmdData, uint32_t* replySize,
                         void* pReplyData);
diff --git a/media/libaudiohal/impl/EffectHalAidl.cpp b/media/libaudiohal/impl/EffectHalAidl.cpp
index 0c19ac8..d6135af 100644
--- a/media/libaudiohal/impl/EffectHalAidl.cpp
+++ b/media/libaudiohal/impl/EffectHalAidl.cpp
@@ -31,6 +31,7 @@
 #include <utils/Log.h>
 
 #include "EffectHalAidl.h"
+#include "EffectProxy.h"
 
 #include <aidl/android/hardware/audio/effect/IEffect.h>
 
@@ -61,19 +62,22 @@
 
 EffectHalAidl::EffectHalAidl(const std::shared_ptr<IFactory>& factory,
                              const std::shared_ptr<IEffect>& effect, uint64_t effectId,
-                             int32_t sessionId, int32_t ioId, const Descriptor& desc)
+                             int32_t sessionId, int32_t ioId, const Descriptor& desc,
+                             bool isProxyEffect)
     : mFactory(factory),
       mEffect(effect),
       mEffectId(effectId),
       mSessionId(sessionId),
       mIoId(ioId),
-      mDesc(desc) {
+      mDesc(desc),
+      mIsProxyEffect(isProxyEffect) {
     createAidlConversion(effect, sessionId, ioId, desc);
 }
 
 EffectHalAidl::~EffectHalAidl() {
-    if (mFactory) {
-        mFactory->destroyEffect(mEffect);
+    if (mEffect) {
+        mIsProxyEffect ? std::static_pointer_cast<EffectProxy>(mEffect)->destroy()
+                       : mFactory->destroyEffect(mEffect);
     }
 }
 
@@ -160,34 +164,49 @@
 
 // write to input FMQ here, wait for statusMQ STATUS_OK, and read from output FMQ
 status_t EffectHalAidl::process() {
-    size_t available = mInputQ->availableToWrite();
+    auto statusQ = mConversion->getStatusMQ();
+    auto inputQ = mConversion->getInputMQ();
+    auto outputQ = mConversion->getOutputMQ();
+    if (!statusQ || !statusQ->isValid() || !inputQ || !inputQ->isValid() || !outputQ ||
+        !outputQ->isValid()) {
+        ALOGE("%s invalid FMQ [Status %d I %d O %d]", __func__, statusQ ? statusQ->isValid() : 0,
+              inputQ ? inputQ->isValid() : 0, outputQ ? outputQ->isValid() : 0);
+        return INVALID_OPERATION;
+    }
+
+    size_t available = inputQ->availableToWrite();
     size_t floatsToWrite = std::min(available, mInBuffer->getSize() / sizeof(float));
     if (floatsToWrite == 0) {
-        ALOGW("%s not able to write, floats in buffer %zu, space in FMQ %zu", __func__,
+        ALOGE("%s not able to write, floats in buffer %zu, space in FMQ %zu", __func__,
               mInBuffer->getSize() / sizeof(float), available);
         return INVALID_OPERATION;
     }
-    if (!mInputQ->write((float*)mInBuffer->ptr(), floatsToWrite)) {
-        ALOGW("%s failed to write %zu into inputQ", __func__, floatsToWrite);
+    if (!mInBuffer->audioBuffer() ||
+        !inputQ->write((float*)mInBuffer->audioBuffer()->f32, floatsToWrite)) {
+        ALOGE("%s failed to write %zu floats from audiobuffer %p to inputQ [avail %zu]", __func__,
+              floatsToWrite, mInBuffer->audioBuffer(), inputQ->availableToWrite());
         return INVALID_OPERATION;
     }
 
     IEffect::Status retStatus{};
-    if (!mStatusQ->readBlocking(&retStatus, 1) || retStatus.status != OK ||
+    if (!statusQ->readBlocking(&retStatus, 1) || retStatus.status != OK ||
         (size_t)retStatus.fmqConsumed != floatsToWrite || retStatus.fmqProduced == 0) {
-        ALOGW("%s read status failed: %s", __func__, retStatus.toString().c_str());
+        ALOGE("%s read status failed: %s", __func__, retStatus.toString().c_str());
         return INVALID_OPERATION;
     }
 
-    available = mOutputQ->availableToRead();
+    available = outputQ->availableToRead();
     size_t floatsToRead = std::min(available, mOutBuffer->getSize() / sizeof(float));
     if (floatsToRead == 0) {
-        ALOGW("%s not able to read, buffer space %zu, floats in FMQ %zu", __func__,
+        ALOGE("%s not able to read, buffer space %zu, floats in FMQ %zu", __func__,
               mOutBuffer->getSize() / sizeof(float), available);
         return INVALID_OPERATION;
     }
-    if (!mOutputQ->read((float*)mOutBuffer->ptr(), floatsToRead)) {
-        ALOGW("%s failed to read %zu from outputQ", __func__, floatsToRead);
+    // always read floating point data for AIDL
+    if (!mOutBuffer->audioBuffer() ||
+        !outputQ->read(mOutBuffer->audioBuffer()->f32, floatsToRead)) {
+        ALOGE("%s failed to read %zu from outputQ to audioBuffer %p", __func__, floatsToRead,
+              mOutBuffer->audioBuffer());
         return INVALID_OPERATION;
     }
 
@@ -210,20 +229,7 @@
         return INVALID_OPERATION;
     }
 
-    status_t ret = mConversion->handleCommand(cmdCode, cmdSize, pCmdData, replySize, pReplyData);
-    // update FMQs when effect open successfully
-    if (ret == OK && cmdCode == EFFECT_CMD_INIT) {
-        const auto& retParam = mConversion->getEffectReturnParam();
-        mStatusQ = std::make_unique<StatusMQ>(retParam.statusMQ);
-        mInputQ = std::make_unique<DataMQ>(retParam.inputDataMQ);
-        mOutputQ = std::make_unique<DataMQ>(retParam.outputDataMQ);
-        if (!mStatusQ->isValid() || !mInputQ->isValid() || !mOutputQ->isValid()) {
-            ALOGE("%s return with invalid FMQ", __func__);
-            return NO_INIT;
-        }
-    }
-
-    return ret;
+    return mConversion->handleCommand(cmdCode, cmdSize, pCmdData, replySize, pReplyData);
 }
 
 status_t EffectHalAidl::getDescriptor(effect_descriptor_t* pDescriptor) {
diff --git a/media/libaudiohal/impl/EffectHalAidl.h b/media/libaudiohal/impl/EffectHalAidl.h
index 194150d..8966363 100644
--- a/media/libaudiohal/impl/EffectHalAidl.h
+++ b/media/libaudiohal/impl/EffectHalAidl.h
@@ -31,11 +31,6 @@
 
 class EffectHalAidl : public EffectHalInterface {
   public:
-    using StatusMQ = ::android::AidlMessageQueue<
-            ::aidl::android::hardware::audio::effect::IEffect::Status,
-            ::aidl::android::hardware::common::fmq::SynchronizedReadWrite>;
-    using DataMQ = ::android::AidlMessageQueue<
-            float, ::aidl::android::hardware::common::fmq::SynchronizedReadWrite>;
 
     // Set the input buffer.
     status_t setInBuffer(const sp<EffectBufferHalInterface>& buffer) override;
@@ -83,12 +78,11 @@
     const int32_t mSessionId;
     const int32_t mIoId;
     const ::aidl::android::hardware::audio::effect::Descriptor mDesc;
+    const bool mIsProxyEffect;
+
     std::unique_ptr<EffectConversionHelperAidl> mConversion;
-    std::unique_ptr<StatusMQ> mStatusQ;
-    std::unique_ptr<DataMQ> mInputQ, mOutputQ;
 
     sp<EffectBufferHalInterface> mInBuffer, mOutBuffer;
-    effect_config_t mConfig;
 
     status_t createAidlConversion(
             std::shared_ptr<::aidl::android::hardware::audio::effect::IEffect> effect,
@@ -99,8 +93,10 @@
             const std::shared_ptr<::aidl::android::hardware::audio::effect::IFactory>& factory,
             const std::shared_ptr<::aidl::android::hardware::audio::effect::IEffect>& effect,
             uint64_t effectId, int32_t sessionId, int32_t ioId,
-            const ::aidl::android::hardware::audio::effect::Descriptor& desc);
+            const ::aidl::android::hardware::audio::effect::Descriptor& desc,
+            bool isProxyEffect);
     bool setEffectReverse(bool reverse);
+    bool needUpdateReturnParam(uint32_t cmdCode);
 
     // The destructor automatically releases the effect.
     virtual ~EffectHalAidl();
diff --git a/media/libaudiohal/impl/EffectProxy.cpp b/media/libaudiohal/impl/EffectProxy.cpp
new file mode 100644
index 0000000..c4d85e5
--- /dev/null
+++ b/media/libaudiohal/impl/EffectProxy.cpp
@@ -0,0 +1,292 @@
+/*
+ * 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 <algorithm>
+#include <memory>
+#define LOG_TAG "EffectProxy"
+//#define LOG_NDEBUG 0
+
+#include <fmq/AidlMessageQueue.h>
+#include <utils/Log.h>
+
+#include "EffectProxy.h"
+
+using ::aidl::android::hardware::audio::effect::CommandId;
+using ::aidl::android::hardware::audio::effect::Descriptor;
+using ::aidl::android::hardware::audio::effect::Flags;
+using ::aidl::android::hardware::audio::effect::IEffect;
+using ::aidl::android::hardware::audio::effect::IFactory;
+using ::aidl::android::hardware::audio::effect::Parameter;
+using ::aidl::android::hardware::audio::effect::State;
+using ::aidl::android::media::audio::common::AudioUuid;
+
+namespace android {
+namespace effect {
+
+EffectProxy::EffectProxy(const Descriptor::Identity& id, const std::shared_ptr<IFactory>& factory)
+    : mIdentity([](const Descriptor::Identity& subId) {
+          // update EffectProxy implementation UUID to the sub-effect proxy UUID
+          ALOG_ASSERT(subId.proxy.has_value(), "Sub-effect Identity must have valid proxy UUID");
+          Descriptor::Identity tempId = subId;
+          tempId.uuid = subId.proxy.value();
+          return tempId;
+      }(id)),
+      mFactory(factory) {}
+
+EffectProxy::~EffectProxy() {
+    close();
+    destroy();
+    mSubEffects.clear();
+}
+
+// sub effect must have same proxy UUID as EffectProxy, and the type UUID must match.
+ndk::ScopedAStatus EffectProxy::addSubEffect(const Descriptor& sub) {
+    ALOGV("%s: %s", __func__, mIdentity.type.toString().c_str());
+    if (0 != mSubEffects.count(sub.common.id) || !sub.common.id.proxy.has_value() ||
+        sub.common.id.proxy.value() != mIdentity.uuid) {
+        ALOGE("%s sub effect already exist or mismatch %s", __func__, sub.toString().c_str());
+        return ndk::ScopedAStatus::fromExceptionCodeWithMessage(EX_ILLEGAL_ARGUMENT,
+                                                                "illegalSubEffect");
+    }
+
+    // not create sub-effect yet
+    std::get<SubEffectTupleIndex::HANDLE>(mSubEffects[sub.common.id]) = nullptr;
+    std::get<SubEffectTupleIndex::DESCRIPTOR>(mSubEffects[sub.common.id]) = sub;
+    // set the last added sub-effect to active before setOffloadParam()
+    mActiveSub = sub.common.id;
+    ALOGI("%s add %s to proxy %s flag %s", __func__, mActiveSub.toString().c_str(),
+          mIdentity.toString().c_str(), sub.common.flags.toString().c_str());
+
+    if (sub.common.flags.hwAcceleratorMode == Flags::HardwareAccelerator::TUNNEL) {
+        mSubFlags.hwAcceleratorMode = Flags::HardwareAccelerator::TUNNEL;
+    }
+
+    // initial flag values before we know which sub-effect to active (with setOffloadParam)
+    // same as HIDL EffectProxy flags
+    mSubFlags.type = Flags::Type::INSERT;
+    mSubFlags.insert = Flags::Insert::LAST;
+    mSubFlags.volume = Flags::Volume::CTRL;
+
+    // set indication if any sub-effect indication was set
+    mSubFlags.offloadIndication |= sub.common.flags.offloadIndication;
+    mSubFlags.deviceIndication |= sub.common.flags.deviceIndication;
+    mSubFlags.audioModeIndication |= sub.common.flags.audioModeIndication;
+    mSubFlags.audioSourceIndication |= sub.common.flags.audioSourceIndication;
+
+    // set bypass when all sub-effects are bypassing
+    mSubFlags.bypass &= sub.common.flags.bypass;
+    return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus EffectProxy::create() {
+    ALOGV("%s: %s", __func__, mIdentity.type.toString().c_str());
+    ndk::ScopedAStatus status = ndk::ScopedAStatus::ok();
+
+    for (auto& sub : mSubEffects) {
+        auto& effectHandle = std::get<SubEffectTupleIndex::HANDLE>(sub.second);
+        ALOGI("%s sub-effect %s", __func__, sub.first.uuid.toString().c_str());
+        status = mFactory->createEffect(sub.first.uuid, &effectHandle);
+        if (!status.isOk() || !effectHandle) {
+            ALOGE("%s sub-effect failed %s", __func__, sub.first.uuid.toString().c_str());
+            break;
+        }
+    }
+
+    // destroy all created effects if failure
+    if (!status.isOk()) {
+        destroy();
+    }
+    return status;
+}
+
+ndk::ScopedAStatus EffectProxy::destroy() {
+    ALOGV("%s: %s", __func__, mIdentity.type.toString().c_str());
+    return runWithAllSubEffects([&](std::shared_ptr<IEffect>& effect) {
+        ndk::ScopedAStatus status = mFactory->destroyEffect(effect);
+        if (status.isOk()) {
+            effect.reset();
+        }
+        return status;
+    });
+}
+
+const IEffect::OpenEffectReturn* EffectProxy::getEffectReturnParam() {
+    return &std::get<SubEffectTupleIndex::RETURN>(mSubEffects[mActiveSub]);
+}
+
+ndk::ScopedAStatus EffectProxy::setOffloadParam(const effect_offload_param_t* offload) {
+    const auto& itor = std::find_if(mSubEffects.begin(), mSubEffects.end(), [&](const auto& sub) {
+        const auto& desc = std::get<SubEffectTupleIndex::DESCRIPTOR>(sub.second);
+        ALOGI("%s: isOffload %d sub-effect: %s, flags %s", __func__, offload->isOffload,
+              desc.common.id.uuid.toString().c_str(), desc.common.flags.toString().c_str());
+        return offload->isOffload ==
+               (desc.common.flags.hwAcceleratorMode == Flags::HardwareAccelerator::TUNNEL);
+    });
+    if (itor == mSubEffects.end()) {
+        ALOGE("%s no %soffload sub-effect found", __func__, offload->isOffload ? "" : "non-");
+        return ndk::ScopedAStatus::fromExceptionCodeWithMessage(EX_NULL_POINTER,
+                                                                "noActiveEffctFound");
+    }
+
+    mActiveSub = itor->first;
+    ALOGI("%s: active %soffload sub-effect: %s, flags %s", __func__,
+          offload->isOffload ? "" : "non-", mActiveSub.uuid.toString().c_str(),
+          std::get<SubEffectTupleIndex::DESCRIPTOR>(itor->second).common.flags.toString().c_str());
+    return ndk::ScopedAStatus::ok();
+}
+
+// EffectProxy go over sub-effects and call IEffect interfaces
+ndk::ScopedAStatus EffectProxy::open(const Parameter::Common& common,
+                                     const std::optional<Parameter::Specific>& specific,
+                                     IEffect::OpenEffectReturn* ret __unused) {
+    ALOGV("%s: %s", __func__, mIdentity.type.toString().c_str());
+    ndk::ScopedAStatus status = ndk::ScopedAStatus::fromExceptionCodeWithMessage(
+            EX_ILLEGAL_ARGUMENT, "nullEffectHandle");
+    for (auto& sub : mSubEffects) {
+        auto& effect = std::get<SubEffectTupleIndex::HANDLE>(sub.second);
+        auto& openRet = std::get<SubEffectTupleIndex::RETURN>(sub.second);
+        if (!effect ||
+            (status = effect->open(common, specific, &openRet)).isOk()) {
+            ALOGE("%s: failed to open UUID %s", __func__, sub.first.uuid.toString().c_str());
+            break;
+        }
+    }
+
+    // close all opened effects if failure
+    if (!status.isOk()) {
+        close();
+    }
+
+    return status;
+}
+
+ndk::ScopedAStatus EffectProxy::close() {
+    ALOGV("%s: %s", __func__, mIdentity.type.toString().c_str());
+    return runWithAllSubEffects([&](std::shared_ptr<IEffect>& effect) {
+        return effect->close();
+    });
+}
+
+ndk::ScopedAStatus EffectProxy::getDescriptor(Descriptor* desc) {
+    if (!desc) {
+        ALOGE("%s: nuull descriptor pointer", __func__);
+        return ndk::ScopedAStatus::fromExceptionCodeWithMessage(EX_NULL_POINTER, "nullptr");
+    }
+
+    auto& activeSubEffect = std::get<SubEffectTupleIndex::HANDLE>(mSubEffects[mActiveSub]);
+    // return initial descriptor if no active sub-effect exist
+    if (!activeSubEffect) {
+        desc->common.id = mIdentity;
+        desc->common.flags = mSubFlags;
+        desc->common.name = "Proxy";
+        desc->common.implementor = "AOSP";
+    } else {
+        *desc = std::get<SubEffectTupleIndex::DESCRIPTOR>(mSubEffects[mActiveSub]);
+        desc->common.id = mIdentity;
+    }
+
+    ALOGI("%s with %s", __func__, desc->toString().c_str());
+    return ndk::ScopedAStatus::ok();
+}
+
+// Handle with active sub-effect first, only send to other sub-effects when success
+ndk::ScopedAStatus EffectProxy::command(CommandId id) {
+    ALOGV("%s: %s, command %s", __func__, mIdentity.type.toString().c_str(),
+          android::internal::ToString(id).c_str());
+    return runWithActiveSubEffectThenOthers(
+            [&](const std::shared_ptr<IEffect>& effect) -> ndk::ScopedAStatus {
+                return effect->command(id);
+            });
+}
+
+// Return the active sub-effect state
+ndk::ScopedAStatus EffectProxy::getState(State* state) {
+    return runWithActiveSubEffect(
+            [&](const std::shared_ptr<IEffect>& effect) -> ndk::ScopedAStatus {
+                return effect->getState(state);
+            });
+}
+
+// Handle with active sub-effect first, only send to other sub-effects when success
+ndk::ScopedAStatus EffectProxy::setParameter(const Parameter& param) {
+    return runWithActiveSubEffectThenOthers(
+            [&](const std::shared_ptr<IEffect>& effect) -> ndk::ScopedAStatus {
+                return effect->setParameter(param);
+            });
+}
+
+// Return the active sub-effect parameter
+ndk::ScopedAStatus EffectProxy::getParameter(const Parameter::Id& id, Parameter* param) {
+    return runWithActiveSubEffect(
+            [&](const std::shared_ptr<IEffect>& effect) -> ndk::ScopedAStatus {
+                return effect->getParameter(id, param);
+            });
+}
+
+ndk::ScopedAStatus EffectProxy::runWithActiveSubEffectThenOthers(
+        std::function<ndk::ScopedAStatus(const std::shared_ptr<IEffect>&)> const& func) {
+    ndk::ScopedAStatus status = runWithActiveSubEffect(func);
+    if (!status.isOk()) {
+        return status;
+    }
+
+    // proceed with others if active sub-effect success
+    for (const auto& sub : mSubEffects) {
+        auto& effect = std::get<SubEffectTupleIndex::HANDLE>(sub.second);
+        if (sub.first != mActiveSub) {
+            if (!effect) {
+                ALOGE("%s null sub-effect interface for %s", __func__,
+                      sub.first.toString().c_str());
+                continue;
+            }
+            func(effect);
+        }
+    }
+    return status;
+}
+
+ndk::ScopedAStatus EffectProxy::runWithActiveSubEffect(
+        std::function<ndk::ScopedAStatus(const std::shared_ptr<IEffect>&)> const& func) {
+    auto& effect = std::get<SubEffectTupleIndex::HANDLE>(mSubEffects[mActiveSub]);
+    if (!effect) {
+        ALOGE("%s null active sub-effect interface, active %s", __func__,
+              mActiveSub.toString().c_str());
+        return ndk::ScopedAStatus::fromExceptionCodeWithMessage(EX_NULL_POINTER,
+                                                                "activeSubEffectNull");
+    }
+    return func(effect);
+}
+
+ndk::ScopedAStatus EffectProxy::runWithAllSubEffects(
+        std::function<ndk::ScopedAStatus(std::shared_ptr<IEffect>&)> const& func) {
+    ndk::ScopedAStatus status = ndk::ScopedAStatus::ok();
+    // proceed with others if active sub-effect success
+    for (auto& sub : mSubEffects) {
+        auto& effect = std::get<SubEffectTupleIndex::HANDLE>(sub.second);
+        if (!effect) {
+            ALOGW("%s null sub-effect interface for %s", __func__, sub.first.toString().c_str());
+            continue;
+        }
+        ndk::ScopedAStatus temp = func(effect);
+        if (!temp.isOk()) {
+            status = ndk::ScopedAStatus::fromStatus(temp.getStatus());
+        }
+    }
+    return status;
+}
+
+} // namespace effect
+} // namespace android
diff --git a/media/libaudiohal/impl/EffectProxy.h b/media/libaudiohal/impl/EffectProxy.h
new file mode 100644
index 0000000..b1ab26e
--- /dev/null
+++ b/media/libaudiohal/impl/EffectProxy.h
@@ -0,0 +1,130 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <map>
+#include <memory>
+
+#include <aidl/android/hardware/audio/effect/BnEffect.h>
+#include <aidl/android/hardware/audio/effect/BnFactory.h>
+#include <fmq/AidlMessageQueue.h>
+#include <system/audio_effect.h>
+
+namespace android {
+namespace effect {
+
+/**
+ * EffectProxy is the proxy for one or more effect AIDL implementations (sub effect) of same type.
+ * The audio framework use EffectProxy as a composite implementation of all sub effect
+ * implementations.
+ *
+ * At any given time, there is only one active effect which consuming and producing data for each
+ * proxy. All setter commands (except the legacy EFFECT_CMD_OFFLOAD, it will be handled by the audio
+ * framework directly) and parameters will be pass through to all sub effects, the getter commands
+ * and parameters will only passthrough to the active sub-effect.
+ *
+ */
+class EffectProxy : public ::aidl::android::hardware::audio::effect::BnEffect {
+  public:
+    EffectProxy(const ::aidl::android::hardware::audio::effect::Descriptor::Identity& id,
+                const std::shared_ptr<::aidl::android::hardware::audio::effect::IFactory>& factory);
+
+    /**
+     * Add a sub effect into the proxy, the descriptor of candidate sub-effect need to have same
+     * proxy UUID as mUuid.
+     */
+    ndk::ScopedAStatus addSubEffect(
+            const ::aidl::android::hardware::audio::effect::Descriptor& sub);
+
+    /**
+     * Create all sub-effects via AIDL IFactory, always call create() after all sub-effects added
+     * successfully with addSubEffect.
+     */
+    ndk::ScopedAStatus create();
+
+    /**
+     * Destroy all sub-effects via AIDL IFactory, always call create() after all sub-effects added
+     * successfully with addSubEffect.
+     */
+    ndk::ScopedAStatus destroy();
+
+    /**
+     * Handle offload parameter setting from framework.
+     */
+    ndk::ScopedAStatus setOffloadParam(const effect_offload_param_t* offload);
+
+    /**
+     * Get the const reference of the active sub-effect return parameters.
+     * Always use this interface to get the effect open return parameters (FMQs) after a success
+     * setOffloadParam() call.
+     */
+    const IEffect::OpenEffectReturn* getEffectReturnParam();
+
+    // IEffect interfaces override
+    ndk::ScopedAStatus open(
+            const ::aidl::android::hardware::audio::effect::Parameter::Common& common,
+            const std::optional<::aidl::android::hardware::audio::effect::Parameter::Specific>&
+                    specific,
+            ::aidl::android::hardware::audio::effect::IEffect::OpenEffectReturn* ret) override;
+    ndk::ScopedAStatus close() override;
+    ndk::ScopedAStatus getDescriptor(
+            ::aidl::android::hardware::audio::effect::Descriptor* desc) override;
+    ndk::ScopedAStatus command(::aidl::android::hardware::audio::effect::CommandId id) override;
+    ndk::ScopedAStatus getState(::aidl::android::hardware::audio::effect::State* state) override;
+    ndk::ScopedAStatus setParameter(
+            const ::aidl::android::hardware::audio::effect::Parameter& param) override;
+    ndk::ScopedAStatus getParameter(
+            const ::aidl::android::hardware::audio::effect::Parameter::Id& id,
+            ::aidl::android::hardware::audio::effect::Parameter* param) override;
+
+  private:
+    // Proxy identity, copy from one sub-effect, and update the implementation UUID to proxy UUID
+    const ::aidl::android::hardware::audio::effect::Descriptor::Identity mIdentity;
+    const std::shared_ptr<::aidl::android::hardware::audio::effect::IFactory> mFactory;
+
+    // A map of sub effects descriptor to the IEffect handle and return FMQ
+    enum SubEffectTupleIndex { HANDLE, DESCRIPTOR, RETURN };
+    using EffectProxySub =
+            std::tuple<std::shared_ptr<::aidl::android::hardware::audio::effect::IEffect>,
+                       ::aidl::android::hardware::audio::effect::Descriptor,
+                       ::aidl::android::hardware::audio::effect::IEffect::OpenEffectReturn>;
+    std::map<const ::aidl::android::hardware::audio::effect::Descriptor::Identity, EffectProxySub>
+            mSubEffects;
+
+    // Descriptor of the only active effect in the mSubEffects map
+    ::aidl::android::hardware::audio::effect::Descriptor::Identity mActiveSub;
+
+    // keep the flag of sub-effects
+    ::aidl::android::hardware::audio::effect::Flags mSubFlags;
+
+    ndk::ScopedAStatus runWithActiveSubEffectThenOthers(
+            std::function<ndk::ScopedAStatus(
+                    const std::shared_ptr<
+                            ::aidl::android::hardware::audio::effect::IEffect>&)> const& func);
+
+    ndk::ScopedAStatus runWithActiveSubEffect(
+            std::function<ndk::ScopedAStatus(const std::shared_ptr<IEffect>&)> const& func);
+
+    ndk::ScopedAStatus runWithAllSubEffects(
+            std::function<ndk::ScopedAStatus(std::shared_ptr<IEffect>&)> const& func);
+
+    // close and release all sub-effects
+    ~EffectProxy();
+};
+
+} // namespace effect
+} // namespace android
diff --git a/media/libaudiohal/impl/EffectsFactoryHalAidl.cpp b/media/libaudiohal/impl/EffectsFactoryHalAidl.cpp
index f289f24..bc05aa0 100644
--- a/media/libaudiohal/impl/EffectsFactoryHalAidl.cpp
+++ b/media/libaudiohal/impl/EffectsFactoryHalAidl.cpp
@@ -15,7 +15,9 @@
  */
 
 #include <algorithm>
+#include <cstddef>
 #include <cstdint>
+#include <iterator>
 #include <memory>
 #define LOG_TAG "EffectsFactoryHalAidl"
 //#define LOG_NDEBUG 0
@@ -29,10 +31,12 @@
 
 #include "EffectBufferHalAidl.h"
 #include "EffectHalAidl.h"
+#include "EffectProxy.h"
 #include "EffectsFactoryHalAidl.h"
 
 using ::aidl::android::legacy2aidl_audio_uuid_t_AudioUuid;
 using aidl::android::aidl_utils::statusTFromBinderStatus;
+using aidl::android::hardware::audio::effect::Descriptor;
 using aidl::android::hardware::audio::effect::IFactory;
 using aidl::android::media::audio::common::AudioUuid;
 using android::detail::AudioHalVersionInfo;
@@ -42,12 +46,56 @@
 
 EffectsFactoryHalAidl::EffectsFactoryHalAidl(std::shared_ptr<IFactory> effectsFactory)
     : mFactory(effectsFactory),
-      mHalVersion(AudioHalVersionInfo(AudioHalVersionInfo::Type::AIDL, [this]() {
-          int32_t majorVersion = 0;
-          return (mFactory && mFactory->getInterfaceVersion(&majorVersion).isOk()) ? majorVersion
-                                                                                   : 0;
-      }())) {
-    ALOG_ASSERT(effectsFactory != nullptr, "Provided IEffectsFactory service is NULL");
+      mHalVersion(AudioHalVersionInfo(
+              AudioHalVersionInfo::Type::AIDL,
+              [this]() {
+                  int32_t majorVersion = 0;
+                  return (mFactory && mFactory->getInterfaceVersion(&majorVersion).isOk())
+                                 ? majorVersion
+                                 : 0;
+              }())),
+      mHalDescList([this]() {
+          std::vector<Descriptor> list;
+          if (mFactory) {
+              mFactory->queryEffects(std::nullopt, std::nullopt, std::nullopt, &list).isOk();
+          }
+          return list;
+      }()),
+      mUuidProxyMap([this]() {
+          std::map<AudioUuid, std::shared_ptr<EffectProxy>> proxyMap;
+          for (const auto& desc : mHalDescList) {
+              // create EffectProxy
+              if (desc.common.id.proxy.has_value()) {
+                  const auto& uuid = desc.common.id.proxy.value();
+                  if (0 == proxyMap.count(uuid)) {
+                      proxyMap.insert({uuid, ndk::SharedRefBase::make<EffectProxy>(desc.common.id,
+                                                                                   mFactory)});
+                  }
+                  proxyMap[uuid]->addSubEffect(desc);
+                  ALOGI("%s addSubEffect %s", __func__, desc.common.toString().c_str());
+              }
+          }
+          return proxyMap;
+      }()),
+      mProxyDescList([this]() {
+          std::vector<Descriptor> list;
+          for (const auto& proxy : mUuidProxyMap) {
+              if (Descriptor desc; proxy.second && proxy.second->getDescriptor(&desc).isOk()) {
+                  list.emplace_back(std::move(desc));
+              }
+          }
+          return list;
+      }()),
+      mNonProxyDescList([this]() {
+          std::vector<Descriptor> list;
+          std::copy_if(mHalDescList.begin(), mHalDescList.end(), std::back_inserter(list),
+                       [](const Descriptor& desc) { return !desc.common.id.proxy.has_value(); });
+          return list;
+      }()),
+      mEffectCount(mNonProxyDescList.size() + mProxyDescList.size()) {
+    ALOG_ASSERT(mFactory != nullptr, "Provided IEffectsFactory service is NULL");
+    ALOGI("%s with %zu nonProxyEffects and %zu proxyEffects", __func__, mNonProxyDescList.size(),
+          mProxyDescList.size());
 }
 
 status_t EffectsFactoryHalAidl::queryNumberEffects(uint32_t *pNumEffects) {
@@ -55,11 +103,7 @@
         return BAD_VALUE;
     }
 
-    {
-        std::lock_guard lg(mLock);
-        RETURN_STATUS_IF_ERROR(queryEffectList_l());
-        *pNumEffects = mDescList->size();
-    }
+    *pNumEffects = mEffectCount;
     ALOGI("%s %d", __func__, *pNumEffects);
     return OK;
 }
@@ -69,42 +113,43 @@
         return BAD_VALUE;
     }
 
-    std::lock_guard lg(mLock);
-    RETURN_STATUS_IF_ERROR(queryEffectList_l());
-
-    auto listSize = mDescList->size();
-    if (index >= listSize) {
-        ALOGE("%s index %d exceed size DescList %zd", __func__, index, listSize);
+    if (index >= mEffectCount) {
+        ALOGE("%s index %d exceed max number %zu", __func__, index, mEffectCount);
         return INVALID_OPERATION;
     }
 
-    *pDescriptor = VALUE_OR_RETURN_STATUS(
-            ::aidl::android::aidl2legacy_Descriptor_effect_descriptor(mDescList->at(index)));
+    if (index >= mNonProxyDescList.size()) {
+        *pDescriptor =
+                VALUE_OR_RETURN_STATUS(::aidl::android::aidl2legacy_Descriptor_effect_descriptor(
+                        mProxyDescList.at(index - mNonProxyDescList.size())));
+    } else {
+        *pDescriptor =
+                VALUE_OR_RETURN_STATUS(::aidl::android::aidl2legacy_Descriptor_effect_descriptor(
+                        mNonProxyDescList.at(index)));
+    }
     return OK;
 }
 
 status_t EffectsFactoryHalAidl::getDescriptor(const effect_uuid_t* halUuid,
                                               effect_descriptor_t* pDescriptor) {
-    if (halUuid == nullptr || pDescriptor == nullptr) {
+    if (halUuid == nullptr) {
         return BAD_VALUE;
     }
 
-    AudioUuid uuid = VALUE_OR_RETURN_STATUS(
-            ::aidl::android::legacy2aidl_audio_uuid_t_AudioUuid(*halUuid));
-    std::lock_guard lg(mLock);
-    return getHalDescriptorWithImplUuid_l(uuid, pDescriptor);
+    AudioUuid uuid =
+            VALUE_OR_RETURN_STATUS(::aidl::android::legacy2aidl_audio_uuid_t_AudioUuid(*halUuid));
+    return getHalDescriptorWithImplUuid(uuid, pDescriptor);
 }
 
 status_t EffectsFactoryHalAidl::getDescriptors(const effect_uuid_t* halType,
                                                std::vector<effect_descriptor_t>* descriptors) {
-    if (halType == nullptr || descriptors == nullptr) {
+    if (halType == nullptr) {
         return BAD_VALUE;
     }
 
-    AudioUuid type = VALUE_OR_RETURN_STATUS(
-            ::aidl::android::legacy2aidl_audio_uuid_t_AudioUuid(*halType));
-    std::lock_guard lg(mLock);
-    return getHalDescriptorWithTypeUuid_l(type, descriptors);
+    AudioUuid type =
+            VALUE_OR_RETURN_STATUS(::aidl::android::legacy2aidl_audio_uuid_t_AudioUuid(*halType));
+    return getHalDescriptorWithTypeUuid(type, descriptors);
 }
 
 status_t EffectsFactoryHalAidl::createEffect(const effect_uuid_t* uuid, int32_t sessionId,
@@ -116,18 +161,25 @@
     if (sessionId == AUDIO_SESSION_DEVICE && ioId == AUDIO_IO_HANDLE_NONE) {
         return INVALID_OPERATION;
     }
-
     ALOGI("%s session %d ioId %d", __func__, sessionId, ioId);
 
-    AudioUuid aidlUuid = VALUE_OR_RETURN_STATUS(
-            ::aidl::android::legacy2aidl_audio_uuid_t_AudioUuid(*uuid));
+    AudioUuid aidlUuid =
+            VALUE_OR_RETURN_STATUS(::aidl::android::legacy2aidl_audio_uuid_t_AudioUuid(*uuid));
     std::shared_ptr<IEffect> aidlEffect;
-    Descriptor desc;
-    RETURN_STATUS_IF_ERROR(statusTFromBinderStatus(mFactory->createEffect(aidlUuid, &aidlEffect)));
+    // Use EffectProxy interface instead of IFactory to create
+    const bool isProxy = isProxyEffect(aidlUuid);
+    if (isProxy) {
+        aidlEffect = mUuidProxyMap.at(aidlUuid);
+        RETURN_STATUS_IF_ERROR(statusTFromBinderStatus(mUuidProxyMap.at(aidlUuid)->create()));
+    } else {
+        RETURN_STATUS_IF_ERROR(
+                statusTFromBinderStatus(mFactory->createEffect(aidlUuid, &aidlEffect)));
+    }
     if (aidlEffect == nullptr) {
-        ALOGE("%s IFactory::createFactory failed UUID %s", __func__, aidlUuid.toString().c_str());
+        ALOGE("%s failed to create effect with UUID: %s", __func__, aidlUuid.toString().c_str());
         return NAME_NOT_FOUND;
     }
+    Descriptor desc;
     RETURN_STATUS_IF_ERROR(statusTFromBinderStatus(aidlEffect->getDescriptor(&desc)));
 
     uint64_t effectId;
@@ -136,13 +188,23 @@
         effectId = ++mEffectIdCounter;
     }
 
-    *effect = sp<EffectHalAidl>::make(mFactory, aidlEffect, effectId, sessionId, ioId, desc);
+    *effect =
+            sp<EffectHalAidl>::make(mFactory, aidlEffect, effectId, sessionId, ioId, desc, isProxy);
     return OK;
 }
 
 status_t EffectsFactoryHalAidl::dumpEffects(int fd) {
-    // TODO: add proxy dump here because AIDL service EffectFactory doesn't have proxy handle
-    return mFactory->dump(fd, nullptr, 0);
+    status_t ret = OK;
+    // record the error ret and continue dump as many effects as possible
+    for (const auto& proxy : mUuidProxyMap) {
+        if (proxy.second) {
+            if (status_t temp = proxy.second->dump(fd, nullptr, 0); temp != OK) {
+                ret = temp;
+            }
+        }
+    }
+    RETURN_STATUS_IF_ERROR(mFactory->dump(fd, nullptr, 0));
+    return ret;
 }
 
 status_t EffectsFactoryHalAidl::allocateBuffer(size_t size, sp<EffectBufferHalInterface>* buffer) {
@@ -160,56 +222,42 @@
     return mHalVersion;
 }
 
-status_t EffectsFactoryHalAidl::queryEffectList_l() {
-    if (!mDescList) {
-        std::vector<Descriptor> list;
-        auto status = mFactory->queryEffects(std::nullopt, std::nullopt, std::nullopt, &list);
-        if (!status.isOk()) {
-            ALOGE("%s IFactory::queryEffects failed %s", __func__, status.getDescription().c_str());
-            return status.getStatus();
-        }
-
-        mDescList = std::make_unique<std::vector<Descriptor>>(list);
-    }
-    return OK;
-}
-
-status_t EffectsFactoryHalAidl::getHalDescriptorWithImplUuid_l(const AudioUuid& uuid,
-                                                               effect_descriptor_t* pDescriptor) {
+status_t EffectsFactoryHalAidl::getHalDescriptorWithImplUuid(const AudioUuid& uuid,
+                                                             effect_descriptor_t* pDescriptor) {
     if (pDescriptor == nullptr) {
         return BAD_VALUE;
     }
-    if (!mDescList) {
-        RETURN_STATUS_IF_ERROR(queryEffectList_l());
-    }
 
-    auto matchIt = std::find_if(mDescList->begin(), mDescList->end(),
-                                 [&](const auto& desc) { return desc.common.id.uuid == uuid; });
-    if (matchIt == mDescList->end()) {
-        ALOGE("%s UUID %s not found", __func__, uuid.toString().c_str());
+    const auto& list = isProxyEffect(uuid) ? mProxyDescList : mNonProxyDescList;
+    auto matchIt = std::find_if(list.begin(), list.end(),
+                                [&](const auto& desc) { return desc.common.id.uuid == uuid; });
+    if (matchIt == list.end()) {
+        ALOGE("%s UUID not found in HAL and proxy list %s", __func__, uuid.toString().c_str());
         return BAD_VALUE;
     }
+    ALOGI("%s UUID impl found %s", __func__, uuid.toString().c_str());
 
     *pDescriptor = VALUE_OR_RETURN_STATUS(
             ::aidl::android::aidl2legacy_Descriptor_effect_descriptor(*matchIt));
     return OK;
 }
 
-status_t EffectsFactoryHalAidl::getHalDescriptorWithTypeUuid_l(
+status_t EffectsFactoryHalAidl::getHalDescriptorWithTypeUuid(
         const AudioUuid& type, std::vector<effect_descriptor_t>* descriptors) {
     if (descriptors == nullptr) {
         return BAD_VALUE;
     }
-    if (!mDescList) {
-        RETURN_STATUS_IF_ERROR(queryEffectList_l());
-    }
+
     std::vector<Descriptor> result;
-    std::copy_if(mDescList->begin(), mDescList->end(), std::back_inserter(result),
+    std::copy_if(mNonProxyDescList.begin(), mNonProxyDescList.end(), std::back_inserter(result),
                  [&](auto& desc) { return desc.common.id.type == type; });
-    if (result.size() == 0) {
-        ALOGE("%s type UUID %s not found", __func__, type.toString().c_str());
+    std::copy_if(mProxyDescList.begin(), mProxyDescList.end(), std::back_inserter(result),
+                 [&](auto& desc) { return desc.common.id.type == type; });
+    if (result.empty()) {
+        ALOGW("%s UUID type not found in HAL and proxy list %s", __func__, type.toString().c_str());
         return BAD_VALUE;
     }
+    ALOGI("%s UUID type found %zu \n %s", __func__, result.size(), type.toString().c_str());
 
     *descriptors = VALUE_OR_RETURN_STATUS(
             aidl::android::convertContainer<std::vector<effect_descriptor_t>>(
@@ -217,6 +265,10 @@
     return OK;
 }
 
+bool EffectsFactoryHalAidl::isProxyEffect(const AudioUuid& uuid) const {
+    return 0 != mUuidProxyMap.count(uuid);
+}
+
 } // namespace effect
 
 // When a shared library is built from a static library, even explicit
diff --git a/media/libaudiohal/impl/EffectsFactoryHalAidl.h b/media/libaudiohal/impl/EffectsFactoryHalAidl.h
index 9c3643b..debfacf 100644
--- a/media/libaudiohal/impl/EffectsFactoryHalAidl.h
+++ b/media/libaudiohal/impl/EffectsFactoryHalAidl.h
@@ -25,6 +25,8 @@
 #include <media/audiohal/EffectsFactoryHalInterface.h>
 #include <system/thread_defs.h>
 
+#include "EffectProxy.h"
+
 namespace android {
 namespace effect {
 
@@ -60,24 +62,35 @@
 
     detail::AudioHalVersionInfo getHalVersion() const override;
 
-    // for TIME_CHECK
-    const std::string getClassName() const { return "EffectHalAidl"; }
-
   private:
-    std::mutex mLock;
     const std::shared_ptr<IFactory> mFactory;
-    uint64_t mEffectIdCounter GUARDED_BY(mLock) = 0; // Align with HIDL (0 is INVALID_ID)
-    std::unique_ptr<std::vector<Descriptor>> mDescList GUARDED_BY(mLock) = nullptr;
     const detail::AudioHalVersionInfo mHalVersion;
+    // Full list of HAL effect descriptors
+    const std::vector<Descriptor> mHalDescList;
+    // Map of proxy UUID (key) to the proxy object
+    const std::map<::aidl::android::media::audio::common::AudioUuid /* proxy impl UUID */,
+                   std::shared_ptr<EffectProxy>>
+            mUuidProxyMap;
+    // List of effect proxy, initialize after mUuidProxyMap because it need to have all sub-effects
+    const std::vector<Descriptor> mProxyDescList;
+    // List of non-proxy effects
+    const std::vector<Descriptor> mNonProxyDescList;
+    // total number of effects including proxy effects
+    const size_t mEffectCount;
+
+    std::mutex mLock;
+    uint64_t mEffectIdCounter GUARDED_BY(mLock) = 0;  // Align with HIDL (0 is INVALID_ID)
 
     virtual ~EffectsFactoryHalAidl() = default;
-    status_t queryEffectList_l() REQUIRES(mLock);
-    status_t getHalDescriptorWithImplUuid_l(
+    status_t getHalDescriptorWithImplUuid(
             const aidl::android::media::audio::common::AudioUuid& uuid,
-            effect_descriptor_t* pDescriptor) REQUIRES(mLock);
-    status_t getHalDescriptorWithTypeUuid_l(
+            effect_descriptor_t* pDescriptor);
+
+    status_t getHalDescriptorWithTypeUuid(
             const aidl::android::media::audio::common::AudioUuid& type,
-            std::vector<effect_descriptor_t>* descriptors) REQUIRES(mLock);
+            std::vector<effect_descriptor_t>* descriptors);
+
+    bool isProxyEffect(const aidl::android::media::audio::common::AudioUuid& uuid) const;
 };
 
 } // namespace effect