lshal: Add VINTF column

Fixes: 71555570
Test: lshal_test

Change-Id: I7ac5ef5a920d41c0f534195c764b1a300429a367
diff --git a/cmds/lshal/ListCommand.cpp b/cmds/lshal/ListCommand.cpp
index 5dbac1b..9c77f70 100644
--- a/cmds/lshal/ListCommand.cpp
+++ b/cmds/lshal/ListCommand.cpp
@@ -126,6 +126,67 @@
     return process;
 }
 
+bool match(const vintf::ManifestInstance& instance, const FqInstance& fqInstance,
+           vintf::TransportArch ta) {
+    // For hwbinder libs, allow missing arch in manifest.
+    // For passthrough libs, allow missing interface/instance in table.
+    return (ta.transport == instance.transport()) &&
+            (ta.transport == vintf::Transport::HWBINDER ||
+             vintf::contains(instance.arch(), ta.arch)) &&
+            (!fqInstance.hasInterface() || fqInstance.getInterface() == instance.interface()) &&
+            (!fqInstance.hasInstance() || fqInstance.getInstance() == instance.instance());
+}
+
+bool match(const vintf::MatrixInstance& instance, const FqInstance& fqInstance,
+           vintf::TransportArch /* ta */) {
+    return (!fqInstance.hasInterface() || fqInstance.getInterface() == instance.interface()) &&
+            (!fqInstance.hasInstance() || instance.matchInstance(fqInstance.getInstance()));
+}
+
+template <typename ObjectType>
+VintfInfo getVintfInfo(const std::shared_ptr<const ObjectType>& object,
+                       const FqInstance& fqInstance, vintf::TransportArch ta, VintfInfo value) {
+    bool found = false;
+    (void)object->forEachInstanceOfVersion(fqInstance.getPackage(), fqInstance.getVersion(),
+                                           [&](const auto& instance) {
+                                               found = match(instance, fqInstance, ta);
+                                               return !found; // continue if not found
+                                           });
+    return found ? value : VINTF_INFO_EMPTY;
+}
+
+std::shared_ptr<const vintf::HalManifest> ListCommand::getDeviceManifest() const {
+    return vintf::VintfObject::GetDeviceHalManifest();
+}
+
+std::shared_ptr<const vintf::CompatibilityMatrix> ListCommand::getDeviceMatrix() const {
+    return vintf::VintfObject::GetDeviceCompatibilityMatrix();
+}
+
+std::shared_ptr<const vintf::HalManifest> ListCommand::getFrameworkManifest() const {
+    return vintf::VintfObject::GetFrameworkHalManifest();
+}
+
+std::shared_ptr<const vintf::CompatibilityMatrix> ListCommand::getFrameworkMatrix() const {
+    return vintf::VintfObject::GetFrameworkCompatibilityMatrix();
+}
+
+VintfInfo ListCommand::getVintfInfo(const std::string& fqInstanceName,
+                                    vintf::TransportArch ta) const {
+    FqInstance fqInstance;
+    if (!fqInstance.setTo(fqInstanceName) &&
+        // Ignore interface / instance for passthrough libs
+        !fqInstance.setTo(splitFirst(fqInstanceName, ':').first)) {
+        err() << "Warning: Cannot parse '" << fqInstanceName << "'; no VINTF info." << std::endl;
+        return VINTF_INFO_EMPTY;
+    }
+
+    return lshal::getVintfInfo(getDeviceManifest(), fqInstance, ta, DEVICE_MANIFEST) |
+            lshal::getVintfInfo(getFrameworkManifest(), fqInstance, ta, FRAMEWORK_MANIFEST) |
+            lshal::getVintfInfo(getDeviceMatrix(), fqInstance, ta, DEVICE_MATRIX) |
+            lshal::getVintfInfo(getFrameworkMatrix(), fqInstance, ta, FRAMEWORK_MATRIX);
+}
+
 static bool scanBinderContext(pid_t pid,
         const std::string &contextName,
         std::function<void(const std::string&)> eachLine) {
@@ -269,6 +330,7 @@
         }
         for (TableEntry& entry : table) {
             entry.partition = getPartition(entry.serverPid);
+            entry.vintfInfo = getVintfInfo(entry.interfaceName, {entry.transport, entry.arch});
         }
     });
     // use a double for loop here because lshal doesn't care about efficiency.
