[SF] Updates to not send hints without a present being followed

Updates the hint to be sent through a frame scheduling.
Reduce the number of callbacks to notifyExpectedPresentIfRequired
by making calls only through EventThread.
Timeout hint is sent directly without scheduling a frame
to avoid delay in sending the hint.
A hint through setTransactionState for ScheduleOnTx will
be in a follow up CL

BUG: 316615878
Test: atest NotifyExpectedPresentTest
Change-Id: I60f555d69626656901951808353f4a632e9b5e71
diff --git a/services/surfaceflinger/Scheduler/EventThread.cpp b/services/surfaceflinger/Scheduler/EventThread.cpp
index c80c8fd..ccd1c0f 100644
--- a/services/surfaceflinger/Scheduler/EventThread.cpp
+++ b/services/surfaceflinger/Scheduler/EventThread.cpp
@@ -404,6 +404,9 @@
     }();
     generateFrameTimeline(vsyncEventData, frameInterval.ns(), systemTime(SYSTEM_TIME_MONOTONIC),
                           presentTime, deadline);
+    if (FlagManager::getInstance().vrr_config()) {
+        mCallback.onExpectedPresentTimePosted(TimePoint::fromNs(presentTime));
+    }
     return vsyncEventData;
 }
 
@@ -721,6 +724,11 @@
                 removeDisplayEventConnectionLocked(consumer);
         }
     }
+    if (event.header.type == DisplayEventReceiver::DISPLAY_EVENT_VSYNC &&
+        FlagManager::getInstance().vrr_config()) {
+        mCallback.onExpectedPresentTimePosted(
+                TimePoint::fromNs(event.vsync.vsyncData.preferredExpectedPresentationTime()));
+    }
 }
 
 void EventThread::dump(std::string& result) const {
diff --git a/services/surfaceflinger/Scheduler/EventThread.h b/services/surfaceflinger/Scheduler/EventThread.h
index 8970103..90e61a9 100644
--- a/services/surfaceflinger/Scheduler/EventThread.h
+++ b/services/surfaceflinger/Scheduler/EventThread.h
@@ -142,6 +142,7 @@
     virtual bool throttleVsync(TimePoint, uid_t) = 0;
     virtual Period getVsyncPeriod(uid_t) = 0;
     virtual void resync() = 0;
+    virtual void onExpectedPresentTimePosted(TimePoint) = 0;
 };
 
 namespace impl {
diff --git a/services/surfaceflinger/Scheduler/ISchedulerCallback.h b/services/surfaceflinger/Scheduler/ISchedulerCallback.h
index 3b61de7..9f4f5b6 100644
--- a/services/surfaceflinger/Scheduler/ISchedulerCallback.h
+++ b/services/surfaceflinger/Scheduler/ISchedulerCallback.h
@@ -30,6 +30,8 @@
     virtual void kernelTimerChanged(bool expired) = 0;
     virtual void triggerOnFrameRateOverridesChanged() = 0;
     virtual void onChoreographerAttached() = 0;
+    virtual void onExpectedPresentTimePosted(TimePoint, ftl::NonNull<DisplayModePtr>,
+                                             Fps renderRate) = 0;
 
 protected:
     ~ISchedulerCallback() = default;
diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp
index fff97f7..ce47c8d 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.cpp
+++ b/services/surfaceflinger/Scheduler/Scheduler.cpp
@@ -71,15 +71,13 @@
 namespace android::scheduler {
 
 Scheduler::Scheduler(ICompositor& compositor, ISchedulerCallback& callback, FeatureFlags features,
-                     surfaceflinger::Factory& factory, Fps activeRefreshRate, TimeStats& timeStats,
-                     IVsyncTrackerCallback& vsyncTrackerCallback)
+                     surfaceflinger::Factory& factory, Fps activeRefreshRate, TimeStats& timeStats)
       : android::impl::MessageQueue(compositor),
         mFeatures(features),
         mVsyncConfiguration(factory.createVsyncConfiguration(activeRefreshRate)),
         mVsyncModulator(sp<VsyncModulator>::make(mVsyncConfiguration->getCurrentConfigs())),
         mRefreshRateStats(std::make_unique<RefreshRateStats>(timeStats, activeRefreshRate)),
-        mSchedulerCallback(callback),
-        mVsyncTrackerCallback(vsyncTrackerCallback) {}
+        mSchedulerCallback(callback) {}
 
 Scheduler::~Scheduler() {
     // MessageQueue depends on VsyncSchedule, so first destroy it.
@@ -134,10 +132,11 @@
 }
 
 void Scheduler::registerDisplay(PhysicalDisplayId displayId, RefreshRateSelectorPtr selectorPtr) {
-    auto schedulePtr = std::make_shared<VsyncSchedule>(
-            selectorPtr->getActiveMode().modePtr, mFeatures,
-            [this](PhysicalDisplayId id, bool enable) { onHardwareVsyncRequest(id, enable); },
-            mVsyncTrackerCallback);
+    auto schedulePtr =
+            std::make_shared<VsyncSchedule>(selectorPtr->getActiveMode().modePtr, mFeatures,
+                                            [this](PhysicalDisplayId id, bool enable) {
+                                                onHardwareVsyncRequest(id, enable);
+                                            });
 
     registerDisplayInternal(displayId, std::move(selectorPtr), std::move(schedulePtr));
 }
@@ -222,7 +221,12 @@
             targets.try_emplace(id, &targeter.target());
         }
 
-        if (!compositor.commit(pacesetterPtr->displayId, targets)) return;
+        if (!compositor.commit(pacesetterPtr->displayId, targets)) {
+            if (FlagManager::getInstance().vrr_config()) {
+                compositor.sendNotifyExpectedPresentHint(pacesetterPtr->displayId);
+            }
+            return;
+        }
     }
 
     // The pacesetter may have changed or been registered anew during commit.
@@ -263,6 +267,9 @@
     }
 
     const auto resultsPerDisplay = compositor.composite(pacesetterPtr->displayId, targeters);
+    if (FlagManager::getInstance().vrr_config()) {
+        compositor.sendNotifyExpectedPresentHint(pacesetterPtr->displayId);
+    }
     compositor.sample();
 
     for (const auto& [id, targeter] : targeters) {
@@ -323,6 +330,19 @@
     // behaviour.
     return Period::fromNs(currentPeriod.ns() * divisor);
 }
+void Scheduler::onExpectedPresentTimePosted(TimePoint expectedPresentTime) {
+    const auto frameRateMode = [this] {
+        std::scoped_lock lock(mDisplayLock);
+        const auto pacesetterOpt = pacesetterDisplayLocked();
+        const Display& pacesetter = *pacesetterOpt;
+        return pacesetter.selectorPtr->getActiveMode();
+    }();
+
+    if (frameRateMode.modePtr->getVrrConfig()) {
+        mSchedulerCallback.onExpectedPresentTimePosted(expectedPresentTime, frameRateMode.modePtr,
+                                                       frameRateMode.fps);
+    }
+}
 
 ConnectionHandle Scheduler::createEventThread(Cycle cycle,
                                               frametimeline::TokenManager* tokenManager,
diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h
index 9912622..9f29e9f 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.h
+++ b/services/surfaceflinger/Scheduler/Scheduler.h
@@ -111,7 +111,7 @@
 
 public:
     Scheduler(ICompositor&, ISchedulerCallback&, FeatureFlags, surfaceflinger::Factory&,
-              Fps activeRefreshRate, TimeStats&, IVsyncTrackerCallback&);
+              Fps activeRefreshRate, TimeStats&);
     virtual ~Scheduler();
 
     void startTimers();
@@ -458,6 +458,7 @@
     bool throttleVsync(TimePoint, uid_t) override;
     Period getVsyncPeriod(uid_t) override EXCLUDES(mDisplayLock);
     void resync() override EXCLUDES(mDisplayLock);
+    void onExpectedPresentTimePosted(TimePoint expectedPresentTime) override EXCLUDES(mDisplayLock);
 
     // Stores EventThread associated with a given VSyncSource, and an initial EventThreadConnection.
     struct Connection {
@@ -497,8 +498,6 @@
 
     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 6e12b33..8697696 100644
--- a/services/surfaceflinger/Scheduler/VSyncPredictor.cpp
+++ b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp
@@ -48,14 +48,12 @@
 VSyncPredictor::~VSyncPredictor() = default;
 
 VSyncPredictor::VSyncPredictor(ftl::NonNull<DisplayModePtr> modePtr, size_t historySize,
-                               size_t minimumSamplesForPrediction, uint32_t outlierTolerancePercent,
-                               IVsyncTrackerCallback& callback)
+                               size_t minimumSamplesForPrediction, uint32_t outlierTolerancePercent)
       : mId(modePtr->getPhysicalDisplayId()),
         mTraceOn(property_get_bool("debug.sf.vsp_trace", false)),
         kHistorySize(historySize),
         kMinimumSamplesForPrediction(minimumSamplesForPrediction),
         kOutlierTolerancePercent(std::min(outlierTolerancePercent, kMaxPercent)),
-        mVsyncTrackerCallback(callback),
         mDisplayModePtr(modePtr) {
     resetModel();
 }
@@ -312,13 +310,7 @@
             FlagManager::getInstance().vrr_config() && !lastFrameMissed && lastVsyncOpt
             ? std::max(timePoint, *lastVsyncOpt + minFramePeriod - threshold)
             : timePoint;
