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/AssembleVintf.cpp b/AssembleVintf.cpp
index 4665f68..4174e93 100644
--- a/AssembleVintf.cpp
+++ b/AssembleVintf.cpp
@@ -420,7 +420,6 @@
 
         if (matrices->front().second.mType == SchemaType::FRAMEWORK) {
             Level deviceLevel = Level::UNSPECIFIED;
-            std::vector<std::string> fileList;
             if (mCheckFile != nullptr) {
                 checkManifest = std::make_unique<HalManifest>();
                 if (!gHalManifestConverter(checkManifest.get(), read(*mCheckFile))) {
@@ -437,27 +436,13 @@
                 deviceLevel = getLowestFcmVersion(*matrices);
             }
 
-            for (auto& e : *matrices) {
-                if (e.second.level() == deviceLevel) {
-                    fileList.push_back(e.first);
-                    matrix = &e.second;
-                }
-            }
-            if (matrix == nullptr) {
-                std::cerr << "FATAL ERROR: cannot find matrix with level '" << deviceLevel << "'"
-                          << std::endl;
-                return false;
-            }
-            for (auto& e : *matrices) {
-                if (e.second.level() <= deviceLevel) {
-                    continue;
-                }
-                fileList.push_back(e.first);
-                if (!matrix->addAllHalsAsOptional(&e.second, &error)) {
-                    std::cerr << "File \"" << e.first << "\" cannot be added: " << error
-                              << ". See <hal> with the same name "
-                              << "in previously parsed files or previously declared in this file."
-                              << std::endl;
+            if (deviceLevel == Level::UNSPECIFIED) {
+                // building empty.xml
+                matrix = &matrices->front().second;
+            } else {
+                matrix = CompatibilityMatrix::combine(deviceLevel, matrices, &error);
+                if (matrix == nullptr) {
+                    std::cerr << error << std::endl;
                     return false;
                 }
             }
@@ -484,8 +469,10 @@
 
             out() << "<!--" << std::endl;
             out() << "    Input:" << std::endl;
-            for (const auto& path : fileList) {
-                out() << "        " << getFileNameFromPath(path) << std::endl;
+            for (const auto& e : *matrices) {
+                if (!e.first.empty()) {
+                    out() << "        " << getFileNameFromPath(e.first) << std::endl;
+                }
             }
             out() << "-->" << std::endl;
         }
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
diff --git a/include/vintf/CompatibilityMatrix.h b/include/vintf/CompatibilityMatrix.h
index 4aa1cb3..9be80ed 100644
--- a/include/vintf/CompatibilityMatrix.h
+++ b/include/vintf/CompatibilityMatrix.h
@@ -73,6 +73,19 @@
 
     status_t fetchAllInformation(const std::string& path, std::string* error = nullptr);
 
+    // Combine a subset of "matrices". For each CompatibilityMatrix in matrices,
+    // - If level() == UNSPECIFIED, use it as the base matrix (for non-HAL, non-XML-file
+    //   requirements).
+    // - If level() < deviceLevel, ignore
+    // - If level() == deviceLevel, all HAL versions and XML files are added as is
+    //   (optionality is kept)
+    // - If level() > deviceLevel, all HAL versions and XML files are added as optional.
+    static CompatibilityMatrix* combine(
+        Level deviceLevel, std::vector<std::pair<std::string, CompatibilityMatrix>>* matrices,
+        std::string* error);
+    static CompatibilityMatrix* findOrInsertBaseMatrix(
+        std::vector<std::pair<std::string, CompatibilityMatrix>>* matrices, std::string* error);
+
     friend struct HalManifest;
     friend struct RuntimeInfo;
     friend struct CompatibilityMatrixConverter;