Merge changes from topic "recovery-aidl" am: 18adf7deec

Original change: https://android-review.googlesource.com/c/platform/system/libvintf/+/1906172

Change-Id: I2f479d18de7600d1d33d13362ad039c2fed67070
diff --git a/Android.bp b/Android.bp
index 67a9edd..7e09272 100644
--- a/Android.bp
+++ b/Android.bp
@@ -110,6 +110,7 @@
         host: {
             srcs: [
                 "RuntimeInfo-host.cpp",
+                "VintfObjectRecovery.cpp",
             ],
         },
         android: {
@@ -117,6 +118,11 @@
                 "RuntimeInfo-target.cpp",
             ],
         },
+        recovery: {
+            srcs: [
+                "VintfObjectRecovery.cpp",
+            ],
+        },
     }
 }
 
diff --git a/VintfObject.cpp b/VintfObject.cpp
index c9460a0..8d9df31 100644
--- a/VintfObject.cpp
+++ b/VintfObject.cpp
@@ -30,6 +30,7 @@
 #include <hidl/metadata.h>
 
 #include "CompatibilityMatrix.h"
+#include "VintfObjectUtils.h"
 #include "constants-private.h"
 #include "parse_string.h"
 #include "parse_xml.h"
@@ -50,29 +51,6 @@
 static constexpr bool kIsTarget = false;
 #endif
 
