Merge "[SF] Move the notifyExpectedPresentHint call to SF" into main
diff --git a/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h b/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h
index 8b736be..9e35717 100644
--- a/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h
+++ b/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h
@@ -148,8 +148,7 @@
     MOCK_METHOD(const aidl::android::hardware::graphics::composer3::OverlayProperties&,
                 getOverlaySupport, (), (const, override));
     MOCK_METHOD(status_t, setRefreshRateChangedCallbackDebugEnabled, (PhysicalDisplayId, bool));
-    MOCK_METHOD(status_t, notifyExpectedPresentIfRequired,
-                (PhysicalDisplayId, Period, TimePoint, Fps, std::optional<Period>));
+    MOCK_METHOD(status_t, notifyExpectedPresent, (PhysicalDisplayId, TimePoint, Fps));
 };
 
 } // namespace mock
diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.cpp b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
index 2e8ecba..3ffd8ea 100644
--- a/services/surfaceflinger/DisplayHardware/HWComposer.cpp
+++ b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
@@ -15,6 +15,7 @@
  */
 
 // TODO(b/129481165): remove the #pragma below and fix conversion issues
+#include <chrono>
 #pragma clang diagnostic push
 #pragma clang diagnostic ignored "-Wconversion"
 
@@ -78,59 +79,6 @@
 using aidl::android::hardware::graphics::composer3::DisplayCapability;
 namespace hal = android::hardware::graphics::composer::hal;
 
-namespace {
-bool isFrameIntervalOnCadence(android::TimePoint expectedPresentTime,
-                              android::TimePoint lastExpectedPresentTimestamp,
-                              android::Fps lastFrameInterval, android::Period timeout,
-                              android::Duration threshold) {
-    if (lastFrameInterval.getPeriodNsecs() == 0) {
-        return false;
-    }
-
-    const auto expectedPresentTimeDeltaNs =
-            expectedPresentTime.ns() - lastExpectedPresentTimestamp.ns();
-
-    if (expectedPresentTimeDeltaNs > timeout.ns()) {
-        return false;
-    }
-
-    const auto expectedPresentPeriods = static_cast<nsecs_t>(
-            std::round(static_cast<float>(expectedPresentTimeDeltaNs) /
-                       static_cast<float>(lastFrameInterval.getPeriodNsecs())));
-    const auto calculatedPeriodsOutNs = lastFrameInterval.getPeriodNsecs() * expectedPresentPeriods;
-    const auto calculatedExpectedPresentTimeNs =
-            lastExpectedPresentTimestamp.ns() + calculatedPeriodsOutNs;
-    const auto presentTimeDelta =
-            std::abs(expectedPresentTime.ns() - calculatedExpectedPresentTimeNs);
-    return presentTimeDelta < threshold.ns();
-}
-
-bool isExpectedPresentWithinTimeout(android::TimePoint expectedPresentTime,
-                                    android::TimePoint lastExpectedPresentTimestamp,
-                                    std::optional<android::Period> timeoutOpt,
-                                    android::Duration threshold) {
-    if (!timeoutOpt) {
-        // Always within timeout if timeoutOpt is absent and don't send hint
-        // for the timeout
-        return true;
-    }
-
-    if (timeoutOpt->ns() == 0) {
-        // Always outside timeout if timeoutOpt is 0 and always send
-        // the hint for the timeout.
-        return false;
-    }
-
-    if (expectedPresentTime.ns() < lastExpectedPresentTimestamp.ns() + timeoutOpt->ns()) {
-        return true;
-    }
-
-    // Check if within the threshold as it can be just outside the timeout
-    return std::abs(expectedPresentTime.ns() -
-                    (lastExpectedPresentTimestamp.ns() + timeoutOpt->ns())) < threshold.ns();
-}
-} // namespace
-
 namespace android {
 
 HWComposer::~HWComposer() = default;
@@ -538,13 +486,6 @@
     }();
 
     displayData.validateWasSkipped = false;
-    {
-        std::scoped_lock lock{displayData.expectedPresentLock};
-        if (expectedPresentTime > displayData.lastExpectedPresentTimestamp.ns()) {
-            displayData.lastExpectedPresentTimestamp = TimePoint::fromNs(expectedPresentTime);
-        }
-    }
-
     ATRACE_FORMAT("NextFrameInterval %d_Hz", frameInterval.getIntValue());
     if (canSkipValidate) {
         sp<Fence> outPresentFence = Fence::NO_FENCE;
@@ -939,55 +880,15 @@
     return NO_ERROR;
 }
 
