Add Tuner AIDL default implementation

Bug: 191825295
Test: make and run VtsHalTvTunerTargetTest
Change-Id: I781f67424ca1890f038160a4fda660507ab9e916
diff --git a/tv/tuner/aidl/default/Android.bp b/tv/tuner/aidl/default/Android.bp
new file mode 100644
index 0000000..ba4af44
--- /dev/null
+++ b/tv/tuner/aidl/default/Android.bp
@@ -0,0 +1,46 @@
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "hardware_interfaces_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["hardware_interfaces_license"],
+}
+
+cc_binary {
+    name: "android.hardware.tv.tuner-service.example",
+    relative_install_path: "hw",
+    init_rc: ["tuner-default.rc"],
+    vintf_fragments: ["tuner-default.xml"],
+    vendor: true,
+    compile_multilib: "first",
+    srcs: [
+        "Demux.cpp",
+        "Descrambler.cpp",
+        "Dvr.cpp",
+        "Filter.cpp",
+        "Frontend.cpp",
+        "Lnb.cpp",
+        "TimeFilter.cpp",
+        "Tuner.cpp",
+        "service.cpp",
+    ],
+    static_libs: [
+        "libaidlcommonsupport",
+    ],
+    shared_libs: [
+        "android.hardware.common.fmq-V1-ndk_platform",
+        "android.hardware.tv.tuner-V1-ndk_platform",
+        "libbase",
+        "libbinder_ndk",
+        "libcutils",
+        "libdmabufheap",
+        "libfmq",
+        "libion",
+        "liblog",
+        "libutils",
+    ],
+    header_libs: [
+        "media_plugin_headers",
+    ],
+}
diff --git a/tv/tuner/aidl/default/Demux.cpp b/tv/tuner/aidl/default/Demux.cpp
new file mode 100644
index 0000000..15022ee
--- /dev/null
+++ b/tv/tuner/aidl/default/Demux.cpp
@@ -0,0 +1,427 @@
+/*
+ * Copyright 2021 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 "android.hardware.tv.tuner-service.example-Demux"
+
+#include <aidl/android/hardware/tv/tuner/DemuxQueueNotifyBits.h>
+
+#include <utils/Log.h>
+#include "Demux.h"
+
+namespace aidl {
+namespace android {
+namespace hardware {
+namespace tv {
+namespace tuner {
+
+#define WAIT_TIMEOUT 3000000000
+
+Demux::Demux(int32_t demuxId, std::shared_ptr<Tuner> tuner) {
+    mDemuxId = demuxId;
+    mTunerService = tuner;
+}
+
+Demux::~Demux() {
+    mFrontendInputThreadRunning = false;
+    std::lock_guard<std::mutex> lock(mFrontendInputThreadLock);
+}
+
+::ndk::ScopedAStatus Demux::setFrontendDataSource(int32_t in_frontendId) {
+    ALOGV("%s", __FUNCTION__);
+
+    if (mTunerService == nullptr) {
+        return ::ndk::ScopedAStatus::fromExceptionCode(STATUS_NO_INIT);
+    }
+
+    mFrontend = mTunerService->getFrontendById(in_frontendId);
+
+    if (mFrontend == nullptr) {
+        return ::ndk::ScopedAStatus::fromExceptionCode(STATUS_INVALID_OPERATION);
+    }
+
+    mTunerService->setFrontendAsDemuxSource(in_frontendId, mDemuxId);
+
+    return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Demux::openFilter(const DemuxFilterType& in_type, int32_t in_bufferSize,
+                                       const std::shared_ptr<IFilterCallback>& in_cb,
+                                       std::shared_ptr<IFilter>* _aidl_return) {
+    ALOGV("%s", __FUNCTION__);
+
+    int64_t filterId;
+    filterId = ++mLastUsedFilterId;
+
+    if (in_cb == nullptr) {
+        ALOGW("[Demux] callback can't be null");
+        *_aidl_return = nullptr;
+        return ::ndk::ScopedAStatus::fromExceptionCode(STATUS_INVALID_OPERATION);
+    }
+
+    std::shared_ptr<Filter> filter =
+            ndk::SharedRefBase::make<Filter>(in_type, filterId, in_bufferSize, in_cb, ref<Demux>());
+    if (!filter->createFilterMQ()) {
+        *_aidl_return = nullptr;
+        return ::ndk::ScopedAStatus::fromExceptionCode(STATUS_UNKNOWN_ERROR);
+    }
+
+    mFilters[filterId] = filter;
+    if (filter->isPcrFilter()) {
+        mPcrFilterIds.insert(filterId);
+    }
+    bool result = true;
+    if (!filter->isRecordFilter()) {
+        // Only save non-record filters for now. Record filters are saved when the
+        // IDvr.attacheFilter is called.
+        mPlaybackFilterIds.insert(filterId);
+        if (mDvrPlayback != nullptr) {
+            result = mDvrPlayback->addPlaybackFilter(filterId, filter);
+        }
+    }
+
+    if (!result) {
+        *_aidl_return = nullptr;
+        return ::ndk::ScopedAStatus::fromExceptionCode(STATUS_INVALID_OPERATION);
+    }
+
+    *_aidl_return = filter;
+    return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Demux::openTimeFilter(std::shared_ptr<ITimeFilter>* _aidl_return) {
+    ALOGV("%s", __FUNCTION__);
+
+    mTimeFilter = ndk::SharedRefBase::make<TimeFilter>(ref<Demux>());
+
+    *_aidl_return = mTimeFilter;
+    return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Demux::getAvSyncHwId(const std::shared_ptr<IFilter>& in_filter,
+                                          int32_t* _aidl_return) {
+    ALOGV("%s", __FUNCTION__);
+
+    int64_t id;
+    ::ndk::ScopedAStatus status;
+
+    status = in_filter->getId64Bit(&id);
+    if (!status.isOk()) {
+        ALOGE("[Demux] Can't get filter Id.");
+        *_aidl_return = -1;
+        return ::ndk::ScopedAStatus::fromExceptionCode(STATUS_INVALID_OPERATION);
+    }
+
+    if (!mFilters[id]->isMediaFilter()) {
+        ALOGE("[Demux] Given filter is not a media filter.");
+        *_aidl_return = -1;
+        return ::ndk::ScopedAStatus::fromExceptionCode(STATUS_INVALID_OPERATION);
+    }
+
+    if (!mPcrFilterIds.empty()) {
+        // Return the lowest pcr filter id in the default implementation as the av sync id
+        *_aidl_return = *mPcrFilterIds.begin();
+        return ::ndk::ScopedAStatus::ok();
+    }
+
+    ALOGE("[Demux] No PCR filter opened.");
+    *_aidl_return = -1;
+    return ::ndk::ScopedAStatus::fromExceptionCode(STATUS_INVALID_OPERATION);
+}
+
+::ndk::ScopedAStatus Demux::getAvSyncTime(int32_t in_avSyncHwId, int64_t* _aidl_return) {
+    ALOGV("%s", __FUNCTION__);
+
+    if (mPcrFilterIds.empty()) {
+        *_aidl_return = -1;
+        return ::ndk::ScopedAStatus::fromExceptionCode(STATUS_INVALID_OPERATION);
+    }
+    if (in_avSyncHwId != *mPcrFilterIds.begin()) {
+        *_aidl_return = -1;
+        return ::ndk::ScopedAStatus::fromExceptionCode(STATUS_INVALID_OPERATION);
+    }
+
+    *_aidl_return = -1;
+    return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Demux::close() {
+    ALOGV("%s", __FUNCTION__);
+
+    set<int64_t>::iterator it;
+    for (it = mPlaybackFilterIds.begin(); it != mPlaybackFilterIds.end(); it++) {
+        mDvrPlayback->removePlaybackFilter(*it);
+    }
+    mPlaybackFilterIds.clear();
+    mRecordFilterIds.clear();
+    mFilters.clear();
+    mLastUsedFilterId = -1;
+    mTunerService->removeDemux(mDemuxId);
+    mFrontendInputThreadRunning = false;
+    std::lock_guard<std::mutex> lock(mFrontendInputThreadLock);
+
+    return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Demux::openDvr(DvrType in_type, int32_t in_bufferSize,
+                                    const std::shared_ptr<IDvrCallback>& in_cb,
+                                    std::shared_ptr<IDvr>* _aidl_return) {
+    ALOGV("%s", __FUNCTION__);
+
+    if (in_cb == nullptr) {
+        ALOGW("[Demux] DVR callback can't be null");
+        *_aidl_return = nullptr;
+        return ::ndk::ScopedAStatus::fromExceptionCode(STATUS_INVALID_OPERATION);
+    }
+
+    set<int64_t>::iterator it;
+    switch (in_type) {
+        case DvrType::PLAYBACK:
+            mDvrPlayback =
+                    ndk::SharedRefBase::make<Dvr>(in_type, in_bufferSize, in_cb, ref<Demux>());
+            if (!mDvrPlayback->createDvrMQ()) {
+                mDvrPlayback = nullptr;
+                *_aidl_return = mDvrPlayback;
+                return ::ndk::ScopedAStatus::fromExceptionCode(STATUS_UNKNOWN_ERROR);
+            }
+
+            for (it = mPlaybackFilterIds.begin(); it != mPlaybackFilterIds.end(); it++) {
+                if (!mDvrPlayback->addPlaybackFilter(*it, mFilters[*it])) {
+                    ALOGE("[Demux] Can't get filter info for DVR playback");
+                    mDvrPlayback = nullptr;
+                    *_aidl_return = mDvrPlayback;
+                    return ::ndk::ScopedAStatus::fromExceptionCode(STATUS_UNKNOWN_ERROR);
+                }
+            }
+
+            *_aidl_return = mDvrPlayback;
+            return ::ndk::ScopedAStatus::ok();
+        case DvrType::RECORD:
+            mDvrRecord = ndk::SharedRefBase::make<Dvr>(in_type, in_bufferSize, in_cb, ref<Demux>());
+            if (!mDvrRecord->createDvrMQ()) {
+                mDvrRecord = nullptr;
+                *_aidl_return = mDvrRecord;
+                return ::ndk::ScopedAStatus::fromExceptionCode(STATUS_UNKNOWN_ERROR);
+            }
+
+            *_aidl_return = mDvrRecord;
+            return ::ndk::ScopedAStatus::ok();
+        default:
+            *_aidl_return = nullptr;
+            return ::ndk::ScopedAStatus::fromExceptionCode(STATUS_INVALID_OPERATION);
+    }
+}
+
+::ndk::ScopedAStatus Demux::connectCiCam(int32_t in_ciCamId) {
+    ALOGV("%s", __FUNCTION__);
+
+    mCiCamId = in_ciCamId;
+
+    return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Demux::disconnectCiCam() {
+    ALOGV("%s", __FUNCTION__);
+
+    return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Demux::removeFilter(int64_t filterId) {
+    ALOGV("%s", __FUNCTION__);
+
+    if (mDvrPlayback != nullptr) {
+        mDvrPlayback->removePlaybackFilter(filterId);
+    }
+    mPlaybackFilterIds.erase(filterId);
+    mRecordFilterIds.erase(filterId);
+    mFilters.erase(filterId);
+
+    return ::ndk::ScopedAStatus::ok();
+}
+
+void Demux::startBroadcastTsFilter(vector<int8_t> data) {
+    set<int64_t>::iterator it;
+    uint16_t pid = ((data[1] & 0x1f) << 8) | ((data[2] & 0xff));
+    if (DEBUG_DEMUX) {
+        ALOGW("[Demux] start ts filter pid: %d", pid);
+    }
+    for (it = mPlaybackFilterIds.begin(); it != mPlaybackFilterIds.end(); it++) {
+        if (pid == mFilters[*it]->getTpid()) {
+            mFilters[*it]->updateFilterOutput(data);
+        }
+    }
+}
+
+void Demux::sendFrontendInputToRecord(vector<int8_t> data) {
+    set<int64_t>::iterator it;
+    if (DEBUG_DEMUX) {
+        ALOGW("[Demux] update record filter output");
+    }
+    for (it = mRecordFilterIds.begin(); it != mRecordFilterIds.end(); it++) {
+        mFilters[*it]->updateRecordOutput(data);
+    }
+}
+
+void Demux::sendFrontendInputToRecord(vector<int8_t> data, uint16_t pid, uint64_t pts) {
+    sendFrontendInputToRecord(data);
+    set<int64_t>::iterator it;
+    for (it = mRecordFilterIds.begin(); it != mRecordFilterIds.end(); it++) {
+        if (pid == mFilters[*it]->getTpid()) {
+            mFilters[*it]->updatePts(pts);
+        }
+    }
+}
+
+bool Demux::startBroadcastFilterDispatcher() {
+    set<int64_t>::iterator it;
+
+    // Handle the output data per filter type
+    for (it = mPlaybackFilterIds.begin(); it != mPlaybackFilterIds.end(); it++) {
+        if (!mFilters[*it]->startFilterHandler().isOk()) {
+            return false;
+        }
+    }
+
+    return true;
+}
+
+bool Demux::startRecordFilterDispatcher() {
+    set<int64_t>::iterator it;
+
+    for (it = mRecordFilterIds.begin(); it != mRecordFilterIds.end(); it++) {
+        if (!mFilters[*it]->startRecordFilterHandler().isOk()) {
+            return false;
+        }
+    }
+
+    return true;
+}
+
+::ndk::ScopedAStatus Demux::startFilterHandler(int64_t filterId) {
+    return mFilters[filterId]->startFilterHandler();
+}
+
+void Demux::updateFilterOutput(int64_t filterId, vector<int8_t> data) {
+    mFilters[filterId]->updateFilterOutput(data);
+}
+
+void Demux::updateMediaFilterOutput(int64_t filterId, vector<int8_t> data, uint64_t pts) {
+    updateFilterOutput(filterId, data);
+    mFilters[filterId]->updatePts(pts);
+}
+
+uint16_t Demux::getFilterTpid(int64_t filterId) {
+    return mFilters[filterId]->getTpid();
+}
+
+void Demux::startFrontendInputLoop() {
+    mFrontendInputThreadRunning = true;
+    pthread_create(&mFrontendInputThread, NULL, __threadLoopFrontend, this);
+    pthread_setname_np(mFrontendInputThread, "frontend_input_thread");
+}
+
+void* Demux::__threadLoopFrontend(void* user) {
+    Demux* const self = static_cast<Demux*>(user);
+    self->frontendInputThreadLoop();
+    return 0;
+}
+
+void Demux::frontendInputThreadLoop() {
+    if (!mFrontendInputThreadRunning) {
+        return;
+    }
+
+    std::lock_guard<std::mutex> lock(mFrontendInputThreadLock);
+    if (!mDvrPlayback) {
+        ALOGW("[Demux] No software Frontend input configured. Ending Frontend thread loop.");
+        mFrontendInputThreadRunning = false;
+        return;
+    }
+
+    while (mFrontendInputThreadRunning) {
+        uint32_t efState = 0;
+        ::android::status_t status = mDvrPlayback->getDvrEventFlag()->wait(
+                static_cast<uint32_t>(DemuxQueueNotifyBits::DATA_READY), &efState, WAIT_TIMEOUT,
+                true /* retry on spurious wake */);
+        if (status != ::android::OK) {
+            ALOGD("[Demux] wait for data ready on the playback FMQ");
+            continue;
+        }
+        if (mDvrPlayback->getSettings().get<DvrSettings::Tag::playback>().dataFormat ==
+            DataFormat::ES) {
+            if (!mDvrPlayback->processEsDataOnPlayback(true /*isVirtualFrontend*/, mIsRecording)) {
+                ALOGE("[Demux] playback es data failed to be filtered. Ending thread");
+                break;
+            }
+            continue;
+        }
+        // Our current implementation filter the data and write it into the filter FMQ immediately
+        // after the DATA_READY from the VTS/framework
+        // This is for the non-ES data source, real playback use case handling.
+        if (!mDvrPlayback->readPlaybackFMQ(true /*isVirtualFrontend*/, mIsRecording) ||
+            !mDvrPlayback->startFilterDispatcher(true /*isVirtualFrontend*/, mIsRecording)) {
+            ALOGE("[Demux] playback data failed to be filtered. Ending thread");
+            break;
+        }
+    }
+
+    mFrontendInputThreadRunning = false;
+    ALOGW("[Demux] Frontend Input thread end.");
+}
+
+void Demux::stopFrontendInput() {
+    ALOGD("[Demux] stop frontend on demux");
+    mKeepFetchingDataFromFrontend = false;
+    mFrontendInputThreadRunning = false;
+    std::lock_guard<std::mutex> lock(mFrontendInputThreadLock);
+}
+
+void Demux::setIsRecording(bool isRecording) {
+    mIsRecording = isRecording;
+}
+
+bool Demux::isRecording() {
+    return mIsRecording;
+}
+
+bool Demux::attachRecordFilter(int64_t filterId) {
+    if (mFilters[filterId] == nullptr || mDvrRecord == nullptr ||
+        !mFilters[filterId]->isRecordFilter()) {
+        return false;
+    }
+
+    mRecordFilterIds.insert(filterId);
+    mFilters[filterId]->attachFilterToRecord(mDvrRecord);
+
+    return true;
+}
+
+bool Demux::detachRecordFilter(int64_t filterId) {
+    if (mFilters[filterId] == nullptr || mDvrRecord == nullptr) {
+        return false;
+    }
+
+    mRecordFilterIds.erase(filterId);
+    mFilters[filterId]->detachFilterFromRecord();
+
+    return true;
+}
+
+}  // namespace tuner
+}  // namespace tv
+}  // namespace hardware
+}  // namespace android
+}  // namespace aidl
diff --git a/tv/tuner/aidl/default/Demux.h b/tv/tuner/aidl/default/Demux.h
new file mode 100644
index 0000000..9f96d0f
--- /dev/null
+++ b/tv/tuner/aidl/default/Demux.h
@@ -0,0 +1,189 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <aidl/android/hardware/tv/tuner/BnDemux.h>
+
+#include <fmq/AidlMessageQueue.h>
+#include <math.h>
+#include <set>
+#include "Dvr.h"
+#include "Filter.h"
+#include "Frontend.h"
+#include "TimeFilter.h"
+#include "Tuner.h"
+
+using namespace std;
+
+namespace aidl {
+namespace android {
+namespace hardware {
+namespace tv {
+namespace tuner {
+
+using ::aidl::android::hardware::common::fmq::MQDescriptor;
+using ::aidl::android::hardware::common::fmq::SynchronizedReadWrite;
+using ::android::AidlMessageQueue;
+using ::android::hardware::EventFlag;
+
+using FilterMQ = AidlMessageQueue<int8_t, SynchronizedReadWrite>;
+
+class Dvr;
+class Filter;
+class Frontend;
+class TimeFilter;
+class Tuner;
+
+class Demux : public BnDemux {
+  public:
+    Demux(int32_t demuxId, std::shared_ptr<Tuner> tuner);
+    ~Demux();
+
+    ::ndk::ScopedAStatus setFrontendDataSource(int32_t in_frontendId) override;
+    ::ndk::ScopedAStatus openFilter(const DemuxFilterType& in_type, int32_t in_bufferSize,
+                                    const std::shared_ptr<IFilterCallback>& in_cb,
+                                    std::shared_ptr<IFilter>* _aidl_return) override;
+    ::ndk::ScopedAStatus openTimeFilter(std::shared_ptr<ITimeFilter>* _aidl_return) override;
+    ::ndk::ScopedAStatus getAvSyncHwId(const std::shared_ptr<IFilter>& in_filter,
+                                       int32_t* _aidl_return) override;
+    ::ndk::ScopedAStatus getAvSyncTime(int32_t in_avSyncHwId, int64_t* _aidl_return) override;
+    ::ndk::ScopedAStatus close() override;
+    ::ndk::ScopedAStatus openDvr(DvrType in_type, int32_t in_bufferSize,
+                                 const std::shared_ptr<IDvrCallback>& in_cb,
+                                 std::shared_ptr<IDvr>* _aidl_return) override;
+    ::ndk::ScopedAStatus connectCiCam(int32_t in_ciCamId) override;
+    ::ndk::ScopedAStatus disconnectCiCam() override;
+
+    // Functions interacts with Tuner Service
+    void stopFrontendInput();
+    ::ndk::ScopedAStatus removeFilter(int64_t filterId);
+    bool attachRecordFilter(int64_t filterId);
+    bool detachRecordFilter(int64_t filterId);
+    ::ndk::ScopedAStatus startFilterHandler(int64_t filterId);
+    void updateFilterOutput(int64_t filterId, vector<int8_t> data);
+    void updateMediaFilterOutput(int64_t filterId, vector<int8_t> data, uint64_t pts);
+    uint16_t getFilterTpid(int64_t filterId);
+    void setIsRecording(bool isRecording);
+    bool isRecording();
+    void startFrontendInputLoop();
+
+    /**
+     * A dispatcher to read and dispatch input data to all the started filters.
+     * Each filter handler handles the data filtering/output writing/filterEvent updating.
+     * Note that recording filters are not included.
+     */
+    bool startBroadcastFilterDispatcher();
+    void startBroadcastTsFilter(vector<int8_t> data);
+
+    void sendFrontendInputToRecord(vector<int8_t> data);
+    void sendFrontendInputToRecord(vector<int8_t> data, uint16_t pid, uint64_t pts);
+    bool startRecordFilterDispatcher();
+
+  private:
+    // Tuner service
+    std::shared_ptr<Tuner> mTunerService;
+
+    // Frontend source
+    std::shared_ptr<Frontend> mFrontend;
+
+    // A struct that passes the arguments to a newly created filter thread
+    struct ThreadArgs {
+        Demux* user;
+        int64_t filterId;
+    };
+
+    static void* __threadLoopFrontend(void* user);
+    void frontendInputThreadLoop();
+
+    /**
+     * To create a FilterMQ with the next available Filter ID.
+     * Creating Event Flag at the same time.
+     * Add the successfully created/saved FilterMQ into the local list.
+     *
+     * Return false is any of the above processes fails.
+     */
+    void deleteEventFlag();
+    bool readDataFromMQ();
+
+    int32_t mDemuxId = -1;
+    int32_t mCiCamId;
+    set<int64_t> mPcrFilterIds;
+    /**
+     * Record the last used filter id. Initial value is -1.
+     * Filter Id starts with 0.
+     */
+    int64_t mLastUsedFilterId = -1;
+    /**
+     * Record all the used playback filter Ids.
+     * Any removed filter id should be removed from this set.
+     */
+    set<int64_t> mPlaybackFilterIds;
+    /**
+     * Record all the attached record filter Ids.
+     * Any removed filter id should be removed from this set.
+     */
+    set<int64_t> mRecordFilterIds;
+    /**
+     * A list of created Filter sp.
+     * The array number is the filter ID.
+     */
+    std::map<int64_t, std::shared_ptr<Filter>> mFilters;
+
+    /**
+     * Local reference to the opened Timer Filter instance.
+     */
+    std::shared_ptr<TimeFilter> mTimeFilter;
+
+    /**
+     * Local reference to the opened DVR object.
+     */
+    std::shared_ptr<Dvr> mDvrPlayback;
+    std::shared_ptr<Dvr> mDvrRecord;
+
+    // Thread handlers
+    pthread_t mFrontendInputThread;
+    /**
+     * If a specific filter's writing loop is still running
+     */
+    bool mFrontendInputThreadRunning;
+    bool mKeepFetchingDataFromFrontend;
+    /**
+     * If the dvr recording is running.
+     */
+    bool mIsRecording = false;
+    /**
+     * Lock to protect writes to the FMQs
+     */
+    std::mutex mWriteLock;
+    /**
+     * Lock to protect writes to the input status
+     */
+    std::mutex mFrontendInputThreadLock;
+
+    // temp handle single PES filter
+    // TODO handle mulptiple Pes filters
+    int mPesSizeLeft = 0;
+    vector<uint8_t> mPesOutput;
+
+    const bool DEBUG_DEMUX = false;
+};
+
+}  // namespace tuner
+}  // namespace tv
+}  // namespace hardware
+}  // namespace android
+}  // namespace aidl
diff --git a/tv/tuner/aidl/default/Descrambler.cpp b/tv/tuner/aidl/default/Descrambler.cpp
new file mode 100644
index 0000000..8af3a92
--- /dev/null
+++ b/tv/tuner/aidl/default/Descrambler.cpp
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2021 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 "android.hardware.tv.tuner-service.example-Descrambler"
+
+#include <aidl/android/hardware/tv/tuner/IFrontendCallback.h>
+#include <utils/Log.h>
+
+#include "Descrambler.h"
+
+namespace aidl {
+namespace android {
+namespace hardware {
+namespace tv {
+namespace tuner {
+
+Descrambler::Descrambler() {}
+
+Descrambler::~Descrambler() {}
+
+::ndk::ScopedAStatus Descrambler::setDemuxSource(int32_t in_demuxId) {
+    ALOGV("%s", __FUNCTION__);
+    if (mDemuxSet) {
+        ALOGW("[   WARN   ] Descrambler has already been set with a demux id %" PRIu32,
+              mSourceDemuxId);
+        return ::ndk::ScopedAStatus::fromExceptionCode(STATUS_INVALID_OPERATION);
+    }
+    mDemuxSet = true;
+    mSourceDemuxId = in_demuxId;
+
+    return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Descrambler::setKeyToken(const std::vector<uint8_t>& /* in_keyToken */) {
+    ALOGV("%s", __FUNCTION__);
+
+    return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Descrambler::addPid(
+        const DemuxPid& /* in_pid */,
+        const std::shared_ptr<IFilter>& /* in_optionalSourceFilter */) {
+    ALOGV("%s", __FUNCTION__);
+
+    return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Descrambler::removePid(
+        const DemuxPid& /* in_pid */,
+        const std::shared_ptr<IFilter>& /* in_optionalSourceFilter */) {
+    ALOGV("%s", __FUNCTION__);
+
+    return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Descrambler::close() {
+    ALOGV("%s", __FUNCTION__);
+    mDemuxSet = false;
+
+    return ::ndk::ScopedAStatus::ok();
+}
+
+}  // namespace tuner
+}  // namespace tv
+}  // namespace hardware
+}  // namespace android
+}  // namespace aidl
diff --git a/tv/tuner/aidl/default/Descrambler.h b/tv/tuner/aidl/default/Descrambler.h
new file mode 100644
index 0000000..ddf2c1d
--- /dev/null
+++ b/tv/tuner/aidl/default/Descrambler.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <aidl/android/hardware/tv/tuner/BnDescrambler.h>
+#include <aidl/android/hardware/tv/tuner/ITuner.h>
+#include <inttypes.h>
+
+using namespace std;
+
+namespace aidl {
+namespace android {
+namespace hardware {
+namespace tv {
+namespace tuner {
+
+class Descrambler : public BnDescrambler {
+  public:
+    Descrambler();
+
+    ::ndk::ScopedAStatus setDemuxSource(int32_t in_demuxId) override;
+    ::ndk::ScopedAStatus setKeyToken(const std::vector<uint8_t>& in_keyToken) override;
+    ::ndk::ScopedAStatus addPid(const DemuxPid& in_pid,
+                                const std::shared_ptr<IFilter>& in_optionalSourceFilter) override;
+    ::ndk::ScopedAStatus removePid(
+            const DemuxPid& in_pid,
+            const std::shared_ptr<IFilter>& in_optionalSourceFilter) override;
+    ::ndk::ScopedAStatus close() override;
+
+  private:
+    virtual ~Descrambler();
+    int32_t mSourceDemuxId;
+    bool mDemuxSet = false;
+};
+
+}  // namespace tuner
+}  // namespace tv
+}  // namespace hardware
+}  // namespace android
+}  // namespace aidl
diff --git a/tv/tuner/aidl/default/Dvr.cpp b/tv/tuner/aidl/default/Dvr.cpp
new file mode 100644
index 0000000..a042dc3
--- /dev/null
+++ b/tv/tuner/aidl/default/Dvr.cpp
@@ -0,0 +1,489 @@
+/*
+ * Copyright 2021 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 "android.hardware.tv.tuner-service.example-Dvr"
+
+#include <aidl/android/hardware/tv/tuner/DemuxQueueNotifyBits.h>
+
+#include <utils/Log.h>
+#include "Dvr.h"
+
+namespace aidl {
+namespace android {
+namespace hardware {
+namespace tv {
+namespace tuner {
+
+#define WAIT_TIMEOUT 3000000000
+
+Dvr::Dvr(DvrType type, uint32_t bufferSize, const std::shared_ptr<IDvrCallback>& cb,
+         std::shared_ptr<Demux> demux) {
+    mType = type;
+    mBufferSize = bufferSize;
+    mCallback = cb;
+    mDemux = demux;
+}
+
+Dvr::~Dvr() {
+    mDvrThreadRunning = false;
+    lock_guard<mutex> lock(mDvrThreadLock);
+}
+
+::ndk::ScopedAStatus Dvr::getQueueDesc(MQDescriptor<int8_t, SynchronizedReadWrite>* out_queue) {
+    ALOGV("%s", __FUNCTION__);
+
+    *out_queue = mDvrMQ->dupeDesc();
+
+    return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Dvr::configure(const DvrSettings& in_settings) {
+    ALOGV("%s", __FUNCTION__);
+
+    mDvrSettings = in_settings;
+    mDvrConfigured = true;
+
+    return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Dvr::attachFilter(const std::shared_ptr<IFilter>& in_filter) {
+    ALOGV("%s", __FUNCTION__);
+
+    int64_t filterId;
+    ::ndk::ScopedAStatus status = in_filter->getId64Bit(&filterId);
+    if (!status.isOk()) {
+        return status;
+    }
+
+    if (!mDemux->attachRecordFilter(filterId)) {
+        return ::ndk::ScopedAStatus::fromExceptionCode(STATUS_INVALID_OPERATION);
+    }
+
+    return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Dvr::detachFilter(const std::shared_ptr<IFilter>& in_filter) {
+    ALOGV("%s", __FUNCTION__);
+
+    int64_t filterId;
+    ::ndk::ScopedAStatus status = in_filter->getId64Bit(&filterId);
+    if (!status.isOk()) {
+        return status;
+    }
+
+    if (!mDemux->detachRecordFilter(filterId)) {
+        return ::ndk::ScopedAStatus::fromExceptionCode(STATUS_INVALID_OPERATION);
+    }
+
+    return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Dvr::start() {
+    ALOGV("%s", __FUNCTION__);
+    if (mDvrThreadRunning) {
+        return ::ndk::ScopedAStatus::ok();
+    }
+
+    if (!mCallback) {
+        return ::ndk::ScopedAStatus::fromExceptionCode(STATUS_NO_INIT);
+    }
+
+    if (!mDvrConfigured) {
+        return ::ndk::ScopedAStatus::fromExceptionCode(STATUS_INVALID_OPERATION);
+    }
+
+    if (mType == DvrType::PLAYBACK) {
+        mDvrThreadRunning = true;
+        pthread_create(&mDvrThread, NULL, __threadLoopPlayback, this);
+        pthread_setname_np(mDvrThread, "playback_waiting_loop");
+    } else if (mType == DvrType::RECORD) {
+        mRecordStatus = RecordStatus::DATA_READY;
+        mDemux->setIsRecording(mType == DvrType::RECORD);
+    }
+
+    // TODO start another thread to send filter status callback to the framework
+
+    return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Dvr::stop() {
+    ALOGV("%s", __FUNCTION__);
+
+    mDvrThreadRunning = false;
+    lock_guard<mutex> lock(mDvrThreadLock);
+
+    mIsRecordStarted = false;
+    mDemux->setIsRecording(false);
+
+    return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Dvr::flush() {
+    ALOGV("%s", __FUNCTION__);
+
+    mRecordStatus = RecordStatus::DATA_READY;
+
+    return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Dvr::close() {
+    ALOGV("%s", __FUNCTION__);
+
+    mDvrThreadRunning = false;
+    lock_guard<mutex> lock(mDvrThreadLock);
+    return ::ndk::ScopedAStatus::ok();
+}
+
+bool Dvr::createDvrMQ() {
+    ALOGV("%s", __FUNCTION__);
+
+    // Create a synchronized FMQ that supports blocking read/write
+    unique_ptr<DvrMQ> tmpDvrMQ = unique_ptr<DvrMQ>(new (nothrow) DvrMQ(mBufferSize, true));
+    if (!tmpDvrMQ->isValid()) {
+        ALOGW("[Dvr] Failed to create FMQ of DVR");
+        return false;
+    }
+
+    mDvrMQ = move(tmpDvrMQ);
+
+    if (EventFlag::createEventFlag(mDvrMQ->getEventFlagWord(), &mDvrEventFlag) != ::android::OK) {
+        return false;
+    }
+
+    return true;
+}
+
+EventFlag* Dvr::getDvrEventFlag() {
+    return mDvrEventFlag;
+}
+
+void* Dvr::__threadLoopPlayback(void* user) {
+    Dvr* const self = static_cast<Dvr*>(user);
+    self->playbackThreadLoop();
+    return 0;
+}
+
+void Dvr::playbackThreadLoop() {
+    ALOGD("[Dvr] playback threadLoop start.");
+    lock_guard<mutex> lock(mDvrThreadLock);
+
+    while (mDvrThreadRunning) {
+        uint32_t efState = 0;
+        ::android::status_t status =
+                mDvrEventFlag->wait(static_cast<uint32_t>(DemuxQueueNotifyBits::DATA_READY),
+                                    &efState, WAIT_TIMEOUT, true /* retry on spurious wake */);
+        if (status != ::android::OK) {
+            ALOGD("[Dvr] wait for data ready on the playback FMQ");
+            continue;
+        }
+
+        // If the both dvr playback and dvr record are created, the playback will be treated as
+        // the source of the record. isVirtualFrontend set to true would direct the dvr playback
+        // input to the demux record filters or live broadcast filters.
+        bool isRecording = mDemux->isRecording();
+        bool isVirtualFrontend = isRecording;
+
+        if (mDvrSettings.get<DvrSettings::Tag::playback>().dataFormat == DataFormat::ES) {
+            if (!processEsDataOnPlayback(isVirtualFrontend, isRecording)) {
+                ALOGE("[Dvr] playback es data failed to be filtered. Ending thread");
+                break;
+            }
+            maySendPlaybackStatusCallback();
+            continue;
+        }
+
+        // Our current implementation filter the data and write it into the filter FMQ immediately
+        // after the DATA_READY from the VTS/framework
+        // This is for the non-ES data source, real playback use case handling.
+        if (!readPlaybackFMQ(isVirtualFrontend, isRecording) ||
+            !startFilterDispatcher(isVirtualFrontend, isRecording)) {
+            ALOGE("[Dvr] playback data failed to be filtered. Ending thread");
+            break;
+        }
+
+        maySendPlaybackStatusCallback();
+    }
+
+    mDvrThreadRunning = false;
+    ALOGD("[Dvr] playback thread ended.");
+}
+
+void Dvr::maySendPlaybackStatusCallback() {
+    lock_guard<mutex> lock(mPlaybackStatusLock);
+    int availableToRead = mDvrMQ->availableToRead();
+    int availableToWrite = mDvrMQ->availableToWrite();
+
+    PlaybackStatus newStatus =
+            checkPlaybackStatusChange(availableToWrite, availableToRead,
+                                      mDvrSettings.get<DvrSettings::Tag::playback>().highThreshold,
+                                      mDvrSettings.get<DvrSettings::Tag::playback>().lowThreshold);
+    if (mPlaybackStatus != newStatus) {
+        mCallback->onPlaybackStatus(newStatus);
+        mPlaybackStatus = newStatus;
+    }
+}
+
+PlaybackStatus Dvr::checkPlaybackStatusChange(uint32_t availableToWrite, uint32_t availableToRead,
+                                              uint32_t highThreshold, uint32_t lowThreshold) {
+    if (availableToWrite == 0) {
+        return PlaybackStatus::SPACE_FULL;
+    } else if (availableToRead > highThreshold) {
+        return PlaybackStatus::SPACE_ALMOST_FULL;
+    } else if (availableToRead < lowThreshold) {
+        return PlaybackStatus::SPACE_ALMOST_EMPTY;
+    } else if (availableToRead == 0) {
+        return PlaybackStatus::SPACE_EMPTY;
+    }
+    return mPlaybackStatus;
+}
+
+bool Dvr::readPlaybackFMQ(bool isVirtualFrontend, bool isRecording) {
+    // Read playback data from the input FMQ
+    int size = mDvrMQ->availableToRead();
+    int playbackPacketSize = mDvrSettings.get<DvrSettings::Tag::playback>().packetSize;
+    vector<int8_t> dataOutputBuffer;
+    dataOutputBuffer.resize(playbackPacketSize);
+    // Dispatch the packet to the PID matching filter output buffer
+    for (int i = 0; i < size / playbackPacketSize; i++) {
+        if (!mDvrMQ->read(dataOutputBuffer.data(), playbackPacketSize)) {
+            return false;
+        }
+        if (isVirtualFrontend) {
+            if (isRecording) {
+                mDemux->sendFrontendInputToRecord(dataOutputBuffer);
+            } else {
+                mDemux->startBroadcastTsFilter(dataOutputBuffer);
+            }
+        } else {
+            startTpidFilter(dataOutputBuffer);
+        }
+    }
+
+    return true;
+}
+
+bool Dvr::processEsDataOnPlayback(bool isVirtualFrontend, bool isRecording) {
+    // Read ES from the DVR FMQ
+    // Note that currently we only provides ES with metaData in a specific format to be parsed.
+    // The ES size should be smaller than the Playback FMQ size to avoid reading truncated data.
+    int size = mDvrMQ->availableToRead();
+    vector<int8_t> dataOutputBuffer;
+    dataOutputBuffer.resize(size);
+    if (!mDvrMQ->read(dataOutputBuffer.data(), size)) {
+        return false;
+    }
+
+    int metaDataSize = size;
+    int totalFrames = 0;
+    int videoEsDataSize = 0;
+    int audioEsDataSize = 0;
+    int audioPid = 0;
+    int videoPid = 0;
+
+    vector<MediaEsMetaData> esMeta;
+    int videoReadPointer = 0;
+    int audioReadPointer = 0;
+    int frameCount = 0;
+    // Get meta data from the es
+    for (int i = 0; i < metaDataSize; i++) {
+        switch (dataOutputBuffer[i]) {
+            case 'm':
+                metaDataSize = 0;
+                getMetaDataValue(i, dataOutputBuffer.data(), metaDataSize);
+                videoReadPointer = metaDataSize;
+                continue;
+            case 'l':
+                getMetaDataValue(i, dataOutputBuffer.data(), totalFrames);
+                esMeta.resize(totalFrames);
+                continue;
+            case 'V':
+                getMetaDataValue(i, dataOutputBuffer.data(), videoEsDataSize);
+                audioReadPointer = metaDataSize + videoEsDataSize;
+                continue;
+            case 'A':
+                getMetaDataValue(i, dataOutputBuffer.data(), audioEsDataSize);
+                continue;
+            case 'p':
+                if (dataOutputBuffer[++i] == 'a') {
+                    getMetaDataValue(i, dataOutputBuffer.data(), audioPid);
+                } else if (dataOutputBuffer[i] == 'v') {
+                    getMetaDataValue(i, dataOutputBuffer.data(), videoPid);
+                }
+                continue;
+            case 'v':
+            case 'a':
+                if (dataOutputBuffer[i + 1] != ',') {
+                    ALOGE("[Dvr] Invalid format meta data.");
+                    return false;
+                }
+                esMeta[frameCount] = {
+                        .isAudio = dataOutputBuffer[i] == 'a' ? true : false,
+                };
+                i += 5;  // Move to Len
+                getMetaDataValue(i, dataOutputBuffer.data(), esMeta[frameCount].len);
+                if (esMeta[frameCount].isAudio) {
+                    esMeta[frameCount].startIndex = audioReadPointer;
+                    audioReadPointer += esMeta[frameCount].len;
+                } else {
+                    esMeta[frameCount].startIndex = videoReadPointer;
+                    videoReadPointer += esMeta[frameCount].len;
+                }
+                i += 4;  // move to PTS
+                getMetaDataValue(i, dataOutputBuffer.data(), esMeta[frameCount].pts);
+                frameCount++;
+                continue;
+            default:
+                continue;
+        }
+    }
+
+    if (frameCount != totalFrames) {
+        ALOGE("[Dvr] Invalid meta data, frameCount=%d, totalFrames reported=%d", frameCount,
+              totalFrames);
+        return false;
+    }
+
+    if (metaDataSize + audioEsDataSize + videoEsDataSize != size) {
+        ALOGE("[Dvr] Invalid meta data, metaSize=%d, videoSize=%d, audioSize=%d, totolSize=%d",
+              metaDataSize, videoEsDataSize, audioEsDataSize, size);
+        return false;
+    }
+
+    // Read es raw data from the FMQ per meta data built previously
+    vector<int8_t> frameData;
+    map<int64_t, std::shared_ptr<IFilter>>::iterator it;
+    int pid = 0;
+    for (int i = 0; i < totalFrames; i++) {
+        frameData.resize(esMeta[i].len);
+        pid = esMeta[i].isAudio ? audioPid : videoPid;
+        memcpy(frameData.data(), dataOutputBuffer.data() + esMeta[i].startIndex, esMeta[i].len);
+        // Send to the media filters or record filters
+        if (!isRecording) {
+            for (it = mFilters.begin(); it != mFilters.end(); it++) {
+                if (pid == mDemux->getFilterTpid(it->first)) {
+                    mDemux->updateMediaFilterOutput(it->first, frameData,
+                                                    static_cast<uint64_t>(esMeta[i].pts));
+                }
+            }
+        } else {
+            mDemux->sendFrontendInputToRecord(frameData, pid, static_cast<uint64_t>(esMeta[i].pts));
+        }
+        startFilterDispatcher(isVirtualFrontend, isRecording);
+        frameData.clear();
+    }
+
+    return true;
+}
+
+void Dvr::getMetaDataValue(int& index, int8_t* dataOutputBuffer, int& value) {
+    index += 2;  // Move the pointer across the ":" to the value
+    while (dataOutputBuffer[index] != ',' && dataOutputBuffer[index] != '\n') {
+        value = ((dataOutputBuffer[index++] - 48) + value * 10);
+    }
+}
+
+void Dvr::startTpidFilter(vector<int8_t> data) {
+    map<int64_t, std::shared_ptr<IFilter>>::iterator it;
+    for (it = mFilters.begin(); it != mFilters.end(); it++) {
+        uint16_t pid = ((data[1] & 0x1f) << 8) | ((data[2] & 0xff));
+        if (DEBUG_DVR) {
+            ALOGW("[Dvr] start ts filter pid: %d", pid);
+        }
+        if (pid == mDemux->getFilterTpid(it->first)) {
+            mDemux->updateFilterOutput(it->first, data);
+        }
+    }
+}
+
+bool Dvr::startFilterDispatcher(bool isVirtualFrontend, bool isRecording) {
+    if (isVirtualFrontend) {
+        if (isRecording) {
+            return mDemux->startRecordFilterDispatcher();
+        } else {
+            return mDemux->startBroadcastFilterDispatcher();
+        }
+    }
+
+    map<int64_t, std::shared_ptr<IFilter>>::iterator it;
+    // Handle the output data per filter type
+    for (it = mFilters.begin(); it != mFilters.end(); it++) {
+        if (mDemux->startFilterHandler(it->first).isOk()) {
+            return false;
+        }
+    }
+
+    return true;
+}
+
+bool Dvr::writeRecordFMQ(const vector<int8_t>& data) {
+    lock_guard<mutex> lock(mWriteLock);
+    if (mRecordStatus == RecordStatus::OVERFLOW) {
+        ALOGW("[Dvr] stops writing and wait for the client side flushing.");
+        return true;
+    }
+    if (mDvrMQ->write(data.data(), data.size())) {
+        mDvrEventFlag->wake(static_cast<uint32_t>(DemuxQueueNotifyBits::DATA_READY));
+        maySendRecordStatusCallback();
+        return true;
+    }
+
+    maySendRecordStatusCallback();
+    return false;
+}
+
+void Dvr::maySendRecordStatusCallback() {
+    lock_guard<mutex> lock(mRecordStatusLock);
+    int availableToRead = mDvrMQ->availableToRead();
+    int availableToWrite = mDvrMQ->availableToWrite();
+
+    RecordStatus newStatus =
+            checkRecordStatusChange(availableToWrite, availableToRead,
+                                    mDvrSettings.get<DvrSettings::Tag::record>().highThreshold,
+                                    mDvrSettings.get<DvrSettings::Tag::record>().lowThreshold);
+    if (mRecordStatus != newStatus) {
+        mCallback->onRecordStatus(newStatus);
+        mRecordStatus = newStatus;
+    }
+}
+
+RecordStatus Dvr::checkRecordStatusChange(uint32_t availableToWrite, uint32_t availableToRead,
+                                          uint32_t highThreshold, uint32_t lowThreshold) {
+    if (availableToWrite == 0) {
+        return RecordStatus::OVERFLOW;
+    } else if (availableToRead > highThreshold) {
+        return RecordStatus::HIGH_WATER;
+    } else if (availableToRead < lowThreshold) {
+        return RecordStatus::LOW_WATER;
+    }
+    return mRecordStatus;
+}
+
+bool Dvr::addPlaybackFilter(int64_t filterId, std::shared_ptr<IFilter> filter) {
+    mFilters[filterId] = filter;
+    return true;
+}
+
+bool Dvr::removePlaybackFilter(int64_t filterId) {
+    mFilters.erase(filterId);
+    return true;
+}
+
+}  // namespace tuner
+}  // namespace tv
+}  // namespace hardware
+}  // namespace android
+}  // namespace aidl
diff --git a/tv/tuner/aidl/default/Dvr.h b/tv/tuner/aidl/default/Dvr.h
new file mode 100644
index 0000000..fb22a2e
--- /dev/null
+++ b/tv/tuner/aidl/default/Dvr.h
@@ -0,0 +1,156 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <aidl/android/hardware/tv/tuner/BnDvr.h>
+#include <aidl/android/hardware/tv/tuner/RecordStatus.h>
+
+#include <fmq/AidlMessageQueue.h>
+#include <math.h>
+#include <set>
+#include "Demux.h"
+#include "Frontend.h"
+#include "Tuner.h"
+
+using namespace std;
+
+namespace aidl {
+namespace android {
+namespace hardware {
+namespace tv {
+namespace tuner {
+
+using ::aidl::android::hardware::common::fmq::MQDescriptor;
+using ::aidl::android::hardware::common::fmq::SynchronizedReadWrite;
+using ::android::AidlMessageQueue;
+using ::android::hardware::EventFlag;
+
+using DvrMQ = AidlMessageQueue<int8_t, SynchronizedReadWrite>;
+
+struct MediaEsMetaData {
+    bool isAudio;
+    int startIndex;
+    int len;
+    int pts;
+};
+
+class Demux;
+class Filter;
+class Frontend;
+class Tuner;
+
+class Dvr : public BnDvr {
+  public:
+    Dvr(DvrType type, uint32_t bufferSize, const std::shared_ptr<IDvrCallback>& cb,
+        std::shared_ptr<Demux> demux);
+    ~Dvr();
+
+    ::ndk::ScopedAStatus getQueueDesc(
+            MQDescriptor<int8_t, SynchronizedReadWrite>* out_queue) override;
+    ::ndk::ScopedAStatus configure(const DvrSettings& in_settings) override;
+    ::ndk::ScopedAStatus attachFilter(const std::shared_ptr<IFilter>& in_filter) override;
+    ::ndk::ScopedAStatus detachFilter(const std::shared_ptr<IFilter>& in_filter) override;
+    ::ndk::ScopedAStatus start() override;
+    ::ndk::ScopedAStatus stop() override;
+    ::ndk::ScopedAStatus flush() override;
+    ::ndk::ScopedAStatus close() override;
+
+    /**
+     * To create a DvrMQ and its Event Flag.
+     *
+     * Return false is any of the above processes fails.
+     */
+    bool createDvrMQ();
+    bool writeRecordFMQ(const std::vector<int8_t>& data);
+    bool addPlaybackFilter(int64_t filterId, std::shared_ptr<IFilter> filter);
+    bool removePlaybackFilter(int64_t filterId);
+    bool readPlaybackFMQ(bool isVirtualFrontend, bool isRecording);
+    bool processEsDataOnPlayback(bool isVirtualFrontend, bool isRecording);
+    bool startFilterDispatcher(bool isVirtualFrontend, bool isRecording);
+    EventFlag* getDvrEventFlag();
+    DvrSettings getSettings() { return mDvrSettings; }
+
+  private:
+    // Demux service
+    std::shared_ptr<Demux> mDemux;
+
+    DvrType mType;
+    uint32_t mBufferSize;
+    std::shared_ptr<IDvrCallback> mCallback;
+    std::map<int64_t, std::shared_ptr<IFilter>> mFilters;
+
+    void deleteEventFlag();
+    bool readDataFromMQ();
+    void getMetaDataValue(int& index, int8_t* dataOutputBuffer, int& value);
+    void maySendPlaybackStatusCallback();
+    void maySendRecordStatusCallback();
+    PlaybackStatus checkPlaybackStatusChange(uint32_t availableToWrite, uint32_t availableToRead,
+                                             uint32_t highThreshold, uint32_t lowThreshold);
+    RecordStatus checkRecordStatusChange(uint32_t availableToWrite, uint32_t availableToRead,
+                                         uint32_t highThreshold, uint32_t lowThreshold);
+    /**
+     * A dispatcher to read and dispatch input data to all the started filters.
+     * Each filter handler handles the data filtering/output writing/filterEvent updating.
+     */
+    void startTpidFilter(vector<int8_t> data);
+    static void* __threadLoopPlayback(void* user);
+    static void* __threadLoopRecord(void* user);
+    void playbackThreadLoop();
+    void recordThreadLoop();
+
+    unique_ptr<DvrMQ> mDvrMQ;
+    EventFlag* mDvrEventFlag;
+    /**
+     * Demux callbacks used on filter events or IO buffer status
+     */
+    bool mDvrConfigured = false;
+    DvrSettings mDvrSettings;
+
+    // Thread handlers
+    pthread_t mDvrThread;
+
+    // FMQ status local records
+    PlaybackStatus mPlaybackStatus;
+    RecordStatus mRecordStatus;
+    /**
+     * If a specific filter's writing loop is still running
+     */
+    bool mDvrThreadRunning;
+    bool mKeepFetchingDataFromFrontend;
+    /**
+     * Lock to protect writes to the FMQs
+     */
+    std::mutex mWriteLock;
+    /**
+     * Lock to protect writes to the input status
+     */
+    std::mutex mPlaybackStatusLock;
+    std::mutex mRecordStatusLock;
+    std::mutex mDvrThreadLock;
+
+    const bool DEBUG_DVR = false;
+
+    // Booleans to check if recording is running.
+    // Recording is ready when both of the following are set to true.
+    bool mIsRecordStarted = false;
+};
+
+}  // namespace tuner
+}  // namespace tv
+}  // namespace hardware
+}  // namespace android
+}  // namespace aidl
diff --git a/tv/tuner/aidl/default/Filter.cpp b/tv/tuner/aidl/default/Filter.cpp
new file mode 100644
index 0000000..18c1f00
--- /dev/null
+++ b/tv/tuner/aidl/default/Filter.cpp
@@ -0,0 +1,1103 @@
+/*
+ * Copyright 2021 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 "android.hardware.tv.tuner-service.example-Filter"
+
+#include <BufferAllocator/BufferAllocator.h>
+#include <aidl/android/hardware/tv/tuner/DemuxFilterMonitorEventType.h>
+#include <aidl/android/hardware/tv/tuner/DemuxQueueNotifyBits.h>
+#include <aidlcommonsupport/NativeHandle.h>
+#include <utils/Log.h>
+
+#include "Filter.h"
+
+namespace aidl {
+namespace android {
+namespace hardware {
+namespace tv {
+namespace tuner {
+
+#define WAIT_TIMEOUT 3000000000
+
+Filter::Filter() {}
+
+Filter::Filter(DemuxFilterType type, int64_t filterId, uint32_t bufferSize,
+               const std::shared_ptr<IFilterCallback>& cb, std::shared_ptr<Demux> demux) {
+    mType = type;
+    mFilterId = filterId;
+    mBufferSize = bufferSize;
+    mDemux = demux;
+    mCallback = cb;
+
+    switch (mType.mainType) {
+        case DemuxFilterMainType::TS:
+            if (mType.subType.get<DemuxFilterTypeDemuxFilterSubType::Tag::tsFilterType>() ==
+                        DemuxTsFilterType::AUDIO ||
+                mType.subType.get<DemuxFilterTypeDemuxFilterSubType::Tag::tsFilterType>() ==
+                        DemuxTsFilterType::VIDEO) {
+                mIsMediaFilter = true;
+            }
+            if (mType.subType.get<DemuxFilterTypeDemuxFilterSubType::Tag::tsFilterType>() ==
+                DemuxTsFilterType::PCR) {
+                mIsPcrFilter = true;
+            }
+            if (mType.subType.get<DemuxFilterTypeDemuxFilterSubType::Tag::tsFilterType>() ==
+                DemuxTsFilterType::RECORD) {
+                mIsRecordFilter = true;
+            }
+            break;
+        case DemuxFilterMainType::MMTP:
+            if (mType.subType.get<DemuxFilterTypeDemuxFilterSubType::Tag::mmtpFilterType>() ==
+                        DemuxMmtpFilterType::AUDIO ||
+                mType.subType.get<DemuxFilterTypeDemuxFilterSubType::Tag::mmtpFilterType>() ==
+                        DemuxMmtpFilterType::VIDEO) {
+                mIsMediaFilter = true;
+            }
+            if (mType.subType.get<DemuxFilterTypeDemuxFilterSubType::Tag::mmtpFilterType>() ==
+                DemuxMmtpFilterType::RECORD) {
+                mIsRecordFilter = true;
+            }
+            break;
+        case DemuxFilterMainType::IP:
+            break;
+        case DemuxFilterMainType::TLV:
+            break;
+        case DemuxFilterMainType::ALP:
+            break;
+        default:
+            break;
+    }
+}
+
+Filter::~Filter() {
+    mFilterThreadRunning = false;
+    std::lock_guard<std::mutex> lock(mFilterThreadLock);
+}
+
+::ndk::ScopedAStatus Filter::getId64Bit(int64_t* _aidl_return) {
+    ALOGV("%s", __FUNCTION__);
+
+    *_aidl_return = mFilterId;
+    return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Filter::getId(int32_t* _aidl_return) {
+    ALOGV("%s", __FUNCTION__);
+
+    *_aidl_return = static_cast<int32_t>(mFilterId);
+    return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Filter::setDataSource(const std::shared_ptr<IFilter>& in_filter) {
+    ALOGV("%s", __FUNCTION__);
+
+    mDataSource = in_filter;
+    mIsDataSourceDemux = false;
+
+    return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Filter::getQueueDesc(MQDescriptor<int8_t, SynchronizedReadWrite>* out_queue) {
+    ALOGV("%s", __FUNCTION__);
+
+    mIsUsingFMQ = mIsRecordFilter ? false : true;
+
+    *out_queue = mFilterMQ->dupeDesc();
+    return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Filter::configure(const DemuxFilterSettings& in_settings) {
+    ALOGV("%s", __FUNCTION__);
+
+    mFilterSettings = in_settings;
+    switch (mType.mainType) {
+        case DemuxFilterMainType::TS:
+            mTpid = in_settings.get<DemuxFilterSettings::Tag::ts>().tpid;
+            break;
+        case DemuxFilterMainType::MMTP:
+            break;
+        case DemuxFilterMainType::IP:
+            break;
+        case DemuxFilterMainType::TLV:
+            break;
+        case DemuxFilterMainType::ALP:
+            break;
+        default:
+            break;
+    }
+
+    mConfigured = true;
+    return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Filter::start() {
+    ALOGV("%s", __FUNCTION__);
+    mFilterThreadRunning = true;
+    vector<DemuxFilterEvent> events;
+    // All the filter event callbacks in start are for testing purpose.
+    switch (mType.mainType) {
+        case DemuxFilterMainType::TS:
+            createMediaEvent(events);
+            mCallback->onFilterEvent(events);
+            createTsRecordEvent(events);
+            mCallback->onFilterEvent(events);
+            createTemiEvent(events);
+            mCallback->onFilterEvent(events);
+            break;
+        case DemuxFilterMainType::MMTP:
+            createDownloadEvent(events);
+            mCallback->onFilterEvent(events);
+            createMmtpRecordEvent(events);
+            mCallback->onFilterEvent(events);
+            break;
+        case DemuxFilterMainType::IP:
+            createSectionEvent(events);
+            mCallback->onFilterEvent(events);
+            createIpPayloadEvent(events);
+            mCallback->onFilterEvent(events);
+            break;
+        case DemuxFilterMainType::TLV:
+            createMonitorEvent(events);
+            mCallback->onFilterEvent(events);
+            break;
+        case DemuxFilterMainType::ALP:
+            createMonitorEvent(events);
+            mCallback->onFilterEvent(events);
+            break;
+        default:
+            break;
+    }
+    return startFilterLoop();
+}
+
+::ndk::ScopedAStatus Filter::stop() {
+    ALOGV("%s", __FUNCTION__);
+    mFilterThreadRunning = false;
+    std::lock_guard<std::mutex> lock(mFilterThreadLock);
+    return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Filter::flush() {
+    ALOGV("%s", __FUNCTION__);
+
+    // temp implementation to flush the FMQ
+    int size = mFilterMQ->availableToRead();
+    int8_t* buffer = new int8_t[size];
+    mFilterMQ->read(buffer, size);
+    delete[] buffer;
+    mFilterStatus = DemuxFilterStatus::DATA_READY;
+
+    return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Filter::releaseAvHandle(const NativeHandle& in_avMemory, int64_t in_avDataId) {
+    ALOGV("%s", __FUNCTION__);
+
+    if ((mSharedAvMemHandle != nullptr) && (in_avMemory.fds.size() > 0) &&
+        (sameFile(in_avMemory.fds[0].get(), mSharedAvMemHandle->data[0]))) {
+        freeSharedAvHandle();
+        return ::ndk::ScopedAStatus::ok();
+    }
+
+    if (mDataId2Avfd.find(in_avDataId) == mDataId2Avfd.end()) {
+        return ::ndk::ScopedAStatus::fromExceptionCode(STATUS_INVALID_OPERATION);
+    }
+
+    ::close(mDataId2Avfd[in_avDataId]);
+    return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Filter::close() {
+    ALOGV("%s", __FUNCTION__);
+
+    mFilterThreadRunning = false;
+    std::lock_guard<std::mutex> lock(mFilterThreadLock);
+    return mDemux->removeFilter(mFilterId);
+}
+
+::ndk::ScopedAStatus Filter::configureIpCid(int32_t in_ipCid) {
+    ALOGV("%s", __FUNCTION__);
+
+    if (mType.mainType != DemuxFilterMainType::IP) {
+        return ::ndk::ScopedAStatus::fromExceptionCode(STATUS_INVALID_OPERATION);
+    }
+
+    mCid = in_ipCid;
+    return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Filter::getAvSharedHandle(NativeHandle* out_avMemory, int64_t* _aidl_return) {
+    ALOGV("%s", __FUNCTION__);
+
+    if (!mIsMediaFilter) {
+        return ::ndk::ScopedAStatus::fromExceptionCode(STATUS_INVALID_OPERATION);
+    }
+
+    if (mSharedAvMemHandle != nullptr) {
+        *out_avMemory = ::android::dupToAidl(mSharedAvMemHandle);
+        *_aidl_return = BUFFER_SIZE_16M;
+        mUsingSharedAvMem = true;
+        return ::ndk::ScopedAStatus::ok();
+    }
+
+    int av_fd = createAvIonFd(BUFFER_SIZE_16M);
+    if (av_fd < 0) {
+        return ::ndk::ScopedAStatus::fromExceptionCode(STATUS_NO_MEMORY);
+    }
+
+    mSharedAvMemHandle = createNativeHandle(av_fd);
+    if (mSharedAvMemHandle == nullptr) {
+        ::close(av_fd);
+        *_aidl_return = 0;
+        return ::ndk::ScopedAStatus::fromExceptionCode(STATUS_UNKNOWN_ERROR);
+    }
+    ::close(av_fd);
+    mUsingSharedAvMem = true;
+
+    *out_avMemory = ::android::dupToAidl(mSharedAvMemHandle);
+    *_aidl_return = BUFFER_SIZE_16M;
+    return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Filter::configureAvStreamType(const AvStreamType& in_avStreamType) {
+    ALOGV("%s", __FUNCTION__);
+
+    if (!mIsMediaFilter) {
+        return ::ndk::ScopedAStatus::fromExceptionCode(STATUS_INVALID_OPERATION);
+    }
+
+    switch (in_avStreamType.getTag()) {
+        case AvStreamType::Tag::audio:
+            mAudioStreamType =
+                    static_cast<uint32_t>(in_avStreamType.get<AvStreamType::Tag::audio>());
+            break;
+        case AvStreamType::Tag::video:
+            mVideoStreamType =
+                    static_cast<uint32_t>(in_avStreamType.get<AvStreamType::Tag::video>());
+            break;
+        default:
+            break;
+    }
+
+    return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Filter::configureMonitorEvent(int in_monitorEventTypes) {
+    ALOGV("%s", __FUNCTION__);
+
+    int32_t newScramblingStatus =
+            in_monitorEventTypes &
+            static_cast<int32_t>(DemuxFilterMonitorEventType::SCRAMBLING_STATUS);
+    int32_t newIpCid =
+            in_monitorEventTypes & static_cast<int32_t>(DemuxFilterMonitorEventType::IP_CID_CHANGE);
+
+    // if scrambling status monitoring flipped, record the new state and send msg on enabling
+    if (newScramblingStatus ^ mScramblingStatusMonitored) {
+        mScramblingStatusMonitored = newScramblingStatus;
+        if (mScramblingStatusMonitored) {
+            if (mCallback != nullptr) {
+                // Assuming current status is always NOT_SCRAMBLED
+                vector<DemuxFilterEvent> events;
+                DemuxFilterMonitorEvent monitorEvent;
+                events.resize(1);
+                monitorEvent.set<DemuxFilterMonitorEvent::Tag::scramblingStatus>(
+                        ScramblingStatus::NOT_SCRAMBLED);
+                events[0].set<DemuxFilterEvent::monitorEvent>(monitorEvent);
+                mCallback->onFilterEvent(events);
+            } else {
+                return ::ndk::ScopedAStatus::fromExceptionCode(STATUS_INVALID_OPERATION);
+            }
+        }
+    }
+
+    // if ip cid monitoring flipped, record the new state and send msg on enabling
+    if (newIpCid ^ mIpCidMonitored) {
+        mIpCidMonitored = newIpCid;
+        if (mIpCidMonitored) {
+            if (mCallback != nullptr) {
+                // Return random cid
+                vector<DemuxFilterEvent> events;
+                DemuxFilterMonitorEvent monitorEvent;
+                events.resize(1);
+                monitorEvent.set<DemuxFilterMonitorEvent::Tag::cid>(1);
+                events[0].set<DemuxFilterEvent::monitorEvent>(monitorEvent);
+                mCallback->onFilterEvent(events);
+            } else {
+                return ::ndk::ScopedAStatus::fromExceptionCode(STATUS_INVALID_OPERATION);
+            }
+        }
+    }
+
+    return ::ndk::ScopedAStatus::ok();
+}
+
+bool Filter::createFilterMQ() {
+    ALOGV("%s", __FUNCTION__);
+
+    // Create a synchronized FMQ that supports blocking read/write
+    std::unique_ptr<FilterMQ> tmpFilterMQ =
+            std::unique_ptr<FilterMQ>(new (std::nothrow) FilterMQ(mBufferSize, true));
+    if (!tmpFilterMQ->isValid()) {
+        ALOGW("[Filter] Failed to create FMQ of filter with id: %" PRIu64, mFilterId);
+        return false;
+    }
+
+    mFilterMQ = std::move(tmpFilterMQ);
+
+    if (EventFlag::createEventFlag(mFilterMQ->getEventFlagWord(), &mFilterEventsFlag) !=
+        ::android::OK) {
+        return false;
+    }
+
+    return true;
+}
+
+::ndk::ScopedAStatus Filter::startFilterLoop() {
+    pthread_create(&mFilterThread, NULL, __threadLoopFilter, this);
+    pthread_setname_np(mFilterThread, "filter_waiting_loop");
+    return ::ndk::ScopedAStatus::ok();
+}
+
+void* Filter::__threadLoopFilter(void* user) {
+    Filter* const self = static_cast<Filter*>(user);
+    self->filterThreadLoop();
+    return 0;
+}
+
+void Filter::filterThreadLoop() {
+    if (!mFilterThreadRunning) {
+        return;
+    }
+    std::lock_guard<std::mutex> lock(mFilterThreadLock);
+    ALOGD("[Filter] filter %" PRIu64 " threadLoop start.", mFilterId);
+
+    // For the first time of filter output, implementation needs to send the filter
+    // Event Callback without waiting for the DATA_CONSUMED to init the process.
+    while (mFilterThreadRunning) {
+        if (mFilterEvents.size() == 0) {
+            if (DEBUG_FILTER) {
+                ALOGD("[Filter] wait for filter data output.");
+            }
+            usleep(1000 * 1000);
+            continue;
+        }
+
+        // After successfully write, send a callback and wait for the read to be done
+        if (mCallback != nullptr) {
+            if (mConfigured) {
+                vector<DemuxFilterEvent> startEvent;
+                startEvent.resize(1);
+                startEvent[0].set<DemuxFilterEvent::Tag::startId>(mStartId++);
+                mCallback->onFilterEvent(startEvent);
+                mConfigured = false;
+            }
+            mCallback->onFilterEvent(mFilterEvents);
+        } else {
+            ALOGD("[Filter] filter callback is not configured yet.");
+            mFilterThreadRunning = false;
+            return;
+        }
+
+        mFilterEvents.resize(0);
+        mFilterStatus = DemuxFilterStatus::DATA_READY;
+        if (mCallback != nullptr) {
+            mCallback->onFilterStatus(mFilterStatus);
+        }
+        break;
+    }
+
+    while (mFilterThreadRunning) {
+        uint32_t efState = 0;
+        // We do not wait for the last round of written data to be read to finish the thread
+        // because the VTS can verify the reading itself.
+        for (int i = 0; i < SECTION_WRITE_COUNT; i++) {
+            if (!mFilterThreadRunning) {
+                break;
+            }
+            while (mFilterThreadRunning && mIsUsingFMQ) {
+                ::android::status_t status = mFilterEventsFlag->wait(
+                        static_cast<uint32_t>(DemuxQueueNotifyBits::DATA_CONSUMED), &efState,
+                        WAIT_TIMEOUT, true /* retry on spurious wake */);
+                if (status != ::android::OK) {
+                    ALOGD("[Filter] wait for data consumed");
+                    continue;
+                }
+                break;
+            }
+
+            maySendFilterStatusCallback();
+
+            while (mFilterThreadRunning) {
+                std::lock_guard<std::mutex> lock(mFilterEventsLock);
+                if (mFilterEvents.size() == 0) {
+                    continue;
+                }
+                // After successfully write, send a callback and wait for the read to be done
+                if (mCallback != nullptr) {
+                    mCallback->onFilterEvent(mFilterEvents);
+                }
+                mFilterEvents.resize(0);
+                break;
+            }
+            // We do not wait for the last read to be done
+            // VTS can verify the read result itself.
+            if (i == SECTION_WRITE_COUNT - 1) {
+                ALOGD("[Filter] filter %" PRIu64 " writing done. Ending thread", mFilterId);
+                break;
+            }
+        }
+        break;
+    }
+    ALOGD("[Filter] filter thread ended.");
+}
+
+void Filter::freeSharedAvHandle() {
+    if (!mIsMediaFilter) {
+        return;
+    }
+    native_handle_close(mSharedAvMemHandle);
+    native_handle_delete(mSharedAvMemHandle);
+    mSharedAvMemHandle = nullptr;
+}
+
+void Filter::maySendFilterStatusCallback() {
+    if (!mIsUsingFMQ) {
+        return;
+    }
+    std::lock_guard<std::mutex> lock(mFilterStatusLock);
+    int availableToRead = mFilterMQ->availableToRead();
+    int availableToWrite = mFilterMQ->availableToWrite();
+    int fmqSize = mFilterMQ->getQuantumCount();
+
+    DemuxFilterStatus newStatus = checkFilterStatusChange(
+            availableToWrite, availableToRead, ceil(fmqSize * 0.75), ceil(fmqSize * 0.25));
+    if (mFilterStatus != newStatus) {
+        if (mCallback != nullptr) {
+            mCallback->onFilterStatus(newStatus);
+        }
+        mFilterStatus = newStatus;
+    }
+}
+
+DemuxFilterStatus Filter::checkFilterStatusChange(uint32_t availableToWrite,
+                                                  uint32_t availableToRead, uint32_t highThreshold,
+                                                  uint32_t lowThreshold) {
+    if (availableToWrite == 0) {
+        return DemuxFilterStatus::OVERFLOW;
+    } else if (availableToRead > highThreshold) {
+        return DemuxFilterStatus::HIGH_WATER;
+    } else if (availableToRead < lowThreshold) {
+        return DemuxFilterStatus::LOW_WATER;
+    }
+    return mFilterStatus;
+}
+
+uint16_t Filter::getTpid() {
+    return mTpid;
+}
+
+void Filter::updateFilterOutput(vector<int8_t> data) {
+    std::lock_guard<std::mutex> lock(mFilterOutputLock);
+    mFilterOutput.insert(mFilterOutput.end(), data.begin(), data.end());
+}
+
+void Filter::updatePts(uint64_t pts) {
+    std::lock_guard<std::mutex> lock(mFilterOutputLock);
+    mPts = pts;
+}
+
+void Filter::updateRecordOutput(vector<int8_t> data) {
+    std::lock_guard<std::mutex> lock(mRecordFilterOutputLock);
+    mRecordFilterOutput.insert(mRecordFilterOutput.end(), data.begin(), data.end());
+}
+
+::ndk::ScopedAStatus Filter::startFilterHandler() {
+    std::lock_guard<std::mutex> lock(mFilterOutputLock);
+    switch (mType.mainType) {
+        case DemuxFilterMainType::TS:
+            switch (mType.subType.get<DemuxFilterTypeDemuxFilterSubType::Tag::tsFilterType>()) {
+                case DemuxTsFilterType::UNDEFINED:
+                    break;
+                case DemuxTsFilterType::SECTION:
+                    startSectionFilterHandler();
+                    break;
+                case DemuxTsFilterType::PES:
+                    startPesFilterHandler();
+                    break;
+                case DemuxTsFilterType::TS:
+                    startTsFilterHandler();
+                    break;
+                case DemuxTsFilterType::AUDIO:
+                case DemuxTsFilterType::VIDEO:
+                    startMediaFilterHandler();
+                    break;
+                case DemuxTsFilterType::PCR:
+                    startPcrFilterHandler();
+                    break;
+                case DemuxTsFilterType::TEMI:
+                    startTemiFilterHandler();
+                    break;
+                default:
+                    break;
+            }
+            break;
+        case DemuxFilterMainType::MMTP:
+            /*mmtpSettings*/
+            break;
+        case DemuxFilterMainType::IP:
+            /*ipSettings*/
+            break;
+        case DemuxFilterMainType::TLV:
+            /*tlvSettings*/
+            break;
+        case DemuxFilterMainType::ALP:
+            /*alpSettings*/
+            break;
+        default:
+            break;
+    }
+    return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Filter::startSectionFilterHandler() {
+    if (mFilterOutput.empty()) {
+        return ::ndk::ScopedAStatus::ok();
+    }
+    if (!writeSectionsAndCreateEvent(mFilterOutput)) {
+        ALOGD("[Filter] filter %" PRIu64 " fails to write into FMQ. Ending thread", mFilterId);
+        return ::ndk::ScopedAStatus::fromExceptionCode(STATUS_UNKNOWN_ERROR);
+    }
+
+    mFilterOutput.clear();
+
+    return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Filter::startPesFilterHandler() {
+    std::lock_guard<std::mutex> lock(mFilterEventsLock);
+    if (mFilterOutput.empty()) {
+        return ::ndk::ScopedAStatus::ok();
+    }
+
+    for (int i = 0; i < mFilterOutput.size(); i += 188) {
+        if (mPesSizeLeft == 0) {
+            uint32_t prefix = (mFilterOutput[i + 4] << 16) | (mFilterOutput[i + 5] << 8) |
+                              mFilterOutput[i + 6];
+            if (DEBUG_FILTER) {
+                ALOGD("[Filter] prefix %d", prefix);
+            }
+            if (prefix == 0x000001) {
+                // TODO handle mulptiple Pes filters
+                mPesSizeLeft = (mFilterOutput[i + 8] << 8) | mFilterOutput[i + 9];
+                mPesSizeLeft += 6;
+                if (DEBUG_FILTER) {
+                    ALOGD("[Filter] pes data length %d", mPesSizeLeft);
+                }
+            } else {
+                continue;
+            }
+        }
+
+        int endPoint = min(184, mPesSizeLeft);
+        // append data and check size
+        vector<int8_t>::const_iterator first = mFilterOutput.begin() + i + 4;
+        vector<int8_t>::const_iterator last = mFilterOutput.begin() + i + 4 + endPoint;
+        mPesOutput.insert(mPesOutput.end(), first, last);
+        // size does not match then continue
+        mPesSizeLeft -= endPoint;
+        if (DEBUG_FILTER) {
+            ALOGD("[Filter] pes data left %d", mPesSizeLeft);
+        }
+        if (mPesSizeLeft > 0) {
+            continue;
+        }
+        // size match then create event
+        if (!writeDataToFilterMQ(mPesOutput)) {
+            ALOGD("[Filter] pes data write failed");
+            mFilterOutput.clear();
+            return ::ndk::ScopedAStatus::fromExceptionCode(STATUS_INVALID_OPERATION);
+        }
+        maySendFilterStatusCallback();
+        DemuxFilterPesEvent pesEvent;
+        pesEvent = {
+                // temp dump meta data
+                .streamId = static_cast<char16_t>(mPesOutput[3]),
+                .dataLength = static_cast<char16_t>(mPesOutput.size()),
+        };
+        if (DEBUG_FILTER) {
+            ALOGD("[Filter] assembled pes data length %d", pesEvent.dataLength);
+        }
+
+        int size = mFilterEvents.size();
+        mFilterEvents.resize(size + 1);
+        mFilterEvents[size].set<DemuxFilterEvent::Tag::pes>(pesEvent);
+        mPesOutput.clear();
+    }
+
+    mFilterOutput.clear();
+
+    return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Filter::startTsFilterHandler() {
+    // TODO handle starting TS filter
+    return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Filter::startMediaFilterHandler() {
+    std::lock_guard<std::mutex> lock(mFilterEventsLock);
+    if (mFilterOutput.empty()) {
+        return ::ndk::ScopedAStatus::ok();
+    }
+
+    ::ndk::ScopedAStatus result;
+    if (mPts) {
+        result = createMediaFilterEventWithIon(mFilterOutput);
+        if (result.isOk()) {
+            mFilterOutput.clear();
+        }
+        return result;
+    }
+
+    for (int i = 0; i < mFilterOutput.size(); i += 188) {
+        if (mPesSizeLeft == 0) {
+            uint32_t prefix = (mFilterOutput[i + 4] << 16) | (mFilterOutput[i + 5] << 8) |
+                              mFilterOutput[i + 6];
+            if (DEBUG_FILTER) {
+                ALOGD("[Filter] prefix %d", prefix);
+            }
+            if (prefix == 0x000001) {
+                // TODO handle mulptiple Pes filters
+                mPesSizeLeft = (mFilterOutput[i + 8] << 8) | mFilterOutput[i + 9];
+                mPesSizeLeft += 6;
+                if (DEBUG_FILTER) {
+                    ALOGD("[Filter] pes data length %d", mPesSizeLeft);
+                }
+            } else {
+                continue;
+            }
+        }
+
+        int endPoint = min(184, mPesSizeLeft);
+        // append data and check size
+        vector<int8_t>::const_iterator first = mFilterOutput.begin() + i + 4;
+        vector<int8_t>::const_iterator last = mFilterOutput.begin() + i + 4 + endPoint;
+        mPesOutput.insert(mPesOutput.end(), first, last);
+        // size does not match then continue
+        mPesSizeLeft -= endPoint;
+        if (DEBUG_FILTER) {
+            ALOGD("[Filter] pes data left %d", mPesSizeLeft);
+        }
+        if (mPesSizeLeft > 0 || mAvBufferCopyCount++ < 10) {
+            continue;
+        }
+
+        result = createMediaFilterEventWithIon(mPesOutput);
+        if (result.isOk()) {
+            return result;
+        }
+    }
+
+    mFilterOutput.clear();
+
+    return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Filter::createMediaFilterEventWithIon(vector<int8_t>& output) {
+    if (mUsingSharedAvMem) {
+        if (mSharedAvMemHandle == nullptr) {
+            return ::ndk::ScopedAStatus::fromExceptionCode(STATUS_UNKNOWN_ERROR);
+        }
+        return createShareMemMediaEvents(output);
+    }
+
+    return createIndependentMediaEvents(output);
+}
+
+::ndk::ScopedAStatus Filter::startRecordFilterHandler() {
+    std::lock_guard<std::mutex> lock(mRecordFilterOutputLock);
+    if (mRecordFilterOutput.empty()) {
+        return ::ndk::ScopedAStatus::ok();
+    }
+
+    if (mDvr == nullptr || !mDvr->writeRecordFMQ(mRecordFilterOutput)) {
+        ALOGD("[Filter] dvr fails to write into record FMQ.");
+        return ::ndk::ScopedAStatus::fromExceptionCode(STATUS_UNKNOWN_ERROR);
+    }
+
+    DemuxFilterTsRecordEvent recordEvent;
+    recordEvent = {
+            .byteNumber = static_cast<int64_t>(mRecordFilterOutput.size()),
+            .pts = (mPts == 0) ? static_cast<int64_t>(time(NULL)) * 900000 : mPts,
+            .firstMbInSlice = 0,  // random address
+    };
+
+    int size;
+    size = mFilterEvents.size();
+    mFilterEvents.resize(size + 1);
+    mFilterEvents[size].set<DemuxFilterEvent::Tag::tsRecord>(recordEvent);
+
+    mRecordFilterOutput.clear();
+    return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Filter::startPcrFilterHandler() {
+    // TODO handle starting PCR filter
+    return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Filter::startTemiFilterHandler() {
+    // TODO handle starting TEMI filter
+    return ::ndk::ScopedAStatus::ok();
+}
+
+bool Filter::writeSectionsAndCreateEvent(vector<int8_t>& data) {
+    // TODO check how many sections has been read
+    ALOGD("[Filter] section handler");
+    std::lock_guard<std::mutex> lock(mFilterEventsLock);
+    if (!writeDataToFilterMQ(data)) {
+        return false;
+    }
+    int size = mFilterEvents.size();
+    mFilterEvents.resize(size + 1);
+    DemuxFilterSectionEvent secEvent;
+    secEvent = {
+            // temp dump meta data
+            .tableId = 0,
+            .version = 1,
+            .sectionNum = 1,
+            .dataLength = static_cast<char16_t>(data.size()),
+    };
+    mFilterEvents[size].set<DemuxFilterEvent::Tag::section>(secEvent);
+    return true;
+}
+
+bool Filter::writeDataToFilterMQ(const std::vector<int8_t>& data) {
+    std::lock_guard<std::mutex> lock(mWriteLock);
+    if (mFilterMQ->write(data.data(), data.size())) {
+        return true;
+    }
+    return false;
+}
+
+void Filter::attachFilterToRecord(const std::shared_ptr<Dvr> dvr) {
+    mDvr = dvr;
+}
+
+void Filter::detachFilterFromRecord() {
+    mDvr = nullptr;
+}
+
+int Filter::createAvIonFd(int size) {
+    // Create an DMA-BUF fd and allocate an av fd mapped to a buffer to it.
+    auto buffer_allocator = std::make_unique<BufferAllocator>();
+    if (!buffer_allocator) {
+        ALOGE("[Filter] Unable to create BufferAllocator object");
+        return -1;
+    }
+    int av_fd = -1;
+    av_fd = buffer_allocator->Alloc("system-uncached", size);
+    if (av_fd < 0) {
+        ALOGE("[Filter] Failed to create av fd %d", errno);
+        return -1;
+    }
+    return av_fd;
+}
+
+uint8_t* Filter::getIonBuffer(int fd, int size) {
+    uint8_t* avBuf = static_cast<uint8_t*>(
+            mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0 /*offset*/));
+    if (avBuf == MAP_FAILED) {
+        ALOGE("[Filter] fail to allocate buffer %d", errno);
+        return NULL;
+    }
+    return avBuf;
+}
+
+native_handle_t* Filter::createNativeHandle(int fd) {
+    native_handle_t* nativeHandle;
+    if (fd < 0) {
+        nativeHandle = native_handle_create(/*numFd*/ 0, 0);
+    } else {
+        // Create a native handle to pass the av fd via the callback event.
+        nativeHandle = native_handle_create(/*numFd*/ 1, 0);
+    }
+    if (nativeHandle == NULL) {
+        ALOGE("[Filter] Failed to create native_handle %d", errno);
+        return NULL;
+    }
+    if (nativeHandle->numFds > 0) {
+        nativeHandle->data[0] = dup(fd);
+    }
+    return nativeHandle;
+}
+
+::ndk::ScopedAStatus Filter::createIndependentMediaEvents(vector<int8_t>& output) {
+    int av_fd = createAvIonFd(output.size());
+    if (av_fd == -1) {
+        return ::ndk::ScopedAStatus::fromExceptionCode(STATUS_UNKNOWN_ERROR);
+    }
+    // copy the filtered data to the buffer
+    uint8_t* avBuffer = getIonBuffer(av_fd, output.size());
+    if (avBuffer == NULL) {
+        return ::ndk::ScopedAStatus::fromExceptionCode(STATUS_UNKNOWN_ERROR);
+    }
+    memcpy(avBuffer, output.data(), output.size() * sizeof(uint8_t));
+
+    native_handle_t* nativeHandle = createNativeHandle(av_fd);
+    if (nativeHandle == NULL) {
+        return ::ndk::ScopedAStatus::fromExceptionCode(STATUS_UNKNOWN_ERROR);
+    }
+
+    // Create a dataId and add a <dataId, av_fd> pair into the dataId2Avfd map
+    uint64_t dataId = mLastUsedDataId++ /*createdUID*/;
+    mDataId2Avfd[dataId] = dup(av_fd);
+
+    // Create mediaEvent and send callback
+    int size = mFilterEvents.size();
+    mFilterEvents.resize(size + 1);
+
+    mFilterEvents[size] = DemuxFilterEvent::make<DemuxFilterEvent::Tag::media>();
+    mFilterEvents[size].get<DemuxFilterEvent::Tag::media>().avMemory =
+            ::android::dupToAidl(nativeHandle);
+    mFilterEvents[size].get<DemuxFilterEvent::Tag::media>().dataLength =
+            static_cast<int32_t>(output.size());
+    mFilterEvents[size].get<DemuxFilterEvent::Tag::media>().avDataId = static_cast<int64_t>(dataId);
+    if (mPts) {
+        mFilterEvents[size].get<DemuxFilterEvent::Tag::media>().pts = mPts;
+        mPts = 0;
+    }
+
+    // Clear and log
+    native_handle_close(nativeHandle);
+    native_handle_delete(nativeHandle);
+    output.clear();
+    mAvBufferCopyCount = 0;
+    if (DEBUG_FILTER) {
+        ALOGD("[Filter] av data length %d", static_cast<int32_t>(output.size()));
+    }
+    return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Filter::createShareMemMediaEvents(vector<int8_t>& output) {
+    // copy the filtered data to the shared buffer
+    uint8_t* sharedAvBuffer =
+            getIonBuffer(mSharedAvMemHandle->data[0], output.size() + mSharedAvMemOffset);
+    if (sharedAvBuffer == NULL) {
+        return ::ndk::ScopedAStatus::fromExceptionCode(STATUS_UNKNOWN_ERROR);
+    }
+    memcpy(sharedAvBuffer + mSharedAvMemOffset, output.data(), output.size() * sizeof(uint8_t));
+
+    // Create a memory handle with numFds == 0
+    native_handle_t* nativeHandle = createNativeHandle(-1);
+    if (nativeHandle == NULL) {
+        return ::ndk::ScopedAStatus::fromExceptionCode(STATUS_UNKNOWN_ERROR);
+    }
+
+    // Create mediaEvent and send callback
+    int size = mFilterEvents.size();
+    mFilterEvents.resize(size + 1);
+    mFilterEvents[size] = DemuxFilterEvent::make<DemuxFilterEvent::Tag::media>();
+    mFilterEvents[size].get<DemuxFilterEvent::Tag::media>().avMemory =
+            ::android::dupToAidl(nativeHandle);
+    mFilterEvents[size].get<DemuxFilterEvent::Tag::media>().offset =
+            static_cast<int32_t>(mSharedAvMemOffset);
+    mFilterEvents[size].get<DemuxFilterEvent::Tag::media>().dataLength =
+            static_cast<int32_t>(output.size());
+    if (mPts) {
+        mFilterEvents[size].get<DemuxFilterEvent::Tag::media>().pts = mPts;
+        mPts = 0;
+    }
+    mSharedAvMemOffset += output.size();
+
+    // Clear and log
+    native_handle_close(nativeHandle);
+    native_handle_delete(nativeHandle);
+    output.clear();
+    if (DEBUG_FILTER) {
+        ALOGD("[Filter] shared av data length %d", static_cast<int32_t>(output.size()));
+    }
+    return ::ndk::ScopedAStatus::ok();
+}
+
+bool Filter::sameFile(int fd1, int fd2) {
+    struct stat stat1, stat2;
+    if (fstat(fd1, &stat1) < 0 || fstat(fd2, &stat2) < 0) {
+        return false;
+    }
+    return (stat1.st_dev == stat2.st_dev) && (stat1.st_ino == stat2.st_ino);
+}
+
+void Filter::createMediaEvent(vector<DemuxFilterEvent>& events) {
+    AudioExtraMetaData audio;
+    events.resize(1);
+
+    audio.adFade = 1;
+    audio.adPan = 2;
+    audio.versionTextTag = 3;
+    audio.adGainCenter = 4;
+    audio.adGainFront = 5;
+    audio.adGainSurround = 6;
+
+    events[0] = DemuxFilterEvent::make<DemuxFilterEvent::Tag::media>();
+    events[0].get<DemuxFilterEvent::Tag::media>().streamId = 1;
+    events[0].get<DemuxFilterEvent::Tag::media>().isPtsPresent = true;
+    events[0].get<DemuxFilterEvent::Tag::media>().dataLength = 3;
+    events[0].get<DemuxFilterEvent::Tag::media>().offset = 4;
+    events[0].get<DemuxFilterEvent::Tag::media>().isSecureMemory = true;
+    events[0].get<DemuxFilterEvent::Tag::media>().mpuSequenceNumber = 6;
+    events[0].get<DemuxFilterEvent::Tag::media>().isPesPrivateData = true;
+    events[0]
+            .get<DemuxFilterEvent::Tag::media>()
+            .extraMetaData.set<DemuxFilterMediaEventExtraMetaData::Tag::audio>(audio);
+
+    int av_fd = createAvIonFd(BUFFER_SIZE_16M);
+    if (av_fd == -1) {
+        return;
+    }
+
+    native_handle_t* nativeHandle = createNativeHandle(av_fd);
+    if (nativeHandle == nullptr) {
+        ::close(av_fd);
+        ALOGE("[Filter] Failed to create native_handle %d", errno);
+        return;
+    }
+
+    // Create a dataId and add a <dataId, av_fd> pair into the dataId2Avfd map
+    uint64_t dataId = mLastUsedDataId++ /*createdUID*/;
+    mDataId2Avfd[dataId] = dup(av_fd);
+
+    events[0].get<DemuxFilterEvent::Tag::media>().avDataId = static_cast<int64_t>(dataId);
+    events[0].get<DemuxFilterEvent::Tag::media>().avMemory = ::android::dupToAidl(nativeHandle);
+
+    native_handle_close(nativeHandle);
+    native_handle_delete(nativeHandle);
+}
+
+void Filter::createTsRecordEvent(vector<DemuxFilterEvent>& events) {
+    events.resize(2);
+
+    DemuxPid pid;
+    DemuxFilterScIndexMask mask;
+    DemuxFilterTsRecordEvent tsRecord1;
+    pid.set<DemuxPid::Tag::tPid>(1);
+    mask.set<DemuxFilterScIndexMask::Tag::scIndex>(1);
+    tsRecord1.pid = pid;
+    tsRecord1.tsIndexMask = 1;
+    tsRecord1.scIndexMask = mask;
+    tsRecord1.byteNumber = 2;
+
+    DemuxFilterTsRecordEvent tsRecord2;
+    tsRecord2.pts = 1;
+    tsRecord2.firstMbInSlice = 2;  // random address
+
+    events[0].set<DemuxFilterEvent::Tag::tsRecord>(tsRecord1);
+    events[1].set<DemuxFilterEvent::Tag::tsRecord>(tsRecord2);
+}
+
+void Filter::createMmtpRecordEvent(vector<DemuxFilterEvent>& events) {
+    events.resize(2);
+
+    DemuxFilterMmtpRecordEvent mmtpRecord1;
+    mmtpRecord1.scHevcIndexMask = 1;
+    mmtpRecord1.byteNumber = 2;
+
+    DemuxFilterMmtpRecordEvent mmtpRecord2;
+    mmtpRecord2.pts = 1;
+    mmtpRecord2.mpuSequenceNumber = 2;
+    mmtpRecord2.firstMbInSlice = 3;
+    mmtpRecord2.tsIndexMask = 4;
+
+    events[0].set<DemuxFilterEvent::Tag::mmtpRecord>(mmtpRecord1);
+    events[1].set<DemuxFilterEvent::Tag::mmtpRecord>(mmtpRecord2);
+}
+
+void Filter::createSectionEvent(vector<DemuxFilterEvent>& events) {
+    events.resize(1);
+
+    DemuxFilterSectionEvent section;
+    section.tableId = 1;
+    section.version = 2;
+    section.sectionNum = 3;
+    section.dataLength = 0;
+
+    events[0].set<DemuxFilterEvent::Tag::section>(section);
+}
+
+void Filter::createPesEvent(vector<DemuxFilterEvent>& events) {
+    events.resize(1);
+
+    DemuxFilterPesEvent pes;
+    pes.streamId = 1;
+    pes.dataLength = 1;
+    pes.mpuSequenceNumber = 2;
+
+    events[0].set<DemuxFilterEvent::Tag::pes>(pes);
+}
+
+void Filter::createDownloadEvent(vector<DemuxFilterEvent>& events) {
+    events.resize(1);
+
+    DemuxFilterDownloadEvent download;
+    download.itemId = 1;
+    download.mpuSequenceNumber = 2;
+    download.itemFragmentIndex = 3;
+    download.lastItemFragmentIndex = 4;
+    download.dataLength = 0;
+
+    events[0].set<DemuxFilterEvent::Tag::download>(download);
+}
+
+void Filter::createIpPayloadEvent(vector<DemuxFilterEvent>& events) {
+    events.resize(1);
+
+    DemuxFilterIpPayloadEvent ipPayload;
+    ipPayload.dataLength = 0;
+
+    events[0].set<DemuxFilterEvent::Tag::ipPayload>(ipPayload);
+}
+
+void Filter::createTemiEvent(vector<DemuxFilterEvent>& events) {
+    events.resize(1);
+
+    DemuxFilterTemiEvent temi;
+    temi.pts = 1;
+    temi.descrTag = 2;
+    temi.descrData = {3};
+
+    events[0].set<DemuxFilterEvent::Tag::temi>(temi);
+}
+
+void Filter::createMonitorEvent(vector<DemuxFilterEvent>& events) {
+    events.resize(1);
+
+    DemuxFilterMonitorEvent monitor;
+    monitor.set<DemuxFilterMonitorEvent::Tag::scramblingStatus>(ScramblingStatus::SCRAMBLED);
+    events[0].set<DemuxFilterEvent::Tag::monitorEvent>(monitor);
+}
+
+void Filter::createRestartEvent(vector<DemuxFilterEvent>& events) {
+    events.resize(1);
+
+    events[0].set<DemuxFilterEvent::Tag::startId>(1);
+}
+
+}  // namespace tuner
+}  // namespace tv
+}  // namespace hardware
+}  // namespace android
+}  // namespace aidl
diff --git a/tv/tuner/aidl/default/Filter.h b/tv/tuner/aidl/default/Filter.h
new file mode 100644
index 0000000..7a037e6
--- /dev/null
+++ b/tv/tuner/aidl/default/Filter.h
@@ -0,0 +1,242 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <aidl/android/hardware/tv/tuner/BnFilter.h>
+#include <aidl/android/hardware/tv/tuner/Constant.h>
+
+#include <fmq/AidlMessageQueue.h>
+#include <inttypes.h>
+#include <ion/ion.h>
+#include <math.h>
+#include <sys/stat.h>
+#include <set>
+#include "Demux.h"
+#include "Dvr.h"
+#include "Frontend.h"
+
+using namespace std;
+
+namespace aidl {
+namespace android {
+namespace hardware {
+namespace tv {
+namespace tuner {
+
+using ::aidl::android::hardware::common::NativeHandle;
+using ::aidl::android::hardware::common::fmq::MQDescriptor;
+using ::aidl::android::hardware::common::fmq::SynchronizedReadWrite;
+using ::android::AidlMessageQueue;
+using ::android::hardware::EventFlag;
+
+using FilterMQ = AidlMessageQueue<int8_t, SynchronizedReadWrite>;
+const uint32_t BUFFER_SIZE_16M = 0x1000000;
+
+class Demux;
+class Dvr;
+
+class Filter : public BnFilter {
+  public:
+    Filter();
+
+    Filter(DemuxFilterType type, int64_t filterId, uint32_t bufferSize,
+           const std::shared_ptr<IFilterCallback>& cb, std::shared_ptr<Demux> demux);
+
+    ~Filter();
+
+    ::ndk::ScopedAStatus getQueueDesc(
+            MQDescriptor<int8_t, SynchronizedReadWrite>* out_queue) override;
+    ::ndk::ScopedAStatus close() override;
+    ::ndk::ScopedAStatus configure(const DemuxFilterSettings& in_settings) override;
+    ::ndk::ScopedAStatus configureAvStreamType(const AvStreamType& in_avStreamType) override;
+    ::ndk::ScopedAStatus configureIpCid(int32_t in_ipCid) override;
+    ::ndk::ScopedAStatus configureMonitorEvent(int32_t in_monitorEventTypes) override;
+    ::ndk::ScopedAStatus start() override;
+    ::ndk::ScopedAStatus stop() override;
+    ::ndk::ScopedAStatus flush() override;
+    ::ndk::ScopedAStatus getAvSharedHandle(NativeHandle* out_avMemory,
+                                           int64_t* _aidl_return) override;
+    ::ndk::ScopedAStatus getId(int32_t* _aidl_return) override;
+    ::ndk::ScopedAStatus getId64Bit(int64_t* _aidl_return) override;
+    ::ndk::ScopedAStatus releaseAvHandle(const NativeHandle& in_avMemory,
+                                         int64_t in_avDataId) override;
+    ::ndk::ScopedAStatus setDataSource(const std::shared_ptr<IFilter>& in_filter) override;
+
+    /**
+     * To create a FilterMQ and its Event Flag.
+     *
+     * Return false is any of the above processes fails.
+     */
+    bool createFilterMQ();
+    uint16_t getTpid();
+    void updateFilterOutput(vector<int8_t> data);
+    void updateRecordOutput(vector<int8_t> data);
+    void updatePts(uint64_t pts);
+    ::ndk::ScopedAStatus startFilterHandler();
+    ::ndk::ScopedAStatus startRecordFilterHandler();
+    void attachFilterToRecord(const std::shared_ptr<Dvr> dvr);
+    void detachFilterFromRecord();
+    void freeSharedAvHandle();
+    bool isMediaFilter() { return mIsMediaFilter; };
+    bool isPcrFilter() { return mIsPcrFilter; };
+    bool isRecordFilter() { return mIsRecordFilter; };
+
+  private:
+    // Tuner service
+    std::shared_ptr<Demux> mDemux;
+    // Dvr reference once the filter is attached to any
+    std::shared_ptr<Dvr> mDvr = nullptr;
+    /**
+     * Filter callbacks used on filter events or FMQ status
+     */
+    std::shared_ptr<IFilterCallback> mCallback = nullptr;
+
+    int64_t mFilterId;
+    int32_t mCid = static_cast<int32_t>(Constant::INVALID_IP_FILTER_CONTEXT_ID);
+    uint32_t mBufferSize;
+    DemuxFilterType mType;
+    bool mIsMediaFilter = false;
+    bool mIsPcrFilter = false;
+    bool mIsRecordFilter = false;
+    DemuxFilterSettings mFilterSettings;
+
+    uint16_t mTpid;
+    std::shared_ptr<IFilter> mDataSource;
+    bool mIsDataSourceDemux = true;
+    vector<int8_t> mFilterOutput;
+    vector<int8_t> mRecordFilterOutput;
+    int64_t mPts = 0;
+    unique_ptr<FilterMQ> mFilterMQ;
+    bool mIsUsingFMQ = false;
+    EventFlag* mFilterEventsFlag;
+    vector<DemuxFilterEvent> mFilterEvents;
+
+    // Thread handlers
+    pthread_t mFilterThread;
+
+    // FMQ status local records
+    DemuxFilterStatus mFilterStatus;
+    /**
+     * If a specific filter's writing loop is still running
+     */
+    bool mFilterThreadRunning;
+    bool mKeepFetchingDataFromFrontend;
+
+    /**
+     * How many times a filter should write
+     * TODO make this dynamic/random/can take as a parameter
+     */
+    const uint16_t SECTION_WRITE_COUNT = 10;
+
+    bool DEBUG_FILTER = false;
+
+    /**
+     * Filter handlers to handle the data filtering.
+     * They are also responsible to write the filtered output into the filter FMQ
+     * and update the filterEvent bound with the same filterId.
+     */
+    ::ndk::ScopedAStatus startSectionFilterHandler();
+    ::ndk::ScopedAStatus startPesFilterHandler();
+    ::ndk::ScopedAStatus startTsFilterHandler();
+    ::ndk::ScopedAStatus startMediaFilterHandler();
+    ::ndk::ScopedAStatus startPcrFilterHandler();
+    ::ndk::ScopedAStatus startTemiFilterHandler();
+    ::ndk::ScopedAStatus startFilterLoop();
+
+    void deleteEventFlag();
+    bool writeDataToFilterMQ(const std::vector<int8_t>& data);
+    bool readDataFromMQ();
+    bool writeSectionsAndCreateEvent(vector<int8_t>& data);
+    void maySendFilterStatusCallback();
+    DemuxFilterStatus checkFilterStatusChange(uint32_t availableToWrite, uint32_t availableToRead,
+                                              uint32_t highThreshold, uint32_t lowThreshold);
+    /**
+     * A dispatcher to read and dispatch input data to all the started filters.
+     * Each filter handler handles the data filtering/output writing/filterEvent updating.
+     */
+    bool startFilterDispatcher();
+    static void* __threadLoopFilter(void* user);
+    void filterThreadLoop();
+
+    int createAvIonFd(int size);
+    uint8_t* getIonBuffer(int fd, int size);
+    native_handle_t* createNativeHandle(int fd);
+    ::ndk::ScopedAStatus createMediaFilterEventWithIon(vector<int8_t>& output);
+    ::ndk::ScopedAStatus createIndependentMediaEvents(vector<int8_t>& output);
+    ::ndk::ScopedAStatus createShareMemMediaEvents(vector<int8_t>& output);
+    bool sameFile(int fd1, int fd2);
+
+    void createMediaEvent(vector<DemuxFilterEvent>&);
+    void createTsRecordEvent(vector<DemuxFilterEvent>&);
+    void createMmtpRecordEvent(vector<DemuxFilterEvent>&);
+    void createSectionEvent(vector<DemuxFilterEvent>&);
+    void createPesEvent(vector<DemuxFilterEvent>&);
+    void createDownloadEvent(vector<DemuxFilterEvent>&);
+    void createIpPayloadEvent(vector<DemuxFilterEvent>&);
+    void createTemiEvent(vector<DemuxFilterEvent>&);
+    void createMonitorEvent(vector<DemuxFilterEvent>&);
+    void createRestartEvent(vector<DemuxFilterEvent>&);
+
+    /**
+     * Lock to protect writes to the FMQs
+     */
+    std::mutex mWriteLock;
+    /**
+     * Lock to protect writes to the filter event
+     */
+    // TODO make each filter separate event lock
+    std::mutex mFilterEventsLock;
+    /**
+     * Lock to protect writes to the input status
+     */
+    std::mutex mFilterStatusLock;
+    std::mutex mFilterThreadLock;
+    std::mutex mFilterOutputLock;
+    std::mutex mRecordFilterOutputLock;
+
+    // temp handle single PES filter
+    // TODO handle mulptiple Pes filters
+    int mPesSizeLeft = 0;
+    vector<int8_t> mPesOutput;
+
+    // A map from data id to ion handle
+    std::map<uint64_t, int> mDataId2Avfd;
+    uint64_t mLastUsedDataId = 1;
+    int mAvBufferCopyCount = 0;
+
+    // Shared A/V memory handle
+    native_handle_t* mSharedAvMemHandle = nullptr;
+    bool mUsingSharedAvMem = false;
+    uint32_t mSharedAvMemOffset = 0;
+
+    uint32_t mAudioStreamType;
+    uint32_t mVideoStreamType;
+
+    // Scrambling status to be monitored
+    uint32_t mStatuses = 0;
+
+    bool mConfigured = false;
+    int mStartId = 0;
+    uint8_t mScramblingStatusMonitored = 0;
+    uint8_t mIpCidMonitored = 0;
+};
+
+}  // namespace tuner
+}  // namespace tv
+}  // namespace hardware
+}  // namespace android
+}  // namespace aidl
diff --git a/tv/tuner/aidl/default/Frontend.cpp b/tv/tuner/aidl/default/Frontend.cpp
new file mode 100644
index 0000000..2be13d3
--- /dev/null
+++ b/tv/tuner/aidl/default/Frontend.cpp
@@ -0,0 +1,721 @@
+/*
+ * Copyright 2021 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 "android.hardware.tv.tuner-service.example-Frontend"
+
+#include "Frontend.h"
+#include <utils/Log.h>
+
+namespace aidl {
+namespace android {
+namespace hardware {
+namespace tv {
+namespace tuner {
+
+Frontend::Frontend(FrontendType type, int32_t id, std::shared_ptr<Tuner> tuner) {
+    mType = type;
+    mId = id;
+    mTunerService = tuner;
+    // Init callback to nullptr
+    mCallback = nullptr;
+}
+
+Frontend::~Frontend() {}
+
+::ndk::ScopedAStatus Frontend::close() {
+    ALOGV("%s", __FUNCTION__);
+    // Reset callback
+    mCallback = nullptr;
+    mIsLocked = false;
+    mTunerService->removeFrontend(mId);
+
+    return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Frontend::setCallback(const std::shared_ptr<IFrontendCallback>& in_callback) {
+    ALOGV("%s", __FUNCTION__);
+    if (in_callback == nullptr) {
+        ALOGW("[   WARN   ] Set Frontend callback with nullptr");
+        return ::ndk::ScopedAStatus::fromExceptionCode(STATUS_INVALID_OPERATION);
+    }
+
+    mCallback = in_callback;
+    return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Frontend::tune(const FrontendSettings& /* in_settings */) {
+    ALOGV("%s", __FUNCTION__);
+    if (mCallback == nullptr) {
+        ALOGW("[   WARN   ] Frontend callback is not set when tune");
+        return ::ndk::ScopedAStatus::fromExceptionCode(STATUS_INVALID_OPERATION);
+    }
+
+    mTunerService->frontendStartTune(mId);
+    mCallback->onEvent(FrontendEventType::LOCKED);
+    mIsLocked = true;
+
+    return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Frontend::stopTune() {
+    ALOGV("%s", __FUNCTION__);
+
+    mTunerService->frontendStopTune(mId);
+    mIsLocked = false;
+
+    return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Frontend::scan(const FrontendSettings& in_settings, FrontendScanType in_type) {
+    ALOGV("%s", __FUNCTION__);
+    FrontendScanMessage msg;
+
+    if (mIsLocked) {
+        msg.set<FrontendScanMessage::Tag::isEnd>(true);
+        mCallback->onScanMessage(FrontendScanMessageType::END, msg);
+        return ::ndk::ScopedAStatus::ok();
+    }
+
+    int32_t frequency = 0;
+    switch (in_settings.getTag()) {
+        case FrontendSettings::Tag::analog:
+            frequency = in_settings.get<FrontendSettings::Tag::analog>().frequency;
+            break;
+        case FrontendSettings::Tag::atsc:
+            frequency = in_settings.get<FrontendSettings::Tag::atsc>().frequency;
+            break;
+        case FrontendSettings::Tag::atsc3:
+            frequency = in_settings.get<FrontendSettings::Tag::atsc3>().frequency;
+            break;
+        case FrontendSettings::Tag::dvbs:
+            frequency = in_settings.get<FrontendSettings::Tag::dvbs>().frequency;
+            break;
+        case FrontendSettings::Tag::dvbc:
+            frequency = in_settings.get<FrontendSettings::Tag::dvbc>().frequency;
+            break;
+        case FrontendSettings::Tag::dvbt:
+            frequency = in_settings.get<FrontendSettings::Tag::dvbt>().frequency;
+            break;
+        case FrontendSettings::Tag::isdbs:
+            frequency = in_settings.get<FrontendSettings::Tag::isdbs>().frequency;
+            break;
+        case FrontendSettings::Tag::isdbs3:
+            frequency = in_settings.get<FrontendSettings::Tag::isdbs3>().frequency;
+            break;
+        case FrontendSettings::Tag::isdbt:
+            frequency = in_settings.get<FrontendSettings::Tag::isdbt>().frequency;
+            break;
+        default:
+            break;
+    }
+
+    if (in_type == FrontendScanType::SCAN_BLIND) {
+        frequency += 100;
+    }
+
+    {
+        FrontendScanMessage msg;
+        vector<int32_t> frequencies = {frequency};
+        msg.set<FrontendScanMessage::Tag::frequencies>(frequencies);
+        mCallback->onScanMessage(FrontendScanMessageType::FREQUENCY, msg);
+    }
+
+    {
+        FrontendScanMessage msg;
+        msg.set<FrontendScanMessage::Tag::progressPercent>(20);
+        mCallback->onScanMessage(FrontendScanMessageType::PROGRESS_PERCENT, msg);
+    }
+
+    {
+        FrontendScanMessage msg;
+        vector<int32_t> symbolRates = {30};
+        msg.set<FrontendScanMessage::Tag::symbolRates>(symbolRates);
+        mCallback->onScanMessage(FrontendScanMessageType::SYMBOL_RATE, msg);
+    }
+
+    if (mType == FrontendType::DVBT) {
+        FrontendScanMessage msg;
+        msg.set<FrontendScanMessage::Tag::hierarchy>(FrontendDvbtHierarchy::HIERARCHY_NON_NATIVE);
+        mCallback->onScanMessage(FrontendScanMessageType::HIERARCHY, msg);
+    }
+
+    if (mType == FrontendType::ANALOG) {
+        FrontendScanMessage msg;
+        msg.set<FrontendScanMessage::Tag::analogType>(FrontendAnalogType::PAL);
+        mCallback->onScanMessage(FrontendScanMessageType::ANALOG_TYPE, msg);
+    }
+
+    {
+        FrontendScanMessage msg;
+        vector<uint8_t> plpIds = {2};
+        msg.set<FrontendScanMessage::Tag::plpIds>(plpIds);
+        mCallback->onScanMessage(FrontendScanMessageType::PLP_IDS, msg);
+    }
+
+    {
+        FrontendScanMessage msg;
+        vector<uint8_t> groupIds = {3};
+        msg.set<FrontendScanMessage::Tag::groupIds>(groupIds);
+        mCallback->onScanMessage(FrontendScanMessageType::GROUP_IDS, msg);
+    }
+
+    {
+        FrontendScanMessage msg;
+        vector<char16_t> inputStreamIds = {1};
+        msg.set<FrontendScanMessage::Tag::inputStreamIds>(inputStreamIds);
+        mCallback->onScanMessage(FrontendScanMessageType::INPUT_STREAM_IDS, msg);
+    }
+
+    switch (mType) {
+        case FrontendType::DVBT: {
+            FrontendScanMessage msg;
+            FrontendScanMessageStandard std;
+            std.set<FrontendScanMessageStandard::Tag::tStd>(FrontendDvbtStandard::AUTO);
+            msg.set<FrontendScanMessage::Tag::std>(std);
+            mCallback->onScanMessage(FrontendScanMessageType::STANDARD, msg);
+            break;
+        }
+        case FrontendType::DVBS: {
+            FrontendScanMessage msg;
+            FrontendScanMessageStandard std;
+            std.set<FrontendScanMessageStandard::Tag::sStd>(FrontendDvbsStandard::AUTO);
+            msg.set<FrontendScanMessage::Tag::std>(std);
+            mCallback->onScanMessage(FrontendScanMessageType::STANDARD, msg);
+            break;
+        }
+        case FrontendType::ANALOG: {
+            FrontendScanMessage msg;
+            FrontendScanMessageStandard std;
+            std.set<FrontendScanMessageStandard::Tag::sifStd>(FrontendAnalogSifStandard::AUTO);
+            msg.set<FrontendScanMessage::Tag::std>(std);
+            mCallback->onScanMessage(FrontendScanMessageType::STANDARD, msg);
+            break;
+        }
+        default:
+            break;
+    }
+
+    {
+        FrontendScanMessage msg;
+        FrontendScanAtsc3PlpInfo info;
+        info.plpId = 1;
+        info.bLlsFlag = false;
+        vector<FrontendScanAtsc3PlpInfo> atsc3PlpInfos = {info};
+        msg.set<FrontendScanMessage::Tag::atsc3PlpInfos>(atsc3PlpInfos);
+        mCallback->onScanMessage(FrontendScanMessageType::ATSC3_PLP_INFO, msg);
+    }
+
+    {
+        FrontendScanMessage msg;
+        FrontendModulation modulation;
+        modulation.set<FrontendModulation::Tag::dvbc>(FrontendDvbcModulation::MOD_16QAM);
+        msg.set<FrontendScanMessage::Tag::modulation>(modulation);
+        mCallback->onScanMessage(FrontendScanMessageType::MODULATION, msg);
+    }
+
+    {
+        FrontendScanMessage msg;
+        msg.set<FrontendScanMessage::Tag::isHighPriority>(true);
+        mCallback->onScanMessage(FrontendScanMessageType::HIGH_PRIORITY, msg);
+    }
+
+    {
+        FrontendScanMessage msg;
+        msg.set<FrontendScanMessage::Tag::isLocked>(true);
+        mCallback->onScanMessage(FrontendScanMessageType::LOCKED, msg);
+        mIsLocked = true;
+    }
+
+    return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Frontend::stopScan() {
+    ALOGV("%s", __FUNCTION__);
+
+    mIsLocked = false;
+    return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Frontend::getStatus(const std::vector<FrontendStatusType>& in_statusTypes,
+                                         std::vector<FrontendStatus>* _aidl_return) {
+    ALOGV("%s", __FUNCTION__);
+
+    for (int i = 0; i < in_statusTypes.size(); i++) {
+        FrontendStatusType type = in_statusTypes[i];
+        FrontendStatus status;
+        // assign randomly selected values for testing.
+        switch (type) {
+            case FrontendStatusType::DEMOD_LOCK: {
+                status.set<FrontendStatus::isDemodLocked>(true);
+                break;
+            }
+            case FrontendStatusType::SNR: {
+                status.set<FrontendStatus::snr>(221);
+                break;
+            }
+            case FrontendStatusType::BER: {
+                status.set<FrontendStatus::ber>(1);
+                break;
+            }
+            case FrontendStatusType::PER: {
+                status.set<FrontendStatus::per>(2);
+                break;
+            }
+            case FrontendStatusType::PRE_BER: {
+                status.set<FrontendStatus::preBer>(3);
+                break;
+            }
+            case FrontendStatusType::SIGNAL_QUALITY: {
+                status.set<FrontendStatus::signalQuality>(4);
+                break;
+            }
+            case FrontendStatusType::SIGNAL_STRENGTH: {
+                status.set<FrontendStatus::signalStrength>(5);
+                break;
+            }
+            case FrontendStatusType::SYMBOL_RATE: {
+                status.set<FrontendStatus::symbolRate>(6);
+                break;
+            }
+            case FrontendStatusType::FEC: {
+                status.set<FrontendStatus::innerFec>(FrontendInnerFec::FEC_2_9);  // value = 1 << 7
+                break;
+            }
+            case FrontendStatusType::MODULATION: {
+                switch (mType) {
+                    case FrontendType::ISDBS: {
+                        FrontendModulationStatus modulationStatus;
+                        modulationStatus.set<FrontendModulationStatus::Tag::isdbs>(
+                                FrontendIsdbsModulation::MOD_BPSK);  // value = 1 << 1
+                        status.set<FrontendStatus::modulationStatus>(modulationStatus);
+                        break;
+                    }
+                    case FrontendType::DVBC: {
+                        FrontendModulationStatus modulationStatus;
+                        modulationStatus.set<FrontendModulationStatus::Tag::dvbc>(
+                                FrontendDvbcModulation::MOD_16QAM);  // value = 1 << 1
+                        status.set<FrontendStatus::modulationStatus>(modulationStatus);
+                        break;
+                    }
+                    case FrontendType::DVBS: {
+                        FrontendModulationStatus modulationStatus;
+                        modulationStatus.set<FrontendModulationStatus::Tag::dvbs>(
+                                FrontendDvbsModulation::MOD_QPSK);  // value = 1 << 1
+                        status.set<FrontendStatus::modulationStatus>(modulationStatus);
+                        break;
+                    }
+                    case FrontendType::ISDBS3: {
+                        FrontendModulationStatus modulationStatus;
+                        modulationStatus.set<FrontendModulationStatus::Tag::isdbs3>(
+                                FrontendIsdbs3Modulation::MOD_BPSK);  // value = 1 << 1
+                        status.set<FrontendStatus::modulationStatus>(modulationStatus);
+                        break;
+                    }
+                    case FrontendType::ISDBT: {
+                        FrontendModulationStatus modulationStatus;
+                        modulationStatus.set<FrontendModulationStatus::Tag::isdbt>(
+                                FrontendIsdbtModulation::MOD_DQPSK);  // value = 1 << 1
+                        status.set<FrontendStatus::modulationStatus>(modulationStatus);
+                        break;
+                    }
+                    default:
+                        break;
+                }
+                break;
+            }
+            case FrontendStatusType::SPECTRAL: {
+                status.set<FrontendStatus::inversion>(FrontendSpectralInversion::NORMAL);
+                break;
+            }
+            case FrontendStatusType::LNB_VOLTAGE: {
+                status.set<FrontendStatus::lnbVoltage>(LnbVoltage::VOLTAGE_5V);
+                break;
+            }
+            case FrontendStatusType::PLP_ID: {
+                status.set<FrontendStatus::plpId>(101);  // type uint8_t
+                break;
+            }
+            case FrontendStatusType::EWBS: {
+                status.set<FrontendStatus::isEWBS>(false);
+                break;
+            }
+            case FrontendStatusType::AGC: {
+                status.set<FrontendStatus::agc>(7);
+                break;
+            }
+            case FrontendStatusType::LNA: {
+                status.set<FrontendStatus::isLnaOn>(false);
+                break;
+            }
+            case FrontendStatusType::LAYER_ERROR: {
+                vector<bool> v = {false, true, true};
+                status.set<FrontendStatus::isLayerError>(v);
+                break;
+            }
+            case FrontendStatusType::MER: {
+                status.set<FrontendStatus::mer>(8);
+                break;
+            }
+            case FrontendStatusType::FREQ_OFFSET: {
+                status.set<FrontendStatus::freqOffset>(9);
+                break;
+            }
+            case FrontendStatusType::HIERARCHY: {
+                status.set<FrontendStatus::hierarchy>(FrontendDvbtHierarchy::HIERARCHY_1_NATIVE);
+                break;
+            }
+            case FrontendStatusType::RF_LOCK: {
+                status.set<FrontendStatus::isRfLocked>(false);
+                break;
+            }
+            case FrontendStatusType::ATSC3_PLP_INFO: {
+                FrontendStatusAtsc3PlpInfo info1;
+                info1.plpId = 3;
+                info1.isLocked = false;
+                info1.uec = 313;
+                FrontendStatusAtsc3PlpInfo info2;
+                info2.plpId = 5;
+                info2.isLocked = true;
+                info2.uec = 515;
+                vector<FrontendStatusAtsc3PlpInfo> infos = {info1, info2};
+                status.set<FrontendStatus::plpInfo>(infos);
+                break;
+            }
+            case FrontendStatusType::MODULATIONS: {
+                FrontendModulation modulation;
+                vector<FrontendModulation> modulations;
+                switch (mType) {
+                    case FrontendType::ISDBS: {
+                        modulation.set<FrontendModulation::Tag::isdbs>(
+                                FrontendIsdbsModulation::MOD_BPSK);  // value = 1 << 1
+                        modulations.push_back(modulation);
+                        status.set<FrontendStatus::modulations>(modulations);
+                        break;
+                    }
+                    case FrontendType::DVBC: {
+                        modulation.set<FrontendModulation::Tag::dvbc>(
+                                FrontendDvbcModulation::MOD_16QAM);  // value = 1 << 1
+                        modulations.push_back(modulation);
+                        status.set<FrontendStatus::modulations>(modulations);
+                        break;
+                    }
+                    case FrontendType::DVBS: {
+                        modulation.set<FrontendModulation::Tag::dvbs>(
+                                FrontendDvbsModulation::MOD_QPSK);  // value = 1 << 1
+                        modulations.push_back(modulation);
+                        status.set<FrontendStatus::modulations>(modulations);
+                        break;
+                    }
+                    case FrontendType::DVBT: {
+                        modulation.set<FrontendModulation::Tag::dvbt>(
+                                FrontendDvbtConstellation::CONSTELLATION_16QAM_R);  // value = 1 <<
+                                                                                    // 16
+                        modulations.push_back(modulation);
+                        status.set<FrontendStatus::modulations>(modulations);
+                        break;
+                    }
+                    case FrontendType::ISDBS3: {
+                        modulation.set<FrontendModulation::Tag::isdbs3>(
+                                FrontendIsdbs3Modulation::MOD_BPSK);  //  value = 1 << 1
+                        modulations.push_back(modulation);
+                        status.set<FrontendStatus::modulations>(modulations);
+                        break;
+                    }
+                    case FrontendType::ISDBT: {
+                        modulation.set<FrontendModulation::Tag::isdbt>(
+                                FrontendIsdbtModulation::MOD_DQPSK);  // value = 1 << 1
+                        modulations.push_back(modulation);
+                        status.set<FrontendStatus::modulations>(modulations);
+                        break;
+                    }
+                    case FrontendType::ATSC: {
+                        modulation.set<FrontendModulation::Tag::atsc>(
+                                FrontendAtscModulation::MOD_8VSB);  // value = 1 << 2
+                        modulations.push_back(modulation);
+                        status.set<FrontendStatus::modulations>(modulations);
+                        break;
+                    }
+                    case FrontendType::ATSC3: {
+                        modulation.set<FrontendModulation::Tag::atsc3>(
+                                FrontendAtsc3Modulation::MOD_QPSK);  // value = 1 << 1
+                        modulations.push_back(modulation);
+                        status.set<FrontendStatus::modulations>(modulations);
+                        break;
+                    }
+                    case FrontendType::DTMB: {
+                        modulation.set<FrontendModulation::Tag::dtmb>(
+                                FrontendDtmbModulation::CONSTELLATION_4QAM);  // value = 1 << 1
+                        modulations.push_back(modulation);
+                        status.set<FrontendStatus::modulations>(modulations);
+                        break;
+                    }
+                    default:
+                        break;
+                }
+                break;
+            }
+            case FrontendStatusType::BERS: {
+                vector<int32_t> bers = {1};
+                status.set<FrontendStatus::bers>(bers);
+                break;
+            }
+            case FrontendStatusType::CODERATES: {
+                vector<FrontendInnerFec> rates;
+                rates.push_back(FrontendInnerFec::FEC_6_15);  // value = 1 << 39
+                status.set<FrontendStatus::codeRates>(rates);
+                break;
+            }
+            case FrontendStatusType::BANDWIDTH: {
+                FrontendBandwidth bandwidth;
+                switch (mType) {
+                    case FrontendType::DVBC: {
+                        bandwidth.set<FrontendBandwidth::Tag::dvbc>(
+                                FrontendDvbcBandwidth::BANDWIDTH_6MHZ);  // value = 1 << 1
+                        status.set<FrontendStatus::bandwidth>(bandwidth);
+                        break;
+                    }
+                    case FrontendType::DVBT: {
+                        bandwidth.set<FrontendBandwidth::Tag::dvbt>(
+                                FrontendDvbtBandwidth::BANDWIDTH_8MHZ);  // value = 1 << 1
+                        status.set<FrontendStatus::bandwidth>(bandwidth);
+                        break;
+                    }
+                    case FrontendType::ISDBT: {
+                        bandwidth.set<FrontendBandwidth::Tag::isdbt>(
+                                FrontendIsdbtBandwidth::BANDWIDTH_8MHZ);  // value = 1 << 1
+                        status.set<FrontendStatus::bandwidth>(bandwidth);
+                        break;
+                    }
+                    case FrontendType::ATSC3: {
+                        bandwidth.set<FrontendBandwidth::Tag::atsc3>(
+                                FrontendAtsc3Bandwidth::BANDWIDTH_6MHZ);  // value = 1 << 1
+                        status.set<FrontendStatus::bandwidth>(bandwidth);
+                        break;
+                    }
+                    case FrontendType::DTMB: {
+                        bandwidth.set<FrontendBandwidth::Tag::dtmb>(
+                                FrontendDtmbBandwidth::BANDWIDTH_8MHZ);  // value = 1 << 1
+                        status.set<FrontendStatus::bandwidth>(bandwidth);
+                        break;
+                    }
+                    default:
+                        break;
+                }
+                break;
+            }
+            case FrontendStatusType::GUARD_INTERVAL: {
+                FrontendGuardInterval interval;
+                switch (mType) {
+                    case FrontendType::DVBT: {
+                        interval.set<FrontendGuardInterval::Tag::dvbt>(
+                                FrontendDvbtGuardInterval::INTERVAL_1_32);  // value = 1 << 1
+                        status.set<FrontendStatus::interval>(interval);
+                        break;
+                    }
+                    case FrontendType::ISDBT: {
+                        interval.set<FrontendGuardInterval::Tag::isdbt>(
+                                FrontendDvbtGuardInterval::INTERVAL_1_32);  // value = 1 << 1
+                        status.set<FrontendStatus::interval>(interval);
+                        break;
+                    }
+                    case FrontendType::DTMB: {
+                        interval.set<FrontendGuardInterval::Tag::dtmb>(
+                                FrontendDtmbGuardInterval::PN_420_VARIOUS);  // value = 1 << 1
+                        status.set<FrontendStatus::interval>(interval);
+                        break;
+                    }
+                    default:
+                        break;
+                }
+                break;
+            }
+            case FrontendStatusType::TRANSMISSION_MODE: {
+                FrontendTransmissionMode transMode;
+                switch (mType) {
+                    case FrontendType::DVBT: {
+                        transMode.set<FrontendTransmissionMode::Tag::dvbt>(
+                                FrontendDvbtTransmissionMode::MODE_16K_E);  // value = 1 << 8
+                        status.set<FrontendStatus::transmissionMode>(transMode);
+                        break;
+                    }
+                    case FrontendType::ISDBT: {
+                        transMode.set<FrontendTransmissionMode::Tag::isdbt>(
+                                FrontendIsdbtMode::MODE_1);  // value = 1 << 1
+                        status.set<FrontendStatus::transmissionMode>(transMode);
+                        break;
+                    }
+                    case FrontendType::DTMB: {
+                        transMode.set<FrontendTransmissionMode::Tag::dtmb>(
+                                FrontendDtmbTransmissionMode::C1);  // value = 1 << 1
+                        status.set<FrontendStatus::transmissionMode>(transMode);
+                        break;
+                    }
+                    default:
+                        break;
+                }
+                break;
+            }
+            case FrontendStatusType::UEC: {
+                status.set<FrontendStatus::uec>(4);
+                break;
+            }
+            case FrontendStatusType::T2_SYSTEM_ID: {
+                status.set<FrontendStatus::systemId>(5);
+                break;
+            }
+            case FrontendStatusType::INTERLEAVINGS: {
+                FrontendInterleaveMode interleave;
+                vector<FrontendInterleaveMode> interleaves;
+                switch (mType) {
+                    case FrontendType::DVBC: {
+                        // value = 1 << 1
+                        interleave.set<FrontendInterleaveMode::Tag::dvbc>(
+                                FrontendCableTimeInterleaveMode::INTERLEAVING_128_1_0);
+                        interleaves.push_back(interleave);
+                        status.set<FrontendStatus::interleaving>(interleaves);
+                        break;
+                    }
+                    case FrontendType::ATSC3: {
+                        interleave.set<FrontendInterleaveMode::Tag::atsc3>(
+                                FrontendAtsc3TimeInterleaveMode::CTI);  // value = 1 << 1
+                        interleaves.push_back(interleave);
+                        status.set<FrontendStatus::interleaving>(interleaves);
+                        break;
+                    }
+                    case FrontendType::DTMB: {
+                        interleave.set<FrontendInterleaveMode::Tag::dtmb>(
+                                FrontendDtmbTimeInterleaveMode::TIMER_INT_240);  // value = 1 << 1
+                        interleaves.push_back(interleave);
+                        status.set<FrontendStatus::interleaving>(interleaves);
+                        break;
+                    }
+                    default:
+                        break;
+                }
+                break;
+            }
+            case FrontendStatusType::ISDBT_SEGMENTS: {
+                vector<uint8_t> segments = {2, 3};
+                status.set<FrontendStatus::isdbtSegment>(segments);
+                break;
+            }
+            case FrontendStatusType::TS_DATA_RATES: {
+                vector<int32_t> dataRates = {4, 5};
+                status.set<FrontendStatus::tsDataRate>(dataRates);
+                break;
+            }
+            case FrontendStatusType::ROLL_OFF: {
+                FrontendRollOff rollOff;
+                switch (mType) {
+                    case FrontendType::DVBS: {
+                        rollOff.set<FrontendRollOff::Tag::dvbs>(
+                                FrontendDvbsRolloff::ROLLOFF_0_35);  // value = 1
+                        status.set<FrontendStatus::rollOff>(rollOff);
+                        break;
+                    }
+                    case FrontendType::ISDBS: {
+                        rollOff.set<FrontendRollOff::Tag::isdbs>(
+                                FrontendIsdbsRolloff::ROLLOFF_0_35);  // value = 1
+                        status.set<FrontendStatus::rollOff>(rollOff);
+                        break;
+                    }
+                    case FrontendType::ISDBS3: {
+                        rollOff.set<FrontendRollOff::Tag::isdbs3>(
+                                FrontendIsdbs3Rolloff::ROLLOFF_0_03);  // value = 1
+                        status.set<FrontendStatus::rollOff>(rollOff);
+                        break;
+                    }
+                    default:
+                        break;
+                }
+                break;
+            }
+            case FrontendStatusType::IS_MISO: {
+                status.set<FrontendStatus::isMiso>(true);
+                break;
+            }
+            case FrontendStatusType::IS_LINEAR: {
+                status.set<FrontendStatus::isLinear>(true);
+                break;
+            }
+            case FrontendStatusType::IS_SHORT_FRAMES: {
+                status.set<FrontendStatus::isShortFrames>(true);
+                break;
+            }
+            default: {
+                continue;
+            }
+        }
+        _aidl_return->push_back(status);
+    }
+
+    return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Frontend::setLna(bool /* in_bEnable */) {
+    ALOGV("%s", __FUNCTION__);
+
+    return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Frontend::setLnb(int32_t /* in_lnbId */) {
+    ALOGV("%s", __FUNCTION__);
+    if (!supportsSatellite()) {
+        return ::ndk::ScopedAStatus::fromExceptionCode(STATUS_INVALID_OPERATION);
+    }
+    return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Frontend::linkCiCam(int32_t in_ciCamId, int32_t* _aidl_return) {
+    ALOGV("%s", __FUNCTION__);
+
+    mCiCamId = in_ciCamId;
+    *_aidl_return = 0;
+
+    return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Frontend::unlinkCiCam(int32_t /* in_ciCamId */) {
+    ALOGV("%s", __FUNCTION__);
+
+    mCiCamId = -1;
+
+    return ::ndk::ScopedAStatus::ok();
+}
+
+FrontendType Frontend::getFrontendType() {
+    return mType;
+}
+
+int32_t Frontend::getFrontendId() {
+    return mId;
+}
+
+bool Frontend::supportsSatellite() {
+    return mType == FrontendType::DVBS || mType == FrontendType::ISDBS ||
+           mType == FrontendType::ISDBS3;
+}
+
+bool Frontend::isLocked() {
+    return mIsLocked;
+}
+
+}  // namespace tuner
+}  // namespace tv
+}  // namespace hardware
+}  // namespace android
+}  // namespace aidl
diff --git a/tv/tuner/aidl/default/Frontend.h b/tv/tuner/aidl/default/Frontend.h
new file mode 100644
index 0000000..f89e74d
--- /dev/null
+++ b/tv/tuner/aidl/default/Frontend.h
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <aidl/android/hardware/tv/tuner/BnFrontend.h>
+#include <fstream>
+#include <iostream>
+#include "Tuner.h"
+
+using namespace std;
+
+namespace aidl {
+namespace android {
+namespace hardware {
+namespace tv {
+namespace tuner {
+
+class Tuner;
+
+class Frontend : public BnFrontend {
+  public:
+    Frontend(FrontendType type, int32_t id, std::shared_ptr<Tuner> tuner);
+
+    ::ndk::ScopedAStatus setCallback(
+            const std::shared_ptr<IFrontendCallback>& in_callback) override;
+    ::ndk::ScopedAStatus tune(const FrontendSettings& in_settings) override;
+    ::ndk::ScopedAStatus stopTune() override;
+    ::ndk::ScopedAStatus close() override;
+    ::ndk::ScopedAStatus scan(const FrontendSettings& in_settings,
+                              FrontendScanType in_type) override;
+    ::ndk::ScopedAStatus stopScan() override;
+    ::ndk::ScopedAStatus getStatus(const std::vector<FrontendStatusType>& in_statusTypes,
+                                   std::vector<FrontendStatus>* _aidl_return) override;
+    ::ndk::ScopedAStatus setLnb(int32_t in_lnbId) override;
+    ::ndk::ScopedAStatus setLna(bool in_bEnable) override;
+    ::ndk::ScopedAStatus linkCiCam(int32_t in_ciCamId, int32_t* _aidl_return) override;
+    ::ndk::ScopedAStatus unlinkCiCam(int32_t in_ciCamId) override;
+
+    FrontendType getFrontendType();
+    int32_t getFrontendId();
+    string getSourceFile();
+    bool isLocked();
+
+  private:
+    virtual ~Frontend();
+    bool supportsSatellite();
+    std::shared_ptr<IFrontendCallback> mCallback;
+    std::shared_ptr<Tuner> mTunerService;
+    FrontendType mType = FrontendType::UNDEFINED;
+    int32_t mId = 0;
+    bool mIsLocked = false;
+    int32_t mCiCamId;
+
+    std::ifstream mFrontendData;
+};
+
+}  // namespace tuner
+}  // namespace tv
+}  // namespace hardware
+}  // namespace android
+}  // namespace aidl
diff --git a/tv/tuner/aidl/default/Lnb.cpp b/tv/tuner/aidl/default/Lnb.cpp
new file mode 100644
index 0000000..35d2da6
--- /dev/null
+++ b/tv/tuner/aidl/default/Lnb.cpp
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2021 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 "android.hardware.tv.tuner-service.example-Lnb"
+
+#include "Lnb.h"
+#include <utils/Log.h>
+
+namespace aidl {
+namespace android {
+namespace hardware {
+namespace tv {
+namespace tuner {
+
+Lnb::Lnb() {}
+
+Lnb::Lnb(int id) {
+    mId = id;
+}
+
+Lnb::~Lnb() {}
+
+::ndk::ScopedAStatus Lnb::setCallback(const std::shared_ptr<ILnbCallback>& /* in_callback */) {
+    ALOGV("%s", __FUNCTION__);
+
+    return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Lnb::setVoltage(LnbVoltage /* in_voltage */) {
+    ALOGV("%s", __FUNCTION__);
+
+    return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Lnb::setTone(LnbTone /* in_tone */) {
+    ALOGV("%s", __FUNCTION__);
+
+    return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Lnb::setSatellitePosition(LnbPosition /* in_position */) {
+    ALOGV("%s", __FUNCTION__);
+
+    return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Lnb::sendDiseqcMessage(const std::vector<uint8_t>& /* in_diseqcMessage */) {
+    ALOGV("%s", __FUNCTION__);
+
+    return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Lnb::close() {
+    ALOGV("%s", __FUNCTION__);
+
+    return ::ndk::ScopedAStatus::ok();
+}
+
+int Lnb::getId() {
+    return mId;
+}
+
+}  // namespace tuner
+}  // namespace tv
+}  // namespace hardware
+}  // namespace android
+}  // namespace aidl
diff --git a/tv/tuner/aidl/default/Lnb.h b/tv/tuner/aidl/default/Lnb.h
new file mode 100644
index 0000000..bfe3097
--- /dev/null
+++ b/tv/tuner/aidl/default/Lnb.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <aidl/android/hardware/tv/tuner/BnLnb.h>
+#include <aidl/android/hardware/tv/tuner/ITuner.h>
+
+using namespace std;
+
+namespace aidl {
+namespace android {
+namespace hardware {
+namespace tv {
+namespace tuner {
+
+class Lnb : public BnLnb {
+  public:
+    Lnb();
+    Lnb(int id);
+
+    ::ndk::ScopedAStatus setCallback(const std::shared_ptr<ILnbCallback>& in_callback) override;
+    ::ndk::ScopedAStatus setVoltage(LnbVoltage in_voltage) override;
+    ::ndk::ScopedAStatus setTone(LnbTone in_tone) override;
+    ::ndk::ScopedAStatus setSatellitePosition(LnbPosition in_position) override;
+    ::ndk::ScopedAStatus sendDiseqcMessage(const std::vector<uint8_t>& in_diseqcMessage) override;
+    ::ndk::ScopedAStatus close() override;
+
+    int getId();
+
+  private:
+    int mId;
+    virtual ~Lnb();
+};
+
+}  // namespace tuner
+}  // namespace tv
+}  // namespace hardware
+}  // namespace android
+}  // namespace aidl
diff --git a/tv/tuner/aidl/default/OWNERS b/tv/tuner/aidl/default/OWNERS
new file mode 100644
index 0000000..bf2b609
--- /dev/null
+++ b/tv/tuner/aidl/default/OWNERS
@@ -0,0 +1,3 @@
+hgchen@google.com
+shubang@google.com
+quxiangfang@google.com
diff --git a/tv/tuner/aidl/default/TimeFilter.cpp b/tv/tuner/aidl/default/TimeFilter.cpp
new file mode 100644
index 0000000..4fd8d21
--- /dev/null
+++ b/tv/tuner/aidl/default/TimeFilter.cpp
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2021 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 "android.hardware.tv.tuner-service.example-TimeFilter"
+
+#include "TimeFilter.h"
+#include <utils/Log.h>
+
+namespace aidl {
+namespace android {
+namespace hardware {
+namespace tv {
+namespace tuner {
+
+TimeFilter::TimeFilter() {}
+
+TimeFilter::TimeFilter(std::shared_ptr<Demux> demux) {
+    mDemux = demux;
+}
+
+TimeFilter::~TimeFilter() {}
+
+::ndk::ScopedAStatus TimeFilter::setTimeStamp(int64_t in_timeStamp) {
+    ALOGV("%s", __FUNCTION__);
+    if (in_timeStamp == INVALID_TIME_STAMP) {
+        return ::ndk::ScopedAStatus::fromExceptionCode(STATUS_INVALID_OPERATION);
+    }
+    mTimeStamp = in_timeStamp;
+    mBeginTime = time(NULL);
+
+    return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus TimeFilter::clearTimeStamp() {
+    ALOGV("%s", __FUNCTION__);
+    mTimeStamp = INVALID_TIME_STAMP;
+
+    return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus TimeFilter::getTimeStamp(int64_t* _aidl_return) {
+    ALOGV("%s", __FUNCTION__);
+    if (mTimeStamp == INVALID_TIME_STAMP) {
+        *_aidl_return = mTimeStamp;
+        return ::ndk::ScopedAStatus::fromExceptionCode(STATUS_INVALID_OPERATION);
+    }
+
+    uint64_t currentTimeStamp = mTimeStamp + difftime(time(NULL), mBeginTime) * 900000;
+    *_aidl_return = currentTimeStamp;
+
+    return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus TimeFilter::getSourceTime(int64_t* _aidl_return) {
+    ALOGV("%s", __FUNCTION__);
+
+    *_aidl_return = 0;
+    return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus TimeFilter::close() {
+    ALOGV("%s", __FUNCTION__);
+    mTimeStamp = INVALID_TIME_STAMP;
+
+    return ::ndk::ScopedAStatus::ok();
+}
+
+}  // namespace tuner
+}  // namespace tv
+}  // namespace hardware
+}  // namespace android
+}  // namespace aidl
diff --git a/tv/tuner/aidl/default/TimeFilter.h b/tv/tuner/aidl/default/TimeFilter.h
new file mode 100644
index 0000000..ff35c47
--- /dev/null
+++ b/tv/tuner/aidl/default/TimeFilter.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <aidl/android/hardware/tv/tuner/BnTimeFilter.h>
+#include "Demux.h"
+#include "time.h"
+
+using namespace std;
+
+namespace aidl {
+namespace android {
+namespace hardware {
+namespace tv {
+namespace tuner {
+
+#define INVALID_TIME_STAMP -1
+
+class Demux;
+
+class TimeFilter : public BnTimeFilter {
+  public:
+    TimeFilter();
+    TimeFilter(std::shared_ptr<Demux> demux);
+    ~TimeFilter();
+
+    ::ndk::ScopedAStatus setTimeStamp(int64_t in_timeStamp) override;
+    ::ndk::ScopedAStatus clearTimeStamp() override;
+    ::ndk::ScopedAStatus getTimeStamp(int64_t* _aidl_return) override;
+    ::ndk::ScopedAStatus getSourceTime(int64_t* _aidl_return) override;
+    ::ndk::ScopedAStatus close() override;
+
+  private:
+    std::shared_ptr<Demux> mDemux;
+    uint64_t mTimeStamp = INVALID_TIME_STAMP;
+    time_t mBeginTime;
+};
+
+}  // namespace tuner
+}  // namespace tv
+}  // namespace hardware
+}  // namespace android
+}  // namespace aidl
diff --git a/tv/tuner/aidl/default/Tuner.cpp b/tv/tuner/aidl/default/Tuner.cpp
new file mode 100644
index 0000000..dc0bd5e
--- /dev/null
+++ b/tv/tuner/aidl/default/Tuner.cpp
@@ -0,0 +1,347 @@
+/*
+ * Copyright 2021 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 "android.hardware.tv.tuner-service.example-Tuner"
+
+#include "Tuner.h"
+#include <utils/Log.h>
+#include "Demux.h"
+#include "Descrambler.h"
+#include "Frontend.h"
+#include "Lnb.h"
+
+namespace aidl {
+namespace android {
+namespace hardware {
+namespace tv {
+namespace tuner {
+
+Tuner::Tuner() {
+    // Static Frontends array to maintain local frontends information
+    // Array index matches their FrontendId in the default impl
+    mFrontendSize = 10;
+    mFrontends[0] = ndk::SharedRefBase::make<Frontend>(FrontendType::ISDBS, 0, ref<Tuner>());
+    mFrontends[1] = ndk::SharedRefBase::make<Frontend>(FrontendType::ATSC3, 1, ref<Tuner>());
+    mFrontends[2] = ndk::SharedRefBase::make<Frontend>(FrontendType::DVBC, 2, ref<Tuner>());
+    mFrontends[3] = ndk::SharedRefBase::make<Frontend>(FrontendType::DVBS, 3, ref<Tuner>());
+    mFrontends[4] = ndk::SharedRefBase::make<Frontend>(FrontendType::DVBT, 4, ref<Tuner>());
+    mFrontends[5] = ndk::SharedRefBase::make<Frontend>(FrontendType::ISDBT, 5, ref<Tuner>());
+    mFrontends[6] = ndk::SharedRefBase::make<Frontend>(FrontendType::ANALOG, 6, ref<Tuner>());
+    mFrontends[7] = ndk::SharedRefBase::make<Frontend>(FrontendType::ATSC, 7, ref<Tuner>());
+    mFrontends[8] = ndk::SharedRefBase::make<Frontend>(FrontendType::ISDBS3, 8, ref<Tuner>());
+    mFrontends[9] = ndk::SharedRefBase::make<Frontend>(FrontendType::DTMB, 9, ref<Tuner>());
+
+    vector<FrontendStatusType> statusCaps;
+
+    FrontendCapabilities capsIsdbs;
+    capsIsdbs.set<FrontendCapabilities::Tag::isdbsCaps>(FrontendIsdbsCapabilities());
+    mFrontendCaps[0] = capsIsdbs;
+    statusCaps = {
+            FrontendStatusType::DEMOD_LOCK,  FrontendStatusType::SNR,
+            FrontendStatusType::FEC,         FrontendStatusType::MODULATION,
+            FrontendStatusType::MODULATIONS, FrontendStatusType::ROLL_OFF,
+    };
+    mFrontendStatusCaps[0] = statusCaps;
+
+    FrontendCapabilities capsAtsc3;
+    capsAtsc3.set<FrontendCapabilities::Tag::atsc3Caps>(FrontendAtsc3Capabilities());
+    mFrontendCaps[1] = capsAtsc3;
+    statusCaps = {
+            FrontendStatusType::BER,
+            FrontendStatusType::PER,
+            FrontendStatusType::ATSC3_PLP_INFO,
+            FrontendStatusType::MODULATIONS,
+            FrontendStatusType::BERS,
+            FrontendStatusType::INTERLEAVINGS,
+            FrontendStatusType::BANDWIDTH,
+    };
+    mFrontendStatusCaps[1] = statusCaps;
+
+    FrontendCapabilities capsDvbc;
+    capsDvbc.set<FrontendCapabilities::Tag::dvbcCaps>(FrontendDvbcCapabilities());
+    mFrontendCaps[2] = capsDvbc;
+    statusCaps = {
+            FrontendStatusType::PRE_BER,       FrontendStatusType::SIGNAL_QUALITY,
+            FrontendStatusType::MODULATION,    FrontendStatusType::SPECTRAL,
+            FrontendStatusType::MODULATIONS,   FrontendStatusType::CODERATES,
+            FrontendStatusType::INTERLEAVINGS, FrontendStatusType::BANDWIDTH,
+    };
+    mFrontendStatusCaps[2] = statusCaps;
+
+    FrontendCapabilities capsDvbs;
+    capsDvbs.set<FrontendCapabilities::Tag::dvbsCaps>(FrontendDvbsCapabilities());
+    mFrontendCaps[3] = capsDvbs;
+    statusCaps = {
+            FrontendStatusType::SIGNAL_STRENGTH, FrontendStatusType::SYMBOL_RATE,
+            FrontendStatusType::MODULATION,      FrontendStatusType::MODULATIONS,
+            FrontendStatusType::ROLL_OFF,        FrontendStatusType::IS_MISO,
+    };
+    mFrontendStatusCaps[3] = statusCaps;
+
+    FrontendCapabilities capsDvbt;
+    capsDvbt.set<FrontendCapabilities::Tag::dvbtCaps>(FrontendDvbtCapabilities());
+    mFrontendCaps[4] = capsDvbt;
+    statusCaps = {
+            FrontendStatusType::EWBS,
+            FrontendStatusType::PLP_ID,
+            FrontendStatusType::HIERARCHY,
+            FrontendStatusType::MODULATIONS,
+            FrontendStatusType::BANDWIDTH,
+            FrontendStatusType::GUARD_INTERVAL,
+            FrontendStatusType::TRANSMISSION_MODE,
+            FrontendStatusType::T2_SYSTEM_ID,
+    };
+    mFrontendStatusCaps[4] = statusCaps;
+
+    FrontendCapabilities capsIsdbt;
+    FrontendIsdbtCapabilities isdbtCaps{
+            .modeCap = (int)FrontendIsdbtMode::MODE_1 | (int)FrontendIsdbtMode::MODE_2,
+            .bandwidthCap = (int)FrontendIsdbtBandwidth::BANDWIDTH_6MHZ,
+            .modulationCap = (int)FrontendIsdbtModulation::MOD_16QAM,
+            // ISDBT shares coderate and guard interval with DVBT
+            .coderateCap = (int)FrontendDvbtCoderate::CODERATE_4_5 |
+                           (int)FrontendDvbtCoderate::CODERATE_6_7,
+            .guardIntervalCap = (int)FrontendDvbtGuardInterval::INTERVAL_1_128,
+    };
+    capsIsdbt.set<FrontendCapabilities::Tag::isdbtCaps>(isdbtCaps);
+    mFrontendCaps[5] = capsIsdbt;
+    statusCaps = {
+            FrontendStatusType::AGC,
+            FrontendStatusType::LNA,
+            FrontendStatusType::MODULATION,
+            FrontendStatusType::MODULATIONS,
+            FrontendStatusType::BANDWIDTH,
+            FrontendStatusType::GUARD_INTERVAL,
+            FrontendStatusType::TRANSMISSION_MODE,
+            FrontendStatusType::ISDBT_SEGMENTS,
+    };
+    mFrontendStatusCaps[5] = statusCaps;
+
+    FrontendCapabilities capsAnalog;
+    capsAnalog.set<FrontendCapabilities::Tag::analogCaps>(FrontendAnalogCapabilities());
+    mFrontendCaps[6] = capsAnalog;
+    statusCaps = {
+            FrontendStatusType::LAYER_ERROR,
+            FrontendStatusType::MER,
+            FrontendStatusType::UEC,
+            FrontendStatusType::TS_DATA_RATES,
+    };
+    mFrontendStatusCaps[6] = statusCaps;
+
+    FrontendCapabilities capsAtsc;
+    capsAtsc.set<FrontendCapabilities::Tag::atscCaps>(FrontendAtscCapabilities());
+    mFrontendCaps[7] = capsAtsc;
+    statusCaps = {
+            FrontendStatusType::FREQ_OFFSET,
+            FrontendStatusType::RF_LOCK,
+            FrontendStatusType::MODULATIONS,
+            FrontendStatusType::IS_LINEAR,
+    };
+    mFrontendStatusCaps[7] = statusCaps;
+
+    FrontendCapabilities capsIsdbs3;
+    capsIsdbs3.set<FrontendCapabilities::Tag::isdbs3Caps>(FrontendIsdbs3Capabilities());
+    mFrontendCaps[8] = capsIsdbs3;
+    statusCaps = {
+            FrontendStatusType::DEMOD_LOCK,      FrontendStatusType::MODULATION,
+            FrontendStatusType::MODULATIONS,     FrontendStatusType::ROLL_OFF,
+            FrontendStatusType::IS_SHORT_FRAMES,
+    };
+    mFrontendStatusCaps[8] = statusCaps;
+
+    FrontendCapabilities capsDtmb;
+    capsDtmb.set<FrontendCapabilities::Tag::dtmbCaps>(FrontendDtmbCapabilities());
+    mFrontendCaps[9] = capsDtmb;
+    statusCaps = {
+            FrontendStatusType::MODULATIONS,       FrontendStatusType::INTERLEAVINGS,
+            FrontendStatusType::BANDWIDTH,         FrontendStatusType::GUARD_INTERVAL,
+            FrontendStatusType::TRANSMISSION_MODE,
+    };
+    mFrontendStatusCaps[9] = statusCaps;
+
+    mLnbs.resize(2);
+    mLnbs[0] = ndk::SharedRefBase::make<Lnb>(0);
+    mLnbs[1] = ndk::SharedRefBase::make<Lnb>(1);
+}
+
+Tuner::~Tuner() {}
+
+::ndk::ScopedAStatus Tuner::getFrontendIds(std::vector<int32_t>* _aidl_return) {
+    ALOGV("%s", __FUNCTION__);
+
+    _aidl_return->resize(mFrontendSize);
+    for (int i = 0; i < mFrontendSize; i++) {
+        (*_aidl_return)[i] = mFrontends[i]->getFrontendId();
+    }
+
+    return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Tuner::openFrontendById(int32_t in_frontendId,
+                                             std::shared_ptr<IFrontend>* _aidl_return) {
+    ALOGV("%s", __FUNCTION__);
+
+    if (in_frontendId >= mFrontendSize || in_frontendId < 0) {
+        ALOGW("[   WARN   ] Frontend with id %d isn't available", in_frontendId);
+        *_aidl_return = nullptr;
+        return ::ndk::ScopedAStatus::fromExceptionCode(STATUS_INVALID_OPERATION);
+    }
+
+    *_aidl_return = mFrontends[in_frontendId];
+    return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Tuner::openDemux(std::vector<int32_t>* out_demuxId,
+                                      std::shared_ptr<IDemux>* _aidl_return) {
+    ALOGV("%s", __FUNCTION__);
+
+    mLastUsedId += 1;
+    mDemuxes[mLastUsedId] = ndk::SharedRefBase::make<Demux>(mLastUsedId, ref<Tuner>());
+
+    out_demuxId->push_back(mLastUsedId);
+    *_aidl_return = mDemuxes[mLastUsedId];
+
+    return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Tuner::getDemuxCaps(DemuxCapabilities* _aidl_return) {
+    ALOGV("%s", __FUNCTION__);
+
+    // IP filter can be an MMTP filter's data source.
+    _aidl_return->linkCaps = {0x00, 0x00, 0x02, 0x00, 0x00};
+    // Support time filter testing
+    _aidl_return->bTimeFilter = true;
+
+    return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Tuner::openDescrambler(std::shared_ptr<IDescrambler>* _aidl_return) {
+    ALOGV("%s", __FUNCTION__);
+
+    *_aidl_return = ndk::SharedRefBase::make<Descrambler>();
+
+    return ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Tuner::getFrontendInfo(int32_t in_frontendId, FrontendInfo* _aidl_return) {
+    ALOGV("%s", __FUNCTION__);
+
+    if (in_frontendId >= mFrontendSize) {
+        return ::ndk::ScopedAStatus::fromExceptionCode(STATUS_INVALID_OPERATION);
+    }
+
+    // assign randomly selected values for testing.
+    *_aidl_return = {
+            .type = mFrontends[in_frontendId]->getFrontendType(),
+            .minFrequency = 139,
+            .maxFrequency = 1139,
+            .minSymbolRate = 45,
+            .maxSymbolRate = 1145,
+            .acquireRange = 30,
+            .exclusiveGroupId = 57,
+            .statusCaps = mFrontendStatusCaps[in_frontendId],
+            .frontendCaps = mFrontendCaps[in_frontendId],
+    };
+
+    return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Tuner::getLnbIds(std::vector<int32_t>* _aidl_return) {
+    ALOGV("%s", __FUNCTION__);
+
+    _aidl_return->resize(mLnbs.size());
+    for (int i = 0; i < mLnbs.size(); i++) {
+        (*_aidl_return)[i] = mLnbs[i]->getId();
+    }
+
+    return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Tuner::openLnbById(int32_t in_lnbId, std::shared_ptr<ILnb>* _aidl_return) {
+    ALOGV("%s", __FUNCTION__);
+
+    if (in_lnbId >= mLnbs.size()) {
+        *_aidl_return = nullptr;
+        return ::ndk::ScopedAStatus::fromExceptionCode(STATUS_INVALID_OPERATION);
+    }
+
+    *_aidl_return = mLnbs[in_lnbId];
+    return ::ndk::ScopedAStatus::ok();
+}
+
+std::shared_ptr<Frontend> Tuner::getFrontendById(int32_t frontendId) {
+    ALOGV("%s", __FUNCTION__);
+
+    return mFrontends[frontendId];
+}
+
+::ndk::ScopedAStatus Tuner::openLnbByName(const std::string& /* in_lnbName */,
+                                          std::vector<int32_t>* out_lnbId,
+                                          std::shared_ptr<ILnb>* _aidl_return) {
+    ALOGV("%s", __FUNCTION__);
+
+    out_lnbId->push_back(1234);
+    *_aidl_return = ndk::SharedRefBase::make<Lnb>();
+
+    return ::ndk::ScopedAStatus::ok();
+}
+
+void Tuner::setFrontendAsDemuxSource(int32_t frontendId, int32_t demuxId) {
+    mFrontendToDemux[frontendId] = demuxId;
+    if (mFrontends[frontendId] != nullptr && mFrontends[frontendId]->isLocked()) {
+        mDemuxes[demuxId]->startFrontendInputLoop();
+    }
+}
+
+void Tuner::removeDemux(int32_t demuxId) {
+    map<int32_t, int32_t>::iterator it;
+    for (it = mFrontendToDemux.begin(); it != mFrontendToDemux.end(); it++) {
+        if (it->second == demuxId) {
+            it = mFrontendToDemux.erase(it);
+            break;
+        }
+    }
+    mDemuxes.erase(demuxId);
+}
+
+void Tuner::removeFrontend(int32_t frontendId) {
+    mFrontendToDemux.erase(frontendId);
+}
+
+void Tuner::frontendStopTune(int32_t frontendId) {
+    map<int32_t, int32_t>::iterator it = mFrontendToDemux.find(frontendId);
+    int32_t demuxId;
+    if (it != mFrontendToDemux.end()) {
+        demuxId = it->second;
+        mDemuxes[demuxId]->stopFrontendInput();
+    }
+}
+
+void Tuner::frontendStartTune(int32_t frontendId) {
+    map<int32_t, int32_t>::iterator it = mFrontendToDemux.find(frontendId);
+    int32_t demuxId;
+    if (it != mFrontendToDemux.end()) {
+        demuxId = it->second;
+        mDemuxes[demuxId]->startFrontendInputLoop();
+    }
+}
+
+}  // namespace tuner
+}  // namespace tv
+}  // namespace hardware
+}  // namespace android
+}  // namespace aidl
diff --git a/tv/tuner/aidl/default/Tuner.h b/tv/tuner/aidl/default/Tuner.h
new file mode 100644
index 0000000..e69990d
--- /dev/null
+++ b/tv/tuner/aidl/default/Tuner.h
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <aidl/android/hardware/tv/tuner/BnTuner.h>
+#include <aidl/android/hardware/tv/tuner/FrontendCapabilities.h>
+
+#include <map>
+#include "Demux.h"
+#include "Frontend.h"
+#include "Lnb.h"
+
+using namespace std;
+
+namespace aidl {
+namespace android {
+namespace hardware {
+namespace tv {
+namespace tuner {
+
+class Frontend;
+class Demux;
+class Lnb;
+
+class Tuner : public BnTuner {
+  public:
+    Tuner();
+    virtual ~Tuner();
+
+    ::ndk::ScopedAStatus getFrontendIds(std::vector<int32_t>* _aidl_return) override;
+    ::ndk::ScopedAStatus openFrontendById(int32_t in_frontendId,
+                                          std::shared_ptr<IFrontend>* _aidl_return) override;
+    ::ndk::ScopedAStatus openDemux(std::vector<int32_t>* out_demuxId,
+                                   std::shared_ptr<IDemux>* _aidl_return) override;
+    ::ndk::ScopedAStatus getDemuxCaps(DemuxCapabilities* _aidl_return) override;
+    ::ndk::ScopedAStatus openDescrambler(std::shared_ptr<IDescrambler>* _aidl_return) override;
+    ::ndk::ScopedAStatus getFrontendInfo(int32_t in_frontendId,
+                                         FrontendInfo* _aidl_return) override;
+    ::ndk::ScopedAStatus getLnbIds(std::vector<int32_t>* _aidl_return) override;
+    ::ndk::ScopedAStatus openLnbById(int32_t in_lnbId,
+                                     std::shared_ptr<ILnb>* _aidl_return) override;
+    ::ndk::ScopedAStatus openLnbByName(const std::string& in_lnbName,
+                                       std::vector<int32_t>* out_lnbId,
+                                       std::shared_ptr<ILnb>* _aidl_return) override;
+
+    std::shared_ptr<Frontend> getFrontendById(int32_t frontendId);
+    void setFrontendAsDemuxSource(int32_t frontendId, int32_t demuxId);
+    void frontendStartTune(int32_t frontendId);
+    void frontendStopTune(int32_t frontendId);
+    void removeDemux(int32_t demuxId);
+    void removeFrontend(int32_t frontendId);
+
+  private:
+    // Static mFrontends array to maintain local frontends information
+    map<int32_t, std::shared_ptr<Frontend>> mFrontends;
+    map<int32_t, FrontendCapabilities> mFrontendCaps;
+    map<int32_t, vector<FrontendStatusType>> mFrontendStatusCaps;
+    map<int32_t, int32_t> mFrontendToDemux;
+    map<int32_t, std::shared_ptr<Demux>> mDemuxes;
+    // To maintain how many Frontends we have
+    int mFrontendSize;
+    // The last used demux id. Initial value is -1.
+    // First used id will be 0.
+    int32_t mLastUsedId = -1;
+    vector<std::shared_ptr<Lnb>> mLnbs;
+};
+
+}  // namespace tuner
+}  // namespace tv
+}  // namespace hardware
+}  // namespace android
+}  // namespace aidl
diff --git a/tv/tuner/aidl/default/service.cpp b/tv/tuner/aidl/default/service.cpp
new file mode 100644
index 0000000..649b763
--- /dev/null
+++ b/tv/tuner/aidl/default/service.cpp
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2021 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 "android.hardware.tv.tuner-service.example"
+
+#include <android-base/logging.h>
+#include <android/binder_manager.h>
+#include <android/binder_process.h>
+
+#include "Tuner.h"
+
+using ::aidl::android::hardware::tv::tuner::Tuner;
+
+int main() {
+    ABinderProcess_setThreadPoolMaxThreadCount(8);
+    std::shared_ptr<Tuner> tuner = ndk::SharedRefBase::make<Tuner>();
+
+    const std::string instance = std::string() + Tuner::descriptor + "/default";
+    binder_status_t status = AServiceManager_addService(tuner->asBinder().get(), instance.c_str());
+    CHECK(status == STATUS_OK);
+
+    ABinderProcess_joinThreadPool();
+    return EXIT_FAILURE;  // should not reached
+}
diff --git a/tv/tuner/aidl/default/tuner-default.rc b/tv/tuner/aidl/default/tuner-default.rc
new file mode 100644
index 0000000..fa09456
--- /dev/null
+++ b/tv/tuner/aidl/default/tuner-default.rc
@@ -0,0 +1,6 @@
+service vendor.tuner-default /vendor/bin/hw/android.hardware.tv.tuner-service.example
+    class hal
+    user media
+    group mediadrm drmrpc
+    ioprio rt 4
+    writepid /dev/cpuset/foreground/tasks
diff --git a/tv/tuner/aidl/default/tuner-default.xml b/tv/tuner/aidl/default/tuner-default.xml
new file mode 100644
index 0000000..f0d03ad
--- /dev/null
+++ b/tv/tuner/aidl/default/tuner-default.xml
@@ -0,0 +1,6 @@
+<manifest version="1.0" type="device">
+    <hal format="aidl">
+        <name>android.hardware.tv.tuner</name>
+        <fqname>ITuner/default</fqname>
+    </hal>
+</manifest>