Move timestamp out of Apex

Apex class served a timestamp holder to determine whether apex-info is
updated or not. To support apex for framework manifest, we need to
externalize the timestamp from Apex and then pair it with HalManifest
data.

This is a first step toward that direction. In this change,
LockedSharedPtr<T> is modified to hold lastModified timestamp,
which is associated with the last modified timestamp of the data source.
Get() now invalidates the last fetched data when the timestamp is changed.

In a followup, we'll change framework manifest to use TimestampedData as
well to support loading framework manifest from /apex.

Bug: 327365139
Test: atest libvintf_test vintf_object_test
Change-Id: I4d10724f44a5ea66ec4e47ad6daf41cb01fb8788
diff --git a/Apex.cpp b/Apex.cpp
index add5c1d..31a6cd8 100644
--- a/Apex.cpp
+++ b/Apex.cpp
@@ -24,9 +24,7 @@
 
 using android::base::StartsWith;
 
-namespace android {
-namespace vintf {
-namespace details {
+namespace android::vintf::apex {
 
 static bool isApexReady(PropertyFetcher* propertyFetcher) {
 #ifdef LIBVINTF_TARGET
@@ -38,49 +36,18 @@
 #endif
 }
 
-static bool operator==(const TimeSpec& a, const TimeSpec& b) {
-    return a.tv_sec == b.tv_sec && a.tv_nsec == b.tv_nsec;
-}
-
-status_t Apex::DeviceVintfDirs(FileSystem* fileSystem, PropertyFetcher* propertyFetcher,
-                               std::vector<std::string>* dirs, std::string* error) {
-    std::string apexInfoFile = kApexInfoFile;
+status_t GetDeviceVintfDirs(FileSystem* fileSystem, PropertyFetcher* propertyFetcher,
+                            std::vector<std::string>* dirs, std::string* error) {
+    std::string apexInfoFile = details::kApexInfoFile;
     std::string apexDir = "/apex";
     if (!isApexReady(propertyFetcher)) {
-        apexInfoFile = kBootstrapApexInfoFile;
+        apexInfoFile = details::kBootstrapApexInfoFile;
         apexDir = "/bootstrap-apex";
     }
-    // Update cached mtime_
-    TimeSpec mtime{};
-    auto status = fileSystem->modifiedTime(apexInfoFile, &mtime, error);
-
-    if (status != OK) {
-        switch (status) {
-            case NAME_NOT_FOUND:
-                status = OK;
-                break;
-            case -EACCES:
-                // Don't error out on access errors, but log it
-                LOG(WARNING) << "APEX Device VINTF Dirs: EACCES: "
-                             << (error ? *error : "(unknown error message)");
-                status = OK;
-                break;
-            default:
-                break;
-        }
-
-        if ((status == OK) && (error)) {
-            error->clear();
-        }
-
-        return status;
-    }
-
-    mtime_ = mtime;
 
     // Load apex-info-list
     std::string xml;
-    status = fileSystem->fetch(apexInfoFile, &xml, error);
+    auto status = fileSystem->fetch(apexInfoFile, &xml, error);
     if (status == NAME_NOT_FOUND) {
         if (error) {
             error->clear();
@@ -113,28 +80,23 @@
     return OK;
 }
 
-// Returns true when /apex/apex-info-list.xml is updated
-bool Apex::HasUpdate(FileSystem* fileSystem, PropertyFetcher* propertyFetcher) const {
+std::optional<TimeSpec> GetModifiedTime(FileSystem* fileSystem, PropertyFetcher* propertyFetcher) {
+    std::string apexInfoFile = details::kApexInfoFile;
     if (!isApexReady(propertyFetcher)) {
-        return false;
+        apexInfoFile = details::kBootstrapApexInfoFile;
     }
 
     TimeSpec mtime{};
     std::string error;
-    status_t status = fileSystem->modifiedTime(kApexInfoFile, &mtime, &error);
+    status_t status = fileSystem->modifiedTime(apexInfoFile, &mtime, &error);
     if (status == NAME_NOT_FOUND) {
-        return false;
+        return std::nullopt;
     }
     if (status != OK) {
         LOG(ERROR) << error;
-        return false;
+        return std::nullopt;
     }
-    if (mtime_.has_value() && mtime == mtime_.value()) {
-        return false;
-    }
-    return true;
+    return mtime;
 }
 
-}  // namespace details
-}  // namespace vintf
-}  // namespace android
+}  // namespace android::vintf::apex
diff --git a/VintfObject.cpp b/VintfObject.cpp
index 2861099..1e503a5 100644
--- a/VintfObject.cpp
+++ b/VintfObject.cpp
@@ -74,10 +74,6 @@
     return propertyFetcher;
 }
 
-static std::unique_ptr<ApexInterface> createDefaultApex() {
-    return std::make_unique<details::Apex>();
-}
-
 // Check whether the current executable is allowed to use libvintf.
 // Allowed binaries:
 // - host binaries
@@ -129,21 +125,10 @@
 }
 
 std::shared_ptr<const HalManifest> VintfObject::getDeviceHalManifest() {
-    // Check if any updates to the APEX data, if so rebuild the manifest
-    {
-        std::lock_guard<std::mutex> lock(mDeviceManifest.mutex);
-        if (mDeviceManifest.fetchedOnce) {
-            if (getApex()->HasUpdate(getFileSystem().get(), getPropertyFetcher().get())) {
-                LOG(INFO) << __func__ << ": Reloading VINTF information.";
-                mDeviceManifest.object = nullptr;
-                mDeviceManifest.fetchedOnce = false;
-                // TODO(b/242070736): only APEX data needs to be updated
-            }
-        }
-    }
-
+    // TODO(b/242070736): only APEX data needs to be updated
     return Get(__func__, &mDeviceManifest,
-               std::bind(&VintfObject::fetchDeviceHalManifest, this, _1, _2));
+               std::bind(&VintfObject::fetchDeviceHalManifest, this, _1, _2),
+               apex::GetModifiedTime(getFileSystem().get(), getPropertyFetcher().get()));
 }
 
 std::shared_ptr<const HalManifest> VintfObject::GetFrameworkHalManifest() {
@@ -294,7 +279,7 @@
 status_t VintfObject::fetchDeviceHalManifestApex(HalManifest* out, std::string* error) {
     std::vector<std::string> dirs;
     status_t status =
-        getApex()->DeviceVintfDirs(getFileSystem().get(), getPropertyFetcher().get(), &dirs, error);
+        apex::GetDeviceVintfDirs(getFileSystem().get(), getPropertyFetcher().get(), &dirs, error);
     if (status != OK) {
         return status;
     }
@@ -1074,10 +1059,6 @@
     return mRuntimeInfoFactory;
 }
 
-const std::unique_ptr<ApexInterface>& VintfObject::getApex() {
-    return mApex;
-}
-
 android::base::Result<bool> VintfObject::hasFrameworkCompatibilityMatrixExtensions() {
     std::vector<CompatibilityMatrix> matrixFragments;
     std::string error;
@@ -1445,17 +1426,11 @@
     return *this;
 }
 
-VintfObjectBuilder& VintfObjectBuilder::setApex(std::unique_ptr<ApexInterface>&& a) {
-    mObject->mApex = std::move(a);
-    return *this;
-}
-
 std::unique_ptr<VintfObject> VintfObjectBuilder::buildInternal() {
     if (!mObject->mFileSystem) mObject->mFileSystem = createDefaultFileSystem();
     if (!mObject->mRuntimeInfoFactory)
         mObject->mRuntimeInfoFactory = std::make_unique<ObjectFactory<RuntimeInfo>>();
     if (!mObject->mPropertyFetcher) mObject->mPropertyFetcher = createDefaultPropertyFetcher();
-    if (!mObject->mApex) mObject->mApex = createDefaultApex();
     return std::move(mObject);
 }
 
diff --git a/VintfObjectRecovery.cpp b/VintfObjectRecovery.cpp
index cc44e1e..4882873 100644
--- a/VintfObjectRecovery.cpp
+++ b/VintfObjectRecovery.cpp
@@ -18,6 +18,7 @@
 
 #include "VintfObjectUtils.h"
 #include "constants-private.h"
+#include "utils.h"
 
 using std::placeholders::_1;
 using std::placeholders::_2;
diff --git a/VintfObjectUtils.h b/VintfObjectUtils.h
index ac55200..3abeb8c 100644
--- a/VintfObjectUtils.h
+++ b/VintfObjectUtils.h
@@ -27,17 +27,26 @@
 namespace vintf {
 namespace details {
 
+// Get() fetches data and caches it in LockedSharedPtr. The cached data will be
+// invalidated when `lastModified` is changed from the last call. Typically `lastModified`
+// can refer to the "last modified" timestamp of the data source.
 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) {
+std::shared_ptr<const T> Get(const char* id, LockedSharedPtr<T>* ptr, const F& fetch,
+                             const std::optional<TimeSpec>& lastModified = std::nullopt) {
+    std::unique_lock<std::mutex> lock(ptr->mutex);
+    // Check if the last fetched data is fresh. If it's old, re-fetch the data
+    // with the new timestamp.
+    if (ptr->object && ptr->lastModified != lastModified) {
+        LOG(INFO) << id << ": Reloading VINTF information.";
+        ptr->object = nullptr;
+    }
+    if (!ptr->object) {
         LOG(INFO) << id << ": Reading VINTF information.";
         ptr->object = std::make_unique<T>();
+        ptr->lastModified = lastModified;
         std::string error;
-        status_t status = fetchAllInformation(ptr->object.get(), &error);
+        status_t status = fetch(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
diff --git a/include/vintf/Apex.h b/include/vintf/Apex.h
index 135ee05..00343b9 100644
--- a/include/vintf/Apex.h
+++ b/include/vintf/Apex.h
@@ -23,34 +23,10 @@
 #include <string>
 #include <vector>
 
-namespace android {
-namespace vintf {
+namespace android::vintf::apex {
 
-// APEX VINTF interface
-class ApexInterface {
-   public:
-    virtual ~ApexInterface() = default;
-    // Check if there is an update for the given type of APEX files in the system
-    virtual bool HasUpdate(FileSystem* fileSystem, PropertyFetcher* propertyFetcher) const = 0;
-    // Get device VINTF directories
-    virtual status_t DeviceVintfDirs(FileSystem* fileSystem, PropertyFetcher* propertyFetcher,
-                                     std::vector<std::string>* out, std::string* error) = 0;
-};
+std::optional<TimeSpec> GetModifiedTime(FileSystem* fileSystem, PropertyFetcher* propertyFetcher);
+status_t GetDeviceVintfDirs(FileSystem* fileSystem, PropertyFetcher* propertyFetcher,
+                            std::vector<std::string>* out, std::string* error);
 
-namespace details {
-
-// Provide default implementation for ApexInterface
-class Apex : public ApexInterface {
-   public:
-    Apex() = default;
-    bool HasUpdate(FileSystem* fileSystem, PropertyFetcher* propertyFetcher) const override;
-    status_t DeviceVintfDirs(FileSystem* fileSystem, PropertyFetcher* propertyFetcher,
-                             std::vector<std::string>* out, std::string* error) override;
-
-   private:
-    std::optional<TimeSpec> mtime_;
-};
-
-}  // namespace details
-}  // namespace vintf
-}  // namespace android
+}  // namespace android::vintf::apex
diff --git a/include/vintf/FileSystem.h b/include/vintf/FileSystem.h
index 1e4737e..2697b80 100644
--- a/include/vintf/FileSystem.h
+++ b/include/vintf/FileSystem.h
@@ -26,11 +26,11 @@
 
 #include <utils/Errors.h>
 
+using TimeSpec = struct timespec;
+
 namespace android {
 namespace vintf {
 
-using TimeSpec = struct timespec;
-
 // Queries the file system in the correct way. Files can come from
 // an actual file system, a sub-directory, or from ADB, depending on the
 // implementation.
diff --git a/include/vintf/VintfObject.h b/include/vintf/VintfObject.h
index e6f8472..e60ce8d 100644
--- a/include/vintf/VintfObject.h
+++ b/include/vintf/VintfObject.h
@@ -52,7 +52,7 @@
 struct LockedSharedPtr {
     std::shared_ptr<T> object;
     std::mutex mutex;
-    bool fetchedOnce = false;
+    std::optional<TimeSpec> lastModified;
 };
 
 struct LockedRuntimeInfoCache {
@@ -221,7 +221,6 @@
     std::unique_ptr<FileSystem> mFileSystem;
     std::unique_ptr<ObjectFactory<RuntimeInfo>> mRuntimeInfoFactory;
     std::unique_ptr<PropertyFetcher> mPropertyFetcher;
-    std::unique_ptr<ApexInterface> mApex;
     details::LockedSharedPtr<HalManifest> mDeviceManifest;
     details::LockedSharedPtr<HalManifest> mFrameworkManifest;
     details::LockedSharedPtr<CompatibilityMatrix> mDeviceMatrix;
@@ -253,7 +252,6 @@
     virtual const std::unique_ptr<FileSystem>& getFileSystem();
     virtual const std::unique_ptr<PropertyFetcher>& getPropertyFetcher();
     virtual const std::unique_ptr<ObjectFactory<RuntimeInfo>>& getRuntimeInfoFactory();
-    virtual const std::unique_ptr<ApexInterface>& getApex();
 
    public:
     /*
@@ -381,7 +379,6 @@
     VintfObjectBuilder& setFileSystem(std::unique_ptr<FileSystem>&&);
     VintfObjectBuilder& setRuntimeInfoFactory(std::unique_ptr<ObjectFactory<RuntimeInfo>>&&);
     VintfObjectBuilder& setPropertyFetcher(std::unique_ptr<PropertyFetcher>&&);
-    VintfObjectBuilder& setApex(std::unique_ptr<ApexInterface>&&);
     template <typename VintfObjectType = VintfObject>
     std::unique_ptr<VintfObjectType> build() {
         return std::unique_ptr<VintfObjectType>(
diff --git a/test/LibVintfTest.cpp b/test/LibVintfTest.cpp
index 7576dbc..5708a96 100644
--- a/test/LibVintfTest.cpp
+++ b/test/LibVintfTest.cpp
@@ -1584,10 +1584,10 @@
 TEST_F(LibVintfTest, ApexInterfaceShouldBeOkayWithoutApexInfoList) {
     details::FileSystemNoOp fs;
     details::PropertyFetcherNoOp pf;
-    details::Apex apex;
-    ASSERT_FALSE(apex.HasUpdate(&fs, &pf));
+    EXPECT_THAT(apex::GetModifiedTime(&fs, &pf), std::nullopt);
     std::vector<std::string> dirs;
-    ASSERT_EQ(OK, apex.DeviceVintfDirs(&fs, &pf, &dirs, nullptr));
+    ASSERT_EQ(OK, apex::GetDeviceVintfDirs(&fs, &pf, &dirs, nullptr));
+    ASSERT_EQ(dirs, std::vector<std::string>{});
 }
 
 struct NativeHalCompatTestParam {
diff --git a/test/utils-fake.h b/test/utils-fake.h
index 0db7091..0faf3b0 100644
--- a/test/utils-fake.h
+++ b/test/utils-fake.h
@@ -91,14 +91,6 @@
     MOCK_CONST_METHOD3(getUintProperty, uint64_t(const std::string&, uint64_t, uint64_t));
 };
 
-class MockApex : public ApexInterface {
-   public:
-    MockApex() = default;
-    MOCK_CONST_METHOD2(HasUpdate, bool(FileSystem*, PropertyFetcher*));
-    MOCK_METHOD4(DeviceVintfDirs,
-                 status_t(FileSystem*, PropertyFetcher*, std::vector<std::string>*, std::string*));
-};
-
 }  // namespace details
 }  // namespace vintf
 }  // namespace android
diff --git a/test/vintf_object_tests.cpp b/test/vintf_object_tests.cpp
index 6f81081..edf45f1 100644
--- a/test/vintf_object_tests.cpp
+++ b/test/vintf_object_tests.cpp
@@ -488,8 +488,10 @@
                           .setRuntimeInfoFactory(std::make_unique<NiceMock<MockRuntimeInfoFactory>>(
                               std::make_shared<NiceMock<MockRuntimeInfo>>()))
                           .setPropertyFetcher(std::make_unique<NiceMock<MockPropertyFetcher>>())
-                          .setApex(std::make_unique<NiceMock<MockApex>>())
                           .build();
+
+        ON_CALL(propertyFetcher(), getBoolProperty("apex.all.ready", _))
+            .WillByDefault(Return(true));
     }
     virtual void TearDown() {
         Mock::VerifyAndClear(&fetcher());
@@ -585,44 +587,9 @@
     MockRuntimeInfoFactory& runtimeInfoFactory() {
         return static_cast<MockRuntimeInfoFactory&>(*vintfObject->getRuntimeInfoFactory());
     }
-    MockApex& apex() {
-        return static_cast<MockApex&>(*vintfObject->getApex());
-    }
-    // Setup APEX calls
-    void SetUpApex(const std::string &manifest,
-                   const std::string &apexDir="/apex/com.test/") {
 
-        // Look in every APEX for data
-        std::vector<std::string> apex_dirs{apexDir + kVintfSubDir};
-
-
-        // Map the apex with manifest to the files below
-        const std::string& active_apex = apex_dirs.at(0);
-
-        EXPECT_CALL(apex(), DeviceVintfDirs(_, _, _, _))
-            .WillOnce(Invoke([apex_dirs](auto*, auto*, auto* out, auto*){
-                *out = apex_dirs;
-                return ::android::OK;
-            }))
-            ;
-
-        EXPECT_CALL(fetcher(), listFiles(_, _, _))
-                .WillRepeatedly(Invoke([](const auto&, auto* out, auto*) {
-                  *out = {};
-                  return ::android::OK;
-                }));
-
-        EXPECT_CALL(fetcher(), listFiles(StrEq(active_apex), _, _))
-            .WillOnce(Invoke([](const auto&, auto* out, auto*) {
-              *out = {"manifest.xml"};
-              return ::android::OK;
-            }));
-
-        // Expect to fetch APEX directory manifest once.
-        expectFetch(std::string(active_apex).append("manifest.xml"), manifest);
-
-        ON_CALL(propertyFetcher(), getBoolProperty("apex.all.ready", _))
-            .WillByDefault(Return(true));
+    void noApex() {
+        expectFileNotExist(StartsWith("/apex/"));
     }
 
     std::unique_ptr<VintfObject> vintfObject;
@@ -634,6 +601,7 @@
     virtual void SetUp() {
         VintfObjectTestBase::SetUp();
         setupMockFetcher(vendorManifestXml1, systemMatrixXml1, systemManifestXml1, vendorMatrixXml1);
+        noApex();
     }
 };
 
@@ -908,77 +876,41 @@
 
 class DeviceManifestTest : public VintfObjectTestBase {
    protected:
-    void setupApex(const std::string &apexWithManifestDir="/apex/com.test/",
-                   const std::string &manifest=apexHalManifest,
-                   const std::string &apexWithoutManifestDir= "/apex/com.novintf/") {
-
-      // Mimic the system initialization
-      //  When first building device manifest setup for no device vintf dirs
-      //  Followed by HasUpdate() -> true with device vintf dirs
-      //  After building the APEX version expect HasUpdate to false with no further call for
-      //   device vintf dirs
-
-      // Look in every APEX for data, only  apexWithManifest will contain a manifest file
-      std::vector<std::string> apex_dirs{apexWithManifestDir + kVintfSubDir,
-                                         apexWithoutManifestDir + kVintfSubDir};
-
-      // Map the apex with manifest to the files below
-      const std::string& active_apex = apex_dirs.at(0);
-
-      EXPECT_CALL(apex(), DeviceVintfDirs(_, _, _, _))
-          .WillOnce(Invoke([](auto*, auto*, auto* out, auto*){
-            *out = {};
-            return ::android::OK;
-          })) // Initialization
-          .WillOnce(Invoke([apex_dirs](auto*, auto*, auto* out, auto*){
-            *out = apex_dirs;
-            return ::android::OK;
-          })) // after apex loaded
-          ;
-
-      EXPECT_CALL(apex(), HasUpdate(_, _)) // Not called during init
-          .WillOnce(Return(true)) // Apex loaded
-          .WillOnce(Return(false)) // no updated to apex data
-          ;
-
-      ON_CALL(propertyFetcher(), getBoolProperty("apex.all.ready", _))
-          .WillByDefault(Return(true));
-
-      EXPECT_CALL(fetcher(), listFiles(_, _, _))
-          .WillRepeatedly(Invoke([](const auto&, auto* out, auto*) {
-              *out = {};
-              return ::android::OK;
-          }));
-
-      EXPECT_CALL(fetcher(), listFiles(StrEq(active_apex), _, _))
-          .WillOnce(Invoke([](const auto&, auto* out, auto*) {
-              *out = {"manifest.xml"};
-              return ::android::OK;
-          }));
-
-
-      // Expect to fetch APEX directory manifest once.
-      expectFetch(std::string(active_apex).append("manifest.xml"), manifest);
-
+    void expectApex(const std::string& halManifest = apexHalManifest) {
+        expectFetchRepeatedly(kApexInfoFile, R"(<apex-info-list>
+            <apex-info moduleName="com.test"
+                preinstalledModulePath="/vendor/apex/com.test.apex" isActive="true"/>
+            <apex-info moduleName="com.novintf"
+                preinstalledModulePath="/vendor/apex/com.novintf.apex" isActive="true"/>
+        </apex-info-list>)");
+        EXPECT_CALL(fetcher(), modifiedTime(kApexInfoFile, _, _))
+            .WillOnce(Invoke([](auto, TimeSpec* out, auto){
+                *out = {};
+                return ::android::OK;
+            }))
+            // Update once, but no more.
+            .WillRepeatedly(Invoke([](auto, TimeSpec* out, auto){
+                *out = {1,};
+                return ::android::OK;
+            }))
+            ;
+        ON_CALL(fetcher(), listFiles("/apex/com.test/etc/vintf/", _, _))
+            .WillByDefault(Invoke([](auto, std::vector<std::string>* out, auto){
+                *out = {"manifest.xml"};
+                return ::android::OK;
+            }));
+        expectFetchRepeatedly("/apex/com.test/etc/vintf/manifest.xml", halManifest);
     }
 
     // Expect that /vendor/etc/vintf/manifest.xml is fetched.
-    void expectVendorManifest(bool repeatedly = false) {
-        if (repeatedly) {
-            expectFetchRepeatedly(kVendorManifest, vendorEtcManifest);
-        } else {
-            expectFetch(kVendorManifest, vendorEtcManifest);
-        }
+    void expectVendorManifest() {
+        expectFetchRepeatedly(kVendorManifest, vendorEtcManifest);
     }
     // /vendor/etc/vintf/manifest.xml does not exist.
     void noVendorManifest() { expectFileNotExist(StrEq(kVendorManifest)); }
     // Expect some ODM manifest is fetched.
-    void expectOdmManifest(bool repeatedly = false) {
-        if (repeatedly) {
-            expectFetchRepeatedly(kOdmManifest, odmManifest);
-        } else {
-            expectFetch(kOdmManifest, odmManifest);
-        }
+    void expectOdmManifest() {
+        expectFetchRepeatedly(kOdmManifest, odmManifest);
     }
     void noOdmManifest() { expectFileNotExist(StartsWith("/odm/")); }
     std::shared_ptr<const HalManifest> get() {
@@ -990,6 +922,7 @@
 TEST_F(DeviceManifestTest, Combine1) {
     expectVendorManifest();
     expectOdmManifest();
+    noApex();
     auto p = get();
     ASSERT_NE(nullptr, p);
     EXPECT_TRUE(containsVendorEtcManifest(p));
@@ -1002,6 +935,7 @@
 TEST_F(DeviceManifestTest, Combine2) {
     expectVendorManifest();
     noOdmManifest();
+    noApex();
     auto p = get();
     ASSERT_NE(nullptr, p);
     EXPECT_TRUE(containsVendorEtcManifest(p));
@@ -1014,6 +948,7 @@
 TEST_F(DeviceManifestTest, Combine3) {
     noVendorManifest();
     expectOdmManifest();
+    noApex();
     auto p = get();
     ASSERT_NE(nullptr, p);
     EXPECT_FALSE(containsVendorEtcManifest(p));
@@ -1026,6 +961,7 @@
 TEST_F(DeviceManifestTest, Combine4) {
     noVendorManifest();
     noOdmManifest();
+    noApex();
     expectFetch(kVendorLegacyManifest, vendorManifest);
     auto p = get();
     ASSERT_NE(nullptr, p);
@@ -1036,138 +972,30 @@
 }
 
 // Run the same tests as above (Combine1,2,3,4) including APEX data.
-// APEX tests all of the same variation:
-//   create device manifest without APEX data
-//   trigger update to APEX
-//   create new device manifest with APEX data
-//   no new APEX data
-//
-// Since HalManifest is created twice expect[Vendor|Odm]Manifest will
-// be called multiple times compared to Combine test.
 
 // Test /vendor/etc/vintf/manifest.xml + ODM manifest + APEX
-TEST_F(DeviceManifestTest, ApexCombine1) {
-    expectVendorManifest(true); // Create device manifest twice.
-    expectOdmManifest(true); // Create device manifest twice.
-    setupApex();
+TEST_F(DeviceManifestTest, Combine5) {
+    expectVendorManifest();
+    expectOdmManifest();
+    expectApex();
     auto p = get();
     ASSERT_NE(nullptr, p);
     EXPECT_TRUE(containsVendorEtcManifest(p));
     EXPECT_TRUE(vendorEtcManifestOverridden(p));
     EXPECT_TRUE(containsOdmManifest(p));
     EXPECT_FALSE(containsVendorManifest(p));
-
-    EXPECT_FALSE(containsApexManifest(p));
+    EXPECT_TRUE(containsApexManifest(p));
 
     // Second call should create new maninfest containing APEX info.
     auto p2 = get();
     ASSERT_NE(nullptr, p2);
     ASSERT_NE(p, p2);
-    EXPECT_TRUE(containsVendorEtcManifest(p2));
-    EXPECT_TRUE(vendorEtcManifestOverridden(p2));
-    EXPECT_TRUE(containsOdmManifest(p2));
-    EXPECT_FALSE(containsVendorManifest(p2));
-
-    EXPECT_TRUE(containsApexManifest(p2));
 
     // Third call expect no update and no call to DeviceVintfDirs.
     auto p3 = get();
     ASSERT_EQ(p2,p3);
 }
 
-// Test /vendor/etc/vintf/manifest.xml + APEX
-TEST_F(DeviceManifestTest, ApexCombine2) {
-    expectVendorManifest(true); // Create device manifest twice.
-    noOdmManifest();
-
-    setupApex();
-    auto p = get();
-    ASSERT_NE(nullptr, p);
-    EXPECT_TRUE(containsVendorEtcManifest(p));
-    EXPECT_FALSE(vendorEtcManifestOverridden(p));
-    EXPECT_FALSE(containsOdmManifest(p));
-    EXPECT_FALSE(containsVendorManifest(p));
-
-    EXPECT_FALSE(containsApexManifest(p));
-
-    // Second call should create new maninfest containing APEX info.
-    auto p2 = get();
-    ASSERT_NE(nullptr, p2);
-    ASSERT_NE(p, p2);
-    EXPECT_TRUE(containsVendorEtcManifest(p2));
-    EXPECT_FALSE(vendorEtcManifestOverridden(p2));
-    EXPECT_FALSE(containsOdmManifest(p2));
-    EXPECT_FALSE(containsVendorManifest(p2));
-
-    EXPECT_TRUE(containsApexManifest(p2));
-
-    // Third call expect no update and no call to DeviceVintfDirs.
-    auto p3 = get();
-    ASSERT_EQ(p2,p3);
-}
-
-// Test ODM manifest + APEX
-TEST_F(DeviceManifestTest, ApexCombine3) {
-    noVendorManifest();
-    expectOdmManifest(true);  // Create device manifest twice.
-
-    setupApex();
-    auto p = get();
-    ASSERT_NE(nullptr, p);
-    EXPECT_FALSE(containsVendorEtcManifest(p));
-    EXPECT_TRUE(vendorEtcManifestOverridden(p));
-    EXPECT_TRUE(containsOdmManifest(p));
-    EXPECT_FALSE(containsVendorManifest(p));
-
-    EXPECT_FALSE(containsApexManifest(p));
-
-    // Second call should create new maninfest containing APEX info.
-    auto p2 = get();
-    ASSERT_NE(nullptr, p2);
-    EXPECT_FALSE(containsVendorEtcManifest(p2));
-    EXPECT_TRUE(vendorEtcManifestOverridden(p2));
-    EXPECT_TRUE(containsOdmManifest(p2));
-    EXPECT_FALSE(containsVendorManifest(p2));
-
-    EXPECT_TRUE(containsApexManifest(p2));
-
-    // Third call expect no update and no call to DeviceVintfDirs.
-    auto p3 = get();
-    ASSERT_EQ(p2,p3);
-}
-
-// Test /vendor/manifest.xml + APEX
-TEST_F(DeviceManifestTest, ApexCombine4) {
-    noVendorManifest();
-    noOdmManifest();
-    expectFetchRepeatedly(kVendorLegacyManifest, vendorManifest);
-    setupApex();
-    auto p = get();
-    ASSERT_NE(nullptr, p);
-    EXPECT_FALSE(containsVendorEtcManifest(p));
-    EXPECT_TRUE(vendorEtcManifestOverridden(p));
-    EXPECT_FALSE(containsOdmManifest(p));
-    EXPECT_TRUE(containsVendorManifest(p));
-
-    EXPECT_FALSE(containsApexManifest(p));
-
-    // Second call should create new maninfest containing APEX info.
-    auto p2 = get();
-    ASSERT_NE(nullptr, p2);
-    ASSERT_NE(p, p2);
-    EXPECT_FALSE(containsVendorEtcManifest(p2));
-    EXPECT_TRUE(vendorEtcManifestOverridden(p2));
-    EXPECT_FALSE(containsOdmManifest(p2));
-    EXPECT_TRUE(containsVendorManifest(p2));
-
-    EXPECT_TRUE(containsApexManifest(p2));
-
-    // Third call expect no update and no call to DeviceVintfDirs.
-    auto p3 = get();
-    ASSERT_EQ(p2,p3);
-}
-
-
 // Tests for valid/invalid APEX defined HAL
 // For a HAL to be defined within an APEX it must not have
 // the update-via-apex attribute defined in the HAL manifest
@@ -1176,7 +1004,7 @@
 TEST_F(DeviceManifestTest, ValidApexHal) {
     expectVendorManifest();
     noOdmManifest();
-    SetUpApex(apexHalManifest);
+    expectApex();
     auto p = get();
     ASSERT_NE(nullptr, p);
     // HALs defined in APEX should set updatable-via-apex
@@ -1202,7 +1030,7 @@
         "</manifest>\n";
     expectVendorManifest();
     noOdmManifest();
-    SetUpApex(apexInvalidManifest);
+    expectApex(apexInvalidManifest);
     auto p = get();
     ASSERT_EQ(nullptr, p);
 }
@@ -1294,7 +1122,7 @@
         expectNeverFetch(kVendorLegacyManifest);
         // Assume no files exist under /odm/ unless otherwise specified.
         expectFileNotExist(StartsWith("/odm/"));
-
+        noApex();
         // set SKU
         productModel = GetParam();
         ON_CALL(propertyFetcher(), getProperty("ro.boot.product.hardware.sku", _))
diff --git a/utils.h b/utils.h
index d1fad9d..f33472b 100644
--- a/utils.h
+++ b/utils.h
@@ -26,6 +26,15 @@
 #include <vintf/RuntimeInfo.h>
 #include <vintf/parse_xml.h>
 
+// Equility operators for TimeSpec in FileSystem.h
+
+inline bool operator==(const TimeSpec& a, const TimeSpec& b) {
+    return a.tv_sec == b.tv_sec && a.tv_nsec == b.tv_nsec;
+}
+inline bool operator!=(const TimeSpec& a, const TimeSpec& b) {
+    return !(a == b);
+}
+
 namespace android {
 namespace vintf {
 namespace details {