Merge "SF: Remove EventControlThread"
diff --git a/services/surfaceflinger/Android.bp b/services/surfaceflinger/Android.bp
index 804a3c3..c55e9df 100644
--- a/services/surfaceflinger/Android.bp
+++ b/services/surfaceflinger/Android.bp
@@ -162,7 +162,6 @@
         "RenderArea.cpp",
         "Scheduler/DispSync.cpp",
         "Scheduler/DispSyncSource.cpp",
-        "Scheduler/EventControlThread.cpp",
         "Scheduler/EventThread.cpp",
         "Scheduler/OneShotTimer.cpp",
         "Scheduler/LayerHistory.cpp",
diff --git a/services/surfaceflinger/DisplayHardware/HWC2.h b/services/surfaceflinger/DisplayHardware/HWC2.h
index 6819ff4..12f26f6 100644
--- a/services/surfaceflinger/DisplayHardware/HWC2.h
+++ b/services/surfaceflinger/DisplayHardware/HWC2.h
@@ -14,8 +14,7 @@
  * limitations under the License.
  */
 
-#ifndef ANDROID_SF_HWC2_H
-#define ANDROID_SF_HWC2_H
+#pragma once
 
 #include <gui/HdrMetadata.h>
 #include <math/mat4.h>
@@ -36,15 +35,16 @@
 #include "Hal.h"
 
 namespace android {
-    struct DisplayedFrameStats;
-    class Fence;
-    class FloatRect;
-    class GraphicBuffer;
-    namespace Hwc2 {
-        class Composer;
-    }
 
-    class TestableSurfaceFlinger;
+class Fence;
+class FloatRect;
+class GraphicBuffer;
+class TestableSurfaceFlinger;
+struct DisplayedFrameStats;
+
+namespace Hwc2 {
+class Composer;
+} // namespace Hwc2
 
 namespace HWC2 {
 
@@ -61,19 +61,17 @@
 // All calls receive a sequenceId, which will be the value that was supplied to
 // HWC2::Device::registerCallback(). It's used to help differentiate callbacks
 // from different hardware composer instances.
-class ComposerCallback {
- public:
-     virtual void onHotplugReceived(int32_t sequenceId, hal::HWDisplayId display,
-                                    hal::Connection connection) = 0;
-     virtual void onRefreshReceived(int32_t sequenceId, hal::HWDisplayId display) = 0;
-     virtual void onVsyncReceived(int32_t sequenceId, hal::HWDisplayId display, int64_t timestamp,
-                                  std::optional<hal::VsyncPeriodNanos> vsyncPeriod) = 0;
-     virtual void onVsyncPeriodTimingChangedReceived(
-             int32_t sequenceId, hal::HWDisplayId display,
-             const hal::VsyncPeriodChangeTimeline& updatedTimeline) = 0;
-     virtual void onSeamlessPossible(int32_t sequenceId, hal::HWDisplayId display) = 0;
+struct ComposerCallback {
+    virtual void onHotplugReceived(int32_t sequenceId, hal::HWDisplayId, hal::Connection) = 0;
+    virtual void onRefreshReceived(int32_t sequenceId, hal::HWDisplayId) = 0;
+    virtual void onVsyncReceived(int32_t sequenceId, hal::HWDisplayId, int64_t timestamp,
+                                 std::optional<hal::VsyncPeriodNanos>) = 0;
+    virtual void onVsyncPeriodTimingChangedReceived(int32_t sequenceId, hal::HWDisplayId,
+                                                    const hal::VsyncPeriodChangeTimeline&) = 0;
+    virtual void onSeamlessPossible(int32_t sequenceId, hal::HWDisplayId) = 0;
 
-     virtual ~ComposerCallback() = default;
+protected:
+    ~ComposerCallback() = default;
 };
 
 // Convenience C++ class to access per display functions directly.
@@ -454,5 +452,3 @@
 } // namespace impl
 } // namespace HWC2
 } // namespace android
-
-#endif // ANDROID_SF_HWC2_H
diff --git a/services/surfaceflinger/Scheduler/EventControlThread.cpp b/services/surfaceflinger/Scheduler/EventControlThread.cpp
deleted file mode 100644
index 7f9db9c..0000000
--- a/services/surfaceflinger/Scheduler/EventControlThread.cpp
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wconversion"
-
-#include <pthread.h>
-#include <sched.h>
-#include <sys/resource.h>
-
-#include <cutils/sched_policy.h>
-#include <log/log.h>
-#include <system/thread_defs.h>
-
-#include "EventControlThread.h"
-
-namespace android {
-
-EventControlThread::~EventControlThread() = default;
-
-namespace impl {
-
-EventControlThread::EventControlThread(EventControlThread::SetVSyncEnabledFunction function)
-      : mSetVSyncEnabled(std::move(function)) {
-    pthread_setname_np(mThread.native_handle(), "EventControlThread");
-
-    pid_t tid = pthread_gettid_np(mThread.native_handle());
-    setpriority(PRIO_PROCESS, tid, ANDROID_PRIORITY_URGENT_DISPLAY);
-    set_sched_policy(tid, SP_FOREGROUND);
-}
-
-EventControlThread::~EventControlThread() {
-    {
-        std::lock_guard<std::mutex> lock(mMutex);
-        mKeepRunning = false;
-        mCondition.notify_all();
-    }
-    mThread.join();
-}
-
-void EventControlThread::setVsyncEnabled(bool enabled) {
-    std::lock_guard<std::mutex> lock(mMutex);
-    mVsyncEnabled = enabled;
-    mCondition.notify_all();
-}
-
-// Unfortunately std::unique_lock gives warnings with -Wthread-safety
-void EventControlThread::threadMain() NO_THREAD_SAFETY_ANALYSIS {
-    auto keepRunning = true;
-    auto currentVsyncEnabled = false;
-
-    while (keepRunning) {
-        mSetVSyncEnabled(currentVsyncEnabled);
-
-        std::unique_lock<std::mutex> lock(mMutex);
-        mCondition.wait(lock, [this, currentVsyncEnabled, keepRunning]() NO_THREAD_SAFETY_ANALYSIS {
-            return currentVsyncEnabled != mVsyncEnabled || keepRunning != mKeepRunning;
-        });
-        currentVsyncEnabled = mVsyncEnabled;
-        keepRunning = mKeepRunning;
-    }
-}
-
-} // namespace impl
-} // namespace android
-
-// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic pop // ignored "-Wconversion"
diff --git a/services/surfaceflinger/Scheduler/EventControlThread.h b/services/surfaceflinger/Scheduler/EventControlThread.h
deleted file mode 100644
index cafae53..0000000
--- a/services/surfaceflinger/Scheduler/EventControlThread.h
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#pragma once
-
-#include <condition_variable>
-#include <cstddef>
-#include <functional>
-#include <mutex>
-#include <thread>
-
-#include <android-base/thread_annotations.h>
-
-namespace android {
-
-class EventControlThread {
-public:
-    virtual ~EventControlThread();
-
-    virtual void setVsyncEnabled(bool enabled) = 0;
-};
-
-namespace impl {
-
-class EventControlThread final : public android::EventControlThread {
-public:
-    using SetVSyncEnabledFunction = std::function<void(bool)>;
-
-    explicit EventControlThread(SetVSyncEnabledFunction function);
-    ~EventControlThread();
-
-    // EventControlThread implementation
-    void setVsyncEnabled(bool enabled) override;
-
-private:
-    void threadMain();
-
-    std::mutex mMutex;
-    std::condition_variable mCondition;
-
-    const SetVSyncEnabledFunction mSetVSyncEnabled;
-    bool mVsyncEnabled GUARDED_BY(mMutex) = false;
-    bool mKeepRunning GUARDED_BY(mMutex) = true;
-
-    // Must be last so that everything is initialized before the thread starts.
-    std::thread mThread{&EventControlThread::threadMain, this};
-};
-
-} // namespace impl
-} // namespace android
diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp
index b53e2dc..4f4c1d0 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.cpp
+++ b/services/surfaceflinger/Scheduler/Scheduler.cpp
@@ -20,11 +20,11 @@
 
 #include "Scheduler.h"
 
+#include <android-base/properties.h>
 #include <android-base/stringprintf.h>
 #include <android/hardware/configstore/1.0/ISurfaceFlingerConfigs.h>
 #include <android/hardware/configstore/1.1/ISurfaceFlingerConfigs.h>
 #include <configstore/Utils.h>
-#include <cutils/properties.h>
 #include <input/InputWindow.h>
 #include <system/window.h>
 #include <ui/DisplayStatInfo.h>
@@ -41,7 +41,6 @@
 #include "../Layer.h"
 #include "DispSync.h"
 #include "DispSyncSource.h"
-#include "EventControlThread.h"
 #include "EventThread.h"
 #include "InjectVSyncSource.h"
 #include "OneShotTimer.h"
@@ -60,54 +59,35 @@
         }                                                            \
     } while (false)
 
