[SF] Adds callback from the VsyncPredictor

Hooks up the NotifyExpectedPresentIfRequired with
expectedPresentTime

BUG: 296636253
BUG: 284845445
Test: atest HWComposerTest
Change-Id: Idfd30929a0f4931b1a9f943340932c655ddd5903
diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp
index 1a8713d..d3db523 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.cpp
+++ b/services/surfaceflinger/Scheduler/Scheduler.cpp
@@ -67,11 +67,12 @@
 namespace android::scheduler {
 
 Scheduler::Scheduler(ICompositor& compositor, ISchedulerCallback& callback, FeatureFlags features,
-                     sp<VsyncModulator> modulatorPtr)
+                     sp<VsyncModulator> modulatorPtr, IVsyncTrackerCallback& vsyncTrackerCallback)
       : impl::MessageQueue(compositor),
         mFeatures(features),
         mVsyncModulator(std::move(modulatorPtr)),
-        mSchedulerCallback(callback) {}
+        mSchedulerCallback(callback),
+        mVsyncTrackerCallback(vsyncTrackerCallback) {}
 
 Scheduler::~Scheduler() {
     // MessageQueue depends on VsyncSchedule, so first destroy it.
@@ -116,10 +117,10 @@
 }
 
 void Scheduler::registerDisplay(PhysicalDisplayId displayId, RefreshRateSelectorPtr selectorPtr) {
-    auto schedulePtr = std::make_shared<VsyncSchedule>(displayId, mFeatures,
-                                                       [this](PhysicalDisplayId id, bool enable) {
-                                                           onHardwareVsyncRequest(id, enable);
-                                                       });
+    auto schedulePtr = std::make_shared<VsyncSchedule>(
+            displayId, mFeatures,
+            [this](PhysicalDisplayId id, bool enable) { onHardwareVsyncRequest(id, enable); },
+            mVsyncTrackerCallback);
 
     registerDisplayInternal(displayId, std::move(selectorPtr), std::move(schedulePtr));
 }
@@ -562,7 +563,19 @@
     ALOGV("%s %s (%s)", __func__, to_string(mode.fps).c_str(),
           to_string(mode.modePtr->getVsyncRate()).c_str());
 
-    display.schedulePtr->getTracker().setRenderRate(renderFrameRate);
+    display.schedulePtr->getTracker().setDisplayModeData(
+            {.renderRate = renderFrameRate,
+             .notifyExpectedPresentTimeoutOpt = getNotifyExpectedPresentTimeout(mode)});
+}
+
+std::optional<Period> Scheduler::getNotifyExpectedPresentTimeout(const FrameRateMode& mode) {
+    if (mode.modePtr->getVrrConfig() && mode.modePtr->getVrrConfig()->notifyExpectedPresentConfig) {
+        return Period::fromNs(
+                mode.modePtr->getVrrConfig()
+                        ->notifyExpectedPresentConfig->notifyExpectedPresentTimeoutNs);
+    } else {
+        return std::nullopt;
+    }
 }
 
 void Scheduler::resync() {
diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h
index b0520a6..a5a5e8d 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.h
+++ b/services/surfaceflinger/Scheduler/Scheduler.h
@@ -102,7 +102,8 @@
     using Impl = android::impl::MessageQueue;
 
 public:
-    Scheduler(ICompositor&, ISchedulerCallback&, FeatureFlags, sp<VsyncModulator>);
+    Scheduler(ICompositor&, ISchedulerCallback&, FeatureFlags, sp<VsyncModulator>,
+              IVsyncTrackerCallback&);
     virtual ~Scheduler();
 
     void startTimers();
@@ -429,6 +430,9 @@
     Period getVsyncPeriod(uid_t) override EXCLUDES(mDisplayLock);
     void resync() override EXCLUDES(mDisplayLock);
 
+    std::optional<Period> getNotifyExpectedPresentTimeout(const FrameRateMode&)
+            REQUIRES(mDisplayLock);
+
     // Stores EventThread associated with a given VSyncSource, and an initial EventThreadConnection.
     struct Connection {
         sp<EventThreadConnection> connection;
@@ -462,6 +466,8 @@
 
     ISchedulerCallback& mSchedulerCallback;
 
+    IVsyncTrackerCallback& mVsyncTrackerCallback;
+
     // mDisplayLock may be locked while under mPolicyLock.
     mutable std::mutex mPolicyLock;
 
diff --git a/services/surfaceflinger/Scheduler/VSyncPredictor.cpp b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp
index e969fdc..799466b 100644
--- a/services/surfaceflinger/Scheduler/VSyncPredictor.cpp
+++ b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp
@@ -35,6 +35,7 @@
 #include <gui/TraceUtils.h>
 #include <utils/Log.h>
 
+#include "FlagManager.h"
 #include "RefreshRateSelector.h"
 #include "VSyncPredictor.h"
 
@@ -47,12 +48,14 @@
 VSyncPredictor::~VSyncPredictor() = default;
 
 VSyncPredictor::VSyncPredictor(PhysicalDisplayId id, nsecs_t idealPeriod, size_t historySize,
-                               size_t minimumSamplesForPrediction, uint32_t outlierTolerancePercent)
+                               size_t minimumSamplesForPrediction, uint32_t outlierTolerancePercent,
+                               IVsyncTrackerCallback& callback)
       : mId(id),
         mTraceOn(property_get_bool("debug.sf.vsp_trace", false)),
         kHistorySize(historySize),
         kMinimumSamplesForPrediction(minimumSamplesForPrediction),
         kOutlierTolerancePercent(std::min(outlierTolerancePercent, kMaxPercent)),
+        mVsyncTrackerCallback(callback),
         mIdealPeriod(idealPeriod) {
     resetModel();
 }
@@ -275,11 +278,11 @@
     mLastVsyncSequence = getVsyncSequenceLocked(timePoint);
 
     const auto renderRatePhase = [&]() REQUIRES(mMutex) -> int {
-        if (!mRenderRate) return 0;
+        if (!mDisplayModeDataOpt) return 0;
 
         const auto divisor =
                 RefreshRateSelector::getFrameRateDivisor(Fps::fromPeriodNsecs(mIdealPeriod),
-                                                         *mRenderRate);
+                                                         mDisplayModeDataOpt->renderRate);
         if (divisor <= 1) return 0;
 
         const int mod = mLastVsyncSequence->seq % divisor;
@@ -289,12 +292,24 @@
     }();
 
     if (renderRatePhase == 0) {
-        return mLastVsyncSequence->vsyncTime;
+        const auto vsyncTime = mLastVsyncSequence->vsyncTime;
+        if (FlagManager::getInstance().vrr_config() && mDisplayModeDataOpt) {
+            mVsyncTrackerCallback.onVsyncGenerated(mId, TimePoint::fromNs(vsyncTime),
+                                                   *mDisplayModeDataOpt,
+                                                   Period::fromNs(mIdealPeriod));
+        }
+        return vsyncTime;
     }
 
     auto const [slope, intercept] = getVSyncPredictionModelLocked();
     const auto approximateNextVsync = mLastVsyncSequence->vsyncTime + slope * renderRatePhase;