-status_t HWComposer::notifyExpectedPresentIfRequired(PhysicalDisplayId displayId,
-                                                     Period vsyncPeriod,
-                                                     TimePoint expectedPresentTime,
-                                                     Fps frameInterval,
-                                                     std::optional<Period> timeoutOpt) {
+status_t HWComposer::notifyExpectedPresent(PhysicalDisplayId displayId,
+                                           TimePoint expectedPresentTime, Fps frameInterval) {
     RETURN_IF_INVALID_DISPLAY(displayId, BAD_INDEX);
-    auto& displayData = mDisplayData[displayId];
-    if (!displayData.hwcDisplay) {
-        // Display setup has not completed yet
-        return BAD_INDEX;
-    }
-    {
-        std::scoped_lock lock{displayData.expectedPresentLock};
-        const auto lastExpectedPresentTimestamp = displayData.lastExpectedPresentTimestamp;
-        const auto lastFrameInterval = displayData.lastFrameInterval;
-        displayData.lastFrameInterval = frameInterval;
-        const auto threshold = Duration::fromNs(vsyncPeriod.ns() / 2);
-
-        const constexpr nsecs_t kOneSecondNs =
-                std::chrono::duration_cast<std::chrono::nanoseconds>(1s).count();
-        const bool frameIntervalIsOnCadence =
-                isFrameIntervalOnCadence(expectedPresentTime, lastExpectedPresentTimestamp,
-                                         lastFrameInterval,
-                                         Period::fromNs(timeoutOpt && timeoutOpt->ns() > 0
-                                                                ? timeoutOpt->ns()
-                                                                : kOneSecondNs),
-                                         threshold);
-
-        const bool expectedPresentWithinTimeout =
-                isExpectedPresentWithinTimeout(expectedPresentTime, lastExpectedPresentTimestamp,
-                                               timeoutOpt, threshold);
-
-        using fps_approx_ops::operator!=;
-        if (frameIntervalIsOnCadence && frameInterval != lastFrameInterval) {
-            displayData.lastExpectedPresentTimestamp = expectedPresentTime;
-        }
-
-        if (expectedPresentWithinTimeout && frameIntervalIsOnCadence) {
-            return NO_ERROR;
-        }
-
-        displayData.lastExpectedPresentTimestamp = expectedPresentTime;
-    }
-    ATRACE_FORMAT("%s ExpectedPresentTime %" PRId64 " frameIntervalNs %d", __func__,
-                  expectedPresentTime, frameInterval.getPeriodNsecs());
-    const auto error = mComposer->notifyExpectedPresent(displayData.hwcDisplay->getId(),
+    ATRACE_FORMAT("%s ExpectedPresentTime in %.2fms frameInterval %.2fms", __func__,
+                  ticks<std::milli, float>(expectedPresentTime - TimePoint::now()),
+                  ticks<std::milli, float>(Duration::fromNs(frameInterval.getPeriodNsecs())));
+    const auto error = mComposer->notifyExpectedPresent(mDisplayData[displayId].hwcDisplay->getId(),
                                                         expectedPresentTime.ns(),
                                                         frameInterval.getPeriodNsecs());
-
     if (error != hal::Error::NONE) {
         ALOGE("Error in notifyExpectedPresent call %s", to_string(error).c_str());
         return INVALID_OPERATION;
diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.h b/services/surfaceflinger/DisplayHardware/HWComposer.h
index af62731..4ca528a 100644
--- a/services/surfaceflinger/DisplayHardware/HWComposer.h
+++ b/services/surfaceflinger/DisplayHardware/HWComposer.h
@@ -303,10 +303,8 @@
             aidl::android::hardware::graphics::common::HdrConversionStrategy,
             aidl::android::hardware::graphics::common::Hdr*) = 0;
     virtual status_t setRefreshRateChangedCallbackDebugEnabled(PhysicalDisplayId, bool enabled) = 0;
-    virtual status_t notifyExpectedPresentIfRequired(PhysicalDisplayId, Period vsyncPeriod,
-                                                     TimePoint expectedPresentTime,
-                                                     Fps frameInterval,
-                                                     std::optional<Period> timeoutOpt) = 0;
+    virtual status_t notifyExpectedPresent(PhysicalDisplayId, TimePoint expectedPresentTime,
+                                           Fps frameInterval) = 0;
 };
 
 static inline bool operator==(const android::HWComposer::DeviceRequestedChanges& lhs,
@@ -466,9 +464,8 @@
             aidl::android::hardware::graphics::common::HdrConversionStrategy,
             aidl::android::hardware::graphics::common::Hdr*) override;
     status_t setRefreshRateChangedCallbackDebugEnabled(PhysicalDisplayId, bool enabled) override;
-    status_t notifyExpectedPresentIfRequired(PhysicalDisplayId, Period vsyncPeriod,
-                                             TimePoint expectedPresentTime, Fps frameInterval,
-                                             std::optional<Period> timeoutOpt) override;
+    status_t notifyExpectedPresent(PhysicalDisplayId, TimePoint expectedPresentTime,
+                                   Fps frameInterval) override;
 
     // for debugging ----------------------------------------------------------
     void dump(std::string& out) const override;
@@ -494,7 +491,6 @@
 private:
     // For unit tests
     friend TestableSurfaceFlinger;
-    friend HWComposerTest;
 
     struct DisplayData {
         std::unique_ptr<HWC2::Display> hwcDisplay;
@@ -502,11 +498,6 @@
         sp<Fence> lastPresentFence = Fence::NO_FENCE; // signals when the last set op retires
         nsecs_t lastPresentTimestamp = 0;
 
-        std::mutex expectedPresentLock;
-        TimePoint lastExpectedPresentTimestamp GUARDED_BY(expectedPresentLock) =
-                TimePoint::fromNs(0);
-        Fps lastFrameInterval GUARDED_BY(expectedPresentLock) = Fps::fromValue(0);
-
         std::unordered_map<HWC2::Layer*, sp<Fence>> releaseFences;
 
         bool validateWasSkipped;
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index 1f8f2d6..c56dc83 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -318,6 +318,53 @@
     return duration > (Clock::now() - updateTime);
 }
 
+bool isFrameIntervalOnCadence(TimePoint expectedPresentTime, TimePoint lastExpectedPresentTimestamp,
+                              Fps lastFrameInterval, Period timeout, Duration threshold) {
+    if (lastFrameInterval.getPeriodNsecs() == 0) {
+        return false;
+    }
+
+    const auto expectedPresentTimeDeltaNs =
+            expectedPresentTime.ns() - lastExpectedPresentTimestamp.ns();
+
+    if (expectedPresentTimeDeltaNs > timeout.ns()) {
+        return false;
+    }
+
+    const auto expectedPresentPeriods = static_cast<nsecs_t>(
+            std::round(static_cast<float>(expectedPresentTimeDeltaNs) /
+                       static_cast<float>(lastFrameInterval.getPeriodNsecs())));
+    const auto calculatedPeriodsOutNs = lastFrameInterval.getPeriodNsecs() * expectedPresentPeriods;
+    const auto calculatedExpectedPresentTimeNs =
+            lastExpectedPresentTimestamp.ns() + calculatedPeriodsOutNs;
+    const auto presentTimeDelta =
+            std::abs(expectedPresentTime.ns() - calculatedExpectedPresentTimeNs);
+    return presentTimeDelta < threshold.ns();
+}
+
+bool isExpectedPresentWithinTimeout(TimePoint expectedPresentTime,
+                                    TimePoint lastExpectedPresentTimestamp,
+                                    std::optional<Period> timeoutOpt, Duration threshold) {
+    if (!timeoutOpt) {
+        // Always within timeout if timeoutOpt is absent and don't send hint
+        // for the timeout
+        return true;
+    }
+
+    if (timeoutOpt->ns() == 0) {
+        // Always outside timeout if timeoutOpt is 0 and always send
+        // the hint for the timeout.
+        return false;
+    }
+
+    if (expectedPresentTime.ns() < lastExpectedPresentTimestamp.ns() + timeoutOpt->ns()) {
+        return true;
+    }
+
+    // Check if within the threshold as it can be just outside the timeout
+    return std::abs(expectedPresentTime.ns() -
+                    (lastExpectedPresentTimestamp.ns() + timeoutOpt->ns())) < threshold.ns();
+}
 }  // namespace anonymous
 
 // ---------------------------------------------------------------------------
@@ -2707,7 +2754,18 @@
     refreshArgs.scheduledFrameTime = mScheduler->getScheduledFrameTime();
     refreshArgs.expectedPresentTime = expectedPresentTime.ns();
     refreshArgs.hasTrustedPresentationListener = mNumTrustedPresentationListeners > 0;
-
+    {
+        auto& notifyExpectedPresentData = mNotifyExpectedPresentMap[pacesetterId];
+        auto lastExpectedPresentTimestamp = TimePoint::fromNs(
+                notifyExpectedPresentData.lastExpectedPresentTimestamp.load().ns());
+        if (expectedPresentTime > lastExpectedPresentTimestamp) {
+            // If the values are not same, then hint is sent with newer value.
+            // And because composition always follows the notifyExpectedPresentIfRequired, we can
+            // skip updating the lastExpectedPresentTimestamp in this case.
+            notifyExpectedPresentData.lastExpectedPresentTimestamp
+                    .compare_exchange_weak(lastExpectedPresentTimestamp, expectedPresentTime);
+        }
+    }
     // Store the present time just before calling to the composition engine so we could notify
     // the scheduler.
     const auto presentTime = systemTime();
@@ -4061,7 +4119,7 @@
 void SurfaceFlinger::onVsyncGenerated(TimePoint expectedPresentTime,
                                       ftl::NonNull<DisplayModePtr> modePtr, Fps renderRate) {
     const auto vsyncPeriod = modePtr->getVsyncRate().getPeriod();
-    const auto timeout = [&]() -> std::optional<Period> {
+    const auto timeoutOpt = [&]() -> std::optional<Period> {
         const auto vrrConfig = modePtr->getVrrConfig();
         if (!vrrConfig) return std::nullopt;
 
@@ -4071,14 +4129,54 @@
         return Period::fromNs(notifyExpectedPresentConfig->notifyExpectedPresentTimeoutNs);
     }();
 
-    const auto displayId = modePtr->getPhysicalDisplayId();
-    const auto status = getHwComposer().notifyExpectedPresentIfRequired(displayId, vsyncPeriod,
-                                                                        expectedPresentTime,
-                                                                        renderRate, timeout);
-    if (status != NO_ERROR) {
-        ALOGE("%s failed to notifyExpectedPresentHint for display %" PRId64, __func__,
-              displayId.value);
+    notifyExpectedPresentIfRequired(modePtr->getPhysicalDisplayId(), vsyncPeriod,
+                                    expectedPresentTime, renderRate, timeoutOpt);
+}
+
+void SurfaceFlinger::notifyExpectedPresentIfRequired(PhysicalDisplayId displayId,
+                                                     Period vsyncPeriod,
+                                                     TimePoint expectedPresentTime,
+                                                     Fps frameInterval,
+                                                     std::optional<Period> timeoutOpt) {
+    {
+        auto& data = mNotifyExpectedPresentMap[displayId];
+        const auto lastExpectedPresentTimestamp = data.lastExpectedPresentTimestamp.load();
+        const auto lastFrameInterval = data.lastFrameInterval;
+        data.lastFrameInterval = frameInterval;
+        const auto threshold = Duration::fromNs(vsyncPeriod.ns() / 2);
+
+        const constexpr nsecs_t kOneSecondNs =
+                std::chrono::duration_cast<std::chrono::nanoseconds>(1s).count();
+        const auto timeout = Period::fromNs(timeoutOpt && timeoutOpt->ns() > 0 ? timeoutOpt->ns()
+                                                                               : kOneSecondNs);
+        const bool frameIntervalIsOnCadence =
+                isFrameIntervalOnCadence(expectedPresentTime, lastExpectedPresentTimestamp,
+                                         lastFrameInterval, timeout, threshold);
+
+        const bool expectedPresentWithinTimeout =
+                isExpectedPresentWithinTimeout(expectedPresentTime, lastExpectedPresentTimestamp,
+                                               timeoutOpt, threshold);
+
+        using fps_approx_ops::operator!=;
+        if (frameIntervalIsOnCadence && frameInterval != lastFrameInterval) {
+            data.lastExpectedPresentTimestamp = expectedPresentTime;
+        }
+
+        if (expectedPresentWithinTimeout && frameIntervalIsOnCadence) {
+            return;
+        }
+        data.lastExpectedPresentTimestamp = expectedPresentTime;
     }
+
+    const char* const whence = __func__;
+    static_cast<void>(mScheduler->schedule([=, this]() FTL_FAKE_GUARD(kMainThreadContext) {
+        const auto status = getHwComposer().notifyExpectedPresent(displayId, expectedPresentTime,
+                                                                  frameInterval);
+        if (status != NO_ERROR) {
+            ALOGE("%s failed to notifyExpectedPresentHint for display %" PRId64, whence,
+                  displayId.value);
+        }
+    }));
 }
 
 void SurfaceFlinger::initScheduler(const sp<const DisplayDevice>& display) {
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index 6a7b322..6b44401 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -1490,6 +1490,19 @@
     // Map of displayid to mirrorRoot
     ftl::SmallMap<int64_t, sp<SurfaceControl>, 3> mMirrorMapForDebug;
 
+    // NotifyExpectedPresentHint
+    struct NotifyExpectedPresentData {
+        // lastExpectedPresentTimestamp is read and write from multiple threads such as
+        // main thread, EventThread, MessageQueue. And is atomic for that reason.
+        std::atomic<TimePoint> lastExpectedPresentTimestamp{};
+        Fps lastFrameInterval{};
+    };
+    std::unordered_map<PhysicalDisplayId, NotifyExpectedPresentData> mNotifyExpectedPresentMap;
+
+    void notifyExpectedPresentIfRequired(PhysicalDisplayId, Period vsyncPeriod,
+                                         TimePoint expectedPresentTime, Fps frameInterval,
+                                         std::optional<Period> timeoutOpt);
+
     void sfdo_enableRefreshRateOverlay(bool active);
     void sfdo_setDebugFlash(int delay);
     void sfdo_scheduleComposite();
diff --git a/services/surfaceflinger/tests/unittests/Android.bp b/services/surfaceflinger/tests/unittests/Android.bp
index dea0194..c75f902 100644
--- a/services/surfaceflinger/tests/unittests/Android.bp
+++ b/services/surfaceflinger/tests/unittests/Android.bp
@@ -106,6 +106,7 @@
         "SurfaceFlinger_HdrOutputControlTest.cpp",
         "SurfaceFlinger_HotplugTest.cpp",
         "SurfaceFlinger_InitializeDisplaysTest.cpp",
+        "SurfaceFlinger_NotifyExpectedPresentTest.cpp",
         "SurfaceFlinger_NotifyPowerBoostTest.cpp",
         "SurfaceFlinger_PowerHintTest.cpp",
         "SurfaceFlinger_SetDisplayStateTest.cpp",
diff --git a/services/surfaceflinger/tests/unittests/HWComposerTest.cpp b/services/surfaceflinger/tests/unittests/HWComposerTest.cpp
index c5e179f..6edecff 100644
--- a/services/surfaceflinger/tests/unittests/HWComposerTest.cpp
+++ b/services/surfaceflinger/tests/unittests/HWComposerTest.cpp
@@ -82,15 +82,6 @@
         EXPECT_CALL(*mHal, setVsyncEnabled(hwcDisplayId, Hwc2::IComposerClient::Vsync::DISABLE));
         EXPECT_CALL(*mHal, onHotplugConnect(hwcDisplayId));
     }
-
-    void setDisplayData(HalDisplayId displayId, TimePoint lastExpectedPresentTimestamp,
-                        Fps lastFrameInterval) {
-        ASSERT_TRUE(mHwc.mDisplayData.find(displayId) != mHwc.mDisplayData.end());
-        auto& displayData = mHwc.mDisplayData.at(displayId);
-        std::scoped_lock lock{displayData.expectedPresentLock};
-        displayData.lastExpectedPresentTimestamp = lastExpectedPresentTimestamp;
-        displayData.lastFrameInterval = lastFrameInterval;
-    }
 };
 
 TEST_F(HWComposerTest, isHeadless) {
@@ -417,144 +408,6 @@
     EXPECT_FALSE(displayIdOpt);
 }
 
-TEST_F(HWComposerTest, notifyExpectedPresentTimeout) {
-    constexpr hal::HWDisplayId kHwcDisplayId = 2;
-    expectHotplugConnect(kHwcDisplayId);
-    const auto info = mHwc.onHotplug(kHwcDisplayId, hal::Connection::CONNECTED);
-    ASSERT_TRUE(info);
-
-    auto expectedPresentTime = systemTime() + ms2ns(10);
-    static constexpr Fps Fps60Hz = 60_Hz;
-    static constexpr int32_t kFrameInterval5HzNs = static_cast<Fps>(5_Hz).getPeriodNsecs();
-    static constexpr int32_t kFrameInterval60HzNs = Fps60Hz.getPeriodNsecs();
-    static constexpr int32_t kFrameInterval120HzNs = static_cast<Fps>(120_Hz).getPeriodNsecs();
-    static constexpr Period kVsyncPeriod =
-            Period::fromNs(static_cast<Fps>(240_Hz).getPeriodNsecs());
-    static constexpr Period kTimeoutNs = Period::fromNs(kFrameInterval5HzNs);
-    static constexpr auto kLastExpectedPresentTimestamp = TimePoint::fromNs(0);
-
-    ASSERT_NO_FATAL_FAILURE(setDisplayData(info->id, kLastExpectedPresentTimestamp, Fps60Hz));
-
-    {
-        // Very first ExpectedPresent after idle, no previous timestamp
-        EXPECT_CALL(*mHal,
-                    notifyExpectedPresent(kHwcDisplayId, expectedPresentTime, kFrameInterval60HzNs))
-                .WillOnce(Return(HalError::NONE));
-        mHwc.notifyExpectedPresentIfRequired(info->id, kVsyncPeriod,
-                                             TimePoint::fromNs(expectedPresentTime), Fps60Hz,
-                                             kTimeoutNs);
-    }
-    {
-        // Absent timeoutNs
-        expectedPresentTime += 2 * kFrameInterval5HzNs;
-        EXPECT_CALL(*mHal, notifyExpectedPresent(kHwcDisplayId, _, _)).Times(0);
-        mHwc.notifyExpectedPresentIfRequired(info->id, kVsyncPeriod,
-                                             TimePoint::fromNs(expectedPresentTime), Fps60Hz,
-                                             /*timeoutOpt*/ std::nullopt);
-    }
-    {
-        // Timeout is 0
-        expectedPresentTime += kFrameInterval60HzNs;
-        EXPECT_CALL(*mHal,
-                    notifyExpectedPresent(kHwcDisplayId, expectedPresentTime, kFrameInterval60HzNs))
-                .WillOnce(Return(HalError::NONE));
-        mHwc.notifyExpectedPresentIfRequired(info->id, kVsyncPeriod,
-                                             TimePoint::fromNs(expectedPresentTime), Fps60Hz,
-                                             Period::fromNs(0));
-    }
-    {
-        // ExpectedPresent is after the timeoutNs
-        expectedPresentTime += 2 * kFrameInterval5HzNs;
-        EXPECT_CALL(*mHal,
-                    notifyExpectedPresent(kHwcDisplayId, expectedPresentTime, kFrameInterval60HzNs))
-                .WillOnce(Return(HalError::NONE));
-        mHwc.notifyExpectedPresentIfRequired(info->id, kVsyncPeriod,
-                                             TimePoint::fromNs(expectedPresentTime), Fps60Hz,
-                                             kTimeoutNs);
-    }
-    {
-        // ExpectedPresent has not changed
-        EXPECT_CALL(*mHal, notifyExpectedPresent(kHwcDisplayId, _, _)).Times(0);
-        mHwc.notifyExpectedPresentIfRequired(info->id, kVsyncPeriod,
-                                             TimePoint::fromNs(expectedPresentTime), Fps60Hz,
-                                             kTimeoutNs);
-    }
-    {
-        // ExpectedPresent is after the last reported ExpectedPresent.
-        expectedPresentTime += kFrameInterval60HzNs;
-        EXPECT_CALL(*mHal, notifyExpectedPresent(kHwcDisplayId, _, _)).Times(0);
-        mHwc.notifyExpectedPresentIfRequired(info->id, kVsyncPeriod,
-                                             TimePoint::fromNs(expectedPresentTime), Fps60Hz,
-                                             kTimeoutNs);
-    }
-    {
-        // ExpectedPresent is before the last reported ExpectedPresent but after the timeoutNs,
-        // representing we changed our decision and want to present earlier than previously
-        // reported.
-        expectedPresentTime -= kFrameInterval120HzNs;
-        EXPECT_CALL(*mHal,
-                    notifyExpectedPresent(kHwcDisplayId, expectedPresentTime, kFrameInterval60HzNs))
-                .WillOnce(Return(HalError::NONE));
-        mHwc.notifyExpectedPresentIfRequired(info->id, kVsyncPeriod,
-                                             TimePoint::fromNs(expectedPresentTime), Fps60Hz,
-                                             kTimeoutNs);
-    }
-}
-
-TEST_F(HWComposerTest, notifyExpectedPresentRenderRateChanged) {
-    constexpr hal::HWDisplayId kHwcDisplayId = 2;
-    expectHotplugConnect(kHwcDisplayId);
-    const auto info = mHwc.onHotplug(kHwcDisplayId, hal::Connection::CONNECTED);
-    ASSERT_TRUE(info);
-
-    const auto now = systemTime();
-    auto expectedPresentTime = now;
-    static constexpr Period kTimeoutNs = Period::fromNs(static_cast<Fps>(1_Hz).getPeriodNsecs());
-
-    ASSERT_NO_FATAL_FAILURE(setDisplayData(info->id, TimePoint::fromNs(now), Fps::fromValue(0)));
-    static constexpr int32_t kFrameIntervalNs120Hz = static_cast<Fps>(120_Hz).getPeriodNsecs();
-    static constexpr int32_t kFrameIntervalNs96Hz = static_cast<Fps>(96_Hz).getPeriodNsecs();
-    static constexpr int32_t kFrameIntervalNs80Hz = static_cast<Fps>(80_Hz).getPeriodNsecs();
-    static constexpr int32_t kFrameIntervalNs60Hz = static_cast<Fps>(60_Hz).getPeriodNsecs();
-    static constexpr int32_t kFrameIntervalNs40Hz = static_cast<Fps>(40_Hz).getPeriodNsecs();
-    static constexpr int32_t kFrameIntervalNs30Hz = static_cast<Fps>(30_Hz).getPeriodNsecs();
-    static constexpr int32_t kFrameIntervalNs24Hz = static_cast<Fps>(24_Hz).getPeriodNsecs();
-    static constexpr int32_t kFrameIntervalNs20Hz = static_cast<Fps>(20_Hz).getPeriodNsecs();
-    static constexpr Period kVsyncPeriod =
-            Period::fromNs(static_cast<Fps>(240_Hz).getPeriodNsecs());
-
-    struct FrameRateIntervalTestData {
-        int32_t frameIntervalNs;
-        bool callExpectedPresent;
-    };
-    const std::vector<FrameRateIntervalTestData> frameIntervals = {
-            {kFrameIntervalNs60Hz, true},  {kFrameIntervalNs96Hz, true},
-            {kFrameIntervalNs80Hz, true},  {kFrameIntervalNs120Hz, true},
-            {kFrameIntervalNs80Hz, true},  {kFrameIntervalNs60Hz, true},
-            {kFrameIntervalNs60Hz, false}, {kFrameIntervalNs30Hz, false},
-            {kFrameIntervalNs24Hz, true},  {kFrameIntervalNs40Hz, true},
-            {kFrameIntervalNs20Hz, false}, {kFrameIntervalNs60Hz, true},
-            {kFrameIntervalNs20Hz, false}, {kFrameIntervalNs120Hz, true},
-    };
-
-    for (const auto& [frameIntervalNs, callExpectedPresent] : frameIntervals) {
-        {
-            expectedPresentTime += frameIntervalNs;
-            if (callExpectedPresent) {
-                EXPECT_CALL(*mHal,
-                            notifyExpectedPresent(kHwcDisplayId, expectedPresentTime,
-                                                  frameIntervalNs))
-                        .WillOnce(Return(HalError::NONE));
-            } else {
-                EXPECT_CALL(*mHal, notifyExpectedPresent(kHwcDisplayId, _, _)).Times(0);
-            }
-            mHwc.notifyExpectedPresentIfRequired(info->id, kVsyncPeriod,
-                                                 TimePoint::fromNs(expectedPresentTime),
-                                                 Fps::fromPeriodNsecs(frameIntervalNs), kTimeoutNs);
-        }
-    }
-}
-
 struct MockHWC2ComposerCallback final : StrictMock<HWC2::ComposerCallback> {
     MOCK_METHOD(void, onComposerHalHotplugEvent, (hal::HWDisplayId, DisplayHotplugEvent),
                 (override));
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_NotifyExpectedPresentTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_NotifyExpectedPresentTest.cpp
new file mode 100644
index 0000000..7206e29
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_NotifyExpectedPresentTest.cpp
@@ -0,0 +1,180 @@
+/*
+ * Copyright 2023 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.
+ */
+
+#undef LOG_TAG
+#define LOG_TAG "LibSurfaceFlingerUnittests"
+
+#include "DisplayTransactionTestHelpers.h"
+
+namespace android {
+
+using FakeHwcDisplayInjector = TestableSurfaceFlinger::FakeHwcDisplayInjector;
+
+class NotifyExpectedPresentTest : public DisplayTransactionTest {
+public:
+    void SetUp() override {
+        mDisplay = PrimaryDisplayVariant::makeFakeExistingDisplayInjector(this).inject();
+        FakeHwcDisplayInjector(mDisplay->getPhysicalId(), hal::DisplayType::PHYSICAL, kIsPrimary)
+                .setPowerMode(hal::PowerMode::ON)
+                .inject(&mFlinger, mComposer);
+    }
+
+protected:
+    sp<DisplayDevice> mDisplay;
+    static constexpr bool kIsPrimary = true;
+    static constexpr hal::HWDisplayId HWC_DISPLAY_ID =
+            FakeHwcDisplayInjector::DEFAULT_HWC_DISPLAY_ID;
+};
+
+TEST_F(NotifyExpectedPresentTest, notifyExpectedPresentTimeout) {
+    const auto physicDisplayId = mDisplay->getPhysicalId();
+    auto expectedPresentTime = systemTime() + ms2ns(10);
+    static constexpr Fps kFps60Hz = 60_Hz;
+    static constexpr int32_t kFrameInterval5HzNs = static_cast<Fps>(5_Hz).getPeriodNsecs();
+    static constexpr int32_t kFrameInterval60HzNs = kFps60Hz.getPeriodNsecs();
+    static constexpr int32_t kFrameInterval120HzNs = static_cast<Fps>(120_Hz).getPeriodNsecs();
+    static constexpr Period kVsyncPeriod =
+            Period::fromNs(static_cast<Fps>(240_Hz).getPeriodNsecs());
+    static constexpr Period kTimeoutNs = Period::fromNs(kFrameInterval5HzNs);
+    static constexpr auto kLastExpectedPresentTimestamp = TimePoint::fromNs(0);
+
+    ASSERT_NO_FATAL_FAILURE(mFlinger.setNotifyExpectedPresentData(physicDisplayId,
+                                                                  kLastExpectedPresentTimestamp,
+                                                                  kFps60Hz));
+
+    {
+        // Very first ExpectedPresent after idle, no previous timestamp
+        EXPECT_CALL(*mComposer,
+                    notifyExpectedPresent(HWC_DISPLAY_ID, expectedPresentTime,
+                                          kFrameInterval60HzNs))
+                .WillOnce(Return(Error::NONE));
+        mFlinger.notifyExpectedPresentIfRequired(physicDisplayId, kVsyncPeriod,
+                                                 TimePoint::fromNs(expectedPresentTime), kFps60Hz,
+                                                 kTimeoutNs);
+    }
+    {
+        // Absent timeoutNs
+        expectedPresentTime += 2 * kFrameInterval5HzNs;
+        EXPECT_CALL(*mComposer, notifyExpectedPresent(HWC_DISPLAY_ID, _, _)).Times(0);
+        mFlinger.notifyExpectedPresentIfRequired(physicDisplayId, kVsyncPeriod,
+                                                 TimePoint::fromNs(expectedPresentTime), kFps60Hz,
+                                                 /*timeoutOpt*/ std::nullopt);
+    }
+    {
+        // Timeout is 0
+        expectedPresentTime += kFrameInterval60HzNs;
+        EXPECT_CALL(*mComposer,
+                    notifyExpectedPresent(HWC_DISPLAY_ID, expectedPresentTime,
+                                          kFrameInterval60HzNs))
+                .WillOnce(Return(Error::NONE));
+        mFlinger.notifyExpectedPresentIfRequired(physicDisplayId, kVsyncPeriod,
+                                                 TimePoint::fromNs(expectedPresentTime), kFps60Hz,
+                                                 Period::fromNs(0));
+    }
+    {
+        // ExpectedPresent is after the timeoutNs
+        expectedPresentTime += 2 * kFrameInterval5HzNs;
+        EXPECT_CALL(*mComposer,
+                    notifyExpectedPresent(HWC_DISPLAY_ID, expectedPresentTime,
+                                          kFrameInterval60HzNs))
+                .WillOnce(Return(Error::NONE));
+        mFlinger.notifyExpectedPresentIfRequired(physicDisplayId, kVsyncPeriod,
+                                                 TimePoint::fromNs(expectedPresentTime), kFps60Hz,
+                                                 kTimeoutNs);
+    }
+    {
+        // ExpectedPresent has not changed
+        EXPECT_CALL(*mComposer, notifyExpectedPresent(HWC_DISPLAY_ID, _, _)).Times(0);
+        mFlinger.notifyExpectedPresentIfRequired(physicDisplayId, kVsyncPeriod,
+                                                 TimePoint::fromNs(expectedPresentTime), kFps60Hz,
+                                                 kTimeoutNs);
+    }
+    {
+        // ExpectedPresent is after the last reported ExpectedPresent.
+        expectedPresentTime += kFrameInterval60HzNs;
+        EXPECT_CALL(*mComposer, notifyExpectedPresent(HWC_DISPLAY_ID, _, _)).Times(0);
+        mFlinger.notifyExpectedPresentIfRequired(physicDisplayId, kVsyncPeriod,
+                                                 TimePoint::fromNs(expectedPresentTime), kFps60Hz,
+                                                 kTimeoutNs);
+    }
+    {
+        // ExpectedPresent is before the last reported ExpectedPresent but after the timeoutNs,
+        // representing we changed our decision and want to present earlier than previously
+        // reported.
+        expectedPresentTime -= kFrameInterval120HzNs;
+        EXPECT_CALL(*mComposer,
+                    notifyExpectedPresent(HWC_DISPLAY_ID, expectedPresentTime,
+                                          kFrameInterval60HzNs))
+                .WillOnce(Return(Error::NONE));
+        mFlinger.notifyExpectedPresentIfRequired(physicDisplayId, kVsyncPeriod,
+                                                 TimePoint::fromNs(expectedPresentTime), kFps60Hz,
+                                                 kTimeoutNs);
+    }
+}
+
+TEST_F(NotifyExpectedPresentTest, notifyExpectedPresentRenderRateChanged) {
+    const auto physicDisplayId = mDisplay->getPhysicalId();
+    const auto now = systemTime();
+    auto expectedPresentTime = now;
+    static constexpr Period kTimeoutNs = Period::fromNs(static_cast<Fps>(1_Hz).getPeriodNsecs());
+
+    ASSERT_NO_FATAL_FAILURE(mFlinger.setNotifyExpectedPresentData(physicDisplayId,
+                                                                  TimePoint::fromNs(now),
+                                                                  Fps::fromValue(0)));
+    static constexpr int32_t kFrameIntervalNs120Hz = static_cast<Fps>(120_Hz).getPeriodNsecs();
+    static constexpr int32_t kFrameIntervalNs96Hz = static_cast<Fps>(96_Hz).getPeriodNsecs();
+    static constexpr int32_t kFrameIntervalNs80Hz = static_cast<Fps>(80_Hz).getPeriodNsecs();
+    static constexpr int32_t kFrameIntervalNs60Hz = static_cast<Fps>(60_Hz).getPeriodNsecs();
+    static constexpr int32_t kFrameIntervalNs40Hz = static_cast<Fps>(40_Hz).getPeriodNsecs();
+    static constexpr int32_t kFrameIntervalNs30Hz = static_cast<Fps>(30_Hz).getPeriodNsecs();
+    static constexpr int32_t kFrameIntervalNs24Hz = static_cast<Fps>(24_Hz).getPeriodNsecs();
+    static constexpr int32_t kFrameIntervalNs20Hz = static_cast<Fps>(20_Hz).getPeriodNsecs();
+    static constexpr Period kVsyncPeriod =
+            Period::fromNs(static_cast<Fps>(240_Hz).getPeriodNsecs());
+
+    struct FrameRateIntervalTestData {
+        int32_t frameIntervalNs;
+        bool callExpectedPresent;
+    };
+    const std::vector<FrameRateIntervalTestData> frameIntervals = {
+            {kFrameIntervalNs60Hz, true},  {kFrameIntervalNs96Hz, true},
+            {kFrameIntervalNs80Hz, true},  {kFrameIntervalNs120Hz, true},
+            {kFrameIntervalNs80Hz, true},  {kFrameIntervalNs60Hz, true},
+            {kFrameIntervalNs60Hz, false}, {kFrameIntervalNs30Hz, false},
+            {kFrameIntervalNs24Hz, true},  {kFrameIntervalNs40Hz, true},
+            {kFrameIntervalNs20Hz, false}, {kFrameIntervalNs60Hz, true},
+            {kFrameIntervalNs20Hz, false}, {kFrameIntervalNs120Hz, true},
+    };
+
+    for (const auto& [frameIntervalNs, callExpectedPresent] : frameIntervals) {
+        {
+            expectedPresentTime += frameIntervalNs;
+            if (callExpectedPresent) {
+                EXPECT_CALL(*mComposer,
+                            notifyExpectedPresent(HWC_DISPLAY_ID, expectedPresentTime,
+                                                  frameIntervalNs))
+                        .WillOnce(Return(Error::NONE));
+            } else {
+                EXPECT_CALL(*mComposer, notifyExpectedPresent(HWC_DISPLAY_ID, _, _)).Times(0);
+            }
+            mFlinger.notifyExpectedPresentIfRequired(physicDisplayId, kVsyncPeriod,
+                                                     TimePoint::fromNs(expectedPresentTime),
+                                                     Fps::fromPeriodNsecs(frameIntervalNs),
+                                                     kTimeoutNs);
+        }
+    }
+}
+} // namespace android
\ No newline at end of file
diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
index 8ba6bf8..22cb24b 100644
--- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
+++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
@@ -695,6 +695,21 @@
         mFlinger->mLegacyFrontEndEnabled = false;
     }
 
+    void notifyExpectedPresentIfRequired(PhysicalDisplayId displayId, Period vsyncPeriod,
+                                         TimePoint expectedPresentTime, Fps frameInterval,
+                                         std::optional<Period> timeoutOpt) {
+        mFlinger->notifyExpectedPresentIfRequired(displayId, vsyncPeriod, expectedPresentTime,
+                                                  frameInterval, timeoutOpt);
+    }
+
+    void setNotifyExpectedPresentData(PhysicalDisplayId displayId,
+                                      TimePoint lastExpectedPresentTimestamp,
+                                      Fps lastFrameInterval) {
+        auto& displayData = mFlinger->mNotifyExpectedPresentMap[displayId];
+        displayData.lastExpectedPresentTimestamp = lastExpectedPresentTimestamp;
+        displayData.lastFrameInterval = lastFrameInterval;
+    }
+
     ~TestableSurfaceFlinger() {
         // All these pointer and container clears help ensure that GMock does
         // not report a leaked object, since the SurfaceFlinger instance may