Allow users to switch between FE source and DVR source when testing
record/descrambling/broadcast

Test: atest VtsHalTvTunerV1_0TargetTest
Test: atest VtsHalTvTunerV1_1TargetTest
Bug: 182519645
CTS-Coverage-Bug: 184077478
Change-Id: I6c57657ac3539d6a6fb3f63d2ecc9af7f6b9e2dc
Merged-In: I6c57657ac3539d6a6fb3f63d2ecc9af7f6b9e2dc
diff --git a/tv/tuner/1.0/vts/functional/VtsHalTvTunerV1_0TargetTest.cpp b/tv/tuner/1.0/vts/functional/VtsHalTvTunerV1_0TargetTest.cpp
index 4c92665..62093cc 100644
--- a/tv/tuner/1.0/vts/functional/VtsHalTvTunerV1_0TargetTest.cpp
+++ b/tv/tuner/1.0/vts/functional/VtsHalTvTunerV1_0TargetTest.cpp
@@ -56,9 +56,6 @@
 }
 
 void TunerFilterHidlTest::testTimeFilter(TimeFilterConfig filterConf) {
-    if (!timeFilter.support) {
-        return;
-    }
     uint32_t demuxId;
     sp<IDemux> demux;
     DemuxCapabilities caps;
@@ -161,27 +158,36 @@
 
 void TunerRecordHidlTest::recordSingleFilterTest(FilterConfig filterConf,
                                                  FrontendConfig frontendConf, DvrConfig dvrConf) {
-    uint32_t feId;
     uint32_t demuxId;
     sp<IDemux> demux;
+    ASSERT_TRUE(mDemuxTests.openDemux(demux, demuxId));
+    mDvrTests.setDemux(demux);
+
+    DvrConfig dvrSourceConfig;
+    if (mLnbId || record.hasFrontendConnection) {
+        uint32_t feId;
+        mFrontendTests.getFrontendIdByType(frontendConf.type, feId);
+        ASSERT_TRUE(feId != INVALID_ID);
+        ASSERT_TRUE(mFrontendTests.openFrontendById(feId));
+        ASSERT_TRUE(mFrontendTests.setFrontendCallback());
+        if (mLnbId) {
+            ASSERT_TRUE(mFrontendTests.setLnb(*mLnbId));
+        }
+        if (frontendConf.isSoftwareFe) {
+            mFrontendTests.setSoftwareFrontendDvrConfig(dvrMap[record.dvrSoftwareFeId]);
+        }
+        ASSERT_TRUE(mDemuxTests.setDemuxFrontendDataSource(feId));
+        mFrontendTests.setDvrTests(mDvrTests);
+    } else {
+        dvrSourceConfig = dvrMap[record.dvrSourceId];
+        ASSERT_TRUE(mDvrTests.openDvrInDemux(dvrSourceConfig.type, dvrSourceConfig.bufferSize));
+        ASSERT_TRUE(mDvrTests.configDvrPlayback(dvrSourceConfig.settings));
+        ASSERT_TRUE(mDvrTests.getDvrPlaybackMQDescriptor());
+    }
+
     uint32_t filterId;
     sp<IFilter> filter;
-
-    mFrontendTests.getFrontendIdByType(frontendConf.type, feId);
-    ASSERT_TRUE(feId != INVALID_ID);
-    ASSERT_TRUE(mFrontendTests.openFrontendById(feId));
-    ASSERT_TRUE(mFrontendTests.setFrontendCallback());
-    if (mLnbId) {
-        ASSERT_TRUE(mFrontendTests.setLnb(*mLnbId));
-    }
-    if (frontendConf.isSoftwareFe) {
-        mFrontendTests.setSoftwareFrontendDvrConfig(dvrMap[record.dvrSoftwareFeId]);
-    }
-    ASSERT_TRUE(mDemuxTests.openDemux(demux, demuxId));
-    ASSERT_TRUE(mDemuxTests.setDemuxFrontendDataSource(feId));
     mFilterTests.setDemux(demux);
-    mDvrTests.setDemux(demux);
-    mFrontendTests.setDvrTests(mDvrTests);
     ASSERT_TRUE(mDvrTests.openDvrInDemux(dvrConf.type, dvrConf.bufferSize));
     ASSERT_TRUE(mDvrTests.configDvrRecord(dvrConf.settings));
     ASSERT_TRUE(mDvrTests.getDvrRecordMQDescriptor());
@@ -195,17 +201,39 @@
     ASSERT_TRUE(mDvrTests.attachFilterToDvr(filter));
     ASSERT_TRUE(mDvrTests.startDvrRecord());
     ASSERT_TRUE(mFilterTests.startFilter(filterId));
-    ASSERT_TRUE(mFrontendTests.tuneFrontend(frontendConf, true /*testWithDemux*/));
+
+    if (mLnbId || record.hasFrontendConnection) {
+        ASSERT_TRUE(mFrontendTests.tuneFrontend(frontendConf, true /*testWithDemux*/));
+    } else {
+        // Start DVR Source
+        mDvrTests.startPlaybackInputThread(dvrSourceConfig.playbackInputFile,
+                                           dvrSourceConfig.settings.playback());
+        ASSERT_TRUE(mDvrTests.startDvrPlayback());
+    }
+
     mDvrTests.testRecordOutput();
     mDvrTests.stopRecordThread();
-    ASSERT_TRUE(mFrontendTests.stopTuneFrontend(true /*testWithDemux*/));
+
+    if (mLnbId || record.hasFrontendConnection) {
+        ASSERT_TRUE(mFrontendTests.stopTuneFrontend(true /*testWithDemux*/));
+    } else {
+        mDvrTests.stopPlaybackThread();
+        ASSERT_TRUE(mDvrTests.stopDvrPlayback());
+    }
+
     ASSERT_TRUE(mFilterTests.stopFilter(filterId));
     ASSERT_TRUE(mDvrTests.stopDvrRecord());
     ASSERT_TRUE(mDvrTests.detachFilterToDvr(filter));
     ASSERT_TRUE(mFilterTests.closeFilter(filterId));
     mDvrTests.closeDvrRecord();
+
+    if (mLnbId || record.hasFrontendConnection) {
+        ASSERT_TRUE(mFrontendTests.closeFrontend());
+    } else {
+        mDvrTests.closeDvrPlayback();
+    }
+
     ASSERT_TRUE(mDemuxTests.closeDemux());
-    ASSERT_TRUE(mFrontendTests.closeFrontend());
 }
 
 void TunerRecordHidlTest::recordSingleFilterTestWithLnb(FilterConfig filterConf,
@@ -236,23 +264,28 @@
 void TunerRecordHidlTest::attachSingleFilterToRecordDvrTest(FilterConfig filterConf,
                                                             FrontendConfig frontendConf,
                                                             DvrConfig dvrConf) {
-    uint32_t feId;
     uint32_t demuxId;
     sp<IDemux> demux;
+    ASSERT_TRUE(mDemuxTests.openDemux(demux, demuxId));
+
+    if (record.hasFrontendConnection) {
+        uint32_t feId;
+        mFrontendTests.getFrontendIdByType(frontendConf.type, feId);
+        ASSERT_TRUE(feId != INVALID_ID);
+        ASSERT_TRUE(mFrontendTests.openFrontendById(feId));
+        ASSERT_TRUE(mFrontendTests.setFrontendCallback());
+        ASSERT_TRUE(mDemuxTests.setDemuxFrontendDataSource(feId));
+    }
+
     uint32_t filterId;
     sp<IFilter> filter;
-
-    mFrontendTests.getFrontendIdByType(frontendConf.type, feId);
-    ASSERT_TRUE(feId != INVALID_ID);
-    ASSERT_TRUE(mFrontendTests.openFrontendById(feId));
-    ASSERT_TRUE(mFrontendTests.setFrontendCallback());
-    ASSERT_TRUE(mDemuxTests.openDemux(demux, demuxId));
-    ASSERT_TRUE(mDemuxTests.setDemuxFrontendDataSource(feId));
     mFilterTests.setDemux(demux);
+
     mDvrTests.setDemux(demux);
     ASSERT_TRUE(mDvrTests.openDvrInDemux(dvrConf.type, dvrConf.bufferSize));
     ASSERT_TRUE(mDvrTests.configDvrRecord(dvrConf.settings));
     ASSERT_TRUE(mDvrTests.getDvrRecordMQDescriptor());
+
     ASSERT_TRUE(mFilterTests.openFilterInDemux(filterConf.type, filterConf.bufferSize));
     ASSERT_TRUE(mFilterTests.getNewlyOpenedFilterId(filterId));
     ASSERT_TRUE(mFilterTests.configFilter(filterConf.settings, filterId));
@@ -268,30 +301,42 @@
     ASSERT_TRUE(mFilterTests.closeFilter(filterId));
     mDvrTests.closeDvrRecord();
     ASSERT_TRUE(mDemuxTests.closeDemux());
-    ASSERT_TRUE(mFrontendTests.closeFrontend());
+
+    if (record.hasFrontendConnection) {
+        ASSERT_TRUE(mFrontendTests.closeFrontend());
+    }
 }
 
 void TunerDescramblerHidlTest::scrambledBroadcastTest(set<struct FilterConfig> mediaFilterConfs,
                                                       FrontendConfig frontendConf,
                                                       DescramblerConfig descConfig) {
-    uint32_t feId;
     uint32_t demuxId;
     sp<IDemux> demux;
+    ASSERT_TRUE(mDemuxTests.openDemux(demux, demuxId));
+
+    DvrConfig dvrSourceConfig;
+    if (descrambling.hasFrontendConnection) {
+        uint32_t feId;
+        mFrontendTests.getFrontendIdByType(frontendConf.type, feId);
+        ASSERT_TRUE(mFrontendTests.openFrontendById(feId));
+        ASSERT_TRUE(mFrontendTests.setFrontendCallback());
+        if (frontendConf.isSoftwareFe) {
+            mFrontendTests.setSoftwareFrontendDvrConfig(dvrMap[descrambling.dvrSoftwareFeId]);
+        }
+        ASSERT_TRUE(mDemuxTests.setDemuxFrontendDataSource(feId));
+        mFrontendTests.setDemux(demux);
+    } else {
+        dvrSourceConfig = dvrMap[descrambling.dvrSourceId];
+        ASSERT_TRUE(mDvrTests.openDvrInDemux(dvrSourceConfig.type, dvrSourceConfig.bufferSize));
+        ASSERT_TRUE(mDvrTests.configDvrPlayback(dvrSourceConfig.settings));
+        ASSERT_TRUE(mDvrTests.getDvrPlaybackMQDescriptor());
+    }
+
     set<uint32_t> filterIds;
     uint32_t filterId;
     set<struct FilterConfig>::iterator config;
     set<uint32_t>::iterator id;
-
-    mFrontendTests.getFrontendIdByType(frontendConf.type, feId);
-    ASSERT_TRUE(mFrontendTests.openFrontendById(feId));
-    ASSERT_TRUE(mFrontendTests.setFrontendCallback());
-    if (frontendConf.isSoftwareFe) {
-        mFrontendTests.setSoftwareFrontendDvrConfig(dvrMap[descrambling.dvrSoftwareFeId]);
-    }
-    ASSERT_TRUE(mDemuxTests.openDemux(demux, demuxId));
-    ASSERT_TRUE(mDemuxTests.setDemuxFrontendDataSource(feId));
     mFilterTests.setDemux(demux);
-    mFrontendTests.setDemux(demux);
     for (config = mediaFilterConfs.begin(); config != mediaFilterConfs.end(); config++) {
         ASSERT_TRUE(mFilterTests.openFilterInDemux((*config).type, (*config).bufferSize));
         ASSERT_TRUE(mFilterTests.getNewlyOpenedFilterId(filterId));
@@ -314,10 +359,26 @@
     for (id = filterIds.begin(); id != filterIds.end(); id++) {
         ASSERT_TRUE(mFilterTests.startFilter(*id));
     }
-    // tune test
-    ASSERT_TRUE(mFrontendTests.tuneFrontend(frontendConf, true /*testWithDemux*/));
+
+    if (descrambling.hasFrontendConnection) {
+        // tune test
+        ASSERT_TRUE(mFrontendTests.tuneFrontend(frontendConf, true /*testWithDemux*/));
+    } else {
+        // Start DVR Source
+        mDvrTests.startPlaybackInputThread(dvrSourceConfig.playbackInputFile,
+                                           dvrSourceConfig.settings.playback());
+        ASSERT_TRUE(mDvrTests.startDvrPlayback());
+    }
+
     ASSERT_TRUE(filterDataOutputTest());
-    ASSERT_TRUE(mFrontendTests.stopTuneFrontend(true /*testWithDemux*/));
+
+    if (descrambling.hasFrontendConnection) {
+        ASSERT_TRUE(mFrontendTests.stopTuneFrontend(true /*testWithDemux*/));
+    } else {
+        mDvrTests.stopPlaybackThread();
+        ASSERT_TRUE(mDvrTests.stopDvrPlayback());
+    }
+
     for (id = filterIds.begin(); id != filterIds.end(); id++) {
         ASSERT_TRUE(mFilterTests.stopFilter(*id));
     }
@@ -328,27 +389,45 @@
     for (id = filterIds.begin(); id != filterIds.end(); id++) {
         ASSERT_TRUE(mFilterTests.closeFilter(*id));
     }
+
+    if (descrambling.hasFrontendConnection) {
+        ASSERT_TRUE(mFrontendTests.closeFrontend());
+    } else {
+        mDvrTests.closeDvrPlayback();
+    }
+
     ASSERT_TRUE(mDemuxTests.closeDemux());
-    ASSERT_TRUE(mFrontendTests.closeFrontend());
 }
 
 TEST_P(TunerFrontendHidlTest, TuneFrontend) {
     description("Tune one Frontend with specific setting and check Lock event");
+    if (!live.hasFrontendConnection) {
+        return;
+    }
     mFrontendTests.tuneTest(frontendMap[live.frontendId]);
 }
 
 TEST_P(TunerFrontendHidlTest, AutoScanFrontend) {
     description("Run an auto frontend scan with specific setting and check lock scanMessage");
+    if (!scan.hasFrontendConnection) {
+        return;
+    }
     mFrontendTests.scanTest(frontendMap[scan.frontendId], FrontendScanType::SCAN_AUTO);
 }
 
 TEST_P(TunerFrontendHidlTest, BlindScanFrontend) {
     description("Run an blind frontend scan with specific setting and check lock scanMessage");
+    if (!scan.hasFrontendConnection) {
+        return;
+    }
     mFrontendTests.scanTest(frontendMap[scan.frontendId], FrontendScanType::SCAN_BLIND);
 }
 
 TEST_P(TunerLnbHidlTest, SendDiseqcMessageToLnb) {
     description("Open and configure an Lnb with specific settings then send a diseqc msg to it.");
+    if (!lnbLive.support) {
+        return;
+    }
     if (lnbMap[lnbLive.lnbId].name.compare(emptyHardwareId) == 0) {
         vector<uint32_t> ids;
         ASSERT_TRUE(mLnbTests.getLnbIds(ids));
@@ -370,6 +449,9 @@
 
 TEST_P(TunerDemuxHidlTest, openDemux) {
     description("Open and close a Demux.");
+    if (!live.hasFrontendConnection) {
+        return;
+    }
     uint32_t feId;
     uint32_t demuxId;
     sp<IDemux> demux;
@@ -385,6 +467,9 @@
 
 TEST_P(TunerDemuxHidlTest, getAvSyncTime) {
     description("Get the A/V sync time from a PCR filter.");
+    if (!live.hasFrontendConnection) {
+        return;
+    }
     if (live.pcrFilterId.compare(emptyHardwareId) == 0) {
         return;
     }
@@ -423,6 +508,9 @@
 
 TEST_P(TunerFilterHidlTest, StartFilterInDemux) {
     description("Open and start a filter in Demux.");
+    if (!live.hasFrontendConnection) {
+        return;
+    }
     // TODO use paramterized tests
     configSingleFilterInDemuxTest(filterMap[live.videoFilterId], frontendMap[live.frontendId]);
 }
@@ -457,22 +545,34 @@
 
 TEST_P(TunerFilterHidlTest, testTimeFilter) {
     description("Open a timer filter in Demux and set time stamp.");
+    if (!timeFilter.support) {
+        return;
+    }
     // TODO use paramterized tests
     testTimeFilter(timeFilterMap[timeFilter.timeFilterId]);
 }
 
 TEST_P(TunerBroadcastHidlTest, BroadcastDataFlowVideoFilterTest) {
     description("Test Video Filter functionality in Broadcast use case.");
+    if (!live.hasFrontendConnection) {
+        return;
+    }
     broadcastSingleFilterTest(filterMap[live.videoFilterId], frontendMap[live.frontendId]);
 }
 
 TEST_P(TunerBroadcastHidlTest, BroadcastDataFlowAudioFilterTest) {
     description("Test Audio Filter functionality in Broadcast use case.");
+    if (!live.hasFrontendConnection) {
+        return;
+    }
     broadcastSingleFilterTest(filterMap[live.audioFilterId], frontendMap[live.frontendId]);
 }
 
 TEST_P(TunerBroadcastHidlTest, BroadcastDataFlowSectionFilterTest) {
     description("Test Section Filter functionality in Broadcast use case.");
+    if (!live.hasFrontendConnection) {
+        return;
+    }
     if (live.sectionFilterId.compare(emptyHardwareId) == 0) {
         return;
     }
@@ -481,6 +581,9 @@
 
 TEST_P(TunerBroadcastHidlTest, IonBufferTest) {
     description("Test the av filter data bufferring.");
+    if (!live.hasFrontendConnection) {
+        return;
+    }
     broadcastSingleFilterTest(filterMap[live.videoFilterId], frontendMap[live.frontendId]);
 }
 
@@ -501,6 +604,22 @@
     playbackSingleFilterTest(filterMap[playback.sectionFilterId], dvrMap[playback.dvrId]);
 }
 
+TEST_P(TunerPlaybackHidlTest, PlaybackDataFlowWithTsAudioFilterTest) {
+    description("Feed ts data from playback and configure Ts audio filter to get output");
+    if (!playback.support) {
+        return;
+    }
+    playbackSingleFilterTest(filterMap[playback.audioFilterId], dvrMap[playback.dvrId]);
+}
+
+TEST_P(TunerPlaybackHidlTest, PlaybackDataFlowWithTsVideoFilterTest) {
+    description("Feed ts data from playback and configure Ts video filter to get output");
+    if (!playback.support) {
+        return;
+    }
+    playbackSingleFilterTest(filterMap[playback.videoFilterId], dvrMap[playback.dvrId]);
+}
+
 TEST_P(TunerRecordHidlTest, AttachFiltersToRecordTest) {
     description("Attach a single filter to the record dvr test.");
     // TODO use paramterized tests
@@ -535,19 +654,26 @@
     if (descrambling.support) {
         return;
     }
-    uint32_t feId;
     uint32_t demuxId;
     sp<IDemux> demux;
-    mFrontendTests.getFrontendIdByType(frontendMap[descrambling.frontendId].type, feId);
-    ASSERT_TRUE(feId != INVALID_ID);
-    ASSERT_TRUE(mFrontendTests.openFrontendById(feId));
-    ASSERT_TRUE(mFrontendTests.setFrontendCallback());
     ASSERT_TRUE(mDemuxTests.openDemux(demux, demuxId));
-    ASSERT_TRUE(mDemuxTests.setDemuxFrontendDataSource(feId));
+
+    if (descrambling.hasFrontendConnection) {
+        uint32_t feId;
+        mFrontendTests.getFrontendIdByType(frontendMap[descrambling.frontendId].type, feId);
+        ASSERT_TRUE(feId != INVALID_ID);
+        ASSERT_TRUE(mFrontendTests.openFrontendById(feId));
+        ASSERT_TRUE(mFrontendTests.setFrontendCallback());
+        ASSERT_TRUE(mDemuxTests.setDemuxFrontendDataSource(feId));
+    }
+
     ASSERT_TRUE(mDescramblerTests.openDescrambler(demuxId));
     ASSERT_TRUE(mDescramblerTests.closeDescrambler());
     ASSERT_TRUE(mDemuxTests.closeDemux());
-    ASSERT_TRUE(mFrontendTests.closeFrontend());
+
+    if (descrambling.hasFrontendConnection) {
+        ASSERT_TRUE(mFrontendTests.closeFrontend());
+    }
 }
 
 TEST_P(TunerDescramblerHidlTest, ScrambledBroadcastDataFlowMediaFiltersTest) {
diff --git a/tv/tuner/1.0/vts/functional/VtsHalTvTunerV1_0TestConfigurations.h b/tv/tuner/1.0/vts/functional/VtsHalTvTunerV1_0TestConfigurations.h
index 65f8615..735bc82 100644
--- a/tv/tuner/1.0/vts/functional/VtsHalTvTunerV1_0TestConfigurations.h
+++ b/tv/tuner/1.0/vts/functional/VtsHalTvTunerV1_0TestConfigurations.h
@@ -148,10 +148,32 @@
 };
 
 inline bool validateConnections() {
-    bool feIsValid = frontendMap.find(live.frontendId) != frontendMap.end() &&
-                     frontendMap.find(scan.frontendId) != frontendMap.end();
-    feIsValid &= record.support ? frontendMap.find(record.frontendId) != frontendMap.end() : true;
-    feIsValid &= descrambling.support
+    if ((!live.hasFrontendConnection || !scan.hasFrontendConnection) && !playback.support) {
+        ALOGW("[vts config] VTS must support either a DVR source or a Frontend source.");
+        return false;
+    }
+
+    if (record.support && !record.hasFrontendConnection &&
+        record.dvrSourceId.compare(emptyHardwareId) == 0) {
+        ALOGW("[vts config] Record must support either a DVR source or a Frontend source.");
+        return false;
+    }
+
+    if (descrambling.support && !descrambling.hasFrontendConnection &&
+        descrambling.dvrSourceId.compare(emptyHardwareId) == 0) {
+        ALOGW("[vts config] Descrambling must support either a DVR source or a Frontend source.");
+        return false;
+    }
+
+    bool feIsValid = live.hasFrontendConnection
+                             ? frontendMap.find(live.frontendId) != frontendMap.end()
+                             : true;
+    feIsValid &= scan.hasFrontendConnection ? frontendMap.find(scan.frontendId) != frontendMap.end()
+                                            : true;
+    feIsValid &= record.support && record.hasFrontendConnection
+                         ? frontendMap.find(record.frontendId) != frontendMap.end()
+                         : true;
+    feIsValid &= (descrambling.support && descrambling.hasFrontendConnection)
                          ? frontendMap.find(descrambling.frontendId) != frontendMap.end()
                          : true;
     feIsValid &= lnbLive.support ? frontendMap.find(lnbLive.frontendId) != frontendMap.end() : true;
@@ -163,18 +185,28 @@
         return false;
     }
 
-    bool dvrIsValid = frontendMap[live.frontendId].isSoftwareFe
+    bool dvrIsValid = (live.hasFrontendConnection && frontendMap[live.frontendId].isSoftwareFe)
                               ? dvrMap.find(live.dvrSoftwareFeId) != dvrMap.end()
                               : true;
     dvrIsValid &= playback.support ? dvrMap.find(playback.dvrId) != dvrMap.end() : true;
     if (record.support) {
-        if (frontendMap[record.frontendId].isSoftwareFe) {
-            dvrIsValid &= dvrMap.find(record.dvrSoftwareFeId) != dvrMap.end();
+        if (record.hasFrontendConnection) {
+            if (frontendMap[record.frontendId].isSoftwareFe) {
+                dvrIsValid &= dvrMap.find(record.dvrSoftwareFeId) != dvrMap.end();
+            }
+        } else {
+            dvrIsValid &= dvrMap.find(record.dvrSourceId) != dvrMap.end();
         }
         dvrIsValid &= dvrMap.find(record.dvrRecordId) != dvrMap.end();
     }
-    if (descrambling.support && frontendMap[descrambling.frontendId].isSoftwareFe) {
-        dvrIsValid &= dvrMap.find(descrambling.dvrSoftwareFeId) != dvrMap.end();
+    if (descrambling.support) {
+        if (descrambling.hasFrontendConnection) {
+            if (frontendMap[descrambling.frontendId].isSoftwareFe) {
+                dvrIsValid &= dvrMap.find(descrambling.dvrSoftwareFeId) != dvrMap.end();
+            }
+        } else {
+            dvrIsValid &= dvrMap.find(descrambling.dvrSourceId) != dvrMap.end();
+        }
     }
 
     if (!dvrIsValid) {
diff --git a/tv/tuner/config/TunerTestingConfigReader.h b/tv/tuner/config/TunerTestingConfigReader.h
index 90499c4..7971fd2 100644
--- a/tv/tuner/config/TunerTestingConfigReader.h
+++ b/tv/tuner/config/TunerTestingConfigReader.h
@@ -120,6 +120,7 @@
 };
 
 struct LiveBroadcastHardwareConnections {
+    bool hasFrontendConnection;
     string frontendId;
     string dvrSoftwareFeId;
     string audioFilterId;
@@ -130,6 +131,7 @@
 };
 
 struct ScanHardwareConnections {
+    bool hasFrontendConnection;
     string frontendId;
 };
 
@@ -145,19 +147,23 @@
 
 struct DvrRecordHardwareConnections {
     bool support;
+    bool hasFrontendConnection;
     string frontendId;
     string dvrRecordId;
     string dvrSoftwareFeId;
     string recordFilterId;
+    string dvrSourceId;
 };
 
 struct DescramblingHardwareConnections {
     bool support;
+    bool hasFrontendConnection;
     string frontendId;
     string dvrSoftwareFeId;
     string audioFilterId;
     string videoFilterId;
     string descramblerId;
+    string dvrSourceId;
     /* list string of extra filters; */
 };
 
@@ -395,7 +401,14 @@
     }
 
     static void connectLiveBroadcast(LiveBroadcastHardwareConnections& live) {
-        auto liveConfig = *getDataFlowConfiguration().getFirstClearLiveBroadcast();
+        auto dataFlow = getDataFlowConfiguration();
+        if (dataFlow.hasClearLiveBroadcast()) {
+            live.hasFrontendConnection = true;
+        } else {
+            live.hasFrontendConnection = false;
+            return;
+        }
+        auto liveConfig = *dataFlow.getFirstClearLiveBroadcast();
         live.frontendId = liveConfig.getFrontendConnection();
 
         live.audioFilterId = liveConfig.getAudioFilterConnection();
@@ -416,8 +429,15 @@
     }
 
     static void connectScan(ScanHardwareConnections& scan) {
-        auto scanConfig = getDataFlowConfiguration().getFirstScan();
-        scan.frontendId = scanConfig->getFrontendConnection();
+        auto dataFlow = getDataFlowConfiguration();
+        if (dataFlow.hasScan()) {
+            scan.hasFrontendConnection = true;
+        } else {
+            scan.hasFrontendConnection = false;
+            return;
+        }
+        auto scanConfig = *dataFlow.getFirstScan();
+        scan.frontendId = scanConfig.getFrontendConnection();
     }
 
     static void connectDvrPlayback(DvrPlaybackHardwareConnections& playback) {
@@ -425,6 +445,7 @@
         if (dataFlow.hasDvrPlayback()) {
             playback.support = true;
         } else {
+            playback.support = false;
             return;
         }
         auto playbackConfig = *dataFlow.getFirstDvrPlayback();
@@ -443,6 +464,7 @@
         if (dataFlow.hasDvrRecord()) {
             record.support = true;
         } else {
+            record.support = false;
             return;
         }
         auto recordConfig = *dataFlow.getFirstDvrRecord();
@@ -452,6 +474,13 @@
         if (recordConfig.hasDvrSoftwareFeConnection()) {
             record.dvrSoftwareFeId = recordConfig.getDvrSoftwareFeConnection();
         }
+        if (recordConfig.getHasFrontendConnection()) {
+            record.hasFrontendConnection = true;
+            record.dvrSourceId = emptyHardwareId;
+        } else {
+            record.hasFrontendConnection = false;
+            record.dvrSourceId = recordConfig.getDvrSourceConnection();
+        }
     }
 
     static void connectDescrambling(DescramblingHardwareConnections& descrambling) {
@@ -459,6 +488,7 @@
         if (dataFlow.hasDescrambling()) {
             descrambling.support = true;
         } else {
+            descrambling.support = false;
             return;
         }
         auto descConfig = *dataFlow.getFirstDescrambling();
@@ -469,6 +499,13 @@
         if (descConfig.hasDvrSoftwareFeConnection()) {
             descrambling.dvrSoftwareFeId = descConfig.getDvrSoftwareFeConnection();
         }
+        if (descConfig.getHasFrontendConnection()) {
+            descrambling.hasFrontendConnection = true;
+            descrambling.dvrSourceId = emptyHardwareId;
+        } else {
+            descrambling.hasFrontendConnection = false;
+            descrambling.dvrSourceId = descConfig.getDvrSourceConnection();
+        }
     }
 
     static void connectLnbLive(LnbLiveHardwareConnections& lnbLive) {
@@ -476,6 +513,7 @@
         if (dataFlow.hasLnbLive()) {
             lnbLive.support = true;
         } else {
+            lnbLive.support = false;
             return;
         }
         auto lnbLiveConfig = *dataFlow.getFirstLnbLive();
@@ -495,6 +533,7 @@
         if (dataFlow.hasLnbRecord()) {
             lnbRecord.support = true;
         } else {
+            lnbRecord.support = false;
             return;
         }
         auto lnbRecordConfig = *dataFlow.getFirstLnbRecord();
@@ -514,6 +553,7 @@
         if (dataFlow.hasTimeFilter()) {
             timeFilter.support = true;
         } else {
+            timeFilter.support = false;
             return;
         }
         auto timeFilterConfig = *dataFlow.getFirstTimeFilter();
diff --git a/tv/tuner/config/api/current.txt b/tv/tuner/config/api/current.txt
index 4255a60..abd7155 100644
--- a/tv/tuner/config/api/current.txt
+++ b/tv/tuner/config/api/current.txt
@@ -48,12 +48,16 @@
     method @Nullable public String getAudioFilterConnection();
     method @Nullable public String getDescramblerConnection();
     method @Nullable public String getDvrSoftwareFeConnection();
+    method @Nullable public String getDvrSourceConnection();
     method @Nullable public String getFrontendConnection();
+    method @Nullable public boolean getHasFrontendConnection();
     method @Nullable public String getVideoFilterConnection();
     method public void setAudioFilterConnection(@Nullable String);
     method public void setDescramblerConnection(@Nullable String);
     method public void setDvrSoftwareFeConnection(@Nullable String);
+    method public void setDvrSourceConnection(@Nullable String);
     method public void setFrontendConnection(@Nullable String);
+    method public void setHasFrontendConnection(@Nullable boolean);
     method public void setVideoFilterConnection(@Nullable String);
   }
 
@@ -73,11 +77,15 @@
     ctor public DataFlowConfiguration.DvrRecord();
     method @Nullable public String getDvrRecordConnection();
     method @Nullable public String getDvrSoftwareFeConnection();
+    method @Nullable public String getDvrSourceConnection();
     method @Nullable public String getFrontendConnection();
+    method @Nullable public boolean getHasFrontendConnection();
     method @Nullable public String getRecordFilterConnection();
     method public void setDvrRecordConnection(@Nullable String);
     method public void setDvrSoftwareFeConnection(@Nullable String);
+    method public void setDvrSourceConnection(@Nullable String);
     method public void setFrontendConnection(@Nullable String);
+    method public void setHasFrontendConnection(@Nullable boolean);
     method public void setRecordFilterConnection(@Nullable String);
   }
 
diff --git a/tv/tuner/config/sample_tuner_vts_config.xml b/tv/tuner/config/sample_tuner_vts_config.xml
index 570171e..2624076 100644
--- a/tv/tuner/config/sample_tuner_vts_config.xml
+++ b/tv/tuner/config/sample_tuner_vts_config.xml
@@ -196,7 +196,8 @@
                             sectionFilterConnection="FILTER_TS_SECTION_0"
                             dvrSoftwareFeConnection="DVR_PLAYBACK_0"/>
         <scan frontendConnection="FE_DEFAULT"/>
-        <descrambling frontendConnection="FE_DEFAULT"
+        <descrambling hasFrontendConnection="true"
+                      frontendConnection="FE_DEFAULT"
                       descramblerConnection="DESCRAMBLER_0"
                       audioFilterConnection="FILTER_AUDIO_DEFAULT"
                       videoFilterConnection="FILTER_VIDEO_DEFAULT"
@@ -205,7 +206,8 @@
                      audioFilterConnection="FILTER_AUDIO_DEFAULT"
                      videoFilterConnection="FILTER_VIDEO_DEFAULT"
                      sectionFilterConnection="FILTER_TS_SECTION_0"/>
-        <dvrRecord frontendConnection="FE_DEFAULT"
+        <dvrRecord hasFrontendConnection="true"
+                   frontendConnection="FE_DEFAULT"
                    recordFilterConnection="FILTER_TS_RECORD_0"
                    dvrRecordConnection="DVR_RECORD_0"
                    dvrSoftwareFeConnection="DVR_PLAYBACK_0"/>
diff --git a/tv/tuner/config/tuner_testing_dynamic_configuration.xsd b/tv/tuner/config/tuner_testing_dynamic_configuration.xsd
index 3fe93ff..e0c3e33 100644
--- a/tv/tuner/config/tuner_testing_dynamic_configuration.xsd
+++ b/tv/tuner/config/tuner_testing_dynamic_configuration.xsd
@@ -534,7 +534,9 @@
     <!-- DATA FLOW CONFIGURATION SESSION -->
     <xs:complexType name="dataFlowConfiguration">
         <xs:sequence>
-            <xs:element name="clearLiveBroadcast" minOccurs="1" maxOccurs="1">
+            <!-- clearLiveBroadcast is only optional when there is no physical frontend. In this
+              case, the dvrPlayback config is required. -->
+            <xs:element name="clearLiveBroadcast" minOccurs="0" maxOccurs="1">
                 <xs:complexType>
                     <xs:attribute name="frontendConnection" type="frontendId" use="required"/>
                     <xs:attribute name="audioFilterConnection" type="filterId" use="required"/>
@@ -546,20 +548,27 @@
                     <xs:attribute name="dvrSoftwareFeConnection" type="dvrId" use="optional"/>
                 </xs:complexType>
             </xs:element>
-            <xs:element name="scan" minOccurs="1" maxOccurs="1">
+            <!-- scan is only optional when there is no physical frontend. In this case, the
+              dvrPlayback config is required. -->
+            <xs:element name="scan" minOccurs="0" maxOccurs="1">
                 <xs:complexType>
                     <xs:attribute name="frontendConnection" type="frontendId" use="required"/>
                 </xs:complexType>
             </xs:element>
             <xs:element name="descrambling" minOccurs="0" maxOccurs="1">
                 <xs:complexType>
+                    <!-- If there is a software or hardware frontend connection or not. If false,
+                      dvrSourceConnection config is required when testing dvrRecord.  -->
+                    <xs:attribute name="hasFrontendConnection" type="xs:boolean" use="required"/>
                     <xs:attribute name="frontendConnection" type="frontendId" use="required"/>
                     <xs:attribute name="descramblerConnection" type="descramblerId" use="required"/>
                     <xs:attribute name="audioFilterConnection" type="filterId" use="required"/>
                     <xs:attribute name="videoFilterConnection" type="filterId" use="required"/>
                     <!-- TODO: b/182519645 allow the users to insert extra filters -->
-                    <!-- DVR is only required when the frontend is using the software input -->
+                    <!-- This DVR is only required when the frontend is using the software input -->
                     <xs:attribute name="dvrSoftwareFeConnection" type="dvrId" use="optional"/>
+                    <!-- This Dvr is only required when there's no frontend(sw or hw) connection -->
+                    <xs:attribute name="dvrSourceConnection" type="dvrId" use="optional"/>
                 </xs:complexType>
             </xs:element>
             <xs:element name="dvrPlayback" minOccurs="0" maxOccurs="1">
@@ -573,10 +582,15 @@
             </xs:element>
             <xs:element name="dvrRecord" minOccurs="0" maxOccurs="1">
                 <xs:complexType>
-                    <xs:attribute name="frontendConnection" type="frontendId" use="required"/>
+                    <!-- If there is a software or hardware frontend connection or not. If false,
+                      dvrSourceConnection config is required when testing dvrRecord.  -->
+                    <xs:attribute name="hasFrontendConnection" type="xs:boolean" use="required"/>
+                    <xs:attribute name="frontendConnection" type="frontendId" use="optional"/>
                     <xs:attribute name="dvrRecordConnection" type="dvrId" use="required"/>
-                    <!-- DVR is only required when the frontend is using the software input -->
+                    <!-- This Dvr is only required when the frontend is using the software input -->
                     <xs:attribute name="dvrSoftwareFeConnection" type="dvrId" use="optional"/>
+                    <!-- This Dvr is only required when there's no frontend(sw or hw) connection -->
+                    <xs:attribute name="dvrSourceConnection" type="dvrId" use="optional"/>
                     <xs:attribute name="recordFilterConnection" type="filterId" use="required"/>
                 </xs:complexType>
             </xs:element>