SF: Introduce a configure stage

Decouple hotplug processing from commit of display transactions, as a
step toward per-display commit/composite. The configure stage will be
responsible for mode setting as well.

Bug: 241285876
Test: libsurfaceflinger_unittest
Change-Id: I72b7223760fa5896debb046f158845a0b4f4599a
diff --git a/services/surfaceflinger/Scheduler/MessageQueue.cpp b/services/surfaceflinger/Scheduler/MessageQueue.cpp
index e1ac301..e4e65b4 100644
--- a/services/surfaceflinger/Scheduler/MessageQueue.cpp
+++ b/services/surfaceflinger/Scheduler/MessageQueue.cpp
@@ -158,6 +158,19 @@
     mLooper->sendMessage(handler, Message());
 }
 
+void MessageQueue::scheduleConfigure() {
+    struct ConfigureHandler : MessageHandler {
+        explicit ConfigureHandler(ICompositor& compositor) : compositor(compositor) {}
+
+        void handleMessage(const Message&) override { compositor.configure(); }
+
+        ICompositor& compositor;
+    };
+
+    // TODO(b/241285876): Batch configure tasks that happen within some duration.
+    postMessage(sp<ConfigureHandler>::make(mCompositor));
+}
+
 void MessageQueue::scheduleFrame() {
     ATRACE_CALL();
 
diff --git a/services/surfaceflinger/Scheduler/MessageQueue.h b/services/surfaceflinger/Scheduler/MessageQueue.h
index 506f27b..899233a 100644
--- a/services/surfaceflinger/Scheduler/MessageQueue.h
+++ b/services/surfaceflinger/Scheduler/MessageQueue.h
@@ -38,6 +38,7 @@
 namespace android {
 
 struct ICompositor {
+    virtual void configure() = 0;
     virtual bool commit(TimePoint frameTime, VsyncId, TimePoint expectedVsyncTime) = 0;
     virtual void composite(TimePoint frameTime, VsyncId) = 0;
     virtual void sample() = 0;
@@ -78,6 +79,7 @@
     virtual void setInjector(sp<EventThreadConnection>) = 0;
     virtual void waitMessage() = 0;
     virtual void postMessage(sp<MessageHandler>&&) = 0;
+    virtual void scheduleConfigure() = 0;
     virtual void scheduleFrame() = 0;
 
     using Clock = std::chrono::steady_clock;
@@ -152,6 +154,7 @@
     void waitMessage() override;
     void postMessage(sp<MessageHandler>&&) override;
 
+    void scheduleConfigure() override;
     void scheduleFrame() override;
 
     std::optional<Clock::time_point> getScheduledFrameTime() const override;
diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h
index 4730493..eb3856d 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.h
+++ b/services/surfaceflinger/Scheduler/Scheduler.h
@@ -116,6 +116,7 @@
     using Impl::getScheduledFrameTime;
     using Impl::setDuration;
 
+    using Impl::scheduleConfigure;
     using Impl::scheduleFrame;
 
     // Schedule an asynchronous or synchronous task on the main thread.
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index f7146c6..85c003a 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -763,10 +763,10 @@
 
 // Do not call property_set on main thread which will be blocked by init
 // Use StartPropertySetThread instead.
-void SurfaceFlinger::init() {
+void SurfaceFlinger::init() FTL_FAKE_GUARD(kMainThreadContext) {
     ALOGI(  "SurfaceFlinger's main thread ready to run. "
             "Initializing graphics H/W...");
-    Mutex::Autolock _l(mStateLock);
+    Mutex::Autolock lock(mStateLock);
 
     // Get a RenderEngine for the given display / config (can't fail)
     // TODO(b/77156734): We need to stop casting and use HAL types when possible.
@@ -806,12 +806,14 @@
     }
 
     // Process any initial hotplug and resulting display changes.
-    processDisplayHotplugEventsLocked();
+    LOG_ALWAYS_FATAL_IF(!configureLocked(),
+                        "Initial display configuration failed: HWC did not hotplug");
+    processDisplayChangesLocked();
+
     const auto display = getDefaultDisplayDeviceLocked();
-    LOG_ALWAYS_FATAL_IF(!display, "Missing primary display after registering composer callback.");
-    const auto displayId = display->getPhysicalId();
-    LOG_ALWAYS_FATAL_IF(!getHwComposer().isConnected(displayId),
-                        "Primary display is disconnected.");
+    LOG_ALWAYS_FATAL_IF(!display, "Failed to configure the primary display");
+    LOG_ALWAYS_FATAL_IF(!getHwComposer().isConnected(display->getPhysicalId()),
+                        "Primary display is disconnected");
 
     // initialize our drawing state
     mDrawingState = mCurrentState;
@@ -1869,23 +1871,14 @@
 
 void SurfaceFlinger::onComposerHalHotplug(hal::HWDisplayId hwcDisplayId,
                                           hal::Connection connection) {
-    const bool connected = connection == hal::Connection::CONNECTED;
-    ALOGI("%s HAL display %" PRIu64, connected ? "Connecting" : "Disconnecting", hwcDisplayId);
-
-    // Only lock if we're not on the main thread. This function is normally
-    // called on a hwbinder thread, but for the primary display it's called on
-    // the main thread with the state lock already held, so don't attempt to
-    // acquire it here.
-    ConditionalLock lock(mStateLock, std::this_thread::get_id() != mMainThreadId);
-
-    mPendingHotplugEvents.emplace_back(HotplugEvent{hwcDisplayId, connection});
-
-    if (std::this_thread::get_id() == mMainThreadId) {
-        // Process all pending hot plug events immediately if we are on the main thread.
-        processDisplayHotplugEventsLocked();
+    {
+        std::lock_guard<std::mutex> lock(mHotplugMutex);
+        mPendingHotplugEvents.push_back(HotplugEvent{hwcDisplayId, connection});
     }
 
-    setTransactionFlags(eDisplayTransactionNeeded);
+    if (mScheduler) {
+        mScheduler->scheduleConfigure();
+    }
 }
 
 void SurfaceFlinger::onComposerHalVsyncPeriodTimingChanged(
@@ -1958,6 +1951,13 @@
     return vsyncDeadline + schedule.period();
 }
 
+void SurfaceFlinger::configure() FTL_FAKE_GUARD(kMainThreadContext) {
+    Mutex::Autolock lock(mStateLock);
+    if (configureLocked()) {
+        setTransactionFlags(eDisplayTransactionNeeded);
+    }
+}
+
 bool SurfaceFlinger::commit(TimePoint frameTime, VsyncId vsyncId, TimePoint expectedVsyncTime)
         FTL_FAKE_GUARD(kMainThreadContext) {
     // The expectedVsyncTime, which was predicted when this frame was scheduled, is normally in the
@@ -2642,8 +2642,14 @@
     return {modes, activeMode};
 }
 
-void SurfaceFlinger::processDisplayHotplugEventsLocked() {
-    for (const auto& event : mPendingHotplugEvents) {
+bool SurfaceFlinger::configureLocked() {
+    std::vector<HotplugEvent> events;
+    {
+        std::lock_guard<std::mutex> lock(mHotplugMutex);
+        events = std::move(mPendingHotplugEvents);
+    }
+
+    for (const auto& event : events) {
         std::optional<DisplayIdentificationInfo> info =
                 getHwComposer().onHotplug(event.hwcDisplayId, event.connection);
 
@@ -2653,6 +2659,7 @@
 
         const auto displayId = info->id;
         const auto token = mPhysicalDisplayTokens.get(displayId);
+        const char* log;
 
         if (event.connection == hal::Connection::CONNECTED) {
             auto [supportedModes, activeMode] = loadDisplayModes(displayId);
@@ -2664,7 +2671,7 @@
             }
 
             if (!token) {
-                ALOGV("Creating display %s", to_string(displayId).c_str());
+                log = "Connecting";
 
                 DisplayDeviceState state;
                 state.physical = {.id = displayId,
@@ -2681,7 +2688,7 @@
                 mPhysicalDisplayTokens.try_emplace(displayId, std::move(token));
                 mInterceptor->saveDisplayCreation(state);
             } else {
-                ALOGV("Recreating display %s", to_string(displayId).c_str());
+                log = "Reconnecting";
 
                 auto& state = mCurrentState.displays.editValueFor(token->get());
                 state.sequenceId = DisplayDeviceState{}.sequenceId; // Generate new sequenceId.
@@ -2692,7 +2699,7 @@
                 }
             }
         } else {
-            ALOGV("Removing display %s", to_string(displayId).c_str());
+            log = "Disconnecting";
 
             if (const ssize_t index = mCurrentState.displays.indexOfKey(token->get()); index >= 0) {
                 const DisplayDeviceState& state = mCurrentState.displays.valueAt(index);
@@ -2703,15 +2710,14 @@
             mPhysicalDisplayTokens.erase(displayId);
         }
 
-        processDisplayChangesLocked();
+        ALOGI("%s display %s (HAL ID %" PRIu64 ")", log, to_string(displayId).c_str(),
+              event.hwcDisplayId);
     }
 
-    mPendingHotplugEvents.clear();
+    return !events.empty();
 }
 
 void SurfaceFlinger::dispatchDisplayHotplugEvent(PhysicalDisplayId displayId, bool connected) {
-    ALOGI("Dispatching display hotplug event displayId=%s, connected=%d",
-          to_string(displayId).c_str(), connected);
     mScheduler->onHotplugReceived(mAppConnectionHandle, displayId, connected);
     mScheduler->onHotplugReceived(mSfConnectionHandle, displayId, connected);
 }
@@ -3041,7 +3047,6 @@
     const bool displayTransactionNeeded = transactionFlags & eDisplayTransactionNeeded;
     if (displayTransactionNeeded) {
         processDisplayChangesLocked();
-        processDisplayHotplugEventsLocked();
     }
     mForceTransactionDisplayChange = displayTransactionNeeded;
 
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index 4d4d263..bbe1589 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -432,11 +432,6 @@
         FINISHED,
     };
 
-    struct HotplugEvent {
-        hal::HWDisplayId hwcDisplayId;
-        hal::Connection connection = hal::Connection::INVALID;
-    };
-
     template <typename F, std::enable_if_t<!std::is_member_function_pointer_v<F>>* = nullptr>
     static Dumper dumper(F&& dump) {
         using namespace std::placeholders;
@@ -611,6 +606,9 @@
 
     // ICompositor overrides:
 
+    // Configures physical displays, processing hotplug and/or mode setting via the Composer HAL.
+    void configure() override;
+
     // Commits transactions for layers and displays. Returns whether any state has been invalidated,
     // i.e. whether a frame should be composited for each display.
     bool commit(TimePoint frameTime, VsyncId, TimePoint expectedVsyncTime) override;
@@ -622,9 +620,7 @@
     // Samples the composited frame via RegionSamplingThread.
     void sample() override;
 
-    /*
-     * ISchedulerCallback
-     */
+    // ISchedulerCallback overrides:
 
     // Toggles hardware VSYNC by calling into HWC.
     void setVsyncEnabled(bool) override;
@@ -634,6 +630,7 @@
     void kernelTimerChanged(bool expired) override;
     // Called when the frame rate override list changed to trigger an event.
     void triggerOnFrameRateOverridesChanged() override;
+
     // Toggles the kernel idle timer on or off depending the policy decisions around refresh rates.
     void toggleKernelIdleTimer() REQUIRES(mStateLock);
     // Get the controller and timeout that will help decide how the kernel idle timer will be
@@ -911,6 +908,13 @@
     std::pair<DisplayModes, DisplayModePtr> loadDisplayModes(PhysicalDisplayId) const
             REQUIRES(mStateLock);
 
+    // TODO(b/241285876): Move to DisplayConfigurator.
+    //
+    // Returns whether displays have been added/changed/removed, i.e. whether ICompositor should
+    // commit display transactions.
+    bool configureLocked() REQUIRES(mStateLock) REQUIRES(kMainThreadContext)
+            EXCLUDES(mHotplugMutex);
+
     sp<DisplayDevice> setupNewDisplayDeviceInternal(
             const wp<IBinder>& displayToken,
             std::shared_ptr<compositionengine::Display> compositionDisplay,
@@ -922,7 +926,6 @@
     void processDisplayChanged(const wp<IBinder>& displayToken,
                                const DisplayDeviceState& currentState,
                                const DisplayDeviceState& drawingState) REQUIRES(mStateLock);
-    void processDisplayHotplugEventsLocked() REQUIRES(mStateLock);
 
     void dispatchDisplayHotplugEvent(PhysicalDisplayId displayId, bool connected);
 
@@ -1145,7 +1148,13 @@
 
     BootStage mBootStage = BootStage::BOOTLOADER;
 
-    std::vector<HotplugEvent> mPendingHotplugEvents GUARDED_BY(mStateLock);
+    struct HotplugEvent {
+        hal::HWDisplayId hwcDisplayId;
+        hal::Connection connection = hal::Connection::INVALID;
+    };
+
+    std::mutex mHotplugMutex;
+    std::vector<HotplugEvent> mPendingHotplugEvents GUARDED_BY(mHotplugMutex);
 
     // Displays are composited in `mDisplays` order. Internal displays are inserted at boot and
     // never removed, so take precedence over external and virtual displays.
diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h
index 4c649f3..3aa3633 100644
--- a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h
+++ b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h
@@ -278,6 +278,7 @@
 
 private:
     // ICompositor overrides:
+    void configure() override {}
     bool commit(TimePoint, VsyncId, TimePoint) override { return false; }
     void composite(TimePoint, VsyncId) override {}
     void sample() override {}
diff --git a/services/surfaceflinger/tests/unittests/DisplayDevice_InitiateModeChange.cpp b/services/surfaceflinger/tests/unittests/DisplayDevice_InitiateModeChange.cpp
index 93af225..982b9ff 100644
--- a/services/surfaceflinger/tests/unittests/DisplayDevice_InitiateModeChange.cpp
+++ b/services/surfaceflinger/tests/unittests/DisplayDevice_InitiateModeChange.cpp
@@ -42,6 +42,7 @@
         PrimaryDisplayVariant::setupHwcGetActiveConfigCallExpectations(this);
 
         mFlinger.onComposerHalHotplug(PrimaryDisplayVariant::HWC_DISPLAY_ID, Connection::CONNECTED);
+        mFlinger.configureAndCommit();
 
         mDisplay = PrimaryDisplayVariant::makeFakeExistingDisplayInjector(this)
                            .setDisplayModes(makeModes(kMode60, kMode90, kMode120), kModeId60)
diff --git a/services/surfaceflinger/tests/unittests/MessageQueueTest.cpp b/services/surfaceflinger/tests/unittests/MessageQueueTest.cpp
index f20b9f9..5e1042e 100644
--- a/services/surfaceflinger/tests/unittests/MessageQueueTest.cpp
+++ b/services/surfaceflinger/tests/unittests/MessageQueueTest.cpp
@@ -32,6 +32,7 @@
 using CallbackToken = scheduler::VSyncDispatch::CallbackToken;
 
 struct NoOpCompositor final : ICompositor {
+    void configure() override {}
     bool commit(TimePoint, VsyncId, TimePoint) override { return false; }
     void composite(TimePoint, VsyncId) override {}
     void sample() override {}
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp
index b607b88..57937dc 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp
@@ -40,18 +40,17 @@
         PrimaryDisplayVariant::setupNativeWindowSurfaceCreationCallExpectations(this);
         PrimaryDisplayVariant::setupHwcGetActiveConfigCallExpectations(this);
 
+        DisplayModes modes = makeModes(kMode60, kMode90, kMode120, kMode90_4K);
+        auto configs = std::make_shared<scheduler::RefreshRateConfigs>(modes, kModeId60);
+
+        setupScheduler(configs);
+
         mFlinger.onComposerHalHotplug(PrimaryDisplayVariant::HWC_DISPLAY_ID, Connection::CONNECTED);
+        mFlinger.configureAndCommit();
 
-        {
-            DisplayModes modes = makeModes(kMode60, kMode90, kMode120, kMode90_4K);
-            auto configs = std::make_shared<scheduler::RefreshRateConfigs>(modes, kModeId60);
-
-            mDisplay = PrimaryDisplayVariant::makeFakeExistingDisplayInjector(this)
-                               .setDisplayModes(std::move(modes), kModeId60, std::move(configs))
-                               .inject();
-        }
-
-        setupScheduler(mDisplay->holdRefreshRateConfigs());
+        mDisplay = PrimaryDisplayVariant::makeFakeExistingDisplayInjector(this)
+                           .setDisplayModes(std::move(modes), kModeId60, std::move(configs))
+                           .inject();
 
         // isVsyncPeriodSwitchSupported should return true, otherwise the SF's HWC proxy
         // will call setActiveConfig instead of setActiveConfigWithConstraints.
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayTransactionCommitTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayTransactionCommitTest.cpp
index bc9e801..71f1a2b 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayTransactionCommitTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayTransactionCommitTest.cpp
@@ -175,7 +175,7 @@
     // --------------------------------------------------------------------
     // Invocation
 
-    mFlinger.commitTransactionsLocked(eDisplayTransactionNeeded);
+    mFlinger.configureAndCommit();
 
     // --------------------------------------------------------------------
     // Postconditions
@@ -204,7 +204,7 @@
     // --------------------------------------------------------------------
     // Invocation
 
-    mFlinger.commitTransactionsLocked(eDisplayTransactionNeeded);
+    mFlinger.configureAndCommit();
 
     // --------------------------------------------------------------------
     // Postconditions
@@ -239,7 +239,7 @@
     // --------------------------------------------------------------------
     // Invocation
 
-    mFlinger.commitTransactionsLocked(eDisplayTransactionNeeded);
+    mFlinger.configureAndCommit();
 
     // --------------------------------------------------------------------
     // Postconditions
@@ -321,7 +321,7 @@
                 // --------------------------------------------------------------------
                 // Invocation
 
-                mFlinger.commitTransactionsLocked(eDisplayTransactionNeeded);
+                mFlinger.configureAndCommit();
 
                 // --------------------------------------------------------------------
                 // Postconditions
@@ -366,7 +366,7 @@
                 // --------------------------------------------------------------------
                 // Invocation
 
-                mFlinger.commitTransactionsLocked(eDisplayTransactionNeeded);
+                mFlinger.configureAndCommit();
 
                 // --------------------------------------------------------------------
                 // Postconditions
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_HotplugTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_HotplugTest.cpp
index 1da3bb6..e5cf4d7 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_HotplugTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_HotplugTest.cpp
@@ -23,37 +23,15 @@
 
 class HotplugTest : public DisplayTransactionTest {};
 
-TEST_F(HotplugTest, enqueuesEventsForDisplayTransaction) {
+TEST_F(HotplugTest, schedulesConfigureToProcessHotplugEvents) {
+    EXPECT_CALL(*mFlinger.scheduler(), scheduleConfigure()).Times(2);
+
     constexpr HWDisplayId hwcDisplayId1 = 456;
-    constexpr HWDisplayId hwcDisplayId2 = 654;
-
-    // --------------------------------------------------------------------
-    // Preconditions
-
-    // Set the main thread id so that the current thread does not appear to be
-    // the main thread.
-    mFlinger.mutableMainThreadId() = std::thread::id();
-
-    // --------------------------------------------------------------------
-    // Call Expectations
-
-    // We expect a scheduled commit for the display transaction.
-    EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(1);
-
-    // --------------------------------------------------------------------
-    // Invocation
-
-    // Simulate two hotplug events (a connect and a disconnect)
     mFlinger.onComposerHalHotplug(hwcDisplayId1, Connection::CONNECTED);
+
+    constexpr HWDisplayId hwcDisplayId2 = 654;
     mFlinger.onComposerHalHotplug(hwcDisplayId2, Connection::DISCONNECTED);
 
-    // --------------------------------------------------------------------
-    // Postconditions
-
-    // The display transaction needed flag should be set.
-    EXPECT_TRUE(hasTransactionFlagSet(eDisplayTransactionNeeded));
-
-    // All events should be in the pending event queue.
     const auto& pendingEvents = mFlinger.mutablePendingHotplugEvents();
     ASSERT_EQ(2u, pendingEvents.size());
     EXPECT_EQ(hwcDisplayId1, pendingEvents[0].hwcDisplayId);
@@ -62,48 +40,16 @@
     EXPECT_EQ(Connection::DISCONNECTED, pendingEvents[1].connection);
 }
 
-TEST_F(HotplugTest, processesEnqueuedEventsIfCalledOnMainThread) {
-    constexpr HWDisplayId displayId1 = 456;
-
-    // --------------------------------------------------------------------
-    // Note:
-    // --------------------------------------------------------------------
-    // This test case is a bit tricky. We want to verify that
-    // onComposerHalHotplug() calls processDisplayHotplugEventsLocked(), but we
-    // don't really want to provide coverage for everything the later function
-    // does as there are specific tests for it.
-    // --------------------------------------------------------------------
-
-    // --------------------------------------------------------------------
-    // Preconditions
-
-    // Set the main thread id so that the current thread does appear to be the
-    // main thread.
-    mFlinger.mutableMainThreadId() = std::this_thread::get_id();
-
-    // --------------------------------------------------------------------
-    // Call Expectations
-
-    // We expect a scheduled commit for the display transaction.
+TEST_F(HotplugTest, schedulesFrameToCommitDisplayTransaction) {
     EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(1);
 
-    // --------------------------------------------------------------------
-    // Invocation
-
-    // Simulate a disconnect on a display id that is not connected. This should
-    // be enqueued by onComposerHalHotplug(), and dequeued by
-    // processDisplayHotplugEventsLocked(), but then ignored as invalid.
+    constexpr HWDisplayId displayId1 = 456;
     mFlinger.onComposerHalHotplug(displayId1, Connection::DISCONNECTED);
+    mFlinger.configure();
 
-    // --------------------------------------------------------------------
-    // Postconditions
-
-    // The display transaction needed flag should be set.
-    EXPECT_TRUE(hasTransactionFlagSet(eDisplayTransactionNeeded));
-
-    // There should be no event queued on return, as it should have been
-    // processed.
+    // The configure stage should consume the hotplug queue and produce a display transaction.
     EXPECT_TRUE(mFlinger.mutablePendingHotplugEvents().empty());
+    EXPECT_TRUE(hasTransactionFlagSet(eDisplayTransactionNeeded));
 }
 
 TEST_F(HotplugTest, rejectsHotplugIfFailedToLoadDisplayModes) {
@@ -125,14 +71,14 @@
             .WillOnce(Return(Error::NONE));
 
     ExternalDisplay::injectPendingHotplugEvent(this, Connection::CONNECTED);
-    mFlinger.processDisplayHotplugEvents();
+    mFlinger.configure();
 
     // The hotplug should be rejected, so no HWComposer::DisplayData should be created.
     EXPECT_FALSE(hasPhysicalHwcDisplay(ExternalDisplay::HWC_DISPLAY_ID));
 
     // Disconnecting a display that does not exist should be a no-op.
     ExternalDisplay::injectPendingHotplugEvent(this, Connection::DISCONNECTED);
-    mFlinger.processDisplayHotplugEvents();
+    mFlinger.configure();
 
     EXPECT_FALSE(hasPhysicalHwcDisplay(ExternalDisplay::HWC_DISPLAY_ID));
 }
diff --git a/services/surfaceflinger/tests/unittests/TestableScheduler.h b/services/surfaceflinger/tests/unittests/TestableScheduler.h
index 66cdfd7..93e3059 100644
--- a/services/surfaceflinger/tests/unittests/TestableScheduler.h
+++ b/services/surfaceflinger/tests/unittests/TestableScheduler.h
@@ -50,6 +50,7 @@
         });
     }
 
+    MOCK_METHOD(void, scheduleConfigure, (), (override));
     MOCK_METHOD(void, scheduleFrame, (), (override));
     MOCK_METHOD(void, postMessage, (sp<MessageHandler>&&), (override));
 
@@ -109,6 +110,7 @@
 
 private:
     // ICompositor overrides:
+    void configure() override {}
     bool commit(TimePoint, VsyncId, TimePoint) override { return false; }
     void composite(TimePoint, VsyncId) override {}
     void sample() override {}
diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
index 7dd9935..42585b5 100644
--- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
+++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
@@ -317,8 +317,15 @@
      * Forwarding for functions being tested
      */
 
-    TimePoint commit(TimePoint frameTime, VsyncId vsyncId, TimePoint expectedVSyncTime) {
-        mFlinger->commit(frameTime, vsyncId, expectedVSyncTime);
+    void configure() { mFlinger->configure(); }
+
+    void configureAndCommit() {
+        configure();
+        commitTransactionsLocked(eDisplayTransactionNeeded);
+    }
+
+    TimePoint commit(TimePoint frameTime, VsyncId vsyncId, TimePoint expectedVsyncTime) {
+        mFlinger->commit(frameTime, vsyncId, expectedVsyncTime);
         return frameTime;
     }
 
@@ -362,19 +369,15 @@
                                                        dispSurface, producer);
     }
 
-    auto commitTransactionsLocked(uint32_t transactionFlags) {
+    void commitTransactionsLocked(uint32_t transactionFlags) {
         Mutex::Autolock lock(mFlinger->mStateLock);
-        return mFlinger->commitTransactionsLocked(transactionFlags);
+        mFlinger->commitTransactionsLocked(transactionFlags);
     }
 
     void onComposerHalHotplug(hal::HWDisplayId hwcDisplayId, hal::Connection connection) {
         mFlinger->onComposerHalHotplug(hwcDisplayId, connection);
     }
 
-    void processDisplayHotplugEvents() {
-        FTL_FAKE_GUARD(mFlinger->mStateLock, mFlinger->processDisplayHotplugEventsLocked());
-    }
-
     auto setDisplayStateLocked(const DisplayState& s) {
         Mutex::Autolock lock(mFlinger->mStateLock);
         return mFlinger->setDisplayStateLocked(s);