-template <typename T, typename F>
-static std::shared_ptr<const T> Get(const char* id, LockedSharedPtr<T>* ptr,
-                                    const F& fetchAllInformation) {
-    std::unique_lock<std::mutex> _lock(ptr->mutex);
-    if (!ptr->fetchedOnce) {
-        LOG(INFO) << id << ": Reading VINTF information.";
-        ptr->object = std::make_unique<T>();
-        std::string error;
-        status_t status = fetchAllInformation(ptr->object.get(), &error);
-        if (status == OK) {
-            ptr->fetchedOnce = true;
-            LOG(INFO) << id << ": Successfully processed VINTF information";
-        } else {
-            // Doubled because a malformed error std::string might cause us to
-            // lose the status.
-            LOG(ERROR) << id << ": status from fetching VINTF information: " << status;
-            LOG(ERROR) << id << ": " << status << " VINTF parse error: " << error;
-            ptr->object = nullptr;  // frees the old object
-        }
-    }
-    return ptr->object;
-}
-
 static std::unique_ptr<FileSystem> createDefaultFileSystem() {
     std::unique_ptr<FileSystem> fileSystem;
     if (kIsTarget) {
@@ -215,8 +193,9 @@
 }
 
 // Load and combine all of the manifests in a directory
+// If forceSchemaType, all fragment manifests are coerced into manifest->type().
 status_t VintfObject::addDirectoryManifests(const std::string& directory, HalManifest* manifest,
-                                            std::string* error) {
+                                            bool forceSchemaType, std::string* error) {
     std::vector<std::string> fileNames;
     status_t err = getFileSystem()->listFiles(directory, &fileNames, error);
     // if the directory isn't there, that's okay
@@ -235,6 +214,10 @@
         err = fetchOneHalManifest(directory + file, &fragmentManifest, error);
         if (err != OK) return err;
 
+        if (forceSchemaType) {
+            fragmentManifest.setType(manifest->type());
+        }
+
         if (!manifest->addAll(&fragmentManifest, error)) {
             if (error) {
                 error->insert(0, "Cannot add manifest fragment " + directory + file + ": ");
@@ -263,7 +246,8 @@
 
     if (vendorStatus == OK) {
         *out = std::move(vendorManifest);
-        status_t fragmentStatus = addDirectoryManifests(kVendorManifestFragmentDir, out, error);
+        status_t fragmentStatus = addDirectoryManifests(kVendorManifestFragmentDir, out,
+                                                        false /* forceSchemaType*/, error);
         if (fragmentStatus != OK) {
             return fragmentStatus;
         }
@@ -284,13 +268,15 @@
                 return UNKNOWN_ERROR;
             }
         }
-        return addDirectoryManifests(kOdmManifestFragmentDir, out, error);
+        return addDirectoryManifests(kOdmManifestFragmentDir, out, false /* forceSchemaType */,
+                                     error);
     }
 
     // vendorStatus != OK, "out" is not changed.
     if (odmStatus == OK) {
         *out = std::move(odmManifest);
-        return addDirectoryManifests(kOdmManifestFragmentDir, out, error);
+        return addDirectoryManifests(kOdmManifestFragmentDir, out, false /* forceSchemaType */,
+                                     error);
     }
 
     // Use legacy /vendor/manifest.xml
@@ -397,7 +383,8 @@
 status_t VintfObject::fetchUnfilteredFrameworkHalManifest(HalManifest* out, std::string* error) {
     auto systemEtcStatus = fetchOneHalManifest(kSystemManifest, out, error);
     if (systemEtcStatus == OK) {
-        auto dirStatus = addDirectoryManifests(kSystemManifestFragmentDir, out, error);
+        auto dirStatus = addDirectoryManifests(kSystemManifestFragmentDir, out,
+                                               false /* forceSchemaType */, error);
         if (dirStatus != OK) {
             return dirStatus;
         }
@@ -421,7 +408,8 @@
                 }
             }
 
-            auto fragmentStatus = addDirectoryManifests(frags, out, error);
+            auto fragmentStatus =
+                addDirectoryManifests(frags, out, false /* forceSchemaType */, error);
             if (fragmentStatus != OK) {
                 return fragmentStatus;
             }
diff --git a/VintfObjectRecovery.cpp b/VintfObjectRecovery.cpp
new file mode 100644
index 0000000..cc44e1e
--- /dev/null
+++ b/VintfObjectRecovery.cpp
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <vintf/VintfObjectRecovery.h>
+
+#include "VintfObjectUtils.h"
+#include "constants-private.h"
+
+using std::placeholders::_1;
+using std::placeholders::_2;
+
+namespace android::vintf {
+
+using details::Get;
+using details::kSystemManifest;
+using details::kSystemManifestFragmentDir;
+
+std::shared_ptr<VintfObjectRecovery> VintfObjectRecovery::GetInstance() {
+    static details::LockedSharedPtr<VintfObjectRecovery> sInstance{};
+    std::unique_lock<std::mutex> lock(sInstance.mutex);
+    if (sInstance.object == nullptr) {
+        std::unique_ptr<VintfObjectRecovery> uptr =
+            VintfObjectRecovery::Builder().build<VintfObjectRecovery>();
+        sInstance.object = std::shared_ptr<VintfObjectRecovery>(uptr.release());
+    }
+    return sInstance.object;
+}
+
+std::shared_ptr<const HalManifest> VintfObjectRecovery::getRecoveryHalManifest() {
+    return Get(__func__, &mRecoveryManifest,
+               std::bind(&VintfObjectRecovery::fetchRecoveryHalManifest, this, _1, _2));
+}
+
+// All manifests are installed under /system/etc/vintf.
+// There may be mixed framework and device manifests under that directory. Treat them all
+// as device manifest fragments.
+// Priority:
+// 1. /system/etc/vintf/manifest.xml
+//    + /system/etc/vintf/manifest/*.xml if they exist
+status_t VintfObjectRecovery::fetchRecoveryHalManifest(HalManifest* out, std::string* error) {
+    HalManifest manifest;
+    status_t systemEtcStatus = fetchOneHalManifest(kSystemManifest, &manifest, error);
+    if (systemEtcStatus != OK && systemEtcStatus != NAME_NOT_FOUND) {
+        return systemEtcStatus;
+    }
+    // Merge |manifest| to |out| only if the main manifest is found.
+    if (systemEtcStatus == OK) {
+        *out = std::move(manifest);
+    }
+    out->setType(SchemaType::DEVICE);
+    status_t fragmentStatus =
+        addDirectoryManifests(kSystemManifestFragmentDir, out, true /* forceSchemaType */, error);
+    if (fragmentStatus != OK) {
+        return fragmentStatus;
+    }
+    return OK;
+}
+
+VintfObjectRecovery::Builder::Builder()
+    : VintfObjectBuilder(std::unique_ptr<VintfObject>(new VintfObjectRecovery())) {}
+
+}  // namespace android::vintf
diff --git a/VintfObjectUtils.h b/VintfObjectUtils.h
new file mode 100644
index 0000000..ac55200
--- /dev/null
+++ b/VintfObjectUtils.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// Special utils for VintfObject(s).
+
+#pragma once
+
+// This is okay because it is a header private to libvintf. Do not do this in exported headers!
+#include <android-base/logging.h>
+
+#include <vintf/VintfObject.h>
+
+namespace android {
+namespace vintf {
+namespace details {
+
+template <typename T, typename F>
+std::shared_ptr<const T> Get(const char* id, LockedSharedPtr<T>* ptr,
+                             const F& fetchAllInformation) {
+    std::unique_lock<std::mutex> _lock(ptr->mutex);
+    if (!ptr->fetchedOnce) {
+        LOG(INFO) << id << ": Reading VINTF information.";
+        ptr->object = std::make_unique<T>();
+        std::string error;
+        status_t status = fetchAllInformation(ptr->object.get(), &error);
+        if (status == OK) {
+            ptr->fetchedOnce = true;
+            LOG(INFO) << id << ": Successfully processed VINTF information";
+        } else {
+            // Doubled because a malformed error std::string might cause us to
+            // lose the status.
+            LOG(ERROR) << id << ": status from fetching VINTF information: " << status;
+            LOG(ERROR) << id << ": " << status << " VINTF parse error: " << error;
+            ptr->object = nullptr;  // frees the old object
+        }
+    }
+    return ptr->object;
+}
+
+}  // namespace details
+}  // namespace vintf
+}  // namespace android
diff --git a/include/vintf/VintfObject.h b/include/vintf/VintfObject.h
index 80e6441..050de80 100644
--- a/include/vintf/VintfObject.h
+++ b/include/vintf/VintfObject.h
@@ -91,6 +91,7 @@
 
 namespace testing {
 class VintfObjectTestBase;
+class VintfObjectRecoveryTest;
 class VintfObjectRuntimeInfoTest;
 class VintfObjectCompatibleTest;
 }  // namespace testing
@@ -286,6 +287,7 @@
 
     // Expose functions for testing and recovery
     friend class testing::VintfObjectTestBase;
+    friend class testing::VintfObjectRecoveryTest;
     friend class testing::VintfObjectRuntimeInfoTest;
     friend class testing::VintfObjectCompatibleTest;
 
@@ -341,7 +343,7 @@
     static std::shared_ptr<const RuntimeInfo> GetRuntimeInfo(
         RuntimeInfo::FetchFlags flags = RuntimeInfo::FetchFlag::ALL);
 
-   private:
+   protected:
     status_t getCombinedFrameworkMatrix(const std::shared_ptr<const HalManifest>& deviceManifest,
                                         Level kernelLevel, CompatibilityMatrix* out,
                                         std::string* error = nullptr);
@@ -350,7 +352,7 @@
     status_t getOneMatrix(const std::string& path, CompatibilityMatrix* out,
                           std::string* error = nullptr);
     status_t addDirectoryManifests(const std::string& directory, HalManifest* manifests,
-                                   std::string* error = nullptr);
+                                   bool ignoreSchemaType, std::string* error);
     status_t fetchDeviceHalManifest(HalManifest* out, std::string* error = nullptr);
     status_t fetchDeviceMatrix(CompatibilityMatrix* out, std::string* error = nullptr);
     status_t fetchOdmHalManifest(HalManifest* out, std::string* error = nullptr);
@@ -392,7 +394,7 @@
         Builder();
     };
 