+using namespace std::string_literals;
+
 namespace android {
 
 namespace {
 
 std::unique_ptr<scheduler::VSyncTracker> createVSyncTracker() {
-    // TODO (144707443) tune Predictor tunables.
-    static constexpr int default_rate = 60;
-    static constexpr auto initial_period =
-            std::chrono::duration<nsecs_t, std::ratio<1, default_rate>>(1);
-    static constexpr size_t vsyncTimestampHistorySize = 20;
-    static constexpr size_t minimumSamplesForPrediction = 6;
-    static constexpr uint32_t discardOutlierPercent = 20;
-    return std::make_unique<
-            scheduler::VSyncPredictor>(std::chrono::duration_cast<std::chrono::nanoseconds>(
-                                               initial_period)
-                                               .count(),
-                                       vsyncTimestampHistorySize, minimumSamplesForPrediction,
-                                       discardOutlierPercent);
+    // TODO(b/144707443): Tune constants.
+    constexpr int kDefaultRate = 60;
+    constexpr auto initialPeriod = std::chrono::duration<nsecs_t, std::ratio<1, kDefaultRate>>(1);
+    constexpr nsecs_t idealPeriod =
+            std::chrono::duration_cast<std::chrono::nanoseconds>(initialPeriod).count();
+    constexpr size_t vsyncTimestampHistorySize = 20;
+    constexpr size_t minimumSamplesForPrediction = 6;
+    constexpr uint32_t discardOutlierPercent = 20;
+    return std::make_unique<scheduler::VSyncPredictor>(idealPeriod, vsyncTimestampHistorySize,
+                                                       minimumSamplesForPrediction,
+                                                       discardOutlierPercent);
 }
 
-std::unique_ptr<scheduler::VSyncDispatch> createVSyncDispatch(
-        const std::unique_ptr<scheduler::VSyncTracker>& vSyncTracker) {
-    if (!vSyncTracker) return {};
-
-    // TODO (144707443) tune Predictor tunables.
-    static constexpr auto vsyncMoveThreshold =
-            std::chrono::duration_cast<std::chrono::nanoseconds>(3ms);
-    static constexpr auto timerSlack = std::chrono::duration_cast<std::chrono::nanoseconds>(500us);
+std::unique_ptr<scheduler::VSyncDispatch> createVSyncDispatch(scheduler::VSyncTracker& tracker) {
+    // TODO(b/144707443): Tune constants.
+    constexpr std::chrono::nanoseconds vsyncMoveThreshold = 3ms;
+    constexpr std::chrono::nanoseconds timerSlack = 500us;
     return std::make_unique<
-            scheduler::VSyncDispatchTimerQueue>(std::make_unique<scheduler::Timer>(), *vSyncTracker,
+            scheduler::VSyncDispatchTimerQueue>(std::make_unique<scheduler::Timer>(), tracker,
                                                 timerSlack.count(), vsyncMoveThreshold.count());
 }
 
-std::unique_ptr<DispSync> createDispSync(
-        const std::unique_ptr<scheduler::VSyncTracker>& vSyncTracker,
-        const std::unique_ptr<scheduler::VSyncDispatch>& vSyncDispatch, bool supportKernelTimer) {
-    if (vSyncTracker && vSyncDispatch) {
-        // TODO (144707443) tune Predictor tunables.
-        static constexpr size_t pendingFenceLimit = 20;
-        return std::make_unique<scheduler::VSyncReactor>(std::make_unique<scheduler::SystemClock>(),
-                                                         *vSyncDispatch, *vSyncTracker,
-                                                         pendingFenceLimit, supportKernelTimer);
-    } else {
-        return std::make_unique<impl::DispSync>("SchedulerDispSync",
-                                                sysprop::running_without_sync_framework(true));
-    }
-}
-
 const char* toContentDetectionString(bool useContentDetection, bool useContentDetectionV2) {
     if (!useContentDetection) return "off";
     return useContentDetectionV2 ? "V2" : "V1";
@@ -115,29 +95,26 @@
 
 } // namespace
 
-Scheduler::Scheduler(impl::EventControlThread::SetVSyncEnabledFunction function,
-                     const scheduler::RefreshRateConfigs& refreshRateConfigs,
-                     ISchedulerCallback& schedulerCallback, bool useContentDetectionV2,
-                     bool useContentDetection)
-      : mSupportKernelTimer(sysprop::support_kernel_idle_timer(false)),
-        // TODO (140302863) remove this and use the vsync_reactor system.
-        mUseVsyncPredictor(property_get_bool("debug.sf.vsync_reactor", true)),
-        mVSyncTracker(mUseVsyncPredictor ? createVSyncTracker() : nullptr),
-        mVSyncDispatch(createVSyncDispatch(mVSyncTracker)),
-        mPrimaryDispSync(createDispSync(mVSyncTracker, mVSyncDispatch, mSupportKernelTimer)),
-        mEventControlThread(new impl::EventControlThread(std::move(function))),
-        mLayerHistory(createLayerHistory(refreshRateConfigs, useContentDetectionV2)),
-        mSchedulerCallback(schedulerCallback),
-        mRefreshRateConfigs(refreshRateConfigs),
-        mUseContentDetection(useContentDetection),
-        mUseContentDetectionV2(useContentDetectionV2) {
+Scheduler::Scheduler(const scheduler::RefreshRateConfigs& configs, ISchedulerCallback& callback)
+      : Scheduler(configs, callback,
+                  {.supportKernelTimer = sysprop::support_kernel_idle_timer(false),
+                   .useContentDetection = sysprop::use_content_detection_for_refresh_rate(false),
+                   .useContentDetectionV2 =
+                           base::GetBoolProperty("debug.sf.use_content_detection_v2"s, true),
+                   // TODO(b/140302863): Remove this and use the vsync_reactor system.
+                   .useVsyncPredictor = base::GetBoolProperty("debug.sf.vsync_reactor"s, true)}) {}
+
+Scheduler::Scheduler(const scheduler::RefreshRateConfigs& configs, ISchedulerCallback& callback,
+                     Options options)
+      : Scheduler(createVsyncSchedule(options), configs, callback,
+                  createLayerHistory(configs, options.useContentDetectionV2), options) {
     using namespace sysprop;
 
-    const int setIdleTimerMs = property_get_int32("debug.sf.set_idle_timer_ms", 0);
+    const int setIdleTimerMs = base::GetIntProperty("debug.sf.set_idle_timer_ms"s, 0);
 
     if (const auto millis = setIdleTimerMs ? setIdleTimerMs : set_idle_timer_ms(0); millis > 0) {
-        const auto callback = mSupportKernelTimer ? &Scheduler::kernelIdleTimerCallback
-                                                  : &Scheduler::idleTimerCallback;
+        const auto callback = mOptions.supportKernelTimer ? &Scheduler::kernelIdleTimerCallback
+                                                          : &Scheduler::idleTimerCallback;
         mIdleTimer.emplace(
                 std::chrono::milliseconds(millis),
                 [this, callback] { std::invoke(callback, this, TimerState::Reset); },
@@ -163,21 +140,16 @@
     }
 }
 
-Scheduler::Scheduler(std::unique_ptr<DispSync> primaryDispSync,
-                     std::unique_ptr<EventControlThread> eventControlThread,
-                     const scheduler::RefreshRateConfigs& configs,
+Scheduler::Scheduler(VsyncSchedule schedule, const scheduler::RefreshRateConfigs& configs,
                      ISchedulerCallback& schedulerCallback,
-                     std::unique_ptr<LayerHistory> layerHistory, bool useContentDetectionV2,
-                     bool useContentDetection)
-      : mSupportKernelTimer(false),
-        mUseVsyncPredictor(true),
-        mPrimaryDispSync(std::move(primaryDispSync)),
-        mEventControlThread(std::move(eventControlThread)),
+                     std::unique_ptr<LayerHistory> layerHistory, Options options)
+      : mOptions(options),
+        mVsyncSchedule(std::move(schedule)),
         mLayerHistory(std::move(layerHistory)),
         mSchedulerCallback(schedulerCallback),
-        mRefreshRateConfigs(configs),
-        mUseContentDetection(useContentDetection),
-        mUseContentDetectionV2(useContentDetectionV2) {}
+        mRefreshRateConfigs(configs) {
+    mSchedulerCallback.setVsyncEnabled(false);
+}
 
 Scheduler::~Scheduler() {
     // Ensure the OneShotTimer threads are joined before we start destroying state.
@@ -186,6 +158,25 @@
     mIdleTimer.reset();
 }
 
+Scheduler::VsyncSchedule Scheduler::createVsyncSchedule(Options options) {
+    if (!options.useVsyncPredictor) {
+        auto sync = std::make_unique<impl::DispSync>("SchedulerDispSync",
+                                                     sysprop::running_without_sync_framework(true));
+        return {std::move(sync), nullptr, nullptr};
+    }
+
+    auto clock = std::make_unique<scheduler::SystemClock>();
+    auto tracker = createVSyncTracker();
+    auto dispatch = createVSyncDispatch(*tracker);
+
+    // TODO(b/144707443): Tune constants.
+    constexpr size_t pendingFenceLimit = 20;
+    auto sync = std::make_unique<scheduler::VSyncReactor>(std::move(clock), *dispatch, *tracker,
+                                                          pendingFenceLimit,
+                                                          options.supportKernelTimer);
+    return {std::move(sync), std::move(tracker), std::move(dispatch)};
+}
+
 std::unique_ptr<LayerHistory> Scheduler::createLayerHistory(
         const scheduler::RefreshRateConfigs& configs, bool useContentDetectionV2) {
     if (!configs.canSwitch()) return nullptr;
@@ -198,12 +189,12 @@
 }
 
 DispSync& Scheduler::getPrimaryDispSync() {
-    return *mPrimaryDispSync;
+    return *mVsyncSchedule.sync;
 }
 
 std::unique_ptr<VSyncSource> Scheduler::makePrimaryDispSyncSource(const char* name,
                                                                   nsecs_t phaseOffsetNs) {
-    return std::make_unique<DispSyncSource>(mPrimaryDispSync.get(), phaseOffsetNs,
+    return std::make_unique<DispSyncSource>(&getPrimaryDispSync(), phaseOffsetNs,
                                             true /* traceVsync */, name);
 }
 
@@ -309,8 +300,8 @@
 }
 
 void Scheduler::getDisplayStatInfo(DisplayStatInfo* stats) {
-    stats->vsyncTime = mPrimaryDispSync->computeNextRefresh(0, systemTime());
-    stats->vsyncPeriod = mPrimaryDispSync->getPeriod();
+    stats->vsyncTime = getPrimaryDispSync().computeNextRefresh(0, systemTime());
+    stats->vsyncPeriod = getPrimaryDispSync().getPeriod();
 }
 
 Scheduler::ConnectionHandle Scheduler::enableVSyncInjection(bool enable) {
@@ -347,8 +338,8 @@
 void Scheduler::enableHardwareVsync() {
     std::lock_guard<std::mutex> lock(mHWVsyncLock);
     if (!mPrimaryHWVsyncEnabled && mHWVsyncAvailable) {
-        mPrimaryDispSync->beginResync();
-        mEventControlThread->setVsyncEnabled(true);
+        getPrimaryDispSync().beginResync();
+        mSchedulerCallback.setVsyncEnabled(true);
         mPrimaryHWVsyncEnabled = true;
     }
 }
@@ -356,8 +347,8 @@
 void Scheduler::disableHardwareVsync(bool makeUnavailable) {
     std::lock_guard<std::mutex> lock(mHWVsyncLock);
     if (mPrimaryHWVsyncEnabled) {
-        mEventControlThread->setVsyncEnabled(false);
-        mPrimaryDispSync->endResync();
+        mSchedulerCallback.setVsyncEnabled(false);
+        getPrimaryDispSync().endResync();
         mPrimaryHWVsyncEnabled = false;
     }
     if (makeUnavailable) {
@@ -397,11 +388,11 @@
 
 void Scheduler::setVsyncPeriod(nsecs_t period) {
     std::lock_guard<std::mutex> lock(mHWVsyncLock);
-    mPrimaryDispSync->setPeriod(period);
+    getPrimaryDispSync().setPeriod(period);
 
     if (!mPrimaryHWVsyncEnabled) {
-        mPrimaryDispSync->beginResync();
-        mEventControlThread->setVsyncEnabled(true);
+        getPrimaryDispSync().beginResync();
+        mSchedulerCallback.setVsyncEnabled(true);
         mPrimaryHWVsyncEnabled = true;
     }
 }
@@ -414,7 +405,7 @@
         std::lock_guard<std::mutex> lock(mHWVsyncLock);
         if (mPrimaryHWVsyncEnabled) {
             needsHwVsync =
-                    mPrimaryDispSync->addResyncSample(timestamp, hwcVsyncPeriod, periodFlushed);
+                    getPrimaryDispSync().addResyncSample(timestamp, hwcVsyncPeriod, periodFlushed);
         }
     }
 
@@ -426,7 +417,7 @@
 }
 
 void Scheduler::addPresentFence(const std::shared_ptr<FenceTime>& fenceTime) {
-    if (mPrimaryDispSync->addPresentFence(fenceTime)) {
+    if (getPrimaryDispSync().addPresentFence(fenceTime)) {
         enableHardwareVsync();
     } else {
         disableHardwareVsync(false);
@@ -434,11 +425,11 @@
 }
 
 void Scheduler::setIgnorePresentFences(bool ignore) {
-    mPrimaryDispSync->setIgnorePresentFences(ignore);
+    getPrimaryDispSync().setIgnorePresentFences(ignore);
 }
 
 nsecs_t Scheduler::getDispSyncExpectedPresentTime(nsecs_t now) {
-    return mPrimaryDispSync->expectedPresentTime(now);
+    return getPrimaryDispSync().expectedPresentTime(now);
 }
 
 void Scheduler::registerLayer(Layer* layer) {
@@ -450,13 +441,13 @@
     if (layer->getWindowType() == InputWindowInfo::Type::STATUS_BAR) {
         mLayerHistory->registerLayer(layer, minFps, maxFps,
                                      scheduler::LayerHistory::LayerVoteType::NoVote);
-    } else if (!mUseContentDetection) {
+    } else if (!mOptions.useContentDetection) {
         // If the content detection feature is off, all layers are registered at Max. We still keep
         // the layer history, since we use it for other features (like Frame Rate API), so layers
         // still need to be registered.
         mLayerHistory->registerLayer(layer, minFps, maxFps,
                                      scheduler::LayerHistory::LayerVoteType::Max);
-    } else if (!mUseContentDetectionV2) {
+    } else if (!mOptions.useContentDetectionV2) {
         // In V1 of content detection, all layers are registered as Heuristic (unless it's
         // wallpaper).
         const auto highFps =
@@ -533,7 +524,7 @@
     if (mTouchTimer) {
         mTouchTimer->reset();
 
-        if (mSupportKernelTimer && mIdleTimer) {
+        if (mOptions.supportKernelTimer && mIdleTimer) {
             mIdleTimer->reset();
         }
     }
@@ -610,7 +601,8 @@
     StringAppendF(&result, "+  Touch timer: %s\n",
                   mTouchTimer ? mTouchTimer->dump().c_str() : "off");
     StringAppendF(&result, "+  Content detection: %s %s\n\n",
-                  toContentDetectionString(mUseContentDetection, mUseContentDetectionV2),
+                  toContentDetectionString(mOptions.useContentDetection,
+                                           mOptions.useContentDetectionV2),
                   mLayerHistory ? mLayerHistory->dump().c_str() : "(no layer history)");
 }
 
@@ -658,7 +650,7 @@
     const bool touchActive = mTouchTimer && mFeatures.touch == TouchState::Active;
     const bool idle = mIdleTimer && mFeatures.idleTimer == TimerState::Expired;
 
-    if (!mUseContentDetectionV2) {
+    if (!mOptions.useContentDetectionV2) {
         // As long as touch is active we want to be in performance mode.
         if (touchActive) {
             return mRefreshRateConfigs.getMaxRefreshRateByPolicy().getConfigId();
diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h
index 2fe5629..6a5082a 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.h
+++ b/services/surfaceflinger/Scheduler/Scheduler.h
@@ -29,7 +29,6 @@
 #include <ui/GraphicTypes.h>
 #pragma clang diagnostic pop
 
-#include "EventControlThread.h"
 #include "EventThread.h"
 #include "LayerHistory.h"
 #include "OneShotTimer.h"
@@ -50,19 +49,22 @@
 class VSyncTracker;
 } // namespace scheduler
 
-class ISchedulerCallback {
-public:
-    virtual ~ISchedulerCallback() = default;
+struct ISchedulerCallback {
+    virtual void setVsyncEnabled(bool) = 0;
     virtual void changeRefreshRate(const scheduler::RefreshRateConfigs::RefreshRate&,
                                    scheduler::RefreshRateConfigEvent) = 0;
     virtual void repaintEverythingForHWC() = 0;
     virtual void kernelTimerChanged(bool expired) = 0;
+
+protected:
+    ~ISchedulerCallback() = default;
 };
 
-class IPhaseOffsetControl {
-public:
-    virtual ~IPhaseOffsetControl() = default;
+struct IPhaseOffsetControl {
     virtual void setPhaseOffset(scheduler::ConnectionHandle, nsecs_t phaseOffset) = 0;
+
+protected:
+    ~IPhaseOffsetControl() = default;
 };
 
 class Scheduler : public IPhaseOffsetControl {
@@ -78,10 +80,7 @@
         Normal      // Start the transaction at the normal time
     };
 
-    Scheduler(impl::EventControlThread::SetVSyncEnabledFunction,
-              const scheduler::RefreshRateConfigs&, ISchedulerCallback& schedulerCallback,
-              bool useContentDetectionV2, bool useContentDetection);
-
+    Scheduler(const scheduler::RefreshRateConfigs&, ISchedulerCallback&);
     virtual ~Scheduler();
 
     DispSync& getPrimaryDispSync();
@@ -176,11 +175,31 @@
     enum class TimerState { Reset, Expired };
     enum class TouchState { Inactive, Active };
 
-    // Used by tests to inject mocks.
-    Scheduler(std::unique_ptr<DispSync>, std::unique_ptr<EventControlThread>,
-              const scheduler::RefreshRateConfigs&, ISchedulerCallback&,
-              std::unique_ptr<LayerHistory>, bool useContentDetectionV2, bool useContentDetection);
+    struct Options {
+        // Whether to use idle timer callbacks that support the kernel timer.
+        bool supportKernelTimer;
+        // Whether to use content detection at all.
+        bool useContentDetection;
+        // Whether to use improved content detection.
+        bool useContentDetectionV2;
+        // Whether to use improved DispSync implementation.
+        bool useVsyncPredictor;
+    };
 
+    struct VsyncSchedule {
+        std::unique_ptr<DispSync> sync;
+        std::unique_ptr<scheduler::VSyncTracker> tracker;
+        std::unique_ptr<scheduler::VSyncDispatch> dispatch;
+    };
+
+    // Unlike the testing constructor, this creates the VsyncSchedule, LayerHistory, and timers.
+    Scheduler(const scheduler::RefreshRateConfigs&, ISchedulerCallback&, Options);
+
+    // Used by tests to inject mocks.
+    Scheduler(VsyncSchedule, const scheduler::RefreshRateConfigs&, ISchedulerCallback&,
+              std::unique_ptr<LayerHistory>, Options);
+
+    static VsyncSchedule createVsyncSchedule(Options);
     static std::unique_ptr<LayerHistory> createLayerHistory(const scheduler::RefreshRateConfigs&,
                                                             bool useContentDetectionV2);
 
@@ -231,14 +250,8 @@
 
     std::atomic<nsecs_t> mLastResyncTime = 0;
 
-    // Whether to use idle timer callbacks that support the kernel timer.
-    const bool mSupportKernelTimer;
-
-    const bool mUseVsyncPredictor;
-    const std::unique_ptr<scheduler::VSyncTracker> mVSyncTracker;
-    const std::unique_ptr<scheduler::VSyncDispatch> mVSyncDispatch;
-    std::unique_ptr<DispSync> mPrimaryDispSync;
-    std::unique_ptr<EventControlThread> mEventControlThread;
+    const Options mOptions;
+    VsyncSchedule mVsyncSchedule;
 
     // Used to choose refresh rate if content detection is enabled.
     const std::unique_ptr<LayerHistory> mLayerHistory;
@@ -284,11 +297,6 @@
     std::optional<hal::VsyncPeriodChangeTimeline> mLastVsyncPeriodChangeTimeline
             GUARDED_BY(mVsyncTimelineLock);
     static constexpr std::chrono::nanoseconds MAX_VSYNC_APPLIED_TIME = 200ms;
-
-    // This variable indicates whether to use the content detection feature at all.
-    const bool mUseContentDetection;
-    // This variable indicates whether to use V2 version of the content detection.
-    const bool mUseContentDetectionV2;
 };
 
 } // namespace android
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index 0d9080d..e738027 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -119,7 +119,6 @@
 #include "RegionSamplingThread.h"
 #include "Scheduler/DispSync.h"
 #include "Scheduler/DispSyncSource.h"
-#include "Scheduler/EventControlThread.h"
 #include "Scheduler/EventThread.h"
 #include "Scheduler/LayerHistory.h"
 #include "Scheduler/MessageQueue.h"
@@ -1679,25 +1678,18 @@
     repaintEverythingForHWC();
 }
 
-void SurfaceFlinger::setPrimaryVsyncEnabled(bool enabled) {
+void SurfaceFlinger::setVsyncEnabled(bool enabled) {
     ATRACE_CALL();
 
-    // Enable / Disable HWVsync from the main thread to avoid race conditions with
-    // display power state.
-    static_cast<void>(schedule([=]() MAIN_THREAD { setPrimaryVsyncEnabledInternal(enabled); }));
-}
+    // On main thread to avoid race conditions with display power state.
+    static_cast<void>(schedule([=]() MAIN_THREAD {
+        mHWCVsyncPendingState = enabled ? hal::Vsync::ENABLE : hal::Vsync::DISABLE;
 
-void SurfaceFlinger::setPrimaryVsyncEnabledInternal(bool enabled) {
-    ATRACE_CALL();
-
-    mHWCVsyncPendingState = enabled ? hal::Vsync::ENABLE : hal::Vsync::DISABLE;
-
-    if (const auto displayId = getInternalDisplayIdLocked()) {
-        sp<DisplayDevice> display = getDefaultDisplayDeviceLocked();
-        if (display && display->isPoweredOn()) {
-            getHwComposer().setVsyncEnabled(*displayId, mHWCVsyncPendingState);
+        if (const auto display = getDefaultDisplayDeviceLocked();
+            display && display->isPoweredOn()) {
+            getHwComposer().setVsyncEnabled(*display->getId(), mHWCVsyncPendingState);
         }
-    }
+    }));
 }
 
 void SurfaceFlinger::resetDisplayState() {
@@ -2985,9 +2977,7 @@
     mPhaseConfiguration = getFactory().createPhaseConfiguration(*mRefreshRateConfigs);
 
     // start the EventThread
-    mScheduler =
-            getFactory().createScheduler([this](bool enabled) { setPrimaryVsyncEnabled(enabled); },
-                                         *mRefreshRateConfigs, *this);
+    mScheduler = getFactory().createScheduler(*mRefreshRateConfigs, *this);
     mAppConnectionHandle =
             mScheduler->createConnection("app", mPhaseConfiguration->getCurrentOffsets().late.app,
                                          impl::EventThread::InterceptVSyncsCallback());
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index 2552a01..7fb1680 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -300,13 +300,6 @@
     // utility function to delete a texture on the main thread
     void deleteTextureAsync(uint32_t texture);
 
-    // enable/disable h/w composer event
-    // TODO: this should be made accessible only to EventThread
-    void setPrimaryVsyncEnabled(bool enabled);
-
-    // main thread function to enable/disable h/w composer event
-    void setPrimaryVsyncEnabledInternal(bool enabled) REQUIRES(mStateLock);
-
     // called on the main thread by MessageQueue when an internal message
     // is received
     // TODO: this should be made accessible only to MessageQueue
@@ -528,8 +521,12 @@
     /* ------------------------------------------------------------------------
      * ISchedulerCallback
      */
+
+    // Toggles hardware VSYNC by calling into HWC.
+    void setVsyncEnabled(bool) override;
+    // Initiates a refresh rate change to be applied on invalidate.
     void changeRefreshRate(const Scheduler::RefreshRate&, Scheduler::ConfigEvent) override;
-    // force full composition on all displays without resetting the scheduler idle timer.
+    // Forces full composition on all displays without resetting the scheduler idle timer.
     void repaintEverythingForHWC() override;
     // Called when kernel idle timer has expired. Used to update the refresh rate overlay.
     void kernelTimerChanged(bool expired) override;
@@ -542,6 +539,7 @@
     bool mSupportKernelIdleTimer = false;
     // Show spinner with refresh rate overlay
     bool mRefreshRateOverlaySpinner = false;
+
     /* ------------------------------------------------------------------------
      * Message handling
      */
diff --git a/services/surfaceflinger/SurfaceFlingerDefaultFactory.cpp b/services/surfaceflinger/SurfaceFlingerDefaultFactory.cpp
index ddd20a5..025d1a4 100644
--- a/services/surfaceflinger/SurfaceFlingerDefaultFactory.cpp
+++ b/services/surfaceflinger/SurfaceFlingerDefaultFactory.cpp
@@ -38,7 +38,6 @@
 
 #include "DisplayHardware/ComposerHal.h"
 #include "Scheduler/DispSync.h"
-#include "Scheduler/EventControlThread.h"
 #include "Scheduler/MessageQueue.h"
 #include "Scheduler/PhaseOffsets.h"
 #include "Scheduler/Scheduler.h"
@@ -51,11 +50,6 @@
     return std::make_unique<android::impl::DispSync>(name, hasSyncFramework);
 }
 
-std::unique_ptr<EventControlThread> DefaultFactory::createEventControlThread(
-        SetVSyncEnabled setVSyncEnabled) {
-    return std::make_unique<android::impl::EventControlThread>(std::move(setVSyncEnabled));
-}
-
 std::unique_ptr<HWComposer> DefaultFactory::createHWComposer(const std::string& serviceName) {
     return std::make_unique<android::impl::HWComposer>(serviceName);
 }
@@ -74,11 +68,8 @@
 }
 
 std::unique_ptr<Scheduler> DefaultFactory::createScheduler(
-        SetVSyncEnabled setVSyncEnabled, const scheduler::RefreshRateConfigs& configs,
-        ISchedulerCallback& schedulerCallback) {
-    return std::make_unique<Scheduler>(std::move(setVSyncEnabled), configs, schedulerCallback,
-                                       property_get_bool("debug.sf.use_content_detection_v2", true),
-                                       sysprop::use_content_detection_for_refresh_rate(false));
+        const scheduler::RefreshRateConfigs& configs, ISchedulerCallback& callback) {
+    return std::make_unique<Scheduler>(configs, callback);
 }
 
 std::unique_ptr<SurfaceInterceptor> DefaultFactory::createSurfaceInterceptor(
diff --git a/services/surfaceflinger/SurfaceFlingerDefaultFactory.h b/services/surfaceflinger/SurfaceFlingerDefaultFactory.h
index bd40cfb..1757fa8 100644
--- a/services/surfaceflinger/SurfaceFlingerDefaultFactory.h
+++ b/services/surfaceflinger/SurfaceFlingerDefaultFactory.h
@@ -27,13 +27,11 @@
     virtual ~DefaultFactory();
 
     std::unique_ptr<DispSync> createDispSync(const char* name, bool hasSyncFramework) override;
-    std::unique_ptr<EventControlThread> createEventControlThread(SetVSyncEnabled) override;
     std::unique_ptr<HWComposer> createHWComposer(const std::string& serviceName) override;
     std::unique_ptr<MessageQueue> createMessageQueue() override;
     std::unique_ptr<scheduler::PhaseConfiguration> createPhaseConfiguration(
             const scheduler::RefreshRateConfigs&) override;
-    std::unique_ptr<Scheduler> createScheduler(SetVSyncEnabled,
-                                               const scheduler::RefreshRateConfigs&,
+    std::unique_ptr<Scheduler> createScheduler(const scheduler::RefreshRateConfigs&,
                                                ISchedulerCallback&) override;
     std::unique_ptr<SurfaceInterceptor> createSurfaceInterceptor(SurfaceFlinger*) override;
     sp<StartPropertySetThread> createStartPropertySetThread(bool timestampPropertyValue) override;
diff --git a/services/surfaceflinger/SurfaceFlingerFactory.h b/services/surfaceflinger/SurfaceFlingerFactory.h
index 6f4fcc6..1d710c4 100644
--- a/services/surfaceflinger/SurfaceFlingerFactory.h
+++ b/services/surfaceflinger/SurfaceFlingerFactory.h
@@ -35,12 +35,10 @@
 class ContainerLayer;
 class DisplayDevice;
 class DispSync;
-class EventControlThread;
 class GraphicBuffer;
 class HWComposer;
 class IGraphicBufferConsumer;
 class IGraphicBufferProducer;
-class ISchedulerCallback;
 class Layer;
 class MessageQueue;
 class Scheduler;
@@ -49,6 +47,7 @@
 class SurfaceInterceptor;
 
 struct DisplayDeviceCreationArgs;
+struct ISchedulerCallback;
 struct LayerCreationArgs;
 
 namespace compositionengine {
@@ -68,16 +67,12 @@
 // of each interface.
 class Factory {
 public:
-    using SetVSyncEnabled = std::function<void(bool)>;
-
     virtual std::unique_ptr<DispSync> createDispSync(const char* name, bool hasSyncFramework) = 0;
-    virtual std::unique_ptr<EventControlThread> createEventControlThread(SetVSyncEnabled) = 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(
             const scheduler::RefreshRateConfigs&) = 0;
-    virtual std::unique_ptr<Scheduler> createScheduler(SetVSyncEnabled,
-                                                       const scheduler::RefreshRateConfigs&,
+    virtual std::unique_ptr<Scheduler> createScheduler(const scheduler::RefreshRateConfigs&,
                                                        ISchedulerCallback&) = 0;
     virtual std::unique_ptr<SurfaceInterceptor> createSurfaceInterceptor(SurfaceFlinger*) = 0;
 
diff --git a/services/surfaceflinger/tests/unittests/Android.bp b/services/surfaceflinger/tests/unittests/Android.bp
index 3c4a791..c46c914 100644
--- a/services/surfaceflinger/tests/unittests/Android.bp
+++ b/services/surfaceflinger/tests/unittests/Android.bp
@@ -40,7 +40,6 @@
         "DispSyncSourceTest.cpp",
         "DisplayIdentificationTest.cpp",
         "DisplayTransactionTest.cpp",
-        "EventControlThreadTest.cpp",
         "EventThreadTest.cpp",
         "HWComposerTest.cpp",
         "OneShotTimerTest.cpp",
@@ -69,7 +68,6 @@
         "mock/DisplayHardware/MockDisplay.cpp",
         "mock/DisplayHardware/MockPowerAdvisor.cpp",
         "mock/MockDispSync.cpp",
-        "mock/MockEventControlThread.cpp",
         "mock/MockEventThread.cpp",
         "mock/MockMessageQueue.cpp",
         "mock/MockNativeWindowSurface.cpp",
diff --git a/services/surfaceflinger/tests/unittests/CompositionTest.cpp b/services/surfaceflinger/tests/unittests/CompositionTest.cpp
index 9959ef6..fe440a9 100644
--- a/services/surfaceflinger/tests/unittests/CompositionTest.cpp
+++ b/services/surfaceflinger/tests/unittests/CompositionTest.cpp
@@ -42,7 +42,6 @@
 #include "mock/DisplayHardware/MockComposer.h"
 #include "mock/DisplayHardware/MockPowerAdvisor.h"
 #include "mock/MockDispSync.h"
-#include "mock/MockEventControlThread.h"
 #include "mock/MockEventThread.h"
 #include "mock/MockMessageQueue.h"
 #include "mock/MockTimeStats.h"
@@ -150,11 +149,10 @@
                 .WillRepeatedly(Return(FakeHwcDisplayInjector::DEFAULT_REFRESH_RATE));
         EXPECT_CALL(*primaryDispSync, expectedPresentTime(_)).WillRepeatedly(Return(0));
 
+        constexpr ISchedulerCallback* kCallback = nullptr;
         constexpr bool kHasMultipleConfigs = true;
-        mFlinger.setupScheduler(std::move(primaryDispSync),
-                                std::make_unique<mock::EventControlThread>(),
-                                std::move(eventThread), std::move(sfEventThread),
-                                kHasMultipleConfigs);
+        mFlinger.setupScheduler(std::move(primaryDispSync), std::move(eventThread),
+                                std::move(sfEventThread), kCallback, kHasMultipleConfigs);
 
         // Layer history should be created if there are multiple configs.
         ASSERT_TRUE(mFlinger.scheduler()->hasLayerHistory());
@@ -896,7 +894,7 @@
     static FlingerLayerType createLayer(CompositionTest* test) {
         FlingerLayerType layer = Base::template createLayerWithFactory<EffectLayer>(test, [test]() {
             return new EffectLayer(
-                    LayerCreationArgs(test->mFlinger.mFlinger.get(), sp<Client>(), "test-layer",
+                    LayerCreationArgs(test->mFlinger.flinger(), sp<Client>(), "test-layer",
                                       LayerProperties::WIDTH, LayerProperties::HEIGHT,
                                       LayerProperties::LAYER_FLAGS, LayerMetadata()));
         });
@@ -935,8 +933,7 @@
 
         FlingerLayerType layer =
                 Base::template createLayerWithFactory<BufferQueueLayer>(test, [test]() {
-                    sp<Client> client;
-                    LayerCreationArgs args(test->mFlinger.mFlinger.get(), client, "test-layer",
+                    LayerCreationArgs args(test->mFlinger.flinger(), sp<Client>(), "test-layer",
                                            LayerProperties::WIDTH, LayerProperties::HEIGHT,
                                            LayerProperties::LAYER_FLAGS, LayerMetadata());
                     args.textureName = test->mFlinger.mutableTexturePool().back();
diff --git a/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp b/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp
index fc11537..b0c40df 100644
--- a/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp
+++ b/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp
@@ -46,10 +46,10 @@
 #include "mock/DisplayHardware/MockComposer.h"
 #include "mock/DisplayHardware/MockPowerAdvisor.h"
 #include "mock/MockDispSync.h"
-#include "mock/MockEventControlThread.h"
 #include "mock/MockEventThread.h"
 #include "mock/MockMessageQueue.h"
 #include "mock/MockNativeWindowSurface.h"
+#include "mock/MockSchedulerCallback.h"
 #include "mock/MockSurfaceInterceptor.h"
 #include "mock/system/window/MockNativeWindow.h"
 
@@ -156,7 +156,7 @@
     mock::SurfaceInterceptor* mSurfaceInterceptor = new mock::SurfaceInterceptor();
 
     mock::DispSync* mPrimaryDispSync = new mock::DispSync;
-    mock::EventControlThread* mEventControlThread = new mock::EventControlThread;
+    mock::SchedulerCallback mSchedulerCallback;
     mock::EventThread* mEventThread = new mock::EventThread;
     mock::EventThread* mSFEventThread = new mock::EventThread;
 
@@ -214,9 +214,8 @@
                                                        ISurfaceComposer::eConfigChangedSuppress)));
 
     mFlinger.setupScheduler(std::unique_ptr<DispSync>(mPrimaryDispSync),
-                            std::unique_ptr<EventControlThread>(mEventControlThread),
                             std::unique_ptr<EventThread>(mEventThread),
-                            std::unique_ptr<EventThread>(mSFEventThread));
+                            std::unique_ptr<EventThread>(mSFEventThread), &mSchedulerCallback);
 }
 
 void DisplayTransactionTest::injectMockComposer(int virtualDisplayCount) {
@@ -1322,7 +1321,7 @@
     // Call Expectations
 
     // The call disable vsyncs
-    EXPECT_CALL(*mEventControlThread, setVsyncEnabled(false)).Times(1);
+    EXPECT_CALL(mSchedulerCallback, setVsyncEnabled(false)).Times(1);
 
     // The call ends any display resyncs
     EXPECT_CALL(*mPrimaryDispSync, endResync()).Times(1);
@@ -3207,9 +3206,9 @@
 };
 
 struct EventThreadBaseSupportedVariant {
-    static void setupEventAndEventControlThreadNoCallExpectations(DisplayTransactionTest* test) {
-        // The event control thread should not be notified.
-        EXPECT_CALL(*test->mEventControlThread, setVsyncEnabled(_)).Times(0);
+    static void setupVsyncAndEventThreadNoCallExpectations(DisplayTransactionTest* test) {
+        // The callback should not be notified to toggle VSYNC.
+        EXPECT_CALL(test->mSchedulerCallback, setVsyncEnabled(_)).Times(0);
 
         // The event thread should not be notified.
         EXPECT_CALL(*test->mEventThread, onScreenReleased()).Times(0);
@@ -3222,29 +3221,29 @@
         // These calls are only expected for the primary display.
 
         // Instead expect no calls.
-        setupEventAndEventControlThreadNoCallExpectations(test);
+        setupVsyncAndEventThreadNoCallExpectations(test);
     }
 
     static void setupReleaseAndDisableVsyncCallExpectations(DisplayTransactionTest* test) {
         // These calls are only expected for the primary display.
 
         // Instead expect no calls.
-        setupEventAndEventControlThreadNoCallExpectations(test);
+        setupVsyncAndEventThreadNoCallExpectations(test);
     }
 };
 
 struct EventThreadIsSupportedVariant : public EventThreadBaseSupportedVariant {
     static void setupAcquireAndEnableVsyncCallExpectations(DisplayTransactionTest* test) {
-        // The event control thread should be notified to enable vsyncs
-        EXPECT_CALL(*test->mEventControlThread, setVsyncEnabled(true)).Times(1);
+        // The callback should be notified to enable VSYNC.
+        EXPECT_CALL(test->mSchedulerCallback, setVsyncEnabled(true)).Times(1);
 
         // The event thread should be notified that the screen was acquired.
         EXPECT_CALL(*test->mEventThread, onScreenAcquired()).Times(1);
     }
 
     static void setupReleaseAndDisableVsyncCallExpectations(DisplayTransactionTest* test) {
-        // There should be a call to setVsyncEnabled(false)
-        EXPECT_CALL(*test->mEventControlThread, setVsyncEnabled(false)).Times(1);
+        // The callback should be notified to disable VSYNC.
+        EXPECT_CALL(test->mSchedulerCallback, setVsyncEnabled(false)).Times(1);
 
         // The event thread should not be notified that the screen was released.
         EXPECT_CALL(*test->mEventThread, onScreenReleased()).Times(1);
@@ -3303,7 +3302,7 @@
     template <typename Case>
     static void setupCallExpectations(DisplayTransactionTest* test) {
         Case::setupComposerCallExpectations(test, Case::Doze::ACTUAL_POWER_MODE_FOR_DOZE_SUSPEND);
-        Case::EventThread::setupEventAndEventControlThreadNoCallExpectations(test);
+        Case::EventThread::setupVsyncAndEventThreadNoCallExpectations(test);
         Case::setupRepaintEverythingCallExpectations(test);
     }
 
@@ -3330,7 +3329,7 @@
       : public TransitionVariantCommon<PowerMode::DOZE_SUSPEND, PowerMode::OFF> {
     template <typename Case>
     static void setupCallExpectations(DisplayTransactionTest* test) {
-        Case::EventThread::setupEventAndEventControlThreadNoCallExpectations(test);
+        Case::EventThread::setupVsyncAndEventThreadNoCallExpectations(test);
         Case::setupComposerCallExpectations(test, IComposerClient::PowerMode::OFF);
     }
 
@@ -3342,7 +3341,7 @@
 struct TransitionOnToDozeVariant : public TransitionVariantCommon<PowerMode::ON, PowerMode::DOZE> {
     template <typename Case>
     static void setupCallExpectations(DisplayTransactionTest* test) {
-        Case::EventThread::setupEventAndEventControlThreadNoCallExpectations(test);
+        Case::EventThread::setupVsyncAndEventThreadNoCallExpectations(test);
         Case::setupComposerCallExpectations(test, Case::Doze::ACTUAL_POWER_MODE_FOR_DOZE);
     }
 };
@@ -3360,7 +3359,7 @@
 struct TransitionDozeToOnVariant : public TransitionVariantCommon<PowerMode::DOZE, PowerMode::ON> {
     template <typename Case>
     static void setupCallExpectations(DisplayTransactionTest* test) {
-        Case::EventThread::setupEventAndEventControlThreadNoCallExpectations(test);
+        Case::EventThread::setupVsyncAndEventThreadNoCallExpectations(test);
         Case::setupComposerCallExpectations(test, IComposerClient::PowerMode::ON);
     }
 };
@@ -3389,7 +3388,7 @@
       : public TransitionVariantCommon<PowerMode::ON, static_cast<PowerMode>(POWER_MODE_LEET)> {
     template <typename Case>
     static void setupCallExpectations(DisplayTransactionTest* test) {
-        Case::EventThread::setupEventAndEventControlThreadNoCallExpectations(test);
+        Case::EventThread::setupVsyncAndEventThreadNoCallExpectations(test);
         Case::setupNoComposerPowerModeCallExpectations(test);
     }
 };
diff --git a/services/surfaceflinger/tests/unittests/EventControlThreadTest.cpp b/services/surfaceflinger/tests/unittests/EventControlThreadTest.cpp
deleted file mode 100644
index 9dc4193..0000000
--- a/services/surfaceflinger/tests/unittests/EventControlThreadTest.cpp
+++ /dev/null
@@ -1,119 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#undef LOG_TAG
-#define LOG_TAG "LibSurfaceFlingerUnittests"
-
-#include <gmock/gmock.h>
-#include <gtest/gtest.h>
-
-#include <log/log.h>
-
-#include "AsyncCallRecorder.h"
-#include "Scheduler/EventControlThread.h"
-
-namespace android {
-namespace {
-
-using namespace std::chrono_literals;
-using testing::_;
-
-class EventControlThreadTest : public testing::Test {
-protected:
-    EventControlThreadTest();
-    ~EventControlThreadTest() override;
-
-    void createThread();
-
-    void expectVSyncEnableCallbackCalled(bool enable);
-
-    AsyncCallRecorder<void (*)(bool)> mVSyncSetEnabledCallRecorder;
-
-    std::unique_ptr<EventControlThread> mThread;
-};
-
-EventControlThreadTest::EventControlThreadTest() {
-    const ::testing::TestInfo* const test_info =
-            ::testing::UnitTest::GetInstance()->current_test_info();
-    ALOGD("**** Setting up for %s.%s\n", test_info->test_case_name(), test_info->name());
-}
-
-EventControlThreadTest::~EventControlThreadTest() {
-    const ::testing::TestInfo* const test_info =
-            ::testing::UnitTest::GetInstance()->current_test_info();
-    ALOGD("**** Tearing down after %s.%s\n", test_info->test_case_name(), test_info->name());
-}
-
-void EventControlThreadTest::createThread() {
-    mThread = std::make_unique<android::impl::EventControlThread>(
-            mVSyncSetEnabledCallRecorder.getInvocable());
-}
-
-void EventControlThreadTest::expectVSyncEnableCallbackCalled(bool expectedEnabled) {
-    auto args = mVSyncSetEnabledCallRecorder.waitForCall();
-    ASSERT_TRUE(args.has_value());
-    EXPECT_EQ(std::get<0>(args.value()), expectedEnabled);
-}
-
-/* ------------------------------------------------------------------------
- * Test cases
- */
-
-TEST_F(EventControlThreadTest, signalsVSyncDisabledOnStartup) {
-    createThread();
-
-    // On thread start, there should be an automatic explicit call to disable
-    // vsyncs
-    expectVSyncEnableCallbackCalled(false);
-}
-
-TEST_F(EventControlThreadTest, signalsVSyncDisabledOnce) {
-    createThread();
-    expectVSyncEnableCallbackCalled(false);
-
-    mThread->setVsyncEnabled(false);
-
-    EXPECT_FALSE(mVSyncSetEnabledCallRecorder.waitForUnexpectedCall().has_value());
-}
-
-TEST_F(EventControlThreadTest, signalsVSyncEnabledThenDisabled) {
-    createThread();
-    expectVSyncEnableCallbackCalled(false);
-
-    mThread->setVsyncEnabled(true);
-
-    expectVSyncEnableCallbackCalled(true);
-
-    mThread->setVsyncEnabled(false);
-
-    expectVSyncEnableCallbackCalled(false);
-}
-
-TEST_F(EventControlThreadTest, signalsVSyncEnabledOnce) {
-    createThread();
-    expectVSyncEnableCallbackCalled(false);
-
-    mThread->setVsyncEnabled(true);
-
-    expectVSyncEnableCallbackCalled(true);
-
-    mThread->setVsyncEnabled(true);
-
-    EXPECT_FALSE(mVSyncSetEnabledCallRecorder.waitForUnexpectedCall().has_value());
-}
-
-} // namespace
-} // namespace android
diff --git a/services/surfaceflinger/tests/unittests/HWComposerTest.cpp b/services/surfaceflinger/tests/unittests/HWComposerTest.cpp
index 91b304c..fa12315 100644
--- a/services/surfaceflinger/tests/unittests/HWComposerTest.cpp
+++ b/services/surfaceflinger/tests/unittests/HWComposerTest.cpp
@@ -23,7 +23,13 @@
 
 #include <vector>
 
+// StrictMock<T> derives from T and is not marked final, so the destructor of T is expected to be
+// virtual in case StrictMock<T> is used as a polymorphic base class. That is not the case here.
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wnon-virtual-dtor"
 #include <gmock/gmock.h>
+#pragma clang diagnostic pop
+
 #include <gui/LayerMetadata.h>
 #include <log/log.h>
 
@@ -45,27 +51,20 @@
 using ::testing::SetArgPointee;
 using ::testing::StrictMock;
 
-struct MockHWC2ComposerCallback : public HWC2::ComposerCallback {
-    ~MockHWC2ComposerCallback() = default;
-
-    MOCK_METHOD3(onHotplugReceived,
-                 void(int32_t sequenceId, hal::HWDisplayId display, hal::Connection connection));
-    MOCK_METHOD2(onRefreshReceived, void(int32_t sequenceId, hal::HWDisplayId display));
+struct MockHWC2ComposerCallback final : StrictMock<HWC2::ComposerCallback> {
+    MOCK_METHOD3(onHotplugReceived, void(int32_t sequenceId, hal::HWDisplayId, hal::Connection));
+    MOCK_METHOD2(onRefreshReceived, void(int32_t sequenceId, hal::HWDisplayId));
     MOCK_METHOD4(onVsyncReceived,
-                 void(int32_t sequenceId, hal::HWDisplayId display, int64_t timestamp,
-                      std::optional<hal::VsyncPeriodNanos> vsyncPeriod));
+                 void(int32_t sequenceId, hal::HWDisplayId, int64_t timestamp,
+                      std::optional<hal::VsyncPeriodNanos>));
     MOCK_METHOD3(onVsyncPeriodTimingChangedReceived,
-                 void(int32_t sequenceId, hal::HWDisplayId display,
-                      const hal::VsyncPeriodChangeTimeline& updatedTimeline));
-    MOCK_METHOD2(onSeamlessPossible, void(int32_t sequenceId, hal::HWDisplayId display));
+                 void(int32_t sequenceId, hal::HWDisplayId, const hal::VsyncPeriodChangeTimeline&));
+    MOCK_METHOD2(onSeamlessPossible, void(int32_t sequenceId, hal::HWDisplayId));
 };
 
-struct HWComposerTest : public testing::Test {
+struct HWComposerSetConfigurationTest : testing::Test {
     Hwc2::mock::Composer* mHal = new StrictMock<Hwc2::mock::Composer>();
-};
-
-struct HWComposerSetConfigurationTest : public HWComposerTest {
-    StrictMock<MockHWC2ComposerCallback> mCallback;
+    MockHWC2ComposerCallback mCallback;
 };
 
 TEST_F(HWComposerSetConfigurationTest, loadsLayerMetadataSupport) {
diff --git a/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp b/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp
index 3ab22d3..0fbe8ec 100644
--- a/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp
@@ -30,6 +30,7 @@
 #include "TestableScheduler.h"
 #include "TestableSurfaceFlinger.h"
 #include "mock/MockLayer.h"
+#include "mock/MockSchedulerCallback.h"
 
 using testing::_;
 using testing::Return;
@@ -75,7 +76,13 @@
                                          .setConfigGroup(0)
                                          .build()},
                                 HwcConfigIndexType(0)};
-    TestableScheduler* const mScheduler{new TestableScheduler(mConfigs, false)};
+
+    mock::NoOpSchedulerCallback mSchedulerCallback;
+    static constexpr bool kUseContentDetectionV2 = false;
+
+    TestableScheduler* const mScheduler =
+            new TestableScheduler(mConfigs, mSchedulerCallback, kUseContentDetectionV2);
+
     TestableSurfaceFlinger mFlinger;
 
     const nsecs_t mTime = systemTime();
diff --git a/services/surfaceflinger/tests/unittests/LayerHistoryTestV2.cpp b/services/surfaceflinger/tests/unittests/LayerHistoryTestV2.cpp
index 1705835..cb376cd 100644
--- a/services/surfaceflinger/tests/unittests/LayerHistoryTestV2.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerHistoryTestV2.cpp
@@ -27,6 +27,7 @@
 #include "TestableScheduler.h"
 #include "TestableSurfaceFlinger.h"
 #include "mock/MockLayer.h"
+#include "mock/MockSchedulerCallback.h"
 
 using testing::_;
 using testing::Return;
@@ -115,9 +116,14 @@
                                          .setConfigGroup(0)
                                          .build()},
                                 HwcConfigIndexType(0)};
-    TestableScheduler* const mScheduler{new TestableScheduler(mConfigs, true)};
-    TestableSurfaceFlinger mFlinger;
 
+    mock::NoOpSchedulerCallback mSchedulerCallback;
+    static constexpr bool kUseContentDetectionV2 = true;
+
+    TestableScheduler* const mScheduler =
+            new TestableScheduler(mConfigs, mSchedulerCallback, kUseContentDetectionV2);
+
+    TestableSurfaceFlinger mFlinger;
 };
 
 namespace {
diff --git a/services/surfaceflinger/tests/unittests/RefreshRateSelectionTest.cpp b/services/surfaceflinger/tests/unittests/RefreshRateSelectionTest.cpp
index 43b8e01..f6bf05a 100644
--- a/services/surfaceflinger/tests/unittests/RefreshRateSelectionTest.cpp
+++ b/services/surfaceflinger/tests/unittests/RefreshRateSelectionTest.cpp
@@ -32,7 +32,6 @@
 #include "TestableSurfaceFlinger.h"
 #include "mock/DisplayHardware/MockComposer.h"
 #include "mock/MockDispSync.h"
-#include "mock/MockEventControlThread.h"
 #include "mock/MockEventThread.h"
 
 namespace android {
@@ -146,8 +145,7 @@
     EXPECT_CALL(*primaryDispSync, getPeriod())
             .WillRepeatedly(Return(FakeHwcDisplayInjector::DEFAULT_REFRESH_RATE));
     EXPECT_CALL(*primaryDispSync, expectedPresentTime(_)).WillRepeatedly(Return(0));
-    mFlinger.setupScheduler(std::move(primaryDispSync),
-                            std::make_unique<mock::EventControlThread>(), std::move(eventThread),
+    mFlinger.setupScheduler(std::move(primaryDispSync), std::move(eventThread),
                             std::move(sfEventThread));
 }
 
diff --git a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
index 382199d..d6468b4 100644
--- a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
@@ -14,20 +14,12 @@
  * 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"
-
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
 #include <log/log.h>
 
 #include <mutex>
 
-#include "Scheduler/EventControlThread.h"
 #include "Scheduler/EventThread.h"
 #include "Scheduler/RefreshRateConfigs.h"
 #include "TestableScheduler.h"
@@ -35,11 +27,13 @@
 #include "mock/DisplayHardware/MockDisplay.h"
 #include "mock/MockEventThread.h"
 #include "mock/MockLayer.h"
+#include "mock/MockSchedulerCallback.h"
 
 using testing::_;
 using testing::Return;
 
 namespace android {
+namespace {
 
 constexpr PhysicalDisplayId PHYSICAL_DISPLAY_ID = 999;
 
@@ -58,7 +52,6 @@
     };
 
     SchedulerTest();
-    ~SchedulerTest() override;
 
     Hwc2::mock::Display mDisplay;
     const scheduler::RefreshRateConfigs mConfigs{{HWC2::Display::Config::Builder(mDisplay, 0)
@@ -67,7 +60,17 @@
                                                           .build()},
                                                  HwcConfigIndexType(0)};
 
-    TestableScheduler mScheduler{mConfigs, false};
+    mock::SchedulerCallback mSchedulerCallback;
+
+    // The scheduler should initially disable VSYNC.
+    struct ExpectDisableVsync {
+        ExpectDisableVsync(mock::SchedulerCallback& callback) {
+            EXPECT_CALL(callback, setVsyncEnabled(false)).Times(1);
+        }
+    } mExpectDisableVsync{mSchedulerCallback};
+
+    static constexpr bool kUseContentDetectionV2 = false;
+    TestableScheduler mScheduler{mConfigs, mSchedulerCallback, kUseContentDetectionV2};
 
     Scheduler::ConnectionHandle mConnectionHandle;
     mock::EventThread* mEventThread;
@@ -75,10 +78,6 @@
 };
 
 SchedulerTest::SchedulerTest() {
-    const ::testing::TestInfo* const test_info =
-            ::testing::UnitTest::GetInstance()->current_test_info();
-    ALOGD("**** Setting up for %s.%s\n", test_info->test_case_name(), test_info->name());
-
     auto eventThread = std::make_unique<mock::EventThread>();
     mEventThread = eventThread.get();
     EXPECT_CALL(*mEventThread, registerDisplayEventConnection(_)).WillOnce(Return(0));
@@ -94,15 +93,7 @@
     EXPECT_TRUE(mConnectionHandle);
 }
 
-SchedulerTest::~SchedulerTest() {
-    const ::testing::TestInfo* const test_info =
-            ::testing::UnitTest::GetInstance()->current_test_info();
-    ALOGD("**** Tearing down after %s.%s\n", test_info->test_case_name(), test_info->name());
-}
-
-/* ------------------------------------------------------------------------
- * Test cases
- */
+} // namespace
 
 TEST_F(SchedulerTest, invalidConnectionHandle) {
     Scheduler::ConnectionHandle handle;
@@ -159,8 +150,7 @@
     mScheduler.setPhaseOffset(mConnectionHandle, 10);
 
     static constexpr size_t kEventConnections = 5;
-    ON_CALL(*mEventThread, getEventThreadConnectionCount())
-            .WillByDefault(Return(kEventConnections));
+    EXPECT_CALL(*mEventThread, getEventThreadConnectionCount()).WillOnce(Return(kEventConnections));
     EXPECT_EQ(kEventConnections, mScheduler.getEventThreadConnectionCount(mConnectionHandle));
 }
 
@@ -181,11 +171,8 @@
     constexpr uint32_t kDisplayArea = 999'999;
     mScheduler.onPrimaryDisplayAreaChanged(kDisplayArea);
 
+    EXPECT_CALL(mSchedulerCallback, changeRefreshRate(_, _)).Times(0);
     mScheduler.chooseRefreshRateForContent();
-    EXPECT_EQ(0, mScheduler.refreshRateChangeCount());
 }
 
 } // namespace android
-
-// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic pop // ignored "-Wconversion"
diff --git a/services/surfaceflinger/tests/unittests/SetFrameRateTest.cpp b/services/surfaceflinger/tests/unittests/SetFrameRateTest.cpp
index 0d6c799..6c01f85 100644
--- a/services/surfaceflinger/tests/unittests/SetFrameRateTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SetFrameRateTest.cpp
@@ -33,7 +33,6 @@
 #include "TestableSurfaceFlinger.h"
 #include "mock/DisplayHardware/MockComposer.h"
 #include "mock/MockDispSync.h"
-#include "mock/MockEventControlThread.h"
 #include "mock/MockEventThread.h"
 #include "mock/MockMessageQueue.h"
 
@@ -182,8 +181,7 @@
     EXPECT_CALL(*primaryDispSync, getPeriod())
             .WillRepeatedly(Return(FakeHwcDisplayInjector::DEFAULT_REFRESH_RATE));
     EXPECT_CALL(*primaryDispSync, expectedPresentTime(_)).WillRepeatedly(Return(0));
-    mFlinger.setupScheduler(std::move(primaryDispSync),
-                            std::make_unique<mock::EventControlThread>(), std::move(eventThread),
+    mFlinger.setupScheduler(std::move(primaryDispSync), std::move(eventThread),
                             std::move(sfEventThread));
 }
 
diff --git a/services/surfaceflinger/tests/unittests/TestableScheduler.h b/services/surfaceflinger/tests/unittests/TestableScheduler.h
index c463294..d78546d 100644
--- a/services/surfaceflinger/tests/unittests/TestableScheduler.h
+++ b/services/surfaceflinger/tests/unittests/TestableScheduler.h
@@ -23,20 +23,27 @@
 #include "Scheduler/EventThread.h"
 #include "Scheduler/LayerHistory.h"
 #include "Scheduler/Scheduler.h"
+#include "Scheduler/VSyncTracker.h"
+#include "mock/MockDispSync.h"
 
 namespace android {
 
-class TestableScheduler : public Scheduler, private ISchedulerCallback {
+class TestableScheduler : public Scheduler {
 public:
-    TestableScheduler(const scheduler::RefreshRateConfigs& configs, bool useContentDetectionV2)
-          : Scheduler([](bool) {}, configs, *this, useContentDetectionV2, true) {}
+    TestableScheduler(const scheduler::RefreshRateConfigs& configs, ISchedulerCallback& callback,
+                      bool useContentDetectionV2)
+          : TestableScheduler(std::make_unique<mock::DispSync>(), configs, callback,
+                              useContentDetectionV2) {}
 
     TestableScheduler(std::unique_ptr<DispSync> primaryDispSync,
-                      std::unique_ptr<EventControlThread> eventControlThread,
-                      const scheduler::RefreshRateConfigs& configs, bool useContentDetectionV2)
-          : Scheduler(std::move(primaryDispSync), std::move(eventControlThread), configs, *this,
-                      createLayerHistory(configs, useContentDetectionV2), useContentDetectionV2,
-                      true) {}
+                      const scheduler::RefreshRateConfigs& configs, ISchedulerCallback& callback,
+                      bool useContentDetectionV2)
+          : Scheduler({std::move(primaryDispSync), nullptr, nullptr}, configs, callback,
+                      createLayerHistory(configs, useContentDetectionV2),
+                      {.supportKernelTimer = false,
+                       .useContentDetection = true,
+                       .useContentDetectionV2 = useContentDetectionV2,
+                       .useVsyncPredictor = false}) {}
 
     // Used to inject mock event thread.
     ConnectionHandle createConnection(std::unique_ptr<EventThread> eventThread) {
@@ -48,28 +55,24 @@
      * post-conditions.
      */
     auto& mutablePrimaryHWVsyncEnabled() { return mPrimaryHWVsyncEnabled; }
-    auto& mutableEventControlThread() { return mEventControlThread; }
-    auto& mutablePrimaryDispSync() { return mPrimaryDispSync; }
     auto& mutableHWVsyncAvailable() { return mHWVsyncAvailable; }
 
-    size_t refreshRateChangeCount() const { return mRefreshRateChangeCount; }
-
     bool hasLayerHistory() const { return static_cast<bool>(mLayerHistory); }
 
     auto* mutableLayerHistory() {
-        LOG_ALWAYS_FATAL_IF(mUseContentDetectionV2);
+        LOG_ALWAYS_FATAL_IF(mOptions.useContentDetectionV2);
         return static_cast<scheduler::impl::LayerHistory*>(mLayerHistory.get());
     }
 
     auto* mutableLayerHistoryV2() {
-        LOG_ALWAYS_FATAL_IF(!mUseContentDetectionV2);
+        LOG_ALWAYS_FATAL_IF(!mOptions.useContentDetectionV2);
         return static_cast<scheduler::impl::LayerHistoryV2*>(mLayerHistory.get());
     }
 
     size_t layerHistorySize() NO_THREAD_SAFETY_ANALYSIS {
         if (!mLayerHistory) return 0;
-        return mUseContentDetectionV2 ? mutableLayerHistoryV2()->mLayerInfos.size()
-                                      : mutableLayerHistory()->mLayerInfos.size();
+        return mOptions.useContentDetectionV2 ? mutableLayerHistoryV2()->mLayerInfos.size()
+                                              : mutableLayerHistory()->mLayerInfos.size();
     }
 
     void replaceTouchTimer(int64_t millis) {
@@ -93,18 +96,9 @@
         // not report a leaked object, since the Scheduler instance may
         // still be referenced by something despite our best efforts to destroy
         // it after each test is done.
-        mutableEventControlThread().reset();
-        mutablePrimaryDispSync().reset();
+        mVsyncSchedule.sync.reset();
         mConnections.clear();
     }
-
-private:
-    void changeRefreshRate(const RefreshRate&, ConfigEvent) override { mRefreshRateChangeCount++; }
-
-    void repaintEverythingForHWC() override {}
-    void kernelTimerChanged(bool /*expired*/) override {}
-
-    size_t mRefreshRateChangeCount = 0;
 };
 
 } // namespace android
diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
index 7489d70..959284a 100644
--- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
+++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
@@ -69,11 +69,6 @@
         return nullptr;
     }
 
-    std::unique_ptr<EventControlThread> createEventControlThread(
-            std::function<void(bool)>) override {
-        return nullptr;
-    }
-
     std::unique_ptr<HWComposer> createHWComposer(const std::string&) override {
         return nullptr;
     }
@@ -87,8 +82,7 @@
         return std::make_unique<scheduler::FakePhaseOffsets>();
     }
 
-    std::unique_ptr<Scheduler> createScheduler(std::function<void(bool)>,
-                                               const scheduler::RefreshRateConfigs&,
+    std::unique_ptr<Scheduler> createScheduler(const scheduler::RefreshRateConfigs&,
                                                ISchedulerCallback&) override {
         return nullptr;
     }
@@ -175,7 +169,7 @@
 
 } // namespace surfaceflinger::test
 
-class TestableSurfaceFlinger {
+class TestableSurfaceFlinger final : private ISchedulerCallback {
 public:
     using HotplugEvent = SurfaceFlinger::HotplugEvent;
 
@@ -198,11 +192,11 @@
         mFlinger->mCompositionEngine->setTimeStats(timeStats);
     }
 
+    // The ISchedulerCallback argument can be nullptr for a no-op implementation.
     void setupScheduler(std::unique_ptr<DispSync> primaryDispSync,
-                        std::unique_ptr<EventControlThread> eventControlThread,
                         std::unique_ptr<EventThread> appEventThread,
                         std::unique_ptr<EventThread> sfEventThread,
-                        bool hasMultipleConfigs = false) {
+                        ISchedulerCallback* callback = nullptr, bool hasMultipleConfigs = false) {
         std::vector<std::shared_ptr<const HWC2::Display::Config>> configs{
                 HWC2::Display::Config::Builder(mDisplay, 0)
                         .setVsyncPeriod(16'666'667)
@@ -227,8 +221,8 @@
 
         constexpr bool kUseContentDetectionV2 = false;
         mScheduler =
-                new TestableScheduler(std::move(primaryDispSync), std::move(eventControlThread),
-                                      *mFlinger->mRefreshRateConfigs, kUseContentDetectionV2);
+                new TestableScheduler(std::move(primaryDispSync), *mFlinger->mRefreshRateConfigs,
+                                      *(callback ?: this), kUseContentDetectionV2);
 
         mFlinger->mAppConnectionHandle = mScheduler->createConnection(std::move(appEventThread));
         mFlinger->mSfConnectionHandle = mScheduler->createConnection(std::move(sfEventThread));
@@ -667,6 +661,12 @@
         const std::optional<hal::HWDisplayId> mHwcDisplayId;
     };
 
+private:
+    void setVsyncEnabled(bool) override {}
+    void changeRefreshRate(const Scheduler::RefreshRate&, Scheduler::ConfigEvent) override {}
+    void repaintEverythingForHWC() override {}
+    void kernelTimerChanged(bool) override {}
+
     surfaceflinger::test::Factory mFactory;
     sp<SurfaceFlinger> mFlinger = new SurfaceFlinger(mFactory, SurfaceFlinger::SkipInitialization);
     TestableScheduler* mScheduler = nullptr;
diff --git a/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp b/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp
index 2a48a22..44b3dc0 100644
--- a/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp
+++ b/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp
@@ -32,7 +32,6 @@
 #include "TestableScheduler.h"
 #include "TestableSurfaceFlinger.h"
 #include "mock/MockDispSync.h"
-#include "mock/MockEventControlThread.h"
 #include "mock/MockEventThread.h"
 #include "mock/MockMessageQueue.h"
 
@@ -81,7 +80,6 @@
                 .WillRepeatedly(Return(FakeHwcDisplayInjector::DEFAULT_REFRESH_RATE));
 
         mFlinger.setupScheduler(std::unique_ptr<mock::DispSync>(mPrimaryDispSync),
-                                std::make_unique<mock::EventControlThread>(),
                                 std::move(eventThread), std::move(sfEventThread));
     }
 
@@ -89,7 +87,6 @@
     TestableSurfaceFlinger mFlinger;
 
     std::unique_ptr<mock::EventThread> mEventThread = std::make_unique<mock::EventThread>();
-    mock::EventControlThread* mEventControlThread = new mock::EventControlThread();
 
     mock::MessageQueue* mMessageQueue = new mock::MessageQueue();
     mock::DispSync* mPrimaryDispSync = new mock::DispSync();
diff --git a/services/surfaceflinger/tests/unittests/VSyncModulatorTest.cpp b/services/surfaceflinger/tests/unittests/VSyncModulatorTest.cpp
index 9c1ec07..af1e84b 100644
--- a/services/surfaceflinger/tests/unittests/VSyncModulatorTest.cpp
+++ b/services/surfaceflinger/tests/unittests/VSyncModulatorTest.cpp
@@ -27,15 +27,15 @@
 
 namespace android::scheduler {
 
-class MockScheduler : public IPhaseOffsetControl {
+class MockScheduler final : public IPhaseOffsetControl {
 public:
-    void setPhaseOffset(ConnectionHandle handle, nsecs_t phaseOffset) {
-        mPhaseOffset[handle] = phaseOffset;
-    }
-
     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;
 };
 
diff --git a/services/surfaceflinger/tests/unittests/mock/MockEventControlThread.cpp b/services/surfaceflinger/tests/unittests/mock/MockEventControlThread.cpp
deleted file mode 100644
index f9bacc8..0000000
--- a/services/surfaceflinger/tests/unittests/mock/MockEventControlThread.cpp
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "mock/MockEventControlThread.h"
-
-namespace android {
-namespace mock {
-
-// Explicit default instantiation is recommended.
-EventControlThread::EventControlThread() = default;
-EventControlThread::~EventControlThread() = default;
-
-} // namespace mock
-} // namespace android
diff --git a/services/surfaceflinger/tests/unittests/mock/MockEventControlThread.h b/services/surfaceflinger/tests/unittests/mock/MockEventControlThread.h
deleted file mode 100644
index 6ef352a..0000000
--- a/services/surfaceflinger/tests/unittests/mock/MockEventControlThread.h
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#pragma once
-
-#include <gmock/gmock.h>
-
-#include "Scheduler/EventControlThread.h"
-
-namespace android {
-namespace mock {
-
-class EventControlThread : public android::EventControlThread {
-public:
-    EventControlThread();
-    ~EventControlThread() override;
-
-    MOCK_METHOD1(setVsyncEnabled, void(bool));
-};
-
-} // namespace mock
-} // namespace android
diff --git a/services/surfaceflinger/tests/unittests/mock/MockSchedulerCallback.h b/services/surfaceflinger/tests/unittests/mock/MockSchedulerCallback.h
new file mode 100644
index 0000000..72bc89c
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/mock/MockSchedulerCallback.h
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <gmock/gmock.h>
+
+#include "Scheduler/Scheduler.h"
+
+namespace android::mock {
+
+struct SchedulerCallback final : ISchedulerCallback {
+    MOCK_METHOD1(setVsyncEnabled, void(bool));
+    MOCK_METHOD2(changeRefreshRate,
+                 void(const scheduler::RefreshRateConfigs::RefreshRate&,
+                      scheduler::RefreshRateConfigEvent));
+    MOCK_METHOD0(repaintEverythingForHWC, void());
+    MOCK_METHOD1(kernelTimerChanged, void(bool));
+};
+
+struct NoOpSchedulerCallback final : ISchedulerCallback {
+    void setVsyncEnabled(bool) override {}
+    void changeRefreshRate(const scheduler::RefreshRateConfigs::RefreshRate&,
+                           scheduler::RefreshRateConfigEvent) override {}
+    void repaintEverythingForHWC() override {}
+    void kernelTimerChanged(bool) override {}
+};
+
+} // namespace android::mock