-    const auto vsyncTime = snapToVsyncAlignedWithRenderRate(baseTime);
-    if (FlagManager::getInstance().vrr_config() && mDisplayModePtr->getVrrConfig()) {
-        const auto vsyncTimePoint = TimePoint::fromNs(vsyncTime);
-        const Fps renderRate = mRenderRateOpt ? *mRenderRateOpt : mDisplayModePtr->getPeakFps();
-        mVsyncTrackerCallback.onVsyncGenerated(vsyncTimePoint, mDisplayModePtr, renderRate);
-    }
-    return vsyncTime;
+    return snapToVsyncAlignedWithRenderRate(baseTime);
 }
 
 nsecs_t VSyncPredictor::snapToVsyncAlignedWithRenderRate(nsecs_t timePoint) const {
diff --git a/services/surfaceflinger/Scheduler/VSyncPredictor.h b/services/surfaceflinger/Scheduler/VSyncPredictor.h
index 9191003..8fd7e60 100644
--- a/services/surfaceflinger/Scheduler/VSyncPredictor.h
+++ b/services/surfaceflinger/Scheduler/VSyncPredictor.h
@@ -37,11 +37,9 @@
      * \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(ftl::NonNull<DisplayModePtr> modePtr, size_t historySize,
-                   size_t minimumSamplesForPrediction, uint32_t outlierTolerancePercent,
-                   IVsyncTrackerCallback&);
+                   size_t minimumSamplesForPrediction, uint32_t outlierTolerancePercent);
     ~VSyncPredictor();
 
     bool addVsyncTimestamp(nsecs_t timestamp) final EXCLUDES(mMutex);
@@ -106,7 +104,6 @@
     size_t const kHistorySize;
     size_t const kMinimumSamplesForPrediction;
     size_t const kOutlierTolerancePercent;
-    IVsyncTrackerCallback& mVsyncTrackerCallback;
     std::mutex mutable mMutex;
 
     std::optional<nsecs_t> mKnownTimestamp GUARDED_BY(mMutex);