-    return nextAnticipatedVSyncTimeFromLocked(approximateNextVsync - slope / 2);
+    const auto nextAnticipatedVsyncTime =
+            nextAnticipatedVSyncTimeFromLocked(approximateNextVsync - slope / 2);
+    if (FlagManager::getInstance().vrr_config() && mDisplayModeDataOpt) {
+        mVsyncTrackerCallback.onVsyncGenerated(mId, TimePoint::fromNs(nextAnticipatedVsyncTime),
+                                               *mDisplayModeDataOpt, Period::fromNs(mIdealPeriod));
+    }
+    return nextAnticipatedVsyncTime;
 }
 
 /*
@@ -332,10 +347,14 @@
     return vsyncSequence.seq % divisor == 0;
 }
 
-void VSyncPredictor::setRenderRate(Fps fps) {
-    ALOGV("%s %s: %s", __func__, to_string(mId).c_str(), to_string(fps).c_str());
+void VSyncPredictor::setDisplayModeData(const DisplayModeData& displayModeData) {
+    ALOGV("%s %s: RenderRate %s notifyExpectedPresentTimeout %s", __func__, to_string(mId).c_str(),
+          to_string(displayModeData.renderRate).c_str(),
+          displayModeData.notifyExpectedPresentTimeoutOpt
+                  ? std::to_string(displayModeData.notifyExpectedPresentTimeoutOpt->ns()).c_str()
+                  : "N/A");
     std::lock_guard lock(mMutex);
-    mRenderRate = fps;
+    mDisplayModeDataOpt = displayModeData;
 }
 
 VSyncPredictor::Model VSyncPredictor::getVSyncPredictionModel() const {
@@ -358,6 +377,7 @@
         mRateMap.erase(mRateMap.begin());
     }
 
+    // TODO(b/308610306) mIdealPeriod to be updated with setDisplayModeData
     mIdealPeriod = period;
     if (mRateMap.find(period) == mRateMap.end()) {
         mRateMap[mIdealPeriod] = {period, 0};
diff --git a/services/surfaceflinger/Scheduler/VSyncPredictor.h b/services/surfaceflinger/Scheduler/VSyncPredictor.h
index c01c44d..c271eb7 100644
--- a/services/surfaceflinger/Scheduler/VSyncPredictor.h
+++ b/services/surfaceflinger/Scheduler/VSyncPredictor.h
@@ -36,9 +36,11 @@
      * \param [in] minimumSamplesForPrediction The minimum number of samples to collect before
      * predicting. \param [in] outlierTolerancePercent a number 0 to 100 that will be used to filter
      * samples that fall outlierTolerancePercent from an anticipated vsync event.
+     * \param [in] IVsyncTrackerCallback The callback for the VSyncTracker.
      */
     VSyncPredictor(PhysicalDisplayId, nsecs_t idealPeriod, size_t historySize,
-                   size_t minimumSamplesForPrediction, uint32_t outlierTolerancePercent);
+                   size_t minimumSamplesForPrediction, uint32_t outlierTolerancePercent,
+                   IVsyncTrackerCallback&);
     ~VSyncPredictor();
 
     bool addVsyncTimestamp(nsecs_t timestamp) final EXCLUDES(mMutex);
@@ -69,7 +71,7 @@
 
     bool isVSyncInPhase(nsecs_t timePoint, Fps frameRate) const final EXCLUDES(mMutex);
 
-    void setRenderRate(Fps) final EXCLUDES(mMutex);
+    void setDisplayModeData(const DisplayModeData&) final EXCLUDES(mMutex);
 
     void dump(std::string& result) const final EXCLUDES(mMutex);
 
@@ -99,6 +101,7 @@
     size_t const kHistorySize;
     size_t const kMinimumSamplesForPrediction;
     size_t const kOutlierTolerancePercent;
+    IVsyncTrackerCallback& mVsyncTrackerCallback;
     std::mutex mutable mMutex;
 
     nsecs_t mIdealPeriod GUARDED_BY(mMutex);
@@ -110,7 +113,7 @@
     size_t mLastTimestampIndex GUARDED_BY(mMutex) = 0;
     std::vector<nsecs_t> mTimestamps GUARDED_BY(mMutex);
 
-    std::optional<Fps> mRenderRate GUARDED_BY(mMutex);
+    std::optional<DisplayModeData> mDisplayModeDataOpt GUARDED_BY(mMutex);
 
     mutable std::optional<VsyncSequence> mLastVsyncSequence GUARDED_BY(mMutex);
 };
diff --git a/services/surfaceflinger/Scheduler/VSyncTracker.h b/services/surfaceflinger/Scheduler/VSyncTracker.h
index bc0e3bc..7eedc31 100644
--- a/services/surfaceflinger/Scheduler/VSyncTracker.h
+++ b/services/surfaceflinger/Scheduler/VSyncTracker.h
@@ -16,6 +16,7 @@
 
 #pragma once
 
+#include <ui/DisplayId.h>
 #include <utils/Timers.h>
 
 #include <scheduler/Fps.h>
