Add <xmlfile> to manifest / comp mat.

manifest.xml is allowed to contain <xmlfile> that is similar to:
<xmlfile>
    <name>media_profile</name>
    <version>1.0</version>
    <path>/path/to/media_profile_v1_0.xml</path>
</xmlfile>

For compatibility-matrix.xml
<xmlfile format="dtd" optional="false">
    <name>media_profile</name>
    <version>1.0-1</version>
    <path>/path/to/media_profile_v1_1.dtd</path>
</xmlfile>

The <path> tag is optional. See test/main.cpp for examples.

Test: libvintf_test
Test: vintf_object_test

Bug: 38359330

Change-Id: I0a921f32d023e1ba9c54ea6e898bb385fc7abd4f
diff --git a/Android.bp b/Android.bp
index 22224e9..5a67c5f 100644
--- a/Android.bp
+++ b/Android.bp
@@ -47,6 +47,7 @@
         "MatrixKernel.cpp",
         "TransportArch.cpp",
         "VintfObject.cpp",
+        "XmlFile.cpp",
         "utils.cpp",
     ],
 
@@ -117,6 +118,7 @@
         "MatrixKernel.cpp",
         "TransportArch.cpp",
         "VintfObject.cpp",
+        "XmlFile.cpp",
         "test/RuntimeInfo-fake.cpp",
         "test/utils-fake.cpp",
     ],
diff --git a/CompatibilityMatrix.cpp b/CompatibilityMatrix.cpp
index 627c238..907ac8c 100644
--- a/CompatibilityMatrix.cpp
+++ b/CompatibilityMatrix.cpp
@@ -58,14 +58,12 @@
 }
 
 bool operator==(const CompatibilityMatrix &lft, const CompatibilityMatrix &rgt) {
-    return lft.mType == rgt.mType &&
-           lft.mHals == rgt.mHals &&
-           (lft.mType != SchemaType::DEVICE || (
-                lft.device.mVndk == rgt.device.mVndk)) &&
-           (lft.mType != SchemaType::FRAMEWORK || (
-                lft.framework.mKernels == rgt.framework.mKernels &&
-                lft.framework.mSepolicy == rgt.framework.mSepolicy &&
-                lft.framework.mAvbMetaVersion == rgt.framework.mAvbMetaVersion));
+    return lft.mType == rgt.mType && lft.mHals == rgt.mHals && lft.mXmlFiles == rgt.mXmlFiles &&
+           (lft.mType != SchemaType::DEVICE || (lft.device.mVndk == rgt.device.mVndk)) &&
+           (lft.mType != SchemaType::FRAMEWORK ||
+            (lft.framework.mKernels == rgt.framework.mKernels &&
+             lft.framework.mSepolicy == rgt.framework.mSepolicy &&
+             lft.framework.mAvbMetaVersion == rgt.framework.mAvbMetaVersion));
 }
 
 } // namespace vintf
diff --git a/HalManifest.cpp b/HalManifest.cpp
index efa543e..5ebe083 100644
--- a/HalManifest.cpp
+++ b/HalManifest.cpp
@@ -55,6 +55,16 @@
     return true;
 }
 
+bool HalManifest::shouldAddXmlFile(const ManifestXmlFile& xmlFile) const {
+    auto existingXmlFiles = getXmlFiles(xmlFile.name());
+    for (auto it = existingXmlFiles.first; it != existingXmlFiles.second; ++it) {
+        if (xmlFile.version() == it->second.version()) {
+            return false;
+        }
+    }
+    return true;
+}
+
 std::set<std::string> HalManifest::getHalNames() const {
     std::set<std::string> names{};
     for (const auto &hal : mHals) {
@@ -365,12 +375,10 @@
 }
 
 bool operator==(const HalManifest &lft, const HalManifest &rgt) {
-    return lft.mType == rgt.mType &&
-           lft.mHals == rgt.mHals &&
-           (lft.mType != SchemaType::DEVICE || (
-                lft.device.mSepolicyVersion == rgt.device.mSepolicyVersion)) &&
-           (lft.mType != SchemaType::FRAMEWORK || (
-                lft.framework.mVndks == rgt.framework.mVndks));
+    return lft.mType == rgt.mType && lft.mHals == rgt.mHals && lft.mXmlFiles == rgt.mXmlFiles &&
+           (lft.mType != SchemaType::DEVICE ||
+            (lft.device.mSepolicyVersion == rgt.device.mSepolicyVersion)) &&
+           (lft.mType != SchemaType::FRAMEWORK || (lft.framework.mVndks == rgt.framework.mVndks));
 }
 
 } // namespace vintf