diff --git a/services/surfaceflinger/Scheduler/VSyncTracker.h b/services/surfaceflinger/Scheduler/VSyncTracker.h
index 417163f..37bd4b4 100644
--- a/services/surfaceflinger/Scheduler/VSyncTracker.h
+++ b/services/surfaceflinger/Scheduler/VSyncTracker.h
@@ -26,12 +26,6 @@
 
 namespace android::scheduler {
 
-struct IVsyncTrackerCallback {
-    virtual ~IVsyncTrackerCallback() = default;
-    virtual void onVsyncGenerated(TimePoint expectedPresentTime, ftl::NonNull<DisplayModePtr>,
-                                  Fps renderRate) = 0;
-};
-
 /*
  * VSyncTracker is an interface for providing estimates on future Vsync signal times based on
  * historical vsync timing data.
diff --git a/services/surfaceflinger/Scheduler/VsyncSchedule.cpp b/services/surfaceflinger/Scheduler/VsyncSchedule.cpp
index db6a187..1eeccd3 100644
--- a/services/surfaceflinger/Scheduler/VsyncSchedule.cpp
+++ b/services/surfaceflinger/Scheduler/VsyncSchedule.cpp
@@ -57,11 +57,10 @@
 };
 
 VsyncSchedule::VsyncSchedule(ftl::NonNull<DisplayModePtr> modePtr, FeatureFlags features,
-                             RequestHardwareVsync requestHardwareVsync,
-                             IVsyncTrackerCallback& callback)
+                             RequestHardwareVsync requestHardwareVsync)
       : mId(modePtr->getPhysicalDisplayId()),
         mRequestHardwareVsync(std::move(requestHardwareVsync)),
-        mTracker(createTracker(modePtr, callback)),
+        mTracker(createTracker(modePtr)),
         mDispatch(createDispatch(mTracker)),
         mController(createController(modePtr->getPhysicalDisplayId(), *mTracker, features)),
         mTracer(features.test(Feature::kTracePredictedVsync)
@@ -111,15 +110,14 @@
     mDispatch->dump(out);
 }
 
-VsyncSchedule::TrackerPtr VsyncSchedule::createTracker(ftl::NonNull<DisplayModePtr> modePtr,
-                                                       IVsyncTrackerCallback& callback) {
+VsyncSchedule::TrackerPtr VsyncSchedule::createTracker(ftl::NonNull<DisplayModePtr> modePtr) {
     // TODO(b/144707443): Tune constants.
     constexpr size_t kHistorySize = 20;
     constexpr size_t kMinSamplesForPrediction = 6;
     constexpr uint32_t kDiscardOutlierPercent = 20;
 
     return std::make_unique<VSyncPredictor>(modePtr, kHistorySize, kMinSamplesForPrediction,
-                                            kDiscardOutlierPercent, callback);
+                                            kDiscardOutlierPercent);
 }
 
 VsyncSchedule::DispatchPtr VsyncSchedule::createDispatch(TrackerPtr tracker) {
diff --git a/services/surfaceflinger/Scheduler/VsyncSchedule.h b/services/surfaceflinger/Scheduler/VsyncSchedule.h
index 722ea0b..4ff4cdf 100644
--- a/services/surfaceflinger/Scheduler/VsyncSchedule.h
+++ b/services/surfaceflinger/Scheduler/VsyncSchedule.h
@@ -57,8 +57,7 @@
 public:
     using RequestHardwareVsync = std::function<void(PhysicalDisplayId, bool enabled)>;
 
-    VsyncSchedule(ftl::NonNull<DisplayModePtr> modePtr, FeatureFlags, RequestHardwareVsync,
-                  IVsyncTrackerCallback&);
+    VsyncSchedule(ftl::NonNull<DisplayModePtr> modePtr, FeatureFlags, RequestHardwareVsync);
     ~VsyncSchedule();
 
     // IVsyncSource overrides:
@@ -127,7 +126,7 @@
     friend class android::VsyncScheduleTest;
     friend class android::fuzz::SchedulerFuzzer;
 
-    static TrackerPtr createTracker(ftl::NonNull<DisplayModePtr> modePtr, IVsyncTrackerCallback&);
+    static TrackerPtr createTracker(ftl::NonNull<DisplayModePtr> modePtr);
     static DispatchPtr createDispatch(TrackerPtr);
     static ControllerPtr createController(PhysicalDisplayId, VsyncTracker&, FeatureFlags);
 
diff --git a/services/surfaceflinger/Scheduler/include/scheduler/interface/ICompositor.h b/services/surfaceflinger/Scheduler/include/scheduler/interface/ICompositor.h
index 12ee36e..8673a22 100644
--- a/services/surfaceflinger/Scheduler/include/scheduler/interface/ICompositor.h
+++ b/services/surfaceflinger/Scheduler/include/scheduler/interface/ICompositor.h
@@ -47,6 +47,9 @@
     virtual CompositeResultsPerDisplay composite(PhysicalDisplayId pacesetterId,
                                                  const scheduler::FrameTargeters&) = 0;
 
+    // Sends a hint about the expected present time
+    virtual void sendNotifyExpectedPresentHint(PhysicalDisplayId) = 0;
+
     // Samples the composited frame via RegionSamplingThread.
     virtual void sample() = 0;
 
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index 0c640aa..02f8309 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -2750,23 +2750,11 @@
         refreshArgs.devOptFlashDirtyRegionsDelay = std::chrono::milliseconds(mDebugFlashDelay);
     }
 
-    const TimePoint expectedPresentTime = pacesetterTarget.expectedPresentTime();
     // TODO(b/255601557) Update frameInterval per display
-    refreshArgs.frameInterval = mScheduler->getNextFrameInterval(pacesetterId, expectedPresentTime);
+    refreshArgs.frameInterval =
+            mScheduler->getNextFrameInterval(pacesetterId, pacesetterTarget.expectedPresentTime());
     refreshArgs.scheduledFrameTime = mScheduler->getScheduledFrameTime();
     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();
@@ -2830,6 +2818,7 @@
         scheduleComposite(FrameHint::kNone);
     }
 
+    mNotifyExpectedPresentMap[pacesetterId].hintStatus = NotifyExpectedPresentHintStatus::Start;
     onCompositionPresented(pacesetterId, frameTargeters, presentTime);
 
     const bool hadGpuComposited =
@@ -4110,8 +4099,9 @@
     }
 }
 
-void SurfaceFlinger::onVsyncGenerated(TimePoint expectedPresentTime,
-                                      ftl::NonNull<DisplayModePtr> modePtr, Fps renderRate) {
+void SurfaceFlinger::onExpectedPresentTimePosted(TimePoint expectedPresentTime,
+                                                 ftl::NonNull<DisplayModePtr> modePtr,
+                                                 Fps renderRate) {
     const auto vsyncPeriod = modePtr->getVsyncRate().getPeriod();
     const auto timeoutOpt = [&]() -> std::optional<Period> {
         const auto vrrConfig = modePtr->getVrrConfig();
@@ -4132,45 +4122,91 @@
                                                      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);
+    auto& data = mNotifyExpectedPresentMap[displayId];
+    const auto lastExpectedPresentTimestamp = data.lastExpectedPresentTimestamp;
+    const auto lastFrameInterval = data.lastFrameInterval;
+    data.lastFrameInterval = frameInterval;
+    data.lastExpectedPresentTimestamp = expectedPresentTime;
+    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 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);
+    const bool expectedPresentWithinTimeout =
+            isExpectedPresentWithinTimeout(expectedPresentTime, lastExpectedPresentTimestamp,
+                                           timeoutOpt, threshold);
+    if (expectedPresentWithinTimeout && frameIntervalIsOnCadence) {
+        return;
+    }
 
-        using fps_approx_ops::operator!=;
-        if (frameIntervalIsOnCadence && frameInterval != lastFrameInterval) {
-            data.lastExpectedPresentTimestamp = expectedPresentTime;
-        }
-
-        if (expectedPresentWithinTimeout && frameIntervalIsOnCadence) {
+    auto hintStatus = data.hintStatus.load();
+    if (!expectedPresentWithinTimeout) {
+        if (hintStatus != NotifyExpectedPresentHintStatus::Sent ||
+            (timeoutOpt && timeoutOpt->ns() == 0)) {
+            // Send the hint immediately if timeout, as the hint gets
+            // delayed otherwise, as the frame is scheduled close
+            // to the actual present.
+            if (data.hintStatus
+                        .compare_exchange_strong(hintStatus,
+                                                 NotifyExpectedPresentHintStatus::ScheduleOnTx)) {
+                scheduleNotifyExpectedPresentHint(displayId);
+            }
             return;
         }
-        data.lastExpectedPresentTimestamp = expectedPresentTime;
+    }
+
+    if (hintStatus != NotifyExpectedPresentHintStatus::Start) {
+        return;
+    }
+    data.hintStatus.store(NotifyExpectedPresentHintStatus::ScheduleOnPresent);
+    mScheduler->scheduleFrame();
+}
+
+void SurfaceFlinger::scheduleNotifyExpectedPresentHint(PhysicalDisplayId displayId) {
+    auto itr = mNotifyExpectedPresentMap.find(displayId);
+    if (itr == mNotifyExpectedPresentMap.end()) {
+        return;
     }
 
     const char* const whence = __func__;
-    static_cast<void>(mScheduler->schedule([=, this]() FTL_FAKE_GUARD(kMainThreadContext) {
-        const auto status = getHwComposer().notifyExpectedPresent(displayId, expectedPresentTime,
-                                                                  frameInterval);
+    const auto sendHint = [=, this]() {
+        auto& data = mNotifyExpectedPresentMap.at(displayId);
+        data.hintStatus.store(NotifyExpectedPresentHintStatus::Sent);
+        const auto status =
+                getHwComposer().notifyExpectedPresent(displayId, data.lastExpectedPresentTimestamp,
+                                                      data.lastFrameInterval);
         if (status != NO_ERROR) {
             ALOGE("%s failed to notifyExpectedPresentHint for display %" PRId64, whence,
                   displayId.value);
         }
-    }));
+    };
+
+    if (itr->second.hintStatus == NotifyExpectedPresentHintStatus::ScheduleOnTx) {
+        return static_cast<void>(mScheduler->schedule([=,
+                                                       this]() FTL_FAKE_GUARD(kMainThreadContext) {
+            auto& data = mNotifyExpectedPresentMap.at(displayId);
+            auto scheduleHintOnTx = NotifyExpectedPresentHintStatus::ScheduleOnTx;
+            if (data.hintStatus.compare_exchange_strong(scheduleHintOnTx,
+                                                        NotifyExpectedPresentHintStatus::Sent)) {
+                sendHint();
+            }
+        }));
+    }
+    sendHint();
+}
+
+void SurfaceFlinger::sendNotifyExpectedPresentHint(PhysicalDisplayId displayId) {
+    if (auto itr = mNotifyExpectedPresentMap.find(displayId);
+        itr == mNotifyExpectedPresentMap.end() ||
+        itr->second.hintStatus != NotifyExpectedPresentHintStatus::ScheduleOnPresent) {
+        return;
+    }
+    scheduleNotifyExpectedPresentHint(displayId);
 }
 
 void SurfaceFlinger::initScheduler(const sp<const DisplayDevice>& display) {
@@ -4212,8 +4248,7 @@
 
     mScheduler = std::make_unique<Scheduler>(static_cast<ICompositor&>(*this),
                                              static_cast<ISchedulerCallback&>(*this), features,
-                                             getFactory(), activeRefreshRate, *mTimeStats,
-                                             static_cast<IVsyncTrackerCallback&>(*this));
+                                             getFactory(), activeRefreshRate, *mTimeStats);
     mScheduler->registerDisplay(display->getPhysicalId(), display->holdRefreshRateSelector());
     if (FlagManager::getInstance().vrr_config()) {
         mScheduler->setRenderRate(display->getPhysicalId(), activeMode.fps);
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index 6e12172..d536597 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -199,8 +199,7 @@
                        private HWC2::ComposerCallback,
                        private ICompositor,
                        private scheduler::ISchedulerCallback,
-                       private compositionengine::ICEPowerCallback,
-                       private scheduler::IVsyncTrackerCallback {
+                       private compositionengine::ICEPowerCallback {
 public:
     struct SkipInitializationTag {};
 
@@ -685,14 +684,12 @@
     void kernelTimerChanged(bool expired) override;
     void triggerOnFrameRateOverridesChanged() override;
     void onChoreographerAttached() override;
+    void onExpectedPresentTimePosted(TimePoint expectedPresentTime, ftl::NonNull<DisplayModePtr>,
+                                     Fps renderRate) override;
 
     // ICEPowerCallback overrides:
     void notifyCpuLoadUp() override;
 
-    // IVsyncTrackerCallback overrides
-    void onVsyncGenerated(TimePoint expectedPresentTime, ftl::NonNull<DisplayModePtr>,
-                          Fps renderRate) override;
-
     // Toggles the kernel idle timer on or off depending the policy decisions around refresh rates.
     void toggleKernelIdleTimer() REQUIRES(mStateLock);
 
@@ -1480,14 +1477,28 @@
     ftl::SmallMap<int64_t, sp<SurfaceControl>, 3> mMirrorMapForDebug;
 
     // NotifyExpectedPresentHint
+    enum class NotifyExpectedPresentHintStatus {
+        // Represents that framework can start sending hint if required.
+        Start,
+        // Represents that the hint is already sent.
+        Sent,
+        // Represents that the hint will be scheduled with a new frame.
+        ScheduleOnPresent,
+        // Represents that a hint will be sent instantly by scheduling on the main thread.
+        ScheduleOnTx
+    };
     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{};
+        TimePoint lastExpectedPresentTimestamp{};
         Fps lastFrameInterval{};
+        // hintStatus is read and write from multiple threads such as
+        // main thread, EventThread. And is atomic for that reason.
+        std::atomic<NotifyExpectedPresentHintStatus> hintStatus =
+                NotifyExpectedPresentHintStatus::Start;
     };
     std::unordered_map<PhysicalDisplayId, NotifyExpectedPresentData> mNotifyExpectedPresentMap;
-
+    void sendNotifyExpectedPresentHint(PhysicalDisplayId displayId) override
+            REQUIRES(kMainThreadContext);
+    void scheduleNotifyExpectedPresentHint(PhysicalDisplayId displayId);
     void notifyExpectedPresentIfRequired(PhysicalDisplayId, Period vsyncPeriod,
                                          TimePoint expectedPresentTime, Fps frameInterval,
                                          std::optional<Period> timeoutOpt);
diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h
index fa79956..0d15f62 100644
--- a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h
+++ b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h
@@ -228,9 +228,9 @@
                       VsyncSchedule::TrackerPtr tracker,
                       std::shared_ptr<RefreshRateSelector> selectorPtr,
                       surfaceflinger::Factory& factory, TimeStats& timeStats,
-                      ISchedulerCallback& callback, IVsyncTrackerCallback& vsyncTrackerCallback)
+                      ISchedulerCallback& callback)
           : Scheduler(*this, callback, Feature::kContentDetection, factory,
-                      selectorPtr->getActiveMode().fps, timeStats, vsyncTrackerCallback) {
+                      selectorPtr->getActiveMode().fps, timeStats) {
         const auto displayId = selectorPtr->getActiveMode().modePtr->getPhysicalDisplayId();
         registerDisplayInternal(displayId, std::move(selectorPtr),
                                 std::shared_ptr<VsyncSchedule>(
@@ -289,6 +289,8 @@
     }
     void sample() override {}
 
+    void sendNotifyExpectedPresentHint(PhysicalDisplayId) override {}
+
     // MessageQueue overrides:
     void scheduleFrame() override {}
     void postMessage(sp<MessageHandler>&& handler) override { handler->handleMessage(Message()); }
@@ -396,8 +398,7 @@
 } // namespace surfaceflinger::test
 
 // TODO(b/189053744) : Create a common test/mock library for surfaceflinger
-class TestableSurfaceFlinger final : private scheduler::ISchedulerCallback,
-                                     private scheduler::IVsyncTrackerCallback {
+class TestableSurfaceFlinger final : private scheduler::ISchedulerCallback {
 public:
     using HotplugEvent = SurfaceFlinger::HotplugEvent;
 
@@ -660,7 +661,6 @@
                         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));
@@ -675,8 +675,7 @@
         mScheduler = new scheduler::TestableScheduler(std::move(vsyncController),
                                                       std::move(vsyncTracker), mRefreshRateSelector,
                                                       mFactory, *mFlinger->mTimeStats,
-                                                      *(callback ?: this),
-                                                      *(vsyncTrackerCallback ?: this));
+                                                      *(callback ?: this));
 
         mFlinger->mAppConnectionHandle = mScheduler->createConnection(std::move(appEventThread));
         mFlinger->mSfConnectionHandle = mScheduler->createConnection(std::move(sfEventThread));
@@ -796,9 +795,7 @@
     void kernelTimerChanged(bool) override {}
     void triggerOnFrameRateOverridesChanged() override {}
     void onChoreographerAttached() override {}
-
-    // IVsyncTrackerCallback overrides
-    void onVsyncGenerated(TimePoint, ftl::NonNull<DisplayModePtr>, Fps) override {}
+    void onExpectedPresentTimePosted(TimePoint, ftl::NonNull<DisplayModePtr>, Fps) override {}
 
     surfaceflinger::test::Factory mFactory;
     sp<SurfaceFlinger> mFlinger =
diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp
index c098dda..76d4207 100644
--- a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp
+++ b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp
@@ -103,6 +103,7 @@
     bool throttleVsync(TimePoint, uid_t) override { return false; }
     Period getVsyncPeriod(uid_t) override { return kSyncPeriod; }
     void resync() override {}
+    void onExpectedPresentTimePosted(TimePoint) override {}
 };
 
 void SchedulerFuzzer::fuzzEventThread() {
@@ -180,21 +181,15 @@
     dump<scheduler::VSyncDispatchTimerQueueEntry>(&entry, &mFdp);
 }
 
-struct VsyncTrackerCallback : public scheduler::IVsyncTrackerCallback {
-    void onVsyncGenerated(TimePoint, ftl::NonNull<DisplayModePtr>, Fps) 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);
-    VsyncTrackerCallback callback;
     const auto mode = ftl::as_non_null(
             mock::createDisplayMode(DisplayModeId(0), Fps::fromPeriodNsecs(idealPeriod)));
     scheduler::VSyncPredictor tracker{mode, historySize, minimumSamplesForPrediction,
-                                      mFdp.ConsumeIntegral<uint32_t>() /*outlierTolerancePercent*/,
-                                      callback};
+                                      mFdp.ConsumeIntegral<uint32_t>() /*outlierTolerancePercent*/};
     uint16_t period = mFdp.ConsumeIntegral<uint16_t>();
     tracker.setDisplayModePtr(ftl::as_non_null(
             mock::createDisplayMode(DisplayModeId(0), Fps::fromPeriodNsecs(period))));
