Merge "Add VTS for FilterDelayHint"
diff --git a/tv/tuner/aidl/vts/functional/FilterTests.cpp b/tv/tuner/aidl/vts/functional/FilterTests.cpp
index a5acdc1..53afef7 100644
--- a/tv/tuner/aidl/vts/functional/FilterTests.cpp
+++ b/tv/tuner/aidl/vts/functional/FilterTests.cpp
@@ -17,6 +17,7 @@
 #include "FilterTests.h"
 
 #include <inttypes.h>
+#include <algorithm>
 
 #include <aidl/android/hardware/tv/tuner/DemuxFilterMonitorEventType.h>
 #include <aidlcommonsupport/NativeHandle.h>
@@ -31,23 +32,24 @@
     mPidFilterOutputCount++;
     mMsgCondition.signal();
 
-    // HACK: we need to cast the const away as DemuxFilterEvent contains a ScopedFileDescriptor
-    // that cannot be copied.
-    for (auto&& e : const_cast<std::vector<DemuxFilterEvent>&>(events)) {
-        auto it = mFilterEventPromises.find(e.getTag());
-        if (it != mFilterEventPromises.cend()) {
-            it->second.set_value(std::move(e));
-            mFilterEventPromises.erase(it);
+    for (auto it = mFilterCallbackVerifiers.begin(); it != mFilterCallbackVerifiers.end();) {
+        auto& [verifier, promise] = *it;
+        if (verifier(events)) {
+            promise.set_value();
+            it = mFilterCallbackVerifiers.erase(it);
+        } else {
+            ++it;
         }
-    }
+    };
 
     return ::ndk::ScopedAStatus::ok();
 }
 
