vintf: clean up output.

Output summarizes <hal> tags and omits kernel
configs now. If a user still wants these output, --verbose
can be used.

Test: adb shell vintf
Change-Id: Id7716d55d9a6a159df180d476609d95035ec0c15
diff --git a/main.cpp b/main.cpp
index f49e484..111b797 100644
--- a/main.cpp
+++ b/main.cpp
@@ -14,13 +14,21 @@
  * limitations under the License.
  */
 
-#include <iostream>
-#include <vintf/parse_xml.h>
-#include <vintf/parse_string.h>
+#include <getopt.h>
+
+#include <android-base/strings.h>
 #include <vintf/VintfObject.h>
+#include <vintf/parse_string.h>
+#include <vintf/parse_xml.h>
+#include <iomanip>
+#include <iostream>
+#include <string>
+#include <vector>
 
 using namespace ::android::vintf;
 
+static const std::string kColumnSeperator = "   ";
+
 std::string existString(bool value) {
     return value ? "GOOD" : "DOES NOT EXIST";
 }
@@ -51,40 +59,241 @@
     }
 }
 
+enum Status : int {
+    OK = 0,
+    USAGE,
+};
+
+struct ParsedOptions {
+    bool verbose = false;
+};
+
+struct Option {
+    char shortOption = '\0';
+    std::string longOption;
+    std::string help;
+    std::function<Status(ParsedOptions*)> op;
+};
+
+std::string getShortOptions(const std::vector<Option>& options) {
+    std::stringstream ret;
+    for (const auto& e : options)
+        if (e.shortOption != '\0') ret << e.shortOption;
+    return ret.str();
+}
+
+std::unique_ptr<struct option[]> getLongOptions(const std::vector<Option>& options,
+                                                int* longOptFlag) {
+    std::unique_ptr<struct option[]> ret{new struct option[options.size() + 1]};
+    int i = 0;
+    for (const auto& e : options) {
+        ret[i].name = e.longOption.c_str();
+        ret[i].has_arg = no_argument;
+        ret[i].flag = longOptFlag;
+        ret[i].val = i;
+
+        i++;
+    }
+    // getopt_long last option has all zeros
+    ret[i].name = NULL;
+    ret[i].has_arg = 0;
+    ret[i].flag = NULL;
+    ret[i].val = 0;
+
+    return ret;
+}
+
+Status parseOptions(int argc, char** argv, const std::vector<Option>& options, ParsedOptions* out) {
+    int longOptFlag;
+    std::unique_ptr<struct option[]> longOptions = getLongOptions(options, &longOptFlag);
+    std::string shortOptions = getShortOptions(options);
+    int optionIndex;
+    for (;;) {
+        int c = getopt_long(argc, argv, shortOptions.c_str(), longOptions.get(), &optionIndex);
+        if (c == -1) {
+            break;
+        }
+        const Option* found = nullptr;
+        for (size_t i = 0; i < options.size(); ++i)
+            if ((c == 0 && longOptFlag == static_cast<int>(i)) ||
+                (c != 0 && c == options[i].shortOption))
+
+                found = &options[i];
+
+        if (found == nullptr) {
+            // see unrecognized options
+            std::cerr << "unrecognized option `" << argv[optind - 1] << "'" << std::endl;
+            return USAGE;
+        }
+
+        Status status = found->op(out);
+        if (status != OK) return status;
+    }
+    if (optind < argc) {
+        // see non option
+        std::cerr << "unrecognized option `" << argv[optind] << "'" << std::endl;
+        return USAGE;
+    }
+    return OK;
+}
+
+void usage(char* me, const std::vector<Option>& options) {
+    std::cerr << me << ": dump VINTF metadata via libvintf." << std::endl;
+    for (const auto& e : options) {
+        if (e.help.empty()) continue;
+        std::cerr << "        ";
+        if (e.shortOption != '\0') std::cerr << "-" << e.shortOption;
+        if (e.shortOption != '\0' && !e.longOption.empty()) std::cerr << ", ";
+        if (!e.longOption.empty()) std::cerr << "--" << e.longOption;
+        std::cerr << ": "
+                  << android::base::Join(android::base::Split(e.help, "\n"), "\n            ")
+                  << std::endl;
+    }
+}
+
+// android.hardware.foo@1.0-1::IFoo/default.
+// Note that the format is extended to support a range of versions.
+std::string fqInstanceName(const std::string& package, const VersionRange& range,
+                           const std::string& interface, const std::string& instance) {
+    std::stringstream ss;
+    ss << package << "@" << range << "::" << interface << "/" << instance;
+    return ss.str();
+}
+
+struct TableRow {
+    // Whether the HAL version is in device manifest, framework manifest, device compatibility
+    // matrix, framework compatibility matrix, respectively.
+    bool dm = false;
+    bool fm = false;
+    bool dcm = false;
+    bool fcm = false;
+    // If the HAL version is in device / framework compatibility matrix, whether it is required
+    // or not.
+    bool required = false;
+
+    // Return true if:
+    // - not a required HAL version; OR
+    // - required in device matrix and framework manifest;
+    // - required in framework matrix and device manifest.
+    bool meetsReqeuirement() const {
+        if (!required) return true;
+        if (dcm && !fm) return false;
+        if (fcm && !dm) return false;
+        return true;
+    }
+};
+
+std::ostream& operator<<(std::ostream& out, const TableRow& row) {
+    return out << (row.required ? "R" : " ") << (row.meetsReqeuirement() ? " " : "!")
+               << kColumnSeperator << (row.dm ? "DM" : "  ") << kColumnSeperator
+               << (row.fm ? "FM" : "  ") << kColumnSeperator << (row.fcm ? "FCM" : "   ")
+               << kColumnSeperator << (row.dcm ? "DCM" : "   ");
+}
+
+using RowMutator = std::function<void(TableRow*)>;
+using Table = std::map<std::string, TableRow>;
+
+// Insert each fqInstanceName foo@x.y::IFoo/instance to the table by inserting the key
+// if it does not exist and setting the corresponding indicator (as specified by "mutate").
+void insert(const HalManifest* manifest, Table* table, const RowMutator& mutate) {
+    if (manifest == nullptr) return;
+    manifest->forEachInstance([&](const auto& package, const auto& version, const auto& interface,
+                                  const auto& instance, bool* /* stop */) {
+        std::string key = fqInstanceName(package, VersionRange{version.majorVer, version.minorVer},
+                                         interface, instance);
+        mutate(&(*table)[key]);
+    });
+}
+
+void insert(const CompatibilityMatrix* matrix, Table* table, const RowMutator& mutate) {
+    if (matrix == nullptr) return;
+    matrix->forEachInstance([&](const auto& package, const auto& range, const auto& interface,
+                                const auto& instance, bool optional, bool* /* stop */) {
+        bool missed = false;
+        for (auto minorVer = range.minMinor; minorVer <= range.maxMinor; ++minorVer) {
+            std::string key = fqInstanceName(package, VersionRange{range.majorVer, minorVer},
+                                             interface, instance);
+            auto it = table->find(key);
+            if (it == table->end()) {
+                missed = true;
+            } else {
+                mutate(&it->second);
+                it->second.required = !optional;
+            }
+        }
+        if (missed) {
+            std::string key = fqInstanceName(package, range, interface, instance);
+            mutate(&(*table)[key]);
+        }
+    });
+}
+
+Table generateHalSummary(const HalManifest* vm, const HalManifest* fm,
+                         const CompatibilityMatrix* vcm, const CompatibilityMatrix* fcm) {
+    Table table;
+    insert(vm, &table, [](auto* row) { row->dm = true; });
+    insert(fm, &table, [](auto* row) { row->fm = true; });
+    insert(vcm, &table, [](auto* row) { row->dcm = true; });
+    insert(fcm, &table, [](auto* row) { row->fcm = true; });
+
+    return table;
+}
+
+static const std::vector<Option> gAvailableOptions{
+    {'h', "help", "Print help message.", [](auto) { return USAGE; }},
+    {'v', "verbose", "Dump detailed and raw content, including kernel configurations", [](auto o) {
+         o->verbose = true;
+         return OK;
+     }}};
 // A convenience binary to dump information available through libvintf.