diff --git a/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp b/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp
index 1379665..fa31643 100644
--- a/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp
+++ b/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp
@@ -80,8 +80,7 @@
                             std::unique_ptr<EventThread>(mEventThread),
                             std::unique_ptr<EventThread>(mSFEventThread),
                             TestableSurfaceFlinger::DefaultDisplayMode{displayId},
-                            TestableSurfaceFlinger::SchedulerCallbackImpl::kMock,
-                            TestableSurfaceFlinger::VsyncTrackerCallbackImpl::kMock);
+                            TestableSurfaceFlinger::SchedulerCallbackImpl::kMock);
 }
 
 void DisplayTransactionTest::injectMockComposer(int virtualDisplayCount) {
diff --git a/services/surfaceflinger/tests/unittests/EventThreadTest.cpp b/services/surfaceflinger/tests/unittests/EventThreadTest.cpp
index 45db0c5..d5ec654 100644
--- a/services/surfaceflinger/tests/unittests/EventThreadTest.cpp
+++ b/services/surfaceflinger/tests/unittests/EventThreadTest.cpp
@@ -85,6 +85,7 @@
     bool throttleVsync(TimePoint, uid_t) override;
     Period getVsyncPeriod(uid_t) override;
     void resync() override;
+    void onExpectedPresentTimePosted(TimePoint) override;
 
     void setupEventThread();
     sp<MockEventThreadConnection> createConnection(ConnectionEventRecorder& recorder,
@@ -107,6 +108,7 @@
                                                       int32_t expectedConfigId,
                                                       nsecs_t expectedVsyncPeriod);
     void expectThrottleVsyncReceived(nsecs_t expectedTimestamp, uid_t);
+    void expectOnExpectedPresentTimePosted(nsecs_t expectedPresentTime);
     void expectUidFrameRateMappingEventReceivedByConnection(PhysicalDisplayId expectedDisplayId,
                                                             std::vector<FrameRateOverride>);
 
@@ -131,6 +133,7 @@
             mVSyncCallbackUnregisterRecorder;
     AsyncCallRecorder<void (*)()> mResyncCallRecorder;
     AsyncCallRecorder<void (*)(nsecs_t, uid_t)> mThrottleVsyncCallRecorder;
+    AsyncCallRecorder<void (*)(nsecs_t)> mOnExpectedPresentTimePostedRecorder;
     ConnectionEventRecorder mConnectionEventCallRecorder{0};
     ConnectionEventRecorder mThrottledConnectionEventCallRecorder{0};
 
@@ -190,6 +193,10 @@
     mResyncCallRecorder.recordCall();
 }
 
+void EventThreadTest::onExpectedPresentTimePosted(TimePoint expectedPresentTime) {
+    mOnExpectedPresentTimePostedRecorder.recordCall(expectedPresentTime.ns());
+}
+
 void EventThreadTest::setupEventThread() {
     mTokenManager = std::make_unique<frametimeline::impl::TokenManager>();
     mThread = std::make_unique<impl::EventThread>("EventThreadTest", mVsyncSchedule,
@@ -244,6 +251,12 @@
     EXPECT_EQ(uid, std::get<1>(args.value()));
 }
 
+void EventThreadTest::expectOnExpectedPresentTimePosted(nsecs_t expectedPresentTime) {
+    auto args = mOnExpectedPresentTimePostedRecorder.waitForCall();
+    ASSERT_TRUE(args.has_value());
+    EXPECT_EQ(expectedPresentTime, std::get<0>(args.value()));
+}
+
 void EventThreadTest::expectVsyncEventReceivedByConnection(
         const char* name, ConnectionEventRecorder& connectionEventRecorder,
         nsecs_t expectedTimestamp, unsigned expectedCount) {
@@ -410,6 +423,7 @@
     onVSyncEvent(123, 456, 789);
     expectThrottleVsyncReceived(456, mConnectionUid);
     expectVsyncEventReceivedByConnection(123, 1u);
+    expectOnExpectedPresentTimePosted(456);
 
     // EventThread is requesting one more callback due to VsyncRequest::SingleSuppressCallback
     expectVSyncCallbackScheduleReceived(true);
@@ -562,16 +576,19 @@
     onVSyncEvent(123, 456, 789);
     expectThrottleVsyncReceived(456, mConnectionUid);
     expectVsyncEventReceivedByConnection(123, 1u);
+    expectOnExpectedPresentTimePosted(456);
 
     // A second event should go to the same places.
     onVSyncEvent(456, 123, 0);
     expectThrottleVsyncReceived(123, mConnectionUid);
     expectVsyncEventReceivedByConnection(456, 2u);
+    expectOnExpectedPresentTimePosted(123);
 
     // A third event should go to the same places.
     onVSyncEvent(789, 777, 111);
     expectThrottleVsyncReceived(777, mConnectionUid);
     expectVsyncEventReceivedByConnection(789, 3u);
+    expectOnExpectedPresentTimePosted(777);
 }
 
 TEST_F(EventThreadTest, setVsyncRateTwoPostsEveryOtherEventToThatConnection) {
diff --git a/services/surfaceflinger/tests/unittests/LayerHistoryIntegrationTest.cpp b/services/surfaceflinger/tests/unittests/LayerHistoryIntegrationTest.cpp
index 734fddb..01762c1 100644
--- a/services/surfaceflinger/tests/unittests/LayerHistoryIntegrationTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerHistoryIntegrationTest.cpp
@@ -165,12 +165,8 @@
                                                   DisplayModeId(0));
 
     mock::SchedulerCallback mSchedulerCallback;
-    mock::VsyncTrackerCallback mVsyncTrackerCallback;
-
     TestableSurfaceFlinger mFlinger;
-
-    TestableScheduler* mScheduler =
-            new TestableScheduler(mSelector, mFlinger, mSchedulerCallback, mVsyncTrackerCallback);
+    TestableScheduler* mScheduler = new TestableScheduler(mSelector, mFlinger, mSchedulerCallback);
 };
 
 namespace {
diff --git a/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp b/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp
index 9456e37..bc06a31 100644
--- a/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp
@@ -146,11 +146,8 @@
                                                   DisplayModeId(0));
 
     mock::SchedulerCallback mSchedulerCallback;
-
-    mock::VsyncTrackerCallback mVsyncTrackerCallback;
     TestableSurfaceFlinger mFlinger;
-    TestableScheduler* mScheduler =
-            new TestableScheduler(mSelector, mFlinger, mSchedulerCallback, mVsyncTrackerCallback);
+    TestableScheduler* mScheduler = new TestableScheduler(mSelector, mFlinger, mSchedulerCallback);
 };
 
 namespace {
diff --git a/services/surfaceflinger/tests/unittests/LayerInfoTest.cpp b/services/surfaceflinger/tests/unittests/LayerInfoTest.cpp
index 22cfbd8..9fe9ee8 100644
--- a/services/surfaceflinger/tests/unittests/LayerInfoTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerInfoTest.cpp
@@ -64,10 +64,8 @@
                                                                               HI_FPS)),
                                                   DisplayModeId(0));
     mock::SchedulerCallback mSchedulerCallback;
