SF: Remove EventControlThread

EventControlThread was a HWC workaround dating back to K for toggling
VSYNC off the main thread, but as of R it defers back to main only to
incur context switches.

Clean up TestableScheduler construction to skip creating DispSync and
timer threads, which fixes several gMock warnings of unexpected calls.

Remove virtual destructors for non-polymorphic interfaces.

Bug: 160012986
Test: systrace
Test: libsurfaceflinger_unittest
Change-Id: I01360016a7dba79dc905f250753e6fce34af0a90
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 3b562bf..76f2081 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 d56321d..9105fd3 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
@@ -535,8 +528,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;
@@ -549,6 +546,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 9add6a5..0c4c742 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());
@@ -897,7 +895,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()));
         });
@@ -936,8 +934,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 9130b04..e0e6873 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);
@@ -3212,9 +3211,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);
@@ -3227,29 +3226,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);
@@ -3308,7 +3307,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);
     }
 
@@ -3335,7 +3334,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);
     }
 
@@ -3347,7 +3346,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);
     }
 };
@@ -3365,7 +3364,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);
     }
 };
@@ -3394,7 +3393,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 256e048..cb748ba 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