SurfaceFlinger: expose duration as part of phase offsets

expose app/sf duration together with the offset configs. These
will be used in the next CLs to calculate when to wake up the
corresponding EventThread.

Test: examine offsets and duration via dumpsys SurfaceFlinger
Bug: 162888874
Change-Id: Ifc1848358823570a34760f29ea975c286fbd8837
diff --git a/services/surfaceflinger/Scheduler/VsyncConfiguration.cpp b/services/surfaceflinger/Scheduler/VsyncConfiguration.cpp
index 5373742..aac2569 100644
--- a/services/surfaceflinger/Scheduler/VsyncConfiguration.cpp
+++ b/services/surfaceflinger/Scheduler/VsyncConfiguration.cpp
@@ -51,145 +51,13 @@
 
 } // namespace
 
-namespace android::scheduler {
+namespace android::scheduler::impl {
 
-PhaseConfiguration::~PhaseConfiguration() = default;
+VsyncConfiguration::VsyncConfiguration(float currentFps) : mRefreshRateFps(currentFps) {}
 
-namespace impl {
-
-PhaseOffsets::PhaseOffsets(const scheduler::RefreshRateConfigs& refreshRateConfigs)
-      : PhaseOffsets(getRefreshRatesFromConfigs(refreshRateConfigs),
-                     refreshRateConfigs.getCurrentRefreshRate().getFps(),
-                     sysprop::vsync_event_phase_offset_ns(1000000),
-                     sysprop::vsync_sf_event_phase_offset_ns(1000000),
-                     getProperty("debug.sf.early_phase_offset_ns"),
-                     getProperty("debug.sf.early_gl_phase_offset_ns"),
-                     getProperty("debug.sf.early_app_phase_offset_ns"),
-                     getProperty("debug.sf.early_gl_app_phase_offset_ns"),
-                     // Below defines the threshold when an offset is considered to be negative,
-                     // i.e. targeting for the N+2 vsync instead of N+1. This means that: For offset
-                     // < threshold, SF wake up (vsync_duration - offset) before HW vsync. For
-                     // offset >= threshold, SF wake up (2 * vsync_duration - offset) before HW
-                     // vsync.
-                     getProperty("debug.sf.phase_offset_threshold_for_next_vsync_ns")
-                             .value_or(std::numeric_limits<nsecs_t>::max())) {}
-
-PhaseOffsets::PhaseOffsets(const std::vector<float>& refreshRates, float currentFps,
-                           nsecs_t vsyncPhaseOffsetNs, nsecs_t sfVSyncPhaseOffsetNs,
-                           std::optional<nsecs_t> earlySfOffsetNs,
-                           std::optional<nsecs_t> earlyGlSfOffsetNs,
-                           std::optional<nsecs_t> earlyAppOffsetNs,
-                           std::optional<nsecs_t> earlyGlAppOffsetNs, nsecs_t thresholdForNextVsync)
-      : mVSyncPhaseOffsetNs(vsyncPhaseOffsetNs),
-        mSfVSyncPhaseOffsetNs(sfVSyncPhaseOffsetNs),
-        mEarlySfOffsetNs(earlySfOffsetNs),
-        mEarlyGlSfOffsetNs(earlyGlSfOffsetNs),
-        mEarlyAppOffsetNs(earlyAppOffsetNs),
-        mEarlyGlAppOffsetNs(earlyGlAppOffsetNs),
-        mThresholdForNextVsync(thresholdForNextVsync),
-        mOffsets(initializeOffsets(refreshRates)),
-        mRefreshRateFps(currentFps) {}
-
-void PhaseOffsets::dump(std::string& result) const {
-    const auto [early, earlyGl, late] = getCurrentOffsets();
-    using base::StringAppendF;
-    StringAppendF(&result,
-                  "           app phase: %9" PRId64 " ns\t         SF phase: %9" PRId64 " ns\n"
-                  "     early app phase: %9" PRId64 " ns\t   early SF phase: %9" PRId64 " ns\n"
-                  "  GL early app phase: %9" PRId64 " ns\tGL early SF phase: %9" PRId64 " ns\n"
-                  "next VSYNC threshold: %9" PRId64 " ns\n",
-                  late.app, late.sf, early.app, early.sf, earlyGl.app, earlyGl.sf,
-                  mThresholdForNextVsync);
-}
-
-std::unordered_map<float, PhaseOffsets::Offsets> PhaseOffsets::initializeOffsets(
-        const std::vector<float>& refreshRates) const {
-    std::unordered_map<float, Offsets> offsets;
-
-    for (const auto& refreshRate : refreshRates) {
-        offsets.emplace(refreshRate,
-                        getPhaseOffsets(refreshRate, static_cast<nsecs_t>(1e9f / refreshRate)));
-    }
-    return offsets;
-}
-
-PhaseOffsets::Offsets PhaseOffsets::getPhaseOffsets(float fps, nsecs_t vsyncPeriod) const {
-    if (fps > 65.0f) {
-        return getHighFpsOffsets(vsyncPeriod);
-    } else {
-        return getDefaultOffsets(vsyncPeriod);
-    }
-}
-
-PhaseOffsets::Offsets PhaseOffsets::getDefaultOffsets(nsecs_t vsyncDuration) const {
-    return {
-            {
-                    mEarlySfOffsetNs.value_or(mSfVSyncPhaseOffsetNs) < mThresholdForNextVsync
-                            ? mEarlySfOffsetNs.value_or(mSfVSyncPhaseOffsetNs)
-                            : mEarlySfOffsetNs.value_or(mSfVSyncPhaseOffsetNs) - vsyncDuration,
-
-                    mEarlyAppOffsetNs.value_or(mVSyncPhaseOffsetNs),
-            },
-            {
-                    mEarlyGlSfOffsetNs.value_or(mSfVSyncPhaseOffsetNs) < mThresholdForNextVsync
-                            ? mEarlyGlSfOffsetNs.value_or(mSfVSyncPhaseOffsetNs)
-                            : mEarlyGlSfOffsetNs.value_or(mSfVSyncPhaseOffsetNs) - vsyncDuration,
-
-                    mEarlyGlAppOffsetNs.value_or(mVSyncPhaseOffsetNs),
-            },
-            {
-                    mSfVSyncPhaseOffsetNs < mThresholdForNextVsync
-                            ? mSfVSyncPhaseOffsetNs
-                            : mSfVSyncPhaseOffsetNs - vsyncDuration,
-
-                    mVSyncPhaseOffsetNs,
-            },
-    };
-}
-
-PhaseOffsets::Offsets PhaseOffsets::getHighFpsOffsets(nsecs_t vsyncDuration) const {
-    const auto highFpsLateAppOffsetNs =
-            getProperty("debug.sf.high_fps_late_app_phase_offset_ns").value_or(2000000);
-    const auto highFpsLateSfOffsetNs =
-            getProperty("debug.sf.high_fps_late_sf_phase_offset_ns").value_or(1000000);
-
-    const auto highFpsEarlySfOffsetNs = getProperty("debug.sf.high_fps_early_phase_offset_ns");
-    const auto highFpsEarlyGlSfOffsetNs = getProperty("debug.sf.high_fps_early_gl_phase_offset_ns");
-    const auto highFpsEarlyAppOffsetNs = getProperty("debug.sf.high_fps_early_app_phase_offset_ns");
-    const auto highFpsEarlyGlAppOffsetNs =
-            getProperty("debug.sf.high_fps_early_gl_app_phase_offset_ns");
-
-    return {
-            {
-                    highFpsEarlySfOffsetNs.value_or(highFpsLateSfOffsetNs) < mThresholdForNextVsync
-                            ? highFpsEarlySfOffsetNs.value_or(highFpsLateSfOffsetNs)
-                            : highFpsEarlySfOffsetNs.value_or(highFpsLateSfOffsetNs) -
-                                    vsyncDuration,
-
-                    highFpsEarlyAppOffsetNs.value_or(highFpsLateAppOffsetNs),
-            },
-            {
-                    highFpsEarlyGlSfOffsetNs.value_or(highFpsLateSfOffsetNs) <
-                                    mThresholdForNextVsync
-                            ? highFpsEarlyGlSfOffsetNs.value_or(highFpsLateSfOffsetNs)
-                            : highFpsEarlyGlSfOffsetNs.value_or(highFpsLateSfOffsetNs) -
-                                    vsyncDuration,
-
-                    highFpsEarlyGlAppOffsetNs.value_or(highFpsLateAppOffsetNs),
-            },
-            {
-                    highFpsLateSfOffsetNs < mThresholdForNextVsync
-                            ? highFpsLateSfOffsetNs
-                            : highFpsLateSfOffsetNs - vsyncDuration,
-
-                    highFpsLateAppOffsetNs,
-            },
-    };
-}
-
-PhaseOffsets::Offsets PhaseOffsets::getOffsetsForRefreshRate(float fps) const {
+PhaseOffsets::VsyncConfigSet VsyncConfiguration::getConfigsForRefreshRate(float fps) const {
     const auto iter = std::find_if(mOffsets.begin(), mOffsets.end(),
-                                   [&fps](const std::pair<float, Offsets>& candidateFps) {
+                                   [&fps](const std::pair<float, VsyncConfigSet>& candidateFps) {
                                        return fpsEqualsWithMargin(fps, candidateFps.first);
                                    });
 
@@ -200,7 +68,194 @@
     // Unknown refresh rate. This might happen if we get a hotplug event for an external display.
     // In this case just construct the offset.
     ALOGW("Can't find offset for %.2f fps", fps);
-    return getPhaseOffsets(fps, static_cast<nsecs_t>(1e9f / fps));
+    return constructOffsets(static_cast<nsecs_t>(1e9f / fps));
+}
+
+void VsyncConfiguration::initializeOffsets(const std::vector<float>& refreshRates) {
+    for (const auto fps : refreshRates) {
+        mOffsets.emplace(fps, constructOffsets(static_cast<nsecs_t>(1e9f / fps)));
+    }
+}
+
+void VsyncConfiguration::dump(std::string& result) const {
+    const auto [early, earlyGpu, late] = getCurrentConfigs();
+    using base::StringAppendF;
+    StringAppendF(&result,
+                  "           app phase:    %9" PRId64 " ns\t         SF phase:    %9" PRId64
+                  " ns\n"
+                  "           app duration: %9lld ns\t         SF duration: %9lld ns\n"
+                  "     early app phase:    %9" PRId64 " ns\t   early SF phase:    %9" PRId64
+                  " ns\n"
+                  "     early app duration: %9lld ns\t   early SF duration: %9lld ns\n"
+                  "  GL early app phase:    %9" PRId64 " ns\tGL early SF phase:    %9" PRId64
+                  " ns\n"
+                  "  GL early app duration: %9lld ns\tGL early SF duration: %9lld ns\n",
+                  late.appOffset, late.sfOffset,
+
+                  late.appWorkDuration.count(), late.sfWorkDuration.count(),
+
+                  early.appOffset, early.sfOffset,
+
+                  early.appWorkDuration.count(), early.sfWorkDuration.count(),
+
+                  earlyGpu.appOffset, earlyGpu.sfOffset,
+
+                  earlyGpu.appWorkDuration.count(), earlyGpu.sfWorkDuration.count());
+}
+
+PhaseOffsets::PhaseOffsets(const scheduler::RefreshRateConfigs& refreshRateConfigs)
+      : PhaseOffsets(getRefreshRatesFromConfigs(refreshRateConfigs),
+                     refreshRateConfigs.getCurrentRefreshRate().getFps(),
+                     sysprop::vsync_event_phase_offset_ns(1000000),
+                     sysprop::vsync_sf_event_phase_offset_ns(1000000),
+                     getProperty("debug.sf.early_phase_offset_ns"),
+                     getProperty("debug.sf.early_gl_phase_offset_ns"),
+                     getProperty("debug.sf.early_app_phase_offset_ns"),
+                     getProperty("debug.sf.early_gl_app_phase_offset_ns"),
+                     getProperty("debug.sf.high_fps_late_app_phase_offset_ns").value_or(2000000),
+                     getProperty("debug.sf.high_fps_late_sf_phase_offset_ns").value_or(1000000),
+                     getProperty("debug.sf.high_fps_early_phase_offset_ns"),
+                     getProperty("debug.sf.high_fps_early_gl_phase_offset_ns"),
+                     getProperty("debug.sf.high_fps_early_app_phase_offset_ns"),
+                     getProperty("debug.sf.high_fps_early_gl_app_phase_offset_ns"),
+                     // Below defines the threshold when an offset is considered to be negative,
+                     // i.e. targeting for the N+2 vsync instead of N+1. This means that: For offset
+                     // < threshold, SF wake up (vsync_duration - offset) before HW vsync. For
+                     // offset >= threshold, SF wake up (2 * vsync_duration - offset) before HW
+                     // vsync.
+                     getProperty("debug.sf.phase_offset_threshold_for_next_vsync_ns")
+                             .value_or(std::numeric_limits<nsecs_t>::max())) {}
+
+PhaseOffsets::PhaseOffsets(
+        const std::vector<float>& refreshRates, float currentFps, nsecs_t vsyncPhaseOffsetNs,
+        nsecs_t sfVSyncPhaseOffsetNs, std::optional<nsecs_t> earlySfOffsetNs,
+        std::optional<nsecs_t> earlyGpuSfOffsetNs, std::optional<nsecs_t> earlyAppOffsetNs,
+        std::optional<nsecs_t> earlyGpuAppOffsetNs, nsecs_t highFpsVsyncPhaseOffsetNs,
+        nsecs_t highFpsSfVSyncPhaseOffsetNs, std::optional<nsecs_t> highFpsEarlySfOffsetNs,
+        std::optional<nsecs_t> highFpsEarlyGpuSfOffsetNs,
+        std::optional<nsecs_t> highFpsEarlyAppOffsetNs,
+        std::optional<nsecs_t> highFpsEarlyGpuAppOffsetNs, nsecs_t thresholdForNextVsync)
+      : VsyncConfiguration(currentFps),
+        mVSyncPhaseOffsetNs(vsyncPhaseOffsetNs),
+        mSfVSyncPhaseOffsetNs(sfVSyncPhaseOffsetNs),
+        mEarlySfOffsetNs(earlySfOffsetNs),
+        mEarlyGpuSfOffsetNs(earlyGpuSfOffsetNs),
+        mEarlyAppOffsetNs(earlyAppOffsetNs),
+        mEarlyGpuAppOffsetNs(earlyGpuAppOffsetNs),
+        mHighFpsVSyncPhaseOffsetNs(highFpsVsyncPhaseOffsetNs),
+        mHighFpsSfVSyncPhaseOffsetNs(highFpsSfVSyncPhaseOffsetNs),
+        mHighFpsEarlySfOffsetNs(highFpsEarlySfOffsetNs),
+        mHighFpsEarlyGpuSfOffsetNs(highFpsEarlyGpuSfOffsetNs),
+        mHighFpsEarlyAppOffsetNs(highFpsEarlyAppOffsetNs),
+        mHighFpsEarlyGpuAppOffsetNs(highFpsEarlyGpuAppOffsetNs),
+        mThresholdForNextVsync(thresholdForNextVsync) {
+    initializeOffsets(refreshRates);
+}
+
+PhaseOffsets::VsyncConfigSet PhaseOffsets::constructOffsets(nsecs_t vsyncDuration) const {
+    if (vsyncDuration < std::chrono::nanoseconds(15ms).count()) {
+        return getHighFpsOffsets(vsyncDuration);
+    } else {
+        return getDefaultOffsets(vsyncDuration);
+    }
+}
+
+namespace {
+std::chrono::nanoseconds sfOffsetToDuration(nsecs_t sfOffset, nsecs_t vsyncDuration) {
+    return std::chrono::nanoseconds(vsyncDuration - sfOffset);
+}
+
+std::chrono::nanoseconds appOffsetToDuration(nsecs_t appOffset, nsecs_t sfOffset,
+                                             nsecs_t vsyncDuration) {
+    auto duration = vsyncDuration + (sfOffset - appOffset);
+    if (duration < vsyncDuration) {
+        duration += vsyncDuration;
+    }
+
+    return std::chrono::nanoseconds(duration);
+}
+} // namespace
+
+PhaseOffsets::VsyncConfigSet PhaseOffsets::getDefaultOffsets(nsecs_t vsyncDuration) const {
+    const auto earlySfOffset =
+            mEarlySfOffsetNs.value_or(mSfVSyncPhaseOffsetNs) < mThresholdForNextVsync
+
+            ? mEarlySfOffsetNs.value_or(mSfVSyncPhaseOffsetNs)
+            : mEarlySfOffsetNs.value_or(mSfVSyncPhaseOffsetNs) - vsyncDuration;
+    const auto earlyAppOffset = mEarlyAppOffsetNs.value_or(mVSyncPhaseOffsetNs);
+    const auto earlyGpuSfOffset =
+            mEarlyGpuSfOffsetNs.value_or(mSfVSyncPhaseOffsetNs) < mThresholdForNextVsync
+
+            ? mEarlyGpuSfOffsetNs.value_or(mSfVSyncPhaseOffsetNs)
+            : mEarlyGpuSfOffsetNs.value_or(mSfVSyncPhaseOffsetNs) - vsyncDuration;
+    const auto earlyGpuAppOffset = mEarlyGpuAppOffsetNs.value_or(mVSyncPhaseOffsetNs);
+    const auto lateSfOffset = mSfVSyncPhaseOffsetNs < mThresholdForNextVsync
+            ? mSfVSyncPhaseOffsetNs
+            : mSfVSyncPhaseOffsetNs - vsyncDuration;
+    const auto lateAppOffset = mVSyncPhaseOffsetNs;
+
+    return {
+            .early = {.sfOffset = earlySfOffset,
+                      .appOffset = earlyAppOffset,
+                      .sfWorkDuration = sfOffsetToDuration(earlySfOffset, vsyncDuration),
+                      .appWorkDuration =
+                              appOffsetToDuration(earlyAppOffset, earlySfOffset, vsyncDuration)},
+            .earlyGpu = {.sfOffset = earlyGpuSfOffset,
+                         .appOffset = earlyGpuAppOffset,
+                         .sfWorkDuration = sfOffsetToDuration(earlyGpuSfOffset, vsyncDuration),
+                         .appWorkDuration = appOffsetToDuration(earlyGpuAppOffset, earlyGpuSfOffset,
+                                                                vsyncDuration)},
+            .late = {.sfOffset = lateSfOffset,
+                     .appOffset = lateAppOffset,
+                     .sfWorkDuration = sfOffsetToDuration(lateSfOffset, vsyncDuration),
+                     .appWorkDuration =
+                             appOffsetToDuration(lateAppOffset, lateSfOffset, vsyncDuration)},
+    };
+}
+
+PhaseOffsets::VsyncConfigSet PhaseOffsets::getHighFpsOffsets(nsecs_t vsyncDuration) const {
+    const auto earlySfOffset =
+            mHighFpsEarlySfOffsetNs.value_or(mHighFpsSfVSyncPhaseOffsetNs) < mThresholdForNextVsync
+            ? mHighFpsEarlySfOffsetNs.value_or(mHighFpsSfVSyncPhaseOffsetNs)
+            : mHighFpsEarlySfOffsetNs.value_or(mHighFpsSfVSyncPhaseOffsetNs) - vsyncDuration;
+    const auto earlyAppOffset = mHighFpsEarlyAppOffsetNs.value_or(mHighFpsVSyncPhaseOffsetNs);
+    const auto earlyGpuSfOffset = mHighFpsEarlyGpuSfOffsetNs.value_or(
+                                          mHighFpsSfVSyncPhaseOffsetNs) < mThresholdForNextVsync
+
+            ? mHighFpsEarlyGpuSfOffsetNs.value_or(mHighFpsSfVSyncPhaseOffsetNs)
+            : mHighFpsEarlyGpuSfOffsetNs.value_or(mHighFpsSfVSyncPhaseOffsetNs) - vsyncDuration;
+    const auto earlyGpuAppOffset = mHighFpsEarlyGpuAppOffsetNs.value_or(mHighFpsVSyncPhaseOffsetNs);
+    const auto lateSfOffset = mHighFpsSfVSyncPhaseOffsetNs < mThresholdForNextVsync
+            ? mHighFpsSfVSyncPhaseOffsetNs
+            : mHighFpsSfVSyncPhaseOffsetNs - vsyncDuration;
+    const auto lateAppOffset = mHighFpsVSyncPhaseOffsetNs;
+
+    return {
+            .early =
+                    {
+                            .sfOffset = earlySfOffset,
+                            .appOffset = earlyAppOffset,
+                            .sfWorkDuration = sfOffsetToDuration(earlySfOffset, vsyncDuration),
+                            .appWorkDuration = appOffsetToDuration(earlyAppOffset, earlySfOffset,
+                                                                   vsyncDuration),
+                    },
+            .earlyGpu =
+                    {
+                            .sfOffset = earlyGpuSfOffset,
+                            .appOffset = earlyGpuAppOffset,
+                            .sfWorkDuration = sfOffsetToDuration(earlyGpuSfOffset, vsyncDuration),
+                            .appWorkDuration = appOffsetToDuration(earlyGpuAppOffset,
+                                                                   earlyGpuSfOffset, vsyncDuration),
+                    },
+            .late =
+                    {
+                            .sfOffset = lateSfOffset,
+                            .appOffset = lateAppOffset,
+                            .sfWorkDuration = sfOffsetToDuration(lateSfOffset, vsyncDuration),
+                            .appWorkDuration =
+                                    appOffsetToDuration(lateAppOffset, lateSfOffset, vsyncDuration),
+                    },
+    };
 }
 
 static void validateSysprops() {
@@ -236,123 +291,105 @@
     validateProperty("debug.sf.high_fps_early_gl_app_phase_offset_ns");
 }
 
-static nsecs_t sfDurationToOffset(nsecs_t sfDuration, nsecs_t vsyncDuration) {
-    return sfDuration == -1 ? 1'000'000 : vsyncDuration - sfDuration % vsyncDuration;
+namespace {
+nsecs_t sfDurationToOffset(std::chrono::nanoseconds sfDuration, nsecs_t vsyncDuration) {
+    return vsyncDuration - sfDuration.count() % vsyncDuration;
 }
 
-static nsecs_t appDurationToOffset(nsecs_t appDuration, nsecs_t sfDuration, nsecs_t vsyncDuration) {
-    return sfDuration == -1 ? 1'000'000
-                            : vsyncDuration - (appDuration + sfDuration) % vsyncDuration;
+nsecs_t appDurationToOffset(std::chrono::nanoseconds appDuration,
+                            std::chrono::nanoseconds sfDuration, nsecs_t vsyncDuration) {
+    return vsyncDuration - (appDuration + sfDuration).count() % vsyncDuration;
 }
+} // namespace
 
-PhaseDurations::Offsets PhaseDurations::constructOffsets(nsecs_t vsyncDuration) const {
-    return Offsets{
-            {
-                    mSfEarlyDuration < vsyncDuration
-                            ? sfDurationToOffset(mSfEarlyDuration, vsyncDuration)
-                            : sfDurationToOffset(mSfEarlyDuration, vsyncDuration) - vsyncDuration,
+WorkDuration::VsyncConfigSet WorkDuration::constructOffsets(nsecs_t vsyncDuration) const {
+    const auto sfDurationFixup = [vsyncDuration](nsecs_t duration) {
+        return duration == -1 ? std::chrono::nanoseconds(vsyncDuration) - 1ms
+                              : std::chrono::nanoseconds(duration);
+    };
 
-                    appDurationToOffset(mAppEarlyDuration, mSfEarlyDuration, vsyncDuration),
-            },
-            {
-                    mSfEarlyGlDuration < vsyncDuration
-                            ? sfDurationToOffset(mSfEarlyGlDuration, vsyncDuration)
-                            : sfDurationToOffset(mSfEarlyGlDuration, vsyncDuration) - vsyncDuration,
+    const auto appDurationFixup = [vsyncDuration](nsecs_t duration) {
+        return duration == -1 ? std::chrono::nanoseconds(vsyncDuration)
+                              : std::chrono::nanoseconds(duration);
+    };
 
-                    appDurationToOffset(mAppEarlyGlDuration, mSfEarlyGlDuration, vsyncDuration),
-            },
-            {
-                    mSfDuration < vsyncDuration
-                            ? sfDurationToOffset(mSfDuration, vsyncDuration)
-                            : sfDurationToOffset(mSfDuration, vsyncDuration) - vsyncDuration,
+    const auto sfEarlyDuration = sfDurationFixup(mSfEarlyDuration);
+    const auto appEarlyDuration = appDurationFixup(mAppEarlyDuration);
+    const auto sfEarlyGpuDuration = sfDurationFixup(mSfEarlyGpuDuration);
+    const auto appEarlyGpuDuration = appDurationFixup(mAppEarlyGpuDuration);
+    const auto sfDuration = sfDurationFixup(mSfDuration);
+    const auto appDuration = appDurationFixup(mAppDuration);
 
-                    appDurationToOffset(mAppDuration, mSfDuration, vsyncDuration),
-            },
+    return {
+            .early =
+                    {
+
+                            .sfOffset = sfEarlyDuration.count() < vsyncDuration
+                                    ? sfDurationToOffset(sfEarlyDuration, vsyncDuration)
+                                    : sfDurationToOffset(sfEarlyDuration, vsyncDuration) -
+                                            vsyncDuration,
+
+                            .appOffset = appDurationToOffset(appEarlyDuration, sfEarlyDuration,
+                                                             vsyncDuration),
+
+                            .sfWorkDuration = sfEarlyDuration,
+                            .appWorkDuration = appEarlyDuration,
+                    },
+            .earlyGpu =
+                    {
+
+                            .sfOffset = sfEarlyGpuDuration.count() < vsyncDuration
+
+                                    ? sfDurationToOffset(sfEarlyGpuDuration, vsyncDuration)
+                                    : sfDurationToOffset(sfEarlyGpuDuration, vsyncDuration) -
+                                            vsyncDuration,
+
+                            .appOffset = appDurationToOffset(appEarlyGpuDuration,
+                                                             sfEarlyGpuDuration, vsyncDuration),
+                            .sfWorkDuration = sfEarlyGpuDuration,
+                            .appWorkDuration = appEarlyGpuDuration,
+                    },
+            .late =
+                    {
+
+                            .sfOffset = sfDuration.count() < vsyncDuration
+
+                                    ? sfDurationToOffset(sfDuration, vsyncDuration)
+                                    : sfDurationToOffset(sfDuration, vsyncDuration) - vsyncDuration,
+
+                            .appOffset =
+                                    appDurationToOffset(appDuration, sfDuration, vsyncDuration),
+
+                            .sfWorkDuration = sfDuration,
+                            .appWorkDuration = appDuration,
+                    },
     };
 }
 
-std::unordered_map<float, PhaseDurations::Offsets> PhaseDurations::initializeOffsets(
-        const std::vector<float>& refreshRates) const {
-    std::unordered_map<float, Offsets> offsets;
-
-    for (const auto fps : refreshRates) {
-        offsets.emplace(fps, constructOffsets(static_cast<nsecs_t>(1e9f / fps)));
-    }
-    return offsets;
-}
-
-PhaseDurations::PhaseDurations(const scheduler::RefreshRateConfigs& refreshRateConfigs)
-      : PhaseDurations(getRefreshRatesFromConfigs(refreshRateConfigs),
-                       refreshRateConfigs.getCurrentRefreshRate().getFps(),
-                       getProperty("debug.sf.late.sf.duration").value_or(-1),
-                       getProperty("debug.sf.late.app.duration").value_or(-1),
-                       getProperty("debug.sf.early.sf.duration").value_or(mSfDuration),
-                       getProperty("debug.sf.early.app.duration").value_or(mAppDuration),
-                       getProperty("debug.sf.earlyGl.sf.duration").value_or(mSfDuration),
-                       getProperty("debug.sf.earlyGl.app.duration").value_or(mAppDuration)) {
+WorkDuration::WorkDuration(const scheduler::RefreshRateConfigs& refreshRateConfigs)
+      : WorkDuration(getRefreshRatesFromConfigs(refreshRateConfigs),
+                     refreshRateConfigs.getCurrentRefreshRate().getFps(),
+                     getProperty("debug.sf.late.sf.duration").value_or(-1),
+                     getProperty("debug.sf.late.app.duration").value_or(-1),
+                     getProperty("debug.sf.early.sf.duration").value_or(mSfDuration),
+                     getProperty("debug.sf.early.app.duration").value_or(mAppDuration),
+                     getProperty("debug.sf.earlyGl.sf.duration").value_or(mSfDuration),
+                     getProperty("debug.sf.earlyGl.app.duration").value_or(mAppDuration)) {
     validateSysprops();
 }
 
-PhaseDurations::PhaseDurations(const std::vector<float>& refreshRates, float currentFps,
-                               nsecs_t sfDuration, nsecs_t appDuration, nsecs_t sfEarlyDuration,
-                               nsecs_t appEarlyDuration, nsecs_t sfEarlyGlDuration,
-                               nsecs_t appEarlyGlDuration)
-      : mSfDuration(sfDuration),
+WorkDuration::WorkDuration(const std::vector<float>& refreshRates, float currentFps,
+                           nsecs_t sfDuration, nsecs_t appDuration, nsecs_t sfEarlyDuration,
+                           nsecs_t appEarlyDuration, nsecs_t sfEarlyGpuDuration,
+                           nsecs_t appEarlyGpuDuration)
+      : VsyncConfiguration(currentFps),
+        mSfDuration(sfDuration),
         mAppDuration(appDuration),
         mSfEarlyDuration(sfEarlyDuration),
         mAppEarlyDuration(appEarlyDuration),
-        mSfEarlyGlDuration(sfEarlyGlDuration),
-        mAppEarlyGlDuration(appEarlyGlDuration),
-        mOffsets(initializeOffsets(refreshRates)),
-        mRefreshRateFps(currentFps) {}
-
-PhaseOffsets::Offsets PhaseDurations::getOffsetsForRefreshRate(float fps) const {
-    const auto iter = std::find_if(mOffsets.begin(), mOffsets.end(), [=](const auto& candidateFps) {
-        return fpsEqualsWithMargin(fps, candidateFps.first);
-    });
-
-    if (iter != mOffsets.end()) {
-        return iter->second;
-    }
-
-    // Unknown refresh rate. This might happen if we get a hotplug event for an external display.
-    // In this case just construct the offset.
-    ALOGW("Can't find offset for %.2f fps", fps);
-    return constructOffsets(static_cast<nsecs_t>(1e9f / fps));
+        mSfEarlyGpuDuration(sfEarlyGpuDuration),
+        mAppEarlyGpuDuration(appEarlyGpuDuration) {
+    initializeOffsets(refreshRates);
 }
 
-void PhaseDurations::dump(std::string& result) const {
-    const auto [early, earlyGl, late] = getCurrentOffsets();
-    using base::StringAppendF;
-    StringAppendF(&result,
-                  "           app phase:    %9" PRId64 " ns\t         SF phase:    %9" PRId64
-                  " ns\n"
-                  "           app duration: %9" PRId64 " ns\t         SF duration: %9" PRId64
-                  " ns\n"
-                  "     early app phase:    %9" PRId64 " ns\t   early SF phase:    %9" PRId64
-                  " ns\n"
-                  "     early app duration: %9" PRId64 " ns\t   early SF duration: %9" PRId64
-                  " ns\n"
-                  "  GL early app phase:    %9" PRId64 " ns\tGL early SF phase:    %9" PRId64
-                  " ns\n"
-                  "  GL early app duration: %9" PRId64 " ns\tGL early SF duration: %9" PRId64
-                  " ns\n",
-                  late.app,
-
-                  late.sf,
-
-                  mAppDuration, mSfDuration,
-
-                  early.app, early.sf,
-
-                  mAppEarlyDuration, mSfEarlyDuration,
-
-                  earlyGl.app,
-
-                  earlyGl.sf,
-
-                  mAppEarlyGlDuration, mSfEarlyGlDuration);
-}
-
-} // namespace impl
-} // namespace android::scheduler
+} // namespace android::scheduler::impl
diff --git a/services/surfaceflinger/Scheduler/VsyncConfiguration.h b/services/surfaceflinger/Scheduler/VsyncConfiguration.h
index 0ae9fef..c27a25d 100644
--- a/services/surfaceflinger/Scheduler/VsyncConfiguration.h
+++ b/services/surfaceflinger/Scheduler/VsyncConfiguration.h
@@ -24,19 +24,18 @@
 namespace android::scheduler {
 
 /*
- * This class encapsulates offsets for different refresh rates. Depending
+ * This class encapsulates vsync configurations for different refresh rates. Depending
  * on what refresh rate we are using, and wheter we are composing in GL,
  * different offsets will help us with latency. This class keeps track of
  * which mode the device is on, and returns approprate offsets when needed.
  */
-class PhaseConfiguration {
+class VsyncConfiguration {
 public:
-    using Offsets = VsyncModulator::OffsetsConfig;
+    using VsyncConfigSet = VsyncModulator::VsyncConfigSet;
 
-    virtual ~PhaseConfiguration();
-
-    virtual Offsets getCurrentOffsets() const = 0;
-    virtual Offsets getOffsetsForRefreshRate(float fps) const = 0;
+    virtual ~VsyncConfiguration() = default;
+    virtual VsyncConfigSet getCurrentConfigs() const = 0;
+    virtual VsyncConfigSet getConfigsForRefreshRate(float fps) const = 0;
 
     virtual void setRefreshRateFps(float fps) = 0;
 
@@ -46,18 +45,21 @@
 namespace impl {
 
 /*
- * This is the old implementation of phase offsets and considered as deprecated.
- * PhaseDurations is the new implementation.
+ * This is a common implementation for both phase offsets and durations.
+ * PhaseOffsets and WorkDuration derive from this class and implement the
+ * constructOffsets method
  */
-class PhaseOffsets : public scheduler::PhaseConfiguration {
+class VsyncConfiguration : public scheduler::VsyncConfiguration {
 public:
-    PhaseOffsets(const scheduler::RefreshRateConfigs&);
+    explicit VsyncConfiguration(float currentFps);
 
     // Returns early, early GL, and late offsets for Apps and SF for a given refresh rate.
-    Offsets getOffsetsForRefreshRate(float fps) const override;
+    VsyncConfigSet getConfigsForRefreshRate(float fps) const override;
 
     // Returns early, early GL, and late offsets for Apps and SF.
-    Offsets getCurrentOffsets() const override { return getOffsetsForRefreshRate(mRefreshRateFps); }
+    VsyncConfigSet getCurrentConfigs() const override {
+        return getConfigsForRefreshRate(mRefreshRateFps);
+    }
 
     // This function should be called when the device is switching between different
     // refresh rates, to properly update the offsets.
@@ -67,61 +69,73 @@
     void dump(std::string& result) const override;
 
 protected:
+    void initializeOffsets(const std::vector<float>& refreshRates);
+    virtual VsyncConfiguration::VsyncConfigSet constructOffsets(nsecs_t vsyncDuration) const = 0;
+
+    std::unordered_map<float, VsyncConfigSet> mOffsets;
+    std::atomic<float> mRefreshRateFps;
+};
+
+/*
+ * This is the old implementation of phase offsets and considered as deprecated.
+ * WorkDuration is the new implementation.
+ */
+class PhaseOffsets : public VsyncConfiguration {
+public:
+    explicit PhaseOffsets(const scheduler::RefreshRateConfigs&);
+
+protected:
     // Used for unit tests
     PhaseOffsets(const std::vector<float>& refreshRates, float currentFps,
                  nsecs_t vsyncPhaseOffsetNs, nsecs_t sfVSyncPhaseOffsetNs,
-                 std::optional<nsecs_t> earlySfOffsetNs, std::optional<nsecs_t> earlyGlSfOffsetNs,
-                 std::optional<nsecs_t> earlyAppOffsetNs, std::optional<nsecs_t> earlyGlAppOffsetNs,
-                 nsecs_t thresholdForNextVsync);
-    std::unordered_map<float, Offsets> initializeOffsets(
-            const std::vector<float>& refreshRates) const;
-    Offsets getDefaultOffsets(nsecs_t vsyncPeriod) const;
-    Offsets getHighFpsOffsets(nsecs_t vsyncPeriod) const;
-    Offsets getPhaseOffsets(float fps, nsecs_t vsyncPeriod) const;
+                 std::optional<nsecs_t> earlySfOffsetNs, std::optional<nsecs_t> earlyGpuSfOffsetNs,
+                 std::optional<nsecs_t> earlyAppOffsetNs,
+                 std::optional<nsecs_t> earlyGpuAppOffsetNs, nsecs_t highFpsVsyncPhaseOffsetNs,
+                 nsecs_t highFpsSfVSyncPhaseOffsetNs, std::optional<nsecs_t> highFpsEarlySfOffsetNs,
+                 std::optional<nsecs_t> highFpsEarlyGpuSfOffsetNs,
+                 std::optional<nsecs_t> highFpsEarlyAppOffsetNs,
+                 std::optional<nsecs_t> highFpsEarlyGpuAppOffsetNs, nsecs_t thresholdForNextVsync);
+
+private:
+    VsyncConfiguration::VsyncConfigSet constructOffsets(nsecs_t vsyncDuration) const override;
+
+    VsyncConfigSet getDefaultOffsets(nsecs_t vsyncPeriod) const;
+    VsyncConfigSet getHighFpsOffsets(nsecs_t vsyncPeriod) const;
 
     const nsecs_t mVSyncPhaseOffsetNs;
     const nsecs_t mSfVSyncPhaseOffsetNs;
     const std::optional<nsecs_t> mEarlySfOffsetNs;
-    const std::optional<nsecs_t> mEarlyGlSfOffsetNs;
+    const std::optional<nsecs_t> mEarlyGpuSfOffsetNs;
     const std::optional<nsecs_t> mEarlyAppOffsetNs;
-    const std::optional<nsecs_t> mEarlyGlAppOffsetNs;
-    const nsecs_t mThresholdForNextVsync;
-    const std::unordered_map<float, Offsets> mOffsets;
+    const std::optional<nsecs_t> mEarlyGpuAppOffsetNs;
 
-    std::atomic<float> mRefreshRateFps;
+    const nsecs_t mHighFpsVSyncPhaseOffsetNs;
+    const nsecs_t mHighFpsSfVSyncPhaseOffsetNs;
+    const std::optional<nsecs_t> mHighFpsEarlySfOffsetNs;
+    const std::optional<nsecs_t> mHighFpsEarlyGpuSfOffsetNs;
+    const std::optional<nsecs_t> mHighFpsEarlyAppOffsetNs;
+    const std::optional<nsecs_t> mHighFpsEarlyGpuAppOffsetNs;
+
+    const nsecs_t mThresholdForNextVsync;
 };
 
 /*
  * Class that encapsulates the phase offsets for SurfaceFlinger and App.
- * The offsets are calculated from durations for each one of the (late, early, earlyGL)
+ * The offsets are calculated from durations for each one of the (late, early, earlyGpu)
  * offset types.
  */
-class PhaseDurations : public scheduler::PhaseConfiguration {
+class WorkDuration : public VsyncConfiguration {
 public:
-    PhaseDurations(const scheduler::RefreshRateConfigs&);
-
-    // Returns early, early GL, and late offsets for Apps and SF for a given refresh rate.
-    Offsets getOffsetsForRefreshRate(float fps) const override;
-
-    // Returns early, early GL, and late offsets for Apps and SF.
-    Offsets getCurrentOffsets() const override { return getOffsetsForRefreshRate(mRefreshRateFps); }
-
-    // This function should be called when the device is switching between different
-    // refresh rates, to properly update the offsets.
-    void setRefreshRateFps(float fps) override { mRefreshRateFps = fps; }
-
-    // Returns current offsets in human friendly format.
-    void dump(std::string& result) const override;
+    explicit WorkDuration(const scheduler::RefreshRateConfigs&);
 
 protected:
     // Used for unit tests
-    PhaseDurations(const std::vector<float>& refreshRates, float currentFps, nsecs_t sfDuration,
-                   nsecs_t appDuration, nsecs_t sfEarlyDuration, nsecs_t appEarlyDuration,
-                   nsecs_t sfEarlyGlDuration, nsecs_t appEarlyGlDuration);
+    WorkDuration(const std::vector<float>& refreshRates, float currentFps, nsecs_t sfDuration,
+                 nsecs_t appDuration, nsecs_t sfEarlyDuration, nsecs_t appEarlyDuration,
+                 nsecs_t sfEarlyGpuDuration, nsecs_t appEarlyGpuDuration);
 
 private:
-    std::unordered_map<float, Offsets> initializeOffsets(const std::vector<float>&) const;
-    PhaseDurations::Offsets constructOffsets(nsecs_t vsyncDuration) const;
+    VsyncConfiguration::VsyncConfigSet constructOffsets(nsecs_t vsyncDuration) const override;
 
     const nsecs_t mSfDuration;
     const nsecs_t mAppDuration;
@@ -129,12 +143,8 @@
     const nsecs_t mSfEarlyDuration;
     const nsecs_t mAppEarlyDuration;
 
-    const nsecs_t mSfEarlyGlDuration;
-    const nsecs_t mAppEarlyGlDuration;
-
-    const std::unordered_map<float, Offsets> mOffsets;
-
-    std::atomic<float> mRefreshRateFps;
+    const nsecs_t mSfEarlyGpuDuration;
+    const nsecs_t mAppEarlyGpuDuration;
 };
 
 } // namespace impl
diff --git a/services/surfaceflinger/Scheduler/VsyncModulator.cpp b/services/surfaceflinger/Scheduler/VsyncModulator.cpp
index 7a1b7e4..1f821be 100644
--- a/services/surfaceflinger/Scheduler/VsyncModulator.cpp
+++ b/services/surfaceflinger/Scheduler/VsyncModulator.cpp
@@ -35,18 +35,19 @@
 
 const std::chrono::nanoseconds VsyncModulator::MIN_EARLY_TRANSACTION_TIME = 1ms;
 
-VsyncModulator::VsyncModulator(const OffsetsConfig& config, Now now)
-      : mOffsetsConfig(config),
+VsyncModulator::VsyncModulator(const VsyncConfigSet& config, Now now)
+      : mVsyncConfigSet(config),
         mNow(now),
         mTraceDetailedInfo(base::GetBoolProperty("debug.sf.vsync_trace_detailed_info", false)) {}
 
-VsyncModulator::Offsets VsyncModulator::setPhaseOffsets(const OffsetsConfig& config) {
+VsyncModulator::VsyncConfig VsyncModulator::setVsyncConfigSet(const VsyncConfigSet& config) {
     std::lock_guard<std::mutex> lock(mMutex);
-    mOffsetsConfig = config;
-    return updateOffsetsLocked();
+    mVsyncConfigSet = config;
+    return updateVsyncConfigLocked();
 }
 
-VsyncModulator::OffsetsOpt VsyncModulator::setTransactionSchedule(TransactionSchedule schedule) {
+VsyncModulator::VsyncConfigOpt VsyncModulator::setTransactionSchedule(
+        TransactionSchedule schedule) {
     switch (schedule) {
         case Schedule::EarlyStart:
             ALOGW_IF(mExplicitEarlyWakeup, "%s: Duplicate EarlyStart", __FUNCTION__);
@@ -76,29 +77,29 @@
         return std::nullopt;
     }
     mTransactionSchedule = schedule;
-    return updateOffsets();
+    return updateVsyncConfig();
 }
 
-VsyncModulator::OffsetsOpt VsyncModulator::onTransactionCommit() {
+VsyncModulator::VsyncConfigOpt VsyncModulator::onTransactionCommit() {
     mLastTransactionCommitTime = mNow();
     if (mTransactionSchedule == Schedule::Late) return std::nullopt;
     mTransactionSchedule = Schedule::Late;
-    return updateOffsets();
+    return updateVsyncConfig();
 }
 
-VsyncModulator::OffsetsOpt VsyncModulator::onRefreshRateChangeInitiated() {
+VsyncModulator::VsyncConfigOpt VsyncModulator::onRefreshRateChangeInitiated() {
     if (mRefreshRateChangePending) return std::nullopt;
     mRefreshRateChangePending = true;
-    return updateOffsets();
+    return updateVsyncConfig();
 }
 
-VsyncModulator::OffsetsOpt VsyncModulator::onRefreshRateChangeCompleted() {
+VsyncModulator::VsyncConfigOpt VsyncModulator::onRefreshRateChangeCompleted() {
     if (!mRefreshRateChangePending) return std::nullopt;
     mRefreshRateChangePending = false;
-    return updateOffsets();
+    return updateVsyncConfig();
 }
 
-VsyncModulator::OffsetsOpt VsyncModulator::onDisplayRefresh(bool usedGpuComposition) {
+VsyncModulator::VsyncConfigOpt VsyncModulator::onDisplayRefresh(bool usedGpuComposition) {
     bool updateOffsetsNeeded = false;
 
     if (mEarlyTransactionStartTime.load() + MIN_EARLY_TRANSACTION_TIME <=
@@ -117,40 +118,40 @@
     }
 
     if (!updateOffsetsNeeded) return std::nullopt;
-    return updateOffsets();
+    return updateVsyncConfig();
 }
 
-VsyncModulator::Offsets VsyncModulator::getOffsets() const {
+VsyncModulator::VsyncConfig VsyncModulator::getVsyncConfig() const {
     std::lock_guard<std::mutex> lock(mMutex);
-    return mOffsets;
+    return mVsyncConfig;
 }
 
-const VsyncModulator::Offsets& VsyncModulator::getNextOffsets() const {
+const VsyncModulator::VsyncConfig& VsyncModulator::getNextVsyncConfig() 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 || mTransactionSchedule == Schedule::EarlyEnd ||
         mEarlyTransactionFrames > 0 || mRefreshRateChangePending) {
-        return mOffsetsConfig.early;
+        return mVsyncConfigSet.early;
     } else if (mEarlyGpuFrames > 0) {
-        return mOffsetsConfig.earlyGpu;
+        return mVsyncConfigSet.earlyGpu;
     } else {
-        return mOffsetsConfig.late;
+        return mVsyncConfigSet.late;
     }
 }
 
-VsyncModulator::Offsets VsyncModulator::updateOffsets() {
+VsyncModulator::VsyncConfig VsyncModulator::updateVsyncConfig() {
     std::lock_guard<std::mutex> lock(mMutex);
-    return updateOffsetsLocked();
+    return updateVsyncConfigLocked();
 }
 
-VsyncModulator::Offsets VsyncModulator::updateOffsetsLocked() {
-    const Offsets& offsets = getNextOffsets();
-    mOffsets = offsets;
+VsyncModulator::VsyncConfig VsyncModulator::updateVsyncConfigLocked() {
+    const VsyncConfig& offsets = getNextVsyncConfig();
+    mVsyncConfig = offsets;
 
     if (mTraceDetailedInfo) {
-        const bool isEarly = &offsets == &mOffsetsConfig.early;
-        const bool isEarlyGpu = &offsets == &mOffsetsConfig.earlyGpu;
-        const bool isLate = &offsets == &mOffsetsConfig.late;
+        const bool isEarly = &offsets == &mVsyncConfigSet.early;
+        const bool isEarlyGpu = &offsets == &mVsyncConfigSet.earlyGpu;
+        const bool isLate = &offsets == &mVsyncConfigSet.late;
 
         ATRACE_INT("Vsync-EarlyOffsetsOn", isEarly);
         ATRACE_INT("Vsync-EarlyGpuOffsetsOn", isEarlyGpu);
diff --git a/services/surfaceflinger/Scheduler/VsyncModulator.h b/services/surfaceflinger/Scheduler/VsyncModulator.h
index f920bd2..355a14a 100644
--- a/services/surfaceflinger/Scheduler/VsyncModulator.h
+++ b/services/surfaceflinger/Scheduler/VsyncModulator.h
@@ -48,62 +48,69 @@
     // 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;
+    // Phase offsets and work durations for SF and app deadlines from VSYNC.
+    struct VsyncConfig {
+        nsecs_t sfOffset;
+        nsecs_t appOffset;
+        std::chrono::nanoseconds sfWorkDuration;
+        std::chrono::nanoseconds appWorkDuration;
 
-        bool operator==(const Offsets& other) const { return sf == other.sf && app == other.app; }
-        bool operator!=(const Offsets& other) const { return !(*this == other); }
+        bool operator==(const VsyncConfig& other) const {
+            return sfOffset == other.sfOffset && appOffset == other.appOffset &&
+                    sfWorkDuration == other.sfWorkDuration &&
+                    appWorkDuration == other.appWorkDuration;
+        }
+
+        bool operator!=(const VsyncConfig& other) const { return !(*this == other); }
     };
 
-    using OffsetsOpt = std::optional<Offsets>;
+    using VsyncConfigOpt = std::optional<VsyncConfig>;
 
-    struct OffsetsConfig {
-        Offsets early;    // Used for early transactions, and during refresh rate change.
-        Offsets earlyGpu; // Used during GPU composition.
-        Offsets late;     // Default.
+    struct VsyncConfigSet {
+        VsyncConfig early;    // Used for early transactions, and during refresh rate change.
+        VsyncConfig earlyGpu; // Used during GPU composition.
+        VsyncConfig late;     // Default.
 
-        bool operator==(const OffsetsConfig& other) const {
+        bool operator==(const VsyncConfigSet& other) const {
             return early == other.early && earlyGpu == other.earlyGpu && late == other.late;
         }
 
-        bool operator!=(const OffsetsConfig& other) const { return !(*this == other); }
+        bool operator!=(const VsyncConfigSet& other) const { return !(*this == other); }
     };
 
     using Clock = std::chrono::steady_clock;
     using TimePoint = Clock::time_point;
     using Now = TimePoint (*)();
 
-    explicit VsyncModulator(const OffsetsConfig&, Now = Clock::now);
+    explicit VsyncModulator(const VsyncConfigSet&, Now = Clock::now);
 
-    Offsets getOffsets() const EXCLUDES(mMutex);
+    VsyncConfig getVsyncConfig() const EXCLUDES(mMutex);
 
-    [[nodiscard]] Offsets setPhaseOffsets(const OffsetsConfig&) EXCLUDES(mMutex);
+    [[nodiscard]] VsyncConfig setVsyncConfigSet(const VsyncConfigSet&) EXCLUDES(mMutex);
 
     // Changes offsets in response to transaction flags or commit.
-    [[nodiscard]] OffsetsOpt setTransactionSchedule(TransactionSchedule);
-    [[nodiscard]] OffsetsOpt onTransactionCommit();
+    [[nodiscard]] VsyncConfigOpt setTransactionSchedule(TransactionSchedule);
+    [[nodiscard]] VsyncConfigOpt onTransactionCommit();
 
     // Called when we send a refresh rate change to hardware composer, so that
     // we can move into early offsets.
-    [[nodiscard]] OffsetsOpt onRefreshRateChangeInitiated();
+    [[nodiscard]] VsyncConfigOpt onRefreshRateChangeInitiated();
 
     // 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.
-    [[nodiscard]] OffsetsOpt onRefreshRateChangeCompleted();
+    [[nodiscard]] VsyncConfigOpt onRefreshRateChangeCompleted();
 
-    [[nodiscard]] OffsetsOpt onDisplayRefresh(bool usedGpuComposition);
+    [[nodiscard]] VsyncConfigOpt onDisplayRefresh(bool usedGpuComposition);
 
 private:
-    const Offsets& getNextOffsets() const REQUIRES(mMutex);
-    [[nodiscard]] Offsets updateOffsets() EXCLUDES(mMutex);
-    [[nodiscard]] Offsets updateOffsetsLocked() REQUIRES(mMutex);
+    const VsyncConfig& getNextVsyncConfig() const REQUIRES(mMutex);
+    [[nodiscard]] VsyncConfig updateVsyncConfig() EXCLUDES(mMutex);
+    [[nodiscard]] VsyncConfig updateVsyncConfigLocked() REQUIRES(mMutex);
 
     mutable std::mutex mMutex;
-    OffsetsConfig mOffsetsConfig GUARDED_BY(mMutex);
+    VsyncConfigSet mVsyncConfigSet GUARDED_BY(mMutex);
 
-    Offsets mOffsets GUARDED_BY(mMutex){mOffsetsConfig.late};
+    VsyncConfig mVsyncConfig GUARDED_BY(mMutex){mVsyncConfigSet.late};
 
     using Schedule = TransactionSchedule;
     std::atomic<Schedule> mTransactionSchedule = Schedule::Late;
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index 9a86625..bd43b77 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -947,9 +947,10 @@
         const nsecs_t period = hwConfig->getVsyncPeriod();
         config.refreshRate = 1e9f / period;
 
-        const auto offsets = mPhaseConfiguration->getOffsetsForRefreshRate(config.refreshRate);
-        config.appVsyncOffset = offsets.late.app;
-        config.sfVsyncOffset = offsets.late.sf;
+        const auto vsyncConfigSet =
+                mVsyncConfiguration->getConfigsForRefreshRate(config.refreshRate);
+        config.appVsyncOffset = vsyncConfigSet.late.appOffset;
+        config.sfVsyncOffset = vsyncConfigSet.late.sfOffset;
         config.configGroup = hwConfig->getConfigGroup();
 
         // This is how far in advance a buffer must be queued for
@@ -1788,8 +1789,8 @@
     // 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]
-                                                : mPreviousPresentFences[1];
+    return mVsyncModulator->getVsyncConfig().sfOffset > 0 ? mPreviousPresentFences[0]
+                                                          : mPreviousPresentFences[1];
 }
 
 bool SurfaceFlinger::previousFramePending(int graceTimeMs) {
@@ -1821,7 +1822,8 @@
     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->getVsyncConfig().sfOffset > 0 ? presentTime
+                                                          : presentTime + stats.vsyncPeriod;
 }
 
 void SurfaceFlinger::onMessageReceived(int32_t what, nsecs_t expectedVSyncTime) {
@@ -2181,12 +2183,12 @@
                                                 nsecs_t compositeToPresentLatency) {
     // Integer division and modulo round toward 0 not -inf, so we need to
     // treat negative and positive offsets differently.
-    nsecs_t idealLatency = (mPhaseConfiguration->getCurrentOffsets().late.sf > 0)
+    nsecs_t idealLatency = (mVsyncConfiguration->getCurrentConfigs().late.sfOffset > 0)
             ? (stats.vsyncPeriod -
-               (mPhaseConfiguration->getCurrentOffsets().late.sf % stats.vsyncPeriod))
-            : ((-mPhaseConfiguration->getCurrentOffsets().late.sf) % stats.vsyncPeriod);
+               (mVsyncConfiguration->getCurrentConfigs().late.sfOffset % stats.vsyncPeriod))
+            : ((-mVsyncConfiguration->getCurrentConfigs().late.sfOffset) % stats.vsyncPeriod);
 
-    // Just in case mPhaseConfiguration->getCurrentOffsets().late.sf == -vsyncInterval.
+    // Just in case mVsyncConfiguration->getCurrentConfigs().late.sf == -vsyncInterval.
     if (idealLatency <= 0) {
         idealLatency = stats.vsyncPeriod;
     }
@@ -2196,7 +2198,7 @@
     // Reducing jitter is important if an app attempts to extrapolate
     // something (such as user input) to an accurate diasplay time.
     // Snapping also allows an app to precisely calculate
-    // mPhaseConfiguration->getCurrentOffsets().late.sf with (presentLatency % interval).
+    // mVsyncConfiguration->getCurrentConfigs().late.sf with (presentLatency % interval).
     nsecs_t bias = stats.vsyncPeriod / 2;
     int64_t extraVsyncs = (compositeToPresentLatency - idealLatency + bias) / stats.vsyncPeriod;
     nsecs_t snappedCompositeToPresentLatency =
@@ -2975,16 +2977,18 @@
                                                           currentConfig, hal::PowerMode::OFF);
     mRefreshRateStats->setConfigMode(currentConfig);
 
-    mPhaseConfiguration = getFactory().createPhaseConfiguration(*mRefreshRateConfigs);
-    mVsyncModulator.emplace(mPhaseConfiguration->getCurrentOffsets());
+    mVsyncConfiguration = getFactory().createVsyncConfiguration(*mRefreshRateConfigs);
+    mVsyncModulator.emplace(mVsyncConfiguration->getCurrentConfigs());
 
     // start the EventThread
     mScheduler = getFactory().createScheduler(*mRefreshRateConfigs, *this);
     mAppConnectionHandle =
-            mScheduler->createConnection("app", mPhaseConfiguration->getCurrentOffsets().late.app,
+            mScheduler->createConnection("app",
+                                         mVsyncConfiguration->getCurrentConfigs().late.appOffset,
                                          impl::EventThread::InterceptVSyncsCallback());
     mSfConnectionHandle =
-            mScheduler->createConnection("sf", mPhaseConfiguration->getCurrentOffsets().late.sf,
+            mScheduler->createConnection("sf",
+                                         mVsyncConfiguration->getCurrentConfigs().late.sfOffset,
                                          [this](nsecs_t timestamp) {
                                              mInterceptor->saveVSyncEvent(timestamp);
                                          });
@@ -3008,13 +3012,13 @@
 }
 
 void SurfaceFlinger::updatePhaseConfiguration(const RefreshRate& refreshRate) {
-    mPhaseConfiguration->setRefreshRateFps(refreshRate.getFps());
-    setPhaseOffsets(mVsyncModulator->setPhaseOffsets(mPhaseConfiguration->getCurrentOffsets()));
+    mVsyncConfiguration->setRefreshRateFps(refreshRate.getFps());
+    setVsyncConfig(mVsyncModulator->setVsyncConfigSet(mVsyncConfiguration->getCurrentConfigs()));
 }
 
-void SurfaceFlinger::setPhaseOffsets(const VsyncModulator::Offsets& offsets) {
-    mScheduler->setPhaseOffset(mAppConnectionHandle, offsets.app);
-    mScheduler->setPhaseOffset(mSfConnectionHandle, offsets.sf);
+void SurfaceFlinger::setVsyncConfig(const VsyncModulator::VsyncConfig& config) {
+    mScheduler->setPhaseOffset(mAppConnectionHandle, config.appOffset);
+    mScheduler->setPhaseOffset(mSfConnectionHandle, config.sfOffset);
 }
 
 void SurfaceFlinger::commitTransaction() {
@@ -4454,7 +4458,7 @@
     mRefreshRateStats->dump(result);
     result.append("\n");
 
-    mPhaseConfiguration->dump(result);
+    mVsyncConfiguration->dump(result);
     StringAppendF(&result,
                   "      present offset: %9" PRId64 " ns\t     VSYNC period: %9" PRId64 " ns\n\n",
                   dispSyncPresentTimeOffset, getVsyncPeriodFromHWC());
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index 3c24158..24837b8 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -608,7 +608,7 @@
 
     void initScheduler(PhysicalDisplayId primaryDisplayId);
     void updatePhaseConfiguration(const RefreshRate&);
-    void setPhaseOffsets(const VsyncModulator::Offsets&);
+    void setVsyncConfig(const VsyncModulator::VsyncConfig&);
 
     /* handlePageFlip - latch a new buffer if available and compute the dirty
      * region. Returns whether a new buffer has been latched, i.e., whether it
@@ -1214,7 +1214,7 @@
     scheduler::ConnectionHandle mSfConnectionHandle;
 
     // Stores phase offsets configured per refresh rate.
-    std::unique_ptr<scheduler::PhaseConfiguration> mPhaseConfiguration;
+    std::unique_ptr<scheduler::VsyncConfiguration> mVsyncConfiguration;
 
     // Optional to defer construction until PhaseConfiguration is created.
     std::optional<scheduler::VsyncModulator> mVsyncModulator;
@@ -1226,10 +1226,10 @@
     hal::Vsync mHWCVsyncPendingState = hal::Vsync::DISABLE;
 
     template <typename... Args,
-              typename Handler = VsyncModulator::OffsetsOpt (VsyncModulator::*)(Args...)>
+              typename Handler = VsyncModulator::VsyncConfigOpt (VsyncModulator::*)(Args...)>
     void modulateVsync(Handler handler, Args... args) {
-        if (const auto offsets = (*mVsyncModulator.*handler)(args...)) {
-            setPhaseOffsets(*offsets);
+        if (const auto config = (*mVsyncModulator.*handler)(args...)) {
+            setVsyncConfig(*config);
         }
     }
 
diff --git a/services/surfaceflinger/SurfaceFlingerDefaultFactory.cpp b/services/surfaceflinger/SurfaceFlingerDefaultFactory.cpp
index f80c804..54ce60e 100644
--- a/services/surfaceflinger/SurfaceFlingerDefaultFactory.cpp
+++ b/services/surfaceflinger/SurfaceFlingerDefaultFactory.cpp
@@ -58,10 +58,10 @@
     return std::make_unique<android::impl::MessageQueue>();
 }
 
-std::unique_ptr<scheduler::PhaseConfiguration> DefaultFactory::createPhaseConfiguration(
+std::unique_ptr<scheduler::VsyncConfiguration> DefaultFactory::createVsyncConfiguration(
         const scheduler::RefreshRateConfigs& refreshRateConfigs) {
     if (property_get_bool("debug.sf.use_phase_offsets_as_durations", false)) {
-        return std::make_unique<scheduler::impl::PhaseDurations>(refreshRateConfigs);
+        return std::make_unique<scheduler::impl::WorkDuration>(refreshRateConfigs);
     } else {
         return std::make_unique<scheduler::impl::PhaseOffsets>(refreshRateConfigs);
     }
diff --git a/services/surfaceflinger/SurfaceFlingerDefaultFactory.h b/services/surfaceflinger/SurfaceFlingerDefaultFactory.h
index 1757fa8..04af8b0 100644
--- a/services/surfaceflinger/SurfaceFlingerDefaultFactory.h
+++ b/services/surfaceflinger/SurfaceFlingerDefaultFactory.h
@@ -29,7 +29,7 @@
     std::unique_ptr<DispSync> createDispSync(const char* name, bool hasSyncFramework) override;
     std::unique_ptr<HWComposer> createHWComposer(const std::string& serviceName) override;
     std::unique_ptr<MessageQueue> createMessageQueue() override;
-    std::unique_ptr<scheduler::PhaseConfiguration> createPhaseConfiguration(
+    std::unique_ptr<scheduler::VsyncConfiguration> createVsyncConfiguration(
             const scheduler::RefreshRateConfigs&) override;
     std::unique_ptr<Scheduler> createScheduler(const scheduler::RefreshRateConfigs&,
                                                ISchedulerCallback&) override;
diff --git a/services/surfaceflinger/SurfaceFlingerFactory.h b/services/surfaceflinger/SurfaceFlingerFactory.h
index 1d710c4..784a77b 100644
--- a/services/surfaceflinger/SurfaceFlingerFactory.h
+++ b/services/surfaceflinger/SurfaceFlingerFactory.h
@@ -55,7 +55,7 @@
 } // namespace compositionengine
 
 namespace scheduler {
-class PhaseConfiguration;
+class VsyncConfiguration;
 class RefreshRateConfigs;
 } // namespace scheduler
 
@@ -70,7 +70,7 @@
     virtual std::unique_ptr<DispSync> createDispSync(const char* name, bool hasSyncFramework) = 0;
     virtual std::unique_ptr<HWComposer> createHWComposer(const std::string& serviceName) = 0;
     virtual std::unique_ptr<MessageQueue> createMessageQueue() = 0;
-    virtual std::unique_ptr<scheduler::PhaseConfiguration> createPhaseConfiguration(
+    virtual std::unique_ptr<scheduler::VsyncConfiguration> createVsyncConfiguration(
             const scheduler::RefreshRateConfigs&) = 0;
     virtual std::unique_ptr<Scheduler> createScheduler(const scheduler::RefreshRateConfigs&,
                                                        ISchedulerCallback&) = 0;
diff --git a/services/surfaceflinger/tests/unittests/FakeVsyncConfiguration.h b/services/surfaceflinger/tests/unittests/FakeVsyncConfiguration.h
index d023b97..4cd1e0a 100644
--- a/services/surfaceflinger/tests/unittests/FakeVsyncConfiguration.h
+++ b/services/surfaceflinger/tests/unittests/FakeVsyncConfiguration.h
@@ -22,15 +22,19 @@
 
 namespace android::scheduler {
 
-struct FakePhaseOffsets : PhaseConfiguration {
+struct FakePhaseOffsets : VsyncConfiguration {
     static constexpr nsecs_t FAKE_PHASE_OFFSET_NS = 0;
+    static constexpr auto FAKE_DURATION_OFFSET_NS = std::chrono::nanoseconds(0);
 
-    Offsets getOffsetsForRefreshRate(float) const override { return getCurrentOffsets(); }
+    VsyncConfigSet getConfigsForRefreshRate(float) const override { return getCurrentConfigs(); }
 
-    Offsets getCurrentOffsets() const override {
-        return {{FAKE_PHASE_OFFSET_NS, FAKE_PHASE_OFFSET_NS},
-                {FAKE_PHASE_OFFSET_NS, FAKE_PHASE_OFFSET_NS},
-                {FAKE_PHASE_OFFSET_NS, FAKE_PHASE_OFFSET_NS}};
+    VsyncConfigSet getCurrentConfigs() const override {
+        return {{FAKE_PHASE_OFFSET_NS, FAKE_PHASE_OFFSET_NS, FAKE_DURATION_OFFSET_NS,
+                 FAKE_DURATION_OFFSET_NS},
+                {FAKE_PHASE_OFFSET_NS, FAKE_PHASE_OFFSET_NS, FAKE_DURATION_OFFSET_NS,
+                 FAKE_DURATION_OFFSET_NS},
+                {FAKE_PHASE_OFFSET_NS, FAKE_PHASE_OFFSET_NS, FAKE_DURATION_OFFSET_NS,
+                 FAKE_DURATION_OFFSET_NS}};
     }
 
     void setRefreshRateFps(float) override {}
diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
index 00fe5ff5..a1049d8 100644
--- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
+++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
@@ -77,7 +77,7 @@
         return std::make_unique<android::impl::MessageQueue>();
     }
 
-    std::unique_ptr<scheduler::PhaseConfiguration> createPhaseConfiguration(
+    std::unique_ptr<scheduler::VsyncConfiguration> createVsyncConfiguration(
             const scheduler::RefreshRateConfigs& /*refreshRateConfigs*/) override {
         return std::make_unique<scheduler::FakePhaseOffsets>();
     }
@@ -216,9 +216,9 @@
                 scheduler::RefreshRateStats>(*mFlinger->mRefreshRateConfigs, *mFlinger->mTimeStats,
                                              /*currentConfig=*/HwcConfigIndexType(0),
                                              /*powerMode=*/hal::PowerMode::OFF);
-        mFlinger->mPhaseConfiguration =
-                mFactory.createPhaseConfiguration(*mFlinger->mRefreshRateConfigs);
-        mFlinger->mVsyncModulator.emplace(mFlinger->mPhaseConfiguration->getCurrentOffsets());
+        mFlinger->mVsyncConfiguration =
+                mFactory.createVsyncConfiguration(*mFlinger->mRefreshRateConfigs);
+        mFlinger->mVsyncModulator.emplace(mFlinger->mVsyncConfiguration->getCurrentConfigs());
 
         constexpr bool kUseContentDetectionV2 = false;
         mScheduler =
diff --git a/services/surfaceflinger/tests/unittests/VsyncConfigurationTest.cpp b/services/surfaceflinger/tests/unittests/VsyncConfigurationTest.cpp
index 0c51cfb..72ee6db 100644
--- a/services/surfaceflinger/tests/unittests/VsyncConfigurationTest.cpp
+++ b/services/surfaceflinger/tests/unittests/VsyncConfigurationTest.cpp
@@ -14,10 +14,6 @@
  * limitations under the License.
  */
 
-// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wconversion"
-
 #undef LOG_TAG
 #define LOG_TAG "SchedulerUnittests"
 
@@ -31,138 +27,280 @@
 
 namespace android::scheduler {
 
-class TestablePhaseOffsetsAsDurations : public impl::PhaseDurations {
+class TestableWorkDuration : public impl::WorkDuration {
 public:
-    TestablePhaseOffsetsAsDurations(float currentFps, nsecs_t sfDuration, nsecs_t appDuration,
-                                    nsecs_t sfEarlyDuration, nsecs_t appEarlyDuration,
-                                    nsecs_t sfEarlyGlDuration, nsecs_t appEarlyGlDuration)
-          : impl::PhaseDurations({60.0f, 90.0f}, currentFps, sfDuration, appDuration,
-                                 sfEarlyDuration, appEarlyDuration, sfEarlyGlDuration,
-                                 appEarlyGlDuration) {}
+    TestableWorkDuration(float currentFps, nsecs_t sfDuration, nsecs_t appDuration,
+                         nsecs_t sfEarlyDuration, nsecs_t appEarlyDuration,
+                         nsecs_t sfEarlyGlDuration, nsecs_t appEarlyGlDuration)
+          : impl::WorkDuration({60.0f, 90.0f}, currentFps, sfDuration, appDuration, sfEarlyDuration,
+                               appEarlyDuration, sfEarlyGlDuration, appEarlyGlDuration) {}
 };
 
-class PhaseDurationTest : public testing::Test {
+class WorkDurationTest : public testing::Test {
 protected:
-    PhaseDurationTest()
-          : mPhaseDurations(60.0f, 10'500'000, 20'500'000, 16'000'000, 16'500'000, 13'500'000,
-                            21'000'000) {}
+    WorkDurationTest()
+          : mWorkDuration(60.0f, 10'500'000, 20'500'000, 16'000'000, 16'500'000, 13'500'000,
+                          21'000'000) {}
 
-    ~PhaseDurationTest() = default;
+    ~WorkDurationTest() = default;
 
-    TestablePhaseOffsetsAsDurations mPhaseDurations;
+    TestableWorkDuration mWorkDuration;
 };
 
 /* ------------------------------------------------------------------------
  * Test cases
  */
-TEST_F(PhaseDurationTest, getOffsetsForRefreshRate_60Hz) {
-    mPhaseDurations.setRefreshRateFps(60.0f);
-    auto currentOffsets = mPhaseDurations.getCurrentOffsets();
-    auto offsets = mPhaseDurations.getOffsetsForRefreshRate(60.0f);
+TEST_F(WorkDurationTest, getConfigsForRefreshRate_60Hz) {
+    mWorkDuration.setRefreshRateFps(60.0f);
+    auto currentOffsets = mWorkDuration.getCurrentConfigs();
+    auto offsets = mWorkDuration.getConfigsForRefreshRate(60.0f);
 
     EXPECT_EQ(currentOffsets, offsets);
-    EXPECT_EQ(offsets.late.sf, 6'166'667);
+    EXPECT_EQ(offsets.late.sfOffset, 6'166'667);
+    EXPECT_EQ(offsets.late.appOffset, 2'333'334);
 
-    EXPECT_EQ(offsets.late.app, 2'333'334);
+    EXPECT_EQ(offsets.late.sfWorkDuration, 10'500'000ns);
+    EXPECT_EQ(offsets.late.appWorkDuration, 20'500'000ns);
 
-    EXPECT_EQ(offsets.early.sf, 666'667);
+    EXPECT_EQ(offsets.early.sfOffset, 666'667);
+    EXPECT_EQ(offsets.early.appOffset, 833'334);
 
-    EXPECT_EQ(offsets.early.app, 833'334);
+    EXPECT_EQ(offsets.early.sfWorkDuration, 16'000'000ns);
+    EXPECT_EQ(offsets.early.appWorkDuration, 16'500'000ns);
 
-    EXPECT_EQ(offsets.earlyGpu.sf, 3'166'667);
+    EXPECT_EQ(offsets.earlyGpu.sfOffset, 3'166'667);
+    EXPECT_EQ(offsets.earlyGpu.appOffset, 15'500'001);
 
-    EXPECT_EQ(offsets.earlyGpu.app, 15'500'001);
+    EXPECT_EQ(offsets.earlyGpu.sfWorkDuration, 13'500'000ns);
+    EXPECT_EQ(offsets.earlyGpu.appWorkDuration, 21'000'000ns);
 }
 
-TEST_F(PhaseDurationTest, getOffsetsForRefreshRate_90Hz) {
-    mPhaseDurations.setRefreshRateFps(90.0f);
-    auto currentOffsets = mPhaseDurations.getCurrentOffsets();
-    auto offsets = mPhaseDurations.getOffsetsForRefreshRate(90.0f);
+TEST_F(WorkDurationTest, getConfigsForRefreshRate_90Hz) {
+    mWorkDuration.setRefreshRateFps(90.0f);
+    auto currentOffsets = mWorkDuration.getCurrentConfigs();
+    auto offsets = mWorkDuration.getConfigsForRefreshRate(90.0f);
 
     EXPECT_EQ(currentOffsets, offsets);
-    EXPECT_EQ(offsets.late.sf, 611'111);
+    EXPECT_EQ(offsets.late.sfOffset, 611'111);
+    EXPECT_EQ(offsets.late.appOffset, 2'333'333);
 
-    EXPECT_EQ(offsets.late.app, 2'333'333);
+    EXPECT_EQ(offsets.late.sfWorkDuration, 10'500'000ns);
+    EXPECT_EQ(offsets.late.appWorkDuration, 20'500'000ns);
 
-    EXPECT_EQ(offsets.early.sf, -4'888'889);
+    EXPECT_EQ(offsets.early.sfOffset, -4'888'889);
+    EXPECT_EQ(offsets.early.appOffset, 833'333);
 
-    EXPECT_EQ(offsets.early.app, 833'333);
+    EXPECT_EQ(offsets.early.sfWorkDuration, 16'000'000ns);
+    EXPECT_EQ(offsets.early.appWorkDuration, 16'500'000ns);
 
-    EXPECT_EQ(offsets.earlyGpu.sf, -2'388'889);
+    EXPECT_EQ(offsets.earlyGpu.sfOffset, -2'388'889);
+    EXPECT_EQ(offsets.earlyGpu.appOffset, 9'944'444);
 
-    EXPECT_EQ(offsets.earlyGpu.app, 9'944'444);
+    EXPECT_EQ(offsets.earlyGpu.sfWorkDuration, 13'500'000ns);
+    EXPECT_EQ(offsets.earlyGpu.appWorkDuration, 21'000'000ns);
 }
 
-TEST_F(PhaseDurationTest, getOffsetsForRefreshRate_DefaultOffsets) {
-    TestablePhaseOffsetsAsDurations phaseOffsetsWithDefaultValues(60.0f, -1, -1, -1, -1, -1, -1);
+TEST_F(WorkDurationTest, getConfigsForRefreshRate_DefaultOffsets) {
+    TestableWorkDuration phaseOffsetsWithDefaultValues(60.0f, -1, -1, -1, -1, -1, -1);
 
-    auto validateOffsets = [](auto& offsets) {
-        EXPECT_EQ(offsets.late.sf, 1'000'000);
+    auto validateOffsets = [](const auto& offsets, std::chrono::nanoseconds vsyncPeriod) {
+        EXPECT_EQ(offsets.late.sfOffset, 1'000'000);
+        EXPECT_EQ(offsets.late.appOffset, 1'000'000);
 
-        EXPECT_EQ(offsets.late.app, 1'000'000);
+        EXPECT_EQ(offsets.late.sfWorkDuration, vsyncPeriod - 1'000'000ns);
+        EXPECT_EQ(offsets.late.appWorkDuration, vsyncPeriod);
 
-        EXPECT_EQ(offsets.early.sf, 1'000'000);
+        EXPECT_EQ(offsets.early.sfOffset, 1'000'000);
+        EXPECT_EQ(offsets.early.appOffset, 1'000'000);
 
-        EXPECT_EQ(offsets.early.app, 1'000'000);
+        EXPECT_EQ(offsets.early.sfWorkDuration, vsyncPeriod - 1'000'000ns);
+        EXPECT_EQ(offsets.early.appWorkDuration, vsyncPeriod);
 
-        EXPECT_EQ(offsets.earlyGpu.sf, 1'000'000);
+        EXPECT_EQ(offsets.earlyGpu.sfOffset, 1'000'000);
+        EXPECT_EQ(offsets.earlyGpu.appOffset, 1'000'000);
 
-        EXPECT_EQ(offsets.earlyGpu.app, 1'000'000);
+        EXPECT_EQ(offsets.earlyGpu.sfWorkDuration, vsyncPeriod - 1'000'000ns);
+        EXPECT_EQ(offsets.earlyGpu.appWorkDuration, vsyncPeriod);
     };
 
-    phaseOffsetsWithDefaultValues.setRefreshRateFps(90.0f);
-    auto currentOffsets = phaseOffsetsWithDefaultValues.getCurrentOffsets();
-    auto offsets = phaseOffsetsWithDefaultValues.getOffsetsForRefreshRate(90.0f);
-    EXPECT_EQ(currentOffsets, offsets);
-    validateOffsets(offsets);
-
-    phaseOffsetsWithDefaultValues.setRefreshRateFps(60.0f);
-    currentOffsets = phaseOffsetsWithDefaultValues.getCurrentOffsets();
-    offsets = phaseOffsetsWithDefaultValues.getOffsetsForRefreshRate(90.0f);
-    EXPECT_EQ(currentOffsets, offsets);
-    validateOffsets(offsets);
-}
-
-TEST_F(PhaseDurationTest, getOffsetsForRefreshRate_unknownRefreshRate) {
-    auto offsets = mPhaseDurations.getOffsetsForRefreshRate(14.7f);
-
-    EXPECT_EQ(offsets.late.sf, 57'527'208);
-
-    EXPECT_EQ(offsets.late.app, 37'027'208);
-
-    EXPECT_EQ(offsets.early.sf, 52'027'208);
-
-    EXPECT_EQ(offsets.early.app, 35'527'208);
-
-    EXPECT_EQ(offsets.earlyGpu.sf, 54'527'208);
-
-    EXPECT_EQ(offsets.earlyGpu.app, 33'527'208);
-}
-
-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};
-        }
+    const auto testForRefreshRate = [&](float refreshRate) {
+        phaseOffsetsWithDefaultValues.setRefreshRateFps(refreshRate);
+        auto currentOffsets = phaseOffsetsWithDefaultValues.getCurrentConfigs();
+        auto offsets = phaseOffsetsWithDefaultValues.getConfigsForRefreshRate(refreshRate);
+        EXPECT_EQ(currentOffsets, offsets);
+        validateOffsets(offsets,
+                        std::chrono::nanoseconds(static_cast<nsecs_t>(1e9f / refreshRate)));
     };
 
-    auto offsets = PhaseOffsets::get().getOffsetsForRefreshRate(14.7f);
+    testForRefreshRate(90.0f);
+    testForRefreshRate(60.0f);
+}
 
-    EXPECT_EQ(offsets.late.sf, 1'000'000);
+TEST_F(WorkDurationTest, getConfigsForRefreshRate_unknownRefreshRate) {
+    auto offsets = mWorkDuration.getConfigsForRefreshRate(14.7f);
 
-    EXPECT_EQ(offsets.late.app, 1'000'000);
+    EXPECT_EQ(offsets.late.sfOffset, 57'527'208);
+    EXPECT_EQ(offsets.late.appOffset, 37'027'208);
 
-    EXPECT_EQ(offsets.early.sf, 1'000'000);
+    EXPECT_EQ(offsets.late.sfWorkDuration, 10'500'000ns);
+    EXPECT_EQ(offsets.late.appWorkDuration, 20'500'000ns);
 
-    EXPECT_EQ(offsets.early.app, 1'000'000);
+    EXPECT_EQ(offsets.early.sfOffset, 52'027'208);
+    EXPECT_EQ(offsets.early.appOffset, 35'527'208);
 
-    EXPECT_EQ(offsets.earlyGpu.sf, 1'000'000);
+    EXPECT_EQ(offsets.early.sfWorkDuration, 16'000'000ns);
+    EXPECT_EQ(offsets.early.appWorkDuration, 16'500'000ns);
 
-    EXPECT_EQ(offsets.earlyGpu.app, 1'000'000);
+    EXPECT_EQ(offsets.earlyGpu.sfOffset, 54'527'208);
+    EXPECT_EQ(offsets.earlyGpu.appOffset, 33'527'208);
+
+    EXPECT_EQ(offsets.earlyGpu.sfWorkDuration, 13'500'000ns);
+    EXPECT_EQ(offsets.earlyGpu.appWorkDuration, 21'000'000ns);
+}
+
+class TestablePhaseOffsets : public impl::PhaseOffsets {
+public:
+    TestablePhaseOffsets(nsecs_t vsyncPhaseOffsetNs, nsecs_t sfVSyncPhaseOffsetNs,
+                         std::optional<nsecs_t> earlySfOffsetNs,
+                         std::optional<nsecs_t> earlyGpuSfOffsetNs,
+                         std::optional<nsecs_t> earlyAppOffsetNs,
+                         std::optional<nsecs_t> earlyGpuAppOffsetNs,
+                         nsecs_t highFpsVsyncPhaseOffsetNs, nsecs_t highFpsSfVSyncPhaseOffsetNs,
+                         std::optional<nsecs_t> highFpsEarlySfOffsetNs,
+                         std::optional<nsecs_t> highFpsEarlyGpuSfOffsetNs,
+                         std::optional<nsecs_t> highFpsEarlyAppOffsetNs,
+                         std::optional<nsecs_t> highFpsEarlyGpuAppOffsetNs,
+                         nsecs_t thresholdForNextVsync)
+          : impl::PhaseOffsets({60.0f, 90.0f}, 60.0f, vsyncPhaseOffsetNs, sfVSyncPhaseOffsetNs,
+                               earlySfOffsetNs, earlyGpuSfOffsetNs, earlyAppOffsetNs,
+                               earlyGpuAppOffsetNs, highFpsVsyncPhaseOffsetNs,
+                               highFpsSfVSyncPhaseOffsetNs, highFpsEarlySfOffsetNs,
+                               highFpsEarlyGpuSfOffsetNs, highFpsEarlyAppOffsetNs,
+                               highFpsEarlyGpuAppOffsetNs, thresholdForNextVsync) {}
+};
+
+class PhaseOffsetsTest : public testing::Test {
+protected:
+    PhaseOffsetsTest() = default;
+    ~PhaseOffsetsTest() = default;
+
+    TestablePhaseOffsets mPhaseOffsets{2'000'000, 6'000'000, 7'000'000, 8'000'000, 3'000'000,
+                                       4'000'000, 2'000'000, 1'000'000, 2'000'000, 3'000'000,
+                                       3'000'000, 4'000'000, 10'000'000};
+};
+
+TEST_F(PhaseOffsetsTest, getConfigsForRefreshRate_unknownRefreshRate) {
+    auto offsets = mPhaseOffsets.getConfigsForRefreshRate(14.7f);
+
+    EXPECT_EQ(offsets.late.sfOffset, 6'000'000);
+    EXPECT_EQ(offsets.late.appOffset, 2'000'000);
+
+    EXPECT_EQ(offsets.late.sfWorkDuration, 62'027'208ns);
+    EXPECT_EQ(offsets.late.appWorkDuration, 72'027'208ns);
+
+    EXPECT_EQ(offsets.early.sfOffset, 7'000'000);
+    EXPECT_EQ(offsets.early.appOffset, 3'000'000);
+
+    EXPECT_EQ(offsets.early.sfWorkDuration, 61'027'208ns);
+    EXPECT_EQ(offsets.early.appWorkDuration, 72'027'208ns);
+
+    EXPECT_EQ(offsets.earlyGpu.sfOffset, 8'000'000);
+    EXPECT_EQ(offsets.earlyGpu.appOffset, 4'000'000);
+
+    EXPECT_EQ(offsets.earlyGpu.sfWorkDuration, 60'027'208ns);
+    EXPECT_EQ(offsets.earlyGpu.appWorkDuration, 72'027'208ns);
+}
+
+TEST_F(PhaseOffsetsTest, getConfigsForRefreshRate_60Hz) {
+    auto offsets = mPhaseOffsets.getConfigsForRefreshRate(60.0f);
+
+    EXPECT_EQ(offsets.late.sfOffset, 6'000'000);
+    EXPECT_EQ(offsets.late.appOffset, 2'000'000);
+
+    EXPECT_EQ(offsets.late.sfWorkDuration, 10'666'667ns);
+    EXPECT_EQ(offsets.late.appWorkDuration, 20'666'667ns);
+
+    EXPECT_EQ(offsets.early.sfOffset, 7'000'000);
+    EXPECT_EQ(offsets.early.appOffset, 3'000'000);
+
+    EXPECT_EQ(offsets.early.sfWorkDuration, 9'666'667ns);
+    EXPECT_EQ(offsets.early.appWorkDuration, 20'666'667ns);
+
+    EXPECT_EQ(offsets.earlyGpu.sfOffset, 8'000'000);
+    EXPECT_EQ(offsets.earlyGpu.appOffset, 4'000'000);
+
+    EXPECT_EQ(offsets.earlyGpu.sfWorkDuration, 8'666'667ns);
+    EXPECT_EQ(offsets.earlyGpu.appWorkDuration, 20'666'667ns);
+}
+
+TEST_F(PhaseOffsetsTest, getConfigsForRefreshRate_90Hz) {
+    auto offsets = mPhaseOffsets.getConfigsForRefreshRate(90.0f);
+
+    EXPECT_EQ(offsets.late.sfOffset, 1'000'000);
+    EXPECT_EQ(offsets.late.appOffset, 2'000'000);
+
+    EXPECT_EQ(offsets.late.sfWorkDuration, 10'111'111ns);
+    EXPECT_EQ(offsets.late.appWorkDuration, 21'222'222ns);
+
+    EXPECT_EQ(offsets.early.sfOffset, 2'000'000);
+    EXPECT_EQ(offsets.early.appOffset, 3'000'000);
+
+    EXPECT_EQ(offsets.early.sfWorkDuration, 9'111'111ns);
+    EXPECT_EQ(offsets.early.appWorkDuration, 21'222'222ns);
+
+    EXPECT_EQ(offsets.earlyGpu.sfOffset, 3'000'000);
+    EXPECT_EQ(offsets.earlyGpu.appOffset, 4'000'000);
+
+    EXPECT_EQ(offsets.earlyGpu.sfWorkDuration, 8'111'111ns);
+    EXPECT_EQ(offsets.earlyGpu.appWorkDuration, 21'222'222ns);
+}
+
+TEST_F(PhaseOffsetsTest, getConfigsForRefreshRate_DefaultValues_60Hz) {
+    TestablePhaseOffsets phaseOffsets{1'000'000, 1'000'000, {}, {}, {}, {},        2'000'000,
+                                      1'000'000, {},        {}, {}, {}, 10'000'000};
+    auto offsets = phaseOffsets.getConfigsForRefreshRate(60.0f);
+
+    EXPECT_EQ(offsets.late.sfOffset, 1'000'000);
+    EXPECT_EQ(offsets.late.appOffset, 1'000'000);
+
+    EXPECT_EQ(offsets.late.sfWorkDuration, 15'666'667ns);
+    EXPECT_EQ(offsets.late.appWorkDuration, 16'666'667ns);
+
+    EXPECT_EQ(offsets.early.sfOffset, 1'000'000);
+    EXPECT_EQ(offsets.early.appOffset, 1'000'000);
+
+    EXPECT_EQ(offsets.early.sfWorkDuration, 15'666'667ns);
+    EXPECT_EQ(offsets.early.appWorkDuration, 16'666'667ns);
+
+    EXPECT_EQ(offsets.earlyGpu.sfOffset, 1'000'000);
+    EXPECT_EQ(offsets.earlyGpu.appOffset, 1'000'000);
+
+    EXPECT_EQ(offsets.earlyGpu.sfWorkDuration, 15'666'667ns);
+    EXPECT_EQ(offsets.earlyGpu.appWorkDuration, 16'666'667ns);
+}
+
+TEST_F(PhaseOffsetsTest, getConfigsForRefreshRate_DefaultValues_90Hz) {
+    TestablePhaseOffsets phaseOffsets{1'000'000, 1'000'000, {}, {}, {}, {},        2'000'000,
+                                      1'000'000, {},        {}, {}, {}, 10'000'000};
+    auto offsets = phaseOffsets.getConfigsForRefreshRate(90.0f);
+
+    EXPECT_EQ(offsets.late.sfOffset, 1'000'000);
+    EXPECT_EQ(offsets.late.appOffset, 2'000'000);
+
+    EXPECT_EQ(offsets.late.sfWorkDuration, 10'111'111ns);
+    EXPECT_EQ(offsets.late.appWorkDuration, 21'222'222ns);
+
+    EXPECT_EQ(offsets.early.sfOffset, 1'000'000);
+    EXPECT_EQ(offsets.early.appOffset, 2'000'000);
+
+    EXPECT_EQ(offsets.early.sfWorkDuration, 10'111'111ns);
+    EXPECT_EQ(offsets.early.appWorkDuration, 21'222'222ns);
+
+    EXPECT_EQ(offsets.earlyGpu.sfOffset, 1'000'000);
+    EXPECT_EQ(offsets.earlyGpu.appOffset, 2'000'000);
+
+    EXPECT_EQ(offsets.earlyGpu.sfWorkDuration, 10'111'111ns);
+    EXPECT_EQ(offsets.earlyGpu.appWorkDuration, 21'222'222ns);
 }
 
 } // 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/tests/unittests/VsyncModulatorTest.cpp b/services/surfaceflinger/tests/unittests/VsyncModulatorTest.cpp
index d7093c6..106da81 100644
--- a/services/surfaceflinger/tests/unittests/VsyncModulatorTest.cpp
+++ b/services/surfaceflinger/tests/unittests/VsyncModulatorTest.cpp
@@ -22,13 +22,19 @@
 namespace android::scheduler {
 
 class VsyncModulatorTest : public testing::Test {
-    enum Offsets {
-        SF_LATE,
-        APP_LATE,
-        SF_EARLY,
-        APP_EARLY,
-        SF_EARLY_GPU,
-        APP_EARLY_GPU,
+    enum {
+        SF_OFFSET_LATE,
+        APP_OFFSET_LATE,
+        SF_DURATION_LATE,
+        APP_DURATION_LATE,
+        SF_OFFSET_EARLY,
+        APP_OFFSET_EARLY,
+        SF_DURATION_EARLY,
+        APP_DURATION_EARLY,
+        SF_OFFSET_EARLY_GPU,
+        APP_OFFSET_EARLY_GPU,
+        SF_DURATION_EARLY_GPU,
+        APP_DURATION_EARLY_GPU,
     };
 
     static VsyncModulator::TimePoint Now() {
@@ -41,25 +47,30 @@
             VsyncModulator::MIN_EARLY_TRANSACTION_FRAMES;
 
     using Schedule = scheduler::TransactionSchedule;
+    using nanos = std::chrono::nanoseconds;
+    const VsyncModulator::VsyncConfig kEarly{SF_OFFSET_EARLY, APP_OFFSET_EARLY,
+                                             nanos(SF_DURATION_LATE), nanos(APP_DURATION_LATE)};
+    const VsyncModulator::VsyncConfig kEarlyGpu{SF_OFFSET_EARLY_GPU, APP_OFFSET_EARLY_GPU,
+                                                nanos(SF_DURATION_EARLY),
+                                                nanos(APP_DURATION_EARLY)};
+    const VsyncModulator::VsyncConfig kLate{SF_OFFSET_LATE, APP_OFFSET_LATE,
+                                            nanos(SF_DURATION_EARLY_GPU),
+                                            nanos(APP_DURATION_EARLY_GPU)};
 
-    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};
+    const VsyncModulator::VsyncConfigSet mOffsets = {kEarly, kEarlyGpu, kLate};
     VsyncModulator mVsyncModulator{mOffsets, Now};
 
-    void SetUp() override { EXPECT_EQ(kLate, mVsyncModulator.setPhaseOffsets(mOffsets)); }
+    void SetUp() override { EXPECT_EQ(kLate, mVsyncModulator.setVsyncConfigSet(mOffsets)); }
 };
 
-#define CHECK_COMMIT(result, offsets)                         \
+#define CHECK_COMMIT(result, configs)                         \
     EXPECT_EQ(result, mVsyncModulator.onTransactionCommit()); \
-    EXPECT_EQ(offsets, mVsyncModulator.getOffsets());
+    EXPECT_EQ(configs, mVsyncModulator.getVsyncConfig());
 
-#define CHECK_REFRESH(N, result, offsets)                           \
+#define CHECK_REFRESH(N, result, configs)                           \
     for (int i = 0; i < N; i++) {                                   \
         EXPECT_EQ(result, mVsyncModulator.onDisplayRefresh(false)); \
-        EXPECT_EQ(offsets, mVsyncModulator.getOffsets());           \
+        EXPECT_EQ(configs, mVsyncModulator.getVsyncConfig());       \
     }
 
 TEST_F(VsyncModulatorTest, Late) {