diff --git a/XmlFile.cpp b/XmlFile.cpp
new file mode 100644
index 0000000..695b3d4
--- /dev/null
+++ b/XmlFile.cpp
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#include "XmlFile.h"
+
+namespace android {
+namespace vintf {
+
+bool MatrixXmlFile::operator==(const MatrixXmlFile& other) const {
+    return name() == other.name() && overriddenPath() == other.overriddenPath() &&
+           optional() == other.optional() && format() == other.format() &&
+           versionRange() == other.versionRange();
+}
+
+bool ManifestXmlFile::operator==(const ManifestXmlFile& other) const {
+    return name() == other.name() && overriddenPath() == other.overriddenPath() &&
+           version() == other.version();
+}
+
+}  // namespace vintf
+}  // namespace android
diff --git a/include/vintf/CompatibilityMatrix.h b/include/vintf/CompatibilityMatrix.h
index 228fe5e..66e5f8d 100644
--- a/include/vintf/CompatibilityMatrix.h
+++ b/include/vintf/CompatibilityMatrix.h
@@ -29,12 +29,13 @@
 #include "SchemaType.h"
 #include "Sepolicy.h"
 #include "Vndk.h"
+#include "XmlFileGroup.h"
 
 namespace android {
 namespace vintf {
 
 // Compatibility matrix defines what hardware does the framework requires.
-struct CompatibilityMatrix : public HalGroup<MatrixHal> {
+struct CompatibilityMatrix : public HalGroup<MatrixHal>, public XmlFileGroup<MatrixXmlFile> {
     // Create a framework compatibility matrix.
     CompatibilityMatrix() : mType(SchemaType::FRAMEWORK) {};
 
diff --git a/include/vintf/HalManifest.h b/include/vintf/HalManifest.h
index 9e01330..2f78301 100644
--- a/include/vintf/HalManifest.h
+++ b/include/vintf/HalManifest.h
@@ -29,6 +29,7 @@
 #include "SchemaType.h"
 #include "Version.h"
 #include "Vndk.h"
+#include "XmlFileGroup.h"
 
 namespace android {
 namespace vintf {
@@ -38,7 +39,7 @@
 
 // A HalManifest is reported by the hardware and query-able from
 // framework code. This is the API for the framework.
-struct HalManifest : public HalGroup<ManifestHal> {
+struct HalManifest : public HalGroup<ManifestHal>, public XmlFileGroup<ManifestXmlFile> {
    public:
     // manifest.version
     constexpr static Version kVersion{1, 0};
@@ -123,6 +124,7 @@
    protected:
     // Check before add()
     bool shouldAdd(const ManifestHal& toAdd) const override;
+    bool shouldAddXmlFile(const ManifestXmlFile& toAdd) const override;
 
    private:
     friend struct HalManifestConverter;
@@ -141,6 +143,9 @@
     // Check if all instances in matrixHal is supported in this manifest.
     bool isCompatible(const MatrixHal& matrixHal) const;
 
+    std::vector<std::string> checkIncompatibleXmlFiles(const CompatibilityMatrix& mat,
+                                                       bool includeOptional = true) const;
+
     SchemaType mType;
 
     // entries for device hal manifest only
diff --git a/include/vintf/MapValueIterator.h b/include/vintf/MapValueIterator.h
index 3a41e82..ec9fb19 100644
--- a/include/vintf/MapValueIterator.h
+++ b/include/vintf/MapValueIterator.h
@@ -92,7 +92,9 @@
             return IteratorImpl<is_const>(mMap.end());
         }
 
-    private:
+        bool empty() const { return begin() == end(); }
+
+       private:
         map_ref mMap;
     };
 
diff --git a/include/vintf/Version.h b/include/vintf/Version.h
index c712269..0f38391 100644
--- a/include/vintf/Version.h
+++ b/include/vintf/Version.h
@@ -55,6 +55,13 @@
     inline bool operator>=(const Version &other) const {
         return !((*this) < other);
     }
+    // Version(2, 1).minorAtLeast(Version(1, 0)) == false
+    // Version(2, 1).minorAtLeast(Version(2, 0)) == true
+    // Version(2, 1).minorAtLeast(Version(2, 1)) == true
+    // Version(2, 1).minorAtLeast(Version(2, 2)) == false
+    inline bool minorAtLeast(const Version& other) const {
+        return majorVer == other.majorVer && minorVer >= other.minorVer;
+    }
 };
 
 struct KernelVersion {
diff --git a/include/vintf/XmlFile.h b/include/vintf/XmlFile.h
new file mode 100644
index 0000000..787e217
--- /dev/null
+++ b/include/vintf/XmlFile.h
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_VINTF_XML_FILE_H
+#define ANDROID_VINTF_XML_FILE_H
+
+#include <string>
+
+#include "Version.h"
+#include "VersionRange.h"
+#include "XmlSchemaFormat.h"
+
+namespace android {
+namespace vintf {
+
+struct XmlFile {
+   public:
+    inline const std::string& name() const { return mName; }
+    inline const std::string& overriddenPath() const { return mOverriddenPath; }
+
+   protected:
+    std::string mName;
+    std::string mOverriddenPath;
+};
+
+// An <xmlfile> entry in matrix
+struct MatrixXmlFile : public XmlFile {
+    inline bool optional() const { return mOptional; }
+    inline XmlSchemaFormat format() const { return mFormat; }
+    inline const VersionRange& versionRange() const { return mVersionRange; }
+    bool operator==(const MatrixXmlFile& other) const;
+
+   private:
+    friend struct MatrixXmlFileConverter;
+    friend struct LibVintfTest;
+    bool mOptional;
+    XmlSchemaFormat mFormat;
+    VersionRange mVersionRange;
+};
+
+// An <xmlfile> entry in manifest
+struct ManifestXmlFile : public XmlFile {
+    inline const Version& version() const { return mVersion; }
+    bool operator==(const ManifestXmlFile& other) const;
+
+   private:
+    friend struct ManifestXmlFileConverter;
+    friend struct LibVintfTest;
+    Version mVersion;
+};
+
+}  // namespace vintf
+}  // namespace android
+#endif  // ANDROID_VINTF_XML_FILE_H
diff --git a/include/vintf/XmlFileGroup.h b/include/vintf/XmlFileGroup.h
new file mode 100644
index 0000000..be2bb73
--- /dev/null
+++ b/include/vintf/XmlFileGroup.h
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_VINTF_XML_FILE_GROUP_H
+#define ANDROID_VINTF_XML_FILE_GROUP_H
+
+#include <map>
+#include <type_traits>
+
+#include "MapValueIterator.h"
+#include "XmlFile.h"
+
+namespace android {
+namespace vintf {
+
+// A XmlFileGroup is a wrapped multimap from name to T, where T
+// must be a subclass of XmlFile.
+template <typename T>
+struct XmlFileGroup {
+    static_assert(std::is_base_of<XmlFile, T>::value, "T must be a subclass of XmlFile");
+
+   private:
+    using map = std::multimap<std::string, T>;
+    using const_range = std::pair<typename map::const_iterator, typename map::const_iterator>;
+
+   public:
+    virtual ~XmlFileGroup() {}
+
+    bool addXmlFile(T&& t) {
+        if (!shouldAddXmlFile(t)) {
+            return false;
+        }
+        std::string name = t.name();
+        mXmlFiles.emplace(std::move(name), std::move(t));
+        return true;
+    }
+
+    virtual bool shouldAddXmlFile(const T&) const { return true; }
+
+    const_range getXmlFiles(const std::string& key) const { return mXmlFiles.equal_range(key); }
+
+    // Return an iterable to all T objects. Call it as follows:
+    // for (const auto& e : vm.getXmlFiles()) { }
+    ConstMultiMapValueIterable<std::string, T> getXmlFiles() const {
+        return ConstMultiMapValueIterable<std::string, T>(mXmlFiles);
+    }
+
+   protected:
+    map mXmlFiles;
+};
+
+}  // namespace vintf
+}  // namespace android
+
+#endif  // ANDROID_VINTF_XML_FILE_GROUP_H
diff --git a/include/vintf/XmlSchemaFormat.h b/include/vintf/XmlSchemaFormat.h
new file mode 100644
index 0000000..32a42b6
--- /dev/null
+++ b/include/vintf/XmlSchemaFormat.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_VINTF_XML_SCHEMA_FORMAT_H
+#define ANDROID_VINTF_XML_SCHEMA_FORMAT_H
+
+#include <array>
+#include <string>
+
+namespace android {
+namespace vintf {
+
+enum XmlSchemaFormat {
+    DTD,
+    XSD,
+};
+
+static const std::array<std::string, 2> gXmlSchemaFormatStrings = {{
+    "dtd", "xsd",
+}};
+
+}  // namespace vintf
+}  // namespace android
+#endif  // ANDROID_VINTF_XML_SCHEMA_FORMAT_H
diff --git a/include/vintf/parse_string.h b/include/vintf/parse_string.h
index 83a4904..2cfd41e 100644
--- a/include/vintf/parse_string.h
+++ b/include/vintf/parse_string.h
@@ -34,6 +34,7 @@
 std::ostream &operator<<(std::ostream &os, KernelConfigType il);
 std::ostream &operator<<(std::ostream &os, Tristate tr);
 std::ostream &operator<<(std::ostream &os, SchemaType ksv);
+std::ostream& operator<<(std::ostream& os, XmlSchemaFormat f);
 std::ostream &operator<<(std::ostream &os, const ManifestHal &hal);
 std::ostream &operator<<(std::ostream &os, const Version &ver);
 std::ostream &operator<<(std::ostream &os, const VersionRange &vr);
@@ -58,6 +59,7 @@
 bool parse(const std::string &s, KernelConfigKey *key);
 bool parse(const std::string &s, Tristate *tr);
 bool parse(const std::string &s, SchemaType *ver);
+bool parse(const std::string& s, XmlSchemaFormat* ver);
 bool parse(const std::string &s, KernelSepolicyVersion *ksv);
 bool parse(const std::string &s, Version *ver);
 bool parse(const std::string &s, VersionRange *vr);
diff --git a/parse_string.cpp b/parse_string.cpp
index d412ac4..4d7ae9a 100644
--- a/parse_string.cpp
+++ b/parse_string.cpp
@@ -97,6 +97,7 @@
 DEFINE_PARSE_STREAMIN_FOR_ENUM(KernelConfigType);
 DEFINE_PARSE_STREAMIN_FOR_ENUM(Tristate);
 DEFINE_PARSE_STREAMIN_FOR_ENUM(SchemaType);
+DEFINE_PARSE_STREAMIN_FOR_ENUM(XmlSchemaFormat);
 
 std::ostream &operator<<(std::ostream &os, const KernelConfigTypedValue &kctv) {
     switch (kctv.mType) {
diff --git a/parse_xml.cpp b/parse_xml.cpp
index 557da76..f12df00 100644
--- a/parse_xml.cpp
+++ b/parse_xml.cpp
@@ -265,6 +265,13 @@
         return true;
     }
 
+    inline bool parseOptionalTextElement(NodeType* root, const std::string& elementName,
+                                         std::string&& defaultValue, std::string* s) const {
+        NodeType* child = getChild(root, elementName);
+        *s = child == nullptr ? std::move(defaultValue) : getText(child);
+        return true;
+    }
+
     inline bool parseTextElements(NodeType *root, const std::string &elementName,
             std::vector<std::string> *v) const {
         auto nodes = getChildren(root, elementName);
@@ -610,17 +617,40 @@
 };
 const HalManifestSepolicyConverter halManifestSepolicyConverter{};
 
+struct ManifestXmlFileConverter : public XmlNodeConverter<ManifestXmlFile> {
+    std::string elementName() const override { return "xmlfile"; }
+    void mutateNode(const ManifestXmlFile& f, NodeType* root, DocType* d) const override {
+        appendTextElement(root, "name", f.name(), d);
+        appendChild(root, versionConverter(f.version(), d));
+        if (!f.overriddenPath().empty()) {
+            appendTextElement(root, "path", f.overriddenPath(), d);
+        }
+    }
+    bool buildObject(ManifestXmlFile* object, NodeType* root) const override {
+        if (!parseTextElement(root, "name", &object->mName) ||
+            !parseChild(root, versionConverter, &object->mVersion) ||
+            !parseOptionalTextElement(root, "path", {}, &object->mOverriddenPath)) {
+            return false;
+        }
+        return true;
+    }
+};
+const ManifestXmlFileConverter manifestXmlFileConverter{};
+
 struct HalManifestConverter : public XmlNodeConverter<HalManifest> {
     std::string elementName() const override { return "manifest"; }
     void mutateNode(const HalManifest &m, NodeType *root, DocType *d) const override {
         appendAttr(root, "version", HalManifest::kVersion);
         appendAttr(root, "type", m.mType);
+
         appendChildren(root, manifestHalConverter, m.getHals(), d);
         if (m.mType == SchemaType::DEVICE) {
             appendChild(root, halManifestSepolicyConverter(m.device.mSepolicyVersion, d));
         } else if (m.mType == SchemaType::FRAMEWORK) {
             appendChildren(root, vndkConverter, m.framework.mVndks, d);
         }
+
+        appendChildren(root, manifestXmlFileConverter, m.getXmlFiles(), d);
     }
     bool buildObject(HalManifest *object, NodeType *root) const override {
         Version version;
@@ -661,6 +691,20 @@
                 return false;
             }
         }
+
+        std::vector<ManifestXmlFile> xmlFiles;
+        if (!parseChildren(root, manifestXmlFileConverter, &xmlFiles)) {
+            return false;
+        }
+        for (auto&& xmlFile : xmlFiles) {
+            std::string description{xmlFile.name()};
+            if (!object->addXmlFile(std::move(xmlFile))) {
+                this->mLastError = "Duplicated manifest.xmlfile entry " + description +
+                                   "; entries cannot have duplicated name and version";
+                return false;
+            }
+        }
+
         return true;
     }
 };
@@ -679,6 +723,30 @@
 };
 const AvbConverter avbConverter{};
 
+struct MatrixXmlFileConverter : public XmlNodeConverter<MatrixXmlFile> {
+    std::string elementName() const override { return "xmlfile"; }
+    void mutateNode(const MatrixXmlFile& f, NodeType* root, DocType* d) const override {
+        appendTextElement(root, "name", f.name(), d);
+        appendAttr(root, "format", f.format());
+        appendAttr(root, "optional", f.optional());
+        appendChild(root, versionRangeConverter(f.versionRange(), d));
+        if (!f.overriddenPath().empty()) {
+            appendTextElement(root, "path", f.overriddenPath(), d);
+        }
+    }
+    bool buildObject(MatrixXmlFile* object, NodeType* root) const override {
+        if (!parseTextElement(root, "name", &object->mName) ||
+            !parseAttr(root, "format", &object->mFormat) ||
+            !parseOptionalAttr(root, "optional", false, &object->mOptional) ||
+            !parseChild(root, versionRangeConverter, &object->mVersionRange) ||
+            !parseOptionalTextElement(root, "path", {}, &object->mOverriddenPath)) {
+            return false;
+        }
+        return true;
+    }
+};
+const MatrixXmlFileConverter matrixXmlFileConverter{};
+
 struct CompatibilityMatrixConverter : public XmlNodeConverter<CompatibilityMatrix> {
     std::string elementName() const override { return "compatibility-matrix"; }
     void mutateNode(const CompatibilityMatrix &m, NodeType *root, DocType *d) const override {
@@ -692,6 +760,8 @@
         } else if (m.mType == SchemaType::DEVICE) {
             appendChild(root, vndkConverter(m.device.mVndk, d));
         }
+
+        appendChildren(root, matrixXmlFileConverter, m.getXmlFiles(), d);
     }
     bool buildObject(CompatibilityMatrix *object, NodeType *root) const override {
         Version version;
@@ -728,6 +798,24 @@
                 return false;
             }
         }
+
+        std::vector<MatrixXmlFile> xmlFiles;
+        if (!parseChildren(root, matrixXmlFileConverter, &xmlFiles)) {
+            return false;
+        }
+        for (auto&& xmlFile : xmlFiles) {
+            if (!xmlFile.optional()) {
+                this->mLastError = "compatibility-matrix.xmlfile entry " + xmlFile.name() +
+                                   " has to be optional for compatibility matrix version 1.0";
+                return false;
+            }
+            std::string description{xmlFile.name()};
+            if (!object->addXmlFile(std::move(xmlFile))) {
+                this->mLastError = "Duplicated compatibility-matrix.xmlfile entry " + description;
+                return false;
+            }
+        }
+
         return true;
     }
 };
diff --git a/test/main.cpp b/test/main.cpp
index 6ce1e23..090fd3d 100644
--- a/test/main.cpp
+++ b/test/main.cpp
@@ -52,6 +52,14 @@
     bool add(HalManifest &vm, ManifestHal &&hal) {
         return vm.add(std::move(hal));
     }
+    void addXmlFile(CompatibilityMatrix& cm, std::string name, VersionRange range) {
+        MatrixXmlFile f;
+        f.mName = name;
+        f.mVersionRange = range;
+        f.mFormat = XmlSchemaFormat::DTD;
+        f.mOptional = true;
+        cm.addXmlFile(std::move(f));
+    }
     void set(CompatibilityMatrix &cm, Sepolicy &&sepolicy) {
         cm.framework.mSepolicy = sepolicy;
     }
@@ -120,6 +128,14 @@
 
         return vm;
     }
