SF: Decouple VsyncModulator from Scheduler

Let VsyncModulator return phase offsets to SurfaceFlinger, and let the
latter propagate them to Scheduler.

Clean up identifiers and comments for consistency and regularity, and
fix uninitialized atomics.

Parametrize clock to avoid sleeping in tests, and minimize boilerplate
in test cases.

Bug: 160012986
Test: systrace with debug.sf.vsync_trace_detailed_info
Test: libsurfaceflinger_unittest
Change-Id: I05a4279592d38fdd933aad48118b2de0135cd0ea
diff --git a/services/surfaceflinger/Scheduler/PhaseOffsets.h b/services/surfaceflinger/Scheduler/PhaseOffsets.h
index e395b42..0ae9fef 100644
--- a/services/surfaceflinger/Scheduler/PhaseOffsets.h
+++ b/services/surfaceflinger/Scheduler/PhaseOffsets.h
@@ -31,7 +31,7 @@
  */
 class PhaseConfiguration {
 public:
-    using Offsets = VSyncModulator::OffsetsConfig;
+    using Offsets = VsyncModulator::OffsetsConfig;
 
     virtual ~PhaseConfiguration();
 
diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h
index 6a5082a..4e7a9a1 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.h
+++ b/services/surfaceflinger/Scheduler/Scheduler.h
@@ -60,28 +60,13 @@
     ~ISchedulerCallback() = default;
 };
 