-   private:
+   protected:
     /* Empty VintfObject without any dependencies. Used by Builder and subclasses. */
     VintfObject() = default;
 };
diff --git a/include/vintf/VintfObjectRecovery.h b/include/vintf/VintfObjectRecovery.h
new file mode 100644
index 0000000..64be749
--- /dev/null
+++ b/include/vintf/VintfObjectRecovery.h
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#if defined(__ANDROID__) && !defined(__ANDROID_RECOVERY__)
+#error VintfObjectRecovery is only supported in recovery and host.
+#endif
+
+#include <vintf/VintfObject.h>
+
+namespace android::vintf {
+
+/**
+ * A special variant of VintfObject for the recovery ramdisk.
+ *
+ * In recovery ramdisk, there is no Treble split. All VINTF data is stored in /system/etc/vintf.
+ *
+ * All getDevice* / getFramework* functions return nullptr. Instead, getRecovery* should be
+ * used instead.
+ */
+class VintfObjectRecovery : public VintfObject {
+   public:
+    /*
+     * Get global instance. Results are cached.
+     */
+    static std::shared_ptr<VintfObjectRecovery> GetInstance();
+
+    /*
+     * Return the API that access the HAL manifests built from component pieces on the
+     * recovery partition.
+     *
+     * Returned manifest has SchemaType::DEVICE.
+     *
+     * No SKU manifest support.
+     */
+    std::shared_ptr<const HalManifest> getRecoveryHalManifest();
+
+    // Not supported. Call getRecoveryHalManifest instead.
+    std::shared_ptr<const HalManifest> getDeviceHalManifest() override { return nullptr; }
+    std::shared_ptr<const HalManifest> getFrameworkHalManifest() override { return nullptr; }
+
+    // Not supported. No compatibility check in recovery because there is no Treble split.
+    std::shared_ptr<const CompatibilityMatrix> getDeviceCompatibilityMatrix() override {
+        return nullptr;
+    }
+    std::shared_ptr<const CompatibilityMatrix> getFrameworkCompatibilityMatrix() override {
+        return nullptr;
+    }
+
+    /** Builder of VintfObjectRecovery. See VintfObjectBuilder for details. */
+    class Builder : public details::VintfObjectBuilder {
+       public:
+        Builder();
+    };
+
+   private:
+    VintfObjectRecovery() = default;
+    status_t fetchRecoveryHalManifest(HalManifest* out, std::string* error);
+    details::LockedSharedPtr<HalManifest> mRecoveryManifest;
+};
+
+}  // namespace android::vintf
diff --git a/test/Android.bp b/test/Android.bp
index dfd49dd..7096f89 100644
--- a/test/Android.bp
+++ b/test/Android.bp
@@ -125,3 +125,23 @@
         "VintfFmTest.cpp",
     ],
 }