-    mock::VsyncTrackerCallback mVsyncTrackerCallback;
     TestableSurfaceFlinger mFlinger;
-    TestableScheduler* mScheduler =
-            new TestableScheduler(mSelector, mFlinger, mSchedulerCallback, mVsyncTrackerCallback);
+    TestableScheduler* mScheduler = new TestableScheduler(mSelector, mFlinger, mSchedulerCallback);
 };
 
 namespace {
diff --git a/services/surfaceflinger/tests/unittests/MessageQueueTest.cpp b/services/surfaceflinger/tests/unittests/MessageQueueTest.cpp
index 249ed40..f5661fc 100644
--- a/services/surfaceflinger/tests/unittests/MessageQueueTest.cpp
+++ b/services/surfaceflinger/tests/unittests/MessageQueueTest.cpp
@@ -41,6 +41,7 @@
         return {};
     }
     void sample() override {}
+    void sendNotifyExpectedPresentHint(PhysicalDisplayId) {}
 } gNoOpCompositor;
 
 class TestableMessageQueue : public impl::MessageQueue {
diff --git a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
index 6986689..10c5848 100644
--- a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
@@ -95,10 +95,8 @@
                                                   kDisplay1Mode60->getId());
 
     mock::SchedulerCallback mSchedulerCallback;
-    mock::VsyncTrackerCallback mVsyncTrackerCallback;
     TestableSurfaceFlinger mFlinger;
-    TestableScheduler* mScheduler =
-            new TestableScheduler{mSelector, mFlinger, mSchedulerCallback, mVsyncTrackerCallback};
+    TestableScheduler* mScheduler = new TestableScheduler{mSelector, mFlinger, mSchedulerCallback};
     surfaceflinger::frontend::LayerHierarchyBuilder mLayerHierarchyBuilder;
 
     ConnectionHandle mConnectionHandle;
@@ -516,6 +514,7 @@
         }
 
         void sample() override {}
+        void sendNotifyExpectedPresentHint(PhysicalDisplayId) override {}
     } compositor(*mScheduler);
 
     mScheduler->doFrameSignal(compositor, VsyncId(42));
@@ -568,7 +567,7 @@
                                                         frameRate.getPeriodNsecs())}));
     std::shared_ptr<VSyncPredictor> vrrTracker =
             std::make_shared<VSyncPredictor>(kMode, kHistorySize, kMinimumSamplesForPrediction,
-                                             kOutlierTolerancePercent, mVsyncTrackerCallback);
+                                             kOutlierTolerancePercent);
     std::shared_ptr<RefreshRateSelector> vrrSelectorPtr =
             std::make_shared<RefreshRateSelector>(makeModes(kMode), kMode->getId());
     TestableScheduler scheduler{std::make_unique<android::mock::VsyncController>(),
@@ -576,8 +575,7 @@
                                 vrrSelectorPtr,
                                 mFlinger.getFactory(),
                                 mFlinger.getTimeStats(),
-                                mSchedulerCallback,
-                                mVsyncTrackerCallback};
+                                mSchedulerCallback};
 
     scheduler.registerDisplay(kMode->getPhysicalDisplayId(), vrrSelectorPtr, vrrTracker);
     vrrSelectorPtr->setActiveMode(kMode->getId(), frameRate);
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_NotifyExpectedPresentTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_NotifyExpectedPresentTest.cpp
index 7206e29..91b9018 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_NotifyExpectedPresentTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_NotifyExpectedPresentTest.cpp
@@ -17,31 +17,81 @@
 #undef LOG_TAG
 #define LOG_TAG "LibSurfaceFlingerUnittests"
 
