HalManifests can be merged.

assemble_vintf and VintfObject can now merge manifests
correctly (previously, only <hal>'s are merged).

Test: libvintf_test
Test: vintf_object_test
Fixes: 78943004
Change-Id: I5e2987e9c97e0b60e976fe4e0bb8833edf043a53
diff --git a/AssembleVintf.cpp b/AssembleVintf.cpp
index bcefc73..e0a22d1 100644
--- a/AssembleVintf.cpp
+++ b/AssembleVintf.cpp
@@ -358,26 +358,8 @@
                 }
             }
 
-            // TODO(b/78943004): add everything
-            if (!halManifest->addAllHals(&manifestToAdd, &error)) {
-                std::cerr << "File \"" << path << "\" cannot be added: conflict on HAL \"" << error
-                          << "\" with an existing HAL. See <hal> with the same name "
-                          << "in previously parsed files or previously declared in this file."
-                          << std::endl;
-                return false;
-            }
-
-            // Check that manifestToAdd is empty.
-            if (!manifestToAdd.empty()) {
-                std::cerr
-                    << "File \"" << path << "\" contains extraneous entries and attributes. "
-                    << "This is currently unsupported (b/78943004); it can only contain "
-                    << "<hal>s and attribute \"type\" and \"version\". Only the first input "
-                    << "file to assemble_vintf can contain other things. "
-                    << "Remaining entries and attributes are:" << std::endl
-                    << gHalManifestConverter(
-                           manifestToAdd,
-                           SerializeFlags::EVERYTHING.disableMetaVersion().disableSchemaType());
+            if (!halManifest->addAll(&manifestToAdd, &error)) {
+                std::cerr << "File \"" << path << "\" cannot be added: " << error << std::endl;
                 return false;
             }
         }