+    HalManifest testDeviceManifestWithXmlFile() {
+        HalManifest vm = testDeviceManifest();
+        ManifestXmlFile xmlFile;
+        xmlFile.mName = "media_profile";
+        xmlFile.mVersion = {1, 0};
+        vm.addXmlFile(std::move(xmlFile));
+        return vm;
+    }
     HalManifest testFrameworkManfiest() {
         HalManifest vm;
         vm.mType = SchemaType::FRAMEWORK;
@@ -1101,6 +1117,87 @@
     EXPECT_TRUE(manifest.checkCompatibility(matrix, &error)) << error;
 }
 
+/////////////////// xmlfile tests
+
+TEST_F(LibVintfTest, HalManifestConverterXmlFile) {
+    HalManifest vm = testDeviceManifestWithXmlFile();
+    std::string xml = gHalManifestConverter(vm);
+    EXPECT_EQ(xml,
+              "<manifest version=\"1.0\" type=\"device\">\n"
+              "    <hal format=\"hidl\">\n"
+              "        <name>android.hardware.camera</name>\n"
+              "        <transport>hwbinder</transport>\n"
+              "        <version>2.0</version>\n"
+              "        <interface>\n"
+              "            <name>IBetterCamera</name>\n"
+              "            <instance>camera</instance>\n"
+              "        </interface>\n"
+              "        <interface>\n"
+              "            <name>ICamera</name>\n"
+              "            <instance>default</instance>\n"
+              "            <instance>legacy/0</instance>\n"
+              "        </interface>\n"
+              "    </hal>\n"
+              "    <hal format=\"hidl\">\n"
+              "        <name>android.hardware.nfc</name>\n"
+              "        <transport arch=\"32+64\">passthrough</transport>\n"
+              "        <version>1.0</version>\n"
+              "        <interface>\n"
+              "            <name>INfc</name>\n"
+              "            <instance>default</instance>\n"
+              "        </interface>\n"
+              "    </hal>\n"
+              "    <sepolicy>\n"
+              "        <version>25.0</version>\n"
+              "    </sepolicy>\n"
+              "    <xmlfile>\n"
+              "        <name>media_profile</name>\n"
+              "        <version>1.0</version>\n"
+              "    </xmlfile>\n"
+              "</manifest>\n");
+    HalManifest vm2;
+    EXPECT_TRUE(gHalManifestConverter(&vm2, xml));
+    EXPECT_EQ(vm, vm2);
+}
+
+TEST_F(LibVintfTest, CompatibilityMatrixConverterXmlFile) {
+    CompatibilityMatrix cm;
+    addXmlFile(cm, "media_profile", {1, 0});
+    std::string xml = gCompatibilityMatrixConverter(cm);
+    EXPECT_EQ(xml,
+              "<compatibility-matrix version=\"1.0\" type=\"framework\">\n"
+              "    <sepolicy>\n"
+              "        <kernel-sepolicy-version>0</kernel-sepolicy-version>\n"
+              "    </sepolicy>\n"
+              "    <avb>\n"
+              "        <vbmeta-version>0.0</vbmeta-version>\n"
+              "    </avb>\n"
+              "    <xmlfile format=\"dtd\" optional=\"true\">\n"
+              "        <name>media_profile</name>\n"
+              "        <version>1.0</version>\n"
+              "    </xmlfile>\n"
+              "</compatibility-matrix>\n");
+    CompatibilityMatrix cm2;
+    EXPECT_TRUE(gCompatibilityMatrixConverter(&cm2, xml));
+    EXPECT_EQ(cm, cm2);
+}
+
+TEST_F(LibVintfTest, CompatibilityMatrixConverterXmlFile2) {
+    std::string xml =
+        "<compatibility-matrix version=\"1.0\" type=\"framework\">\n"
+        "    <xmlfile format=\"dtd\" optional=\"false\">\n"
+        "        <name>media_profile</name>\n"
+        "        <version>1.0</version>\n"
+        "    </xmlfile>\n"
+        "</compatibility-matrix>\n";
+    CompatibilityMatrix cm;
+    EXPECT_FALSE(gCompatibilityMatrixConverter(&cm, xml));
+    EXPECT_EQ(
+        "compatibility-matrix.xmlfile entry media_profile has to be optional for "
+        "compatibility matrix version 1.0",
+        gCompatibilityMatrixConverter.lastError());
+}
+
 } // namespace vintf
 } // namespace android