@@ -23,6 +24,23 @@
 #include "VSyncDispatch.h"
 
 namespace android::scheduler {
+
+struct DisplayModeData {
+    Fps renderRate;
+    std::optional<Period> notifyExpectedPresentTimeoutOpt;
+
+    bool operator==(const DisplayModeData& other) const {
+        return isApproxEqual(renderRate, other.renderRate) &&
+                notifyExpectedPresentTimeoutOpt == other.notifyExpectedPresentTimeoutOpt;
+    }
+};
+
+struct IVsyncTrackerCallback {
+    virtual ~IVsyncTrackerCallback() = default;
+    virtual void onVsyncGenerated(PhysicalDisplayId, TimePoint expectedPresentTime,
+                                  const DisplayModeData&, Period vsyncPeriod) = 0;
+};
+
 /*
  * VSyncTracker is an interface for providing estimates on future Vsync signal times based on
  * historical vsync timing data.
@@ -80,16 +98,20 @@
     virtual bool isVSyncInPhase(nsecs_t timePoint, Fps frameRate) const = 0;
 
     /*
-     * Sets a render rate on the tracker. If the render rate is not a divisor
-     * of the period, the render rate is ignored until the period changes.
+     * Sets the metadata about the currently active display mode such as VRR
+     * timeout period, vsyncPeriod and framework property such as render rate.
+     * If the render rate is not a divisor of the period, the render rate is
+     * ignored until the period changes.
      * The tracker will continue to track the vsync timeline and expect it
      * to match the current period, however, nextAnticipatedVSyncTimeFrom will
      * return vsyncs according to the render rate set. Setting a render rate is useful
      * when a display is running at 120Hz but the render frame rate is 60Hz.
+     * When IVsyncTrackerCallback::onVsyncGenerated callback is made we will pass along
+     * the vsyncPeriod, render rate and timeoutNs.
      *
-     * \param [in] Fps   The render rate the tracker should operate at.
+     * \param [in] DisplayModeData The DisplayModeData the tracker will use.
      */
-    virtual void setRenderRate(Fps) = 0;
+    virtual void setDisplayModeData(const DisplayModeData&) = 0;
 
     virtual void dump(std::string& result) const = 0;
 
diff --git a/services/surfaceflinger/Scheduler/VsyncSchedule.cpp b/services/surfaceflinger/Scheduler/VsyncSchedule.cpp
index ff3f29d..5fb53f9 100644
--- a/services/surfaceflinger/Scheduler/VsyncSchedule.cpp
+++ b/services/surfaceflinger/Scheduler/VsyncSchedule.cpp
@@ -54,10 +54,11 @@
 };
 
 VsyncSchedule::VsyncSchedule(PhysicalDisplayId id, FeatureFlags features,
-                             RequestHardwareVsync requestHardwareVsync)
+                             RequestHardwareVsync requestHardwareVsync,
+                             IVsyncTrackerCallback& callback)
       : mId(id),
         mRequestHardwareVsync(std::move(requestHardwareVsync)),
-        mTracker(createTracker(id)),
+        mTracker(createTracker(id, callback)),
         mDispatch(createDispatch(mTracker)),
         mController(createController(id, *mTracker, features)),
         mTracer(features.test(Feature::kTracePredictedVsync)
@@ -100,7 +101,8 @@
     mDispatch->dump(out);
 }
 
-VsyncSchedule::TrackerPtr VsyncSchedule::createTracker(PhysicalDisplayId id) {
+VsyncSchedule::TrackerPtr VsyncSchedule::createTracker(PhysicalDisplayId id,
+                                                       IVsyncTrackerCallback& callback) {
     // TODO(b/144707443): Tune constants.
     constexpr nsecs_t kInitialPeriod = (60_Hz).getPeriodNsecs();
     constexpr size_t kHistorySize = 20;
@@ -108,7 +110,8 @@
     constexpr uint32_t kDiscardOutlierPercent = 20;
 
     return std::make_unique<VSyncPredictor>(id, kInitialPeriod, kHistorySize,
-                                            kMinSamplesForPrediction, kDiscardOutlierPercent);
+                                            kMinSamplesForPrediction, kDiscardOutlierPercent,
+                                            callback);
 }
 
 VsyncSchedule::DispatchPtr VsyncSchedule::createDispatch(TrackerPtr tracker) {
diff --git a/services/surfaceflinger/Scheduler/VsyncSchedule.h b/services/surfaceflinger/Scheduler/VsyncSchedule.h
index 47e92e1..ca61f87 100644
--- a/services/surfaceflinger/Scheduler/VsyncSchedule.h
+++ b/services/surfaceflinger/Scheduler/VsyncSchedule.h
@@ -31,6 +31,7 @@
 #include <scheduler/Time.h>
 
 #include "ThreadContext.h"
+#include "VSyncTracker.h"
 
 namespace android {
 class EventThreadTest;
@@ -56,7 +57,7 @@
 public:
     using RequestHardwareVsync = std::function<void(PhysicalDisplayId, bool enabled)>;
 
-    VsyncSchedule(PhysicalDisplayId, FeatureFlags, RequestHardwareVsync);
+    VsyncSchedule(PhysicalDisplayId, FeatureFlags, RequestHardwareVsync, IVsyncTrackerCallback&);
     ~VsyncSchedule();
 
     // IVsyncSource overrides:
@@ -124,7 +125,7 @@
     friend class android::VsyncScheduleTest;
     friend class android::fuzz::SchedulerFuzzer;
 
-    static TrackerPtr createTracker(PhysicalDisplayId);
+    static TrackerPtr createTracker(PhysicalDisplayId, IVsyncTrackerCallback&);
     static DispatchPtr createDispatch(TrackerPtr);
     static ControllerPtr createController(PhysicalDisplayId, VsyncTracker&, FeatureFlags);
 
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index 62eb17d..b1d8db5 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -4036,6 +4036,21 @@
     }
 }
 