diff --git a/CompatibilityMatrix.cpp b/CompatibilityMatrix.cpp
index 290ebf9..5bb6d1e 100644
--- a/CompatibilityMatrix.cpp
+++ b/CompatibilityMatrix.cpp
@@ -28,6 +28,8 @@
 namespace android {
 namespace vintf {
 
+using details::mergeField;
+
 bool CompatibilityMatrix::addKernel(MatrixKernel&& kernel, std::string* error) {
     if (mType != SchemaType::FRAMEWORK) {
         if (error) {
@@ -294,22 +296,6 @@
     return true;
 }
 
-template <typename T>
-static bool mergeField(T* dst, T* src) {
-    static const T kEmpty{};
-    if (*dst == *src) {
-        return true;  // no conflict
-    }
-    if (*src == kEmpty) {
-        return true;
-    }
-    if (*dst == kEmpty) {
-        *dst = std::move(*src);
-        return true;
-    }
-    return false;
-}
-
 bool CompatibilityMatrix::addSepolicy(CompatibilityMatrix* other, std::string* error) {
     bool success = mergeField(&this->framework.mSepolicy, &other->framework.mSepolicy);
     if (!success && error) *error = "<sepolicy> is already defined";
diff --git a/HalManifest.cpp b/HalManifest.cpp
index d844c4c..59a7c31 100644
--- a/HalManifest.cpp
+++ b/HalManifest.cpp
@@ -36,6 +36,7 @@
 
 using details::Instances;
 using details::InstancesOfVersion;
+using details::mergeField;
 
 // Check <version> tag for all <hal> with the same name.
 bool HalManifest::shouldAdd(const ManifestHal& hal) const {
@@ -479,5 +480,87 @@
     return device.mKernel;
 }
 
+bool HalManifest::addAll(HalManifest* other, std::string* error) {
+    if (other->mMetaVersion.majorVer != mMetaVersion.majorVer) {
+        if (error) {
+            *error = "Cannot merge manifest version " + to_string(mMetaVersion) + " and " +
+                     to_string(other->mMetaVersion);
+        }
+        return false;
+    }
+    mMetaVersion.minorVer = std::max(mMetaVersion.minorVer, other->mMetaVersion.minorVer);
+
+    if (type() != other->type()) {
+        if (error) {
+            *error = "Cannot add a " + to_string(other->type()) + " manifest to a " +
+                     to_string(type()) + " manifest";
+        }
+        return false;
+    }
+
+    if (!addAllHals(other, error)) {
+        return false;
+    }
+
+    if (!addAllXmlFiles(other, error)) {
+        return false;
+    }
+
+    if (!mergeField(&mLevel, &other->mLevel, Level::UNSPECIFIED)) {
+        if (error) {
+            *error = "Conflicting target-level: " + to_string(level()) + " vs. " +
+                     to_string(other->level());
+        }
+        return false;
+    }
+
+    if (type() == SchemaType::DEVICE) {
+        if (!mergeField(&device.mSepolicyVersion, &other->device.mSepolicyVersion)) {
+            if (error) {
+                *error = "Conflicting sepolicy version: " + to_string(sepolicyVersion()) + " vs. " +
+                         to_string(other->sepolicyVersion());
+            }
+            return false;
+        }
+
+        if (!mergeField(&device.mKernel, &other->device.mKernel)) {
+            // If fails, both have values.
+            if (error) {
+                *error = "Conflicting kernel: " + to_string(device.mKernel->version()) + " vs. " +
+                         to_string(other->device.mKernel->version());
+            }
+            return false;
+        }
+    } else if (type() == SchemaType::FRAMEWORK) {
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
+        framework.mVndks.insert(framework.mVndks.end(), other->framework.mVndks.begin(),
+                                other->framework.mVndks.end());
+        other->framework.mVndks.clear();
+#pragma clang diagnostic pop
+
+        framework.mVendorNdks.insert(framework.mVendorNdks.end(),
+                                     other->framework.mVendorNdks.begin(),
+                                     other->framework.mVendorNdks.end());
+        other->framework.mVendorNdks.clear();
+
+        framework.mSystemSdk.addAll(&other->framework.mSystemSdk);
+    } else {
+        LOG(FATAL) << "unknown SchemaType: "
+                   << static_cast<std::underlying_type_t<SchemaType>>(type());
+    }
+
+    if (!other->empty()) {
+        if (error) {
+            *error =
+                "Cannot add another manifest because it contains extraneous entries that "
+                "are not recognized.";
+        }
+        return false;
+    }
+
+    return true;
+}
+
 } // namespace vintf
 } // namespace android
diff --git a/SystemSdk.cpp b/SystemSdk.cpp
index d2ee123..1dfd463 100644
--- a/SystemSdk.cpp
+++ b/SystemSdk.cpp
@@ -32,5 +32,10 @@
     return versions() == other.versions();
 }
 
+void SystemSdk::addAll(SystemSdk* other) {
+    mVersions.insert(other->mVersions.begin(), other->mVersions.end());
+    other->mVersions.clear();
+}
+
 }  // namespace vintf
 }  // namespace android
diff --git a/VintfObject.cpp b/VintfObject.cpp
index fbe4045..e66bcbe 100644
--- a/VintfObject.cpp
+++ b/VintfObject.cpp
@@ -215,7 +215,12 @@
         err = fetchOneHalManifest(directory + file, &fragmentManifest, error);
         if (err != OK) return err;
 
-        manifest->addAllHals(&fragmentManifest);
+        if (!manifest->addAll(&fragmentManifest, error)) {
+            if (error) {
+                error->insert(0, "Cannot add manifest fragment " + directory + file + ":");
+            }
+            return UNKNOWN_ERROR;
+        }
     }
 
     return OK;
@@ -250,7 +255,12 @@
 
     if (vendorStatus == OK) {
         if (odmStatus == OK) {
-            out->addAllHals(&odmManifest);
+            if (!out->addAll(&odmManifest, error)) {
+                if (error) {
+                    error->insert(0, "Cannot add ODM manifest :");
+                }
+                return UNKNOWN_ERROR;
+            }
         }
         return addDirectoryManifests(kOdmManifestFragmentDir, out, error);
     }
diff --git a/include/vintf/HalManifest.h b/include/vintf/HalManifest.h
index 1c90d17..6bcc8d6 100644
--- a/include/vintf/HalManifest.h
+++ b/include/vintf/HalManifest.h
@@ -131,6 +131,10 @@
     // Get the <kernel> tag. Assumes type() == DEVICE.
     const std::optional<KernelInfo>& kernel() const;
 
