SF: Clean up API for refresh rate selection

Define types for each step: ScoredRefreshRate, RefreshRateRanking,
RankedRefreshRates, DisplayModeChoice, and DisplayModeRequest. The
last will replace DisplayDevice::ActiveModeInfo in a follow-up CL.

Add Scheduler::mLeaderDisplayId (always the primary display for now)
and provisionally use its DisplayModeChoice until Scheduler::Policy
is tracked per display.

Rewrite multi-display tests, which relied on each DisplayMode having
the same PhysicalDisplayId, and did not actually verify mode/display
association (`expectedDisplays` was unused). Test RefreshRateRanking
ordering by descending score.

Bug: 241285191
Test: libsurfaceflinger_unittest
Change-Id: I1d24d6a1fa9285aa7fc4bf2dd6654fa660d27b08
diff --git a/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp b/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp
index 30483a2..39850c7 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp
+++ b/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp
@@ -23,6 +23,7 @@
 
 #include <chrono>
 #include <cmath>
+#include <deque>
 
 #include <android-base/properties.h>
 #include <android-base/stringprintf.h>
@@ -143,8 +144,7 @@
 
         ATRACE_INT(name.c_str(), static_cast<int>(std::round(overallScore * 100)));
 
-        constexpr float kEpsilon = 0.0001f;
-        if (std::abs(overallScore - rhs.overallScore) > kEpsilon) {
+        if (!ScoredRefreshRate::scoresEqual(overallScore, rhs.overallScore)) {
             return overallScore > rhs.overallScore;
         }
 
@@ -288,8 +288,7 @@
 }
 
 auto RefreshRateConfigs::getRankedRefreshRates(const std::vector<LayerRequirement>& layers,
-                                               GlobalSignals signals) const
-        -> std::pair<std::vector<RefreshRateRanking>, GlobalSignals> {
+                                               GlobalSignals signals) const -> RankedRefreshRates {
     std::lock_guard lock(mLock);
 
     if (mGetRankedRefreshRatesCache &&
@@ -304,7 +303,7 @@
 
 auto RefreshRateConfigs::getRankedRefreshRatesLocked(const std::vector<LayerRequirement>& layers,
                                                      GlobalSignals signals) const
-        -> std::pair<std::vector<RefreshRateRanking>, GlobalSignals> {
+        -> RankedRefreshRates {
     using namespace fps_approx_ops;
     ATRACE_CALL();
     ALOGV("%s: %zu layers", __func__, layers.size());
@@ -314,8 +313,7 @@
     // Keep the display at max refresh rate for the duration of powering on the display.
     if (signals.powerOnImminent) {
         ALOGV("Power On Imminent");
-        return {getRefreshRatesByPolicyLocked(activeMode.getGroup(), RefreshRateOrder::Descending,
-                                              /*preferredDisplayModeOpt*/ std::nullopt),
+        return {rankRefreshRates(activeMode.getGroup(), RefreshRateOrder::Descending),
                 GlobalSignals{.powerOnImminent = true}};
     }
 
@@ -375,8 +373,7 @@
     // selected a refresh rate to see if we should apply touch boost.
     if (signals.touch && !hasExplicitVoteLayers) {
         ALOGV("Touch Boost");
-        return {getRefreshRatesByPolicyLocked(anchorGroup, RefreshRateOrder::Descending,
-                                              /*preferredDisplayModeOpt*/ std::nullopt),
+        return {rankRefreshRates(anchorGroup, RefreshRateOrder::Descending),
                 GlobalSignals{.touch = true}};
     }
 
@@ -388,24 +385,19 @@
 
     if (!signals.touch && signals.idle && !(primaryRangeIsSingleRate && hasExplicitVoteLayers)) {
         ALOGV("Idle");
-        return {getRefreshRatesByPolicyLocked(activeMode.getGroup(), RefreshRateOrder::Ascending,
-                                              /*preferredDisplayModeOpt*/ std::nullopt),
+        return {rankRefreshRates(activeMode.getGroup(), RefreshRateOrder::Ascending),
                 GlobalSignals{.idle = true}};
     }
 
     if (layers.empty() || noVoteLayers == layers.size()) {
         ALOGV("No layers with votes");
-        return {getRefreshRatesByPolicyLocked(anchorGroup, RefreshRateOrder::Descending,
-                                              /*preferredDisplayModeOpt*/ std::nullopt),
-                kNoSignals};
+        return {rankRefreshRates(anchorGroup, RefreshRateOrder::Descending), kNoSignals};
     }
 
     // Only if all layers want Min we should return Min
     if (noVoteLayers + minVoteLayers == layers.size()) {
         ALOGV("All layers Min");
-        return {getRefreshRatesByPolicyLocked(activeMode.getGroup(), RefreshRateOrder::Ascending,
-                                              /*preferredDisplayModeOpt*/ std::nullopt),
-                kNoSignals};
+        return {rankRefreshRates(activeMode.getGroup(), RefreshRateOrder::Ascending), kNoSignals};
     }
 
     // Find the best refresh rate based on score
@@ -557,12 +549,13 @@
             maxVoteLayers > 0 ? RefreshRateOrder::Descending : RefreshRateOrder::Ascending;
     std::sort(scores.begin(), scores.end(),
               RefreshRateScoreComparator{.refreshRateOrder = refreshRateOrder});
-    std::vector<RefreshRateRanking> rankedRefreshRates;
-    rankedRefreshRates.reserve(scores.size());
 
-    std::transform(scores.begin(), scores.end(), back_inserter(rankedRefreshRates),
+    RefreshRateRanking ranking;
+    ranking.reserve(scores.size());
+
+    std::transform(scores.begin(), scores.end(), back_inserter(ranking),
                    [](const RefreshRateScore& score) {
-                       return RefreshRateRanking{score.modeIt->second, score.overallScore};
+                       return ScoredRefreshRate{score.modeIt->second, score.overallScore};
                    });
 
     const bool noLayerScore = std::all_of(scores.begin(), scores.end(), [](RefreshRateScore score) {
@@ -574,11 +567,9 @@
         // range instead of picking a random score from the app range.
         if (noLayerScore) {
             ALOGV("Layers not scored");
-            return {getRefreshRatesByPolicyLocked(anchorGroup, RefreshRateOrder::Descending,
-                                                  /*preferredDisplayModeOpt*/ std::nullopt),
-                    kNoSignals};
+            return {rankRefreshRates(anchorGroup, RefreshRateOrder::Descending), kNoSignals};
         } else {
-            return {rankedRefreshRates, kNoSignals};
+            return {ranking, kNoSignals};
         }
     }
 
@@ -596,14 +587,12 @@
         }
     }();
 
-    const auto& touchRefreshRates =
-            getRefreshRatesByPolicyLocked(anchorGroup, RefreshRateOrder::Descending,
-                                          /*preferredDisplayModeOpt*/ std::nullopt);
+    const auto touchRefreshRates = rankRefreshRates(anchorGroup, RefreshRateOrder::Descending);
+
     using fps_approx_ops::operator<;
 
     if (signals.touch && explicitDefaultVoteLayers == 0 && touchBoostForExplicitExact &&
-        scores.front().modeIt->second->getFps() <
-                touchRefreshRates.front().displayModePtr->getFps()) {
+        scores.front().modeIt->second->getFps() < touchRefreshRates.front().modePtr->getFps()) {
         ALOGV("Touch Boost");
         return {touchRefreshRates, GlobalSignals{.touch = true}};
     }
@@ -612,12 +601,11 @@
     // current config
     if (noLayerScore && refreshRateOrder == RefreshRateOrder::Ascending) {
         const auto preferredDisplayMode = activeMode.getId();
-        return {getRefreshRatesByPolicyLocked(anchorGroup, RefreshRateOrder::Ascending,
-                                              preferredDisplayMode),
+        return {rankRefreshRates(anchorGroup, RefreshRateOrder::Ascending, preferredDisplayMode),
                 kNoSignals};
     }
 
-    return {rankedRefreshRates, kNoSignals};
+    return {ranking, kNoSignals};
 }
 
 std::unordered_map<uid_t, std::vector<const RefreshRateConfigs::LayerRequirement*>>
@@ -783,11 +771,12 @@
     return mPrimaryRefreshRates.back()->second;
 }
 
-std::vector<RefreshRateRanking> RefreshRateConfigs::getRefreshRatesByPolicyLocked(
+auto RefreshRateConfigs::rankRefreshRates(
         std::optional<int> anchorGroupOpt, RefreshRateOrder refreshRateOrder,
-        std::optional<DisplayModeId> preferredDisplayModeOpt) const {
-    std::deque<RefreshRateRanking> rankings;
-    const auto makeRanking = [&](const DisplayModeIterator it) REQUIRES(mLock) {
+        std::optional<DisplayModeId> preferredDisplayModeOpt) const -> RefreshRateRanking {
+    std::deque<ScoredRefreshRate> ranking;
+
+    const auto rankRefreshRate = [&](DisplayModeIterator it) REQUIRES(mLock) {
         const auto& mode = it->second;
         if (anchorGroupOpt && mode->getGroup() != anchorGroupOpt) {
             return;
@@ -800,31 +789,32 @@
         }
         if (preferredDisplayModeOpt) {
             if (*preferredDisplayModeOpt == mode->getId()) {
-                rankings.push_front(RefreshRateRanking{mode, /*score*/ 1.0f});
+                constexpr float kScore = std::numeric_limits<float>::max();
+                ranking.push_front(ScoredRefreshRate{mode, kScore});
                 return;
             }
             constexpr float kNonPreferredModePenalty = 0.95f;
             score *= kNonPreferredModePenalty;
         }
-        rankings.push_back(RefreshRateRanking{mode, score});
+        ranking.push_back(ScoredRefreshRate{mode, score});
     };
 
     if (refreshRateOrder == RefreshRateOrder::Ascending) {
-        std::for_each(mPrimaryRefreshRates.begin(), mPrimaryRefreshRates.end(), makeRanking);
+        std::for_each(mPrimaryRefreshRates.begin(), mPrimaryRefreshRates.end(), rankRefreshRate);
     } else {
-        std::for_each(mPrimaryRefreshRates.rbegin(), mPrimaryRefreshRates.rend(), makeRanking);
+        std::for_each(mPrimaryRefreshRates.rbegin(), mPrimaryRefreshRates.rend(), rankRefreshRate);
     }
 
-    if (!rankings.empty() || !anchorGroupOpt) {
-        return {rankings.begin(), rankings.end()};
+    if (!ranking.empty() || !anchorGroupOpt) {
+        return {ranking.begin(), ranking.end()};
     }
 
     ALOGW("Can't find %s refresh rate by policy with the same mode group"
           " as the mode group %d",
           refreshRateOrder == RefreshRateOrder::Ascending ? "min" : "max", anchorGroupOpt.value());
 
-    return getRefreshRatesByPolicyLocked(/*anchorGroupOpt*/ std::nullopt, refreshRateOrder,
-                                         preferredDisplayModeOpt);
+    constexpr std::optional<int> kNoAnchorGroup = std::nullopt;
+    return rankRefreshRates(kNoAnchorGroup, refreshRateOrder, preferredDisplayModeOpt);
 }
 
 DisplayModePtr RefreshRateConfigs::getActiveModePtr() const {
diff --git a/services/surfaceflinger/Scheduler/RefreshRateConfigs.h b/services/surfaceflinger/Scheduler/RefreshRateConfigs.h
index 7219584..99f81aa 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateConfigs.h
+++ b/services/surfaceflinger/Scheduler/RefreshRateConfigs.h
@@ -23,6 +23,7 @@
 #include <utility>
 #include <variant>
 
+#include <ftl/concat.h>
 #include <gui/DisplayEventReceiver.h>
 
 #include <scheduler/Fps.h>
@@ -46,15 +47,6 @@
     return static_cast<DisplayModeEvent>(static_cast<T>(lhs) | static_cast<T>(rhs));
 }
 
-struct RefreshRateRanking {
-    DisplayModePtr displayModePtr;
-    float score = 0.0f;
-
-    bool operator==(const RefreshRateRanking& ranking) const {
-        return displayModePtr == ranking.displayModePtr && score == ranking.score;
-    }
-};
-
 using FrameRateOverride = DisplayEventReceiver::Event::FrameRateOverride;
 
 /**
@@ -208,12 +200,46 @@
             return touch == other.touch && idle == other.idle &&
                     powerOnImminent == other.powerOnImminent;
         }
+
+        auto toString() const {
+            return ftl::Concat("{touch=", touch, ", idle=", idle,
+                               ", powerOnImminent=", powerOnImminent, '}');
+        }
     };
 
-    // Returns the list in the descending order of refresh rates desired
-    // based on their overall score, and the GlobalSignals that were considered.
-    std::pair<std::vector<RefreshRateRanking>, GlobalSignals> getRankedRefreshRates(
-            const std::vector<LayerRequirement>&, GlobalSignals) const EXCLUDES(mLock);
+    struct ScoredRefreshRate {
+        DisplayModePtr modePtr;
+        float score = 0.0f;
+
+        bool operator==(const ScoredRefreshRate& other) const {
+            return modePtr == other.modePtr && score == other.score;
+        }
+
+        static bool scoresEqual(float lhs, float rhs) {
+            constexpr float kEpsilon = 0.0001f;
+            return std::abs(lhs - rhs) <= kEpsilon;
+        }
+
+        struct DescendingScore {
+            bool operator()(const ScoredRefreshRate& lhs, const ScoredRefreshRate& rhs) const {
+                return lhs.score > rhs.score && !scoresEqual(lhs.score, rhs.score);
+            }
+        };
+    };
+
+    using RefreshRateRanking = std::vector<ScoredRefreshRate>;
+
+    struct RankedRefreshRates {
+        RefreshRateRanking ranking; // Ordered by descending score.
+        GlobalSignals consideredSignals;
+
+        bool operator==(const RankedRefreshRates& other) const {
+            return ranking == other.ranking && consideredSignals == other.consideredSignals;
+        }
+    };
+
+    RankedRefreshRates getRankedRefreshRates(const std::vector<LayerRequirement>&,
+                                             GlobalSignals) const EXCLUDES(mLock);
 
     FpsRange getSupportedRefreshRateRange() const EXCLUDES(mLock) {
         std::lock_guard lock(mLock);
@@ -354,8 +380,8 @@
     // See mActiveModeIt for thread safety.
     DisplayModeIterator getActiveModeItLocked() const REQUIRES(mLock);
 
-    std::pair<std::vector<RefreshRateRanking>, GlobalSignals> getRankedRefreshRatesLocked(
-            const std::vector<LayerRequirement>&, GlobalSignals) const REQUIRES(mLock);
+    RankedRefreshRates getRankedRefreshRatesLocked(const std::vector<LayerRequirement>&,
+                                                   GlobalSignals) const REQUIRES(mLock);
 
     // Returns number of display frames and remainder when dividing the layer refresh period by
     // display refresh period.
@@ -373,11 +399,10 @@
 
     enum class RefreshRateOrder { Ascending, Descending };
 
-    // Returns the rankings in RefreshRateOrder. May change at runtime.
     // Only uses the primary range, not the app request range.
-    std::vector<RefreshRateRanking> getRefreshRatesByPolicyLocked(
-            std::optional<int> anchorGroupOpt, RefreshRateOrder,
-            std::optional<DisplayModeId> preferredDisplayModeOpt) const REQUIRES(mLock);
+    RefreshRateRanking rankRefreshRates(std::optional<int> anchorGroupOpt, RefreshRateOrder,
+                                        std::optional<DisplayModeId> preferredDisplayModeOpt =
+                                                std::nullopt) const REQUIRES(mLock);
 
     const Policy* getCurrentPolicyLocked() const REQUIRES(mLock);
     bool isPolicyValidLocked(const Policy& policy) const REQUIRES(mLock);
@@ -436,7 +461,7 @@
 
     struct GetRankedRefreshRatesCache {
         std::pair<std::vector<LayerRequirement>, GlobalSignals> arguments;
-        std::pair<std::vector<RefreshRateRanking>, GlobalSignals> result;
+        RankedRefreshRates result;
     };
     mutable std::optional<GetRankedRefreshRatesCache> mGetRankedRefreshRatesCache GUARDED_BY(mLock);
 
diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp
index 30f2c27..be3ebb7 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.cpp
+++ b/services/surfaceflinger/Scheduler/Scheduler.cpp
@@ -127,11 +127,19 @@
 }
 
 void Scheduler::registerDisplay(sp<const DisplayDevice> display) {
+    if (display->isPrimary()) {
+        mLeaderDisplayId = display->getPhysicalId();
+    }
+
     const bool ok = mDisplays.try_emplace(display->getPhysicalId(), std::move(display)).second;
     ALOGE_IF(!ok, "%s: Duplicate display", __func__);
 }
 
 void Scheduler::unregisterDisplay(PhysicalDisplayId displayId) {
+    if (mLeaderDisplayId == displayId) {
+        mLeaderDisplayId.reset();
+    }
+
     mDisplays.erase(displayId);
 }
 
@@ -631,9 +639,8 @@
 
 template <typename S, typename T>
 auto Scheduler::applyPolicy(S Policy::*statePtr, T&& newState) -> GlobalSignals {
-    DisplayModePtr newMode;
+    std::vector<display::DisplayModeRequest> modeRequests;
     GlobalSignals consideredSignals;
-    std::vector<DisplayModeConfig> displayModeConfigs;
 
     bool refreshRateChanged = false;
     bool frameRateOverridesChanged;
@@ -646,42 +653,41 @@
         if (currentState == newState) return {};
         currentState = std::forward<T>(newState);
 
-        displayModeConfigs = getBestDisplayModeConfigs();
+        auto modeChoices = chooseDisplayModes();
 
-        // mPolicy holds the current mode, using the current mode we find out
-        // what display is currently being tracked through the policy and
-        // then find the DisplayModeConfig for that display. So that
-        // later we check if the policy mode has changed for the same display in policy.
-        // If mPolicy mode isn't available then we take the first display from the best display
-        // modes as the candidate for policy changes and frame rate overrides.
-        // TODO(b/240743786) Update the single display based assumptions and make mode changes
-        // and mPolicy per display.
-        const DisplayModeConfig& displayModeConfigForCurrentPolicy = mPolicy.mode
-                ? *std::find_if(displayModeConfigs.begin(), displayModeConfigs.end(),
-                                [&](const auto& displayModeConfig) REQUIRES(mPolicyLock) {
-                                    return displayModeConfig.displayModePtr
-                                                   ->getPhysicalDisplayId() ==
-                                            mPolicy.mode->getPhysicalDisplayId();
-                                })
-                : displayModeConfigs.front();
+        // TODO(b/240743786): The leader display's mode must change for any DisplayModeRequest to go
+        // through. Fix this by tracking per-display Scheduler::Policy and timers.
+        DisplayModePtr modePtr;
+        std::tie(modePtr, consideredSignals) =
+                modeChoices.get(*mLeaderDisplayId)
+                        .transform([](const DisplayModeChoice& choice) {
+                            return std::make_pair(choice.modePtr, choice.consideredSignals);
+                        })
+                        .value();
 
-        newMode = displayModeConfigForCurrentPolicy.displayModePtr;
-        consideredSignals = displayModeConfigForCurrentPolicy.signals;
-        frameRateOverridesChanged = updateFrameRateOverrides(consideredSignals, newMode->getFps());
+        modeRequests.reserve(modeChoices.size());
+        for (auto& [id, choice] : modeChoices) {
+            modeRequests.emplace_back(
+                    display::DisplayModeRequest{.modePtr =
+                                                        ftl::as_non_null(std::move(choice.modePtr)),
+                                                .emitEvent = !choice.consideredSignals.idle});
+        }
 
-        if (mPolicy.mode == newMode) {
+        frameRateOverridesChanged = updateFrameRateOverrides(consideredSignals, modePtr->getFps());
+
+        if (mPolicy.mode != modePtr) {
+            mPolicy.mode = modePtr;
+            refreshRateChanged = true;
+        } else {
             // We don't need to change the display mode, but we might need to send an event
             // about a mode change, since it was suppressed if previously considered idle.
             if (!consideredSignals.idle) {
                 dispatchCachedReportedMode();
             }
-        } else {
-            mPolicy.mode = newMode;
-            refreshRateChanged = true;
         }
     }
     if (refreshRateChanged) {
-        mSchedulerCallback.requestDisplayModes(std::move(displayModeConfigs));
+        mSchedulerCallback.requestDisplayModes(std::move(modeRequests));
     }
     if (frameRateOverridesChanged) {
         mSchedulerCallback.triggerOnFrameRateOverridesChanged();
@@ -689,11 +695,11 @@
     return consideredSignals;
 }
 
-std::vector<DisplayModeConfig> Scheduler::getBestDisplayModeConfigs() const {
+auto Scheduler::chooseDisplayModes() const -> DisplayModeChoiceMap {
     ATRACE_CALL();
 
-    using Rankings = std::pair<std::vector<RefreshRateRanking>, GlobalSignals>;
-    display::PhysicalDisplayVector<Rankings> perDisplayRankings;
+    using RankedRefreshRates = RefreshRateConfigs::RankedRefreshRates;
+    display::PhysicalDisplayVector<RankedRefreshRates> perDisplayRanking;
 
     // Tallies the score of a refresh rate across `displayCount` displays.
     struct RefreshRateTally {
@@ -710,11 +716,11 @@
     const auto globalSignals = makeGlobalSignals();
 
     for (const auto& [id, display] : mDisplays) {
-        auto [rankings, signals] =
+        auto rankedRefreshRates =
                 display->holdRefreshRateConfigs()
                         ->getRankedRefreshRates(mPolicy.contentRequirements, globalSignals);
 
-        for (const auto& [modePtr, score] : rankings) {
+        for (const auto& [modePtr, score] : rankedRefreshRates.ranking) {
             const auto [it, inserted] = refreshRateTallies.try_emplace(modePtr->getFps(), score);
 
             if (!inserted) {
@@ -724,7 +730,7 @@
             }
         }
 
-        perDisplayRankings.emplace_back(std::move(rankings), signals);
+        perDisplayRanking.push_back(std::move(rankedRefreshRates));
     }
 
     auto maxScoreIt = refreshRateTallies.cbegin();
@@ -750,26 +756,27 @@
             ? std::make_optional(maxScoreIt->first)
             : std::nullopt;
 
-    std::vector<DisplayModeConfig> displayModeConfigs;
-    displayModeConfigs.reserve(mDisplays.size());
+    DisplayModeChoiceMap modeChoices;
 
     using fps_approx_ops::operator==;
 
-    for (const auto& [rankings, signals] : perDisplayRankings) {
+    for (auto& [ranking, signals] : perDisplayRanking) {
         if (!chosenFps) {
-            displayModeConfigs.emplace_back(signals, rankings.front().displayModePtr);
+            auto& [modePtr, _] = ranking.front();
+            modeChoices.try_emplace(modePtr->getPhysicalDisplayId(),
+                                    DisplayModeChoice{std::move(modePtr), signals});
             continue;
         }
 
-        for (const auto& ranking : rankings) {
-            const auto& modePtr = ranking.displayModePtr;
+        for (auto& [modePtr, _] : ranking) {
             if (modePtr->getFps() == *chosenFps) {
-                displayModeConfigs.emplace_back(signals, modePtr);
+                modeChoices.try_emplace(modePtr->getPhysicalDisplayId(),
+                                        DisplayModeChoice{std::move(modePtr), signals});
                 break;
             }
         }
     }
-    return displayModeConfigs;
+    return modeChoices;
 }
 
 GlobalSignals Scheduler::makeGlobalSignals() const {
@@ -787,11 +794,11 @@
     // Make sure the stored mode is up to date.
     if (mPolicy.mode) {
         const auto configs = holdRefreshRateConfigs();
-        const auto rankings =
+        const auto ranking =
                 configs->getRankedRefreshRates(mPolicy.contentRequirements, makeGlobalSignals())
-                        .first;
+                        .ranking;
 
-        mPolicy.mode = rankings.front().displayModePtr;
+        mPolicy.mode = ranking.front().modePtr;
     }
     return mPolicy.mode;
 }
diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h
index 6633b05..33f6126 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.h
+++ b/services/surfaceflinger/Scheduler/Scheduler.h
@@ -38,6 +38,7 @@
 #include <ui/DisplayId.h>
 
 #include "Display/DisplayMap.h"
+#include "Display/DisplayModeRequest.h"
 #include "DisplayDevice.h"
 #include "EventThread.h"
 #include "FrameRateOverrideMappings.h"
@@ -88,20 +89,9 @@
 
 using GlobalSignals = RefreshRateConfigs::GlobalSignals;
 
-// Config representing the DisplayMode and considered signals for the Display.
-struct DisplayModeConfig {
-    const GlobalSignals signals;
-    const DisplayModePtr displayModePtr;
-
-    DisplayModeConfig(GlobalSignals signals, DisplayModePtr displayModePtr)
-          : signals(signals), displayModePtr(std::move(displayModePtr)) {}
-};
-
 struct ISchedulerCallback {
-    using DisplayModeEvent = scheduler::DisplayModeEvent;
-
     virtual void setVsyncEnabled(bool) = 0;
-    virtual void requestDisplayModes(std::vector<DisplayModeConfig>) = 0;
+    virtual void requestDisplayModes(std::vector<display::DisplayModeRequest>) = 0;
     virtual void kernelTimerChanged(bool expired) = 0;
     virtual void triggerOnFrameRateOverridesChanged() = 0;
 
@@ -278,8 +268,26 @@
     template <typename S, typename T>
     GlobalSignals applyPolicy(S Policy::*, T&&) EXCLUDES(mPolicyLock);
 
-    // Returns the best display mode per display.
-    std::vector<DisplayModeConfig> getBestDisplayModeConfigs() const REQUIRES(mPolicyLock);
+    struct DisplayModeChoice {
+        DisplayModeChoice(DisplayModePtr modePtr, GlobalSignals consideredSignals)
+              : modePtr(std::move(modePtr)), consideredSignals(consideredSignals) {}
+
+        DisplayModePtr modePtr;
+        GlobalSignals consideredSignals;
+
+        bool operator==(const DisplayModeChoice& other) const {
+            return modePtr == other.modePtr && consideredSignals == other.consideredSignals;
+        }
+
+        // For tests.
+        friend std::ostream& operator<<(std::ostream& stream, const DisplayModeChoice& choice) {
+            return stream << '{' << to_string(*choice.modePtr) << " considering "
+                          << choice.consideredSignals.toString().c_str() << '}';
+        }
+    };
+
+    using DisplayModeChoiceMap = display::PhysicalDisplayMap<PhysicalDisplayId, DisplayModeChoice>;
+    DisplayModeChoiceMap chooseDisplayModes() const REQUIRES(mPolicyLock);
 
     GlobalSignals makeGlobalSignals() const REQUIRES(mPolicyLock);
 
@@ -329,6 +337,7 @@
     mutable std::mutex mPolicyLock;
 
     display::PhysicalDisplayMap<PhysicalDisplayId, sp<const DisplayDevice>> mDisplays;
+    std::optional<PhysicalDisplayId> mLeaderDisplayId;
 
     struct Policy {
         // Policy for choosing the display mode.