Add dvr dynamic configuration into Tuner 1.0 VTS

Test: atest VtsHalTvTunerV1_0TargetTest
Bug: 182519645
CTS-Coverage-Bug: 184077478

Change-Id: I04ef708064179e62c0c7b8c790fe844543b3eac8
diff --git a/tv/tuner/config/OWNERS b/tv/tuner/config/OWNERS
new file mode 100644
index 0000000..1b3d095
--- /dev/null
+++ b/tv/tuner/config/OWNERS
@@ -0,0 +1,4 @@
+nchalko@google.com
+amyjojo@google.com
+shubang@google.com
+quxiangfang@google.com
diff --git a/tv/tuner/config/TunerTestingConfigReader.h b/tv/tuner/config/TunerTestingConfigReader.h
index 5c7f564..aa2b75c 100644
--- a/tv/tuner/config/TunerTestingConfigReader.h
+++ b/tv/tuner/config/TunerTestingConfigReader.h
@@ -70,8 +70,16 @@
     vector<FrontendStatus> expectTuneStatuses;
 };
 
+struct DvrConfig {
+    DvrType type;
+    uint32_t bufferSize;
+    DvrSettings settings;
+    string playbackInputFile;
+};
+
 struct LiveBroadcastHardwareConnections {
     string frontendId;
+    string dvrSoftwareFeId;
     /* string audioFilterId;
     string videoFilterId;
     list string of extra filters; */
@@ -81,9 +89,20 @@
     string frontendId;
 };
 
+struct DvrPlaybackHardwareConnections {
+    bool support;
+    string frontendId;
+    string dvrId;
+    /* string audioFilterId;
+    string videoFilterId;
+    list string of extra filters; */
+};
+
 struct DvrRecordHardwareConnections {
     bool support;
     string frontendId;
+    string dvrRecordId;
+    string dvrSoftwareFeId;
     /* string recordFilterId;
     string dvrId; */
 };