+void SurfaceFlinger::onVsyncGenerated(PhysicalDisplayId displayId, TimePoint expectedPresentTime,
+                                      const scheduler::DisplayModeData& displayModeData,
+                                      Period vsyncPeriod) {
+    const auto status =
+            getHwComposer()
+                    .notifyExpectedPresentIfRequired(displayId, vsyncPeriod, expectedPresentTime,
+                                                     displayModeData.renderRate,
+                                                     displayModeData
+                                                             .notifyExpectedPresentTimeoutOpt);
+    if (status != NO_ERROR) {
+        ALOGE("%s failed to notifyExpectedPresentHint for display %" PRId64, __func__,
+              displayId.value);
+    }
+}
+
 void SurfaceFlinger::initScheduler(const sp<const DisplayDevice>& display) {
     using namespace scheduler;
 
@@ -4074,8 +4089,12 @@
 
     mScheduler = std::make_unique<Scheduler>(static_cast<ICompositor&>(*this),
                                              static_cast<ISchedulerCallback&>(*this), features,
-                                             std::move(modulatorPtr));
+                                             std::move(modulatorPtr),
+                                             static_cast<IVsyncTrackerCallback&>(*this));
     mScheduler->registerDisplay(display->getPhysicalId(), display->holdRefreshRateSelector());
+    if (FlagManager::getInstance().vrr_config()) {
+        mScheduler->setRenderRate(display->getPhysicalId(), activeMode.fps);
+    }
     mScheduler->startTimers();
 
     const auto configs = mVsyncConfiguration->getCurrentConfigs();
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index 520bd22..94eedc8 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -195,7 +195,8 @@
                        private HWC2::ComposerCallback,
                        private ICompositor,
                        private scheduler::ISchedulerCallback,
-                       private compositionengine::ICEPowerCallback {
+                       private compositionengine::ICEPowerCallback,
+                       private scheduler::IVsyncTrackerCallback {
 public:
     struct SkipInitializationTag {};
 
@@ -656,6 +657,10 @@
     // ICEPowerCallback overrides:
     void notifyCpuLoadUp() override;
 
+    // IVsyncTrackerCallback overrides
+    void onVsyncGenerated(PhysicalDisplayId, TimePoint expectedPresentTime,
+                          const scheduler::DisplayModeData&, Period vsyncPeriod) override;
+
     // Toggles the kernel idle timer on or off depending the policy decisions around refresh rates.
     void toggleKernelIdleTimer() REQUIRES(mStateLock);
 
diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h
index c4077df..70ce884 100644
--- a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h
+++ b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h
@@ -225,16 +225,19 @@
 class TestableScheduler : public Scheduler, private ICompositor {
 public:
     TestableScheduler(const std::shared_ptr<scheduler::RefreshRateSelector>& selectorPtr,
-                      sp<VsyncModulator> modulatorPtr, ISchedulerCallback& callback)
+                      sp<VsyncModulator> modulatorPtr, ISchedulerCallback& callback,
+                      IVsyncTrackerCallback& vsyncTrackerCallback)
           : TestableScheduler(std::make_unique<android::mock::VsyncController>(),
                               std::make_shared<android::mock::VSyncTracker>(), selectorPtr,
-                              std::move(modulatorPtr), callback) {}
+                              std::move(modulatorPtr), callback, vsyncTrackerCallback) {}
 
     TestableScheduler(std::unique_ptr<VsyncController> controller,
                       VsyncSchedule::TrackerPtr tracker,
                       std::shared_ptr<RefreshRateSelector> selectorPtr,
-                      sp<VsyncModulator> modulatorPtr, ISchedulerCallback& callback)
-          : Scheduler(*this, callback, Feature::kContentDetection, std::move(modulatorPtr)) {
+                      sp<VsyncModulator> modulatorPtr, ISchedulerCallback& callback,
+                      IVsyncTrackerCallback& vsyncTrackerCallback)
+          : Scheduler(*this, callback, Feature::kContentDetection, std::move(modulatorPtr),
+                      vsyncTrackerCallback) {
         const auto displayId = selectorPtr->getActiveMode().modePtr->getPhysicalDisplayId();
         registerDisplayInternal(displayId, std::move(selectorPtr),
                                 std::shared_ptr<VsyncSchedule>(
@@ -400,7 +403,8 @@
 } // namespace surfaceflinger::test
 
 // TODO(b/189053744) : Create a common test/mock library for surfaceflinger
-class TestableSurfaceFlinger final : private scheduler::ISchedulerCallback {
+class TestableSurfaceFlinger final : private scheduler::ISchedulerCallback,
+                                     private scheduler::IVsyncTrackerCallback {
 public:
     using HotplugEvent = SurfaceFlinger::HotplugEvent;
 
@@ -656,6 +660,7 @@
                         std::unique_ptr<EventThread> appEventThread,
                         std::unique_ptr<EventThread> sfEventThread,
                         scheduler::ISchedulerCallback* callback = nullptr,
+                        scheduler::IVsyncTrackerCallback* vsyncTrackerCallback = nullptr,
                         bool hasMultipleModes = false) {
         constexpr DisplayModeId kModeId60{0};
         DisplayModes modes = makeModes(mock::createDisplayMode(kModeId60, 60_Hz));
@@ -678,7 +683,8 @@
 
         mScheduler = new scheduler::TestableScheduler(std::move(vsyncController),
                                                       std::move(vsyncTracker), mRefreshRateSelector,
-                                                      std::move(modulatorPtr), *(callback ?: this));
+                                                      std::move(modulatorPtr), *(callback ?: this),
+                                                      *(vsyncTrackerCallback ?: this));
 
         mFlinger->mAppConnectionHandle = mScheduler->createConnection(std::move(appEventThread));
         mFlinger->mSfConnectionHandle = mScheduler->createConnection(std::move(sfEventThread));
@@ -799,6 +805,10 @@
     void triggerOnFrameRateOverridesChanged() override {}
     void onChoreographerAttached() override {}
 
+    // IVsyncTrackerCallback overrides
+    void onVsyncGenerated(PhysicalDisplayId, TimePoint, const scheduler::DisplayModeData&,
+                          Period) override {}
+
     surfaceflinger::test::Factory mFactory;
     sp<SurfaceFlinger> mFlinger =
             sp<SurfaceFlinger>::make(mFactory, SurfaceFlinger::SkipInitialization);
diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp
index a8727f9..8fcfd81 100644
--- a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp
+++ b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp
@@ -178,14 +178,23 @@
     dump<scheduler::VSyncDispatchTimerQueueEntry>(&entry, &mFdp);
 }
 
+struct VsyncTrackerCallback : public scheduler::IVsyncTrackerCallback {
+    void onVsyncGenerated(PhysicalDisplayId, TimePoint, const scheduler::DisplayModeData&,
+                          Period) override {}
+};
+
 void SchedulerFuzzer::fuzzVSyncPredictor() {
     uint16_t now = mFdp.ConsumeIntegral<uint16_t>();
     uint16_t historySize = mFdp.ConsumeIntegralInRange<uint16_t>(1, UINT16_MAX);
     uint16_t minimumSamplesForPrediction = mFdp.ConsumeIntegralInRange<uint16_t>(1, UINT16_MAX);
     nsecs_t idealPeriod = mFdp.ConsumeIntegralInRange<nsecs_t>(1, UINT32_MAX);
-    scheduler::VSyncPredictor tracker{kDisplayId, idealPeriod, historySize,
+    VsyncTrackerCallback callback;
+    scheduler::VSyncPredictor tracker{kDisplayId,
+                                      idealPeriod,
+                                      historySize,
                                       minimumSamplesForPrediction,
-                                      mFdp.ConsumeIntegral<uint32_t>() /*outlierTolerancePercent*/};
+                                      mFdp.ConsumeIntegral<uint32_t>() /*outlierTolerancePercent*/,
+                                      callback};
     uint16_t period = mFdp.ConsumeIntegral<uint16_t>();
     tracker.setPeriod(period);
     for (uint16_t i = 0; i < minimumSamplesForPrediction; ++i) {
diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.h b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.h
index 8061a8f..728708f 100644
--- a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.h
+++ b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.h
@@ -100,7 +100,7 @@
         return true;
     }
 
-    void setRenderRate(Fps) override {}
+    void setDisplayModeData(const scheduler::DisplayModeData&) override {}
 
     nsecs_t nextVSyncTime(nsecs_t timePoint) const {
         if (timePoint % mPeriod == 0) {
diff --git a/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp b/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp
index fa31643..1379665 100644
--- a/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp
+++ b/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp
@@ -80,7 +80,8 @@
                             std::unique_ptr<EventThread>(mEventThread),
                             std::unique_ptr<EventThread>(mSFEventThread),
                             TestableSurfaceFlinger::DefaultDisplayMode{displayId},
-                            TestableSurfaceFlinger::SchedulerCallbackImpl::kMock);
+                            TestableSurfaceFlinger::SchedulerCallbackImpl::kMock,
+                            TestableSurfaceFlinger::VsyncTrackerCallbackImpl::kMock);
 }
 
 void DisplayTransactionTest::injectMockComposer(int virtualDisplayCount) {
diff --git a/services/surfaceflinger/tests/unittests/LayerHistoryIntegrationTest.cpp b/services/surfaceflinger/tests/unittests/LayerHistoryIntegrationTest.cpp
index 2f6058f..190c0e8 100644
--- a/services/surfaceflinger/tests/unittests/LayerHistoryIntegrationTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerHistoryIntegrationTest.cpp
@@ -165,8 +165,10 @@
                                                   DisplayModeId(0));
 
     mock::SchedulerCallback mSchedulerCallback;
+    mock::VsyncTrackerCallback mVsyncTrackerCallback;
 
-    TestableScheduler* mScheduler = new TestableScheduler(mSelector, mSchedulerCallback);
+    TestableScheduler* mScheduler =
+            new TestableScheduler(mSelector, mSchedulerCallback, mVsyncTrackerCallback);
 
     TestableSurfaceFlinger mFlinger;
 };
diff --git a/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp b/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp
index e8831ab..5328fa3 100644
--- a/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp
@@ -144,7 +144,9 @@
 
     mock::SchedulerCallback mSchedulerCallback;
 
-    TestableScheduler* mScheduler = new TestableScheduler(mSelector, mSchedulerCallback);
+    mock::VsyncTrackerCallback mVsyncTrackerCallback;
+    TestableScheduler* mScheduler =
+            new TestableScheduler(mSelector, mSchedulerCallback, mVsyncTrackerCallback);
 
     TestableSurfaceFlinger mFlinger;
 };
diff --git a/services/surfaceflinger/tests/unittests/LayerInfoTest.cpp b/services/surfaceflinger/tests/unittests/LayerInfoTest.cpp
index 11072bc..830dcce 100644
--- a/services/surfaceflinger/tests/unittests/LayerInfoTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerInfoTest.cpp
@@ -61,7 +61,9 @@
                                                                               HI_FPS)),
                                                   DisplayModeId(0));
     mock::SchedulerCallback mSchedulerCallback;