@@ -777,6 +839,15 @@
     }, "Emit debug info from\nIBase::debug with empty options. Cannot be used with --neat.\n"
         "Writes to specified file if 'arg' is provided, otherwise stdout."});
 
+    mOptions.push_back({'V', "vintf", no_argument, v++, [](ListCommand* thiz, const char*) {
+        thiz->mSelectedColumns.push_back(TableColumnType::VINTF);
+        return OK;
+    }, "print VINTF info. This column contains a comma-separated list of:\n"
+       "    - DM: device manifest\n"
+       "    - DC: device compatibility matrix\n"
+       "    - FM: framework manifest\n"
+       "    - FC: framework compatibility matrix"});
+
     // long options without short alternatives
     mOptions.push_back({'\0', "init-vintf", no_argument, v++, [](ListCommand* thiz, const char* arg) {
         thiz->mVintf = true;
diff --git a/cmds/lshal/ListCommand.h b/cmds/lshal/ListCommand.h
index c35561d..87d93b5 100644
--- a/cmds/lshal/ListCommand.h
+++ b/cmds/lshal/ListCommand.h
@@ -28,6 +28,7 @@
 #include <android/hidl/manager/1.0/IServiceManager.h>
 #include <hidl-util/FqInstance.h>
 #include <vintf/HalManifest.h>
+#include <vintf/VintfObject.h>
 
 #include "Command.h"
 #include "NullableOStream.h"
@@ -87,7 +88,9 @@
 
 protected:
     Status parseArgs(const Arg &arg);
+    // Retrieve first-hand information
     Status fetch();
+    // Retrieve derived information base on existing table
     virtual void postprocess();
     Status dump();
     void putEntry(TableEntrySource source, TableEntry &&entry);
@@ -122,6 +125,13 @@
     virtual Partition getPartition(pid_t pid);
     Partition resolvePartition(Partition processPartition, const FqInstance &fqInstance) const;
 
+    VintfInfo getVintfInfo(const std::string &fqInstanceName, vintf::TransportArch ta) const;
+    // Allow to mock these functions for testing.
+    virtual std::shared_ptr<const vintf::HalManifest> getDeviceManifest() const;
+    virtual std::shared_ptr<const vintf::CompatibilityMatrix> getDeviceMatrix() const;
+    virtual std::shared_ptr<const vintf::HalManifest> getFrameworkManifest() const;
+    virtual std::shared_ptr<const vintf::CompatibilityMatrix> getFrameworkMatrix() const;
+
     void forEachTable(const std::function<void(Table &)> &f);
     void forEachTable(const std::function<void(const Table &)> &f) const;
 
diff --git a/cmds/lshal/TableEntry.cpp b/cmds/lshal/TableEntry.cpp
index fbd17ee..0154a2e 100644
--- a/cmds/lshal/TableEntry.cpp
+++ b/cmds/lshal/TableEntry.cpp
@@ -16,6 +16,9 @@
 #define LOG_TAG "lshal"
 #include <android-base/logging.h>
 
+#include <map>
+
+#include <android-base/strings.h>
 #include <hidl-hash/Hash.h>
 #include <vintf/parse_string.h>
 
@@ -58,6 +61,7 @@
         case TableColumnType::THREADS:          return "Thread Use";
         case TableColumnType::RELEASED:         return "R";
         case TableColumnType::HASH:             return "Hash";
+        case TableColumnType::VINTF:            return "VINTF";
         default:
             LOG(FATAL) << __func__ << "Should not reach here. " << static_cast<int>(type);
             return "";
@@ -88,6 +92,8 @@
             return isReleased();
         case TableColumnType::HASH:
             return hash;
+        case TableColumnType::VINTF:
+            return getVintfInfo();
         default:
             LOG(FATAL) << __func__ << "Should not reach here. " << static_cast<int>(type);
             return "";
@@ -106,6 +112,23 @@
     return "Y"; // released
 }
 
+std::string TableEntry::getVintfInfo() const {
+    static const std::map<VintfInfo, std::string> values{
+            {DEVICE_MANIFEST, "DM"},
+            {DEVICE_MATRIX, "DC"},
+            {FRAMEWORK_MANIFEST, "FM"},
+            {FRAMEWORK_MATRIX, "FC"},
+    };
+    std::vector<std::string> ret;
+    for (const auto& pair : values) {
+        if (vintfInfo & pair.first) {
+            ret.push_back(pair.second);
+        }
+    }
+    auto joined = base::Join(ret, ',');
+    return joined.empty() ? "X" : joined;
+}
+
 TextTable Table::createTextTable(bool neat,
     const std::function<std::string(const std::string&)>& emitDebugInfo) const {
 
diff --git a/cmds/lshal/TableEntry.h b/cmds/lshal/TableEntry.h
index 08a1303..c9a6a23 100644
--- a/cmds/lshal/TableEntry.h
+++ b/cmds/lshal/TableEntry.h
@@ -54,8 +54,18 @@
     THREADS,
     RELEASED,
     HASH,
+    VINTF,
 };
 
+enum : unsigned int {
+    VINTF_INFO_EMPTY = 0,
+    DEVICE_MANIFEST = 1 << 0,
+    DEVICE_MATRIX = 1 << 1,
+    FRAMEWORK_MANIFEST = 1 << 2,
+    FRAMEWORK_MATRIX = 1 << 3,
+};
+using VintfInfo = unsigned int;
+
 enum {
     NO_PID = -1,
     NO_PTR = 0
@@ -75,6 +85,7 @@
     // empty: unknown, all zeros: unreleased, otherwise: released
     std::string hash{};
     Partition partition{Partition::UNKNOWN};
+    VintfInfo vintfInfo{VINTF_INFO_EMPTY};
 
     static bool sortByInterfaceName(const TableEntry &a, const TableEntry &b) {
         return a.interfaceName < b.interfaceName;
@@ -93,6 +104,8 @@
 
     std::string isReleased() const;
 
+    std::string getVintfInfo() const;
+
     std::string getField(TableColumnType type) const;
 
     bool operator==(const TableEntry& other) const;
diff --git a/cmds/lshal/test.cpp b/cmds/lshal/test.cpp
index 2af0199..501c04d 100644
--- a/cmds/lshal/test.cpp
+++ b/cmds/lshal/test.cpp
@@ -45,7 +45,12 @@
 using ::android::hardware::hidl_string;
 using ::android::hardware::hidl_vec;
 using android::vintf::Arch;
+using android::vintf::CompatibilityMatrix;
+using android::vintf::gCompatibilityMatrixConverter;
+using android::vintf::gHalManifestConverter;
+using android::vintf::HalManifest;
 using android::vintf::Transport;
+using android::vintf::VintfObject;
 
 using InstanceDebugInfo = IServiceManager::InstanceDebugInfo;
 
@@ -209,6 +214,11 @@
     MOCK_CONST_METHOD2(getPidInfo, bool(pid_t, PidInfo*));
     MOCK_CONST_METHOD1(parseCmdline, std::string(pid_t));
     MOCK_METHOD1(getPartition, Partition(pid_t));
+
+    MOCK_CONST_METHOD0(getDeviceManifest, std::shared_ptr<const vintf::HalManifest>());
+    MOCK_CONST_METHOD0(getDeviceMatrix, std::shared_ptr<const vintf::CompatibilityMatrix>());
+    MOCK_CONST_METHOD0(getFrameworkManifest, std::shared_ptr<const vintf::HalManifest>());
+    MOCK_CONST_METHOD0(getFrameworkMatrix, std::shared_ptr<const vintf::CompatibilityMatrix>());
 };
 
 class ListParseArgsTest : public ::testing::Test {
@@ -337,6 +347,15 @@
             });
         }));
         ON_CALL(*mockList, getPartition(_)).WillByDefault(Return(Partition::VENDOR));