-struct IPhaseOffsetControl {
-    virtual void setPhaseOffset(scheduler::ConnectionHandle, nsecs_t phaseOffset) = 0;
-
-protected:
-    ~IPhaseOffsetControl() = default;
-};
-
-class Scheduler : public IPhaseOffsetControl {
+class Scheduler {
 public:
     using RefreshRate = scheduler::RefreshRateConfigs::RefreshRate;
     using ConfigEvent = scheduler::RefreshRateConfigEvent;
 
-    // Indicates whether to start the transaction early, or at vsync time.
-    enum class TransactionStart {
-        Early,      // DEPRECATED. Start the transaction early. Times out on its own
-        EarlyStart, // Start the transaction early and keep this config until EarlyEnd
-        EarlyEnd,   // End the early config started at EarlyStart
-        Normal      // Start the transaction at the normal time
-    };
-
     Scheduler(const scheduler::RefreshRateConfigs&, ISchedulerCallback&);
-    virtual ~Scheduler();
+    ~Scheduler();
 
     DispSync& getPrimaryDispSync();
 
@@ -104,7 +89,7 @@
     void onScreenReleased(ConnectionHandle);
 
     // Modifies phase offset in the event thread.
-    void setPhaseOffset(ConnectionHandle, nsecs_t phaseOffset) override;
+    void setPhaseOffset(ConnectionHandle, nsecs_t phaseOffset);
 
     void getDisplayStatInfo(DisplayStatInfo* stats);
 
diff --git a/services/surfaceflinger/Scheduler/VsyncModulator.cpp b/services/surfaceflinger/Scheduler/VsyncModulator.cpp
index 96d47ad..7a1b7e4 100644
--- a/services/surfaceflinger/Scheduler/VsyncModulator.cpp
+++ b/services/surfaceflinger/Scheduler/VsyncModulator.cpp
@@ -14,55 +14,51 @@
  * limitations under the License.
  */
 
-// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wconversion"
-
 #define ATRACE_TAG ATRACE_TAG_GRAPHICS
 
+#undef LOG_TAG
+#define LOG_TAG "VsyncModulator"
+
 #include "VsyncModulator.h"
 
-#include <cutils/properties.h>
+#include <android-base/properties.h>
+#include <log/log.h>
 #include <utils/Trace.h>
 
 #include <chrono>
 #include <cinttypes>
 #include <mutex>
 
+using namespace std::chrono_literals;
+
 namespace android::scheduler {
 
-VSyncModulator::VSyncModulator(IPhaseOffsetControl& phaseOffsetControl,
-                               Scheduler::ConnectionHandle appConnectionHandle,
-                               Scheduler::ConnectionHandle sfConnectionHandle,
-                               const OffsetsConfig& config)
-      : mPhaseOffsetControl(phaseOffsetControl),
-        mAppConnectionHandle(appConnectionHandle),
-        mSfConnectionHandle(sfConnectionHandle),
-        mOffsetsConfig(config) {
-    char value[PROPERTY_VALUE_MAX];
-    property_get("debug.sf.vsync_trace_detailed_info", value, "0");
-    mTraceDetailedInfo = atoi(value);
-}
+const std::chrono::nanoseconds VsyncModulator::MIN_EARLY_TRANSACTION_TIME = 1ms;
 
-void VSyncModulator::setPhaseOffsets(const OffsetsConfig& config) {
+VsyncModulator::VsyncModulator(const OffsetsConfig& config, Now now)
+      : mOffsetsConfig(config),
+        mNow(now),
+        mTraceDetailedInfo(base::GetBoolProperty("debug.sf.vsync_trace_detailed_info", false)) {}
+
+VsyncModulator::Offsets VsyncModulator::setPhaseOffsets(const OffsetsConfig& config) {
     std::lock_guard<std::mutex> lock(mMutex);
     mOffsetsConfig = config;
-    updateOffsetsLocked();
+    return updateOffsetsLocked();
 }
 
-void VSyncModulator::setTransactionStart(Scheduler::TransactionStart transactionStart) {
-    switch (transactionStart) {
-        case Scheduler::TransactionStart::EarlyStart:
-            ALOGW_IF(mExplicitEarlyWakeup, "Already in TransactionStart::EarlyStart");
+VsyncModulator::OffsetsOpt VsyncModulator::setTransactionSchedule(TransactionSchedule schedule) {
+    switch (schedule) {
+        case Schedule::EarlyStart:
+            ALOGW_IF(mExplicitEarlyWakeup, "%s: Duplicate EarlyStart", __FUNCTION__);
             mExplicitEarlyWakeup = true;
             break;
-        case Scheduler::TransactionStart::EarlyEnd:
-            ALOGW_IF(!mExplicitEarlyWakeup, "Not in TransactionStart::EarlyStart");
+        case Schedule::EarlyEnd:
+            ALOGW_IF(!mExplicitEarlyWakeup, "%s: Unexpected EarlyEnd", __FUNCTION__);
             mExplicitEarlyWakeup = false;
             break;
-        case Scheduler::TransactionStart::Normal:
-        case Scheduler::TransactionStart::Early:
-            // Non explicit don't change the explicit early wakeup state
+        case Schedule::Early:
+        case Schedule::Late:
+            // No change to mExplicitEarlyWakeup for non-explicit states.
             break;
     }
 
@@ -70,114 +66,98 @@
         ATRACE_INT("mExplicitEarlyWakeup", mExplicitEarlyWakeup);
     }
 
-    if (!mExplicitEarlyWakeup &&
-        (transactionStart == Scheduler::TransactionStart::Early ||
-         transactionStart == Scheduler::TransactionStart::EarlyEnd)) {
-        mRemainingEarlyFrameCount = MIN_EARLY_FRAME_COUNT_TRANSACTION;
-        mEarlyTxnStartTime = std::chrono::steady_clock::now();
+    if (!mExplicitEarlyWakeup && (schedule == Schedule::Early || schedule == Schedule::EarlyEnd)) {
+        mEarlyTransactionFrames = MIN_EARLY_TRANSACTION_FRAMES;
+        mEarlyTransactionStartTime = mNow();
     }
 
     // An early transaction stays an early transaction.
-    if (transactionStart == mTransactionStart ||
-        mTransactionStart == Scheduler::TransactionStart::EarlyEnd) {
-        return;
+    if (schedule == mTransactionSchedule || mTransactionSchedule == Schedule::EarlyEnd) {
+        return std::nullopt;
     }
-    mTransactionStart = transactionStart;
-    updateOffsets();
+    mTransactionSchedule = schedule;
+    return updateOffsets();
 }
 
-void VSyncModulator::onTransactionHandled() {
-    mTxnAppliedTime = std::chrono::steady_clock::now();
-    if (mTransactionStart == Scheduler::TransactionStart::Normal) return;
-    mTransactionStart = Scheduler::TransactionStart::Normal;
-    updateOffsets();
+VsyncModulator::OffsetsOpt VsyncModulator::onTransactionCommit() {
+    mLastTransactionCommitTime = mNow();
+    if (mTransactionSchedule == Schedule::Late) return std::nullopt;
+    mTransactionSchedule = Schedule::Late;
+    return updateOffsets();
 }
 
-void VSyncModulator::onRefreshRateChangeInitiated() {
-    if (mRefreshRateChangePending) {
-        return;
-    }
+VsyncModulator::OffsetsOpt VsyncModulator::onRefreshRateChangeInitiated() {
+    if (mRefreshRateChangePending) return std::nullopt;
     mRefreshRateChangePending = true;
-    updateOffsets();
+    return updateOffsets();
 }
 
-void VSyncModulator::onRefreshRateChangeCompleted() {
-    if (!mRefreshRateChangePending) {
-        return;
-    }
+VsyncModulator::OffsetsOpt VsyncModulator::onRefreshRateChangeCompleted() {
+    if (!mRefreshRateChangePending) return std::nullopt;
     mRefreshRateChangePending = false;
-    updateOffsets();
+    return updateOffsets();
 }
 
-void VSyncModulator::onRefreshed(bool usedRenderEngine) {
+VsyncModulator::OffsetsOpt VsyncModulator::onDisplayRefresh(bool usedGpuComposition) {
     bool updateOffsetsNeeded = false;
 
-    // Apply a margin to account for potential data races
-    // This might make us stay in early offsets for one
-    // additional frame but it's better to be conservative here.
-    if ((mEarlyTxnStartTime.load() + MARGIN_FOR_TX_APPLY) < mTxnAppliedTime.load()) {
-        if (mRemainingEarlyFrameCount > 0) {
-            mRemainingEarlyFrameCount--;
+    if (mEarlyTransactionStartTime.load() + MIN_EARLY_TRANSACTION_TIME <=
+        mLastTransactionCommitTime.load()) {
+        if (mEarlyTransactionFrames > 0) {
+            mEarlyTransactionFrames--;
             updateOffsetsNeeded = true;
         }
     }
-    if (usedRenderEngine) {
-        mRemainingRenderEngineUsageCount = MIN_EARLY_GL_FRAME_COUNT_TRANSACTION;
+    if (usedGpuComposition) {
+        mEarlyGpuFrames = MIN_EARLY_GPU_FRAMES;
         updateOffsetsNeeded = true;
-    } else if (mRemainingRenderEngineUsageCount > 0) {
-        mRemainingRenderEngineUsageCount--;
+    } else if (mEarlyGpuFrames > 0) {
+        mEarlyGpuFrames--;
         updateOffsetsNeeded = true;
     }
-    if (updateOffsetsNeeded) {
-        updateOffsets();
-    }
+
+    if (!updateOffsetsNeeded) return std::nullopt;
+    return updateOffsets();
 }
 
-VSyncModulator::Offsets VSyncModulator::getOffsets() const {
+VsyncModulator::Offsets VsyncModulator::getOffsets() const {
     std::lock_guard<std::mutex> lock(mMutex);
     return mOffsets;
 }
 
-const VSyncModulator::Offsets& VSyncModulator::getNextOffsets() const {
+const VsyncModulator::Offsets& VsyncModulator::getNextOffsets() const {
     // Early offsets are used if we're in the middle of a refresh rate
     // change, or if we recently begin a transaction.
-    if (mExplicitEarlyWakeup || mTransactionStart == Scheduler::TransactionStart::EarlyEnd ||
-        mRemainingEarlyFrameCount > 0 || mRefreshRateChangePending) {
+    if (mExplicitEarlyWakeup || mTransactionSchedule == Schedule::EarlyEnd ||
+        mEarlyTransactionFrames > 0 || mRefreshRateChangePending) {
         return mOffsetsConfig.early;
-    } else if (mRemainingRenderEngineUsageCount > 0) {
-        return mOffsetsConfig.earlyGl;
+    } else if (mEarlyGpuFrames > 0) {
+        return mOffsetsConfig.earlyGpu;
     } else {
         return mOffsetsConfig.late;
     }
 }
 
-void VSyncModulator::updateOffsets() {
+VsyncModulator::Offsets VsyncModulator::updateOffsets() {
     std::lock_guard<std::mutex> lock(mMutex);
-    updateOffsetsLocked();
+    return updateOffsetsLocked();
 }
 
-void VSyncModulator::updateOffsetsLocked() {
+VsyncModulator::Offsets VsyncModulator::updateOffsetsLocked() {
     const Offsets& offsets = getNextOffsets();
-
-    mPhaseOffsetControl.setPhaseOffset(mSfConnectionHandle, offsets.sf);
-    mPhaseOffsetControl.setPhaseOffset(mAppConnectionHandle, offsets.app);
-
     mOffsets = offsets;
 
-    if (!mTraceDetailedInfo) {
-        return;
+    if (mTraceDetailedInfo) {
+        const bool isEarly = &offsets == &mOffsetsConfig.early;
+        const bool isEarlyGpu = &offsets == &mOffsetsConfig.earlyGpu;
+        const bool isLate = &offsets == &mOffsetsConfig.late;
+
+        ATRACE_INT("Vsync-EarlyOffsetsOn", isEarly);
+        ATRACE_INT("Vsync-EarlyGpuOffsetsOn", isEarlyGpu);
+        ATRACE_INT("Vsync-LateOffsetsOn", isLate);
     }
 
-    const bool isEarly = &offsets == &mOffsetsConfig.early;
-    const bool isEarlyGl = &offsets == &mOffsetsConfig.earlyGl;
-    const bool isLate = &offsets == &mOffsetsConfig.late;
-
-    ATRACE_INT("Vsync-EarlyOffsetsOn", isEarly);
-    ATRACE_INT("Vsync-EarlyGLOffsetsOn", isEarlyGl);
-    ATRACE_INT("Vsync-LateOffsetsOn", isLate);
+    return offsets;
 }
 
 } // namespace android::scheduler
-
-// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic pop // ignored "-Wconversion"
diff --git a/services/surfaceflinger/Scheduler/VsyncModulator.h b/services/surfaceflinger/Scheduler/VsyncModulator.h
index ab678c9..f920bd2 100644
--- a/services/surfaceflinger/Scheduler/VsyncModulator.h
+++ b/services/surfaceflinger/Scheduler/VsyncModulator.h
@@ -18,108 +18,106 @@
 
 #include <chrono>
 #include <mutex>
+#include <optional>
 
-#include "Scheduler.h"
+#include <android-base/thread_annotations.h>
+#include <utils/Timers.h>
 
 namespace android::scheduler {
 
-/*
- * Modulates the vsync-offsets depending on current SurfaceFlinger state.
- */
-class VSyncModulator {
-private:
-    // Number of frames we'll keep the early phase offsets once they are activated for a
-    // transaction. This acts as a low-pass filter in case the client isn't quick enough in
-    // sending new transactions.
-    static constexpr int MIN_EARLY_FRAME_COUNT_TRANSACTION = 2;
+// State machine controlled by transaction flags. VsyncModulator switches to early phase offsets
+// when a transaction is flagged EarlyStart or Early, lasting until an EarlyEnd transaction or a
+// fixed number of frames, respectively.
+enum class TransactionSchedule {
+    Late,  // Default.
+    Early, // Deprecated.
+    EarlyStart,
+    EarlyEnd
+};
 
-    // Number of frames we'll keep the early gl phase offsets once they are activated.
-    // This acts as a low-pass filter to avoid scenarios where we rapidly
-    // switch in and out of gl composition.
-    static constexpr int MIN_EARLY_GL_FRAME_COUNT_TRANSACTION = 2;
-
-    // Margin used to account for potential data races
-    static const constexpr std::chrono::nanoseconds MARGIN_FOR_TX_APPLY = 1ms;
-
+// Modulates VSYNC phase depending on transaction schedule and refresh rate changes.
+class VsyncModulator {
 public:
-    // Wrapper for a collection of surfaceflinger/app offsets for a particular
-    // configuration.
+    // Number of frames to keep early offsets after an early transaction or GPU composition.
+    // This acts as a low-pass filter in case subsequent transactions are delayed, or if the
+    // composition strategy alternates on subsequent frames.
+    static constexpr int MIN_EARLY_TRANSACTION_FRAMES = 2;
+    static constexpr int MIN_EARLY_GPU_FRAMES = 2;
+
+    // Duration to delay the MIN_EARLY_TRANSACTION_FRAMES countdown after an early transaction.
+    // This may keep early offsets for an extra frame, but avoids a race with transaction commit.
+    static const std::chrono::nanoseconds MIN_EARLY_TRANSACTION_TIME;
+
+    // Phase offsets for SF and app deadlines from VSYNC.
     struct Offsets {
         nsecs_t sf;
         nsecs_t app;
 
         bool operator==(const Offsets& other) const { return sf == other.sf && app == other.app; }
-
         bool operator!=(const Offsets& other) const { return !(*this == other); }
     };
 
+    using OffsetsOpt = std::optional<Offsets>;
+
     struct OffsetsConfig {
-        Offsets early;   // For transactions with the eEarlyWakeup flag.
-        Offsets earlyGl; // As above but while compositing with GL.
-        Offsets late;    // Default.
+        Offsets early;    // Used for early transactions, and during refresh rate change.
+        Offsets earlyGpu; // Used during GPU composition.
+        Offsets late;     // Default.
 
         bool operator==(const OffsetsConfig& other) const {
-            return early == other.early && earlyGl == other.earlyGl && late == other.late;
+            return early == other.early && earlyGpu == other.earlyGpu && late == other.late;
         }
 
         bool operator!=(const OffsetsConfig& other) const { return !(*this == other); }
     };
 
-    VSyncModulator(IPhaseOffsetControl&, ConnectionHandle appConnectionHandle,
-                   ConnectionHandle sfConnectionHandle, const OffsetsConfig&);
+    using Clock = std::chrono::steady_clock;
+    using TimePoint = Clock::time_point;
+    using Now = TimePoint (*)();
 
-    void setPhaseOffsets(const OffsetsConfig&) EXCLUDES(mMutex);
+    explicit VsyncModulator(const OffsetsConfig&, Now = Clock::now);
 
-    // Signals that a transaction has started, and changes offsets accordingly.
-    void setTransactionStart(Scheduler::TransactionStart transactionStart);
+    Offsets getOffsets() const EXCLUDES(mMutex);
 
-    // Signals that a transaction has been completed, so that we can finish
-    // special handling for a transaction.
-    void onTransactionHandled();
+    [[nodiscard]] Offsets setPhaseOffsets(const OffsetsConfig&) EXCLUDES(mMutex);
+
+    // Changes offsets in response to transaction flags or commit.
+    [[nodiscard]] OffsetsOpt setTransactionSchedule(TransactionSchedule);
+    [[nodiscard]] OffsetsOpt onTransactionCommit();
 
     // Called when we send a refresh rate change to hardware composer, so that
     // we can move into early offsets.
-    void onRefreshRateChangeInitiated();
+    [[nodiscard]] OffsetsOpt onRefreshRateChangeInitiated();
 
-    // Called when we detect from vsync signals that the refresh rate changed.
+    // Called when we detect from VSYNC signals that the refresh rate changed.
     // This way we can move out of early offsets if no longer necessary.
-    void onRefreshRateChangeCompleted();
+    [[nodiscard]] OffsetsOpt onRefreshRateChangeCompleted();
 
-    // Called when the display is presenting a new frame. usedRenderEngine
-    // should be set to true if RenderEngine was involved with composing the new
-    // frame.
-    void onRefreshed(bool usedRenderEngine);
-
-    // Returns the offsets that we are currently using
-    Offsets getOffsets() const EXCLUDES(mMutex);
+    [[nodiscard]] OffsetsOpt onDisplayRefresh(bool usedGpuComposition);
 
 private:
-    friend class VSyncModulatorTest;
-    // Returns the next offsets that we should be using
     const Offsets& getNextOffsets() const REQUIRES(mMutex);
-    // Updates offsets and persists them into the scheduler framework.
-    void updateOffsets() EXCLUDES(mMutex);
-    void updateOffsetsLocked() REQUIRES(mMutex);
-
-    IPhaseOffsetControl& mPhaseOffsetControl;
-    const ConnectionHandle mAppConnectionHandle;
-    const ConnectionHandle mSfConnectionHandle;
+    [[nodiscard]] Offsets updateOffsets() EXCLUDES(mMutex);
+    [[nodiscard]] Offsets updateOffsetsLocked() REQUIRES(mMutex);
 
     mutable std::mutex mMutex;
     OffsetsConfig mOffsetsConfig GUARDED_BY(mMutex);
 
     Offsets mOffsets GUARDED_BY(mMutex){mOffsetsConfig.late};
 
-    std::atomic<Scheduler::TransactionStart> mTransactionStart =
-            Scheduler::TransactionStart::Normal;
-    std::atomic<bool> mRefreshRateChangePending = false;
+    using Schedule = TransactionSchedule;
+    std::atomic<Schedule> mTransactionSchedule = Schedule::Late;
     std::atomic<bool> mExplicitEarlyWakeup = false;
-    std::atomic<int> mRemainingEarlyFrameCount = 0;
-    std::atomic<int> mRemainingRenderEngineUsageCount = 0;
-    std::atomic<std::chrono::steady_clock::time_point> mEarlyTxnStartTime = {};
-    std::atomic<std::chrono::steady_clock::time_point> mTxnAppliedTime = {};
 
-    bool mTraceDetailedInfo = false;
+    std::atomic<bool> mRefreshRateChangePending = false;
+
+    std::atomic<int> mEarlyTransactionFrames = 0;
+    std::atomic<int> mEarlyGpuFrames = 0;
+    std::atomic<TimePoint> mEarlyTransactionStartTime = TimePoint();
+    std::atomic<TimePoint> mLastTransactionCommitTime = TimePoint();
+
+    const Now mNow;
+    const bool mTraceDetailedInfo;
 };
 
 } // namespace android::scheduler
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index 4e0d375..d3cf44b 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -1036,10 +1036,9 @@
         mScheduler->resyncToHardwareVsync(true, refreshRate.getVsyncPeriod());
         // As we called to set period, we will call to onRefreshRateChangeCompleted once
         // DispSync model is locked.
-        mVSyncModulator->onRefreshRateChangeInitiated();
+        modulateVsync(&VsyncModulator::onRefreshRateChangeInitiated);
 
-        mPhaseConfiguration->setRefreshRateFps(refreshRate.getFps());
-        mVSyncModulator->setPhaseOffsets(mPhaseConfiguration->getCurrentOffsets());
+        updatePhaseConfiguration(refreshRate);
         mScheduler->setConfigChangePending(true);
     }
 
@@ -1098,8 +1097,7 @@
     if (refreshRate.getVsyncPeriod() != oldRefreshRate.getVsyncPeriod()) {
         mTimeStats->incrementRefreshRateSwitches();
     }
-    mPhaseConfiguration->setRefreshRateFps(refreshRate.getFps());
-    mVSyncModulator->setPhaseOffsets(mPhaseConfiguration->getCurrentOffsets());
+    updatePhaseConfiguration(refreshRate);
     ATRACE_INT("ActiveConfigFPS", refreshRate.getFps());
 
     if (mUpcomingActiveConfig.event != Scheduler::ConfigEvent::None) {
@@ -1119,9 +1117,9 @@
 
     const auto& refreshRate =
             mRefreshRateConfigs->getRefreshRateFromConfigId(mDesiredActiveConfig.configId);
+
     mScheduler->resyncToHardwareVsync(true, refreshRate.getVsyncPeriod());
-    mPhaseConfiguration->setRefreshRateFps(refreshRate.getFps());
-    mVSyncModulator->setPhaseOffsets(mPhaseConfiguration->getCurrentOffsets());
+    updatePhaseConfiguration(refreshRate);
     mScheduler->setConfigChangePending(false);
 }
 
@@ -1600,7 +1598,7 @@
     bool periodFlushed = false;
     mScheduler->addResyncSample(timestamp, vsyncPeriod, &periodFlushed);
     if (periodFlushed) {
-        mVSyncModulator->onRefreshRateChangeCompleted();
+        modulateVsync(&VsyncModulator::onRefreshRateChangeCompleted);
     }
 }
 
@@ -1790,7 +1788,7 @@
     // We are storing the last 2 present fences. If sf's phase offset is to be
     // woken up before the actual vsync but targeting the next vsync, we need to check
     // fence N-2
-    return mVSyncModulator->getOffsets().sf > 0 ? mPreviousPresentFences[0]
+    return mVsyncModulator->getOffsets().sf > 0 ? mPreviousPresentFences[0]
                                                 : mPreviousPresentFences[1];
 }
 
@@ -1823,7 +1821,7 @@
     mScheduler->getDisplayStatInfo(&stats);
     const nsecs_t presentTime = mScheduler->getDispSyncExpectedPresentTime(now);
     // Inflate the expected present time if we're targetting the next vsync.
-    return mVSyncModulator->getOffsets().sf > 0 ? presentTime : presentTime + stats.vsyncPeriod;
+    return mVsyncModulator->getOffsets().sf > 0 ? presentTime : presentTime + stats.vsyncPeriod;
 }
 
 void SurfaceFlinger::onMessageReceived(int32_t what, nsecs_t expectedVSyncTime) {
@@ -2120,7 +2118,8 @@
     }
 
     // TODO: b/160583065 Enable skip validation when SF caches all client composition layers
-    mVSyncModulator->onRefreshed(mHadClientComposition || mReusedClientComposition);
+    const bool usedGpuComposition = mHadClientComposition || mReusedClientComposition;
+    modulateVsync(&VsyncModulator::onDisplayRefresh, usedGpuComposition);
 
     mLayersWithQueuedFrames.clear();
     if (mVisibleRegionsDirty) {
@@ -2416,7 +2415,7 @@
     // with mStateLock held to guarantee that mCurrentState won't change
     // until the transaction is committed.
 
-    mVSyncModulator->onTransactionHandled();
+    modulateVsync(&VsyncModulator::onTransactionCommit);
     transactionFlags = getTransactionFlags(eTransactionMask);
     handleTransactionLocked(transactionFlags);
 
@@ -2977,6 +2976,7 @@
     mRefreshRateStats->setConfigMode(currentConfig);
 
     mPhaseConfiguration = getFactory().createPhaseConfiguration(*mRefreshRateConfigs);
+    mVsyncModulator.emplace(mPhaseConfiguration->getCurrentOffsets());
 
     // start the EventThread
     mScheduler = getFactory().createScheduler(*mRefreshRateConfigs, *this);
@@ -2990,8 +2990,6 @@
                                          });
 
     mEventQueue->setEventConnection(mScheduler->getEventConnection(mSfConnectionHandle));
-    mVSyncModulator.emplace(*mScheduler, mAppConnectionHandle, mSfConnectionHandle,
-                            mPhaseConfiguration->getCurrentOffsets());
 
     mRegionSamplingThread =
             new RegionSamplingThread(*this, *mScheduler,
@@ -3009,8 +3007,17 @@
                                               vsyncPeriod);
 }
 
-void SurfaceFlinger::commitTransaction()
-{
+void SurfaceFlinger::updatePhaseConfiguration(const RefreshRate& refreshRate) {
+    mPhaseConfiguration->setRefreshRateFps(refreshRate.getFps());
+    setPhaseOffsets(mVsyncModulator->setPhaseOffsets(mPhaseConfiguration->getCurrentOffsets()));
+}
+
+void SurfaceFlinger::setPhaseOffsets(const VsyncModulator::Offsets& offsets) {
+    mScheduler->setPhaseOffset(mAppConnectionHandle, offsets.app);
+    mScheduler->setPhaseOffset(mSfConnectionHandle, offsets.sf);
+}
+
+void SurfaceFlinger::commitTransaction() {
     commitTransactionLocked();
     mTransactionPending = false;
     mAnimTransactionPending = false;
@@ -3250,16 +3257,13 @@
 }
 
 uint32_t SurfaceFlinger::setTransactionFlags(uint32_t flags) {
-    return setTransactionFlags(flags, Scheduler::TransactionStart::Normal);
+    return setTransactionFlags(flags, TransactionSchedule::Late);
 }
 
-uint32_t SurfaceFlinger::setTransactionFlags(uint32_t flags,
-                                             Scheduler::TransactionStart transactionStart) {
+uint32_t SurfaceFlinger::setTransactionFlags(uint32_t flags, TransactionSchedule schedule) {
     uint32_t old = mTransactionFlags.fetch_or(flags);
-    mVSyncModulator->setTransactionStart(transactionStart);
-    if ((old & flags)==0) { // wake the server up
-        signalTransaction();
-    }
+    modulateVsync(&VsyncModulator::setTransactionSchedule, schedule);
+    if ((old & flags) == 0) signalTransaction();
     return old;
 }
 
@@ -3467,17 +3471,11 @@
         mForceTraversal = true;
     }
 
-    const auto transactionStart = [](uint32_t flags) {
-        if (flags & eEarlyWakeup) {
-            return Scheduler::TransactionStart::Early;
-        }
-        if (flags & eExplicitEarlyWakeupEnd) {
-            return Scheduler::TransactionStart::EarlyEnd;
-        }
-        if (flags & eExplicitEarlyWakeupStart) {
-            return Scheduler::TransactionStart::EarlyStart;
-        }
-        return Scheduler::TransactionStart::Normal;
+    const auto schedule = [](uint32_t flags) {
+        if (flags & eEarlyWakeup) return TransactionSchedule::Early;
+        if (flags & eExplicitEarlyWakeupEnd) return TransactionSchedule::EarlyEnd;
+        if (flags & eExplicitEarlyWakeupStart) return TransactionSchedule::EarlyStart;
+        return TransactionSchedule::Late;
     }(flags);
 
     if (transactionFlags) {
@@ -3496,7 +3494,7 @@
         }
 
         // this triggers the transaction
-        setTransactionFlags(transactionFlags, transactionStart);
+        setTransactionFlags(transactionFlags, schedule);
 
         if (flags & eAnimation) {
             mAnimTransactionPending = true;
@@ -3534,11 +3532,10 @@
             }
         }
     } else {
-        // even if a transaction is not needed, we need to update VsyncModulator
-        // about explicit early indications
-        if (transactionStart == Scheduler::TransactionStart::EarlyStart ||
-            transactionStart == Scheduler::TransactionStart::EarlyEnd) {
-            mVSyncModulator->setTransactionStart(transactionStart);
+        // Update VsyncModulator state machine even if transaction is not needed.
+        if (schedule == TransactionSchedule::EarlyStart ||
+            schedule == TransactionSchedule::EarlyEnd) {
+            modulateVsync(&VsyncModulator::setTransactionSchedule, schedule);
         }
     }
 }
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index 1261eec..7ce1ca5 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -549,8 +549,6 @@
     void signalLayerUpdate();
     void signalRefresh();
 
-    using RefreshRate = scheduler::RefreshRateConfigs::RefreshRate;
-
     struct ActiveConfigInfo {
         HwcConfigIndexType configId;
         Scheduler::ConfigEvent event = Scheduler::ConfigEvent::None;
@@ -604,7 +602,13 @@
     void updateInputWindowInfo();
     void commitInputWindowCommands() REQUIRES(mStateLock);
     void updateCursorAsync();
+
+    using RefreshRate = scheduler::RefreshRateConfigs::RefreshRate;
+    using VsyncModulator = scheduler::VsyncModulator;
+
     void initScheduler(PhysicalDisplayId primaryDisplayId);
+    void updatePhaseConfiguration(const RefreshRate&);
+    void setPhaseOffsets(const VsyncModulator::Offsets&);
 
     /* handlePageFlip - latch a new buffer if available and compute the dirty
      * region. Returns whether a new buffer has been latched, i.e., whether it
@@ -615,6 +619,8 @@
     /* ------------------------------------------------------------------------
      * Transactions
      */
+    using TransactionSchedule = scheduler::TransactionSchedule;
+
     void applyTransactionState(const Vector<ComposerState>& state,
                                const Vector<DisplayState>& displays, uint32_t flags,
                                const InputWindowCommands& inputWindowCommands,
@@ -637,7 +643,7 @@
     // but there is no need to try and wake up immediately to do it. Rather we rely on
     // onFrameAvailable or another layer update to wake us up.
     void setTraversalNeeded();
-    uint32_t setTransactionFlags(uint32_t flags, Scheduler::TransactionStart transactionStart);
+    uint32_t setTransactionFlags(uint32_t flags, TransactionSchedule);
     void commitTransaction() REQUIRES(mStateLock);
     void commitOffscreenLayers();
     bool transactionIsReadyToBeApplied(int64_t desiredPresentTime,
@@ -843,14 +849,13 @@
     void dispatchDisplayHotplugEvent(PhysicalDisplayId displayId, bool connected);
 
     /* ------------------------------------------------------------------------
-     * VSync
+     * VSYNC
      */
     nsecs_t getVsyncPeriodFromHWC() const REQUIRES(mStateLock);
 
     // Sets the refresh rate by switching active configs, if they are available for
     // the desired refresh rate.
-    void changeRefreshRateLocked(const RefreshRate&, Scheduler::ConfigEvent event)
-            REQUIRES(mStateLock);
+    void changeRefreshRateLocked(const RefreshRate&, Scheduler::ConfigEvent) REQUIRES(mStateLock);
 
     bool isDisplayConfigAllowed(HwcConfigIndexType configId) const REQUIRES(mStateLock);
 
@@ -1206,8 +1211,8 @@
     // Stores phase offsets configured per refresh rate.
     std::unique_ptr<scheduler::PhaseConfiguration> mPhaseConfiguration;
 
-    // Optional to defer construction until scheduler connections are created.
-    std::optional<scheduler::VSyncModulator> mVSyncModulator;
+    // Optional to defer construction until PhaseConfiguration is created.
+    std::optional<scheduler::VsyncModulator> mVsyncModulator;
 
     std::unique_ptr<scheduler::RefreshRateConfigs> mRefreshRateConfigs;
     std::unique_ptr<scheduler::RefreshRateStats> mRefreshRateStats;
@@ -1215,6 +1220,14 @@
     std::atomic<nsecs_t> mExpectedPresentTime = 0;
     hal::Vsync mHWCVsyncPendingState = hal::Vsync::DISABLE;
 
+    template <typename... Args,
+              typename Handler = VsyncModulator::OffsetsOpt (VsyncModulator::*)(Args...)>
+    void modulateVsync(Handler handler, Args... args) {
+        if (const auto offsets = (*mVsyncModulator.*handler)(args...)) {
+            setPhaseOffsets(*offsets);
+        }
+    }
+
     /* ------------------------------------------------------------------------
      * Generic Layer Metadata
      */
diff --git a/services/surfaceflinger/tests/unittests/PhaseOffsetsTest.cpp b/services/surfaceflinger/tests/unittests/PhaseOffsetsTest.cpp
index 0b74682..96bb74b 100644
--- a/services/surfaceflinger/tests/unittests/PhaseOffsetsTest.cpp
+++ b/services/surfaceflinger/tests/unittests/PhaseOffsetsTest.cpp
@@ -29,8 +29,7 @@
 
 using namespace testing;
 
-namespace android {
-namespace scheduler {
+namespace android::scheduler {
 
 class TestablePhaseOffsetsAsDurations : public impl::PhaseDurations {
 public:
@@ -53,7 +52,6 @@
     TestablePhaseOffsetsAsDurations mPhaseDurations;
 };
 
-namespace {
 /* ------------------------------------------------------------------------
  * Test cases
  */
@@ -71,9 +69,9 @@
 
     EXPECT_EQ(offsets.early.app, 833'334);
 
-    EXPECT_EQ(offsets.earlyGl.sf, 3'166'667);
+    EXPECT_EQ(offsets.earlyGpu.sf, 3'166'667);
 
-    EXPECT_EQ(offsets.earlyGl.app, 15'500'001);
+    EXPECT_EQ(offsets.earlyGpu.app, 15'500'001);
 }
 
 TEST_F(PhaseDurationTest, getOffsetsForRefreshRate_90Hz) {
@@ -90,9 +88,9 @@
 
     EXPECT_EQ(offsets.early.app, 833'333);
 
-    EXPECT_EQ(offsets.earlyGl.sf, -2'388'889);
+    EXPECT_EQ(offsets.earlyGpu.sf, -2'388'889);
 
-    EXPECT_EQ(offsets.earlyGl.app, 9'944'444);
+    EXPECT_EQ(offsets.earlyGpu.app, 9'944'444);
 }
 
 TEST_F(PhaseDurationTest, getOffsetsForRefreshRate_DefaultOffsets) {
@@ -107,9 +105,9 @@
 
         EXPECT_EQ(offsets.early.app, 1'000'000);
 
-        EXPECT_EQ(offsets.earlyGl.sf, 1'000'000);
+        EXPECT_EQ(offsets.earlyGpu.sf, 1'000'000);
 
-        EXPECT_EQ(offsets.earlyGl.app, 1'000'000);
+        EXPECT_EQ(offsets.earlyGpu.app, 1'000'000);
     };
 
     phaseOffsetsWithDefaultValues.setRefreshRateFps(90.0f);
@@ -136,31 +134,20 @@
 
     EXPECT_EQ(offsets.early.app, 35'527'208);
 
-    EXPECT_EQ(offsets.earlyGl.sf, 54'527'208);
+    EXPECT_EQ(offsets.earlyGpu.sf, 54'527'208);
 
-    EXPECT_EQ(offsets.earlyGl.app, 33'527'208);
+    EXPECT_EQ(offsets.earlyGpu.app, 33'527'208);
 }
 
-} // namespace
+TEST(PhaseOffsetsTest, getOffsetsForRefreshRate_unknownRefreshRate) {
+    struct PhaseOffsets : impl::PhaseOffsets {
+        using impl::PhaseOffsets::PhaseOffsets;
+        static PhaseOffsets get() {
+            return {{60.0f, 90.0f}, 60.0f, 1'000'000, 1'000'000, {}, {}, {}, {}, 10'000'000};
+        }
+    };
 
-class TestablePhaseOffsets : public impl::PhaseOffsets {
-public:
-    TestablePhaseOffsets()
-          : impl::PhaseOffsets({60.0f, 90.0f}, 60.0f, 1'000'000, 1'000'000, {}, {}, {}, {},
-                               10'000'000) {}
-};
-
-class PhaseOffsetsTest : public testing::Test {
-protected:
-    PhaseOffsetsTest() = default;
-    ~PhaseOffsetsTest() = default;
-
-    TestablePhaseOffsets mPhaseOffsets;
-};
-
-namespace {
-TEST_F(PhaseOffsetsTest, getOffsetsForRefreshRate_unknownRefreshRate) {
-    auto offsets = mPhaseOffsets.getOffsetsForRefreshRate(14.7f);
+    auto offsets = PhaseOffsets::get().getOffsetsForRefreshRate(14.7f);
 
     EXPECT_EQ(offsets.late.sf, 1'000'000);
 
@@ -170,14 +157,12 @@
 
     EXPECT_EQ(offsets.early.app, 1'000'000);
 
-    EXPECT_EQ(offsets.earlyGl.sf, 1'000'000);
+    EXPECT_EQ(offsets.earlyGpu.sf, 1'000'000);
 
-    EXPECT_EQ(offsets.earlyGl.app, 1'000'000);
+    EXPECT_EQ(offsets.earlyGpu.app, 1'000'000);
 }
 
-} // namespace
-} // namespace scheduler
-} // namespace android
+} // namespace android::scheduler
 
 // TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic pop // ignored "-Wconversion"
\ No newline at end of file
+#pragma clang diagnostic pop // ignored "-Wconversion"
diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
index 3941d42..ade16e6 100644
--- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
+++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
@@ -218,6 +218,7 @@
                                              /*powerMode=*/hal::PowerMode::OFF);
         mFlinger->mPhaseConfiguration =
                 mFactory.createPhaseConfiguration(*mFlinger->mRefreshRateConfigs);
+        mFlinger->mVsyncModulator.emplace(mFlinger->mPhaseConfiguration->getCurrentOffsets());
 
         constexpr bool kUseContentDetectionV2 = false;
         mScheduler =
@@ -227,10 +228,6 @@
         mFlinger->mAppConnectionHandle = mScheduler->createConnection(std::move(appEventThread));
         mFlinger->mSfConnectionHandle = mScheduler->createConnection(std::move(sfEventThread));
         resetScheduler(mScheduler);
-
-        mFlinger->mVSyncModulator.emplace(*mScheduler, mFlinger->mAppConnectionHandle,
-                                          mFlinger->mSfConnectionHandle,
-                                          mFlinger->mPhaseConfiguration->getCurrentOffsets());
     }
 
     void resetScheduler(Scheduler* scheduler) { mFlinger->mScheduler.reset(scheduler); }
diff --git a/services/surfaceflinger/tests/unittests/VsyncModulatorTest.cpp b/services/surfaceflinger/tests/unittests/VsyncModulatorTest.cpp
index edff16c..d7093c6 100644
--- a/services/surfaceflinger/tests/unittests/VsyncModulatorTest.cpp
+++ b/services/surfaceflinger/tests/unittests/VsyncModulatorTest.cpp
@@ -14,288 +14,157 @@
  * limitations under the License.
  */
 
-#undef LOG_TAG
-#define LOG_TAG "LibSurfaceFlingerUnittests"
-#define LOG_NDEBUG 0
-
-#include "Scheduler/VsyncModulator.h"
-
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
 
-using namespace testing;
+#include "Scheduler/VsyncModulator.h"
 
 namespace android::scheduler {
 
-class MockScheduler final : public IPhaseOffsetControl {
-public:
-    nsecs_t getOffset(ConnectionHandle handle) { return mPhaseOffset[handle]; }
-
-private:
-    void setPhaseOffset(ConnectionHandle handle, nsecs_t phaseOffset) override {
-        mPhaseOffset[handle] = phaseOffset;
-    }
-
-    std::unordered_map<ConnectionHandle, nsecs_t> mPhaseOffset;
-};
-
-class VSyncModulatorTest : public testing::Test {
-protected:
-    static constexpr auto MIN_EARLY_FRAME_COUNT_TRANSACTION =
-            VSyncModulator::MIN_EARLY_FRAME_COUNT_TRANSACTION;
-    // Add a 1ms slack to avoid strange timer race conditions.
-    static constexpr auto MARGIN_FOR_TX_APPLY = VSyncModulator::MARGIN_FOR_TX_APPLY + 1ms;
-
-    // Used to enumerate the different offsets we have
-    enum {
+class VsyncModulatorTest : public testing::Test {
+    enum Offsets {
         SF_LATE,
         APP_LATE,
         SF_EARLY,
         APP_EARLY,
-        SF_EARLY_GL,
-        APP_EARLY_GL,
+        SF_EARLY_GPU,
+        APP_EARLY_GPU,
     };
 
-    std::unique_ptr<VSyncModulator> mVSyncModulator;
-    MockScheduler mMockScheduler;
-    ConnectionHandle mAppConnection{1};
-    ConnectionHandle mSfConnection{2};
-    VSyncModulator::OffsetsConfig mOffsets = {{SF_EARLY, APP_EARLY},
-                                              {SF_EARLY_GL, APP_EARLY_GL},
-                                              {SF_LATE, APP_LATE}};
+    static VsyncModulator::TimePoint Now() {
+        static VsyncModulator::TimePoint now;
+        return now += VsyncModulator::MIN_EARLY_TRANSACTION_TIME;
+    }
 
-    void SetUp() override {
-        mVSyncModulator = std::make_unique<VSyncModulator>(mMockScheduler, mAppConnection,
-                                                           mSfConnection, mOffsets);
-        mVSyncModulator->setPhaseOffsets(mOffsets);
+protected:
+    static constexpr auto MIN_EARLY_TRANSACTION_FRAMES =
+            VsyncModulator::MIN_EARLY_TRANSACTION_FRAMES;
 
-        EXPECT_EQ(APP_LATE, mMockScheduler.getOffset(mAppConnection));
-        EXPECT_EQ(SF_LATE, mMockScheduler.getOffset(mSfConnection));
-    };
+    using Schedule = scheduler::TransactionSchedule;
 
-    void TearDown() override { mVSyncModulator.reset(); }
+    const VsyncModulator::Offsets kEarly{SF_EARLY, APP_EARLY};
+    const VsyncModulator::Offsets kEarlyGpu{SF_EARLY_GPU, APP_EARLY_GPU};
+    const VsyncModulator::Offsets kLate{SF_LATE, APP_LATE};
+
+    const VsyncModulator::OffsetsConfig mOffsets = {kEarly, kEarlyGpu, kLate};
+    VsyncModulator mVsyncModulator{mOffsets, Now};
+
+    void SetUp() override { EXPECT_EQ(kLate, mVsyncModulator.setPhaseOffsets(mOffsets)); }
 };
 
-TEST_F(VSyncModulatorTest, Normal) {
-    mVSyncModulator->setTransactionStart(Scheduler::TransactionStart::Normal);
-    std::this_thread::sleep_for(MARGIN_FOR_TX_APPLY);
-    mVSyncModulator->onTransactionHandled();
-    EXPECT_EQ(APP_LATE, mMockScheduler.getOffset(mAppConnection));
-    EXPECT_EQ(SF_LATE, mMockScheduler.getOffset(mSfConnection));
+#define CHECK_COMMIT(result, offsets)                         \
+    EXPECT_EQ(result, mVsyncModulator.onTransactionCommit()); \
+    EXPECT_EQ(offsets, mVsyncModulator.getOffsets());
 
-    for (int i = 0; i < MIN_EARLY_FRAME_COUNT_TRANSACTION; i++) {
-        mVSyncModulator->onRefreshed(false);
-        EXPECT_EQ(APP_LATE, mMockScheduler.getOffset(mAppConnection));
-        EXPECT_EQ(SF_LATE, mMockScheduler.getOffset(mSfConnection));
+#define CHECK_REFRESH(N, result, offsets)                           \
+    for (int i = 0; i < N; i++) {                                   \
+        EXPECT_EQ(result, mVsyncModulator.onDisplayRefresh(false)); \
+        EXPECT_EQ(offsets, mVsyncModulator.getOffsets());           \
     }
+
+TEST_F(VsyncModulatorTest, Late) {
+    EXPECT_FALSE(mVsyncModulator.setTransactionSchedule(Schedule::Late));
+
+    CHECK_COMMIT(std::nullopt, kLate);
+    CHECK_REFRESH(MIN_EARLY_TRANSACTION_FRAMES, std::nullopt, kLate);
 }
 
-TEST_F(VSyncModulatorTest, EarlyEnd) {
-    mVSyncModulator->setTransactionStart(Scheduler::TransactionStart::EarlyEnd);
-    std::this_thread::sleep_for(MARGIN_FOR_TX_APPLY);
-    mVSyncModulator->onTransactionHandled();
-    EXPECT_EQ(APP_EARLY, mMockScheduler.getOffset(mAppConnection));
-    EXPECT_EQ(SF_EARLY, mMockScheduler.getOffset(mSfConnection));
+TEST_F(VsyncModulatorTest, EarlyEnd) {
+    EXPECT_EQ(kEarly, mVsyncModulator.setTransactionSchedule(Schedule::EarlyEnd));
 
-    for (int i = 0; i < MIN_EARLY_FRAME_COUNT_TRANSACTION - 1; i++) {
-        mVSyncModulator->onRefreshed(false);
-        EXPECT_EQ(APP_EARLY, mMockScheduler.getOffset(mAppConnection));
-        EXPECT_EQ(SF_EARLY, mMockScheduler.getOffset(mSfConnection));
-    }
-
-    mVSyncModulator->onRefreshed(false);
-    EXPECT_EQ(APP_LATE, mMockScheduler.getOffset(mAppConnection));
-    EXPECT_EQ(SF_LATE, mMockScheduler.getOffset(mSfConnection));
+    CHECK_COMMIT(kEarly, kEarly);
+    CHECK_REFRESH(MIN_EARLY_TRANSACTION_FRAMES - 1, kEarly, kEarly);
+    CHECK_REFRESH(1, kLate, kLate);
 }
 
-TEST_F(VSyncModulatorTest, EarlyStart) {
-    mVSyncModulator->setTransactionStart(Scheduler::TransactionStart::EarlyStart);
-    std::this_thread::sleep_for(MARGIN_FOR_TX_APPLY);
-    mVSyncModulator->onTransactionHandled();
-    EXPECT_EQ(APP_EARLY, mMockScheduler.getOffset(mAppConnection));
-    EXPECT_EQ(SF_EARLY, mMockScheduler.getOffset(mSfConnection));
+TEST_F(VsyncModulatorTest, EarlyStart) {
+    EXPECT_EQ(kEarly, mVsyncModulator.setTransactionSchedule(Schedule::EarlyStart));
 
-    for (int i = 0; i < 5 * MIN_EARLY_FRAME_COUNT_TRANSACTION; i++) {
-        mVSyncModulator->onRefreshed(false);
-        EXPECT_EQ(APP_EARLY, mMockScheduler.getOffset(mAppConnection));
-        EXPECT_EQ(SF_EARLY, mMockScheduler.getOffset(mSfConnection));
-    }
+    CHECK_COMMIT(kEarly, kEarly);
+    CHECK_REFRESH(5 * MIN_EARLY_TRANSACTION_FRAMES, std::nullopt, kEarly);
 
-    mVSyncModulator->setTransactionStart(Scheduler::TransactionStart::EarlyEnd);
-    std::this_thread::sleep_for(MARGIN_FOR_TX_APPLY);
-    mVSyncModulator->onTransactionHandled();
-    EXPECT_EQ(APP_EARLY, mMockScheduler.getOffset(mAppConnection));
-    EXPECT_EQ(SF_EARLY, mMockScheduler.getOffset(mSfConnection));
+    EXPECT_EQ(kEarly, mVsyncModulator.setTransactionSchedule(Schedule::EarlyEnd));
 
-    for (int i = 0; i < MIN_EARLY_FRAME_COUNT_TRANSACTION - 1; i++) {
-        mVSyncModulator->onRefreshed(false);
-        EXPECT_EQ(APP_EARLY, mMockScheduler.getOffset(mAppConnection));
-        EXPECT_EQ(SF_EARLY, mMockScheduler.getOffset(mSfConnection));
-    }
-
-    mVSyncModulator->onRefreshed(false);
-    EXPECT_EQ(APP_LATE, mMockScheduler.getOffset(mAppConnection));
-    EXPECT_EQ(SF_LATE, mMockScheduler.getOffset(mSfConnection));
+    CHECK_COMMIT(kEarly, kEarly);
+    CHECK_REFRESH(MIN_EARLY_TRANSACTION_FRAMES - 1, kEarly, kEarly);
+    CHECK_REFRESH(1, kLate, kLate);
 }
 
-TEST_F(VSyncModulatorTest, EarlyStartWithEarly) {
-    mVSyncModulator->setTransactionStart(Scheduler::TransactionStart::EarlyStart);
-    std::this_thread::sleep_for(MARGIN_FOR_TX_APPLY);
-    mVSyncModulator->onTransactionHandled();
-    EXPECT_EQ(APP_EARLY, mMockScheduler.getOffset(mAppConnection));
-    EXPECT_EQ(SF_EARLY, mMockScheduler.getOffset(mSfConnection));
+TEST_F(VsyncModulatorTest, EarlyStartWithEarly) {
+    EXPECT_EQ(kEarly, mVsyncModulator.setTransactionSchedule(Schedule::EarlyStart));
 
-    for (int i = 0; i < 5 * MIN_EARLY_FRAME_COUNT_TRANSACTION; i++) {
-        mVSyncModulator->onRefreshed(false);
-        EXPECT_EQ(APP_EARLY, mMockScheduler.getOffset(mAppConnection));
-        EXPECT_EQ(SF_EARLY, mMockScheduler.getOffset(mSfConnection));
-    }
+    CHECK_COMMIT(kEarly, kEarly);
+    CHECK_REFRESH(5 * MIN_EARLY_TRANSACTION_FRAMES, std::nullopt, kEarly);
 
-    mVSyncModulator->setTransactionStart(Scheduler::TransactionStart::Early);
-    std::this_thread::sleep_for(MARGIN_FOR_TX_APPLY);
-    mVSyncModulator->onTransactionHandled();
-    EXPECT_EQ(APP_EARLY, mMockScheduler.getOffset(mAppConnection));
-    EXPECT_EQ(SF_EARLY, mMockScheduler.getOffset(mSfConnection));
+    EXPECT_EQ(kEarly, mVsyncModulator.setTransactionSchedule(Schedule::Early));
 
-    for (int i = 0; i < 5 * MIN_EARLY_FRAME_COUNT_TRANSACTION; i++) {
-        mVSyncModulator->onRefreshed(false);
-        EXPECT_EQ(APP_EARLY, mMockScheduler.getOffset(mAppConnection));
-        EXPECT_EQ(SF_EARLY, mMockScheduler.getOffset(mSfConnection));
-    }
+    CHECK_COMMIT(kEarly, kEarly);
+    CHECK_REFRESH(5 * MIN_EARLY_TRANSACTION_FRAMES, std::nullopt, kEarly);
 
-    mVSyncModulator->setTransactionStart(Scheduler::TransactionStart::EarlyEnd);
-    std::this_thread::sleep_for(MARGIN_FOR_TX_APPLY);
-    mVSyncModulator->onTransactionHandled();
-    EXPECT_EQ(APP_EARLY, mMockScheduler.getOffset(mAppConnection));
-    EXPECT_EQ(SF_EARLY, mMockScheduler.getOffset(mSfConnection));
+    EXPECT_EQ(kEarly, mVsyncModulator.setTransactionSchedule(Schedule::EarlyEnd));
 
-    for (int i = 0; i < MIN_EARLY_FRAME_COUNT_TRANSACTION - 1; i++) {
-        mVSyncModulator->onRefreshed(false);
-        EXPECT_EQ(APP_EARLY, mMockScheduler.getOffset(mAppConnection));
-        EXPECT_EQ(SF_EARLY, mMockScheduler.getOffset(mSfConnection));
-    }
-
-    mVSyncModulator->onRefreshed(false);
-    EXPECT_EQ(APP_LATE, mMockScheduler.getOffset(mAppConnection));
-    EXPECT_EQ(SF_LATE, mMockScheduler.getOffset(mSfConnection));
+    CHECK_COMMIT(kEarly, kEarly);
+    CHECK_REFRESH(MIN_EARLY_TRANSACTION_FRAMES - 1, kEarly, kEarly);
+    CHECK_REFRESH(1, kLate, kLate);
 }
 
-TEST_F(VSyncModulatorTest, EarlyStartWithMoreTransactions) {
-    mVSyncModulator->setTransactionStart(Scheduler::TransactionStart::EarlyStart);
-    std::this_thread::sleep_for(MARGIN_FOR_TX_APPLY);
-    mVSyncModulator->onTransactionHandled();
-    EXPECT_EQ(APP_EARLY, mMockScheduler.getOffset(mAppConnection));
-    EXPECT_EQ(SF_EARLY, mMockScheduler.getOffset(mSfConnection));
+TEST_F(VsyncModulatorTest, EarlyStartWithMoreTransactions) {
+    EXPECT_EQ(kEarly, mVsyncModulator.setTransactionSchedule(Schedule::EarlyStart));
 
-    for (int i = 0; i < 5 * MIN_EARLY_FRAME_COUNT_TRANSACTION; i++) {
-        mVSyncModulator->setTransactionStart(Scheduler::TransactionStart::Normal);
-        std::this_thread::sleep_for(MARGIN_FOR_TX_APPLY);
-        mVSyncModulator->onRefreshed(false);
-        EXPECT_EQ(APP_EARLY, mMockScheduler.getOffset(mAppConnection));
-        EXPECT_EQ(SF_EARLY, mMockScheduler.getOffset(mSfConnection));
+    CHECK_COMMIT(kEarly, kEarly);
+
+    for (int i = 0; i < 5 * MIN_EARLY_TRANSACTION_FRAMES; i++) {
+        EXPECT_FALSE(mVsyncModulator.setTransactionSchedule(Schedule::Late));
+        CHECK_REFRESH(1, std::nullopt, kEarly);
     }
 
-    mVSyncModulator->setTransactionStart(Scheduler::TransactionStart::EarlyEnd);
-    std::this_thread::sleep_for(MARGIN_FOR_TX_APPLY);
-    mVSyncModulator->onTransactionHandled();
-    EXPECT_EQ(APP_EARLY, mMockScheduler.getOffset(mAppConnection));
-    EXPECT_EQ(SF_EARLY, mMockScheduler.getOffset(mSfConnection));
+    EXPECT_EQ(kEarly, mVsyncModulator.setTransactionSchedule(Schedule::EarlyEnd));
 
-    for (int i = 0; i < MIN_EARLY_FRAME_COUNT_TRANSACTION - 1; i++) {
-        mVSyncModulator->onRefreshed(false);
-        EXPECT_EQ(APP_EARLY, mMockScheduler.getOffset(mAppConnection));
-        EXPECT_EQ(SF_EARLY, mMockScheduler.getOffset(mSfConnection));
-    }
-
-    mVSyncModulator->onRefreshed(false);
-    EXPECT_EQ(APP_LATE, mMockScheduler.getOffset(mAppConnection));
-    EXPECT_EQ(SF_LATE, mMockScheduler.getOffset(mSfConnection));
+    CHECK_COMMIT(kEarly, kEarly);
+    CHECK_REFRESH(MIN_EARLY_TRANSACTION_FRAMES - 1, kEarly, kEarly);
+    CHECK_REFRESH(1, kLate, kLate);
 }
 
-TEST_F(VSyncModulatorTest, EarlyStartAfterEarlyEnd) {
-    mVSyncModulator->setTransactionStart(Scheduler::TransactionStart::EarlyEnd);
-    std::this_thread::sleep_for(MARGIN_FOR_TX_APPLY);
-    mVSyncModulator->onTransactionHandled();
-    EXPECT_EQ(APP_EARLY, mMockScheduler.getOffset(mAppConnection));
-    EXPECT_EQ(SF_EARLY, mMockScheduler.getOffset(mSfConnection));
+TEST_F(VsyncModulatorTest, EarlyStartAfterEarlyEnd) {
+    EXPECT_EQ(kEarly, mVsyncModulator.setTransactionSchedule(Schedule::EarlyEnd));
 
-    for (int i = 0; i < MIN_EARLY_FRAME_COUNT_TRANSACTION - 1; i++) {
-        mVSyncModulator->onRefreshed(false);
-        EXPECT_EQ(APP_EARLY, mMockScheduler.getOffset(mAppConnection));
-        EXPECT_EQ(SF_EARLY, mMockScheduler.getOffset(mSfConnection));
-    }
+    CHECK_COMMIT(kEarly, kEarly);
+    CHECK_REFRESH(MIN_EARLY_TRANSACTION_FRAMES - 1, kEarly, kEarly);
 
-    mVSyncModulator->setTransactionStart(Scheduler::TransactionStart::EarlyStart);
-    std::this_thread::sleep_for(MARGIN_FOR_TX_APPLY);
-    mVSyncModulator->onTransactionHandled();
-    EXPECT_EQ(APP_EARLY, mMockScheduler.getOffset(mAppConnection));
-    EXPECT_EQ(SF_EARLY, mMockScheduler.getOffset(mSfConnection));
+    EXPECT_EQ(kEarly, mVsyncModulator.setTransactionSchedule(Schedule::EarlyStart));
 
-    for (int i = 0; i < 5 * MIN_EARLY_FRAME_COUNT_TRANSACTION; i++) {
-        mVSyncModulator->onRefreshed(false);
-        EXPECT_EQ(APP_EARLY, mMockScheduler.getOffset(mAppConnection));
-        EXPECT_EQ(SF_EARLY, mMockScheduler.getOffset(mSfConnection));
-    }
+    CHECK_COMMIT(kEarly, kEarly);
+    CHECK_REFRESH(1, kEarly, kEarly);
+    CHECK_REFRESH(5 * MIN_EARLY_TRANSACTION_FRAMES, std::nullopt, kEarly);
 
-    mVSyncModulator->setTransactionStart(Scheduler::TransactionStart::EarlyEnd);
-    std::this_thread::sleep_for(MARGIN_FOR_TX_APPLY);
-    mVSyncModulator->onTransactionHandled();
-    EXPECT_EQ(APP_EARLY, mMockScheduler.getOffset(mAppConnection));
-    EXPECT_EQ(SF_EARLY, mMockScheduler.getOffset(mSfConnection));
+    EXPECT_EQ(kEarly, mVsyncModulator.setTransactionSchedule(Schedule::EarlyEnd));
 
-    for (int i = 0; i < MIN_EARLY_FRAME_COUNT_TRANSACTION - 1; i++) {
-        mVSyncModulator->onRefreshed(false);
-        EXPECT_EQ(APP_EARLY, mMockScheduler.getOffset(mAppConnection));
-        EXPECT_EQ(SF_EARLY, mMockScheduler.getOffset(mSfConnection));
-    }
-
-    mVSyncModulator->onRefreshed(false);
-    EXPECT_EQ(APP_LATE, mMockScheduler.getOffset(mAppConnection));
-    EXPECT_EQ(SF_LATE, mMockScheduler.getOffset(mSfConnection));
+    CHECK_COMMIT(kEarly, kEarly);
+    CHECK_REFRESH(MIN_EARLY_TRANSACTION_FRAMES - 1, kEarly, kEarly);
+    CHECK_REFRESH(1, kLate, kLate);
 }
 
-TEST_F(VSyncModulatorTest, EarlyStartAfterEarlyEndWithMoreTransactions) {
-    mVSyncModulator->setTransactionStart(Scheduler::TransactionStart::EarlyEnd);
-    std::this_thread::sleep_for(MARGIN_FOR_TX_APPLY);
-    mVSyncModulator->onTransactionHandled();
-    EXPECT_EQ(APP_EARLY, mMockScheduler.getOffset(mAppConnection));
-    EXPECT_EQ(SF_EARLY, mMockScheduler.getOffset(mSfConnection));
+TEST_F(VsyncModulatorTest, EarlyStartAfterEarlyEndWithMoreTransactions) {
+    EXPECT_EQ(kEarly, mVsyncModulator.setTransactionSchedule(Schedule::EarlyEnd));
 
-    for (int i = 0; i < MIN_EARLY_FRAME_COUNT_TRANSACTION - 1; i++) {
-        mVSyncModulator->onRefreshed(false);
-        EXPECT_EQ(APP_EARLY, mMockScheduler.getOffset(mAppConnection));
-        EXPECT_EQ(SF_EARLY, mMockScheduler.getOffset(mSfConnection));
+    CHECK_COMMIT(kEarly, kEarly);
+    CHECK_REFRESH(MIN_EARLY_TRANSACTION_FRAMES - 1, kEarly, kEarly);
+
+    EXPECT_EQ(kEarly, mVsyncModulator.setTransactionSchedule(Schedule::EarlyStart));
+
+    CHECK_COMMIT(kEarly, kEarly);
+    CHECK_REFRESH(1, kEarly, kEarly);
+
+    for (int i = 0; i < 5 * MIN_EARLY_TRANSACTION_FRAMES; i++) {
+        EXPECT_FALSE(mVsyncModulator.setTransactionSchedule(Schedule::Late));
+        CHECK_REFRESH(1, std::nullopt, kEarly);
     }
 
-    mVSyncModulator->setTransactionStart(Scheduler::TransactionStart::EarlyStart);
-    std::this_thread::sleep_for(MARGIN_FOR_TX_APPLY);
-    mVSyncModulator->onTransactionHandled();
-    EXPECT_EQ(APP_EARLY, mMockScheduler.getOffset(mAppConnection));
-    EXPECT_EQ(SF_EARLY, mMockScheduler.getOffset(mSfConnection));
+    EXPECT_EQ(kEarly, mVsyncModulator.setTransactionSchedule(Schedule::EarlyEnd));
 
-    for (int i = 0; i < 5 * MIN_EARLY_FRAME_COUNT_TRANSACTION; i++) {
-        mVSyncModulator->setTransactionStart(Scheduler::TransactionStart::Normal);
-        std::this_thread::sleep_for(MARGIN_FOR_TX_APPLY);
-        mVSyncModulator->onRefreshed(false);
-        EXPECT_EQ(APP_EARLY, mMockScheduler.getOffset(mAppConnection));
-        EXPECT_EQ(SF_EARLY, mMockScheduler.getOffset(mSfConnection));
-    }
-
-    mVSyncModulator->setTransactionStart(Scheduler::TransactionStart::EarlyEnd);
-    std::this_thread::sleep_for(MARGIN_FOR_TX_APPLY);
-    mVSyncModulator->onTransactionHandled();
-    EXPECT_EQ(APP_EARLY, mMockScheduler.getOffset(mAppConnection));
-    EXPECT_EQ(SF_EARLY, mMockScheduler.getOffset(mSfConnection));
-
-    for (int i = 0; i < MIN_EARLY_FRAME_COUNT_TRANSACTION - 1; i++) {
-        mVSyncModulator->onRefreshed(false);
-        EXPECT_EQ(APP_EARLY, mMockScheduler.getOffset(mAppConnection));
-        EXPECT_EQ(SF_EARLY, mMockScheduler.getOffset(mSfConnection));
-    }
-
-    mVSyncModulator->onRefreshed(false);
-    EXPECT_EQ(APP_LATE, mMockScheduler.getOffset(mAppConnection));
-    EXPECT_EQ(SF_LATE, mMockScheduler.getOffset(mSfConnection));
+    CHECK_COMMIT(kEarly, kEarly);
+    CHECK_REFRESH(MIN_EARLY_TRANSACTION_FRAMES - 1, kEarly, kEarly);
+    CHECK_REFRESH(1, kLate, kLate);
 }
 
 } // namespace android::scheduler