-    TestableScheduler* mScheduler = new TestableScheduler(mSelector, mSchedulerCallback);
+    mock::VsyncTrackerCallback mVsyncTrackerCallback;
+    TestableScheduler* mScheduler =
+            new TestableScheduler(mSelector, mSchedulerCallback, mVsyncTrackerCallback);
     TestableSurfaceFlinger mFlinger;
 };
 
diff --git a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
index 87fae2c..b5eb777 100644
--- a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
@@ -89,7 +89,9 @@
                                                   kDisplay1Mode60->getId());
 
     mock::SchedulerCallback mSchedulerCallback;
-    TestableScheduler* mScheduler = new TestableScheduler{mSelector, mSchedulerCallback};
+    mock::VsyncTrackerCallback mVsyncTrackerCallback;
+    TestableScheduler* mScheduler =
+            new TestableScheduler{mSelector, mSchedulerCallback, mVsyncTrackerCallback};
     surfaceflinger::frontend::LayerHierarchyBuilder mLayerHierarchyBuilder{{}};
 
     ConnectionHandle mConnectionHandle;
diff --git a/services/surfaceflinger/tests/unittests/TestableScheduler.h b/services/surfaceflinger/tests/unittests/TestableScheduler.h
index 3d1c900..8b6f0f1 100644
--- a/services/surfaceflinger/tests/unittests/TestableScheduler.h
+++ b/services/surfaceflinger/tests/unittests/TestableScheduler.h
@@ -36,18 +36,21 @@
 
 class TestableScheduler : public Scheduler, private ICompositor {
 public:
-    TestableScheduler(RefreshRateSelectorPtr selectorPtr, ISchedulerCallback& callback)
+    TestableScheduler(RefreshRateSelectorPtr selectorPtr, ISchedulerCallback& callback,
+                      IVsyncTrackerCallback& vsyncTrackerCallback)
           : TestableScheduler(std::make_unique<mock::VsyncController>(),
                               std::make_shared<mock::VSyncTracker>(), std::move(selectorPtr),
-                              sp<VsyncModulator>::make(VsyncConfigSet{}), callback) {}
+                              sp<VsyncModulator>::make(VsyncConfigSet{}), callback,
+                              vsyncTrackerCallback) {}
 
     TestableScheduler(std::unique_ptr<VsyncController> controller,
                       std::shared_ptr<VSyncTracker> tracker, RefreshRateSelectorPtr selectorPtr,
-                      sp<VsyncModulator> modulatorPtr, ISchedulerCallback& callback)
-          : Scheduler(*this, callback,
+                      sp<VsyncModulator> modulatorPtr, ISchedulerCallback& schedulerCallback,
+                      IVsyncTrackerCallback& vsyncTrackerCallback)
+          : Scheduler(*this, schedulerCallback,
                       (FeatureFlags)Feature::kContentDetection |
                               Feature::kSmallDirtyContentDetection,
-                      std::move(modulatorPtr)) {
+                      std::move(modulatorPtr), vsyncTrackerCallback) {
         const auto displayId = selectorPtr->getActiveMode().modePtr->getPhysicalDisplayId();
         registerDisplay(displayId, std::move(selectorPtr), std::move(controller),
                         std::move(tracker));
diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
index 03af56c..d0b2199 100644
--- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
+++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
@@ -53,6 +53,7 @@
 #include "mock/MockFrameTimeline.h"
 #include "mock/MockFrameTracer.h"
 #include "mock/MockSchedulerCallback.h"
+#include "mock/MockVsyncTrackerCallback.h"
 #include "mock/system/window/MockNativeWindow.h"
 
 #include "Scheduler/VSyncTracker.h"
@@ -204,6 +205,8 @@
 
     enum class SchedulerCallbackImpl { kNoOp, kMock };
 
+    enum class VsyncTrackerCallbackImpl { kNoOp, kMock };
+
     struct DefaultDisplayMode {
         // The ID of the injected RefreshRateSelector and its default display mode.
         PhysicalDisplayId displayId;
@@ -213,13 +216,14 @@
 
     using DisplayModesVariant = std::variant<DefaultDisplayMode, RefreshRateSelectorPtr>;
 
-    void setupScheduler(std::unique_ptr<scheduler::VsyncController> vsyncController,
-                        std::shared_ptr<scheduler::VSyncTracker> vsyncTracker,
-                        std::unique_ptr<EventThread> appEventThread,
-                        std::unique_ptr<EventThread> sfEventThread,
-                        DisplayModesVariant modesVariant,
-                        SchedulerCallbackImpl callbackImpl = SchedulerCallbackImpl::kNoOp,
-                        bool useNiceMock = false) {
+    void setupScheduler(
+            std::unique_ptr<scheduler::VsyncController> vsyncController,
+            std::shared_ptr<scheduler::VSyncTracker> vsyncTracker,
+            std::unique_ptr<EventThread> appEventThread, std::unique_ptr<EventThread> sfEventThread,
+            DisplayModesVariant modesVariant,
+            SchedulerCallbackImpl callbackImpl = SchedulerCallbackImpl::kNoOp,
+            VsyncTrackerCallbackImpl vsyncTrackerCallbackImpl = VsyncTrackerCallbackImpl::kNoOp,
+            bool useNiceMock = false) {
         RefreshRateSelectorPtr selectorPtr = ftl::match(
                 modesVariant,
                 [](DefaultDisplayMode arg) {
@@ -239,10 +243,16 @@
 
         mTokenManager = std::make_unique<frametimeline::impl::TokenManager>();
 
-        using Callback = scheduler::ISchedulerCallback;
-        Callback& callback = callbackImpl == SchedulerCallbackImpl::kNoOp
-                ? static_cast<Callback&>(mNoOpSchedulerCallback)
-                : static_cast<Callback&>(mSchedulerCallback);
+        using ISchedulerCallback = scheduler::ISchedulerCallback;
+        ISchedulerCallback& schedulerCallback = callbackImpl == SchedulerCallbackImpl::kNoOp
+                ? static_cast<ISchedulerCallback&>(mNoOpSchedulerCallback)
+                : static_cast<ISchedulerCallback&>(mSchedulerCallback);
+
+        using VsyncTrackerCallback = scheduler::IVsyncTrackerCallback;
+        VsyncTrackerCallback& vsyncTrackerCallback =
+                vsyncTrackerCallbackImpl == VsyncTrackerCallbackImpl::kNoOp
+                ? static_cast<VsyncTrackerCallback&>(mNoOpVsyncTrackerCallback)
+                : static_cast<VsyncTrackerCallback&>(mVsyncTrackerCallback);
 
         auto modulatorPtr = sp<scheduler::VsyncModulator>::make(
                 mFlinger->mVsyncConfiguration->getCurrentConfigs());
@@ -253,12 +263,14 @@
                                                                         std::move(vsyncTracker),
                                                                         std::move(selectorPtr),
                                                                         std::move(modulatorPtr),
-                                                                        callback);
+                                                                        schedulerCallback,
+                                                                        vsyncTrackerCallback);
         } else {
             mScheduler = new scheduler::TestableScheduler(std::move(vsyncController),
                                                           std::move(vsyncTracker),
                                                           std::move(selectorPtr),
-                                                          std::move(modulatorPtr), callback);
+                                                          std::move(modulatorPtr),
+                                                          schedulerCallback, vsyncTrackerCallback);
         }
 
         mScheduler->initVsync(mScheduler->getVsyncSchedule()->getDispatch(), *mTokenManager, 0ms);
@@ -297,7 +309,8 @@
         EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0));
         setupScheduler(std::move(vsyncController), std::move(vsyncTracker), std::move(eventThread),
                        std::move(sfEventThread), DefaultDisplayMode{options.displayId},
-                       SchedulerCallbackImpl::kNoOp, options.useNiceMock);
+                       SchedulerCallbackImpl::kNoOp, VsyncTrackerCallbackImpl::kNoOp,
+                       options.useNiceMock);
     }
 
     void resetScheduler(scheduler::Scheduler* scheduler) { mFlinger->mScheduler.reset(scheduler); }