-int main(int, char **) {
+int main(int argc, char** argv) {
+    ParsedOptions options;
+    Status status = parseOptions(argc, argv, gAvailableOptions, &options);
+    if (status == USAGE) usage(argv[0], gAvailableOptions);
+    if (status != OK) return status;
 
+    auto vm = VintfObject::GetDeviceHalManifest();
+    auto fm = VintfObject::GetFrameworkHalManifest();
+    auto vcm = VintfObject::GetDeviceCompatibilityMatrix();
+    auto fcm = VintfObject::GetFrameworkCompatibilityMatrix();
+    auto ki = VintfObject::GetRuntimeInfo();
+
+    if (!options.verbose) {
+        std::cout << "======== HALs =========" << std::endl
+                  << "R: required. (empty): optional or missing from matrices. "
+                  << "!: required and not in manifest." << std::endl
+                  << "DM: device manifest. FM: framework manifest." << std::endl
+                  << "FCM: framework compatibility matrix. DCM: device compatibility matrix."
+                  << std::endl
+                  << std::endl;
+        auto table = generateHalSummary(vm.get(), fm.get(), vcm.get(), fcm.get());
+
+        for (const auto& pair : table)
+            std::cout << pair.second << kColumnSeperator << pair.first << std::endl;
+
+        std::cout << std::endl;
+    }
+
+    SerializeFlags flags = SerializeFlag::EVERYTHING;
+    if (!options.verbose) {
+        flags |= SerializeFlag::NO_HALS;
+        flags |= SerializeFlag::NO_KERNEL;
+    }
     std::cout << "======== Device HAL Manifest =========" << std::endl;
-
-    std::shared_ptr<const HalManifest> vm = VintfObject::GetDeviceHalManifest();
-    if (vm != nullptr)
-        std::cout << gHalManifestConverter(*vm);
-
+    if (vm != nullptr) std::cout << gHalManifestConverter(*vm, flags);
     std::cout << "======== Framework HAL Manifest =========" << std::endl;
-
-    std::shared_ptr<const HalManifest> fm = VintfObject::GetFrameworkHalManifest();
-    if (fm != nullptr)
-        std::cout << gHalManifestConverter(*fm);
-
+    if (fm != nullptr) std::cout << gHalManifestConverter(*fm, flags);
     std::cout << "======== Device Compatibility Matrix =========" << std::endl;
-
-    std::shared_ptr<const CompatibilityMatrix> vcm = VintfObject::GetDeviceCompatibilityMatrix();
-    if (vcm != nullptr)
-        std::cout << gCompatibilityMatrixConverter(*vcm);
-
+    if (vcm != nullptr) std::cout << gCompatibilityMatrixConverter(*vcm, flags);
     std::cout << "======== Framework Compatibility Matrix =========" << std::endl;
-
-    std::shared_ptr<const CompatibilityMatrix> fcm = VintfObject::GetFrameworkCompatibilityMatrix();
-    if (fcm != nullptr)
-        std::cout << gCompatibilityMatrixConverter(*fcm);
+    if (fcm != nullptr) std::cout << gCompatibilityMatrixConverter(*fcm, flags);
 
     std::cout << "======== Runtime Info =========" << std::endl;
+    if (ki != nullptr) std::cout << dump(*ki, options.verbose);
 
-    std::shared_ptr<const RuntimeInfo> ki = VintfObject::GetRuntimeInfo();
-    if (ki != nullptr) std::cout << dump(*ki);
     std::cout << std::endl;
 
-    std::cout << "======== Compatibility check =========" << std::endl;
+    std::cout << "======== Summary =========" << std::endl;
     std::cout << "Device Manifest?    " << existString(vm != nullptr) << std::endl
               << "Device Matrix?      " << existString(vcm != nullptr) << std::endl
               << "Framework Manifest? " << existString(fm != nullptr) << std::endl