+    // Add everything from another manifest. If no errors (return true), it is guaranteed
+    // that other->empty() == true after execution.
+    [[nodiscard]] bool addAll(HalManifest* other, std::string* error = nullptr);
+
    protected:
     // Check before add()
     bool shouldAdd(const ManifestHal& toAdd) const override;
diff --git a/include/vintf/SystemSdk.h b/include/vintf/SystemSdk.h
index 1828e4a..703c1e0 100644
--- a/include/vintf/SystemSdk.h
+++ b/include/vintf/SystemSdk.h
@@ -38,6 +38,9 @@
     // return {v : v in this and not in other}
     SystemSdk removeVersions(const SystemSdk& other) const;
 
+    // Move all from "other" to "this".
+    void addAll(SystemSdk* other);
+
    private:
     friend class AssembleVintfImpl;
     friend struct SystemSdkConverter;
diff --git a/include/vintf/XmlFileGroup.h b/include/vintf/XmlFileGroup.h
index dd15647..b67de86 100644
--- a/include/vintf/XmlFileGroup.h
+++ b/include/vintf/XmlFileGroup.h
@@ -67,6 +67,7 @@
                 return false;
             }
         }
+        other->mXmlFiles.clear();
         return true;
     }
 
diff --git a/test/LibVintfTest.cpp b/test/LibVintfTest.cpp
index f5690aa..35804e7 100644
--- a/test/LibVintfTest.cpp
+++ b/test/LibVintfTest.cpp
@@ -3475,6 +3475,132 @@
         gKernelInfoConverter(ki, SerializeFlags::NO_TAGS.enableKernelConfigs()));
 }
 