@@ -91,6 +110,7 @@
 struct DescramblingHardwareConnections {
     bool support;
     string frontendId;
+    string dvrSoftwareFeId;
     /* string descramblerId;
     string audioFilterId;
     string videoFilterId;
@@ -196,9 +216,41 @@
         }
     }
 
+    static void readDvrConfig1_0(map<string, DvrConfig>& dvrMap) {
+        auto hardwareConfig = getHardwareConfig();
+        if (hardwareConfig.hasDvrs()) {
+            auto dvrs = *hardwareConfig.getFirstDvrs();
+            for (auto dvrConfig : dvrs.getDvr()) {
+                string id = dvrConfig.getId();
+                DvrType type;
+                switch (dvrConfig.getType()) {
+                    case DvrTypeEnum::PLAYBACK:
+                        type = DvrType::PLAYBACK;
+                        dvrMap[id].settings.playback(readPlaybackSettings(dvrConfig));
+                        break;
+                    case DvrTypeEnum::RECORD:
+                        type = DvrType::RECORD;
+                        dvrMap[id].settings.record(readRecordSettings(dvrConfig));
+                        break;
+                    case DvrTypeEnum::UNKNOWN:
+                        ALOGW("[ConfigReader] invalid DVR type");
+                        return;
+                }
+                dvrMap[id].type = type;
+                dvrMap[id].bufferSize = static_cast<uint32_t>(dvrConfig.getBufferSize());
+                if (dvrConfig.hasInputFilePath()) {
+                    dvrMap[id].playbackInputFile = dvrConfig.getInputFilePath();
+                }
+            }
+        }
+    }
+
     static void connectLiveBroadcast(LiveBroadcastHardwareConnections& live) {
         auto liveConfig = getDataFlowConfiguration().getFirstClearLiveBroadcast();
         live.frontendId = liveConfig->getFrontendConnection();
+        if (liveConfig->hasDvrSoftwareFeConnection()) {
+            live.dvrSoftwareFeId = liveConfig->getDvrSoftwareFeConnection();
+        }
     }
 
     static void connectScan(ScanHardwareConnections& scan) {
@@ -206,6 +258,16 @@
         scan.frontendId = scanConfig->getFrontendConnection();
     }
 
+    static void connectDvrPlayback(DvrPlaybackHardwareConnections& playback) {
+        auto dataFlow = getDataFlowConfiguration();
+        if (!dataFlow.hasDvrPlayback()) {
+            playback.support = false;
+            return;
+        }
+        auto playbackConfig = dataFlow.getFirstDvrPlayback();
+        playback.dvrId = playbackConfig->getDvrConnection();
+    }
+
     static void connectDvrRecord(DvrRecordHardwareConnections& record) {
         auto dataFlow = getDataFlowConfiguration();
         if (!dataFlow.hasDvrRecord()) {
@@ -214,6 +276,10 @@
         }
         auto recordConfig = dataFlow.getFirstDvrRecord();
         record.frontendId = recordConfig->getFrontendConnection();
+        record.dvrRecordId = recordConfig->getDvrRecordConnection();
+        if (recordConfig->hasDvrSoftwareFeConnection()) {
+            record.dvrSoftwareFeId = recordConfig->getDvrSoftwareFeConnection();
+        }
     }
 
     static void connectDescrambling(DescramblingHardwareConnections& descrambling) {
@@ -224,6 +290,9 @@
         }
         auto descConfig = dataFlow.getFirstDescrambling();
         descrambling.frontendId = descConfig->getFrontendConnection();
+        if (descConfig->hasDvrSoftwareFeConnection()) {
+            descrambling.dvrSoftwareFeId = descConfig->getDvrSoftwareFeConnection();
+        }
     }
 
     static void connectLnbLive(LnbLiveHardwareConnections& lnbLive) {
@@ -248,7 +317,7 @@
 
   private:
     static FrontendDvbtSettings readDvbtFrontendSettings(Frontend feConfig) {
-        ALOGW("[ConfigReader] type is dvbt");
+        ALOGW("[ConfigReader] fe type is dvbt");
         FrontendDvbtSettings dvbtSettings{
                 .frequency = (uint32_t)feConfig.getFrequency(),
         };
@@ -266,7 +335,7 @@
     }
 
     static FrontendDvbsSettings readDvbsFrontendSettings(Frontend feConfig) {
-        ALOGW("[ConfigReader] type is dvbs");
+        ALOGW("[ConfigReader] fe type is dvbs");
         FrontendDvbsSettings dvbsSettings{
                 .frequency = (uint32_t)feConfig.getFrequency(),
         };
@@ -281,6 +350,30 @@
         return dvbsSettings;
     }
 
+    static PlaybackSettings readPlaybackSettings(Dvr dvrConfig) {
+        ALOGW("[ConfigReader] dvr type is playback");
+        PlaybackSettings playbackSettings{
+                .statusMask = static_cast<uint8_t>(dvrConfig.getStatusMask()),
+                .lowThreshold = static_cast<uint32_t>(dvrConfig.getLowThreshold()),
+                .highThreshold = static_cast<uint32_t>(dvrConfig.getHighThreshold()),
+                .dataFormat = static_cast<DataFormat>(dvrConfig.getDataFormat()),
+                .packetSize = static_cast<uint8_t>(dvrConfig.getPacketSize()),
+        };
+        return playbackSettings;
+    }
+
+    static RecordSettings readRecordSettings(Dvr dvrConfig) {
+        ALOGW("[ConfigReader] dvr type is record");
+        RecordSettings recordSettings{
+                .statusMask = static_cast<uint8_t>(dvrConfig.getStatusMask()),
+                .lowThreshold = static_cast<uint32_t>(dvrConfig.getLowThreshold()),
+                .highThreshold = static_cast<uint32_t>(dvrConfig.getHighThreshold()),
+                .dataFormat = static_cast<DataFormat>(dvrConfig.getDataFormat()),
+                .packetSize = static_cast<uint8_t>(dvrConfig.getPacketSize()),
+        };
+        return recordSettings;
+    }
+
     static TunerConfiguration getTunerConfig() { return *read(configFilePath.c_str()); }
 
     static HardwareConfiguration getHardwareConfig() {
diff --git a/tv/tuner/config/api/current.txt b/tv/tuner/config/api/current.txt
index b0f410d..1ebd8e1 100644
--- a/tv/tuner/config/api/current.txt
+++ b/tv/tuner/config/api/current.txt
@@ -5,12 +5,14 @@
     ctor public DataFlowConfiguration();
     method @Nullable public android.media.tuner.testing.configuration.V1_0.DataFlowConfiguration.ClearLiveBroadcast getClearLiveBroadcast();
     method @Nullable public android.media.tuner.testing.configuration.V1_0.DataFlowConfiguration.Descrambling getDescrambling();
+    method @Nullable public android.media.tuner.testing.configuration.V1_0.DataFlowConfiguration.DvrPlayback getDvrPlayback();
     method @Nullable public android.media.tuner.testing.configuration.V1_0.DataFlowConfiguration.DvrRecord getDvrRecord();
     method @Nullable public android.media.tuner.testing.configuration.V1_0.DataFlowConfiguration.LnbLive getLnbLive();
     method @Nullable public android.media.tuner.testing.configuration.V1_0.DataFlowConfiguration.LnbRecord getLnbRecord();
     method @Nullable public android.media.tuner.testing.configuration.V1_0.DataFlowConfiguration.Scan getScan();
     method public void setClearLiveBroadcast(@Nullable android.media.tuner.testing.configuration.V1_0.DataFlowConfiguration.ClearLiveBroadcast);
     method public void setDescrambling(@Nullable android.media.tuner.testing.configuration.V1_0.DataFlowConfiguration.Descrambling);
+    method public void setDvrPlayback(@Nullable android.media.tuner.testing.configuration.V1_0.DataFlowConfiguration.DvrPlayback);
     method public void setDvrRecord(@Nullable android.media.tuner.testing.configuration.V1_0.DataFlowConfiguration.DvrRecord);
     method public void setLnbLive(@Nullable android.media.tuner.testing.configuration.V1_0.DataFlowConfiguration.LnbLive);
     method public void setLnbRecord(@Nullable android.media.tuner.testing.configuration.V1_0.DataFlowConfiguration.LnbRecord);
@@ -19,19 +21,33 @@
 
   public static class DataFlowConfiguration.ClearLiveBroadcast {
     ctor public DataFlowConfiguration.ClearLiveBroadcast();
+    method @Nullable public String getDvrSoftwareFeConnection();
     method @Nullable public String getFrontendConnection();
+    method public void setDvrSoftwareFeConnection(@Nullable String);
     method public void setFrontendConnection(@Nullable String);
   }
 
   public static class DataFlowConfiguration.Descrambling {
     ctor public DataFlowConfiguration.Descrambling();
+    method @Nullable public String getDvrSoftwareFeConnection();
     method @Nullable public String getFrontendConnection();
+    method public void setDvrSoftwareFeConnection(@Nullable String);
     method public void setFrontendConnection(@Nullable String);
   }
 
+  public static class DataFlowConfiguration.DvrPlayback {
+    ctor public DataFlowConfiguration.DvrPlayback();
+    method @Nullable public String getDvrConnection();
+    method public void setDvrConnection(@Nullable String);
+  }
+
   public static class DataFlowConfiguration.DvrRecord {
     ctor public DataFlowConfiguration.DvrRecord();
+    method @Nullable public String getDvrRecordConnection();
+    method @Nullable public String getDvrSoftwareFeConnection();
     method @Nullable public String getFrontendConnection();
+    method public void setDvrRecordConnection(@Nullable String);
+    method public void setDvrSoftwareFeConnection(@Nullable String);
     method public void setFrontendConnection(@Nullable String);
   }
 
@@ -43,7 +59,9 @@
 
   public static class DataFlowConfiguration.LnbRecord {
     ctor public DataFlowConfiguration.LnbRecord();
+    method @Nullable public String getDvrRecordConnection();
     method @Nullable public String getFrontendConnection();
+    method public void setDvrRecordConnection(@Nullable String);
     method public void setFrontendConnection(@Nullable String);
   }
 
@@ -71,6 +89,50 @@
     method public void setTransmissionMode(@Nullable java.math.BigInteger);
   }
 
+  public class Dvr {
+    ctor public Dvr();
+    method @Nullable public java.math.BigInteger getBufferSize();
+    method @Nullable public android.media.tuner.testing.configuration.V1_0.DvrDataFormatEnum getDataFormat();
+    method @Nullable public java.math.BigInteger getHighThreshold();
+    method @Nullable public String getId();
+    method @Nullable public String getInputFilePath();
+    method @Nullable public java.math.BigInteger getLowThreshold();
+    method @Nullable public java.math.BigInteger getPacketSize();
+    method @Nullable public java.math.BigInteger getStatusMask();
+    method @Nullable public android.media.tuner.testing.configuration.V1_0.DvrTypeEnum getType();
+    method public void setBufferSize(@Nullable java.math.BigInteger);
+    method public void setDataFormat(@Nullable android.media.tuner.testing.configuration.V1_0.DvrDataFormatEnum);
+    method public void setHighThreshold(@Nullable java.math.BigInteger);
+    method public void setId(@Nullable String);
+    method public void setInputFilePath(@Nullable String);
+    method public void setLowThreshold(@Nullable java.math.BigInteger);
+    method public void setPacketSize(@Nullable java.math.BigInteger);
+    method public void setStatusMask(@Nullable java.math.BigInteger);
+    method public void setType(@Nullable android.media.tuner.testing.configuration.V1_0.DvrTypeEnum);
+  }
+
+  public enum DvrDataFormatEnum {
+    method @NonNull public String getRawName();
+    enum_constant public static final android.media.tuner.testing.configuration.V1_0.DvrDataFormatEnum ES;
+    enum_constant public static final android.media.tuner.testing.configuration.V1_0.DvrDataFormatEnum PES;
+    enum_constant public static final android.media.tuner.testing.configuration.V1_0.DvrDataFormatEnum SHV_TLV;
+    enum_constant public static final android.media.tuner.testing.configuration.V1_0.DvrDataFormatEnum TS;
+  }
+
+  public enum DvrStatusEnum {
+    method @NonNull public String getRawName();
+    enum_constant public static final android.media.tuner.testing.configuration.V1_0.DvrStatusEnum DATA_READY;
+    enum_constant public static final android.media.tuner.testing.configuration.V1_0.DvrStatusEnum HIGH_WATER;
+    enum_constant public static final android.media.tuner.testing.configuration.V1_0.DvrStatusEnum LOW_WATER;
+    enum_constant public static final android.media.tuner.testing.configuration.V1_0.DvrStatusEnum OVERFLOW;
+  }
+
+  public enum DvrTypeEnum {
+    method @NonNull public String getRawName();
+    enum_constant public static final android.media.tuner.testing.configuration.V1_0.DvrTypeEnum PLAYBACK;
+    enum_constant public static final android.media.tuner.testing.configuration.V1_0.DvrTypeEnum RECORD;
+  }
+
   public class Frontend {
     ctor public Frontend();
     method @Nullable public java.math.BigInteger getConnectToCicamId();
@@ -108,10 +170,17 @@
 
   public class HardwareConfiguration {
     ctor public HardwareConfiguration();
+    method @Nullable public android.media.tuner.testing.configuration.V1_0.HardwareConfiguration.Dvrs getDvrs();
     method @Nullable public android.media.tuner.testing.configuration.V1_0.HardwareConfiguration.Frontends getFrontends();
+    method public void setDvrs(@Nullable android.media.tuner.testing.configuration.V1_0.HardwareConfiguration.Dvrs);
     method public void setFrontends(@Nullable android.media.tuner.testing.configuration.V1_0.HardwareConfiguration.Frontends);
   }
 
+  public static class HardwareConfiguration.Dvrs {
+    ctor public HardwareConfiguration.Dvrs();
+    method @Nullable public java.util.List<android.media.tuner.testing.configuration.V1_0.Dvr> getDvr();
+  }
+
   public static class HardwareConfiguration.Frontends {
     ctor public HardwareConfiguration.Frontends();
     method @Nullable public java.util.List<android.media.tuner.testing.configuration.V1_0.Frontend> getFrontend();
diff --git a/tv/tuner/config/sample_tuner_vts_config.xml b/tv/tuner/config/sample_tuner_vts_config.xml
index c4080d9..001e045 100644
--- a/tv/tuner/config/sample_tuner_vts_config.xml
+++ b/tv/tuner/config/sample_tuner_vts_config.xml
@@ -60,16 +60,49 @@
                       connectToCicamId="0" frequency="578000" endFrequency="800000">
             </frontend>
         </frontends>
+        <!-- Dvr section:
+            This section contains configurations of all the dvrs that would be used in the tests.
+                - This section is optional and can be skipped if DVR is not supported.
+                - The users can configure 1 or more dvr elements in the dvrs sections.
+
+            Each dvr element contain the following attributes:
+                "id": unique id of the dvr that could be used to connect to the test the
+                    "dataFlowConfiguration"
+                "type": the dvr type.
+                "bufferSize": the dvr buffer size.
+                "statusMask": register callbacks of specific status.
+                "lowThreshold": the dvr status low threshold.
+                "highThreshold": the dvr status high threshold.
+                "dataFormat": the dvr data format.
+                "packetSize": the dvr packet size.
+                "inputFilePath": the dvr playback input file path. Only required in playback dvr.
+        -->
+        <dvrs>
+            <dvr id="DVR_PLAYBACK_0" type="PLAYBACK" bufferSize="4194304"
+                 statusMask="15" lowThreshold="4096" highThreshold="32767"
+                 dataFormat="TS" packetSize="188" inputFilePath="/data/local/tmp/segment000000.ts"/>
+            <dvr id="DVR_RECORD_0" type="RECORD" bufferSize="4194304"
+                 statusMask="15" lowThreshold="4096" highThreshold="32767"
+                 dataFormat="TS" packetSize="188"/>
+            <dvr id="DVR_PLAYBACK_1" type="PLAYBACK" bufferSize="4194304"
+                 statusMask="15" lowThreshold="4096" highThreshold="32767"
+                 dataFormat="ES" packetSize="188" inputFilePath="/data/local/tmp/test.es"/>
+        </dvrs>
     </hardwareConfiguration>
 
     <!-- Data flow configuration section connects each data flow under test to the ids of the
         hardwares that would be used during the tests. -->
     <dataFlowConfiguration>
-        <clearLiveBroadcast frontendConnection="FE_DEFAULT"/>
+        <clearLiveBroadcast frontendConnection="FE_DEFAULT"
+                            dvrSoftwareFeConnection="DVR_PLAYBACK_0"/>
         <scan frontendConnection="FE_DEFAULT"/>
-        <descrambling frontendConnection="FE_DEFAULT"/>
-        <dvrRecord frontendConnection="FE_DEFAULT"/>
+        <descrambling frontendConnection="FE_DEFAULT"
+                      dvrSoftwareFeConnection="DVR_PLAYBACK_0"/>
+        <dvrRecord frontendConnection="FE_DEFAULT"
+                   dvrRecordConnection="DVR_RECORD_0"
+                   dvrSoftwareFeConnection="DVR_PLAYBACK_0"/>
         <lnbLive frontendConnection="FE_DVBS_0"/>
-        <lnbRecord frontendConnection="FE_DVBS_0"/>
+	<lnbRecord frontendConnection="FE_DVBS_0"
+		   dvrRecordConnection="DVR_RECORD_0"/>
     </dataFlowConfiguration>
 </TunerConfiguration>
diff --git a/tv/tuner/config/tuner_testing_dynamic_configuration.xsd b/tv/tuner/config/tuner_testing_dynamic_configuration.xsd
index cd8b061..45d25e5 100644
--- a/tv/tuner/config/tuner_testing_dynamic_configuration.xsd
+++ b/tv/tuner/config/tuner_testing_dynamic_configuration.xsd
@@ -99,14 +99,80 @@
         </xs:choice>
         <xs:attribute name="id" type="frontendId" use="required"/>
         <xs:attribute name="type" type="frontendTypeEnum" use="required"/>
-        <xs:attribute name="isSoftwareFrontend" type="xs:boolean" use="required"/>
         <!-- A dvr connection is required in the data flow config section when
             "isSoftwareFrontend" is true. -->
+        <xs:attribute name="isSoftwareFrontend" type="xs:boolean" use="required"/>
         <xs:attribute name="frequency" type="xs:nonNegativeInteger" use="required"/>
         <xs:attribute name="connectToCicamId" type="xs:nonNegativeInteger" use="optional"/>
         <xs:attribute name="endFrequency" type="xs:nonNegativeInteger" use="optional"/>
     </xs:complexType>
 
+    <!-- DVR SESSION -->
+    <xs:simpleType name="dvrId">
+        <!-- Dvr id must be DVR_TYPE_NUM. <dvr id="DVR_PLAYBACK_0"/> -->
+        <xs:restriction base="xs:string">
+            <xs:pattern value="DVR_RECORD_[0-9]+|DVR_PLAYBACK_[0-9]+"/>
+        </xs:restriction>
+    </xs:simpleType>
+    <xs:simpleType name="dvrStatusMask">
+        <!-- Dvr status mask must masking the <dvrStatusEnum> -->
+        <xs:restriction base="xs:integer">
+            <xs:minInclusive value="0"/>
+            <xs:maxInclusive value="15"/>
+        </xs:restriction>
+    </xs:simpleType>
+
+    <xs:simpleType name="dvrStatusEnum">
+        <xs:restriction base="xs:string">
+            <xs:enumeration value="DATA_READY" />
+            <xs:enumeration value="LOW_WATER" />
+            <xs:enumeration value="HIGH_WATER" />
+            <xs:enumeration value="OVERFLOW" />
+        </xs:restriction>
+    </xs:simpleType>
+    <xs:simpleType name="dvrTypeEnum">
+        <xs:restriction base="xs:string">
+            <xs:enumeration value="PLAYBACK" />
+            <xs:enumeration value="RECORD" />
+        </xs:restriction>
+    </xs:simpleType>
+    <xs:simpleType name="dvrDataFormatEnum">
+        <xs:restriction base="xs:string">
+            <xs:enumeration value="TS" />
+            <xs:enumeration value="PES" />
+            <xs:enumeration value="ES" />
+            <xs:enumeration value="SHV_TLV" />
+        </xs:restriction>
+    </xs:simpleType>
+
+    <xs:complexType name="dvr">
+        <xs:annotation>
+            <xs:documentation>
+                Each dvr element contain the following attributes:
+                    "id": unique id of the dvr that could be used to connect to the test the
+                        "dataFlowConfiguration"
+                    "type": the dvr type.
+                    "bufferSize": the dvr buffer size.
+                    "statusMask": register callbacks of specific status.
+                    "lowThreshold": the dvr status low threshold.
+                    "highThreshold": the dvr status high threshold.
+                    "dataFormat": the dvr data format.
+                    "packetSize": the dvr packet size.
+                    "inputFilePath": the dvr playback input file path. Only required in playback
+                        dvr.
+            </xs:documentation>
+        </xs:annotation>
+        <xs:attribute name="id" type="dvrId" use="required"/>
+        <xs:attribute name="type" type="dvrTypeEnum" use="required"/>
+        <xs:attribute name="bufferSize" type="xs:nonNegativeInteger" use="required"/>
+        <xs:attribute name="statusMask" type="dvrStatusMask" use="required"/>
+        <xs:attribute name="lowThreshold" type="xs:nonNegativeInteger" use="required"/>
+        <xs:attribute name="highThreshold" type="xs:nonNegativeInteger" use="required"/>
+        <xs:attribute name="dataFormat" type="dvrDataFormatEnum" use="required"/>
+        <xs:attribute name="packetSize" type="xs:nonNegativeInteger" use="required"/>
+        <xs:attribute name="inputFilePath" type="xs:anyURI" use="optional"/>
+    </xs:complexType>
+
     <!-- HARDWARE CONFIGURATION SESSION -->
     <xs:complexType name="hardwareConfiguration">
         <xs:sequence>
@@ -131,6 +197,23 @@
                     </xs:sequence>
                 </xs:complexType>
             </xs:element>
+            <xs:element name="dvrs" minOccurs="0" maxOccurs="1">
+                <xs:complexType>
+                    <xs:annotation>
+                        <xs:documentation xml:lang="en">
+                            This section contains configurations of all the dvrs that would be used
+                                in the tests.
+                                - This section is optional and can be skipped if the device does
+                                    not support dvr.
+                                - The users can configure 1 or more dvr elements in the dvrs
+                                   sections.
+                        </xs:documentation>
+                    </xs:annotation>
+                    <xs:sequence>
+                        <xs:element name="dvr" type="dvr" minOccurs="1" maxOccurs="unbounded"/>
+                    </xs:sequence>
+                </xs:complexType>
+            </xs:element>
         </xs:sequence>
     </xs:complexType>
 
@@ -139,8 +222,9 @@
         <xs:sequence>
             <xs:element name="clearLiveBroadcast" minOccurs="1" maxOccurs="1">
                 <xs:complexType>
-                    <!-- TODO: add optional dvr config for software input -->
                     <xs:attribute name="frontendConnection" type="frontendId" use="required"/>
+                    <!-- DVR is only required when the frontend is using the software input -->
+                    <xs:attribute name="dvrSoftwareFeConnection" type="dvrId" use="optional"/>
                 </xs:complexType>
             </xs:element>
             <xs:element name="scan" minOccurs="1" maxOccurs="1">
@@ -151,11 +235,21 @@
             <xs:element name="descrambling" minOccurs="0" maxOccurs="1">
                 <xs:complexType>
                     <xs:attribute name="frontendConnection" type="frontendId" use="required"/>
+                    <!-- DVR is only required when the frontend is using the software input -->
+                    <xs:attribute name="dvrSoftwareFeConnection" type="dvrId" use="optional"/>
+                </xs:complexType>
+            </xs:element>
+            <xs:element name="dvrPlayback" minOccurs="0" maxOccurs="1">
+                <xs:complexType>
+                    <xs:attribute name="dvrConnection" type="dvrId" use="required"/>
                 </xs:complexType>
             </xs:element>
             <xs:element name="dvrRecord" minOccurs="0" maxOccurs="1">
                 <xs:complexType>
                     <xs:attribute name="frontendConnection" type="frontendId" use="required"/>
+                    <xs:attribute name="dvrRecordConnection" type="dvrId" use="required"/>
+                    <!-- DVR is only required when the frontend is using the software input -->
+                    <xs:attribute name="dvrSoftwareFeConnection" type="dvrId" use="optional"/>
                 </xs:complexType>
             </xs:element>
             <xs:element name="lnbLive" minOccurs="0" maxOccurs="1">
@@ -166,6 +260,7 @@
             <xs:element name="lnbRecord" minOccurs="0" maxOccurs="1">
                 <xs:complexType>
                     <xs:attribute name="frontendConnection" type="frontendId" use="required"/>
+                    <xs:attribute name="dvrRecordConnection" type="dvrId" use="required"/>
                 </xs:complexType>
             </xs:element>
         </xs:sequence>