@@ -1071,6 +1084,8 @@
     sp<SurfaceFlinger> mFlinger;
     scheduler::mock::SchedulerCallback mSchedulerCallback;
     scheduler::mock::NoOpSchedulerCallback mNoOpSchedulerCallback;
+    scheduler::mock::VsyncTrackerCallback mVsyncTrackerCallback;
+    scheduler::mock::NoOpVsyncTrackerCallback mNoOpVsyncTrackerCallback;
     std::unique_ptr<frametimeline::impl::TokenManager> mTokenManager;
     scheduler::TestableScheduler* mScheduler = nullptr;
     Hwc2::mock::PowerAdvisor mPowerAdvisor;
diff --git a/services/surfaceflinger/tests/unittests/VSyncDispatchRealtimeTest.cpp b/services/surfaceflinger/tests/unittests/VSyncDispatchRealtimeTest.cpp
index 41866a1..4be07a1 100644
--- a/services/surfaceflinger/tests/unittests/VSyncDispatchRealtimeTest.cpp
+++ b/services/surfaceflinger/tests/unittests/VSyncDispatchRealtimeTest.cpp
@@ -54,7 +54,7 @@
     void resetModel() final {}
     bool needsMoreSamples() const final { return false; }
     bool isVSyncInPhase(nsecs_t, Fps) const final { return false; }