+TEST_F(LibVintfTest, ManifestAddAllDeviceManifest) {
+    std::string xml1 = "<manifest version=\"1.0\" type=\"device\" />\n";
+    std::string xml2 =
+        "<manifest version=\"1.0\" type=\"device\" target-level=\"3\">\n"
+        "    <hal format=\"hidl\">\n"
+        "        <name>android.hardware.foo</name>\n"
+        "        <transport>hwbinder</transport>\n"
+        "        <fqname>@1.0::IFoo/default</fqname>\n"
+        "    </hal>\n"
+        "    <sepolicy>\n"
+        "        <version>25.5</version>\n"
+        "    </sepolicy>\n"
+        "    <kernel version=\"3.18.31\">\n"
+        "        <config>\n"
+        "            <key>CONFIG_64BIT</key>\n"
+        "            <value>y</value>\n"
+        "        </config>\n"
+        "    </kernel>\n"
+        "    <xmlfile>\n"
+        "        <name>media_profile</name>\n"
+        "        <version>1.0</version>\n"
+        "    </xmlfile>\n"
+        "</manifest>\n";
+
+    std::string error;
+    HalManifest manifest1;
+    ASSERT_TRUE(gHalManifestConverter(&manifest1, xml1, &error)) << error;
+    HalManifest manifest2;
+    ASSERT_TRUE(gHalManifestConverter(&manifest2, xml2, &error)) << error;
+
+    ASSERT_TRUE(manifest1.addAll(&manifest2, &error)) << error;
+
+    EXPECT_EQ(xml2, gHalManifestConverter(manifest1));
+}
+
+TEST_F(LibVintfTest, ManifestAddAllFrameworkManifest) {
+    std::string xml1 = "<manifest version=\"1.0\" type=\"framework\" />\n";
+    std::string xml2 =
+        "<manifest version=\"1.0\" type=\"framework\">\n"
+        "    <hal format=\"hidl\">\n"
+        "        <name>android.hardware.foo</name>\n"
+        "        <transport>hwbinder</transport>\n"
+        "        <fqname>@1.0::IFoo/default</fqname>\n"
+        "    </hal>\n"
+        "    <vendor-ndk>\n"
+        "        <version>P</version>\n"
+        "        <library>libbase.so</library>\n"
+        "    </vendor-ndk>\n"
+        "    <system-sdk>\n"
+        "        <version>1</version>\n"
+        "    </system-sdk>\n"
+        "    <xmlfile>\n"
+        "        <name>media_profile</name>\n"
+        "        <version>1.0</version>\n"
+        "    </xmlfile>\n"
+        "</manifest>\n";
+
+    std::string error;
+    HalManifest manifest1;
+    ASSERT_TRUE(gHalManifestConverter(&manifest1, xml1, &error)) << error;
+    HalManifest manifest2;
+    ASSERT_TRUE(gHalManifestConverter(&manifest2, xml2, &error)) << error;
+
+    ASSERT_TRUE(manifest1.addAll(&manifest2, &error)) << error;
+
+    EXPECT_EQ(xml2, gHalManifestConverter(manifest1));
+}
+
+TEST_F(LibVintfTest, ManifestAddAllConflictLevel) {
+    std::string xml1 = "<manifest version=\"1.0\" type=\"device\" target-level=\"2\" />\n";
+    std::string xml2 = "<manifest version=\"1.0\" type=\"device\" target-level=\"3\" />\n";
+
+    std::string error;
+    HalManifest manifest1;
+    ASSERT_TRUE(gHalManifestConverter(&manifest1, xml1, &error)) << error;
+    HalManifest manifest2;
+    ASSERT_TRUE(gHalManifestConverter(&manifest2, xml2, &error)) << error;
+
+    ASSERT_FALSE(manifest1.addAll(&manifest2, &error));
+    EXPECT_IN("Conflicting target-level", error);
+}
+
+TEST_F(LibVintfTest, ManifestAddAllConflictSepolicy) {
+    std::string xml1 =
+        "<manifest version=\"1.0\" type=\"device\">\n"
+        "    <sepolicy>\n"
+        "        <version>25.5</version>\n"
+        "    </sepolicy>\n"
+        "</manifest>\n";
+    std::string xml2 =
+        "<manifest version=\"1.0\" type=\"device\">\n"
+        "    <sepolicy>\n"
+        "        <version>30.0</version>\n"
+        "    </sepolicy>\n"
+        "</manifest>\n";
+
+    std::string error;
+    HalManifest manifest1;
+    ASSERT_TRUE(gHalManifestConverter(&manifest1, xml1, &error)) << error;
+    HalManifest manifest2;
+    ASSERT_TRUE(gHalManifestConverter(&manifest2, xml2, &error)) << error;
+
+    ASSERT_FALSE(manifest1.addAll(&manifest2, &error));
+    EXPECT_IN("Conflicting sepolicy version", error);
+}
+
+TEST_F(LibVintfTest, ManifestAddAllConflictKernel) {
+    std::string xml1 =
+        "<manifest version=\"1.0\" type=\"device\">\n"
+        "    <kernel version=\"3.18.0\" />\n"
+        "</manifest>\n";
+    std::string xml2 =
+        "<manifest version=\"1.0\" type=\"device\">\n"
+        "    <kernel version=\"3.18.1\" />\n"
+        "</manifest>\n";
+
+    std::string error;
+    HalManifest manifest1;
+    ASSERT_TRUE(gHalManifestConverter(&manifest1, xml1, &error)) << error;
+    HalManifest manifest2;
+    ASSERT_TRUE(gHalManifestConverter(&manifest2, xml2, &error)) << error;
+
+    ASSERT_FALSE(manifest1.addAll(&manifest2, &error));
+    EXPECT_IN("Conflicting kernel", error);
+}
+
 } // namespace vintf
 } // namespace android
 
diff --git a/utils.h b/utils.h
index dccc811..5378b67 100644
--- a/utils.h
+++ b/utils.h
@@ -81,6 +81,25 @@
     virtual bool getBoolProperty(const std::string& key, bool defaultValue) const override;
 };
 
+// Merge src into dst.
+// postcondition (if successful): *src == empty.
+template <typename T>
+static bool mergeField(T* dst, T* src, const T& empty = T{}) {
+    if (*dst == *src) {
+        *src = empty;
+        return true;  // no conflict
+    }
+    if (*src == empty) {
+        return true;
+    }
+    if (*dst == empty) {
+        *dst = std::move(*src);
+        *src = empty;
+        return true;
+    }
+    return false;
+}
+
 }  // namespace details
 }  // namespace vintf
 }  // namespace android