+
+        ON_CALL(*mockList, getDeviceManifest())
+                .WillByDefault(Return(VintfObject::GetDeviceHalManifest()));
+        ON_CALL(*mockList, getDeviceMatrix())
+                .WillByDefault(Return(VintfObject::GetDeviceCompatibilityMatrix()));
+        ON_CALL(*mockList, getFrameworkManifest())
+                .WillByDefault(Return(VintfObject::GetFrameworkHalManifest()));
+        ON_CALL(*mockList, getFrameworkMatrix())
+                .WillByDefault(Return(VintfObject::GetFrameworkCompatibilityMatrix()));
     }
 
     void initMockServiceManager() {
@@ -656,6 +675,87 @@
     EXPECT_THAT(err.str(), HasSubstr("Unrecognized HAL type: a"));
 }
 
+TEST_F(ListTest, Vintf) {
+    std::string deviceManifestXml =
+            "<manifest version=\"1.0\" type=\"device\">\n"
+            "    <hal>\n"
+            "        <name>a.h.foo1</name>\n"
+            "        <transport>hwbinder</transport>\n"
+            "        <fqname>@1.0::IFoo/1</fqname>\n"
+            "    </hal>\n"
+            "    <hal>\n"
+            "        <name>a.h.foo3</name>\n"
+            "        <transport arch=\"32+64\">passthrough</transport>\n"
+            "        <fqname>@3.0::IFoo/3</fqname>\n"
+            "    </hal>\n"
+            "</manifest>\n";
+    std::string frameworkManifestXml =
+            "<manifest version=\"1.0\" type=\"framework\">\n"
+            "    <hal>\n"
+            "        <name>a.h.foo5</name>\n"
+            "        <transport arch=\"32\">passthrough</transport>\n"
+            "        <fqname>@5.0::IFoo/5</fqname>\n"
+            "    </hal>\n"
+            "</manifest>\n";
+    std::string deviceMatrixXml =
+            "<compatibility-matrix version=\"1.0\" type=\"device\">\n"
+            "    <hal>\n"
+            "        <name>a.h.foo5</name>\n"
+            "        <version>5.0</version>\n"
+            "        <interface>\n"
+            "            <name>IFoo</name>\n"
+            "            <instance>5</instance>\n"
+            "        </interface>\n"
+            "    </hal>\n"
+            "</compatibility-matrix>\n";
+    std::string frameworkMatrixXml =
+            "<compatibility-matrix version=\"1.0\" type=\"framework\">\n"
+            "    <hal>\n"
+            "        <name>a.h.foo1</name>\n"
+            "        <version>1.0</version>\n"
+            "        <interface>\n"
+            "            <name>IFoo</name>\n"
+            "            <instance>1</instance>\n"
+            "        </interface>\n"
+            "    </hal>\n"
+            "    <hal>\n"
+            "        <name>a.h.foo3</name>\n"
+            "        <version>3.0</version>\n"
+            "        <interface>\n"
+            "            <name>IFoo</name>\n"
+            "            <instance>3</instance>\n"
+            "        </interface>\n"
+            "    </hal>\n"
+            "</compatibility-matrix>\n";
+
+    std::string expected = "DM,FC a.h.foo1@1.0::IFoo/1\n"
+                           "X     a.h.foo2@2.0::IFoo/2\n"
+                           "DM,FC a.h.foo3@3.0::IFoo/3\n"
+                           "X     a.h.foo4@4.0::IFoo/4\n"
+                           "DC,FM a.h.foo5@5.0::IFoo/5\n"
+                           "X     a.h.foo6@6.0::IFoo/6\n";
+
+    auto deviceManifest = std::make_shared<HalManifest>();
+    auto frameworkManifest = std::make_shared<HalManifest>();
+    auto deviceMatrix = std::make_shared<CompatibilityMatrix>();
+    auto frameworkMatrix = std::make_shared<CompatibilityMatrix>();
+
+    ASSERT_TRUE(gHalManifestConverter(deviceManifest.get(), deviceManifestXml));
+    ASSERT_TRUE(gHalManifestConverter(frameworkManifest.get(), frameworkManifestXml));
+    ASSERT_TRUE(gCompatibilityMatrixConverter(deviceMatrix.get(), deviceMatrixXml));
+    ASSERT_TRUE(gCompatibilityMatrixConverter(frameworkMatrix.get(), frameworkMatrixXml));
+
+    ON_CALL(*mockList, getDeviceManifest()).WillByDefault(Return(deviceManifest));
+    ON_CALL(*mockList, getDeviceMatrix()).WillByDefault(Return(deviceMatrix));
+    ON_CALL(*mockList, getFrameworkManifest()).WillByDefault(Return(frameworkManifest));
+    ON_CALL(*mockList, getFrameworkMatrix()).WillByDefault(Return(frameworkMatrix));
+
+    optind = 1; // mimic Lshal::parseArg()
+    EXPECT_EQ(0u, mockList->main(createArg({"lshal", "-Vi", "--neat"})));
+    EXPECT_THAT(out.str(), HasSubstr(expected));
+    EXPECT_EQ("", err.str());
+}
+
 class HelpTest : public ::testing::Test {
 public:
     void SetUp() override {