+
+cc_test_host {
+    name: "vintf_object_recovery_test",
+    defaults: [
+        "libvintf-defaults",
+        "libvintf_static_user_defaults",
+    ],
+    static_libs: [
+        "libgmock",
+        "libvintf",
+        "libutils",
+    ],
+    header_libs: [
+        "libvintf_local_headers",
+    ],
+    srcs: [
+        "RuntimeInfo-fake.cpp",
+        "VintfObjectRecoveryTest.cpp",
+    ],
+}
diff --git a/test/VintfObjectRecoveryTest.cpp b/test/VintfObjectRecoveryTest.cpp
new file mode 100644
index 0000000..381d805
--- /dev/null
+++ b/test/VintfObjectRecoveryTest.cpp
@@ -0,0 +1,309 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <vintf/VintfObjectRecovery.h>
+#include <vintf/parse_string.h>
+
+#include "constants-private.h"
+#include "test_constants.h"
+#include "utils-fake.h"
+
+using android::base::ConsumePrefix;
+using android::base::StringPrintf;
+using testing::_;
+using testing::Combine;
+using testing::Invoke;
+using testing::IsEmpty;
+using testing::Mock;
+using testing::NiceMock;
+using testing::StartsWith;
+using testing::StrEq;
+using testing::TestParamInfo;
+using testing::UnorderedElementsAre;
+using testing::ValuesIn;
+
+namespace android::vintf::testing {
+
+using details::kSystemManifest;
+using details::kSystemManifestFragmentDir;
+using details::MockFileSystemWithError;
+using details::MockPropertyFetcher;
+using details::MockRuntimeInfo;
+using details::MockRuntimeInfoFactory;
+
+template <typename T>
+using StatusOr = std::variant<status_t, T>;
+
+using DirectoryContent = std::map<std::string, StatusOr<std::string>>;
+
+using OptionalType = std::optional<SchemaType>;
+std::vector<OptionalType> OptionalTypes() {
+    return {std::nullopt, SchemaType::DEVICE, SchemaType::FRAMEWORK};
+}
+
+std::string OptionalTypeToString(const OptionalType& optionalType) {
+    if (!optionalType.has_value()) return "broken";
+    return to_string(*optionalType);
+}
+
+constexpr const char* kMainFmt = R"(<manifest %s type="%s">
+    <hal format="aidl">
+        <name>android.hardware.main</name>
+        <fqname>IMain/default</fqname>
+    </hal>
+</manifest>
+)";
+
+constexpr const char* kFragment1Fmt = R"(<manifest %s type="%s">
+    <hal format="aidl">
+        <name>android.hardware.fragment1</name>
+        <fqname>IFragment/default</fqname>
+    </hal>
+</manifest>
+)";
+
+constexpr const char* kFragment2Fmt = R"(<manifest %s type="%s">
+    <hal format="aidl">
+        <name>android.hardware.fragment2</name>
+        <fqname>IFragment/default</fqname>
+    </hal>
+</manifest>
+)";
+
+std::string formatManifest(const char* fmt, const OptionalType& optionalType) {
+    if (!optionalType.has_value()) {
+        return "(broken manifest)";
+    }
+    return StringPrintf(fmt, kMetaVersionStr.c_str(), to_string(*optionalType).c_str());
+}
+
+using VintfObjectRecoveryTestParam = std::tuple<OptionalType, OptionalType, OptionalType>;
+class VintfObjectRecoveryTest : public ::testing::TestWithParam<VintfObjectRecoveryTestParam> {
+   public:
+    virtual void SetUp() {
+        vintfObject = VintfObjectRecovery::Builder()
+                          .setFileSystem(std::make_unique<NiceMock<MockFileSystemWithError>>())
+                          .setRuntimeInfoFactory(std::make_unique<NiceMock<MockRuntimeInfoFactory>>(
+                              std::make_shared<NiceMock<MockRuntimeInfo>>()))
+                          .setPropertyFetcher(std::make_unique<NiceMock<MockPropertyFetcher>>())
+                          .build<VintfObjectRecovery>();
+        auto [mainType, fragType1, fragType2] = GetParam();
+        main = formatManifest(kMainFmt, mainType);
+        frag1 = formatManifest(kFragment1Fmt, fragType1);
+        frag2 = formatManifest(kFragment2Fmt, fragType2);
+    }
+    virtual void TearDown() { Mock::VerifyAndClear(&fs()); }
+
+    MockFileSystemWithError& fs() {
+        return static_cast<MockFileSystemWithError&>(*vintfObject->getFileSystem());
+    }
+
+    void setUpManifests(const StatusOr<std::string>& mainContent,
+                        const StatusOr<DirectoryContent>& frags) {
+        // By default, no files exist in the file system.
+        ON_CALL(fs(), listFiles(_, _, _)).WillByDefault(Return(NAME_NOT_FOUND));
+        ON_CALL(fs(), fetch(_, _, _))
+            .WillByDefault(Invoke([](const auto& path, auto*, auto* error) {
+                if (error != nullptr) {
+                    *error = "fetch " + path + ": cannot be found on empty filesystem: " +
+                             statusToString(NAME_NOT_FOUND);
+                }
+                return NAME_NOT_FOUND;
+            }));
+        ON_CALL(fs(), fetch(StrEq(kSystemManifest), _, _))
+            .WillByDefault(Invoke([=](const auto& path, auto* content, auto* error) -> status_t {
+                if (std::holds_alternative<status_t>(mainContent)) {
+                    if (error != nullptr) {
+                        *error = "fetch " + path + ": set to return " +
+                                 statusToString(std::get<status_t>(mainContent));
+                    }
+                    return std::get<status_t>(mainContent);
+                }
+                *content = std::get<std::string>(mainContent);
+                return OK;
+            }));
+        ON_CALL(fs(), listFiles(StrEq(kSystemManifestFragmentDir), _, _))
+            .WillByDefault(Invoke([=](const std::string& path, std::vector<std::string>* out,
+                                      auto* error) -> status_t {
+                if (std::holds_alternative<status_t>(frags)) {
+                    if (error != nullptr) {
+                        *error = "list " + path + ": set to return " +
+                                 statusToString(std::get<status_t>(frags));
+                    }
+                    return std::get<status_t>(frags);
+                }
+                for (const auto& [name, statusOrFile] : std::get<DirectoryContent>(frags)) {
+                    out->push_back(name);
+                }
+                return OK;
+            }));
+        ON_CALL(fs(), fetch(StartsWith(kSystemManifestFragmentDir), _, _))
+            .WillByDefault(Invoke([=](const auto& path, auto* content, auto* error) -> status_t {
+                if (std::holds_alternative<status_t>(frags)) {
+                    if (error != nullptr) {
+                        *error = "fetch " + path + ": for dir, set to return " +
+                                 statusToString(std::get<status_t>(frags));
+                    }
+                    return std::get<status_t>(frags);
+                }
+                const auto& directoryContent = std::get<DirectoryContent>(frags);
+                std::string_view subpath = path;
+                bool consumed = ConsumePrefix(&subpath, kSystemManifestFragmentDir);
+                EXPECT_TRUE(consumed)
+                    << path << " does not start with " << kSystemManifestFragmentDir;
+                auto it = directoryContent.find(std::string(subpath));
+                if (it == directoryContent.end()) {
+                    if (error != nullptr) {
+                        *error = "fetch " + path +
+                                 ": not in DirectoryContent: " + statusToString(NAME_NOT_FOUND);
+                    }
+                    return NAME_NOT_FOUND;
+                }
+
+                const auto& [name, statusOrFile] = *it;
+                if (std::holds_alternative<status_t>(statusOrFile)) {
+                    *error = "fetch " + path + ": for file, set to return " +
+                             statusToString(std::get<status_t>(statusOrFile));
+                    return std::get<status_t>(statusOrFile);
+                }
+                *content = std::get<std::string>(statusOrFile);
+                return OK;
+            }));
+    }
+
+    static std::string ParamToString(const TestParamInfo<ParamType>& info) {
+        auto [mainType, fragType1, fragType2] = info.param;
+        auto s = "main_" + OptionalTypeToString(mainType);
+        s += "_frag1_" + OptionalTypeToString(fragType1);
+        s += "_frag2_" + OptionalTypeToString(fragType2);
+        return s;
+    }
+
+    std::unique_ptr<VintfObjectRecovery> vintfObject;
+    std::string main;
+    std::string frag1;
+    std::string frag2;
+};
+
+TEST_P(VintfObjectRecoveryTest, Empty) {
+    setUpManifests(NAME_NOT_FOUND, NAME_NOT_FOUND);
+    auto manifest = vintfObject->getRecoveryHalManifest();
+    ASSERT_NE(nullptr, manifest);
+    auto hals = manifest->getHalNames();
+    EXPECT_THAT(hals, IsEmpty());
+}
+
+TEST_P(VintfObjectRecoveryTest, InaccessibleMainManifest) {
+    setUpManifests(UNKNOWN_ERROR, NAME_NOT_FOUND);
+    auto manifest = vintfObject->getRecoveryHalManifest();
+    EXPECT_EQ(nullptr, manifest);
+}
+
+TEST_P(VintfObjectRecoveryTest, MainManifestOnly) {
+    auto [mainType, fragType1, fragType2] = GetParam();
+    setUpManifests(main, NAME_NOT_FOUND);
+    auto manifest = vintfObject->getRecoveryHalManifest();
+    if (!mainType.has_value()) {  // main manifest is broken
+        EXPECT_EQ(nullptr, manifest);
+        return;
+    }
+    ASSERT_NE(nullptr, manifest);
+    EXPECT_THAT(manifest->getHalNames(), UnorderedElementsAre("android.hardware.main"));
+}
+
+TEST_P(VintfObjectRecoveryTest, MainManifestAndDirectoryOnly) {
+    auto [mainType, fragType1, fragType2] = GetParam();
+    setUpManifests(main, {});
+    auto manifest = vintfObject->getRecoveryHalManifest();
+    if (!mainType.has_value()) {  // main manifest is broken
+        EXPECT_EQ(nullptr, manifest);
+        return;
+    }
+    ASSERT_NE(nullptr, manifest);
+    EXPECT_THAT(manifest->getHalNames(), UnorderedElementsAre("android.hardware.main"));
+}
+
+TEST_P(VintfObjectRecoveryTest, MainManifestAndInaccessibleFragment) {
+    setUpManifests(main, DirectoryContent{{"frag1.xml", UNKNOWN_ERROR}});
+    auto manifest = vintfObject->getRecoveryHalManifest();
+    EXPECT_EQ(nullptr, manifest);
+}
+
+TEST_P(VintfObjectRecoveryTest, MainManifestAndFragments) {
+    auto [mainType, fragType1, fragType2] = GetParam();
+    setUpManifests(main, DirectoryContent{{"frag1.xml", frag1}, {"frag2.xml", frag2}});
+    auto manifest = vintfObject->getRecoveryHalManifest();
+    if (!mainType.has_value() || !fragType1.has_value() || !fragType2.has_value()) {
+        // some manifest(s) are broken
+        EXPECT_EQ(nullptr, manifest);
+        return;
+    }
+    ASSERT_NE(nullptr, manifest);
+    EXPECT_THAT(manifest->getHalNames(),
+                UnorderedElementsAre("android.hardware.main", "android.hardware.fragment1",
+                                     "android.hardware.fragment2"));
+}
+
+TEST_P(VintfObjectRecoveryTest, InaccessibleDirectory) {
+    setUpManifests(NAME_NOT_FOUND, UNKNOWN_ERROR);
+    auto manifest = vintfObject->getRecoveryHalManifest();
+    EXPECT_EQ(nullptr, manifest);
+}
+
+TEST_P(VintfObjectRecoveryTest, InaccessibleFragment) {
+    setUpManifests(NAME_NOT_FOUND, DirectoryContent{{"frag1.xml", UNKNOWN_ERROR}});
+    auto manifest = vintfObject->getRecoveryHalManifest();
+    EXPECT_EQ(nullptr, manifest);
+}
+
+TEST_P(VintfObjectRecoveryTest, SomeInaccessibleFragment) {
+    setUpManifests(NAME_NOT_FOUND,
+                   DirectoryContent{{"frag1.xml", UNKNOWN_ERROR}, {"frag2.xml", frag2}});
+    auto manifest = vintfObject->getRecoveryHalManifest();
+    EXPECT_EQ(nullptr, manifest);
+}
+
+TEST_P(VintfObjectRecoveryTest, DirectoryOnly) {
+    setUpManifests(NAME_NOT_FOUND, {});
+    auto manifest = vintfObject->getRecoveryHalManifest();
+    ASSERT_NE(nullptr, manifest);
+    EXPECT_THAT(manifest->getHalNames(), IsEmpty());
+}
+
+TEST_P(VintfObjectRecoveryTest, FragmentsOnly) {
+    auto [mainType, fragType1, fragType2] = GetParam();
+    setUpManifests(NAME_NOT_FOUND, DirectoryContent{{"frag1.xml", frag1}, {"frag2.xml", frag2}});
+    auto manifest = vintfObject->getRecoveryHalManifest();
+    if (!fragType1.has_value() || !fragType2.has_value()) {
+        // some manifest(s) are broken
+        EXPECT_EQ(nullptr, manifest);
+        return;
+    }
+    ASSERT_NE(nullptr, manifest);
+    EXPECT_THAT(manifest->getHalNames(),
+                UnorderedElementsAre("android.hardware.fragment1", "android.hardware.fragment2"));
+}
+
+INSTANTIATE_TEST_CASE_P(VintfObjectRecoveryTest, VintfObjectRecoveryTest,
+                        Combine(ValuesIn(OptionalTypes()), ValuesIn(OptionalTypes()),
+                                ValuesIn(OptionalTypes())),
+                        VintfObjectRecoveryTest::ParamToString);
+
+}  // namespace android::vintf::testing
diff --git a/test/utils-fake.h b/test/utils-fake.h
index 846ea2c..37219fa 100644
--- a/test/utils-fake.h
+++ b/test/utils-fake.h
@@ -45,6 +45,14 @@
     FileSystemImpl mImpl;
 };
 
+class MockFileSystemWithError : public FileSystem {
+   public:
+    MOCK_METHOD(status_t, fetch, (const std::string&, std::string*, std::string*),
+                (const override));
+    MOCK_METHOD(status_t, listFiles, (const std::string&, std::vector<std::string>*, std::string*),
+                (const override));
+};
+
 class MockRuntimeInfo : public RuntimeInfo {
    public:
     MockRuntimeInfo();