Merge "libstagefright: Check overflow during VideoFrame creation" into sc-dev
diff --git a/apex/Android.bp b/apex/Android.bp
index d198be0..aacb9a8 100644
--- a/apex/Android.bp
+++ b/apex/Android.bp
@@ -105,6 +105,8 @@
         "mediaswcodec",
     ],
     native_shared_libs: [
+        "libcodec2_hidl@1.0",
+        "libcodec2_hidl@1.1",
         "libstagefright_foundation",
     ],
     prebuilts: [
diff --git a/drm/libmediadrm/interface/mediadrm/DrmUtils.h b/drm/libmediadrm/interface/mediadrm/DrmUtils.h
index 4a169ee..c362aa6 100644
--- a/drm/libmediadrm/interface/mediadrm/DrmUtils.h
+++ b/drm/libmediadrm/interface/mediadrm/DrmUtils.h
@@ -127,7 +127,7 @@
     ::V1_4::IDrmPlugin::getLogMessages_cb cb = [&](
             ::V1_4::Status status,
             hidl_vec<::V1_4::LogMessage> hLogs) {
-        if (::V1_4::Status::OK == status) {
+        if (::V1_4::Status::OK != status) {
             err = status;
             return;
         }
diff --git a/media/codec2/hidl/1.0/utils/Android.bp b/media/codec2/hidl/1.0/utils/Android.bp
index f38a688..008def8 100644
--- a/media/codec2/hidl/1.0/utils/Android.bp
+++ b/media/codec2/hidl/1.0/utils/Android.bp
@@ -89,6 +89,7 @@
         "libbase",
         "libcodec2",
         "libcodec2_vndk",
+        "libcodec2_hidl_plugin_stub",
         "libcutils",
         "libhidlbase",
         "liblog",
@@ -102,9 +103,17 @@
         vendor: {
             exclude_shared_libs: [
                 "libstagefright_bufferqueue_helper_novndk",
+                "libcodec2_hidl_plugin_stub",
             ],
             shared_libs: [
                 "libstagefright_bufferqueue_helper",
+                "libcodec2_hidl_plugin",
+            ],
+        },
+        apex: {
+            exclude_shared_libs: [
+                "libcodec2_hidl_plugin",
+                "libcodec2_hidl_plugin_stub",
             ],
         },
     },
diff --git a/media/codec2/hidl/1.0/utils/Component.cpp b/media/codec2/hidl/1.0/utils/Component.cpp
index 8a84601..082c5e3 100644
--- a/media/codec2/hidl/1.0/utils/Component.cpp
+++ b/media/codec2/hidl/1.0/utils/Component.cpp
@@ -22,6 +22,10 @@
 #include <codec2/hidl/1.0/ComponentStore.h>
 #include <codec2/hidl/1.0/InputBufferManager.h>
 
+#ifndef __ANDROID_APEX__
+#include <FilterWrapper.h>
+#endif
+
 #include <hidl/HidlBinderSupport.h>
 #include <utils/Timers.h>
 
@@ -390,10 +394,17 @@
         uint32_t allocatorId,
         createBlockPool_cb _hidl_cb) {
     std::shared_ptr<C2BlockPool> blockPool;
+#ifdef __ANDROID_APEX__
     c2_status_t status = CreateCodec2BlockPool(
             static_cast<C2PlatformAllocatorStore::id_t>(allocatorId),
             mComponent,
             &blockPool);
+#else
+    c2_status_t status = ComponentStore::GetFilterWrapper()->createBlockPool(
+            static_cast<C2PlatformAllocatorStore::id_t>(allocatorId),
+            mComponent,
+            &blockPool);
+#endif
     if (status != C2_OK) {
         blockPool = nullptr;
     }
diff --git a/media/codec2/hidl/1.0/utils/ComponentStore.cpp b/media/codec2/hidl/1.0/utils/ComponentStore.cpp
index 9b9d449..1c0d5b0 100644
--- a/media/codec2/hidl/1.0/utils/ComponentStore.cpp
+++ b/media/codec2/hidl/1.0/utils/ComponentStore.cpp
@@ -35,6 +35,14 @@
 #include <ostream>
 #include <sstream>
 
+#ifndef __ANDROID_APEX__
+#include <codec2/hidl/plugin/FilterPlugin.h>
+#include <dlfcn.h>
+#include <C2Config.h>
+#include <DefaultFilterPlugin.h>
+#include <FilterWrapper.h>
+#endif
+
 namespace android {
 namespace hardware {
 namespace media {
@@ -176,6 +184,16 @@
     return mParameterCache;
 }
 
+#ifndef __ANDROID_APEX__
+// static
+std::shared_ptr<FilterWrapper> ComponentStore::GetFilterWrapper() {
+    constexpr const char kPluginPath[] = "libc2filterplugin.so";
+    static std::shared_ptr<FilterWrapper> wrapper = FilterWrapper::Create(
+            std::make_unique<DefaultFilterPlugin>(kPluginPath));
+    return wrapper;
+}
+#endif
+
 // Methods from ::android::hardware::media::c2::V1_0::IComponentStore
 Return<void> ComponentStore::createComponent(
         const hidl_string& name,
@@ -189,6 +207,9 @@
             mStore->createComponent(name, &c2component));
 
     if (status == Status::OK) {
+#ifndef __ANDROID_APEX__
+        c2component = GetFilterWrapper()->maybeWrapComponent(c2component);
+#endif
         onInterfaceLoaded(c2component->intf());
         component = new Component(c2component, listener, this, pool);
         if (!component) {
@@ -214,8 +235,12 @@
         createInterface_cb _hidl_cb) {
     std::shared_ptr<C2ComponentInterface> c2interface;
     c2_status_t res = mStore->createInterface(name, &c2interface);
+
     sp<IComponentInterface> interface;
     if (res == C2_OK) {
+#ifndef __ANDROID_APEX__
+        c2interface = GetFilterWrapper()->maybeWrapInterface(c2interface);
+#endif
         onInterfaceLoaded(c2interface);
         interface = new ComponentInterface(c2interface, mParameterCache);
     }
@@ -458,7 +483,6 @@
     return Void();
 }
 
-
 }  // namespace utils
 }  // namespace V1_0
 }  // namespace c2
diff --git a/media/codec2/hidl/1.0/utils/include/codec2/hidl/1.0/ComponentStore.h b/media/codec2/hidl/1.0/utils/include/codec2/hidl/1.0/ComponentStore.h
index fe7d048..27e2a05 100644
--- a/media/codec2/hidl/1.0/utils/include/codec2/hidl/1.0/ComponentStore.h
+++ b/media/codec2/hidl/1.0/utils/include/codec2/hidl/1.0/ComponentStore.h
@@ -37,6 +37,8 @@
 #include <vector>
 
 namespace android {
+class FilterWrapper;
+
 namespace hardware {
 namespace media {
 namespace c2 {
@@ -74,6 +76,8 @@
      */
     std::shared_ptr<ParameterCache> getParameterCache() const;
 
+    static std::shared_ptr<FilterWrapper> GetFilterWrapper();
+
     // Methods from ::android::hardware::media::c2::V1_0::IComponentStore.
     virtual Return<void> createComponent(
             const hidl_string& name,
diff --git a/media/codec2/hidl/1.1/utils/Android.bp b/media/codec2/hidl/1.1/utils/Android.bp
index 1d34ce9..839a910 100644
--- a/media/codec2/hidl/1.1/utils/Android.bp
+++ b/media/codec2/hidl/1.1/utils/Android.bp
@@ -100,6 +100,7 @@
         "libbase",
         "libcodec2",
         "libcodec2_hidl@1.0",
+        "libcodec2_hidl_plugin_stub",
         "libcodec2_vndk",
         "libcutils",
         "libhidlbase",
@@ -114,9 +115,17 @@
         vendor: {
             exclude_shared_libs: [
                 "libstagefright_bufferqueue_helper_novndk",
+                "libcodec2_hidl_plugin_stub",
             ],
             shared_libs: [
                 "libstagefright_bufferqueue_helper",
+                "libcodec2_hidl_plugin",
+            ],
+        },
+        apex: {
+            exclude_shared_libs: [
+                "libcodec2_hidl_plugin_stub",
+                "libcodec2_hidl_plugin",
             ],
         },
     },
diff --git a/media/codec2/hidl/1.1/utils/Component.cpp b/media/codec2/hidl/1.1/utils/Component.cpp
index ed281e6..1d7d3d8 100644
--- a/media/codec2/hidl/1.1/utils/Component.cpp
+++ b/media/codec2/hidl/1.1/utils/Component.cpp
@@ -22,6 +22,10 @@
 #include <codec2/hidl/1.1/ComponentStore.h>
 #include <codec2/hidl/1.1/InputBufferManager.h>
 
+#ifndef __ANDROID_APEX__
+#include <FilterWrapper.h>
+#endif
+
 #include <hidl/HidlBinderSupport.h>
 #include <utils/Timers.h>
 
@@ -390,10 +394,17 @@
         uint32_t allocatorId,
         createBlockPool_cb _hidl_cb) {
     std::shared_ptr<C2BlockPool> blockPool;
+#ifdef __ANDROID_APEX__
     c2_status_t status = CreateCodec2BlockPool(
             static_cast<C2PlatformAllocatorStore::id_t>(allocatorId),
             mComponent,
             &blockPool);
+#else
+    c2_status_t status = ComponentStore::GetFilterWrapper()->createBlockPool(
+            static_cast<C2PlatformAllocatorStore::id_t>(allocatorId),
+            mComponent,
+            &blockPool);
+#endif
     if (status != C2_OK) {
         blockPool = nullptr;
     }
diff --git a/media/codec2/hidl/1.1/utils/ComponentStore.cpp b/media/codec2/hidl/1.1/utils/ComponentStore.cpp
index 225cd09..163686d 100644
--- a/media/codec2/hidl/1.1/utils/ComponentStore.cpp
+++ b/media/codec2/hidl/1.1/utils/ComponentStore.cpp
@@ -35,6 +35,14 @@
 #include <ostream>
 #include <sstream>
 
+#ifndef __ANDROID_APEX__
+#include <codec2/hidl/plugin/FilterPlugin.h>
+#include <dlfcn.h>
+#include <C2Config.h>
+#include <DefaultFilterPlugin.h>
+#include <FilterWrapper.h>
+#endif
+
 namespace android {
 namespace hardware {
 namespace media {
@@ -176,6 +184,16 @@
     return mParameterCache;
 }
 
+#ifndef __ANDROID_APEX__
+// static
+std::shared_ptr<FilterWrapper> ComponentStore::GetFilterWrapper() {
+    constexpr const char kPluginPath[] = "libc2filterplugin.so";
+    static std::shared_ptr<FilterWrapper> wrapper = FilterWrapper::Create(
+            std::make_unique<DefaultFilterPlugin>(kPluginPath));
+    return wrapper;
+}
+#endif
+
 // Methods from ::android::hardware::media::c2::V1_0::IComponentStore
 Return<void> ComponentStore::createComponent(
         const hidl_string& name,
@@ -189,6 +207,9 @@
             mStore->createComponent(name, &c2component));
 
     if (status == Status::OK) {
+#ifndef __ANDROID_APEX__
+        c2component = GetFilterWrapper()->maybeWrapComponent(c2component);
+#endif
         onInterfaceLoaded(c2component->intf());
         component = new Component(c2component, listener, this, pool);
         if (!component) {
@@ -216,6 +237,9 @@
     c2_status_t res = mStore->createInterface(name, &c2interface);
     sp<IComponentInterface> interface;
     if (res == C2_OK) {
+#ifndef __ANDROID_APEX__
+        c2interface = GetFilterWrapper()->maybeWrapInterface(c2interface);
+#endif
         onInterfaceLoaded(c2interface);
         interface = new ComponentInterface(c2interface, mParameterCache);
     }
diff --git a/media/codec2/hidl/1.1/utils/include/codec2/hidl/1.1/ComponentStore.h b/media/codec2/hidl/1.1/utils/include/codec2/hidl/1.1/ComponentStore.h
index 1f04391..f6daee7 100644
--- a/media/codec2/hidl/1.1/utils/include/codec2/hidl/1.1/ComponentStore.h
+++ b/media/codec2/hidl/1.1/utils/include/codec2/hidl/1.1/ComponentStore.h
@@ -38,6 +38,8 @@
 #include <vector>
 
 namespace android {
+class FilterWrapper;
+
 namespace hardware {
 namespace media {
 namespace c2 {
@@ -75,6 +77,8 @@
      */
     std::shared_ptr<ParameterCache> getParameterCache() const;
 
+    static std::shared_ptr<FilterWrapper> GetFilterWrapper();
+
     // Methods from ::android::hardware::media::c2::V1_0::IComponentStore.
     virtual Return<void> createComponent(
             const hidl_string& name,
diff --git a/media/codec2/hidl/plugin/Android.bp b/media/codec2/hidl/plugin/Android.bp
new file mode 100644
index 0000000..4708b12
--- /dev/null
+++ b/media/codec2/hidl/plugin/Android.bp
@@ -0,0 +1,66 @@
+cc_library_headers {
+    name: "libcodec2_hidl_plugin_headers",
+    vendor_available: true,
+    export_include_dirs: [
+        "include",
+    ],
+}
+
+cc_library {
+    name: "libcodec2_hidl_plugin_stub",
+
+    srcs: [
+        "DefaultFilterPlugin.cpp",
+        "FilterWrapperStub.cpp",
+    ],
+
+    header_libs: [
+        "libcodec2_internal", // private
+    ],
+
+    shared_libs: [
+        "libbase",
+        "libcodec2",
+        "libcodec2_vndk",
+        "liblog",
+        "libutils",
+    ],
+
+    export_include_dirs: [
+        "include",
+        "internal",
+    ],
+
+    apex_available: [
+        "//apex_available:platform",
+        "com.android.media.swcodec",
+    ],
+    min_sdk_version: "29",
+}
+
+cc_library {
+    name: "libcodec2_hidl_plugin",
+    vendor: true,
+
+    srcs: [
+        "DefaultFilterPlugin.cpp",
+        "FilterWrapper.cpp",
+    ],
+
+    header_libs: [
+        "libcodec2_internal", // private
+    ],
+
+    shared_libs: [
+        "libbase",
+        "libcodec2",
+        "libcodec2_vndk",
+        "liblog",
+        "libutils",
+    ],
+
+    export_include_dirs: [
+        "include",
+        "internal",
+    ],
+}
diff --git a/media/codec2/hidl/plugin/DefaultFilterPlugin.cpp b/media/codec2/hidl/plugin/DefaultFilterPlugin.cpp
new file mode 100644
index 0000000..cd1bcb0
--- /dev/null
+++ b/media/codec2/hidl/plugin/DefaultFilterPlugin.cpp
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "Codec2-DefaultFilterPlugin"
+#include <android-base/logging.h>
+
+#include <set>
+
+#include <dlfcn.h>
+
+#include <C2Config.h>
+#include <C2Debug.h>
+#include <C2ParamInternal.h>
+
+#include <codec2/hidl/plugin/FilterPlugin.h>
+
+#include <DefaultFilterPlugin.h>
+#include <FilterWrapper.h>
+
+namespace android {
+
+DefaultFilterPlugin::DefaultFilterPlugin(const char *pluginPath)
+    : mInit(NO_INIT),
+      mHandle(nullptr),
+      mDestroyPlugin(nullptr),
+      mPlugin(nullptr) {
+    mHandle = dlopen(pluginPath, RTLD_NOW | RTLD_NODELETE);
+    if (!mHandle) {
+        LOG(DEBUG) << "FilterPlugin: no plugin detected";
+        return;
+    }
+    GetFilterPluginVersionFunc getVersion =
+        (GetFilterPluginVersionFunc)dlsym(mHandle, "GetFilterPluginVersion");
+    if (!getVersion) {
+        LOG(WARNING) << "FilterPlugin: GetFilterPluginVersion undefined";
+        return;
+    }
+    int32_t version = getVersion();
+    if (version != FilterPlugin_V1::VERSION) {
+        LOG(WARNING) << "FilterPlugin: unrecognized version (" << version << ")";
+        return;
+    }
+    CreateFilterPluginFunc createPlugin =
+        (CreateFilterPluginFunc)dlsym(mHandle, "CreateFilterPlugin");
+    if (!createPlugin) {
+        LOG(WARNING) << "FilterPlugin: CreateFilterPlugin undefined";
+        return;
+    }
+    mDestroyPlugin =
+        (DestroyFilterPluginFunc)dlsym(mHandle, "DestroyFilterPlugin");
+    if (!mDestroyPlugin) {
+        LOG(WARNING) << "FilterPlugin: DestroyFilterPlugin undefined";
+        return;
+    }
+    mPlugin = (FilterPlugin_V1 *)createPlugin();
+    if (!mPlugin) {
+        LOG(WARNING) << "FilterPlugin: CreateFilterPlugin returned nullptr";
+        return;
+    }
+    mStore = mPlugin->getComponentStore();
+    if (!mStore) {
+        LOG(WARNING) << "FilterPlugin: FilterPlugin_V1::getComponentStore returned nullptr";
+        return;
+    }
+    mInit = OK;
+}
+
+DefaultFilterPlugin::~DefaultFilterPlugin() {
+    if (mHandle) {
+        if (mDestroyPlugin && mPlugin) {
+            mDestroyPlugin(mPlugin);
+            mPlugin = nullptr;
+        }
+        dlclose(mHandle);
+        mHandle = nullptr;
+        mDestroyPlugin = nullptr;
+    }
+}
+
+bool DefaultFilterPlugin::describe(C2String name, FilterWrapper::Descriptor *desc) {
+    if (mInit != OK) {
+        return false;
+    }
+    return mPlugin->describe(name, desc);
+}
+
+bool DefaultFilterPlugin::isFilteringEnabled(const std::shared_ptr<C2ComponentInterface> &intf) {
+    if (mInit != OK) {
+        return false;
+    }
+    return mPlugin->isFilteringEnabled(intf);
+}
+
+}  // namespace android
diff --git a/media/codec2/hidl/plugin/FilterWrapper.cpp b/media/codec2/hidl/plugin/FilterWrapper.cpp
new file mode 100644
index 0000000..0b38bc1
--- /dev/null
+++ b/media/codec2/hidl/plugin/FilterWrapper.cpp
@@ -0,0 +1,921 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "Codec2-FilterWrapper"
+#include <android-base/logging.h>
+
+#include <set>
+#include <sstream>
+
+#include <dlfcn.h>
+
+#include <C2Config.h>
+#include <C2Debug.h>
+#include <C2ParamInternal.h>
+
+#include <codec2/hidl/plugin/FilterPlugin.h>
+
+#include <FilterWrapper.h>
+
+namespace android {
+
+namespace {
+
+// Indices that the last filter in the chain should consume.
+static constexpr uint32_t kTypesForLastFilter[] = {
+    // In case we have an output surface, we want to use the block pool
+    // backed by the output surface for the output buffer going to the client.
+    C2PortBlockPoolsTuning::output::PARAM_TYPE,
+};
+
+class WrappedDecoderInterface : public C2ComponentInterface {
+public:
+    WrappedDecoderInterface(
+            std::shared_ptr<C2ComponentInterface> intf,
+            std::vector<FilterWrapper::Component> &&filters)
+        : mIntf(intf) {
+        takeFilters(std::move(filters));
+    }
+
+    ~WrappedDecoderInterface() override = default;
+
+    void takeFilters(std::vector<FilterWrapper::Component> &&filters) {
+        std::unique_lock lock(mMutex);
+        std::vector<std::unique_ptr<C2Param>> lastFilterParams;
+        if (!mFilters.empty()) {
+            std::vector<C2Param::Index> indices;
+            std::vector<std::shared_ptr<C2ParamDescriptor>> paramDescs;
+            c2_status_t err = mFilters.back().intf->querySupportedParams_nb(&paramDescs);
+            if (err != C2_OK) {
+                LOG(WARNING) << "WrappedDecoderInterface: " << mFilters.back().traits.name
+                        << " returned error for querySupportedParams_nb; err=" << err;
+                paramDescs.clear();
+            }
+            for (const std::shared_ptr<C2ParamDescriptor> &paramDesc : paramDescs) {
+                C2Param::Index index = paramDesc->index();
+                if (std::count(
+                            std::begin(kTypesForLastFilter),
+                            std::end(kTypesForLastFilter),
+                            index.type()) != 0) {
+                    if (index.forStream()) {
+                        // querySupportedParams does not return per-stream params.
+                        // We only support stream-0 for now.
+                        index = index.withStream(0u);
+                    }
+                    indices.push_back(index);
+                }
+            }
+            if (!indices.empty()) {
+                mFilters.back().intf->query_vb({}, indices, C2_MAY_BLOCK, &lastFilterParams);
+            }
+        }
+
+        // TODO: documentation
+        mFilters = std::move(filters);
+        mTypeToIndexForQuery.clear();
+        mTypeToIndexForConfig.clear();
+        for (size_t i = 0; i < mFilters.size(); ++i) {
+            if (i == 0) {
+                transferParams_l(mIntf, mFilters[0].intf, C2_MAY_BLOCK);
+            } else {
+                transferParams_l(mFilters[i - 1].intf, mFilters[i].intf, C2_MAY_BLOCK);
+            }
+            for (C2Param::Type type : mFilters[i].desc.controlParams) {
+                mTypeToIndexForQuery[type.type()] = i;
+                mTypeToIndexForConfig[type.type() & ~C2Param::CoreIndex::IS_REQUEST_FLAG] = i;
+            }
+            for (C2Param::Type type : mFilters[i].desc.affectedParams) {
+                mTypeToIndexForQuery[type.type()] = i;
+            }
+        }
+        if (!mFilters.empty()) {
+            for (uint32_t type : kTypesForLastFilter) {
+                mTypeToIndexForQuery[type] = mFilters.size() - 1;
+                mTypeToIndexForConfig[type & ~C2Param::CoreIndex::IS_REQUEST_FLAG] =
+                    mFilters.size() - 1;
+            }
+            if (!lastFilterParams.empty()) {
+                std::vector<C2Param *> paramPtrs(lastFilterParams.size());
+                std::transform(
+                        lastFilterParams.begin(),
+                        lastFilterParams.end(),
+                        paramPtrs.begin(),
+                        [](const std::unique_ptr<C2Param> &param) {
+                            return param.get();
+                        });
+                std::vector<std::unique_ptr<C2SettingResult>> failures;
+                mFilters.back().intf->config_vb(paramPtrs, C2_MAY_BLOCK, &failures);
+            }
+        }
+    }
+
+    C2String getName() const override { return mIntf->getName(); }
+
+    c2_node_id_t getId() const override { return mIntf->getId(); }
+
+    c2_status_t query_vb(
+            const std::vector<C2Param *> &stackParams,
+            const std::vector<C2Param::Index> &heapParamIndices,
+            c2_blocking_t mayBlock,
+            std::vector<std::unique_ptr<C2Param>>* const heapParams) const override {
+        std::unique_lock lock(mMutex);
+        std::list<C2Param *> stackParamsList(stackParams.size());
+        std::copy_n(stackParams.begin(), stackParams.size(), stackParamsList.begin());
+        heapParams->clear();
+        c2_status_t result = C2_OK;
+        // TODO: loop optimization
+        for (size_t i = 0; i < mFilters.size(); ++i) {
+            // Filter stack params according to mTypeToIndexForQuery
+            std::vector<C2Param *> stackParamsForFilter;
+            for (auto it = stackParamsList.begin(); it != stackParamsList.end(); ) {
+                C2Param *param = *it;
+                uint32_t type = param->type().type();
+                auto it2 = mTypeToIndexForQuery.find(type);
+                if (it2 == mTypeToIndexForQuery.end() || it2->second != i) {
+                    ++it;
+                    continue;
+                }
+                stackParamsForFilter.push_back(param);
+                it = stackParamsList.erase(it);
+            }
+            // Filter heap params according to mTypeToIndexForQuery
+            std::vector<C2Param::Index> heapParamIndicesForFilter;
+            for (size_t j = 0; j < heapParamIndices.size(); ++j) {
+                uint32_t type = heapParamIndices[j].type();
+                auto it = mTypeToIndexForQuery.find(type);
+                if (it == mTypeToIndexForQuery.end() || it->second != i) {
+                    continue;
+                }
+                heapParamIndicesForFilter.push_back(heapParamIndices[j]);
+            }
+            std::vector<std::unique_ptr<C2Param>> heapParamsForFilter;
+            const std::shared_ptr<C2ComponentInterface> &filter = mFilters[i].intf;
+            c2_status_t err = filter->query_vb(
+                    stackParamsForFilter, heapParamIndicesForFilter, mayBlock,
+                    &heapParamsForFilter);
+            if (err != C2_OK && err != C2_BAD_INDEX) {
+                LOG(WARNING) << "WrappedDecoderInterface: " << filter->getName()
+                        << " returned error for query_vb; err=" << err;
+                result = err;
+                continue;
+            }
+            heapParams->insert(
+                    heapParams->end(),
+                    std::make_move_iterator(heapParamsForFilter.begin()),
+                    std::make_move_iterator(heapParamsForFilter.end()));
+        }
+
+        std::vector<C2Param *> stackParamsForIntf;
+        std::copy_n(stackParamsList.begin(), stackParamsList.size(), stackParamsForIntf.begin());
+
+        // Gather heap params that did not get queried from the filter interfaces above.
+        // These need to be queried from the decoder interface.
+        std::vector<C2Param::Index> heapParamIndicesForIntf;
+        for (size_t j = 0; j < heapParamIndices.size(); ++j) {
+            uint32_t type = heapParamIndices[j].type();
+            if (mTypeToIndexForQuery.find(type) != mTypeToIndexForQuery.end()) {
+                continue;
+            }
+            heapParamIndicesForIntf.push_back(heapParamIndices[j]);
+        }
+
+        std::vector<std::unique_ptr<C2Param>> heapParamsForIntf;
+        c2_status_t err = mIntf->query_vb(
+                stackParamsForIntf, heapParamIndicesForIntf, mayBlock, &heapParamsForIntf);
+        if (err != C2_OK) {
+            LOG(err == C2_BAD_INDEX ? VERBOSE : WARNING)
+                    << "WrappedDecoderInterface: " << mIntf->getName()
+                    << " returned error for query_vb; err=" << err;
+            result = err;
+        }
+
+        // TODO: params needs to preserve the order
+        heapParams->insert(
+                heapParams->end(),
+                std::make_move_iterator(heapParamsForIntf.begin()),
+                std::make_move_iterator(heapParamsForIntf.end()));
+
+        return result;
+    }
+
+    c2_status_t config_vb(
+            const std::vector<C2Param *> &params,
+            c2_blocking_t mayBlock,
+            std::vector<std::unique_ptr<C2SettingResult>>* const failures) override {
+        std::unique_lock lock(mMutex);
+        c2_status_t result = C2_OK;
+        std::vector<C2Param *> paramsForIntf;
+        for (C2Param* param : params) {
+            auto it = mTypeToIndexForConfig.find(param->type().type());
+            if (it != mTypeToIndexForConfig.end()) {
+                continue;
+            }
+            paramsForIntf.push_back(param);
+        }
+        c2_status_t err = mIntf->config_vb(paramsForIntf, mayBlock, failures);
+        if (err != C2_OK) {
+            LOG(err == C2_BAD_INDEX ? VERBOSE : WARNING)
+                    << "WrappedDecoderInterface: " << mIntf->getName()
+                    << " returned error for config_vb; err=" << err;
+            result = err;
+        }
+        for (size_t i = 0; i < mFilters.size(); ++i) {
+            if (i == 0) {
+                transferParams_l(mIntf, mFilters[0].intf, mayBlock);
+            } else {
+                transferParams_l(mFilters[i - 1].intf, mFilters[i].intf, mayBlock);
+            }
+            const std::shared_ptr<C2ComponentInterface> &filter = mFilters[i].intf;
+            std::vector<std::unique_ptr<C2SettingResult>> filterFailures;
+            std::vector<C2Param *> paramsForFilter;
+            for (C2Param* param : params) {
+                auto it = mTypeToIndexForConfig.find(param->type().type());
+                if (it != mTypeToIndexForConfig.end() && it->second != i) {
+                    continue;
+                }
+                paramsForFilter.push_back(param);
+            }
+            c2_status_t err = filter->config_vb(paramsForFilter, mayBlock, &filterFailures);
+            if (err != C2_OK) {
+                LOG(err == C2_BAD_INDEX ? VERBOSE : WARNING)
+                        << "WrappedDecoderInterface: " << filter->getName()
+                        << " returned error for config_vb; err=" << err;
+                result = err;
+            }
+        }
+
+        return result;
+    }
+
+    c2_status_t createTunnel_sm(c2_node_id_t) override { return C2_OMITTED; }
+    c2_status_t releaseTunnel_sm(c2_node_id_t) override { return C2_OMITTED; }
+
+    c2_status_t querySupportedParams_nb(
+            std::vector<std::shared_ptr<C2ParamDescriptor>> * const params) const override {
+        std::unique_lock lock(mMutex);
+        c2_status_t result = mIntf->querySupportedParams_nb(params);
+        if (result != C2_OK) {
+            LOG(WARNING) << "WrappedDecoderInterface: " << mIntf->getName()
+                    << " returned error for querySupportedParams_nb; err=" << result;
+            return result;
+        }
+        // TODO: optimization idea --- pre-compute at takeFilter().
+        for (const FilterWrapper::Component &filter : mFilters) {
+            std::vector<std::shared_ptr<C2ParamDescriptor>> filterParams;
+            c2_status_t err = filter.intf->querySupportedParams_nb(&filterParams);
+            if (err != C2_OK) {
+                LOG(WARNING) << "WrappedDecoderInterface: " << filter.intf->getName()
+                        << " returned error for querySupportedParams_nb; err=" << result;
+                result = err;
+                continue;
+            }
+            for (const std::shared_ptr<C2ParamDescriptor> &paramDesc : filterParams) {
+                if (std::count(
+                        filter.desc.controlParams.begin(),
+                        filter.desc.controlParams.end(),
+                        paramDesc->index().type()) == 0) {
+                    continue;
+                }
+                params->push_back(paramDesc);
+            }
+        }
+        return result;
+    }
+
+    c2_status_t querySupportedValues_vb(
+            std::vector<C2FieldSupportedValuesQuery> &fields,
+            c2_blocking_t mayBlock) const override {
+        std::unique_lock lock(mMutex);
+        c2_status_t result = mIntf->querySupportedValues_vb(fields, mayBlock);
+        if (result != C2_OK && result != C2_BAD_INDEX) {
+            LOG(WARNING) << "WrappedDecoderInterface: " << mIntf->getName()
+                    << " returned error for querySupportedParams_nb; err=" << result;
+            return result;
+        }
+        for (const FilterWrapper::Component &filter : mFilters) {
+            std::vector<C2FieldSupportedValuesQuery> filterFields;
+            std::vector<size_t> indices;
+            for (size_t i = 0; i < fields.size(); ++i) {
+                const C2FieldSupportedValuesQuery &field = fields[i];
+                uint32_t type = C2Param::Index(_C2ParamInspector::GetIndex(field.field())).type();
+                if (std::count(
+                        filter.desc.controlParams.begin(),
+                        filter.desc.controlParams.end(),
+                        type) == 0) {
+                    continue;
+                }
+                filterFields.push_back(field);
+                indices.push_back(i);
+            }
+            c2_status_t err = filter.intf->querySupportedValues_vb(filterFields, mayBlock);
+            if (err != C2_OK && err != C2_BAD_INDEX) {
+                LOG(WARNING) << "WrappedDecoderInterface: " << filter.intf->getName()
+                        << " returned error for querySupportedParams_nb; err=" << result;
+                result = err;
+                continue;
+            }
+            for (size_t i = 0; i < filterFields.size(); ++i) {
+                fields[indices[i]] = filterFields[i];
+            }
+        }
+        return result;
+    }
+
+private:
+    mutable std::mutex mMutex;
+    std::shared_ptr<C2ComponentInterface> mIntf;
+    std::vector<FilterWrapper::Component> mFilters;
+    std::map<uint32_t, size_t> mTypeToIndexForQuery;
+    std::map<uint32_t, size_t> mTypeToIndexForConfig;
+
+    c2_status_t transferParams_l(
+            const std::shared_ptr<C2ComponentInterface> &curr,
+            const std::shared_ptr<C2ComponentInterface> &next,
+            c2_blocking_t mayBlock) {
+        // NOTE: this implementation is preliminary --- it could change once
+        // we define what parameters needs to be propagated in component chaining.
+        std::vector<std::shared_ptr<C2ParamDescriptor>> paramDescs;
+        c2_status_t err = next->querySupportedParams_nb(&paramDescs);
+        if (err != C2_OK) {
+            LOG(DEBUG) << "WrappedDecoderInterface: " << next->getName()
+                    << " returned error for querySupportedParams_nb; err=" << err;
+            return err;
+        }
+        // Find supported input params from the next interface and flip direction
+        // so they become output params.
+        std::vector<C2Param::Index> indices;
+        for (const std::shared_ptr<C2ParamDescriptor> &paramDesc : paramDescs) {
+            C2Param::Index index = paramDesc->index();
+            if (!index.forInput() || paramDesc->isReadOnly()) {
+                continue;
+            }
+            if (index.forStream()) {
+                uint32_t stream = index.stream();
+                index = index.withPort(true /* output */).withStream(stream);
+            } else {
+                index = index.withPort(true /* output */);
+            }
+            indices.push_back(index);
+        }
+        // Query those output params from the current interface
+        std::vector<std::unique_ptr<C2Param>> heapParams;
+        err = curr->query_vb({}, indices, mayBlock, &heapParams);
+        if (err != C2_OK && err != C2_BAD_INDEX) {
+            LOG(DEBUG) << "WrappedDecoderInterface: " << curr->getName()
+                    << " returned error for query_vb; err=" << err;
+            return err;
+        }
+        // Flip the direction of the queried params, so they become input parameters.
+        // Configure the next interface with the params.
+        std::vector<C2Param *> configParams;
+        for (size_t i = 0; i < heapParams.size(); ++i) {
+            if (heapParams[i]->forStream()) {
+                heapParams[i] = C2Param::CopyAsStream(
+                        *heapParams[i], false /* output */, heapParams[i]->stream());
+            } else {
+                heapParams[i] = C2Param::CopyAsPort(*heapParams[i], false /* output */);
+            }
+            configParams.push_back(heapParams[i].get());
+        }
+        std::vector<std::unique_ptr<C2SettingResult>> failures;
+        err = next->config_vb(configParams, mayBlock, &failures);
+        if (err != C2_OK && err != C2_BAD_INDEX) {
+            LOG(DEBUG) << "WrappedDecoderInterface: " << next->getName()
+                    << " returned error for config_vb; err=" << err;
+            return err;
+        }
+        return C2_OK;
+    }
+};
+
+class WrappedDecoder : public C2Component, public std::enable_shared_from_this<WrappedDecoder> {
+public:
+    WrappedDecoder(
+            std::shared_ptr<C2Component> comp,
+            std::vector<FilterWrapper::Component> &&filters,
+            std::weak_ptr<FilterWrapper> filterWrapper)
+        : mComp(comp), mFilters(std::move(filters)), mFilterWrapper(filterWrapper) {
+        std::vector<FilterWrapper::Component> filtersDup(mFilters);
+        mIntf = std::make_shared<WrappedDecoderInterface>(
+                comp->intf(), std::move(filtersDup));
+    }
+
+    ~WrappedDecoder() override = default;
+
+    std::shared_ptr<C2ComponentInterface> intf() override { return mIntf; }
+
+    c2_status_t setListener_vb(
+            const std::shared_ptr<Listener> &listener, c2_blocking_t mayBlock) override {
+        if (listener) {
+            setListenerInternal(mFilters, listener, mayBlock);
+        } else {
+            mComp->setListener_vb(nullptr, mayBlock);
+            for (FilterWrapper::Component &filter : mFilters) {
+                filter.comp->setListener_vb(nullptr, mayBlock);
+            }
+        }
+        mListener = listener;
+        return C2_OK;
+    }
+
+    c2_status_t queue_nb(std::list<std::unique_ptr<C2Work>>* const items) override {
+        return mComp->queue_nb(items);
+    }
+
+    c2_status_t announce_nb(const std::vector<C2WorkOutline> &) override {
+        return C2_OMITTED;
+    }
+
+    c2_status_t flush_sm(
+            flush_mode_t mode, std::list<std::unique_ptr<C2Work>>* const flushedWork) override {
+        c2_status_t result = mComp->flush_sm(mode, flushedWork);
+        std::list<std::unique_ptr<C2Work>> filterFlushedWork;
+        for (FilterWrapper::Component filter : mRunningFilters) {
+            c2_status_t err = filter.comp->flush_sm(mode, &filterFlushedWork);
+            if (err != C2_OK) {
+                result = err;
+            }
+            flushedWork->splice(flushedWork->end(), filterFlushedWork);
+        }
+        return result;
+    }
+
+    c2_status_t drain_nb(drain_mode_t mode) override {
+        // TODO: simplify using comp->drain_nb(mode)
+        switch (mode) {
+        case DRAIN_COMPONENT_WITH_EOS: {
+            std::unique_ptr<C2Work> eosWork{new C2Work};
+            eosWork->input.flags = C2FrameData::FLAG_END_OF_STREAM;
+            eosWork->worklets.push_back(std::make_unique<C2Worklet>());
+            std::list<std::unique_ptr<C2Work>> items;
+            items.push_back(std::move(eosWork));
+            mComp->queue_nb(&items);
+            return C2_OK;
+        }
+        case DRAIN_COMPONENT_NO_EOS:
+        case DRAIN_CHAIN:
+        default:
+            return C2_BAD_VALUE;
+        }
+    }
+
+    c2_status_t start() override {
+        std::vector<FilterWrapper::Component> filters;
+        if (std::shared_ptr<FilterWrapper> filterWrapper = mFilterWrapper.lock()) {
+            // Let's check if we have filters that we can skip
+            for (FilterWrapper::Component &filter : mFilters) {
+                if (!filterWrapper->isFilteringEnabled(filter.intf)) {
+                    LOG(VERBOSE) << "filtering disabled for " << filter.traits.name;
+                    continue;
+                }
+                LOG(VERBOSE) << "filtering enabled for " << filter.traits.name;
+                filters.push_back(filter);
+            }
+            if (filters.size() < mFilters.size()) {
+                LOG(VERBOSE) << (mFilters.size() - filters.size()) << " filter(s) skipped";
+                setListenerInternal(filters, mListener, C2_MAY_BLOCK);
+                std::vector filtersCopy(filters);
+                mIntf->takeFilters(std::move(filtersCopy));
+            }
+        }
+
+        c2_status_t err = mComp->start();
+        if (err != C2_OK) {
+            return err;
+        }
+        for (FilterWrapper::Component &filter : filters) {
+            c2_status_t err = filter.comp->start();
+            if (err != C2_OK) {
+                // Previous components are already started successfully;
+                // we ended up in an incoherent state.
+                return C2_CORRUPTED;
+            }
+        }
+        mRunningFilters = std::move(filters);
+        return C2_OK;
+    }
+
+    c2_status_t stop() override {
+        c2_status_t err = mComp->stop();
+        if (err != C2_OK) {
+            return err;
+        }
+        for (FilterWrapper::Component filter : mRunningFilters) {
+            c2_status_t err = filter.comp->stop();
+            if (err != C2_OK) {
+                // Previous components are already stopped successfully;
+                // we ended up in an incoherent state.
+                return C2_CORRUPTED;
+            }
+        }
+        mRunningFilters.clear();
+        return C2_OK;
+    }
+
+    c2_status_t reset() override {
+        c2_status_t result = mComp->reset();
+        if (result != C2_OK) {
+            result = C2_CORRUPTED;
+        }
+        for (FilterWrapper::Component filter : mFilters) {
+            c2_status_t err = filter.comp->reset();
+            if (err != C2_OK) {
+                // Previous components are already reset successfully;
+                // we ended up in an incoherent state.
+                result = C2_CORRUPTED;
+                // continue for the rest of the chain
+            }
+        }
+        mRunningFilters.clear();
+        return result;
+    }
+
+    c2_status_t release() override {
+        c2_status_t result = mComp->release();
+        if (result != C2_OK) {
+            result = C2_CORRUPTED;
+        }
+        for (FilterWrapper::Component filter : mFilters) {
+            c2_status_t err = filter.comp->release();
+            if (err != C2_OK) {
+                // Previous components are already released successfully;
+                // we ended up in an incoherent state.
+                result = C2_CORRUPTED;
+                // continue for the rest of the chain
+            }
+        }
+        mRunningFilters.clear();
+        return result;
+    }
+
+private:
+    class PassingListener : public Listener {
+    public:
+        PassingListener(
+                std::shared_ptr<C2Component> wrappedComponent,
+                const std::shared_ptr<Listener> &wrappedComponentListener,
+                std::shared_ptr<C2Component> nextComponent)
+            : mWrappedComponent(wrappedComponent),
+              mWrappedComponentListener(wrappedComponentListener),
+              mNextComponent(nextComponent) {
+        }
+
+        void onWorkDone_nb(
+                std::weak_ptr<C2Component>,
+                std::list<std::unique_ptr<C2Work>> workItems) override {
+            std::shared_ptr<C2Component> nextComponent = mNextComponent.lock();
+            std::list<std::unique_ptr<C2Work>> failedWorkItems;
+            if (!nextComponent) {
+                for (std::unique_ptr<C2Work> &work : workItems) {
+                    // Next component unexpectedly released while the work is
+                    // in-flight. Report C2_CORRUPTED to the client.
+                    work->result = C2_CORRUPTED;
+                    failedWorkItems.push_back(std::move(work));
+                }
+                workItems.clear();
+            } else {
+                for (auto it = workItems.begin(); it != workItems.end(); ) {
+                    const std::unique_ptr<C2Work> &work = *it;
+                    if (work->result != C2_OK
+                            || work->worklets.size() != 1) {
+                        failedWorkItems.push_back(std::move(*it));
+                        it = workItems.erase(it);
+                        continue;
+                    }
+                    C2FrameData &output = work->worklets.front()->output;
+                    c2_cntr64_t customOrdinal = work->input.ordinal.customOrdinal;
+                    work->input = std::move(output);
+                    work->input.ordinal.customOrdinal = customOrdinal;
+                    output.flags = C2FrameData::flags_t(0);
+                    output.buffers.clear();
+                    output.configUpdate.clear();
+                    output.infoBuffers.clear();
+                    ++it;
+                }
+            }
+            if (!failedWorkItems.empty()) {
+                for (const std::unique_ptr<C2Work> &work : failedWorkItems) {
+                    LOG(VERBOSE) << "work #" << work->input.ordinal.frameIndex.peek()
+                            << " failed: err=" << work->result
+                            << " worklets.size()=" << work->worklets.size();
+                }
+                if (std::shared_ptr<Listener> wrappedComponentListener =
+                        mWrappedComponentListener.lock()) {
+                    wrappedComponentListener->onWorkDone_nb(
+                            mWrappedComponent, std::move(failedWorkItems));
+                }
+            }
+            if (!workItems.empty()) {
+                nextComponent->queue_nb(&workItems);
+            }
+        }
+
+        void onTripped_nb(
+                std::weak_ptr<C2Component>,
+                std::vector<std::shared_ptr<C2SettingResult>>) override {
+            // Trip not supported
+        }
+
+        void onError_nb(std::weak_ptr<C2Component>, uint32_t errorCode) {
+            if (std::shared_ptr<Listener> wrappedComponentListener =
+                    mWrappedComponentListener.lock()) {
+                wrappedComponentListener->onError_nb(mWrappedComponent, errorCode);
+            }
+        }
+
+    private:
+        std::weak_ptr<C2Component> mWrappedComponent;
+        std::weak_ptr<Listener> mWrappedComponentListener;
+        std::weak_ptr<C2Component> mNextComponent;
+    };
+
+    class LastListener : public Listener {
+    public:
+        LastListener(
+                std::shared_ptr<C2Component> wrappedComponent,
+                const std::shared_ptr<Listener> &wrappedComponentListener)
+            : mWrappedComponent(wrappedComponent),
+              mWrappedComponentListener(wrappedComponentListener) {
+        }
+
+        void onWorkDone_nb(
+                std::weak_ptr<C2Component>,
+                std::list<std::unique_ptr<C2Work>> workItems) override {
+            if (mWrappedComponent.expired()) {
+                return;
+            }
+            if (std::shared_ptr<Listener> wrappedComponentListener =
+                    mWrappedComponentListener.lock()) {
+                wrappedComponentListener->onWorkDone_nb(
+                        mWrappedComponent, std::move(workItems));
+            }
+        }
+
+        void onTripped_nb(
+                std::weak_ptr<C2Component>,
+                std::vector<std::shared_ptr<C2SettingResult>>) override {
+            // Trip not supported
+        }
+
+        void onError_nb(std::weak_ptr<C2Component>, uint32_t errorCode) {
+            if (mWrappedComponent.expired()) {
+                return;
+            }
+            if (std::shared_ptr<Listener> wrappedComponentListener =
+                    mWrappedComponentListener.lock()) {
+                wrappedComponentListener->onError_nb(mWrappedComponent, errorCode);
+            }
+        }
+
+    private:
+        std::weak_ptr<C2Component> mWrappedComponent;
+        std::weak_ptr<Listener> mWrappedComponentListener;
+    };
+
+    std::shared_ptr<C2Component> mComp;
+    std::shared_ptr<WrappedDecoderInterface> mIntf;
+    std::vector<FilterWrapper::Component> mFilters;
+    std::vector<FilterWrapper::Component> mRunningFilters;
+    std::weak_ptr<FilterWrapper> mFilterWrapper;
+    std::shared_ptr<Listener> mListener;
+#if defined(LOG_NDEBUG) && !LOG_NDEBUG
+    base::ScopedLogSeverity mScopedLogSeverity{base::VERBOSE};
+#endif
+
+    c2_status_t setListenerInternal(
+            const std::vector<FilterWrapper::Component> &filters,
+            const std::shared_ptr<Listener> &listener,
+            c2_blocking_t mayBlock) {
+        if (filters.empty()) {
+            return mComp->setListener_vb(listener, mayBlock);
+        }
+        std::shared_ptr passingListener = std::make_shared<PassingListener>(
+                shared_from_this(),
+                listener,
+                filters.front().comp);
+        mComp->setListener_vb(passingListener, mayBlock);
+        for (size_t i = 0; i < filters.size() - 1; ++i) {
+            filters[i].comp->setListener_vb(
+                    std::make_shared<PassingListener>(
+                            shared_from_this(),
+                            listener,
+                            filters[i + 1].comp),
+                    mayBlock);
+        }
+        filters.back().comp->setListener_vb(
+                std::make_shared<LastListener>(shared_from_this(), listener), mayBlock);
+        return C2_OK;
+    }
+};
+
+}  // anonymous namespace
+
+FilterWrapper::FilterWrapper(std::unique_ptr<Plugin> &&plugin)
+    : mInit(NO_INIT),
+      mPlugin(std::move(plugin)) {
+    if (mPlugin->status() != OK) {
+        LOG(ERROR) << "plugin not OK: " << mPlugin->status();
+        mPlugin.reset();
+        return;
+    }
+    mStore = mPlugin->getStore();
+    if (!mStore) {
+        LOG(ERROR) << "no store";
+        mPlugin.reset();
+        return;
+    }
+    std::vector<std::shared_ptr<const C2Component::Traits>> traits =
+        mStore->listComponents();
+    std::sort(
+            traits.begin(),
+            traits.end(),
+            [](std::shared_ptr<const C2Component::Traits> &a,
+                    std::shared_ptr<const C2Component::Traits> &b) {
+                return a->rank < b->rank;
+            });
+    for (size_t i = 0; i < traits.size(); ++i) {
+        const std::shared_ptr<const C2Component::Traits> &trait = traits[i];
+        if (trait->domain == C2Component::DOMAIN_OTHER
+                || trait->domain == C2Component::DOMAIN_AUDIO
+                || trait->kind != C2Component::KIND_OTHER) {
+            LOG(DEBUG) << trait->name << " is ignored because of domain/kind: "
+                << trait->domain << "/" << trait->kind;
+            continue;
+        }
+        Descriptor desc;
+        if (!mPlugin->describe(trait->name, &desc)) {
+            LOG(DEBUG) << trait->name << " is ignored because describe() failed";
+            continue;
+        }
+        mComponents.push_back({nullptr, nullptr, *trait, desc});
+    }
+    if (mComponents.empty()) {
+        LOG(DEBUG) << "FilterWrapper: no filter component found";
+        mPlugin.reset();
+        return;
+    }
+    mInit = OK;
+}
+
+FilterWrapper::~FilterWrapper() {
+}
+
+std::vector<FilterWrapper::Component> FilterWrapper::createFilters() {
+    std::vector<FilterWrapper::Component> filters;
+    for (const FilterWrapper::Component &filter : mComponents) {
+        std::shared_ptr<C2Component> comp;
+        std::shared_ptr<C2ComponentInterface> intf;
+        if (C2_OK != mStore->createComponent(filter.traits.name, &comp)) {
+            return {};
+        }
+        if (C2_OK != mStore->createInterface(filter.traits.name, &intf)) {
+            return {};
+        }
+        filters.push_back({comp, intf, filter.traits, filter.desc});
+    }
+    return filters;
+}
+
+C2Component::Traits FilterWrapper::getTraits(
+        const std::shared_ptr<C2ComponentInterface> &intf) {
+    {
+        std::unique_lock lock(mCacheMutex);
+        if (mCachedTraits.count(intf->getName())) {
+            return mCachedTraits.at(intf->getName());
+        }
+    }
+    C2ComponentDomainSetting domain;
+    C2ComponentKindSetting kind;
+    c2_status_t err = intf->query_vb({&domain, &kind}, {}, C2_MAY_BLOCK, nullptr);
+    C2Component::Traits traits = {
+        "query failed",  // name
+        C2Component::DOMAIN_OTHER,
+        C2Component::KIND_OTHER,
+        0,   // rank, unused
+        "",  // media type, unused
+        "",  // owner, unused
+        {},  // aliases, unused
+    };
+    if (err == C2_OK) {
+        traits = {
+            intf->getName(),
+            domain.value,
+            kind.value,
+            0,   // rank, unused
+            "",  // media type, unused
+            "",  // owner, unused
+            {},  // aliases, unused
+        };
+        std::unique_lock lock(mCacheMutex);
+        mCachedTraits[traits.name] = traits;
+    }
+    return traits;
+}
+
+std::shared_ptr<C2ComponentInterface> FilterWrapper::maybeWrapInterface(
+        const std::shared_ptr<C2ComponentInterface> intf) {
+    if (mInit != OK) {
+        LOG(VERBOSE) << "maybeWrapInterface: Wrapper not initialized: "
+                << intf->getName() << " is not wrapped.";
+        return intf;
+    }
+    C2Component::Traits traits = getTraits(intf);
+    if (traits.name != intf->getName()) {
+        LOG(INFO) << "maybeWrapInterface: Querying traits from " << intf->getName()
+                << " failed; not wrapping the interface";
+        return intf;
+    }
+    if ((traits.domain != C2Component::DOMAIN_VIDEO && traits.domain != C2Component::DOMAIN_IMAGE)
+            || traits.kind != C2Component::KIND_DECODER) {
+        LOG(VERBOSE) << "maybeWrapInterface: " << traits.name
+                << " is not video/image decoder; not wrapping the interface";
+        return intf;
+    }
+    return std::make_shared<WrappedDecoderInterface>(intf, createFilters());
+}
+
+std::shared_ptr<C2Component> FilterWrapper::maybeWrapComponent(
+        const std::shared_ptr<C2Component> comp) {
+    if (mInit != OK) {
+        LOG(VERBOSE) << "maybeWrapComponent: Wrapper not initialized: "
+                << comp->intf()->getName() << " is not wrapped.";
+        return comp;
+    }
+    C2Component::Traits traits = getTraits(comp->intf());
+    if (traits.name != comp->intf()->getName()) {
+        LOG(INFO) << "maybeWrapComponent: Querying traits from " << comp->intf()->getName()
+                << " failed; not wrapping the component";
+        return comp;
+    }
+    if ((traits.domain != C2Component::DOMAIN_VIDEO && traits.domain != C2Component::DOMAIN_IMAGE)
+            || traits.kind != C2Component::KIND_DECODER) {
+        LOG(VERBOSE) << "maybeWrapComponent: " << traits.name
+                << " is not video/image decoder; not wrapping the component";
+        return comp;
+    }
+    std::vector<Component> filters = createFilters();
+    std::shared_ptr wrapped = std::make_shared<WrappedDecoder>(
+            comp, std::move(filters), weak_from_this());
+    {
+        std::unique_lock lock(mWrappedComponentsMutex);
+        std::vector<std::weak_ptr<const C2Component>> &components =
+            mWrappedComponents.emplace_back();
+        components.push_back(wrapped);
+        components.push_back(comp);
+        for (const Component &filter : filters) {
+            components.push_back(filter.comp);
+        }
+    }
+    return wrapped;
+}
+
+bool FilterWrapper::isFilteringEnabled(const std::shared_ptr<C2ComponentInterface> &intf) {
+    if (mInit != OK) {
+        LOG(WARNING) << "isFilteringEnabled: Wrapper not initialized: ";
+        return false;
+    }
+    return mPlugin->isFilteringEnabled(intf);
+}
+
+c2_status_t FilterWrapper::createBlockPool(
+        C2PlatformAllocatorStore::id_t allocatorId,
+        std::shared_ptr<const C2Component> component,
+        std::shared_ptr<C2BlockPool> *pool) {
+    std::unique_lock lock(mWrappedComponentsMutex);
+    for (auto it = mWrappedComponents.begin(); it != mWrappedComponents.end(); ) {
+        std::shared_ptr<const C2Component> comp = it->front().lock();
+        if (!comp) {
+            it = mWrappedComponents.erase(it);
+            continue;
+        }
+        if (component == comp) {
+            std::vector<std::shared_ptr<const C2Component>> components(it->size());
+            std::transform(
+                    it->begin(), it->end(), components.begin(),
+                    [](const std::weak_ptr<const C2Component> &el) {
+                        return el.lock();
+                    });
+            if (C2_OK == CreateCodec2BlockPool(allocatorId, components, pool)) {
+                return C2_OK;
+            }
+        }
+        ++it;
+    }
+    return CreateCodec2BlockPool(allocatorId, component, pool);
+}
+
+}  // namespace android
diff --git a/media/codec2/hidl/plugin/FilterWrapperStub.cpp b/media/codec2/hidl/plugin/FilterWrapperStub.cpp
new file mode 100644
index 0000000..1b94a1a
--- /dev/null
+++ b/media/codec2/hidl/plugin/FilterWrapperStub.cpp
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "Codec2-FilterWrapperStub"
+
+#include <FilterWrapper.h>
+
+namespace android {
+
+FilterWrapper::FilterWrapper(std::unique_ptr<Plugin> &&) {
+}
+
+FilterWrapper::~FilterWrapper() {
+}
+
+std::shared_ptr<C2ComponentInterface> FilterWrapper::maybeWrapInterface(
+        const std::shared_ptr<C2ComponentInterface> intf) {
+    return intf;
+}
+
+std::shared_ptr<C2Component> FilterWrapper::maybeWrapComponent(
+        const std::shared_ptr<C2Component> comp) {
+    return comp;
+}
+
+bool FilterWrapper::isFilteringEnabled(const std::shared_ptr<C2ComponentInterface> &) {
+    return false;
+}
+
+c2_status_t FilterWrapper::createBlockPool(
+        C2PlatformAllocatorStore::id_t,
+        std::shared_ptr<const C2Component>,
+        std::shared_ptr<C2BlockPool> *) {
+    return C2_OMITTED;
+}
+
+}  // namespace android
diff --git a/media/codec2/hidl/plugin/include/codec2/hidl/plugin/FilterPlugin.h b/media/codec2/hidl/plugin/include/codec2/hidl/plugin/FilterPlugin.h
new file mode 100644
index 0000000..6f1f907
--- /dev/null
+++ b/media/codec2/hidl/plugin/include/codec2/hidl/plugin/FilterPlugin.h
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2018, 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.
+ */
+
+#ifndef CODEC2_HIDL_PLUGIN_FILTER_PLUGIN_H
+
+#define CODEC2_HIDL_PLUGIN_FILTER_PLUGIN_H
+
+#include <memory>
+
+#include <C2Component.h>
+
+namespace android {
+
+class FilterPlugin_V1 {
+public:
+    static constexpr int32_t VERSION = 1;
+
+    virtual ~FilterPlugin_V1() = default;
+
+    /**
+     * Returns a C2ComponentStore object with which clients can create
+     * filter components / interfaces.
+     */
+    virtual std::shared_ptr<C2ComponentStore> getComponentStore() = 0;
+    struct Descriptor {
+        // Parameters that client sets for filter control.
+        std::initializer_list<C2Param::Type> controlParams;
+        // Parameters that the component changes after filtering.
+        std::initializer_list<C2Param::Type> affectedParams;
+    };
+
+    /**
+     * Describe a filter component.
+     *
+     * @param name[in]  filter's name
+     * @param desc[out] pointer to filter descriptor to be populated
+     * @return  true if |name| is in the store and |desc| is populated;
+     *          false if |name| is not recognized
+     */
+    virtual bool describe(C2String name, Descriptor *desc) = 0;
+
+    /**
+     * Returns true if a component will apply filtering after all given the
+     * current configuration; false if it will be no-op.
+     */
+    virtual bool isFilteringEnabled(const std::shared_ptr<C2ComponentInterface> &intf) = 0;
+};
+
+}  // namespace android
+
+extern "C" {
+
+typedef int32_t (*GetFilterPluginVersionFunc)();
+int32_t GetFilterPluginVersion();
+
+typedef void* (*CreateFilterPluginFunc)();
+void *CreateFilterPlugin();
+
+typedef void (*DestroyFilterPluginFunc)(void *);
+void DestroyFilterPlugin(void *plugin);
+
+}  // extern "C"
+
+#endif  // CODEC2_HIDL_PLUGIN_FILTER_PLUGIN_H
diff --git a/media/codec2/hidl/plugin/internal/DefaultFilterPlugin.h b/media/codec2/hidl/plugin/internal/DefaultFilterPlugin.h
new file mode 100644
index 0000000..f856324
--- /dev/null
+++ b/media/codec2/hidl/plugin/internal/DefaultFilterPlugin.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2020, 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.
+ */
+
+#ifndef CODEC2_HIDL_PLUGIN_DEFAULT_FILTER_PLUGIN_H
+
+#define CODEC2_HIDL_PLUGIN_DEFAULT_FILTER_PLUGIN_H
+
+#include <codec2/hidl/plugin/FilterPlugin.h>
+
+#include <FilterWrapper.h>
+
+namespace android {
+
+class DefaultFilterPlugin : public FilterWrapper::Plugin {
+public:
+    explicit DefaultFilterPlugin(const char *pluginPath);
+
+    ~DefaultFilterPlugin();
+
+    status_t status() const override { return mInit; }
+
+    std::shared_ptr<C2ComponentStore> getStore() override { return mStore; }
+    bool describe(C2String name, FilterWrapper::Descriptor *desc) override;
+    bool isFilteringEnabled(const std::shared_ptr<C2ComponentInterface> &intf) override;
+
+private:
+    status_t mInit;
+    void *mHandle;
+    DestroyFilterPluginFunc mDestroyPlugin;
+    FilterPlugin_V1 *mPlugin;
+    std::shared_ptr<C2ComponentStore> mStore;
+};
+
+}  // namespace android
+
+#endif  // CODEC2_HIDL_PLUGIN_DEFAULT_FILTER_PLUGIN_H
diff --git a/media/codec2/hidl/plugin/internal/FilterWrapper.h b/media/codec2/hidl/plugin/internal/FilterWrapper.h
new file mode 100644
index 0000000..5ced435
--- /dev/null
+++ b/media/codec2/hidl/plugin/internal/FilterWrapper.h
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2020, 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.
+ */
+
+#ifndef CODEC2_HIDL_PLUGIN_FILTER_WRAPPER_H
+
+#define CODEC2_HIDL_PLUGIN_FILTER_WRAPPER_H
+
+#include <map>
+#include <memory>
+#include <mutex>
+
+#include <C2Component.h>
+#include <C2PlatformSupport.h>
+
+#include <codec2/hidl/plugin/FilterPlugin.h>
+#include <utils/Errors.h>
+
+namespace android {
+
+// TODO: documentation
+class FilterWrapper : public std::enable_shared_from_this<FilterWrapper> {
+public:
+    using Descriptor = FilterPlugin_V1::Descriptor;
+
+    class Plugin {
+    public:
+        Plugin() = default;
+        virtual ~Plugin() = default;
+        virtual status_t status() const = 0;
+        virtual std::shared_ptr<C2ComponentStore> getStore() = 0;
+        virtual bool describe(C2String name, Descriptor *desc) = 0;
+        virtual bool isFilteringEnabled(const std::shared_ptr<C2ComponentInterface> &intf) = 0;
+        C2_DO_NOT_COPY(Plugin);
+    };
+
+    struct Component {
+        const std::shared_ptr<C2Component> comp;
+        const std::shared_ptr<C2ComponentInterface> intf;
+        const C2Component::Traits traits;
+        const Descriptor desc;
+    };
+
+private:
+    explicit FilterWrapper(std::unique_ptr<Plugin> &&plugin);
+public:
+    static std::shared_ptr<FilterWrapper> Create(std::unique_ptr<Plugin> &&plugin) {
+        return std::shared_ptr<FilterWrapper>(new FilterWrapper(std::move(plugin)));
+    }
+    ~FilterWrapper();
+
+    /**
+     * Returns wrapped interface, or |intf| if wrapping is not possible / needed.
+     */
+    std::shared_ptr<C2ComponentInterface> maybeWrapInterface(
+            const std::shared_ptr<C2ComponentInterface> intf);
+
+    /**
+     * Returns wrapped component, or |comp| if wrapping is not possible / needed.
+     */
+    std::shared_ptr<C2Component> maybeWrapComponent(
+            const std::shared_ptr<C2Component> comp);
+
+    /**
+     * Returns ture iff the filtering will apply to the buffer in current configuration.
+     */
+    bool isFilteringEnabled(const std::shared_ptr<C2ComponentInterface> &intf);
+
+    c2_status_t createBlockPool(
+            C2PlatformAllocatorStore::id_t allocatorId,
+            std::shared_ptr<const C2Component> component,
+            std::shared_ptr<C2BlockPool> *pool);
+
+private:
+    status_t mInit;
+    std::unique_ptr<Plugin> mPlugin;
+    std::shared_ptr<C2ComponentStore> mStore;
+    std::list<FilterWrapper::Component> mComponents;
+
+    std::mutex mCacheMutex;
+    std::map<std::string, C2Component::Traits> mCachedTraits;
+
+    std::mutex mWrappedComponentsMutex;
+    std::list<std::vector<std::weak_ptr<const C2Component>>> mWrappedComponents;
+
+    std::vector<FilterWrapper::Component> createFilters();
+    C2Component::Traits getTraits(const std::shared_ptr<C2ComponentInterface> &intf);
+
+    C2_DO_NOT_COPY(FilterWrapper);
+};
+
+}  // namespace android
+
+#endif  // CODEC2_HIDL_PLUGIN_FILTER_WRAPPER_H
diff --git a/media/codec2/hidl/plugin/samples/Android.bp b/media/codec2/hidl/plugin/samples/Android.bp
new file mode 100644
index 0000000..c823e31
--- /dev/null
+++ b/media/codec2/hidl/plugin/samples/Android.bp
@@ -0,0 +1,49 @@
+cc_defaults {
+    name: "sample-codec2-hidl-plugin-defaults",
+
+    srcs: [
+        "SampleFilterPlugin.cpp",
+    ],
+
+    defaults: [
+        "libcodec2-impl-defaults",
+    ],
+
+    header_libs: [
+        "libcodec2_hidl_plugin_headers",
+        "libgui_headers",
+    ],
+
+    shared_libs: [
+        "libEGL",
+        "libGLESv1_CM",
+        "libGLESv2",
+        "libGLESv3",
+        "libbase",
+        "libcodec2",
+        "libcutils",
+        "libprocessgroup",
+        "libsfplugin_ccodec_utils",
+        "libsync",
+        "libui",
+        "libutils",
+    ],
+
+    static_libs: [
+        "librenderfright",
+    ],
+
+    cflags: [
+        "-Werror",
+        "-Wall",
+    ],
+}
+
+cc_library {
+    name: "sample-codec2-hidl-plugin",
+    vendor: true,
+
+    defaults: [
+        "sample-codec2-hidl-plugin-defaults",
+    ],
+}
diff --git a/media/codec2/hidl/plugin/samples/SampleFilterPlugin.cpp b/media/codec2/hidl/plugin/samples/SampleFilterPlugin.cpp
new file mode 100644
index 0000000..94811fc
--- /dev/null
+++ b/media/codec2/hidl/plugin/samples/SampleFilterPlugin.cpp
@@ -0,0 +1,969 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "SampleFilterPlugin"
+#include <android-base/logging.h>
+
+#include <chrono>
+#include <thread>
+
+#include <codec2/hidl/plugin/FilterPlugin.h>
+
+#include <C2AllocatorGralloc.h>
+#include <C2Config.h>
+#include <C2PlatformSupport.h>
+#include <Codec2Mapper.h>
+#include <util/C2InterfaceHelper.h>
+
+#include <renderengine/RenderEngine.h>
+#include <system/window.h>
+#include <ui/GraphicBuffer.h>
+#include <utils/RefBase.h>
+
+typedef C2StreamParam<C2Info, C2ColorAspectsStruct,
+                kParamIndexColorAspects | C2Param::CoreIndex::IS_REQUEST_FLAG>
+        C2StreamColorAspectsRequestInfo;
+
+namespace android {
+
+using namespace std::literals::chrono_literals;
+
+class SampleToneMappingFilter
+    : public C2Component, public std::enable_shared_from_this<SampleToneMappingFilter> {
+public:
+    class Interface : public C2ComponentInterface {
+    public:
+        static const std::string NAME;
+        static const FilterPlugin_V1::Descriptor DESCRIPTOR;
+
+        explicit Interface(c2_node_id_t id)
+            : mId(id),
+              mReflector(std::make_shared<C2ReflectorHelper>()),
+              mHelper(mReflector) {
+        }
+        ~Interface() override = default;
+        C2String getName() const override { return NAME; }
+        c2_node_id_t getId() const override { return mId; }
+
+        c2_status_t query_vb(
+                const std::vector<C2Param*> &stackParams,
+                const std::vector<C2Param::Index> &heapParamIndices,
+                c2_blocking_t mayBlock,
+                std::vector<std::unique_ptr<C2Param>>* const heapParams) const override {
+            return mHelper.query(stackParams, heapParamIndices, mayBlock, heapParams);
+        }
+        c2_status_t config_vb(
+                const std::vector<C2Param*> &params,
+                c2_blocking_t mayBlock,
+                std::vector<std::unique_ptr<C2SettingResult>>* const failures) override {
+            return mHelper.config(params, mayBlock, failures);
+        }
+        c2_status_t querySupportedParams_nb(
+                std::vector<std::shared_ptr<C2ParamDescriptor>> * const params) const override {
+            return mHelper.querySupportedParams(params);
+        }
+        c2_status_t querySupportedValues_vb(
+                std::vector<C2FieldSupportedValuesQuery> &fields,
+                c2_blocking_t mayBlock) const override {
+            return mHelper.querySupportedValues(fields, mayBlock);
+        }
+        c2_status_t createTunnel_sm(c2_node_id_t) override { return C2_OMITTED; }
+        c2_status_t releaseTunnel_sm(c2_node_id_t) override { return C2_OMITTED; }
+
+        uint32_t getDataSpace() {
+            Helper::Lock lock = mHelper.lock();
+            uint32_t dataspace = HAL_DATASPACE_UNKNOWN;
+            C2Mapper::map(
+                    mHelper.mInputColorAspectInfo->range,
+                    mHelper.mInputColorAspectInfo->primaries,
+                    mHelper.mInputColorAspectInfo->matrix,
+                    mHelper.mInputColorAspectInfo->transfer,
+                    &dataspace);
+            return dataspace;
+        }
+        std::shared_ptr<C2StreamHdrStaticInfo::input> getHdrStaticMetadata() {
+            Helper::Lock lock = mHelper.lock();
+            return mHelper.mInputHdrStaticInfo;
+        }
+        C2BlockPool::local_id_t getPoolId() {
+            Helper::Lock lock = mHelper.lock();
+            return mHelper.mOutputPoolIds->m.values[0];
+        }
+
+        static bool IsFilteringEnabled(const std::shared_ptr<C2ComponentInterface> &intf) {
+            C2StreamColorAspectsRequestInfo::output info(0u);
+            std::vector<std::unique_ptr<C2Param>> heapParams;
+            c2_status_t err = intf->query_vb({&info}, {}, C2_MAY_BLOCK, &heapParams);
+            if (err != C2_OK && err != C2_BAD_INDEX) {
+                LOG(WARNING) << "SampleToneMappingFilter::Interface::IsFilteringEnabled: "
+                        << "query failed for " << intf->getName();
+                return false;
+            }
+            return info && info.transfer == C2Color::TRANSFER_170M;
+        }
+    private:
+        const c2_node_id_t mId;
+        std::shared_ptr<C2ReflectorHelper> mReflector;
+        struct Helper : public C2InterfaceHelper {
+            explicit Helper(std::shared_ptr<C2ReflectorHelper> reflector)
+                : C2InterfaceHelper(reflector) {
+                setDerivedInstance(this);
+
+                addParameter(
+                        DefineParam(mApiFeatures, C2_PARAMKEY_API_FEATURES)
+                        .withConstValue(new C2ApiFeaturesSetting(C2Config::api_feature_t(
+                                API_REFLECTION |
+                                API_VALUES |
+                                API_CURRENT_VALUES |
+                                API_DEPENDENCY |
+                                API_SAME_INPUT_BUFFER)))
+                        .build());
+
+                mName = C2ComponentNameSetting::AllocShared(NAME.size() + 1);
+                strncpy(mName->m.value, NAME.c_str(), NAME.size() + 1);
+                addParameter(
+                        DefineParam(mName, C2_PARAMKEY_COMPONENT_NAME)
+                        .withConstValue(mName)
+                        .build());
+
+                addParameter(
+                        DefineParam(mKind, C2_PARAMKEY_COMPONENT_KIND)
+                        .withConstValue(new C2ComponentKindSetting(C2Component::KIND_OTHER))
+                        .build());
+
+                addParameter(
+                        DefineParam(mDomain, C2_PARAMKEY_COMPONENT_DOMAIN)
+                        .withConstValue(new C2ComponentDomainSetting(C2Component::DOMAIN_VIDEO))
+                        .build());
+
+                addParameter(
+                        DefineParam(mInputStreamCount, C2_PARAMKEY_INPUT_STREAM_COUNT)
+                        .withConstValue(new C2PortStreamCountTuning::input(1))
+                        .build());
+
+                addParameter(
+                        DefineParam(mOutputStreamCount, C2_PARAMKEY_OUTPUT_STREAM_COUNT)
+                        .withConstValue(new C2PortStreamCountTuning::output(1))
+                        .build());
+
+                addParameter(
+                        DefineParam(mInputFormat, C2_PARAMKEY_INPUT_STREAM_BUFFER_TYPE)
+                        .withConstValue(new C2StreamBufferTypeSetting::input(
+                                0u, C2BufferData::GRAPHIC))
+                        .build());
+
+                static const std::string kRawMediaType = "video/raw";
+                mInputMediaType = C2PortMediaTypeSetting::input::AllocShared(
+                        kRawMediaType.size() + 1);
+                strncpy(mInputMediaType->m.value, kRawMediaType.c_str(), kRawMediaType.size() + 1);
+                addParameter(
+                        DefineParam(mInputMediaType, C2_PARAMKEY_INPUT_MEDIA_TYPE)
+                        .withConstValue(mInputMediaType)
+                        .build());
+
+                addParameter(
+                        DefineParam(mOutputFormat, C2_PARAMKEY_OUTPUT_STREAM_BUFFER_TYPE)
+                        .withConstValue(new C2StreamBufferTypeSetting::output(
+                                0u, C2BufferData::GRAPHIC))
+                        .build());
+
+                mOutputMediaType = C2PortMediaTypeSetting::output::AllocShared(
+                        kRawMediaType.size() + 1);
+                strncpy(mOutputMediaType->m.value, kRawMediaType.c_str(), kRawMediaType.size() + 1);
+                addParameter(
+                        DefineParam(mOutputMediaType, C2_PARAMKEY_OUTPUT_MEDIA_TYPE)
+                        .withConstValue(mOutputMediaType)
+                        .build());
+
+                addParameter(
+                        DefineParam(mActualInputDelay, C2_PARAMKEY_INPUT_DELAY)
+                        .withConstValue(new C2PortActualDelayTuning::input(0u))
+                        .build());
+
+                addParameter(
+                        DefineParam(mActualOutputDelay, C2_PARAMKEY_OUTPUT_DELAY)
+                        .withConstValue(new C2PortActualDelayTuning::output(0u))
+                        .build());
+
+                addParameter(
+                        DefineParam(mActualPipelineDelay, C2_PARAMKEY_PIPELINE_DELAY)
+                        .withConstValue(new C2ActualPipelineDelayTuning(0u))
+                        .build());
+
+                C2BlockPool::local_id_t outputPoolIds[1] = { C2BlockPool::BASIC_GRAPHIC };
+                addParameter(
+                        DefineParam(mOutputPoolIds, C2_PARAMKEY_OUTPUT_BLOCK_POOLS)
+                        .withDefault(C2PortBlockPoolsTuning::output::AllocShared(outputPoolIds))
+                        .withFields({ C2F(mOutputPoolIds, m.values[0]).any(),
+                                      C2F(mOutputPoolIds, m.values).inRange(0, 1) })
+                        .withSetter(OutputBlockPoolSetter)
+                        .build());
+
+                addParameter(
+                        DefineParam(mInputHdrStaticInfo, C2_PARAMKEY_HDR_STATIC_INFO)
+                        .withDefault(new C2StreamHdrStaticInfo::input(0u))
+                        .withFields({
+                            C2F(mInputHdrStaticInfo, mastering.red.x).any(),
+                        })
+                        .withSetter(HdrStaticInfoSetter)
+                        .build());
+
+                addParameter(
+                        DefineParam(mOutputHdrStaticInfo, C2_PARAMKEY_HDR_STATIC_INFO)
+                        .withConstValue(new C2StreamHdrStaticInfo::output(0u))
+                        .build());
+
+                addParameter(
+                        DefineParam(mInputColorAspectInfo, C2_PARAMKEY_COLOR_ASPECTS)
+                        .withDefault(new C2StreamColorAspectsInfo::input(0u))
+                        .withFields({
+                            C2F(mInputColorAspectInfo, range).any(),
+                            C2F(mInputColorAspectInfo, primaries).any(),
+                            C2F(mInputColorAspectInfo, transfer).any(),
+                            C2F(mInputColorAspectInfo, matrix).any(),
+                        })
+                        .withSetter(InputColorAspectsSetter)
+                        .build());
+
+                addParameter(
+                        DefineParam(
+                            mColorAspectRequestInfo,
+                            (std::string(C2_PARAMKEY_COLOR_ASPECTS) + ".request").c_str())
+                        .withDefault(new C2StreamColorAspectsRequestInfo::output(0u))
+                        .withFields({
+                            C2F(mColorAspectRequestInfo, range).any(),
+                            C2F(mColorAspectRequestInfo, primaries).any(),
+                            C2F(mColorAspectRequestInfo, transfer).oneOf({
+                                C2Color::TRANSFER_UNSPECIFIED,
+                                C2Color::TRANSFER_170M,
+                            }),
+                            C2F(mColorAspectRequestInfo, matrix).any(),
+                        })
+                        .withSetter(ColorAspectsRequestSetter)
+                        .build());
+
+                addParameter(
+                        DefineParam(mOutputColorAspectInfo, C2_PARAMKEY_COLOR_ASPECTS)
+                        .withDefault(new C2StreamColorAspectsInfo::output(0u))
+                        .withFields({
+                            C2F(mOutputColorAspectInfo, range).any(),
+                            C2F(mOutputColorAspectInfo, primaries).any(),
+                            C2F(mOutputColorAspectInfo, transfer).any(),
+                            C2F(mOutputColorAspectInfo, matrix).any(),
+                        })
+                        .withSetter(OutputColorAspectsSetter,
+                                    mInputColorAspectInfo,
+                                    mColorAspectRequestInfo)
+                        .build());
+            }
+
+            static C2R OutputBlockPoolSetter(
+                    bool mayBlock,
+                    C2P<C2PortBlockPoolsTuning::output> &me) {
+                (void)mayBlock, (void)me;
+                return C2R::Ok();
+            }
+
+            static C2R HdrStaticInfoSetter(
+                    bool mayBlock,
+                    C2P<C2StreamHdrStaticInfo::input> &me) {
+                (void)mayBlock, (void)me;
+                return C2R::Ok();
+            }
+
+            static C2R InputColorAspectsSetter(
+                    bool mayBlock,
+                    C2P<C2StreamColorAspectsInfo::input> &me) {
+                (void)mayBlock, (void)me;
+                return C2R::Ok();
+            }
+
+            static C2R OutputColorAspectsSetter(
+                    bool mayBlock,
+                    C2P<C2StreamColorAspectsInfo::output> &me,
+                    const C2P<C2StreamColorAspectsInfo::input> &inputColor,
+                    const C2P<C2StreamColorAspectsRequestInfo::output> &request) {
+                (void)mayBlock;
+                me.set().range = inputColor.v.range;
+                me.set().primaries = inputColor.v.primaries;
+                me.set().transfer = inputColor.v.transfer;
+                if (request.v.transfer == C2Color::TRANSFER_170M) {
+                    me.set().transfer = C2Color::TRANSFER_170M;
+                }
+                me.set().matrix = inputColor.v.matrix;
+                return C2R::Ok();
+            }
+
+            static C2R ColorAspectsRequestSetter(
+                    bool mayBlock,
+                    C2P<C2StreamColorAspectsRequestInfo::output> &me) {
+                (void)mayBlock;
+                if (me.v.range != C2Color::RANGE_UNSPECIFIED) {
+                    me.set().range = C2Color::RANGE_UNSPECIFIED;
+                }
+                if (me.v.primaries != C2Color::PRIMARIES_UNSPECIFIED) {
+                    me.set().primaries = C2Color::PRIMARIES_UNSPECIFIED;
+                }
+                if (me.v.transfer != C2Color::TRANSFER_170M) {
+                    me.set().transfer = C2Color::TRANSFER_UNSPECIFIED;
+                }
+                if (me.v.matrix != C2Color::MATRIX_UNSPECIFIED) {
+                    me.set().matrix = C2Color::MATRIX_UNSPECIFIED;
+                }
+                return C2R::Ok();
+            }
+
+            std::shared_ptr<C2ApiFeaturesSetting> mApiFeatures;
+
+            std::shared_ptr<C2ComponentNameSetting> mName;
+            std::shared_ptr<C2ComponentAliasesSetting> mAliases;
+            std::shared_ptr<C2ComponentKindSetting> mKind;
+            std::shared_ptr<C2ComponentDomainSetting> mDomain;
+
+            std::shared_ptr<C2PortMediaTypeSetting::input> mInputMediaType;
+            std::shared_ptr<C2PortMediaTypeSetting::output> mOutputMediaType;
+            std::shared_ptr<C2StreamBufferTypeSetting::input> mInputFormat;
+            std::shared_ptr<C2StreamBufferTypeSetting::output> mOutputFormat;
+
+            std::shared_ptr<C2PortActualDelayTuning::input> mActualInputDelay;
+            std::shared_ptr<C2PortActualDelayTuning::output> mActualOutputDelay;
+            std::shared_ptr<C2ActualPipelineDelayTuning> mActualPipelineDelay;
+
+            std::shared_ptr<C2PortStreamCountTuning::input> mInputStreamCount;
+            std::shared_ptr<C2PortStreamCountTuning::output> mOutputStreamCount;
+
+            std::shared_ptr<C2PortBlockPoolsTuning::output> mOutputPoolIds;
+
+            std::shared_ptr<C2StreamHdrStaticInfo::input> mInputHdrStaticInfo;
+            std::shared_ptr<C2StreamHdrStaticInfo::output> mOutputHdrStaticInfo;
+            std::shared_ptr<C2StreamColorAspectsInfo::input> mInputColorAspectInfo;
+            std::shared_ptr<C2StreamColorAspectsInfo::output> mOutputColorAspectInfo;
+            std::shared_ptr<C2StreamColorAspectsRequestInfo::output> mColorAspectRequestInfo;
+        } mHelper;
+    };
+
+    explicit SampleToneMappingFilter(c2_node_id_t id)
+        : mIntf(std::make_shared<Interface>(id)) {
+    }
+    ~SampleToneMappingFilter() override {
+        if (mProcessingThread.joinable()) {
+            mProcessingThread.join();
+        }
+    }
+
+    c2_status_t setListener_vb(
+            const std::shared_ptr<Listener> &listener, c2_blocking_t mayBlock) override {
+        std::chrono::steady_clock::time_point deadline = std::chrono::steady_clock::now() + 5ms;
+        {
+            std::unique_lock lock(mStateMutex);
+            if (mState == RELEASED) {
+                return C2_BAD_STATE;
+            }
+            if (mState == RUNNING && listener) {
+                return C2_BAD_STATE;
+            }
+            if (mState != STOPPED) {
+                return C2_BAD_STATE;
+            }
+        }
+        std::unique_lock lock(mListenerMutex, std::try_to_lock);
+        if (lock) {
+            mListener = listener;
+            return C2_OK;
+        }
+        if (mayBlock == C2_DONT_BLOCK) {
+            return C2_BLOCKING;
+        }
+        lock.try_lock_until(deadline);
+        if (!lock) {
+            return C2_TIMED_OUT;
+        }
+        mListener = listener;
+        return C2_OK;
+    }
+
+    c2_status_t queue_nb(std::list<std::unique_ptr<C2Work>>* const items) override {
+        if (!items) {
+            return C2_BAD_VALUE;
+        }
+        {
+            std::unique_lock lock(mStateMutex);
+            if (mState != RUNNING) {
+                return C2_BAD_STATE;
+            }
+        }
+        std::unique_lock lock(mQueueMutex);
+        mQueue.splice(mQueue.end(), *items);
+        return C2_OK;
+    }
+
+    c2_status_t announce_nb(const std::vector<C2WorkOutline> &) override { return C2_OMITTED; }
+
+    c2_status_t flush_sm(
+            flush_mode_t mode,
+            std::list<std::unique_ptr<C2Work>>* const flushedWork) override {
+        if (!flushedWork) {
+            return C2_BAD_VALUE;
+        }
+        if (mode == FLUSH_CHAIN) {
+            return C2_BAD_VALUE;
+        }
+        {
+            std::unique_lock lock(mStateMutex);
+            if (mState != RUNNING) {
+                return C2_BAD_STATE;
+            }
+        }
+        {
+            std::unique_lock lock(mQueueMutex);
+            mQueue.swap(*flushedWork);
+        }
+        // NOTE: this component does not have internal state to flush.
+        return C2_OK;
+    }
+
+    c2_status_t drain_nb(drain_mode_t mode) override {
+        if (mode == DRAIN_CHAIN) {
+            return C2_BAD_VALUE;
+        }
+        {
+            std::unique_lock lock(mStateMutex);
+            if (mState != RUNNING) {
+                return C2_BAD_STATE;
+            }
+        }
+        // NOTE: this component does not wait for work items before processing.
+        return C2_OK;
+    }
+
+    c2_status_t start() override {
+        //std::chrono::steady_clock::time_point deadline = std::chrono::steady_clock::now() + 500ms;
+        {
+            std::unique_lock lock(mStateMutex);
+            if (mState == STARTING) {
+                return C2_DUPLICATE;
+            }
+            if (mState != STOPPED) {
+                return C2_BAD_STATE;
+            }
+            mState = STARTING;
+        }
+        {
+            std::unique_lock lock(mProcessingMutex);
+            if (!mProcessingThread.joinable()) {
+                mProcessingThread = std::thread([this]() {
+                    processLoop(shared_from_this());
+                });
+            }
+        }
+        {
+            std::unique_lock lock(mStateMutex);
+            mState = RUNNING;
+        }
+        return C2_OK;
+    }
+
+    c2_status_t stop() override {
+        //std::chrono::steady_clock::time_point deadline = std::chrono::steady_clock::now() + 500ms;
+        {
+            std::unique_lock lock(mStateMutex);
+            if (mState == STOPPING) {
+                return C2_DUPLICATE;
+            }
+            if (mState != RUNNING) {
+                return C2_BAD_STATE;
+            }
+            mState = STOPPING;
+        }
+        {
+            std::unique_lock lock(mQueueMutex);
+            mQueueCondition.notify_all();
+        }
+        {
+            std::unique_lock lock(mProcessingMutex);
+            if (mProcessingThread.joinable()) {
+                mProcessingThread.join();
+            }
+        }
+        {
+            std::unique_lock lock(mStateMutex);
+            mState = STOPPED;
+        }
+        return C2_OK;
+    }
+
+    c2_status_t reset() override {
+        //std::chrono::steady_clock::time_point deadline = std::chrono::steady_clock::now() + 500ms;
+        {
+            std::unique_lock lock(mStateMutex);
+            if (mState == RESETTING) {
+                return C2_DUPLICATE;
+            }
+            if (mState == RELEASED) {
+                return C2_BAD_STATE;
+            }
+            mState = RESETTING;
+        }
+        {
+            std::unique_lock lock(mQueueMutex);
+            mQueueCondition.notify_all();
+        }
+        {
+            std::unique_lock lock(mProcessingMutex);
+            if (mProcessingThread.joinable()) {
+                mProcessingThread.join();
+            }
+        }
+        {
+            std::unique_lock lock(mStateMutex);
+            mState = STOPPED;
+        }
+        return C2_OK;
+    }
+
+    c2_status_t release() override {
+        //std::chrono::steady_clock::time_point deadline = std::chrono::steady_clock::now() + 500ms;
+        {
+            std::unique_lock lock(mStateMutex);
+            if (mState == RELEASED || mState == RELEASING) {
+                return C2_DUPLICATE;
+            }
+            // TODO: return C2_BAD_STATE if not stopped
+            mState = RELEASING;
+        }
+        {
+            std::unique_lock lock(mQueueMutex);
+            mQueueCondition.notify_all();
+        }
+        {
+            std::unique_lock lock(mProcessingMutex);
+            if (mProcessingThread.joinable()) {
+                mProcessingThread.join();
+            }
+        }
+        {
+            std::unique_lock lock(mStateMutex);
+            mState = RELEASED;
+        }
+        return C2_OK;
+    }
+
+    std::shared_ptr<C2ComponentInterface> intf() override {
+        return mIntf;
+    }
+
+private:
+    void processLoop(std::shared_ptr<SampleToneMappingFilter> thiz) {
+        constexpr float kDefaultMaxLumiance = 500.0;
+        constexpr float kDefaultMaxMasteringLuminance = 1000.0;
+        constexpr float kDefaultMaxContentLuminance = 1000.0;
+        constexpr uint32_t kDstUsage =
+                GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN |
+                GRALLOC_USAGE_HW_RENDER | GRALLOC_USAGE_HW_TEXTURE;
+
+        int32_t workCount = 0;
+        std::unique_ptr<renderengine::RenderEngine> renderEngine = renderengine::RenderEngine::create(
+                renderengine::RenderEngineCreationArgs::Builder()
+                    .setPixelFormat(static_cast<int>(ui::PixelFormat::RGBA_8888))
+                    .setImageCacheSize(2 /*maxFrameBufferAcquiredBuffers*/)
+                    .setUseColorManagerment(true)
+                    .setEnableProtectedContext(false)
+                    .setPrecacheToneMapperShaderOnly(true)
+                    .setContextPriority(renderengine::RenderEngine::ContextPriority::LOW)
+                    .build());
+        if (!renderEngine) {
+            std::unique_lock lock(mListenerMutex);
+            mListener->onError_nb(thiz, C2_CORRUPTED);
+            return;
+        }
+        uint32_t textureName = 0;
+        renderEngine->genTextures(1, &textureName);
+
+        while (true) {
+            // Before doing anything, verify the state
+            {
+                std::unique_lock lock(mStateMutex);
+                if (mState != RUNNING) {
+                    break;
+                }
+            }
+            // Extract one work item
+            std::unique_ptr<C2Work> work;
+            {
+                std::unique_lock lock(mQueueMutex);
+                if (mQueue.empty()) {
+                    mQueueCondition.wait_for(lock, 1s);
+                }
+                if (mQueue.empty()) {
+                    continue;
+                }
+                mQueue.front().swap(work);
+                mQueue.pop_front();
+                ++workCount;
+            }
+            LOG(VERBOSE) << "work #" << workCount << ": flags=" << work->input.flags
+                    << " timestamp=" << work->input.ordinal.timestamp.peek();;
+            std::shared_ptr<C2StreamHdrStaticInfo::input> hdrStaticInfo =
+                mIntf->getHdrStaticMetadata();
+            uint32_t dataspace = mIntf->getDataSpace();
+
+            std::shared_ptr<C2Buffer> buffer;
+            if (!work->input.buffers.empty()) {
+                buffer = work->input.buffers.front();
+            }
+            std::shared_ptr<C2Buffer> outC2Buffer;
+            status_t err = OK;
+            if (buffer) {
+                if (buffer->hasInfo(C2StreamHdrStaticInfo::output::PARAM_TYPE)) {
+                    std::shared_ptr<const C2Info> info =
+                        buffer->getInfo(C2StreamHdrStaticInfo::output::PARAM_TYPE);
+                    std::unique_ptr<C2Param> flipped = C2Param::CopyAsStream(
+                            *info, false /* output */, info->stream());
+                    hdrStaticInfo.reset(static_cast<C2StreamHdrStaticInfo::input *>(
+                            flipped.release()));
+                }
+                const C2Handle *c2Handle =
+                    buffer->data().graphicBlocks().front().handle();
+                uint32_t width, height, format, stride, igbp_slot, generation;
+                uint64_t usage, igbp_id;
+                _UnwrapNativeCodec2GrallocMetadata(
+                        c2Handle, &width, &height, &format, &usage, &stride, &generation,
+                        &igbp_id, &igbp_slot);
+                native_handle_t *grallocHandle = UnwrapNativeCodec2GrallocHandle(c2Handle);
+                sp<GraphicBuffer> srcBuffer = new GraphicBuffer(
+                        grallocHandle, GraphicBuffer::CLONE_HANDLE,
+                        width, height, format, 1, usage, stride);
+
+                std::shared_ptr<C2GraphicBlock> dstBlock;
+                C2BlockPool::local_id_t poolId = mIntf->getPoolId();
+                std::shared_ptr<C2BlockPool> pool;
+                GetCodec2BlockPool(poolId, thiz, &pool);
+                pool->fetchGraphicBlock(
+                        width, height, HAL_PIXEL_FORMAT_RGBA_8888, C2AndroidMemoryUsage::FromGrallocUsage(kDstUsage),
+                        &dstBlock);
+                outC2Buffer = C2Buffer::CreateGraphicBuffer(
+                        dstBlock->share(C2Rect(width, height), C2Fence()));
+                c2Handle = dstBlock->handle();
+                _UnwrapNativeCodec2GrallocMetadata(
+                        c2Handle, &width, &height, &format, &usage, &stride, &generation,
+                        &igbp_id, &igbp_slot);
+                grallocHandle = UnwrapNativeCodec2GrallocHandle(c2Handle);
+                sp<GraphicBuffer> dstBuffer = new GraphicBuffer(
+                        grallocHandle, GraphicBuffer::CLONE_HANDLE,
+                        width, height, format, 1, usage, stride);
+
+                Rect sourceCrop(0, 0, width, height);
+
+                renderengine::DisplaySettings clientCompositionDisplay;
+                std::vector<const renderengine::LayerSettings*> clientCompositionLayers;
+
+                clientCompositionDisplay.physicalDisplay = sourceCrop;
+                clientCompositionDisplay.clip = sourceCrop;
+
+                clientCompositionDisplay.outputDataspace = ui::Dataspace::V0_SRGB;
+                clientCompositionDisplay.maxLuminance = kDefaultMaxLumiance;
+                clientCompositionDisplay.clearRegion = Region::INVALID_REGION;
+                renderengine::LayerSettings layerSettings;
+                layerSettings.geometry.boundaries = sourceCrop.toFloatRect();
+                layerSettings.alpha = 1.0f;
+
+                layerSettings.sourceDataspace = static_cast<ui::Dataspace>(dataspace);
+
+                // from BufferLayer
+                layerSettings.source.buffer.buffer = srcBuffer;
+                layerSettings.source.buffer.isOpaque = true;
+                // TODO: fence
+                layerSettings.source.buffer.fence = Fence::NO_FENCE;
+                layerSettings.source.buffer.textureName = textureName;
+                layerSettings.source.buffer.usePremultipliedAlpha = false;
+                layerSettings.source.buffer.isY410BT2020 =
+                    (layerSettings.sourceDataspace == ui::Dataspace::BT2020_ITU_PQ ||
+                     layerSettings.sourceDataspace == ui::Dataspace::BT2020_ITU_HLG) &&
+                    format == HAL_PIXEL_FORMAT_RGBA_1010102;
+                layerSettings.source.buffer.maxMasteringLuminance =
+                    (hdrStaticInfo && *hdrStaticInfo &&
+                     hdrStaticInfo->mastering.maxLuminance > 0 &&
+                     hdrStaticInfo->mastering.minLuminance > 0)
+                        ? hdrStaticInfo->mastering.maxLuminance : kDefaultMaxMasteringLuminance;
+                layerSettings.source.buffer.maxContentLuminance =
+                    (hdrStaticInfo && *hdrStaticInfo && hdrStaticInfo->maxCll > 0)
+                        ? hdrStaticInfo->maxCll : kDefaultMaxContentLuminance;
+
+                // Set filtering to false since the capture itself doesn't involve
+                // any scaling, metadata retriever JNI is scaling the bitmap if
+                // display size is different from decoded size. If that scaling
+                // needs to be handled by server side, consider enable this based
+                // display size vs decoded size.
+                layerSettings.source.buffer.useTextureFiltering = false;
+                layerSettings.source.buffer.textureTransform = mat4();
+                clientCompositionLayers.push_back(&layerSettings);
+
+                // Use an empty fence for the buffer fence, since we just created the buffer so
+                // there is no need for synchronization with the GPU.
+                base::unique_fd bufferFence;
+                base::unique_fd drawFence;
+                renderEngine->useProtectedContext(false);
+                err = renderEngine->drawLayers(
+                        clientCompositionDisplay, clientCompositionLayers, dstBuffer.get(),
+                        /*useFramebufferCache=*/false, std::move(bufferFence), &drawFence);
+
+                sp<Fence> fence = new Fence(std::move(drawFence));
+
+                // We can move waiting for fence & sending it back on a separate thread to improve
+                // efficiency, but leaving it here for simplicity.
+                if (err != OK) {
+                    LOG(ERROR) << "drawLayers returned err " << err;
+                } else {
+                    err = fence->wait(500);
+                    if (err != OK) {
+                        LOG(WARNING) << "wait for fence returned err " << err;
+                    }
+                }
+                renderEngine->cleanupPostRender(renderengine::RenderEngine::CleanupMode::CLEAN_ALL);
+            }
+
+            work->worklets.front()->output.ordinal = work->input.ordinal;
+            work->worklets.front()->output.flags = work->input.flags;
+            if (err == OK) {
+                work->workletsProcessed = 1;
+                if (outC2Buffer) {
+                    work->worklets.front()->output.buffers.push_back(outC2Buffer);
+                }
+                work->result = C2_OK;
+            } else {
+                work->result = C2_CORRUPTED;
+            }
+            std::list<std::unique_ptr<C2Work>> items;
+            items.push_back(std::move(work));
+
+            std::unique_lock lock(mListenerMutex);
+            mListener->onWorkDone_nb(thiz, std::move(items));
+            LOG(VERBOSE) << "sent work #" << workCount;
+        }
+    }
+
+    mutable std::timed_mutex mListenerMutex;
+    std::shared_ptr<Listener> mListener;
+
+    mutable std::mutex mQueueMutex;
+    mutable std::condition_variable mQueueCondition;
+    std::list<std::unique_ptr<C2Work>> mQueue;
+
+    const std::shared_ptr<Interface> mIntf;
+
+    mutable std::mutex mStateMutex;
+    enum State {
+        STOPPED,
+        RUNNING,
+        RELEASED,
+        STARTING,   // STOPPED -> RUNNING
+        STOPPING,   // RUNNING -> STOPPED
+        RESETTING,  // <<ANY>> -> STOPPED
+        RELEASING,  // STOPPED -> RELEASED
+    } mState;
+
+    mutable std::mutex mProcessingMutex;
+    std::thread mProcessingThread;
+
+};
+
+// static
+const std::string SampleToneMappingFilter::Interface::NAME = "c2.sample.tone-mapper";
+// static
+const FilterPlugin_V1::Descriptor SampleToneMappingFilter::Interface::DESCRIPTOR = {
+    // controlParams
+    { C2StreamColorAspectsRequestInfo::output::PARAM_TYPE },
+    // affectedParams
+    {
+        C2StreamHdrStaticInfo::output::PARAM_TYPE,
+        C2StreamHdr10PlusInfo::output::PARAM_TYPE,
+        C2StreamColorAspectsInfo::output::PARAM_TYPE,
+    },
+};
+
+class SampleC2ComponentStore : public C2ComponentStore {
+public:
+    SampleC2ComponentStore()
+        : mReflector(std::make_shared<C2ReflectorHelper>()),
+          mIntf(mReflector),
+          mFactories(CreateFactories()) {
+    }
+    ~SampleC2ComponentStore() = default;
+
+    C2String getName() const override { return "android.sample.filter-plugin-store"; }
+    c2_status_t createComponent(
+            C2String name, std::shared_ptr<C2Component>* const component) override {
+        if (mFactories.count(name) == 0) {
+            return C2_BAD_VALUE;
+        }
+        return mFactories.at(name)->createComponent(++mNodeId, component);
+    }
+    c2_status_t createInterface(
+            C2String name, std::shared_ptr<C2ComponentInterface>* const interface) override {
+        if (mFactories.count(name) == 0) {
+            return C2_BAD_VALUE;
+        }
+        return mFactories.at(name)->createInterface(++mNodeId, interface);
+    }
+    std::vector<std::shared_ptr<const C2Component::Traits>> listComponents() override {
+        std::vector<std::shared_ptr<const C2Component::Traits>> ret;
+        for (const auto &[name, factory] : mFactories) {
+            ret.push_back(factory->getTraits());
+        }
+        return ret;
+    }
+    c2_status_t copyBuffer(
+            std::shared_ptr<C2GraphicBuffer>, std::shared_ptr<C2GraphicBuffer>) override {
+        return C2_OMITTED;
+    }
+    c2_status_t query_sm(
+            const std::vector<C2Param*> &stackParams,
+            const std::vector<C2Param::Index> &heapParamIndices,
+            std::vector<std::unique_ptr<C2Param>>* const heapParams) const override {
+        return mIntf.query(stackParams, heapParamIndices, C2_MAY_BLOCK, heapParams);
+    }
+    c2_status_t config_sm(
+            const std::vector<C2Param*> &params,
+            std::vector<std::unique_ptr<C2SettingResult>>* const failures) override {
+        return mIntf.config(params, C2_MAY_BLOCK, failures);
+    }
+    std::shared_ptr<C2ParamReflector> getParamReflector() const override {
+        return mReflector;
+    }
+    c2_status_t querySupportedParams_nb(
+            std::vector<std::shared_ptr<C2ParamDescriptor>> * const params) const override {
+        return mIntf.querySupportedParams(params);
+    }
+    c2_status_t querySupportedValues_sm(
+            std::vector<C2FieldSupportedValuesQuery> &fields) const override {
+        return mIntf.querySupportedValues(fields, C2_MAY_BLOCK);
+    }
+
+private:
+    class ComponentFactory {
+    public:
+        virtual ~ComponentFactory() = default;
+
+        const std::shared_ptr<const C2Component::Traits> &getTraits() { return mTraits; }
+
+        virtual c2_status_t createComponent(
+                c2_node_id_t id,
+                std::shared_ptr<C2Component>* const component) const = 0;
+        virtual c2_status_t createInterface(
+                c2_node_id_t id,
+                std::shared_ptr<C2ComponentInterface>* const interface) const = 0;
+    protected:
+        ComponentFactory(const std::shared_ptr<const C2Component::Traits> &traits)
+            : mTraits(traits) {
+        }
+    private:
+        const std::shared_ptr<const C2Component::Traits> mTraits;
+    };
+
+    template <class T>
+    struct ComponentFactoryImpl : public ComponentFactory {
+    public:
+        ComponentFactoryImpl(const std::shared_ptr<const C2Component::Traits> &traits)
+            : ComponentFactory(traits) {
+        }
+        ~ComponentFactoryImpl() override = default;
+        c2_status_t createComponent(
+                c2_node_id_t id,
+                std::shared_ptr<C2Component>* const component) const override {
+            *component = std::make_shared<T>(id);
+            return C2_OK;
+        }
+        c2_status_t createInterface(
+                c2_node_id_t id,
+                std::shared_ptr<C2ComponentInterface>* const interface) const override {
+            *interface = std::make_shared<typename T::Interface>(id);
+            return C2_OK;
+        }
+    };
+
+    template <class T>
+    static void AddFactory(std::map<C2String, std::unique_ptr<ComponentFactory>> *factories) {
+        std::shared_ptr<C2ComponentInterface> intf{new typename T::Interface(0)};
+        std::shared_ptr<C2Component::Traits> traits(new (std::nothrow) C2Component::Traits);
+        CHECK(C2InterfaceUtils::FillTraitsFromInterface(traits.get(), intf))
+                << "Failed to fill traits from interface";
+        factories->emplace(traits->name, new ComponentFactoryImpl<T>(traits));
+    }
+
+    static std::map<C2String, std::unique_ptr<ComponentFactory>> CreateFactories() {
+        std::map<C2String, std::unique_ptr<ComponentFactory>> factories;
+        AddFactory<SampleToneMappingFilter>(&factories);
+        return factories;
+    }
+
+
+    std::shared_ptr<C2ReflectorHelper> mReflector;
+    struct Interface : public C2InterfaceHelper {
+        explicit Interface(std::shared_ptr<C2ReflectorHelper> reflector)
+            : C2InterfaceHelper(reflector) {
+        }
+    } mIntf;
+
+    const std::map<C2String, std::unique_ptr<ComponentFactory>> mFactories;
+
+    std::atomic_int32_t mNodeId{0};
+};
+
+class SampleFilterPlugin : public FilterPlugin_V1 {
+public:
+    SampleFilterPlugin() : mStore(new SampleC2ComponentStore) {}
+    ~SampleFilterPlugin() override = default;
+
+    std::shared_ptr<C2ComponentStore> getComponentStore() override {
+        return mStore;
+    }
+
+    bool describe(C2String name, Descriptor *desc) override {
+        if (name == SampleToneMappingFilter::Interface::NAME) {
+            *desc = SampleToneMappingFilter::Interface::DESCRIPTOR;
+            return true;
+        }
+        return false;
+    }
+
+    bool isFilteringEnabled(const std::shared_ptr<C2ComponentInterface> &intf) override {
+        if (intf->getName() == SampleToneMappingFilter::Interface::NAME) {
+            return SampleToneMappingFilter::Interface::IsFilteringEnabled(intf);
+        }
+        return false;
+    }
+
+private:
+    std::shared_ptr<C2ComponentStore> mStore;
+};
+
+}  // namespace android
+
+extern "C" {
+
+int32_t GetFilterPluginVersion() {
+    return ::android::SampleFilterPlugin::VERSION;
+}
+
+void *CreateFilterPlugin() {
+    return new ::android::SampleFilterPlugin;
+}
+
+void DestroyFilterPlugin(void *plugin) {
+    delete (::android::SampleFilterPlugin *)plugin;
+}
+
+}  // extern "C"
diff --git a/media/codec2/sfplugin/CCodec.cpp b/media/codec2/sfplugin/CCodec.cpp
index 4fd0341..de0a129 100644
--- a/media/codec2/sfplugin/CCodec.cpp
+++ b/media/codec2/sfplugin/CCodec.cpp
@@ -1054,7 +1054,10 @@
         C2StreamMaxBufferSizeInfo::input maxInputSize(0u, 0u);
         C2PrependHeaderModeSetting prepend(PREPEND_HEADER_TO_NONE);
 
+        C2Param::Index colorAspectsRequestIndex =
+            C2StreamColorAspectsInfo::output::PARAM_TYPE | C2Param::CoreIndex::IS_REQUEST_FLAG;
         std::initializer_list<C2Param::Index> indices {
+            colorAspectsRequestIndex.withStream(0u),
         };
         c2_status_t c2err = comp->query(
                 { &usage, &maxInputSize, &prepend },
@@ -1065,11 +1068,6 @@
             ALOGE("Failed to query component interface: %d", c2err);
             return UNKNOWN_ERROR;
         }
-        if (params.size() != indices.size()) {
-            ALOGE("Component returns wrong number of params: expected %zu actual %zu",
-                    indices.size(), params.size());
-            return UNKNOWN_ERROR;
-        }
         if (usage) {
             if (usage.value & C2MemoryUsage::CPU_READ) {
                 config->mInputFormat->setInt32("using-sw-read-often", true);
@@ -1191,6 +1189,33 @@
             }
         }
 
+        std::unique_ptr<C2Param> colorTransferRequestParam;
+        for (std::unique_ptr<C2Param> &param : params) {
+            if (param->index() == colorAspectsRequestIndex.withStream(0u)) {
+                ALOGI("found color transfer request param");
+                colorTransferRequestParam = std::move(param);
+            }
+        }
+        int32_t colorTransferRequest = 0;
+        if (config->mDomain & (Config::IS_IMAGE | Config::IS_VIDEO)
+                && !sdkParams->findInt32("color-transfer-request", &colorTransferRequest)) {
+            colorTransferRequest = 0;
+        }
+
+        if (colorTransferRequest != 0) {
+            if (colorTransferRequestParam && *colorTransferRequestParam) {
+                C2StreamColorAspectsInfo::output *info =
+                    static_cast<C2StreamColorAspectsInfo::output *>(
+                            colorTransferRequestParam.get());
+                if (!C2Mapper::map(info->transfer, &colorTransferRequest)) {
+                    colorTransferRequest = 0;
+                }
+            } else {
+                colorTransferRequest = 0;
+            }
+            config->mInputFormat->setInt32("color-transfer-request", colorTransferRequest);
+        }
+
         ALOGD("setup formats input: %s and output: %s",
                 config->mInputFormat->debugString().c_str(),
                 config->mOutputFormat->debugString().c_str());
diff --git a/media/codec2/sfplugin/CCodecConfig.cpp b/media/codec2/sfplugin/CCodecConfig.cpp
index 5decb99..7214bf7 100644
--- a/media/codec2/sfplugin/CCodecConfig.cpp
+++ b/media/codec2/sfplugin/CCodecConfig.cpp
@@ -415,19 +415,37 @@
     add(ConfigMapper("color-matrix",        C2_PARAMKEY_DEFAULT_COLOR_ASPECTS,   "matrix")
         .limitTo((D::VIDEO | D::IMAGE) & D::DECODER  & D::CODED & (D::CONFIG | D::PARAM)));
 
+    // read back default for decoders. This is needed in case the component does not support
+    // color aspects. In that case, these values get copied to color-* keys.
+    add(ConfigMapper("default-color-range",     C2_PARAMKEY_DEFAULT_COLOR_ASPECTS,   "range")
+        .limitTo((D::VIDEO | D::IMAGE) & D::DECODER  & D::CODED & D::READ)
+        .withC2Mappers<C2Color::range_t>());
+    add(ConfigMapper("default-color-transfer",  C2_PARAMKEY_DEFAULT_COLOR_ASPECTS,   "transfer")
+        .limitTo((D::VIDEO | D::IMAGE) & D::DECODER  & D::CODED & D::READ)
+        .withC2Mappers<C2Color::transfer_t>());
+    add(ConfigMapper("default-color-primaries", C2_PARAMKEY_DEFAULT_COLOR_ASPECTS,   "primaries")
+        .limitTo((D::VIDEO | D::IMAGE) & D::DECODER  & D::CODED & D::READ));
+    add(ConfigMapper("default-color-matrix",    C2_PARAMKEY_DEFAULT_COLOR_ASPECTS,   "matrix")
+        .limitTo((D::VIDEO | D::IMAGE) & D::DECODER  & D::CODED & D::READ));
+
     // read back final for decoder output (also, configure final aspects as well. This should be
     // overwritten based on coded/default values if component supports color aspects, but is used
     // as final values if component does not support aspects at all)
     add(ConfigMapper(KEY_COLOR_RANGE,       C2_PARAMKEY_COLOR_ASPECTS,   "range")
-        .limitTo((D::VIDEO | D::IMAGE) & D::DECODER  & D::RAW)
+        .limitTo((D::VIDEO | D::IMAGE) & D::DECODER  & D::RAW & D::READ)
         .withC2Mappers<C2Color::range_t>());
     add(ConfigMapper(KEY_COLOR_TRANSFER,    C2_PARAMKEY_COLOR_ASPECTS,   "transfer")
-        .limitTo((D::VIDEO | D::IMAGE) & D::DECODER  & D::RAW)
+        .limitTo((D::VIDEO | D::IMAGE) & D::DECODER  & D::RAW & D::READ)
         .withC2Mappers<C2Color::transfer_t>());
     add(ConfigMapper("color-primaries",     C2_PARAMKEY_COLOR_ASPECTS,   "primaries")
-        .limitTo((D::VIDEO | D::IMAGE) & D::DECODER  & D::RAW));
+        .limitTo((D::VIDEO | D::IMAGE) & D::DECODER  & D::RAW & D::READ));
     add(ConfigMapper("color-matrix",        C2_PARAMKEY_COLOR_ASPECTS,   "matrix")
-        .limitTo((D::VIDEO | D::IMAGE) & D::DECODER  & D::RAW));
+        .limitTo((D::VIDEO | D::IMAGE) & D::DECODER  & D::RAW & D::READ));
+
+    // configure transfer request
+    add(ConfigMapper("color-transfer-request", C2_PARAMKEY_COLOR_ASPECTS, "transfer")
+        .limitTo((D::VIDEO | D::IMAGE) & D::DECODER  & D::RAW & D::CONFIG)
+        .withC2Mappers<C2Color::transfer_t>());
 
     // configure source aspects for encoders and read them back on the coded(!) port.
     // This is to ensure muxing the desired aspects into the container.
@@ -1001,11 +1019,14 @@
                     new C2StreamPixelAspectRatioInfo::output(0u, 1u, 1u),
                     C2_PARAMKEY_PIXEL_ASPECT_RATIO);
             addLocalParam(new C2StreamRotationInfo::output(0u, 0), C2_PARAMKEY_ROTATION);
-            addLocalParam(new C2StreamColorAspectsInfo::output(0u), C2_PARAMKEY_COLOR_ASPECTS);
+            addLocalParam(
+                    new C2StreamColorAspectsTuning::output(0u),
+                    C2_PARAMKEY_DEFAULT_COLOR_ASPECTS);
             addLocalParam<C2StreamDataSpaceInfo::output>(C2_PARAMKEY_DATA_SPACE);
             addLocalParam<C2StreamHdrStaticInfo::output>(C2_PARAMKEY_HDR_STATIC_INFO);
-            addLocalParam(new C2StreamSurfaceScalingInfo::output(0u, VIDEO_SCALING_MODE_SCALE_TO_FIT),
-                          C2_PARAMKEY_SURFACE_SCALING_MODE);
+            addLocalParam(
+                    new C2StreamSurfaceScalingInfo::output(0u, VIDEO_SCALING_MODE_SCALE_TO_FIT),
+                    C2_PARAMKEY_SURFACE_SCALING_MODE);
         } else {
             addLocalParam(new C2StreamColorAspectsInfo::input(0u), C2_PARAMKEY_COLOR_ASPECTS);
         }
@@ -1289,8 +1310,37 @@
     }
 
     { // convert color info
+        // move default color to color aspect if not read from the component
+        int32_t tmp;
+        int32_t range;
+        if (msg->findInt32("default-color-range", &range)) {
+            if (!msg->findInt32(KEY_COLOR_RANGE, &tmp)) {
+                msg->setInt32(KEY_COLOR_RANGE, range);
+            }
+            msg->removeEntryAt(msg->findEntryByName("default-color-range"));
+        }
+        int32_t transfer;
+        if (msg->findInt32("default-color-transfer", &transfer)) {
+            if (!msg->findInt32(KEY_COLOR_TRANSFER, &tmp)) {
+                msg->setInt32(KEY_COLOR_TRANSFER, transfer);
+            }
+            msg->removeEntryAt(msg->findEntryByName("default-color-transfer"));
+        }
         C2Color::primaries_t primaries;
+        if (msg->findInt32("default-color-primaries", (int32_t*)&primaries)) {
+            if (!msg->findInt32("color-primaries", &tmp)) {
+                msg->setInt32("color-primaries", primaries);
+            }
+            msg->removeEntryAt(msg->findEntryByName("default-color-primaries"));
+        }
         C2Color::matrix_t matrix;
+        if (msg->findInt32("default-color-matrix", (int32_t*)&matrix)) {
+            if (!msg->findInt32("color-matrix", &tmp)) {
+                msg->setInt32("color-matrix", matrix);
+            }
+            msg->removeEntryAt(msg->findEntryByName("default-color-matrix"));
+        }
+
         if (msg->findInt32("color-primaries", (int32_t*)&primaries)
                 && msg->findInt32("color-matrix", (int32_t*)&matrix)) {
             int32_t standard;
@@ -1382,22 +1432,22 @@
                 meta.sType1.mMinDisplayLuminance = hdr.mastering.minLuminance / 0.0001 + 0.5;
                 meta.sType1.mMaxContentLightLevel = hdr.maxCll + 0.5;
                 meta.sType1.mMaxFrameAverageLightLevel = hdr.maxFall + 0.5;
-                msg->removeEntryAt(msg->findEntryByName("smpte2086.red.x"));
-                msg->removeEntryAt(msg->findEntryByName("smpte2086.red.y"));
-                msg->removeEntryAt(msg->findEntryByName("smpte2086.green.x"));
-                msg->removeEntryAt(msg->findEntryByName("smpte2086.green.y"));
-                msg->removeEntryAt(msg->findEntryByName("smpte2086.blue.x"));
-                msg->removeEntryAt(msg->findEntryByName("smpte2086.blue.y"));
-                msg->removeEntryAt(msg->findEntryByName("smpte2086.white.x"));
-                msg->removeEntryAt(msg->findEntryByName("smpte2086.white.y"));
-                msg->removeEntryAt(msg->findEntryByName("smpte2086.max-luminance"));
-                msg->removeEntryAt(msg->findEntryByName("smpte2086.min-luminance"));
-                msg->removeEntryAt(msg->findEntryByName("cta861.max-cll"));
-                msg->removeEntryAt(msg->findEntryByName("cta861.max-fall"));
                 msg->setBuffer(KEY_HDR_STATIC_INFO, ABuffer::CreateAsCopy(&meta, sizeof(meta)));
             } else {
                 ALOGD("found invalid HDR static metadata %s", msg->debugString(8).c_str());
             }
+            msg->removeEntryAt(msg->findEntryByName("smpte2086.red.x"));
+            msg->removeEntryAt(msg->findEntryByName("smpte2086.red.y"));
+            msg->removeEntryAt(msg->findEntryByName("smpte2086.green.x"));
+            msg->removeEntryAt(msg->findEntryByName("smpte2086.green.y"));
+            msg->removeEntryAt(msg->findEntryByName("smpte2086.blue.x"));
+            msg->removeEntryAt(msg->findEntryByName("smpte2086.blue.y"));
+            msg->removeEntryAt(msg->findEntryByName("smpte2086.white.x"));
+            msg->removeEntryAt(msg->findEntryByName("smpte2086.white.y"));
+            msg->removeEntryAt(msg->findEntryByName("smpte2086.max-luminance"));
+            msg->removeEntryAt(msg->findEntryByName("smpte2086.min-luminance"));
+            msg->removeEntryAt(msg->findEntryByName("cta861.max-cll"));
+            msg->removeEntryAt(msg->findEntryByName("cta861.max-fall"));
         }
     }
 
@@ -1632,8 +1682,8 @@
             }
         }
     }
-    ALOGV("filtered %s to %s", params->debugString(4).c_str(),
-            filtered.debugString(4).c_str());
+    ALOGV("filter src msg %s", params->debugString(4).c_str());
+    ALOGV("filter dst params %s", filtered.debugString(4).c_str());
     return filtered;
 }
 
diff --git a/media/libaudioclient/AudioTrack.cpp b/media/libaudioclient/AudioTrack.cpp
index 57bd04f..a8255a5 100644
--- a/media/libaudioclient/AudioTrack.cpp
+++ b/media/libaudioclient/AudioTrack.cpp
@@ -1857,6 +1857,8 @@
         .set(AMEDIAMETRICS_PROP_FLAGS, toString(mFlags).c_str())
         .set(AMEDIAMETRICS_PROP_ORIGINALFLAGS, toString(mOrigFlags).c_str())
         .set(AMEDIAMETRICS_PROP_SESSIONID, (int32_t)mSessionId)
+        .set(AMEDIAMETRICS_PROP_LOGSESSIONID, mLogSessionId)
+        .set(AMEDIAMETRICS_PROP_PLAYERIID, mPlayerIId)
         .set(AMEDIAMETRICS_PROP_TRACKID, mPortId) // dup from key
         .set(AMEDIAMETRICS_PROP_CONTENTTYPE, toString(mAttributes.content_type).c_str())
         .set(AMEDIAMETRICS_PROP_USAGE, toString(mAttributes.usage).c_str())
@@ -3263,6 +3265,30 @@
     return mProxy->getUnderrunFrames();
 }
 
+void AudioTrack::setLogSessionId(const char *logSessionId)
+{
+     AutoMutex lock(mLock);
+    if (mLogSessionId == logSessionId) return;
+
+     mLogSessionId = logSessionId;
+     mediametrics::LogItem(mMetricsId)
+         .set(AMEDIAMETRICS_PROP_EVENT, AMEDIAMETRICS_PROP_EVENT_VALUE_SETLOGSESSIONID)
+         .set(AMEDIAMETRICS_PROP_LOGSESSIONID, logSessionId)
+         .record();
+}
+
+void AudioTrack::setPlayerIId(int playerIId)
+{
+    AutoMutex lock(mLock);
+    if (mPlayerIId == playerIId) return;
+
+    mPlayerIId = playerIId;
+    mediametrics::LogItem(mMetricsId)
+        .set(AMEDIAMETRICS_PROP_EVENT, AMEDIAMETRICS_PROP_EVENT_VALUE_SETPLAYERIID)
+        .set(AMEDIAMETRICS_PROP_PLAYERIID, playerIId)
+        .record();
+}
+
 status_t AudioTrack::addAudioDeviceCallback(const sp<AudioSystem::AudioDeviceCallback>& callback)
 {
 
diff --git a/media/libaudioclient/include/media/AudioTrack.h b/media/libaudioclient/include/media/AudioTrack.h
index e60ed55..51f1445 100644
--- a/media/libaudioclient/include/media/AudioTrack.h
+++ b/media/libaudioclient/include/media/AudioTrack.h
@@ -986,6 +986,22 @@
      */
             audio_port_handle_t getPortId() const { return mPortId; };
 
+    /* Sets the LogSessionId field which is used for metrics association of
+     * this object with other objects.
+     */
+            void setLogSessionId(const char *logSessionId);
+
+    /* Sets the playerIId field to associate the AudioTrack with an interface managed by
+     * AudioService.
+     *
+     * If this value is not set, then the playerIId is reported as -1
+     * (not associated with an AudioService player interface).
+     *
+     * For metrics purposes, we keep the playerIId association in the native
+     * client AudioTrack to improve the robustness under track restoration.
+     */
+            void setPlayerIId(int playerIId);
+
             void setAudioTrackCallback(const sp<media::IAudioTrackCallback>& callback) {
                 mAudioTrackCallback->setAudioTrackCallback(callback);
             }
@@ -1255,6 +1271,18 @@
     int                     mAuxEffectId;
     audio_port_handle_t     mPortId;                    // Id from Audio Policy Manager
 
+    /**
+     * mPlayerIId is the player id of the AudioTrack used by AudioManager.
+     * For an AudioTrack created by the Java interface, this is generally set once.
+     */
+    int                     mPlayerIId = -1;  // AudioManager.h PLAYER_PIID_INVALID
+
+    /**
+     * mLogSessionId is a string identifying this AudioTrack for the metrics service.
+     * It may be unique or shared with other objects.
+     */
+    std::string             mLogSessionId{};
+
     mutable Mutex           mLock;
 
     int                     mPreviousPriority;          // before start()
diff --git a/media/libmediametrics/include/MediaMetricsConstants.h b/media/libmediametrics/include/MediaMetricsConstants.h
index 2af7eee..a181b06 100644
--- a/media/libmediametrics/include/MediaMetricsConstants.h
+++ b/media/libmediametrics/include/MediaMetricsConstants.h
@@ -125,12 +125,14 @@
 #define AMEDIAMETRICS_PROP_INPUTDEVICES   "inputDevices"   // string value
 #define AMEDIAMETRICS_PROP_INTERVALCOUNT  "intervalCount"  // int32
 #define AMEDIAMETRICS_PROP_LATENCYMS      "latencyMs"      // double value
+#define AMEDIAMETRICS_PROP_LOGSESSIONID   "logSessionId"   // hex string, "" none
 #define AMEDIAMETRICS_PROP_NAME           "name"           // string value
 #define AMEDIAMETRICS_PROP_ORIGINALFLAGS  "originalFlags"  // int32
 #define AMEDIAMETRICS_PROP_OUTPUTDEVICES  "outputDevices"  // string value
 #define AMEDIAMETRICS_PROP_PERFORMANCEMODE "performanceMode"    // string value, "none", lowLatency"
 #define AMEDIAMETRICS_PROP_PLAYBACK_PITCH "playback.pitch" // double value (AudioTrack)
 #define AMEDIAMETRICS_PROP_PLAYBACK_SPEED "playback.speed" // double value (AudioTrack)
+#define AMEDIAMETRICS_PROP_PLAYERIID      "playerIId"      // int32 (-1 invalid/unset IID)
 #define AMEDIAMETRICS_PROP_ROUTEDDEVICEID "routedDeviceId" // int32
 #define AMEDIAMETRICS_PROP_SAMPLERATE     "sampleRate"     // int32
 #define AMEDIAMETRICS_PROP_SELECTEDDEVICEID "selectedDeviceId" // int32
@@ -181,7 +183,9 @@
 #define AMEDIAMETRICS_PROP_EVENT_VALUE_RESTORE    "restore"
 #define AMEDIAMETRICS_PROP_EVENT_VALUE_SETMODE    "setMode" // AudioFlinger
 #define AMEDIAMETRICS_PROP_EVENT_VALUE_SETBUFFERSIZE    "setBufferSize" // AudioTrack
+#define AMEDIAMETRICS_PROP_EVENT_VALUE_SETLOGSESSIONID  "setLogSessionId" // AudioTrack, Record
 #define AMEDIAMETRICS_PROP_EVENT_VALUE_SETPLAYBACKPARAM "setPlaybackParam" // AudioTrack
+#define AMEDIAMETRICS_PROP_EVENT_VALUE_SETPLAYERIID "setPlayerIId" // AudioTrack
 #define AMEDIAMETRICS_PROP_EVENT_VALUE_SETVOICEVOLUME   "setVoiceVolume" // AudioFlinger
 #define AMEDIAMETRICS_PROP_EVENT_VALUE_SETVOLUME  "setVolume"  // AudioTrack
 #define AMEDIAMETRICS_PROP_EVENT_VALUE_START      "start"  // AudioTrack, AudioRecord
diff --git a/media/libmediatranscoding/TranscodingClientManager.cpp b/media/libmediatranscoding/TranscodingClientManager.cpp
index b6a2381..76bb33e 100644
--- a/media/libmediatranscoding/TranscodingClientManager.cpp
+++ b/media/libmediatranscoding/TranscodingClientManager.cpp
@@ -315,6 +315,15 @@
 status_t TranscodingClientManager::addClient(
         const std::shared_ptr<ITranscodingClientCallback>& callback, const std::string& clientName,
         const std::string& opPackageName, std::shared_ptr<ITranscodingClient>* outClient) {
+    int32_t callingPid = AIBinder_getCallingPid();
+    int32_t callingUid = AIBinder_getCallingUid();
+
+    // Check if client has the permission
+    if (!isTrustedCaller(callingPid, callingUid)) {
+        ALOGE("addClient rejected (clientPid %d, clientUid %d)", callingPid, callingUid);
+        return IMediaTranscodingService::ERROR_PERMISSION_DENIED;
+    }
+
     // Validate the client.
     if (callback == nullptr || clientName.empty() || opPackageName.empty()) {
         ALOGE("Invalid client");
diff --git a/media/libstagefright/bqhelper/tests/Android.bp b/media/libstagefright/bqhelper/tests/Android.bp
index 6b53e38..95953ee 100644
--- a/media/libstagefright/bqhelper/tests/Android.bp
+++ b/media/libstagefright/bqhelper/tests/Android.bp
@@ -9,17 +9,19 @@
 
 cc_test {
     name: "FrameDropper_test",
-    test_suites: ["device-tests"],
-
+    test_suites: [
+        "device-tests",
+        "mts",
+    ],
     srcs: ["FrameDropper_test.cpp"],
-
     shared_libs: [
         "libstagefright_bufferqueue_helper",
         "libutils",
     ],
-
     cflags: [
         "-Werror",
         "-Wall",
     ],
+    compile_multilib: "first",
+
 }
diff --git a/media/libstagefright/include/media/stagefright/MediaCodecConstants.h b/media/libstagefright/include/media/stagefright/MediaCodecConstants.h
index d3ced29..619cb44 100644
--- a/media/libstagefright/include/media/stagefright/MediaCodecConstants.h
+++ b/media/libstagefright/include/media/stagefright/MediaCodecConstants.h
@@ -753,7 +753,7 @@
 constexpr char KEY_CA_SYSTEM_ID[] = "ca-system-id";
 constexpr char KEY_CA_PRIVATE_DATA[] = "ca-private-data";
 constexpr char KEY_CAPTURE_RATE[] = "capture-rate";
-constexpr char KEY_CHANNEL_COUNT[] = "channel-count";
+constexpr char KEY_CHANNEL_COUNT[] = "channel-count";   // value N, eq to range 1..N
 constexpr char KEY_CHANNEL_MASK[] = "channel-mask";
 constexpr char KEY_COLOR_FORMAT[] = "color-format";
 constexpr char KEY_COLOR_RANGE[] = "color-range";
@@ -808,6 +808,14 @@
 constexpr char KEY_TILE_HEIGHT[] = "tile-height";
 constexpr char KEY_TILE_WIDTH[] = "tile-width";
 constexpr char KEY_TRACK_ID[] = "track-id";
+constexpr char KEY_VIDEO_QP_B_MAX[] = "video-qp-b-max";
+constexpr char KEY_VIDEO_QP_B_MIN[] = "video-qp-b-min";
+constexpr char KEY_VIDEO_QP_I_MAX[] = "video-qp-i-max";
+constexpr char KEY_VIDEO_QP_I_MIN[] = "video-qp-i-min";
+constexpr char KEY_VIDEO_QP_MAX[] = "video-qp-max";
+constexpr char KEY_VIDEO_QP_MIN[] = "video-qp-min";
+constexpr char KEY_VIDEO_QP_P_MAX[] = "video-qp-p-max";
+constexpr char KEY_VIDEO_QP_P_MIN[] = "video-qp-p-min";
 constexpr char KEY_WIDTH[] = "width";
 
 // from MediaCodec.java
diff --git a/media/ndk/Android.bp b/media/ndk/Android.bp
index 8172334..3007574 100644
--- a/media/ndk/Android.bp
+++ b/media/ndk/Android.bp
@@ -276,5 +276,9 @@
         },
     },
 
-    apex_available: ["com.android.media"],
+    apex_available: [
+        "//apex_available:platform",
+        "com.android.media",
+        "com.android.media.swcodec",
+    ],
 }
diff --git a/media/ndk/NdkMediaFormat.cpp b/media/ndk/NdkMediaFormat.cpp
index 8e673ca..1773023 100644
--- a/media/ndk/NdkMediaFormat.cpp
+++ b/media/ndk/NdkMediaFormat.cpp
@@ -383,6 +383,14 @@
 EXPORT const char* AMEDIAFORMAT_KEY_TRACK_ID = "track-id";
 EXPORT const char* AMEDIAFORMAT_KEY_TRACK_INDEX = "track-index";
 EXPORT const char* AMEDIAFORMAT_KEY_VALID_SAMPLES = "valid-samples";
+EXPORT const char* AMEDIAFORMAT_VIDEO_QP_B_MAX = "video-qp-b-max";
+EXPORT const char* AMEDIAFORMAT_VIDEO_QP_B_MIN = "video-qp-b-min";
+EXPORT const char* AMEDIAFORMAT_VIDEO_QP_I_MAX = "video-qp-i-max";
+EXPORT const char* AMEDIAFORMAT_VIDEO_QP_I_MIN = "video-qp-i-min";
+EXPORT const char* AMEDIAFORMAT_VIDEO_QP_MAX = "video-qp-max";
+EXPORT const char* AMEDIAFORMAT_VIDEO_QP_MIN = "video-qp-min";
+EXPORT const char* AMEDIAFORMAT_VIDEO_QP_P_MAX = "video-qp-p-max";
+EXPORT const char* AMEDIAFORMAT_VIDEO_QP_P_MIN = "video-qp-p-min";
 EXPORT const char* AMEDIAFORMAT_KEY_WIDTH = "width";
 EXPORT const char* AMEDIAFORMAT_KEY_XMP_OFFSET = "xmp-offset";
 EXPORT const char* AMEDIAFORMAT_KEY_XMP_SIZE = "xmp-size";
diff --git a/media/ndk/include/media/NdkMediaFormat.h b/media/ndk/include/media/NdkMediaFormat.h
index eb6d510..476bbd9 100644
--- a/media/ndk/include/media/NdkMediaFormat.h
+++ b/media/ndk/include/media/NdkMediaFormat.h
@@ -308,6 +308,15 @@
 extern const char* AMEDIAFORMAT_KEY_XMP_OFFSET __INTRODUCED_IN(31);
 extern const char* AMEDIAFORMAT_KEY_XMP_SIZE __INTRODUCED_IN(31);
 
+extern const char* AMEDIAFORMAT_VIDEO_QP_B_MAX __INTRODUCED_IN(31);
+extern const char* AMEDIAFORMAT_VIDEO_QP_B_MIN __INTRODUCED_IN(31);
+extern const char* AMEDIAFORMAT_VIDEO_QP_I_MAX __INTRODUCED_IN(31);
+extern const char* AMEDIAFORMAT_VIDEO_QP_I_MIN __INTRODUCED_IN(31);
+extern const char* AMEDIAFORMAT_VIDEO_QP_MAX __INTRODUCED_IN(31);
+extern const char* AMEDIAFORMAT_VIDEO_QP_MIN __INTRODUCED_IN(31);
+extern const char* AMEDIAFORMAT_VIDEO_QP_P_MAX __INTRODUCED_IN(31);
+extern const char* AMEDIAFORMAT_VIDEO_QP_P_MIN __INTRODUCED_IN(31);
+
 __END_DECLS
 
 #endif // _NDK_MEDIA_FORMAT_H
diff --git a/services/audioflinger/AudioFlinger.cpp b/services/audioflinger/AudioFlinger.cpp
index dc2c171..ae9fc64 100644
--- a/services/audioflinger/AudioFlinger.cpp
+++ b/services/audioflinger/AudioFlinger.cpp
@@ -413,13 +413,18 @@
 /* static */
 int AudioFlinger::onExternalVibrationStart(const sp<os::ExternalVibration>& externalVibration) {
     sp<os::IExternalVibratorService> evs = getExternalVibratorService();
-    if (evs != 0) {
+    if (evs != nullptr) {
         int32_t ret;
         binder::Status status = evs->onExternalVibrationStart(*externalVibration, &ret);
         if (status.isOk()) {
+            ALOGD("%s, start external vibration with intensity as %d", __func__, ret);
             return ret;
         }
     }
+    ALOGD("%s, start external vibration with intensity as MUTE due to %s",
+            __func__,
+            evs == nullptr ? "external vibration service not found"
+                           : "error when querying intensity");
     return static_cast<int>(os::HapticScale::MUTE);
 }
 
diff --git a/services/audiopolicy/fuzzer/audiopolicy_fuzzer.cpp b/services/audiopolicy/fuzzer/audiopolicy_fuzzer.cpp
index c1f2aa8..692ce08 100644
--- a/services/audiopolicy/fuzzer/audiopolicy_fuzzer.cpp
+++ b/services/audiopolicy/fuzzer/audiopolicy_fuzzer.cpp
@@ -633,7 +633,9 @@
 }
 
 bool AudioPolicyManagerFuzzerDPPlaybackReRouting::initialize() {
-    AudioPolicyManagerFuzzerDynamicPolicy::initialize();
+    if (!AudioPolicyManagerFuzzerDynamicPolicy::initialize()) {
+        return false;
+    }
     mTracker.reset(new RecordingActivityTracker());
 
     mAudioConfig = AUDIO_CONFIG_INITIALIZER;
@@ -743,7 +745,9 @@
 }
 
 bool AudioPolicyManagerFuzzerDPMixRecordInjection::initialize() {
-    AudioPolicyManagerFuzzerDynamicPolicy::initialize();
+    if (!AudioPolicyManagerFuzzerDynamicPolicy::initialize()) {
+        return false;
+    }
 
     mTracker.reset(new RecordingActivityTracker());
 
diff --git a/services/audiopolicy/managerdefault/AudioPolicyManager.cpp b/services/audiopolicy/managerdefault/AudioPolicyManager.cpp
index 04bc5f1..698692d 100644
--- a/services/audiopolicy/managerdefault/AudioPolicyManager.cpp
+++ b/services/audiopolicy/managerdefault/AudioPolicyManager.cpp
@@ -2184,7 +2184,9 @@
             status = BAD_VALUE;
             goto error;
         }
-        if (policyMix) {
+        if (device->type() == AUDIO_DEVICE_IN_ECHO_REFERENCE) {
+            *inputType = API_INPUT_MIX_CAPTURE;
+        } else if (policyMix) {
             ALOG_ASSERT(policyMix->mMixType == MIX_TYPE_RECORDERS, "Invalid Mix Type");
             // there is an external policy, but this input is attached to a mix of recorders,
             // meaning it receives audio injected into the framework, so the recorder doesn't
diff --git a/services/tuner/TunerDemux.cpp b/services/tuner/TunerDemux.cpp
index ba8d6a7..1122368 100644
--- a/services/tuner/TunerDemux.cpp
+++ b/services/tuner/TunerDemux.cpp
@@ -98,7 +98,7 @@
         return Status::fromServiceSpecificError(static_cast<int32_t>(status));
     }
 
-    *_aidl_return = ::ndk::SharedRefBase::make<TunerFilter>(filterSp, cbSp, type, subType);
+    *_aidl_return = ::ndk::SharedRefBase::make<TunerFilter>(filterSp, type, subType);
     return Status::ok();
 }
 
diff --git a/services/tuner/TunerFilter.cpp b/services/tuner/TunerFilter.cpp
index dc9d246..39a6723 100644
--- a/services/tuner/TunerFilter.cpp
+++ b/services/tuner/TunerFilter.cpp
@@ -39,10 +39,9 @@
 using namespace std;
 
 TunerFilter::TunerFilter(
-        sp<IFilter> filter, sp<IFilterCallback> callback, int mainType, int subType) {
+        sp<IFilter> filter, int mainType, int subType) {
     mFilter = filter;
     mFilter_1_1 = ::android::hardware::tv::tuner::V1_1::IFilter::castFrom(filter);
-    mFilterCallback = callback;
     mMainType = mainType;
     mSubType = subType;
 }
@@ -50,7 +49,6 @@
 TunerFilter::~TunerFilter() {
     mFilter = nullptr;
     mFilter_1_1 = nullptr;
-    mFilterCallback = nullptr;
 }
 
 Status TunerFilter::getQueueDesc(AidlMQDesc* _aidl_return) {
diff --git a/services/tuner/TunerFilter.h b/services/tuner/TunerFilter.h
index d12b7ac..ff4728c 100644
--- a/services/tuner/TunerFilter.h
+++ b/services/tuner/TunerFilter.h
@@ -92,7 +92,7 @@
 class TunerFilter : public BnTunerFilter {
 
 public:
-    TunerFilter(sp<IFilter> filter, sp<IFilterCallback> callback, int mainType, int subTyp);
+    TunerFilter(sp<IFilter> filter, int mainType, int subTyp);
     virtual ~TunerFilter();
     Status getId(int32_t* _aidl_return) override;
     Status getId64Bit(int64_t* _aidl_return) override;
@@ -181,7 +181,6 @@
 
     sp<IFilter> mFilter;
     sp<::android::hardware::tv::tuner::V1_1::IFilter> mFilter_1_1;
-    sp<IFilterCallback> mFilterCallback;
     int32_t mId;
     int64_t mId64Bit;
     int mMainType;
diff --git a/services/tuner/TunerService.cpp b/services/tuner/TunerService.cpp
index b80fd85..9624e27 100644
--- a/services/tuner/TunerService.cpp
+++ b/services/tuner/TunerService.cpp
@@ -49,7 +49,12 @@
 
 namespace android {
 
-TunerService::TunerService() {}
+TunerService::TunerService() {
+    ::ndk::SpAIBinder binder(AServiceManager_waitForService("tv_tuner_resource_mgr"));
+    mTunerResourceManager = ITunerResourceManager::fromBinder(binder);
+    updateTunerResources();
+}
+
 TunerService::~TunerService() {}
 
 binder_status_t TunerService::instantiate() {
@@ -282,19 +287,15 @@
     return Status::ok();
 }
 
-Status TunerService::updateTunerResources() {
-    if (!hasITuner()) {
-        return Status::fromServiceSpecificError(static_cast<int32_t>(Result::UNAVAILABLE));
+void TunerService::updateTunerResources() {
+    if (!hasITuner() || mTunerResourceManager == NULL) {
+        ALOGE("Failed to updateTunerResources");
+        return;
     }
 
-    // Connect with Tuner Resource Manager.
-    ::ndk::SpAIBinder binder(AServiceManager_getService("tv_tuner_resource_mgr"));
-    mTunerResourceManager = ITunerResourceManager::fromBinder(binder);
-
     updateFrontendResources();
     updateLnbResources();
     // TODO: update Demux, Descrambler.
-    return Status::ok();
 }
 
 Status TunerService::getTunerHalVersion(int* _aidl_return) {
diff --git a/services/tuner/TunerService.h b/services/tuner/TunerService.h
index cc65b39..0570681 100644
--- a/services/tuner/TunerService.h
+++ b/services/tuner/TunerService.h
@@ -113,7 +113,6 @@
     Status getDemuxCaps(TunerDemuxCapabilities* _aidl_return) override;
     Status openDescrambler(int32_t descramblerHandle,
             std::shared_ptr<ITunerDescrambler>* _aidl_return) override;
-    Status updateTunerResources() override;
     Status getTunerHalVersion(int* _aidl_return) override;
 
     // TODO: create a map between resource id and handles.
@@ -131,6 +130,7 @@
 private:
     bool hasITuner();
     bool hasITuner_1_1();
+    void updateTunerResources();
 
     void updateFrontendResources();
     void updateLnbResources();
diff --git a/services/tuner/aidl/android/media/tv/tuner/ITunerService.aidl b/services/tuner/aidl/android/media/tv/tuner/ITunerService.aidl
index f1651b9..755b152 100644
--- a/services/tuner/aidl/android/media/tv/tuner/ITunerService.aidl
+++ b/services/tuner/aidl/android/media/tv/tuner/ITunerService.aidl
@@ -97,12 +97,6 @@
     ITunerDescrambler openDescrambler(in int descramblerHandle);
 
     /**
-     * Update Tuner Resources in TunerResourceManager.
-     */
-    // TODO: b/178124017 update TRM in TunerService independently.
-    void updateTunerResources();
-
-    /**
      * Get an integer that carries the Tuner HIDL version. The high 16 bits are the
      * major version number while the low 16 bits are the minor version. Default
      * value is unknown version 0.