-std::future<DemuxFilterEvent> FilterCallback::getNextFilterEventWithTag(DemuxFilterEvent::Tag tag) {
-    // Note: this currently only supports one future per DemuxFilterEvent::Tag.
-    mFilterEventPromises[tag] = std::promise<DemuxFilterEvent>();
-    return mFilterEventPromises[tag].get_future();
+std::future<void> FilterCallback::verifyFilterCallback(FilterCallbackVerifier&& verifier) {
+    std::promise<void> promise;
+    auto future = promise.get_future();
+    mFilterCallbackVerifiers.emplace_back(std::move(verifier), std::move(promise));
+    return future;
 }
 
 void FilterCallback::testFilterDataOutput() {
diff --git a/tv/tuner/aidl/vts/functional/FilterTests.h b/tv/tuner/aidl/vts/functional/FilterTests.h
index 6258bac..f579441 100644
--- a/tv/tuner/aidl/vts/functional/FilterTests.h
+++ b/tv/tuner/aidl/vts/functional/FilterTests.h
@@ -60,9 +60,18 @@
 
 class FilterCallback : public BnFilterCallback {
   public:
+    /**
+     * A FilterCallbackVerifier is used to test and verify filter callbacks.
+     * The function should return true when a callback has been handled by this
+     * filter verifier. This will cause the associated future to be unblocked.
+     * If the function returns false, we continue to wait for future callbacks
+     * (the future remains blocked).
+     */
+    using FilterCallbackVerifier = std::function<bool(const std::vector<DemuxFilterEvent>&)>;
+
     virtual ::ndk::ScopedAStatus onFilterEvent(const vector<DemuxFilterEvent>& events) override;
 
-    std::future<DemuxFilterEvent> getNextFilterEventWithTag(DemuxFilterEvent::Tag tag);
+    std::future<void> verifyFilterCallback(FilterCallbackVerifier&& verifier);
 
     virtual ::ndk::ScopedAStatus onFilterStatus(const DemuxFilterStatus /*status*/) override {
         return ::ndk::ScopedAStatus::ok();
@@ -85,7 +94,7 @@
     int32_t mFilterId;
     std::shared_ptr<IFilter> mFilter;
 
-    std::unordered_map<DemuxFilterEvent::Tag, std::promise<DemuxFilterEvent>> mFilterEventPromises;
+    std::vector<std::pair<FilterCallbackVerifier, std::promise<void>>> mFilterCallbackVerifiers;
     native_handle_t* mAvSharedHandle = nullptr;
     uint64_t mAvSharedMemSize = -1;
 
diff --git a/tv/tuner/aidl/vts/functional/VtsHalTvTunerTargetTest.cpp b/tv/tuner/aidl/vts/functional/VtsHalTvTunerTargetTest.cpp
index 88890e4..89e42df 100644
--- a/tv/tuner/aidl/vts/functional/VtsHalTvTunerTargetTest.cpp
+++ b/tv/tuner/aidl/vts/functional/VtsHalTvTunerTargetTest.cpp
@@ -640,10 +640,51 @@
     testTimeFilter(timeFilterMap[timeFilter.timeFilterId]);
 }
 
-// TODO: move boilerplate into text fixture
-TEST_P(TunerFilterAidlTest, FilterTimeDelayHintTest) {
-    description("Test filter delay hint.");
+static bool isMediaFilter(const FilterConfig& filterConfig) {
+    switch (filterConfig.type.mainType) {
+        case DemuxFilterMainType::TS: {
+            // TS Audio and Video filters are media filters
+            auto tsFilterType =
+                    filterConfig.type.subType.get<DemuxFilterSubType::Tag::tsFilterType>();
+            return (tsFilterType == DemuxTsFilterType::AUDIO ||
+                    tsFilterType == DemuxTsFilterType::VIDEO);
+        }
+        case DemuxFilterMainType::MMTP: {
+            // MMTP Audio and Video filters are media filters
+            auto mmtpFilterType =
+                    filterConfig.type.subType.get<DemuxFilterSubType::Tag::mmtpFilterType>();
+            return (mmtpFilterType == DemuxMmtpFilterType::AUDIO ||
+                    mmtpFilterType == DemuxMmtpFilterType::VIDEO);
+        }
+        default:
+            return false;
+    }
+}
 
+static int getDemuxFilterEventDataLength(const DemuxFilterEvent& event) {
+    switch (event.getTag()) {
+        case DemuxFilterEvent::Tag::section:
+            return event.get<DemuxFilterEvent::Tag::section>().dataLength;
+        case DemuxFilterEvent::Tag::media:
+            return event.get<DemuxFilterEvent::Tag::media>().dataLength;
+        case DemuxFilterEvent::Tag::pes:
+            return event.get<DemuxFilterEvent::Tag::pes>().dataLength;
+        case DemuxFilterEvent::Tag::download:
+            return event.get<DemuxFilterEvent::Tag::download>().dataLength;
+        case DemuxFilterEvent::Tag::ipPayload:
+            return event.get<DemuxFilterEvent::Tag::ipPayload>().dataLength;
+
+        case DemuxFilterEvent::Tag::tsRecord:
+        case DemuxFilterEvent::Tag::mmtpRecord:
+        case DemuxFilterEvent::Tag::temi:
+        case DemuxFilterEvent::Tag::monitorEvent:
+        case DemuxFilterEvent::Tag::startId:
+            return 0;
+    }
+}
+
+// TODO: move boilerplate into text fixture
+void TunerFilterAidlTest::testDelayHint(const FilterConfig& filterConf) {
     int32_t feId;
     int32_t demuxId;
     std::shared_ptr<IDemux> demux;
@@ -657,40 +698,87 @@
     ASSERT_TRUE(mDemuxTests.setDemuxFrontendDataSource(feId));
     mFilterTests.setDemux(demux);
 
-    const auto& filterConf = filterMap[live.ipFilterId];
     ASSERT_TRUE(mFilterTests.openFilterInDemux(filterConf.type, filterConf.bufferSize));
     ASSERT_TRUE(mFilterTests.getNewlyOpenedFilterId_64bit(filterId));
-    ASSERT_TRUE(mFilterTests.configFilter(filterConf.settings, filterId));
-    ASSERT_TRUE(mFilterTests.configIpFilterCid(filterConf.ipCid, filterId));
 
+    bool mediaFilter = isMediaFilter(filterConf);
     auto filter = mFilterTests.getFilterById(filterId);
 
-    auto delayValue = std::chrono::milliseconds(5000);
-    FilterDelayHint delayHint;
-    delayHint.hintType = FilterDelayHintType::TIME_DELAY_IN_MS;
-    delayHint.hintValue = delayValue.count();
+    auto timeDelayInMs = std::chrono::milliseconds(filterConf.timeDelayInMs);
+    if (timeDelayInMs.count() > 0) {
+        FilterDelayHint delayHint;
+        delayHint.hintType = FilterDelayHintType::TIME_DELAY_IN_MS;
+        delayHint.hintValue = timeDelayInMs.count();
 
-    auto status = filter->setDelayHint(delayHint);
-    ASSERT_TRUE(status.isOk());
+        // setDelayHint should fail for media filters.
+        ASSERT_EQ(filter->setDelayHint(delayHint).isOk(), !mediaFilter);
+    }
 
-    auto startTime = std::chrono::steady_clock::now();
-    ASSERT_TRUE(mFilterTests.startFilter(filterId));
+    int dataDelayInBytes = filterConf.dataDelayInBytes;
+    if (dataDelayInBytes > 0) {
+        FilterDelayHint delayHint;
+        delayHint.hintType = FilterDelayHintType::DATA_SIZE_DELAY_IN_BYTES;
+        delayHint.hintValue = dataDelayInBytes;
 
-    auto cb = mFilterTests.getFilterCallbacks().at(filterId);
-    auto future = cb->getNextFilterEventWithTag(DemuxFilterEvent::Tag::ipPayload);
+        // setDelayHint should fail for media filters.
+        ASSERT_EQ(filter->setDelayHint(delayHint).isOk(), !mediaFilter);
+    }
 
-    // block and wait for callback to be received.
-    ASSERT_EQ(future.wait_for(std::chrono::seconds(10)), std::future_status::ready);
-    auto duration = std::chrono::steady_clock::now() - startTime;
-    ASSERT_GE(duration, delayValue);
+    // start and stop filter in order to circumvent callback scheduler race
+    // conditions after adjusting filter delays.
+    mFilterTests.startFilter(filterId);
+    mFilterTests.stopFilter(filterId);
 
-    // cleanup
-    ASSERT_TRUE(mFilterTests.stopFilter(filterId));
+    if (!mediaFilter) {
+        auto cb = mFilterTests.getFilterCallbacks().at(filterId);
+        int callbackSize = 0;
+        auto future = cb->verifyFilterCallback(
+                [&callbackSize](const std::vector<DemuxFilterEvent>& events) {
+                    for (const auto& event : events) {
+                        callbackSize += getDemuxFilterEventDataLength(event);
+                    }
+                    return true;
+                });
+
+        // The configure stage can also produce events, so we should set the delay
+        // hint beforehand.
+        ASSERT_TRUE(mFilterTests.configFilter(filterConf.settings, filterId));
+
+        auto startTime = std::chrono::steady_clock::now();
+        ASSERT_TRUE(mFilterTests.startFilter(filterId));
+
+        // block and wait for callback to be received.
+        auto timeout = std::chrono::seconds(30);
+        ASSERT_EQ(future.wait_for(timeout), std::future_status::ready);
+        auto duration = std::chrono::steady_clock::now() - startTime;
+
+        bool delayHintTest = duration >= timeDelayInMs;
+        bool dataSizeTest = callbackSize >= dataDelayInBytes;
+
+        if (timeDelayInMs.count() > 0 && dataDelayInBytes > 0) {
+            ASSERT_TRUE(delayHintTest || dataSizeTest);
+        } else {
+            // if only one of time delay / data delay is configured, one of them
+            // holds true by default, so we want both assertions to be true.
+            ASSERT_TRUE(delayHintTest && dataSizeTest);
+        }
+
+        ASSERT_TRUE(mFilterTests.stopFilter(filterId));
+    }
+
     ASSERT_TRUE(mFilterTests.closeFilter(filterId));
     ASSERT_TRUE(mDemuxTests.closeDemux());
     ASSERT_TRUE(mFrontendTests.closeFrontend());
 }
 
+TEST_P(TunerFilterAidlTest, FilterDelayHintTest) {
+    description("Test filter time delay hint.");
+
+    for (const auto& obj : filterMap) {
+        testDelayHint(obj.second);
+    }
+}
+
 TEST_P(TunerPlaybackAidlTest, PlaybackDataFlowWithTsSectionFilterTest) {
     description("Feed ts data from playback and configure Ts section filter to get output");
     if (!playback.support || playback.sectionFilterId.compare(emptyHardwareId) == 0) {
diff --git a/tv/tuner/aidl/vts/functional/VtsHalTvTunerTargetTest.h b/tv/tuner/aidl/vts/functional/VtsHalTvTunerTargetTest.h
index 13c5a80..7f80d90 100644
--- a/tv/tuner/aidl/vts/functional/VtsHalTvTunerTargetTest.h
+++ b/tv/tuner/aidl/vts/functional/VtsHalTvTunerTargetTest.h
@@ -140,6 +140,7 @@
     void reconfigSingleFilterInDemuxTest(FilterConfig filterConf, FilterConfig filterReconf,
                                          FrontendConfig frontendConf);
     void testTimeFilter(TimeFilterConfig filterConf);
+    void testDelayHint(const FilterConfig& filterConf);
 
     DemuxFilterType getLinkageFilterType(int bit) {
         DemuxFilterType type;
diff --git a/tv/tuner/config/TunerTestingConfigAidlReaderV1_0.h b/tv/tuner/config/TunerTestingConfigAidlReaderV1_0.h
index 7ccf31a..b6cc5f8 100644
--- a/tv/tuner/config/TunerTestingConfigAidlReaderV1_0.h
+++ b/tv/tuner/config/TunerTestingConfigAidlReaderV1_0.h
@@ -96,6 +96,8 @@
     AvStreamType streamType;
     int32_t ipCid;
     int32_t monitorEventTypes;
+    int timeDelayInMs = 0;
+    int dataDelayInBytes = 0;
 
     bool operator<(const FilterConfig& /*c*/) const { return false; }
 };
@@ -336,6 +338,12 @@
                 if (filterConfig.hasMonitorEventTypes()) {
                     filterMap[id].monitorEventTypes = (int32_t)filterConfig.getMonitorEventTypes();
                 }
+                if (filterConfig.hasTimeDelayInMs()) {
+                    filterMap[id].timeDelayInMs = filterConfig.getTimeDelayInMs();
+                }
+                if (filterConfig.hasDataDelayInBytes()) {
+                    filterMap[id].dataDelayInBytes = filterConfig.getDataDelayInBytes();
+                }
                 if (filterConfig.hasAvFilterSettings_optional()) {
                     auto av = filterConfig.getFirstAvFilterSettings_optional();
                     if (av->hasAudioStreamType_optional()) {
diff --git a/tv/tuner/config/api/current.txt b/tv/tuner/config/api/current.txt
index 6c637a4..db1d076 100644
--- a/tv/tuner/config/api/current.txt
+++ b/tv/tuner/config/api/current.txt
@@ -256,6 +256,7 @@
     ctor public Filter();
     method @Nullable public android.media.tuner.testing.configuration.V1_0.AvFilterSettings getAvFilterSettings_optional();
     method @Nullable public java.math.BigInteger getBufferSize();
+    method @Nullable public java.math.BigInteger getDataDelayInBytes();
     method @Nullable public String getId();
     method @Nullable public android.media.tuner.testing.configuration.V1_0.IpFilterConfig getIpFilterConfig_optional();
     method @Nullable public android.media.tuner.testing.configuration.V1_0.FilterMainTypeEnum getMainType();
@@ -264,9 +265,11 @@
     method @Nullable public android.media.tuner.testing.configuration.V1_0.RecordFilterSettings getRecordFilterSettings_optional();
     method @Nullable public android.media.tuner.testing.configuration.V1_0.SectionFilterSettings getSectionFilterSettings_optional();
     method @Nullable public android.media.tuner.testing.configuration.V1_0.FilterSubTypeEnum getSubType();
+    method @Nullable public java.math.BigInteger getTimeDelayInMs();
     method @Nullable public boolean getUseFMQ();
     method public void setAvFilterSettings_optional(@Nullable android.media.tuner.testing.configuration.V1_0.AvFilterSettings);
     method public void setBufferSize(@Nullable java.math.BigInteger);
+    method public void setDataDelayInBytes(@Nullable java.math.BigInteger);
     method public void setId(@Nullable String);
     method public void setIpFilterConfig_optional(@Nullable android.media.tuner.testing.configuration.V1_0.IpFilterConfig);
     method public void setMainType(@Nullable android.media.tuner.testing.configuration.V1_0.FilterMainTypeEnum);
@@ -275,6 +278,7 @@
     method public void setRecordFilterSettings_optional(@Nullable android.media.tuner.testing.configuration.V1_0.RecordFilterSettings);
     method public void setSectionFilterSettings_optional(@Nullable android.media.tuner.testing.configuration.V1_0.SectionFilterSettings);
     method public void setSubType(@Nullable android.media.tuner.testing.configuration.V1_0.FilterSubTypeEnum);
+    method public void setTimeDelayInMs(@Nullable java.math.BigInteger);
     method public void setUseFMQ(@Nullable boolean);
   }
 
diff --git a/tv/tuner/config/sample_tuner_vts_config_aidl_V1.xml b/tv/tuner/config/sample_tuner_vts_config_aidl_V1.xml
index 8a6229e..da77200 100644
--- a/tv/tuner/config/sample_tuner_vts_config_aidl_V1.xml
+++ b/tv/tuner/config/sample_tuner_vts_config_aidl_V1.xml
@@ -101,7 +101,7 @@
                     bufferSize="16777216" pid="257" useFMQ="false">
                 <recordFilterSettings tsIndexMask="1" scIndexType="NONE"/>
             </filter>
-            <filter id="FILTER_IP_IP_0" mainType="IP" subType="IP" bufferSize="16777216" useFMQ="false">
+            <filter id="FILTER_IP_IP_0" mainType="IP" subType="IP" bufferSize="16777216" useFMQ="false" timeDelayInMs="5000">
                 <ipFilterConfig ipCid="1">
                     <srcIpAddress isIpV4="true" ip="192 168 1 1"/>
                     <destIpAddress isIpV4="true" ip="192 168 1 1"/>
diff --git a/tv/tuner/config/tuner_testing_dynamic_configuration.xsd b/tv/tuner/config/tuner_testing_dynamic_configuration.xsd
index fc2827f..54cedfc 100644
--- a/tv/tuner/config/tuner_testing_dynamic_configuration.xsd
+++ b/tv/tuner/config/tuner_testing_dynamic_configuration.xsd
@@ -243,6 +243,10 @@
                     "bufferSize": the buffer size of the filter in hex.
                     "pid": the pid that would be used to configure the filter.
                     "useFMQ": if the filter uses FMQ.
+                    "timeDelayInMs": the filter's time delay hint. 0 by default.  Must not be
+                        longer than 30 seconds.
+                    "dataDelayInBytes": the filter's data delay hint. 0 by default. Configured data
+                        size must be received within 30 seconds.
 
                 Each filter element also contains at most one type-related "filterSettings".
                     - The settings type should match the filter "subType" attribute.
@@ -274,6 +278,8 @@
         <xs:attribute name="pid" type="xs:nonNegativeInteger" use="optional"/>
         <xs:attribute name="useFMQ" type="xs:boolean" use="required"/>
         <xs:attribute name="monitorEventTypes" type="monitoEvents" use="optional"/>
+        <xs:attribute name="timeDelayInMs" type="xs:nonNegativeInteger" use="optional"/>
+        <xs:attribute name="dataDelayInBytes" type="xs:nonNegativeInteger" use="optional"/>
     </xs:complexType>
 
     <!-- DVR SESSION -->