+#include <gui/SurfaceComposerClient.h>
 #include "DisplayTransactionTestHelpers.h"
 
 namespace android {
 
 using FakeHwcDisplayInjector = TestableSurfaceFlinger::FakeHwcDisplayInjector;
+using android::hardware::graphics::composer::V2_1::Error;
 
 class NotifyExpectedPresentTest : public DisplayTransactionTest {
 public:
     void SetUp() override {
-        mDisplay = PrimaryDisplayVariant::makeFakeExistingDisplayInjector(this).inject();
-        FakeHwcDisplayInjector(mDisplay->getPhysicalId(), hal::DisplayType::PHYSICAL, kIsPrimary)
+        const auto display = PrimaryDisplayVariant::makeFakeExistingDisplayInjector(this).inject();
+        mPhysicalDisplayId = display->getPhysicalId();
+        FakeHwcDisplayInjector(mPhysicalDisplayId, hal::DisplayType::PHYSICAL, /*isPrimary=*/true)
                 .setPowerMode(hal::PowerMode::ON)
                 .inject(&mFlinger, mComposer);
+
+        ASSERT_NO_FATAL_FAILURE(mFlinger.setNotifyExpectedPresentData(mPhysicalDisplayId,
+                                                                      TimePoint::fromNs(0),
+                                                                      kFps60Hz));
+        mCompositor = std::make_unique<Compositor>(mPhysicalDisplayId, mFlinger);
     }
 
 protected:
-    sp<DisplayDevice> mDisplay;
-    static constexpr bool kIsPrimary = true;
-    static constexpr hal::HWDisplayId HWC_DISPLAY_ID =
-            FakeHwcDisplayInjector::DEFAULT_HWC_DISPLAY_ID;
-};
+    struct Compositor final : ICompositor {
+        explicit Compositor(PhysicalDisplayId displayId, TestableSurfaceFlinger& surfaceFlinger)
+              : displayId(displayId), surfaceFlinger(surfaceFlinger) {}
 
-TEST_F(NotifyExpectedPresentTest, notifyExpectedPresentTimeout) {
-    const auto physicDisplayId = mDisplay->getPhysicalId();
-    auto expectedPresentTime = systemTime() + ms2ns(10);
+        void sendNotifyExpectedPresentHint(PhysicalDisplayId id) override {
+            surfaceFlinger.sendNotifyExpectedPresentHint(id);
+        }
+
+        bool commit(PhysicalDisplayId, const scheduler::FrameTargets&) override {
+            return committed;
+        }
+
+        CompositeResultsPerDisplay composite(PhysicalDisplayId pacesetterId,
+                                             const scheduler::FrameTargeters& targeters) override {
+            pacesetterIds.composite = pacesetterId;
+            CompositeResultsPerDisplay results;
+
+            for (const auto& [id, targeter] : targeters) {
+                vsyncIds.composite.emplace_back(id, targeter->target().vsyncId());
+                surfaceFlinger.resetNotifyExpectedPresentHintState(pacesetterId);
+                results.try_emplace(id,
+                                    CompositeResult{.compositionCoverage =
+                                                            CompositionCoverage::Hwc});
+            }
+
+            return results;
+        }
+
+        void sample() override {}
+        void configure() override {}
+
+        struct {
+            PhysicalDisplayId commit;
+            PhysicalDisplayId composite;
+        } pacesetterIds;
+
+        using VsyncIds = std::vector<std::pair<PhysicalDisplayId, VsyncId>>;
+        struct {
+            VsyncIds commit;
+            VsyncIds composite;
+        } vsyncIds;
+
+        bool committed = true;
+        PhysicalDisplayId displayId;
+        TestableSurfaceFlinger& surfaceFlinger;
+    };
+
+    PhysicalDisplayId mPhysicalDisplayId;
+    std::unique_ptr<Compositor> mCompositor;
+    static constexpr hal::HWDisplayId kHwcDisplayId =
+            FakeHwcDisplayInjector::DEFAULT_HWC_DISPLAY_ID;
     static constexpr Fps kFps60Hz = 60_Hz;
     static constexpr int32_t kFrameInterval5HzNs = static_cast<Fps>(5_Hz).getPeriodNsecs();
     static constexpr int32_t kFrameInterval60HzNs = kFps60Hz.getPeriodNsecs();
@@ -49,89 +99,171 @@
     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
+TEST_F(NotifyExpectedPresentTest, noNotifyExpectedPresentHintCall_absentTimeout) {
+    auto expectedPresentTime = systemTime() + ms2ns(10);
+    ASSERT_NO_FATAL_FAILURE(
+            mFlinger.setNotifyExpectedPresentData(mPhysicalDisplayId,
+                                                  TimePoint::fromNs(expectedPresentTime),
+                                                  kFps60Hz));
+    EXPECT_CALL(*mComposer, notifyExpectedPresent(kHwcDisplayId, _, _)).Times(0);
+    for (int i = 0; i < 5; i++) {
         expectedPresentTime += 2 * kFrameInterval5HzNs;
-        EXPECT_CALL(*mComposer, notifyExpectedPresent(HWC_DISPLAY_ID, _, _)).Times(0);
-        mFlinger.notifyExpectedPresentIfRequired(physicDisplayId, kVsyncPeriod,
+        mFlinger.notifyExpectedPresentIfRequired(mPhysicalDisplayId, kVsyncPeriod,
                                                  TimePoint::fromNs(expectedPresentTime), kFps60Hz,
                                                  /*timeoutOpt*/ std::nullopt);
+        EXPECT_TRUE(
+                mFlinger.verifyLastExpectedPresentTime(mPhysicalDisplayId, expectedPresentTime));
+        ASSERT_TRUE(mFlinger.verifyHintStatusIsStart(mPhysicalDisplayId));
     }
+}
+
+TEST_F(NotifyExpectedPresentTest, notifyExpectedPresentHint_zeroTimeout) {
+    auto expectedPresentTime = systemTime() + ms2ns(10);
     {
-        // Timeout is 0
-        expectedPresentTime += kFrameInterval60HzNs;
+        // Very first ExpectedPresent after idle, no previous timestamp.
         EXPECT_CALL(*mComposer,
-                    notifyExpectedPresent(HWC_DISPLAY_ID, expectedPresentTime,
-                                          kFrameInterval60HzNs))
+                    notifyExpectedPresent(kHwcDisplayId, 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,
+        mFlinger.notifyExpectedPresentIfRequired(mPhysicalDisplayId, kVsyncPeriod,
                                                  TimePoint::fromNs(expectedPresentTime), kFps60Hz,
                                                  kTimeoutNs);
+        ASSERT_TRUE(mFlinger.verifyHintIsSent(mPhysicalDisplayId));
+
+        // Present frame
+        mFlinger.scheduler()->doFrameSignal(*mCompositor, VsyncId{42});
+        // Present happens and NotifyExpectedPresentHintStatus is start.
+        ASSERT_TRUE(mFlinger.verifyHintStatusIsStart(mPhysicalDisplayId));
+    }
+    {
+        mCompositor->committed = false;
+        expectedPresentTime += kFrameInterval60HzNs;
+        EXPECT_CALL(*mComposer,
+                    notifyExpectedPresent(kHwcDisplayId, expectedPresentTime, kFrameInterval60HzNs))
+                .WillOnce(Return(Error::NONE));
+        mFlinger.notifyExpectedPresentIfRequired(mPhysicalDisplayId, kVsyncPeriod,
+                                                 TimePoint::fromNs(expectedPresentTime), kFps60Hz,
+                                                 Period::fromNs(0));
+        EXPECT_TRUE(
+                mFlinger.verifyLastExpectedPresentTime(mPhysicalDisplayId, expectedPresentTime));
+        ASSERT_TRUE(mFlinger.verifyHintIsSent(mPhysicalDisplayId));
+        mFlinger.scheduler()->doFrameSignal(*mCompositor, VsyncId{42});
+        // Hint sent
+        ASSERT_TRUE(mFlinger.verifyHintIsSent(mPhysicalDisplayId));
+    }
+    {
+        expectedPresentTime += kFrameInterval60HzNs;
+        EXPECT_CALL(*mComposer,
+                    notifyExpectedPresent(kHwcDisplayId, expectedPresentTime, kFrameInterval60HzNs))
+                .WillOnce(Return(Error::NONE));
+        mFlinger.notifyExpectedPresentIfRequired(mPhysicalDisplayId, kVsyncPeriod,
+                                                 TimePoint::fromNs(expectedPresentTime), kFps60Hz,
+                                                 Period::fromNs(0));
+        EXPECT_TRUE(
+                mFlinger.verifyLastExpectedPresentTime(mPhysicalDisplayId, expectedPresentTime));
+        // Hint is executed
+        ASSERT_TRUE(mFlinger.verifyHintIsSent(mPhysicalDisplayId));
+        mFlinger.scheduler()->doFrameSignal(*mCompositor, VsyncId{42});
+        ASSERT_TRUE(mFlinger.verifyHintIsSent(mPhysicalDisplayId));
+    }
+}
+
+TEST_F(NotifyExpectedPresentTest, notifyExpectedPresentTimeout) {
+    auto expectedPresentTime = systemTime() + ms2ns(10);
+    {
+        // Very first ExpectedPresent after idle, no previous timestamp
+        mCompositor->committed = false;
+        EXPECT_CALL(*mComposer,
+                    notifyExpectedPresent(kHwcDisplayId, expectedPresentTime, kFrameInterval60HzNs))
+                .WillOnce(Return(Error::NONE));
+        mFlinger.notifyExpectedPresentIfRequired(mPhysicalDisplayId, kVsyncPeriod,
+                                                 TimePoint::fromNs(expectedPresentTime), kFps60Hz,
+                                                 kTimeoutNs);
+        ASSERT_TRUE(mFlinger.verifyHintIsSent(mPhysicalDisplayId));
+        mFlinger.scheduler()->doFrameSignal(*mCompositor, VsyncId{42});
+        ASSERT_TRUE(mFlinger.verifyHintIsSent(mPhysicalDisplayId));
+    }
+    {
+        // ExpectedPresentTime is after the timeoutNs
+        mCompositor->committed = true;
+        expectedPresentTime += 2 * kFrameInterval5HzNs;
+        EXPECT_CALL(*mComposer, notifyExpectedPresent(kHwcDisplayId, _, _)).Times(0);
+        mFlinger.notifyExpectedPresentIfRequired(mPhysicalDisplayId, kVsyncPeriod,
+                                                 TimePoint::fromNs(expectedPresentTime), kFps60Hz,
+                                                 kTimeoutNs);
+        EXPECT_TRUE(
+                mFlinger.verifyLastExpectedPresentTime(mPhysicalDisplayId, expectedPresentTime));
+        ASSERT_TRUE(mFlinger.verifyHintIsSent(mPhysicalDisplayId));
+        mFlinger.scheduler()->doFrameSignal(*mCompositor, VsyncId{42});
+        // Present happens notifyExpectedPresentHintStatus is Start
+        ASSERT_TRUE(mFlinger.verifyHintStatusIsStart(mPhysicalDisplayId));
+
+        // Another expectedPresent after timeout
+        expectedPresentTime += 2 * kFrameInterval5HzNs;
+        EXPECT_CALL(*mComposer,
+                    notifyExpectedPresent(kHwcDisplayId, expectedPresentTime, kFrameInterval60HzNs))
+                .WillOnce(Return(Error::NONE));
+        mFlinger.notifyExpectedPresentIfRequired(mPhysicalDisplayId, kVsyncPeriod,
+                                                 TimePoint::fromNs(expectedPresentTime), kFps60Hz,
+                                                 kTimeoutNs);
+        EXPECT_TRUE(
+                mFlinger.verifyLastExpectedPresentTime(mPhysicalDisplayId, expectedPresentTime));
+        ASSERT_TRUE(mFlinger.verifyHintIsSent(mPhysicalDisplayId));
+        mFlinger.scheduler()->doFrameSignal(*mCompositor, VsyncId{42});
+        ASSERT_TRUE(mFlinger.verifyHintStatusIsStart(mPhysicalDisplayId));
     }
     {
         // ExpectedPresent has not changed
-        EXPECT_CALL(*mComposer, notifyExpectedPresent(HWC_DISPLAY_ID, _, _)).Times(0);
-        mFlinger.notifyExpectedPresentIfRequired(physicDisplayId, kVsyncPeriod,
+        EXPECT_CALL(*mComposer, notifyExpectedPresent(kHwcDisplayId, _, _)).Times(0);
+        mFlinger.notifyExpectedPresentIfRequired(mPhysicalDisplayId, kVsyncPeriod,
                                                  TimePoint::fromNs(expectedPresentTime), kFps60Hz,
                                                  kTimeoutNs);
+        EXPECT_TRUE(
+                mFlinger.verifyLastExpectedPresentTime(mPhysicalDisplayId, expectedPresentTime));
+        ASSERT_TRUE(mFlinger.verifyHintStatusIsStart(mPhysicalDisplayId));
+        mFlinger.scheduler()->doFrameSignal(*mCompositor, VsyncId{42});
+        ASSERT_TRUE(mFlinger.verifyHintStatusIsStart(mPhysicalDisplayId));
     }
     {
-        // ExpectedPresent is after the last reported ExpectedPresent.
+        // ExpectedPresent is after the last reported ExpectedPresent and within timeout.
         expectedPresentTime += kFrameInterval60HzNs;
-        EXPECT_CALL(*mComposer, notifyExpectedPresent(HWC_DISPLAY_ID, _, _)).Times(0);
-        mFlinger.notifyExpectedPresentIfRequired(physicDisplayId, kVsyncPeriod,
+        EXPECT_CALL(*mComposer, notifyExpectedPresent(kHwcDisplayId, _, _)).Times(0);
+        mFlinger.notifyExpectedPresentIfRequired(mPhysicalDisplayId, kVsyncPeriod,
                                                  TimePoint::fromNs(expectedPresentTime), kFps60Hz,
                                                  kTimeoutNs);
+        EXPECT_TRUE(
+                mFlinger.verifyLastExpectedPresentTime(mPhysicalDisplayId, expectedPresentTime));
+        ASSERT_TRUE(mFlinger.verifyHintStatusIsStart(mPhysicalDisplayId));
+        mFlinger.scheduler()->doFrameSignal(*mCompositor, VsyncId{42});
+        ASSERT_TRUE(mFlinger.verifyHintStatusIsStart(mPhysicalDisplayId));
     }
     {
         // ExpectedPresent is before the last reported ExpectedPresent but after the timeoutNs,
         // representing we changed our decision and want to present earlier than previously
         // reported.
+        mCompositor->committed = false;
         expectedPresentTime -= kFrameInterval120HzNs;
         EXPECT_CALL(*mComposer,
-                    notifyExpectedPresent(HWC_DISPLAY_ID, expectedPresentTime,
-                                          kFrameInterval60HzNs))
+                    notifyExpectedPresent(kHwcDisplayId, expectedPresentTime, kFrameInterval60HzNs))
                 .WillOnce(Return(Error::NONE));
-        mFlinger.notifyExpectedPresentIfRequired(physicDisplayId, kVsyncPeriod,
+        mFlinger.notifyExpectedPresentIfRequired(mPhysicalDisplayId, kVsyncPeriod,
                                                  TimePoint::fromNs(expectedPresentTime), kFps60Hz,
                                                  kTimeoutNs);
+        EXPECT_TRUE(
+                mFlinger.verifyLastExpectedPresentTime(mPhysicalDisplayId, expectedPresentTime));
+        ASSERT_TRUE(mFlinger.verifyHintIsScheduledOnPresent(mPhysicalDisplayId));
+        mFlinger.scheduler()->doFrameSignal(*mCompositor, VsyncId{42});
+        ASSERT_TRUE(mFlinger.verifyHintIsSent(mPhysicalDisplayId));
     }
 }
 
 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,
+    ASSERT_NO_FATAL_FAILURE(mFlinger.setNotifyExpectedPresentData(mPhysicalDisplayId,
                                                                   TimePoint::fromNs(now),
                                                                   Fps::fromValue(0)));
     static constexpr int32_t kFrameIntervalNs120Hz = static_cast<Fps>(120_Hz).getPeriodNsecs();
@@ -147,7 +279,7 @@
 
     struct FrameRateIntervalTestData {
         int32_t frameIntervalNs;
-        bool callExpectedPresent;
+        bool callNotifyExpectedPresentHint;
     };
     const std::vector<FrameRateIntervalTestData> frameIntervals = {
             {kFrameIntervalNs60Hz, true},  {kFrameIntervalNs96Hz, true},
@@ -159,21 +291,35 @@
             {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);
+    for (size_t i = 0; i < frameIntervals.size(); i++) {
+        const auto& [frameIntervalNs, callNotifyExpectedPresentHint] = frameIntervals[i];
+        expectedPresentTime += frameIntervalNs;
+        mFlinger.notifyExpectedPresentIfRequired(mPhysicalDisplayId, kVsyncPeriod,
+                                                 TimePoint::fromNs(expectedPresentTime),
+                                                 Fps::fromPeriodNsecs(frameIntervalNs), kTimeoutNs);
+
+        if (callNotifyExpectedPresentHint) {
+            mCompositor->committed = false;
+            ASSERT_TRUE(mFlinger.verifyHintIsScheduledOnPresent(mPhysicalDisplayId))
+                    << "Hint not scheduled for frameInterval " << frameIntervalNs << " at index "
+                    << i;
+            EXPECT_CALL(*mComposer,
+                        notifyExpectedPresent(kHwcDisplayId, expectedPresentTime, frameIntervalNs))
+                    .WillOnce(Return(Error::NONE));
+        } else {
+            // Only lastExpectedPresentTime is updated
+            EXPECT_TRUE(
+                    mFlinger.verifyLastExpectedPresentTime(mPhysicalDisplayId, expectedPresentTime))
+                    << "LastExpectedPresentTime for frameInterval " << frameIntervalNs
+                    << "at index " << i << " did not match for frameInterval " << frameIntervalNs;
+            EXPECT_CALL(*mComposer, notifyExpectedPresent(kHwcDisplayId, _, _)).Times(0);
+        }
+        mFlinger.scheduler()->doFrameSignal(*mCompositor, VsyncId{42});
+
+        if (callNotifyExpectedPresentHint) {
+            // Present resumes the calls to the notifyExpectedPresentHint.
+            mCompositor->committed = true;
+            mFlinger.scheduler()->doFrameSignal(*mCompositor, VsyncId{42});
         }
     }
 }
diff --git a/services/surfaceflinger/tests/unittests/TestableScheduler.cpp b/services/surfaceflinger/tests/unittests/TestableScheduler.cpp
index e0b7366..7b92a5b 100644
--- a/services/surfaceflinger/tests/unittests/TestableScheduler.cpp
+++ b/services/surfaceflinger/tests/unittests/TestableScheduler.cpp
@@ -21,11 +21,10 @@
 
 TestableScheduler::TestableScheduler(RefreshRateSelectorPtr selectorPtr,
                                      TestableSurfaceFlinger& testableSurfaceFlinger,
-                                     ISchedulerCallback& callback,
-                                     IVsyncTrackerCallback& vsyncTrackerCallback)
+                                     ISchedulerCallback& callback)
       : TestableScheduler(std::make_unique<android::mock::VsyncController>(),
                           std::make_shared<android::mock::VSyncTracker>(), std::move(selectorPtr),
                           testableSurfaceFlinger.getFactory(),
-                          testableSurfaceFlinger.getTimeStats(), callback, vsyncTrackerCallback) {}
+                          testableSurfaceFlinger.getTimeStats(), callback) {}
 
 } // namespace android::scheduler
diff --git a/services/surfaceflinger/tests/unittests/TestableScheduler.h b/services/surfaceflinger/tests/unittests/TestableScheduler.h
index 6213713..25a85df 100644
--- a/services/surfaceflinger/tests/unittests/TestableScheduler.h
+++ b/services/surfaceflinger/tests/unittests/TestableScheduler.h
@@ -41,18 +41,16 @@
 class TestableScheduler : public Scheduler, private ICompositor {
 public:
     TestableScheduler(RefreshRateSelectorPtr selectorPtr,
-                      TestableSurfaceFlinger& testableSurfaceFlinger, ISchedulerCallback& callback,
-                      IVsyncTrackerCallback& vsyncTrackerCallback);
+                      TestableSurfaceFlinger& testableSurfaceFlinger, ISchedulerCallback& callback);
 
     TestableScheduler(std::unique_ptr<VsyncController> controller,
                       std::shared_ptr<VSyncTracker> tracker, RefreshRateSelectorPtr selectorPtr,
                       surfaceflinger::Factory& factory, TimeStats& timeStats,
-                      ISchedulerCallback& schedulerCallback,
-                      IVsyncTrackerCallback& vsyncTrackerCallback)
+                      ISchedulerCallback& schedulerCallback)
           : Scheduler(*this, schedulerCallback,
                       (FeatureFlags)Feature::kContentDetection |
                               Feature::kSmallDirtyContentDetection,
-                      factory, selectorPtr->getActiveMode().fps, timeStats, vsyncTrackerCallback) {
+                      factory, selectorPtr->getActiveMode().fps, timeStats) {
         const auto displayId = selectorPtr->getActiveMode().modePtr->getPhysicalDisplayId();
         registerDisplay(displayId, std::move(selectorPtr), std::move(controller),
                         std::move(tracker));
@@ -210,6 +208,7 @@
         return {};
     }
     void sample() override {}
+    void sendNotifyExpectedPresentHint(PhysicalDisplayId) override {}
 };
 
 } // namespace android::scheduler
diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
index 4e0b5af..6a916a4 100644
--- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
+++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
@@ -53,7 +53,6 @@
 #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"
@@ -205,8 +204,6 @@
 
     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;
@@ -220,14 +217,13 @@
 
     TimeStats& getTimeStats() { return *mFlinger->mTimeStats; }
 
-    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) {
+    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) {
         RefreshRateSelectorPtr selectorPtr = ftl::match(
                 modesVariant,
                 [](DefaultDisplayMode arg) {
@@ -245,12 +241,6 @@
                 ? 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);
-
         if (useNiceMock) {
             mScheduler =
                     new testing::NiceMock<scheduler::TestableScheduler>(std::move(vsyncController),
@@ -258,14 +248,12 @@
                                                                         std::move(selectorPtr),
                                                                         mFactory,
                                                                         *mFlinger->mTimeStats,
-                                                                        schedulerCallback,
-                                                                        vsyncTrackerCallback);
+                                                                        schedulerCallback);
         } else {
             mScheduler = new scheduler::TestableScheduler(std::move(vsyncController),
                                                           std::move(vsyncTracker),
                                                           std::move(selectorPtr), mFactory,
-                                                          *mFlinger->mTimeStats, schedulerCallback,
-                                                          vsyncTrackerCallback);
+                                                          *mFlinger->mTimeStats, schedulerCallback);
         }
 
         mScheduler->initVsync(*mTokenManager, 0ms);
@@ -307,8 +295,7 @@
         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, VsyncTrackerCallbackImpl::kNoOp,
-                       options.useNiceMock);
+                       SchedulerCallbackImpl::kNoOp, options.useNiceMock);
     }
 
     void resetScheduler(scheduler::Scheduler* scheduler) { mFlinger->mScheduler.reset(scheduler); }
@@ -698,6 +685,36 @@
                                                   frameInterval, timeoutOpt);
     }
 