-    void setRenderRate(Fps) final {}
+    void setDisplayModeData(const DisplayModeData&) final {}
     void dump(std::string&) const final {}
 
 private:
@@ -92,7 +92,7 @@
     void resetModel() final {}
     bool needsMoreSamples() const final { return false; }
     bool isVSyncInPhase(nsecs_t, Fps) const final { return false; }
-    void setRenderRate(Fps) final {}
+    void setDisplayModeData(const DisplayModeData&) final {}
     void dump(std::string&) const final {}
 
 private:
diff --git a/services/surfaceflinger/tests/unittests/VSyncDispatchTimerQueueTest.cpp b/services/surfaceflinger/tests/unittests/VSyncDispatchTimerQueueTest.cpp
index 1dc5498..fbbb4a2 100644
--- a/services/surfaceflinger/tests/unittests/VSyncDispatchTimerQueueTest.cpp
+++ b/services/surfaceflinger/tests/unittests/VSyncDispatchTimerQueueTest.cpp
@@ -59,7 +59,7 @@
     MOCK_METHOD0(resetModel, void());
     MOCK_CONST_METHOD0(needsMoreSamples, bool());
     MOCK_CONST_METHOD2(isVSyncInPhase, bool(nsecs_t, Fps));
-    MOCK_METHOD(void, setRenderRate, (Fps), (override));
+    MOCK_METHOD(void, setDisplayModeData, (const DisplayModeData&), (override));
     MOCK_CONST_METHOD1(dump, void(std::string&));
 
     nsecs_t nextVSyncTime(nsecs_t timePoint) const {
diff --git a/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp b/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp
index 43d683d..30a2855 100644
--- a/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp
+++ b/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp
@@ -23,7 +23,9 @@
 #define LOG_TAG "LibSurfaceFlingerUnittests"
 #define LOG_NDEBUG 0
 
+#include "FlagUtils.h"
 #include "Scheduler/VSyncPredictor.h"
+#include "mock/MockVsyncTrackerCallback.h"
 
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
@@ -31,8 +33,11 @@
 #include <chrono>
 #include <utility>
 
+#include <com_android_graphics_surfaceflinger_flags.h>
+
 using namespace testing;
 using namespace std::literals;
+using namespace com::android::graphics::surfaceflinger;
 
 namespace android::scheduler {
 
@@ -52,13 +57,18 @@
 struct VSyncPredictorTest : testing::Test {
     nsecs_t mNow = 0;
     nsecs_t mPeriod = 1000;
+    scheduler::mock::VsyncTrackerCallback mVsyncTrackerCallback;
     static constexpr size_t kHistorySize = 10;
     static constexpr size_t kMinimumSamplesForPrediction = 6;
     static constexpr size_t kOutlierTolerancePercent = 25;
     static constexpr nsecs_t mMaxRoundingError = 100;
 
-    VSyncPredictor tracker{DEFAULT_DISPLAY_ID, mPeriod, kHistorySize, kMinimumSamplesForPrediction,
-                           kOutlierTolerancePercent};
+    VSyncPredictor tracker{DEFAULT_DISPLAY_ID,
+                           mPeriod,
+                           kHistorySize,
+                           kMinimumSamplesForPrediction,
+                           kOutlierTolerancePercent,
+                           mVsyncTrackerCallback};
 };
 
 TEST_F(VSyncPredictorTest, reportsAnticipatedPeriod) {
@@ -378,8 +388,12 @@
 
 // See b/151146131
 TEST_F(VSyncPredictorTest, hasEnoughPrecision) {
-    VSyncPredictor tracker{DEFAULT_DISPLAY_ID, mPeriod, 20, kMinimumSamplesForPrediction,
-                           kOutlierTolerancePercent};
+    VSyncPredictor tracker{DEFAULT_DISPLAY_ID,
+                           mPeriod,
+                           20,
+                           kMinimumSamplesForPrediction,
+                           kOutlierTolerancePercent,
+                           mVsyncTrackerCallback};
     std::vector<nsecs_t> const simulatedVsyncs{840873348817, 840890049444, 840906762675,
                                                840923581635, 840940161584, 840956868096,
                                                840973702473, 840990256277, 841007116851,
@@ -566,7 +580,7 @@
         tracker.addVsyncTimestamp(mNow);
     }
 
-    tracker.setRenderRate(Fps::fromPeriodNsecs(3 * mPeriod));
+    tracker.setDisplayModeData({.renderRate = Fps::fromPeriodNsecs(3 * mPeriod)});
 
     EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(mNow + mPeriod));
     EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 100), Eq(mNow + mPeriod));
@@ -588,12 +602,12 @@
 
     const auto refreshRate = Fps::fromPeriodNsecs(mPeriod);
 
-    tracker.setRenderRate(refreshRate / 4);
+    tracker.setDisplayModeData({.renderRate = refreshRate / 4});
     EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(mNow + 3 * mPeriod));
     EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 3 * mPeriod), Eq(mNow + 7 * mPeriod));
     EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 7 * mPeriod), Eq(mNow + 11 * mPeriod));
 
