assemble_vintf: move logic to CompatibilityMatrix::combine

... that combines <hal> and <xmlfile> elements of a list
of comp matrices.

* All required HALs at deviceLevel will be required in output matrix.
* All HALs at level > deviceLevel will be optional in output matrix.

The logic is also improved in the following ways:
* <xmlfile>s are also handled
* <hal>s and <xmlfile>s are moved to the "unspecified" matrix (that has non-hal
  / xmlfile information)

Test: libvintf_test

Bug: 69636193
Change-Id: Ifdc2227d1823840c0303ce6476ffa4b542e04e12
diff --git a/CompatibilityMatrix.cpp b/CompatibilityMatrix.cpp
index 1e7d542..98d4414 100644
--- a/CompatibilityMatrix.cpp
+++ b/CompatibilityMatrix.cpp
@@ -21,6 +21,9 @@
 #include "parse_string.h"
 #include "utils.h"
 
+#include <iostream>
+#include "parse_xml.h"
+
 namespace android {
 namespace vintf {
 
@@ -166,5 +169,123 @@
              lft.framework.mAvbMetaVersion == rgt.framework.mAvbMetaVersion));
 }
 
+// Find compatibility_matrix.empty.xml (which has unspecified level) and use it
+// as a base matrix.
+CompatibilityMatrix* CompatibilityMatrix::findOrInsertBaseMatrix(
+    std::vector<std::pair<std::string, CompatibilityMatrix>>* matrices, std::string* error) {
+    bool multipleFound = false;
+    CompatibilityMatrix* matrix = nullptr;
+    for (auto& e : *matrices) {
+        if (e.second.level() == Level::UNSPECIFIED) {
+            if (!e.second.mHals.empty()) {
+                if (error) {
+                    *error =
+                        "Error: File \"" + e.first + "\" should not contain " + "HAL elements.";
+                }
+                return nullptr;
+            }
+
+            if (!e.second.mXmlFiles.empty()) {
+                if (error) {
+                    *error = "Error: File \"" + e.first + "\" should not contain " +
+                             "XML File elements.";
+                }
+                return nullptr;
+            }
+
+            if (matrix != nullptr) {
+                multipleFound = true;
+            }
+
+            matrix = &e.second;
+            // continue to detect multiple files with "unspecified" levels
+        }
+    }
+
+    if (multipleFound) {
+        if (error) {
+            *error =
+                "Error: multiple framework compatibility matrix files have "
+                "unspecified level; there should only be one such file.\n";
+            for (auto& e : *matrices) {
+                if (e.second.level() == Level::UNSPECIFIED) {
+                    *error += "    " + e.first + "\n";
+                }
+            }
+        }
+        return nullptr;
+    }
+
+    if (matrix == nullptr) {
+        matrix = &matrices->emplace(matrices->end())->second;
+        matrix->mType = SchemaType::FRAMEWORK;
+        matrix->mLevel = Level::UNSPECIFIED;
+    }
+
+    return matrix;
+}
+
+CompatibilityMatrix* CompatibilityMatrix::combine(
+    Level deviceLevel, std::vector<std::pair<std::string, CompatibilityMatrix>>* matrices,
+    std::string* error) {
+    if (deviceLevel == Level::UNSPECIFIED) {
+        if (error) {
+            *error = "Error: device level is unspecified.";
+        }
+        return nullptr;
+    }
+
+    CompatibilityMatrix* matrix = findOrInsertBaseMatrix(matrices, error);
+    if (matrix == nullptr) {
+        return nullptr;
+    }
+
+    matrix->mLevel = deviceLevel;
+
+    for (auto& e : *matrices) {
+        if (&e.second != matrix && e.second.level() == deviceLevel) {
+            if (!matrix->addAllHals(&e.second, error)) {
+                if (error) {
+                    *error = "File \"" + e.first + "\" cannot be added: HAL " + *error +
+                             " has a conflict.";
+                }
+                return nullptr;
+            }
+
+            if (!matrix->addAllXmlFiles(&e.second, error)) {
+                if (error) {
+                    *error = "File \"" + e.first + "\" cannot be added: XML File entry " + *error +
+                             " has a conflict.";
+                }
+                return nullptr;
+            }
+        }
+    }
+
+    for (auto& e : *matrices) {
+        if (&e.second != matrix && e.second.level() != Level::UNSPECIFIED &&
+            e.second.level() > deviceLevel) {
+            if (!matrix->addAllHalsAsOptional(&e.second, error)) {
+                if (error) {
+                    *error = "File \"" + e.first + "\" cannot be added: " + *error +
+                             ". See <hal> with the same name " +
+                             "in previously parsed files or previously declared in this file.";
+                }
+                return nullptr;
+            }
+
+            if (!matrix->addAllXmlFilesAsOptional(&e.second, error)) {
+                if (error) {
+                    *error = "File \"" + e.first + "\" cannot be added: XML File entry " + *error +
+                             " has a conflict.";
+                }
+                return nullptr;
+            }
+        }
+    }
+
+    return matrix;
+}
+
 } // namespace vintf
 } // namespace android