+    void sendNotifyExpectedPresentHint(PhysicalDisplayId displayId) {
+        ftl::FakeGuard guard(kMainThreadContext);
+        mFlinger->sendNotifyExpectedPresentHint(displayId);
+    }
+
+    bool verifyHintIsScheduledOnPresent(PhysicalDisplayId displayId) {
+        return mFlinger->mNotifyExpectedPresentMap.at(displayId).hintStatus ==
+                SurfaceFlinger::NotifyExpectedPresentHintStatus::ScheduleOnPresent;
+    }
+
+    bool verifyHintIsSent(PhysicalDisplayId displayId) {
+        return mFlinger->mNotifyExpectedPresentMap.at(displayId).hintStatus ==
+                SurfaceFlinger::NotifyExpectedPresentHintStatus::Sent;
+    }
+
+    bool verifyHintStatusIsStart(PhysicalDisplayId displayId) {
+        return mFlinger->mNotifyExpectedPresentMap.at(displayId).hintStatus ==
+                SurfaceFlinger::NotifyExpectedPresentHintStatus::Start;
+    }
+
+    bool verifyHintStatusIsScheduledOnTx(PhysicalDisplayId displayId) {
+        return mFlinger->mNotifyExpectedPresentMap.at(displayId).hintStatus ==
+                SurfaceFlinger::NotifyExpectedPresentHintStatus::ScheduleOnTx;
+    }
+
+    bool verifyLastExpectedPresentTime(PhysicalDisplayId displayId, nsecs_t expectedPresentTime) {
+        return mFlinger->mNotifyExpectedPresentMap.at(displayId)
+                       .lastExpectedPresentTimestamp.ns() == expectedPresentTime;
+    }
+
     void setNotifyExpectedPresentData(PhysicalDisplayId displayId,
                                       TimePoint lastExpectedPresentTimestamp,
                                       Fps lastFrameInterval) {
@@ -706,6 +723,11 @@
         displayData.lastFrameInterval = lastFrameInterval;
     }
 
+    void resetNotifyExpectedPresentHintState(PhysicalDisplayId displayId) {
+        mFlinger->mNotifyExpectedPresentMap.at(displayId).hintStatus =
+                SurfaceFlinger::NotifyExpectedPresentHintStatus::Start;
+    }
+
     ~TestableSurfaceFlinger() {
         // All these pointer and container clears help ensure that GMock does
         // not report a leaked object, since the SurfaceFlinger instance may
@@ -1102,8 +1124,6 @@
     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/VSyncPredictorTest.cpp b/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp
index d952b70..b9f3d70 100644
--- a/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp
+++ b/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp
@@ -26,7 +26,6 @@
 #include <common/test/FlagUtils.h>
 #include "Scheduler/VSyncPredictor.h"
 #include "mock/DisplayHardware/MockDisplayMode.h"
-#include "mock/MockVsyncTrackerCallback.h"
 
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
@@ -82,14 +81,13 @@
     nsecs_t mNow = 0;
     nsecs_t mPeriod = 1000;
     ftl::NonNull<DisplayModePtr> mMode = displayMode(mPeriod);
-    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{mMode, kHistorySize, kMinimumSamplesForPrediction,
-                           kOutlierTolerancePercent, mVsyncTrackerCallback};
+                           kOutlierTolerancePercent};
 };
 
 TEST_F(VSyncPredictorTest, reportsAnticipatedPeriod) {
@@ -410,8 +408,7 @@
 // See b/151146131
 TEST_F(VSyncPredictorTest, hasEnoughPrecision) {
     const auto mode = displayMode(mPeriod);
-    VSyncPredictor tracker{mode, 20, kMinimumSamplesForPrediction, kOutlierTolerancePercent,
-                           mVsyncTrackerCallback};
+    VSyncPredictor tracker{mode, 20, kMinimumSamplesForPrediction, kOutlierTolerancePercent};
     std::vector<nsecs_t> const simulatedVsyncs{840873348817, 840890049444, 840906762675,
                                                840923581635, 840940161584, 840956868096,
                                                840973702473, 840990256277, 841007116851,
@@ -658,48 +655,6 @@
     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);
-    NotifyExpectedPresentConfig notifyExpectedPresentConfig;
-    notifyExpectedPresentConfig.timeoutNs = Period::fromNs(30).ns();
-
-    hal::VrrConfig vrrConfig;
-    vrrConfig.notifyExpectedPresentConfig = notifyExpectedPresentConfig;
-    vrrConfig.minFrameIntervalNs = refreshRate.getPeriodNsecs();
-
-    const int32_t kGroup = 0;
-    const auto kResolution = ui::Size(1920, 1080);
-    const auto mode =
-            ftl::as_non_null(createVrrDisplayMode(DisplayModeId(0), refreshRate, vrrConfig, kGroup,
-                                                  kResolution, DEFAULT_DISPLAY_ID));
-
-    tracker.setDisplayModePtr(mode);
-    auto last = mNow;
-    for (auto i = 0u; i < kMinimumSamplesForPrediction; i++) {
-        EXPECT_CALL(mVsyncTrackerCallback,
-                    onVsyncGenerated(TimePoint::fromNs(last + mPeriod), mode,
-                                     FpsMatcher(refreshRate)))
-                .Times(1);
-        EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(last + mPeriod));
-        mNow += mPeriod;
-        last = mNow;
-        tracker.addVsyncTimestamp(mNow);
-    }
-
-    tracker.setRenderRate(refreshRate / 2);
-    {
-        // out of render rate phase
-        EXPECT_CALL(mVsyncTrackerCallback,
-                    onVsyncGenerated(TimePoint::fromNs(mNow + 3 * mPeriod), mode,
-                                     FpsMatcher(refreshRate / 2)))
-                .Times(1);
-        EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 1 * mPeriod),
-                    Eq(mNow + 3 * mPeriod));
-    }
-}
-
 TEST_F(VSyncPredictorTest, adjustsVrrTimeline) {
     SET_FLAG_FOR_TEST(flags::vrr_config, true);
 
@@ -716,7 +671,7 @@
                                      .build());
 
     VSyncPredictor vrrTracker{kMode, kHistorySize, kMinimumSamplesForPrediction,
-                              kOutlierTolerancePercent, mVsyncTrackerCallback};
+                              kOutlierTolerancePercent};
 
     vrrTracker.setRenderRate(minFrameRate);
     vrrTracker.addVsyncTimestamp(0);
@@ -733,28 +688,6 @@
     EXPECT_EQ(5000, vrrTracker.nextAnticipatedVSyncTimeFrom(4500, 4500));
     EXPECT_EQ(6000, vrrTracker.nextAnticipatedVSyncTimeFrom(5000, 5000));
 }
-
-TEST_F(VSyncPredictorTest, absentVrrConfigNoVsyncTrackerCallback) {
-    SET_FLAG_FOR_TEST(flags::vrr_config, true);
-    const auto refreshRate = Fps::fromPeriodNsecs(mPeriod);
-    const std::optional<hal::VrrConfig> vrrConfigOpt = std::nullopt;
-    constexpr int32_t kGroup = 0;
-    constexpr auto kResolution = ui::Size(1920, 1080);
-    const auto mode =
-            ftl::as_non_null(createVrrDisplayMode(DisplayModeId(0), refreshRate, vrrConfigOpt,
-                                                  kGroup, kResolution, DEFAULT_DISPLAY_ID));
-    tracker.setDisplayModePtr(mode);
-
-    auto last = mNow;
-    for (auto i = 0u; i < kMinimumSamplesForPrediction; i++) {
-        EXPECT_CALL(mVsyncTrackerCallback, onVsyncGenerated(_, _, _)).Times(0);
-        EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(last + mPeriod));
-        mNow += mPeriod;
-        last = mNow;
-        tracker.addVsyncTimestamp(mNow);
-    }
-}
-
 } // namespace android::scheduler
 
 // TODO(b/129481165): remove the #pragma below and fix conversion issues
diff --git a/services/surfaceflinger/tests/unittests/mock/MockSchedulerCallback.h b/services/surfaceflinger/tests/unittests/mock/MockSchedulerCallback.h
index 22b2ccc..4ca0542 100644
--- a/services/surfaceflinger/tests/unittests/mock/MockSchedulerCallback.h
+++ b/services/surfaceflinger/tests/unittests/mock/MockSchedulerCallback.h
@@ -28,6 +28,8 @@
     MOCK_METHOD(void, kernelTimerChanged, (bool), (override));
     MOCK_METHOD(void, triggerOnFrameRateOverridesChanged, (), (override));
     MOCK_METHOD(void, onChoreographerAttached, (), (override));
+    MOCK_METHOD(void, onExpectedPresentTimePosted, (TimePoint, ftl::NonNull<DisplayModePtr>, Fps),
+                (override));
 };
 
 struct NoOpSchedulerCallback final : ISchedulerCallback {
@@ -36,6 +38,7 @@
     void kernelTimerChanged(bool) override {}
     void triggerOnFrameRateOverridesChanged() override {}
     void onChoreographerAttached() override {}
+    void onExpectedPresentTimePosted(TimePoint, ftl::NonNull<DisplayModePtr>, Fps) override {}
 };
 
 } // namespace android::scheduler::mock
diff --git a/services/surfaceflinger/tests/unittests/mock/MockVsyncTrackerCallback.h b/services/surfaceflinger/tests/unittests/mock/MockVsyncTrackerCallback.h
deleted file mode 100644
index b48529f..0000000
--- a/services/surfaceflinger/tests/unittests/mock/MockVsyncTrackerCallback.h
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * 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, (TimePoint, ftl::NonNull<DisplayModePtr>, Fps), (override));
-};
-
-struct NoOpVsyncTrackerCallback final : IVsyncTrackerCallback {
-    void onVsyncGenerated(TimePoint, ftl::NonNull<DisplayModePtr>, Fps) override{};
-};
-} // namespace android::scheduler::mock