-    tracker.setRenderRate(refreshRate / 2);
+    tracker.setDisplayModeData({.renderRate = refreshRate / 2});
     EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(mNow + 1 * mPeriod));
     EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 1 * mPeriod), Eq(mNow + 3 * mPeriod));
     EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 3 * mPeriod), Eq(mNow + 5 * mPeriod));
@@ -601,7 +615,7 @@
     EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 7 * mPeriod), Eq(mNow + 9 * mPeriod));
     EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 9 * mPeriod), Eq(mNow + 11 * mPeriod));
 
-    tracker.setRenderRate(refreshRate / 6);
+    tracker.setDisplayModeData({.renderRate = refreshRate / 6});
     EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(mNow + 1 * mPeriod));
     EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 1 * mPeriod), Eq(mNow + 7 * mPeriod));
 }
@@ -615,7 +629,7 @@
         tracker.addVsyncTimestamp(mNow);
     }
 
-    tracker.setRenderRate(Fps::fromPeriodNsecs(3.5f * mPeriod));
+    tracker.setDisplayModeData({.renderRate = Fps::fromPeriodNsecs(3.5f * mPeriod)});
 
     EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(mNow + mPeriod));
     EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 100), Eq(mNow + mPeriod));
@@ -626,6 +640,39 @@
     EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 5100), Eq(mNow + 6 * mPeriod));
 }
 
+TEST_F(VSyncPredictorTest, vsyncTrackerCallback) {
+    SET_FLAG_FOR_TEST(flags::vrr_config, true);
+    const auto refreshRate = Fps::fromPeriodNsecs(mPeriod);
+    DisplayModeData displayModeData =
+            DisplayModeData{.renderRate = refreshRate,
+                            .notifyExpectedPresentTimeoutOpt = Period::fromNs(30)};
+    tracker.setDisplayModeData(displayModeData);
+    auto last = mNow;
+    for (auto i = 0u; i < kMinimumSamplesForPrediction; i++) {
+        EXPECT_CALL(mVsyncTrackerCallback,
+                    onVsyncGenerated(DEFAULT_DISPLAY_ID, TimePoint::fromNs(last + mPeriod),
+                                     displayModeData, Period::fromNs(mPeriod)))
+                .Times(1);
+        EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(last + mPeriod));
+        mNow += mPeriod;
+        last = mNow;
+        tracker.addVsyncTimestamp(mNow);
+    }
+
+    displayModeData = DisplayModeData{.renderRate = refreshRate / 2,
+                                      .notifyExpectedPresentTimeoutOpt = Period::fromNs(30)};
+    tracker.setDisplayModeData(displayModeData);
+    {
+        // out of render rate phase
+        EXPECT_CALL(mVsyncTrackerCallback,
+                    onVsyncGenerated(DEFAULT_DISPLAY_ID, TimePoint::fromNs(mNow + 3 * mPeriod),
+                                     displayModeData, Period::fromNs(mPeriod)))
+                .Times(1);
+        EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 1 * mPeriod),
+                    Eq(mNow + 3 * mPeriod));
+    }
+}
+
 } // namespace android::scheduler
 
 // TODO(b/129481165): remove the #pragma below and fix conversion issues
diff --git a/services/surfaceflinger/tests/unittests/VSyncReactorTest.cpp b/services/surfaceflinger/tests/unittests/VSyncReactorTest.cpp
index 122192b..aca3ccc 100644
--- a/services/surfaceflinger/tests/unittests/VSyncReactorTest.cpp
+++ b/services/surfaceflinger/tests/unittests/VSyncReactorTest.cpp
@@ -50,7 +50,7 @@
     MOCK_METHOD0(resetModel, void());
     MOCK_CONST_METHOD0(needsMoreSamples, bool());
     MOCK_CONST_METHOD2(isVSyncInPhase, bool(nsecs_t, Fps));
-    MOCK_METHOD(void, setRenderRate, (Fps), (override));
+    MOCK_METHOD(void, setDisplayModeData, (const DisplayModeData&), (override));
     MOCK_CONST_METHOD1(dump, void(std::string&));
 };
 
diff --git a/services/surfaceflinger/tests/unittests/mock/MockVSyncTracker.h b/services/surfaceflinger/tests/unittests/mock/MockVSyncTracker.h
index dcf25e1..31eb86e 100644
--- a/services/surfaceflinger/tests/unittests/mock/MockVSyncTracker.h
+++ b/services/surfaceflinger/tests/unittests/mock/MockVSyncTracker.h
@@ -34,7 +34,7 @@
     MOCK_METHOD0(resetModel, void());
     MOCK_CONST_METHOD0(needsMoreSamples, bool());
     MOCK_CONST_METHOD2(isVSyncInPhase, bool(nsecs_t, Fps));
-    MOCK_METHOD(void, setRenderRate, (Fps), (override));
+    MOCK_METHOD(void, setDisplayModeData, (const scheduler::DisplayModeData&), (override));
     MOCK_CONST_METHOD1(dump, void(std::string&));
 };
 
diff --git a/services/surfaceflinger/tests/unittests/mock/MockVsyncTrackerCallback.h b/services/surfaceflinger/tests/unittests/mock/MockVsyncTrackerCallback.h
new file mode 100644
index 0000000..b8e24e0
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/mock/MockVsyncTrackerCallback.h
@@ -0,0 +1,35 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <gmock/gmock.h>
+
+#include "Scheduler/VSyncTracker.h"
+
+namespace android::scheduler::mock {
+
+struct VsyncTrackerCallback final : IVsyncTrackerCallback {
+    MOCK_METHOD(void, onVsyncGenerated,
+                (PhysicalDisplayId, TimePoint, const scheduler::DisplayModeData&, Period),
+                (override));
+};
+
+struct NoOpVsyncTrackerCallback final : IVsyncTrackerCallback {
+    void onVsyncGenerated(PhysicalDisplayId, TimePoint, const scheduler::DisplayModeData&,
+                          Period) override{};
+};
+} // namespace android::scheduler::mock