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/libs/ui/include/ui/DisplayId.h b/libs/ui/include/ui/DisplayId.h
index d0c03fe..3a31fa0 100644
--- a/libs/ui/include/ui/DisplayId.h
+++ b/libs/ui/include/ui/DisplayId.h
@@ -17,6 +17,7 @@
 #pragma once
 
 #include <cstdint>
+#include <ostream>
 #include <string>
 
 #include <ftl/optional.h>
@@ -67,6 +68,11 @@
     return std::to_string(displayId.value);
 }
 
+// For tests.
+inline std::ostream& operator<<(std::ostream& stream, DisplayId displayId) {
+    return stream << "DisplayId{" << displayId.value << '}';
+}
+
 // DisplayId of a physical display, such as the internal display or externally connected display.
 struct PhysicalDisplayId : DisplayId {
     static constexpr ftl::Optional<PhysicalDisplayId> tryCast(DisplayId id) {
diff --git a/services/surfaceflinger/Display/DisplayModeRequest.h b/services/surfaceflinger/Display/DisplayModeRequest.h
new file mode 100644
index 0000000..ac25fe0
--- /dev/null
+++ b/services/surfaceflinger/Display/DisplayModeRequest.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2022 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 <ftl/non_null.h>
+
+#include "DisplayHardware/DisplayMode.h"
+
+namespace android::display {
+
+struct DisplayModeRequest {
+    ftl::NonNull<DisplayModePtr> modePtr;
+
+    // Whether to emit DisplayEventReceiver::DISPLAY_EVENT_MODE_CHANGE.
+    bool emitEvent = false;
+};
+
+inline bool operator==(const DisplayModeRequest& lhs, const DisplayModeRequest& rhs) {
+    return lhs.modePtr == rhs.modePtr && lhs.emitEvent == rhs.emitEvent;
+}
+
+} // namespace android::display
diff --git a/services/surfaceflinger/DisplayDevice.h b/services/surfaceflinger/DisplayDevice.h
index 06a812b..7abb94b 100644
--- a/services/surfaceflinger/DisplayDevice.h
+++ b/services/surfaceflinger/DisplayDevice.h
@@ -41,6 +41,7 @@
 #include <utils/RefBase.h>
 #include <utils/Timers.h>
 
+#include "Display/DisplayModeRequest.h"
 #include "DisplayHardware/DisplayMode.h"
 #include "DisplayHardware/Hal.h"
 #include "DisplayHardware/PowerAdvisor.h"
@@ -190,9 +191,20 @@
     /* ------------------------------------------------------------------------
      * Display mode management.
      */
+
+    // TODO(b/241285876): Replace ActiveModeInfo and DisplayModeEvent with DisplayModeRequest.
     struct ActiveModeInfo {
+        using Event = scheduler::DisplayModeEvent;
+
+        ActiveModeInfo() = default;
+        ActiveModeInfo(DisplayModePtr mode, Event event) : mode(std::move(mode)), event(event) {}
+
+        explicit ActiveModeInfo(display::DisplayModeRequest&& request)
+              : ActiveModeInfo(std::move(request.modePtr).take(),
+                               request.emitEvent ? Event::Changed : Event::None) {}
+
         DisplayModePtr mode;
-        scheduler::DisplayModeEvent event = scheduler::DisplayModeEvent::None;
+        Event event = Event::None;
 
         bool operator!=(const ActiveModeInfo& other) const {
             return mode != other.mode || event != other.event;
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.
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index 029dd9f..8e29715 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -1064,30 +1064,28 @@
     return NO_ERROR;
 }
 
-void SurfaceFlinger::setDesiredActiveMode(const ActiveModeInfo& info) {
+void SurfaceFlinger::setDesiredActiveMode(display::DisplayModeRequest&& request) {
     ATRACE_CALL();
 
-    if (!info.mode) {
-        ALOGW("requested display mode is null");
-        return;
-    }
-    auto display = getDisplayDeviceLocked(info.mode->getPhysicalDisplayId());
+    auto display = getDisplayDeviceLocked(request.modePtr->getPhysicalDisplayId());
     if (!display) {
         ALOGW("%s: display is no longer valid", __func__);
         return;
     }
 
-    if (display->setDesiredActiveMode(info)) {
+    const Fps refreshRate = request.modePtr->getFps();
+
+    if (display->setDesiredActiveMode(DisplayDevice::ActiveModeInfo(std::move(request)))) {
         scheduleComposite(FrameHint::kNone);
 
         // Start receiving vsync samples now, so that we can detect a period
         // switch.
-        mScheduler->resyncToHardwareVsync(true, info.mode->getFps());
+        mScheduler->resyncToHardwareVsync(true, refreshRate);
         // As we called to set period, we will call to onRefreshRateChangeCompleted once
         // VsyncController model is locked.
         modulateVsync(&VsyncModulator::onRefreshRateChangeInitiated);
 
-        updatePhaseConfiguration(info.mode->getFps());
+        updatePhaseConfiguration(refreshRate);
         mScheduler->setModeChangePending(true);
     }
 }
@@ -1179,7 +1177,7 @@
     mRefreshRateStats->setRefreshRate(refreshRate);
     updatePhaseConfiguration(refreshRate);
 
-    if (upcomingModeInfo.event != DisplayModeEvent::None) {
+    if (upcomingModeInfo.event != scheduler::DisplayModeEvent::None) {
         mScheduler->onPrimaryDisplayModeChanged(mAppConnectionHandle, upcomingModeInfo.mode);
     }
 }
@@ -3331,34 +3329,33 @@
     mCompositionEngine->updateCursorAsync(refreshArgs);
 }
 
-void SurfaceFlinger::requestDisplayModes(
-        std::vector<scheduler::DisplayModeConfig> displayModeConfigs) {
+void SurfaceFlinger::requestDisplayModes(std::vector<display::DisplayModeRequest> modeRequests) {
     if (mBootStage != BootStage::FINISHED) {
         ALOGV("Currently in the boot stage, skipping display mode changes");
         return;
     }
 
     ATRACE_CALL();
+
     // If this is called from the main thread mStateLock must be locked before
     // Currently the only way to call this function from the main thread is from
     // Scheduler::chooseRefreshRateForContent
 
     ConditionalLock lock(mStateLock, std::this_thread::get_id() != mMainThreadId);
 
-    std::for_each(displayModeConfigs.begin(), displayModeConfigs.end(),
-                  [&](const auto& config) REQUIRES(mStateLock) {
-                      const auto& displayModePtr = config.displayModePtr;
-                      if (const auto display =
-                                  getDisplayDeviceLocked(displayModePtr->getPhysicalDisplayId());
-                          display->refreshRateConfigs().isModeAllowed(displayModePtr->getId())) {
-                          const auto event = config.signals.idle ? DisplayModeEvent::None
-                                                                 : DisplayModeEvent::Changed;
-                          setDesiredActiveMode({displayModePtr, event});
-                      } else {
-                          ALOGV("Skipping disallowed mode %d for display %" PRId64,
-                                displayModePtr->getId().value(), display->getPhysicalId().value);
-                      }
-                  });
+    for (auto& request : modeRequests) {
+        const auto& modePtr = request.modePtr;
+        const auto display = getDisplayDeviceLocked(modePtr->getPhysicalDisplayId());
+
+        if (!display) continue;
+
+        if (display->refreshRateConfigs().isModeAllowed(modePtr->getId())) {
+            setDesiredActiveMode(std::move(request));
+        } else {
+            ALOGV("%s: Mode %d is disallowed for display %s", __func__, modePtr->getId().value(),
+                  to_string(display->getId()).c_str());
+        }
+    }
 }
 
 void SurfaceFlinger::triggerOnFrameRateOverridesChanged() {
@@ -6583,18 +6580,19 @@
     }
 }
 
-std::optional<DisplayModePtr> SurfaceFlinger::getPreferredDisplayMode(
+std::optional<ftl::NonNull<DisplayModePtr>> SurfaceFlinger::getPreferredDisplayMode(
         PhysicalDisplayId displayId, DisplayModeId defaultModeId) const {
     if (const auto schedulerMode = mScheduler->getPreferredDisplayMode();
         schedulerMode && schedulerMode->getPhysicalDisplayId() == displayId) {
-        return schedulerMode;
+        return ftl::as_non_null(schedulerMode);
     }
 
     return mPhysicalDisplays.get(displayId)
             .transform(&PhysicalDisplay::snapshotRef)
             .and_then([&](const display::DisplaySnapshot& snapshot) {
                 return snapshot.displayModes().get(defaultModeId);
-            });
+            })
+            .transform(&ftl::as_non_null<const DisplayModePtr&>);
 }
 
 status_t SurfaceFlinger::setDesiredDisplayModeSpecsInternal(
@@ -6650,7 +6648,7 @@
         return INVALID_OPERATION;
     }
 
-    setDesiredActiveMode({std::move(preferredMode), DisplayModeEvent::Changed});
+    setDesiredActiveMode({std::move(preferredMode), .emitEvent = true});
     return NO_ERROR;
 }
 
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index b65dec4..9ffe6ab 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -29,6 +29,7 @@
 #include <cutils/atomic.h>
 #include <cutils/compiler.h>
 #include <ftl/future.h>
+#include <ftl/non_null.h>
 #include <gui/BufferQueue.h>
 #include <gui/CompositorTiming.h>
 #include <gui/FrameTimestamps.h>
@@ -430,10 +431,6 @@
                 mCounterByLayerHandle GUARDED_BY(mLock);
     };
 
-    using ActiveModeInfo = DisplayDevice::ActiveModeInfo;
-    using KernelIdleTimerController =
-            ::android::scheduler::RefreshRateConfigs::KernelIdleTimerController;
-
     enum class BootStage {
         BOOTLOADER,
         BOOTANIMATION,
@@ -628,16 +625,17 @@
     // ISchedulerCallback overrides:
 
     // Toggles hardware VSYNC by calling into HWC.
+    // TODO(b/241286146): Rename for self-explanatory API.
     void setVsyncEnabled(bool) override;
-    // Sets the desired display mode per display if allowed by policy .
-    void requestDisplayModes(std::vector<scheduler::DisplayModeConfig>) override;
-    // Called when kernel idle timer has expired. Used to update the refresh rate overlay.
+    void requestDisplayModes(std::vector<display::DisplayModeRequest>) override;
     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);
+
+    using KernelIdleTimerController = scheduler::RefreshRateConfigs::KernelIdleTimerController;
+
     // Get the controller and timeout that will help decide how the kernel idle timer will be
     // configured and what value to use as the timeout.
     std::pair<std::optional<KernelIdleTimerController>, std::chrono::milliseconds>
@@ -652,8 +650,8 @@
     // Show spinner with refresh rate overlay
     bool mRefreshRateOverlaySpinner = false;
 
-    // Sets the desired active mode bit. It obtains the lock, and sets mDesiredActiveMode.
-    void setDesiredActiveMode(const ActiveModeInfo& info) REQUIRES(mStateLock);
+    void setDesiredActiveMode(display::DisplayModeRequest&&) REQUIRES(mStateLock);
+
     status_t setActiveModeFromBackdoor(const sp<display::DisplayToken>&, DisplayModeId);
     // Sets the active mode and a new refresh rate in SF.
     void updateInternalStateWithChangedMode() REQUIRES(mStateLock, kMainThreadContext);
@@ -672,9 +670,8 @@
 
     // Returns the preferred mode for PhysicalDisplayId if the Scheduler has selected one for that
     // display. Falls back to the display's defaultModeId otherwise.
-    std::optional<DisplayModePtr> getPreferredDisplayMode(PhysicalDisplayId,
-                                                          DisplayModeId defaultModeId) const
-            REQUIRES(mStateLock);
+    std::optional<ftl::NonNull<DisplayModePtr>> getPreferredDisplayMode(
+            PhysicalDisplayId, DisplayModeId defaultModeId) const REQUIRES(mStateLock);
 
     status_t setDesiredDisplayModeSpecsInternal(const sp<DisplayDevice>&,
                                                 const scheduler::RefreshRateConfigs::PolicyVariant&)
diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h
index e2ae4f4..99279dc 100644
--- a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h
+++ b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h
@@ -780,7 +780,7 @@
 
 private:
     void setVsyncEnabled(bool) override {}
-    void requestDisplayModes(std::vector<scheduler::DisplayModeConfig>) override {}
+    void requestDisplayModes(std::vector<display::DisplayModeRequest>) override {}
     void kernelTimerChanged(bool) override {}
     void triggerOnFrameRateOverridesChanged() override {}
 
diff --git a/services/surfaceflinger/tests/unittests/FakeDisplayInjector.h b/services/surfaceflinger/tests/unittests/FakeDisplayInjector.h
index 6ee4b9b..6e4bf2b 100644
--- a/services/surfaceflinger/tests/unittests/FakeDisplayInjector.h
+++ b/services/surfaceflinger/tests/unittests/FakeDisplayInjector.h
@@ -29,7 +29,7 @@
 using android::Hwc2::mock::PowerAdvisor;
 
 struct FakeDisplayInjectorArgs {
-    uint8_t port = 255u;
+    PhysicalDisplayId displayId = PhysicalDisplayId::fromPort(255u);
     HWDisplayId hwcDisplayId = 0;
     bool isPrimary = true;
 };
@@ -67,7 +67,7 @@
         auto compositionDisplay = compositionengine::impl::
                 createDisplay(mFlinger.getCompositionEngine(),
                               compositionengine::DisplayCreationArgsBuilder()
-                                      .setId(PhysicalDisplayId::fromPort(args.port))
+                                      .setId(args.displayId)
                                       .setPixels(kResolution)
                                       .setPowerAdvisor(&mPowerAdvisor)
                                       .build());
diff --git a/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp b/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp
index 620825f..924c5be 100644
--- a/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp
+++ b/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp
@@ -17,6 +17,9 @@
 #undef LOG_TAG
 #define LOG_TAG "SchedulerUnittests"
 
+#include <algorithm>
+#include <array>
+
 #include <ftl/enum.h>
 #include <ftl/fake_guard.h>
 #include <gmock/gmock.h>
@@ -34,15 +37,17 @@
 
 namespace hal = android::hardware::graphics::composer::hal;
 
-using SetPolicyResult = RefreshRateConfigs::SetPolicyResult;
-using LayerVoteType = RefreshRateConfigs::LayerVoteType;
 using LayerRequirement = RefreshRateConfigs::LayerRequirement;
+using LayerVoteType = RefreshRateConfigs::LayerVoteType;
+using SetPolicyResult = RefreshRateConfigs::SetPolicyResult;
 
 using mock::createDisplayMode;
 
 struct TestableRefreshRateConfigs : RefreshRateConfigs {
-    using RefreshRateConfigs::RefreshRateConfigs;
     using RefreshRateConfigs::RefreshRateOrder;
+    using RefreshRateConfigs::RefreshRateRanking;
+
+    using RefreshRateConfigs::RefreshRateConfigs;
 
     void setActiveModeId(DisplayModeId modeId) {
         ftl::FakeGuard guard(kMainThreadContext);
@@ -74,12 +79,10 @@
         return getMaxRefreshRateByPolicyLocked(getActiveModeItLocked()->second->getGroup());
     }
 
-    std::vector<RefreshRateRanking> getRefreshRatesByPolicy(
-            std::optional<int> anchorGroupOpt, RefreshRateOrder refreshRateOrder) const {
+    RefreshRateRanking rankRefreshRates(std::optional<int> anchorGroupOpt,
+                                        RefreshRateOrder refreshRateOrder) const {
         std::lock_guard lock(mLock);
-        return RefreshRateConfigs::
-                getRefreshRatesByPolicyLocked(anchorGroupOpt, refreshRateOrder,
-                                              /*preferredDisplayModeOpt*/ std::nullopt);
+        return RefreshRateConfigs::rankRefreshRates(anchorGroupOpt, refreshRateOrder);
     }
 
     const std::vector<Fps>& knownFrameRates() const { return mKnownFrameRates; }
@@ -87,14 +90,25 @@
     using RefreshRateConfigs::GetRankedRefreshRatesCache;
     auto& mutableGetRankedRefreshRatesCache() { return mGetRankedRefreshRatesCache; }
 
-    auto getRankedRefreshRatesAndSignals(const std::vector<LayerRequirement>& layers,
-                                         GlobalSignals signals) const {
-        return RefreshRateConfigs::getRankedRefreshRates(layers, signals);
+    auto getRankedRefreshRates(const std::vector<LayerRequirement>& layers,
+                               GlobalSignals signals) const {
+        const auto result = RefreshRateConfigs::getRankedRefreshRates(layers, signals);
+
+        EXPECT_TRUE(std::is_sorted(result.ranking.begin(), result.ranking.end(),
+                                   ScoredRefreshRate::DescendingScore{}));
+
+        return result;
+    }
+
+    auto getRankedRefreshRatesAsPair(const std::vector<LayerRequirement>& layers,
+                                     GlobalSignals signals) const {
+        const auto [ranking, consideredSignals] = getRankedRefreshRates(layers, signals);
+        return std::make_pair(ranking, consideredSignals);
     }
 
     DisplayModePtr getBestRefreshRate(const std::vector<LayerRequirement>& layers = {},
                                       GlobalSignals signals = {}) const {
-        return getRankedRefreshRatesAndSignals(layers, signals).first.front().displayModePtr;
+        return getRankedRefreshRates(layers, signals).ranking.front().modePtr;
     }
 
     SetPolicyResult setPolicy(const PolicyVariant& policy) {
@@ -109,6 +123,8 @@
 
 class RefreshRateConfigsTest : public testing::Test {
 protected:
+    using RefreshRateOrder = TestableRefreshRateConfigs::RefreshRateOrder;
+
     RefreshRateConfigsTest();
     ~RefreshRateConfigsTest();
 
@@ -1050,20 +1066,17 @@
     // The kModes_30_60_90 contains two kMode72_G1, kMode120_G1 which are from the
     // different group.
     TestableRefreshRateConfigs configs(kModes_30_60_90, kModeId60);
-    const std::vector<RefreshRateRanking>& expectedRefreshRates = {RefreshRateRanking{kMode90},
-                                                                   RefreshRateRanking{kMode60},
-                                                                   RefreshRateRanking{kMode30}};
 
-    const std::vector<RefreshRateRanking>& refreshRates =
-            configs.getRefreshRatesByPolicy(configs.getActiveMode().getGroup(),
-                                            TestableRefreshRateConfigs::RefreshRateOrder::
-                                                    Descending);
+    const auto refreshRates = configs.rankRefreshRates(configs.getActiveMode().getGroup(),
+                                                       RefreshRateOrder::Descending);
 
+    const std::array expectedRefreshRates = {kMode90, kMode60, kMode30};
     ASSERT_EQ(expectedRefreshRates.size(), refreshRates.size());
+
     for (size_t i = 0; i < expectedRefreshRates.size(); ++i) {
-        EXPECT_EQ(expectedRefreshRates[i].displayModePtr, refreshRates[i].displayModePtr)
-                << "Expected fps " << expectedRefreshRates[i].displayModePtr->getFps().getIntValue()
-                << " Actual fps " << refreshRates[i].displayModePtr->getFps().getIntValue();
+        EXPECT_EQ(expectedRefreshRates[i], refreshRates[i].modePtr)
+                << "Expected fps " << expectedRefreshRates[i]->getFps().getIntValue()
+                << " Actual fps " << refreshRates[i].modePtr->getFps().getIntValue();
     }
 }
 
@@ -1071,20 +1084,17 @@
     // The kModes_30_60_90 contains two kMode72_G1, kMode120_G1 which are from the
     // different group.
     TestableRefreshRateConfigs configs(kModes_30_60_90, kModeId60);
-    const std::vector<RefreshRateRanking>& expectedRefreshRates = {RefreshRateRanking{kMode30},
-                                                                   RefreshRateRanking{kMode60},
-                                                                   RefreshRateRanking{kMode90}};
 
-    const std::vector<RefreshRateRanking>& refreshRates =
-            configs.getRefreshRatesByPolicy(configs.getActiveMode().getGroup(),
-                                            TestableRefreshRateConfigs::RefreshRateOrder::
-                                                    Ascending);
+    const auto refreshRates = configs.rankRefreshRates(configs.getActiveMode().getGroup(),
+                                                       RefreshRateOrder::Ascending);
 
+    const std::array expectedRefreshRates = {kMode30, kMode60, kMode90};
     ASSERT_EQ(expectedRefreshRates.size(), refreshRates.size());
+
     for (size_t i = 0; i < expectedRefreshRates.size(); ++i) {
-        EXPECT_EQ(expectedRefreshRates[i].displayModePtr, refreshRates[i].displayModePtr)
-                << "Expected fps " << expectedRefreshRates[i].displayModePtr->getFps().getIntValue()
-                << " Actual fps " << refreshRates[i].displayModePtr->getFps().getIntValue();
+        EXPECT_EQ(expectedRefreshRates[i], refreshRates[i].modePtr)
+                << "Expected fps " << expectedRefreshRates[i]->getFps().getIntValue()
+                << " Actual fps " << refreshRates[i].modePtr->getFps().getIntValue();
     }
 }
 
@@ -1092,23 +1102,20 @@
     // The kModes_30_60_90 contains two kMode72_G1, kMode120_G1 which are from the
     // different group.
     TestableRefreshRateConfigs configs(kModes_30_60_90, kModeId72);
-    const std::vector<RefreshRateRanking>& expectedRefreshRates = {RefreshRateRanking{kMode30},
-                                                                   RefreshRateRanking{kMode60},
-                                                                   RefreshRateRanking{kMode90}};
 
     EXPECT_EQ(SetPolicyResult::Changed,
               configs.setDisplayManagerPolicy({kModeId60, {30_Hz, 90_Hz}, {30_Hz, 90_Hz}}));
 
-    const std::vector<RefreshRateRanking>& refreshRates =
-            configs.getRefreshRatesByPolicy(/*anchorGroupOpt*/ std::nullopt,
-                                            TestableRefreshRateConfigs::RefreshRateOrder::
-                                                    Ascending);
+    const auto refreshRates =
+            configs.rankRefreshRates(/*anchorGroupOpt*/ std::nullopt, RefreshRateOrder::Ascending);
 
+    const std::array expectedRefreshRates = {kMode30, kMode60, kMode90};
     ASSERT_EQ(expectedRefreshRates.size(), refreshRates.size());
+
     for (size_t i = 0; i < expectedRefreshRates.size(); ++i) {
-        EXPECT_EQ(expectedRefreshRates[i].displayModePtr, refreshRates[i].displayModePtr)
-                << "Expected fps " << expectedRefreshRates[i].displayModePtr->getFps().getIntValue()
-                << " Actual fps " << refreshRates[i].displayModePtr->getFps().getIntValue();
+        EXPECT_EQ(expectedRefreshRates[i], refreshRates[i].modePtr)
+                << "Expected fps " << expectedRefreshRates[i]->getFps().getIntValue()
+                << " Actual fps " << refreshRates[i].modePtr->getFps().getIntValue();
     }
 }
 
@@ -1116,47 +1123,48 @@
     // The kModes_30_60_90 contains two kMode72_G1, kMode120_G1 which are from the
     // different group.
     TestableRefreshRateConfigs configs(kModes_30_60_90, kModeId72);
-    const std::vector<RefreshRateRanking>& expectedRefreshRates = {RefreshRateRanking{kMode90},
-                                                                   RefreshRateRanking{kMode60},
-                                                                   RefreshRateRanking{kMode30}};
 
     EXPECT_EQ(SetPolicyResult::Changed,
               configs.setDisplayManagerPolicy({kModeId60, {30_Hz, 90_Hz}, {30_Hz, 90_Hz}}));
 
-    const std::vector<RefreshRateRanking>& refreshRates =
-            configs.getRefreshRatesByPolicy(/*anchorGroupOpt*/ std::nullopt,
-                                            TestableRefreshRateConfigs::RefreshRateOrder::
-                                                    Descending);
+    const auto refreshRates =
+            configs.rankRefreshRates(/*anchorGroupOpt*/ std::nullopt, RefreshRateOrder::Descending);
 
+    const std::array expectedRefreshRates = {kMode90, kMode60, kMode30};
     ASSERT_EQ(expectedRefreshRates.size(), refreshRates.size());
+
     for (size_t i = 0; i < expectedRefreshRates.size(); ++i) {
-        EXPECT_EQ(expectedRefreshRates[i].displayModePtr, refreshRates[i].displayModePtr)
-                << "Expected fps " << expectedRefreshRates[i].displayModePtr->getFps().getIntValue()
-                << " Actual fps " << refreshRates[i].displayModePtr->getFps().getIntValue();
+        EXPECT_EQ(expectedRefreshRates[i], refreshRates[i].modePtr)
+                << "Expected fps " << expectedRefreshRates[i]->getFps().getIntValue()
+                << " Actual fps " << refreshRates[i].modePtr->getFps().getIntValue();
     }
 }
 
 TEST_F(RefreshRateConfigsTest, powerOnImminentConsidered) {
-    RefreshRateConfigs configs(kModes_60_90, kModeId60);
-    std::vector<RefreshRateRanking> expectedRefreshRates = {RefreshRateRanking{kMode90},
-                                                            RefreshRateRanking{kMode60}};
+    TestableRefreshRateConfigs configs(kModes_60_90, kModeId60);
 
     auto [refreshRates, signals] = configs.getRankedRefreshRates({}, {});
     EXPECT_FALSE(signals.powerOnImminent);
+
+    std::array expectedRefreshRates = {kMode90, kMode60};
     ASSERT_EQ(expectedRefreshRates.size(), refreshRates.size());
+
     for (size_t i = 0; i < expectedRefreshRates.size(); ++i) {
-        EXPECT_EQ(expectedRefreshRates[i].displayModePtr, refreshRates[i].displayModePtr)
-                << "Expected fps " << expectedRefreshRates[i].displayModePtr->getFps().getIntValue()
-                << " Actual fps " << refreshRates[i].displayModePtr->getFps().getIntValue();
+        EXPECT_EQ(expectedRefreshRates[i], refreshRates[i].modePtr)
+                << "Expected fps " << expectedRefreshRates[i]->getFps().getIntValue()
+                << " Actual fps " << refreshRates[i].modePtr->getFps().getIntValue();
     }
 
-    std::tie(refreshRates, signals) = configs.getRankedRefreshRates({}, {.powerOnImminent = true});
+    std::tie(refreshRates, signals) =
+            configs.getRankedRefreshRatesAsPair({}, {.powerOnImminent = true});
     EXPECT_TRUE(signals.powerOnImminent);
+
     ASSERT_EQ(expectedRefreshRates.size(), refreshRates.size());
+
     for (size_t i = 0; i < expectedRefreshRates.size(); ++i) {
-        EXPECT_EQ(expectedRefreshRates[i].displayModePtr, refreshRates[i].displayModePtr)
-                << "Expected fps " << expectedRefreshRates[i].displayModePtr->getFps().getIntValue()
-                << " Actual fps " << refreshRates[i].displayModePtr->getFps().getIntValue();
+        EXPECT_EQ(expectedRefreshRates[i], refreshRates[i].modePtr)
+                << "Expected fps " << expectedRefreshRates[i]->getFps().getIntValue()
+                << " Actual fps " << refreshRates[i].modePtr->getFps().getIntValue();
     }
 
     std::vector<LayerRequirement> layers = {{.weight = 1.f}};
@@ -1166,34 +1174,38 @@
     lr1.name = "60Hz ExplicitExactOrMultiple";
 
     std::tie(refreshRates, signals) =
-            configs.getRankedRefreshRates(layers, {.powerOnImminent = true});
+            configs.getRankedRefreshRatesAsPair(layers, {.powerOnImminent = true});
     EXPECT_TRUE(signals.powerOnImminent);
+
     ASSERT_EQ(expectedRefreshRates.size(), refreshRates.size());
+
     for (size_t i = 0; i < expectedRefreshRates.size(); ++i) {
-        EXPECT_EQ(expectedRefreshRates[i].displayModePtr, refreshRates[i].displayModePtr)
-                << "Expected fps " << expectedRefreshRates[i].displayModePtr->getFps().getIntValue()
-                << " Actual fps " << refreshRates[i].displayModePtr->getFps().getIntValue();
+        EXPECT_EQ(expectedRefreshRates[i], refreshRates[i].modePtr)
+                << "Expected fps " << expectedRefreshRates[i]->getFps().getIntValue()
+                << " Actual fps " << refreshRates[i].modePtr->getFps().getIntValue();
     }
 
-    expectedRefreshRates = {RefreshRateRanking{kMode60}, RefreshRateRanking{kMode90}};
     std::tie(refreshRates, signals) =
-            configs.getRankedRefreshRates(layers, {.powerOnImminent = false});
+            configs.getRankedRefreshRatesAsPair(layers, {.powerOnImminent = false});
     EXPECT_FALSE(signals.powerOnImminent);
+
+    expectedRefreshRates = {kMode60, kMode90};
     ASSERT_EQ(expectedRefreshRates.size(), refreshRates.size());
+
     for (size_t i = 0; i < expectedRefreshRates.size(); ++i) {
-        EXPECT_EQ(expectedRefreshRates[i].displayModePtr, refreshRates[i].displayModePtr)
-                << "Expected fps " << expectedRefreshRates[i].displayModePtr->getFps().getIntValue()
-                << " Actual fps " << refreshRates[i].displayModePtr->getFps().getIntValue();
+        EXPECT_EQ(expectedRefreshRates[i], refreshRates[i].modePtr)
+                << "Expected fps " << expectedRefreshRates[i]->getFps().getIntValue()
+                << " Actual fps " << refreshRates[i].modePtr->getFps().getIntValue();
     }
 }
 
 TEST_F(RefreshRateConfigsTest, touchConsidered) {
-    RefreshRateConfigs configs(kModes_60_90, kModeId60);
+    TestableRefreshRateConfigs configs(kModes_60_90, kModeId60);
 
     auto [_, signals] = configs.getRankedRefreshRates({}, {});
     EXPECT_FALSE(signals.touch);
 
-    std::tie(std::ignore, signals) = configs.getRankedRefreshRates({}, {.touch = true});
+    std::tie(std::ignore, signals) = configs.getRankedRefreshRatesAsPair({}, {.touch = true});
     EXPECT_TRUE(signals.touch);
 
     std::vector<LayerRequirement> layers = {{.weight = 1.f}, {.weight = 1.f}};
@@ -1206,7 +1218,7 @@
     lr2.vote = LayerVoteType::Heuristic;
     lr2.desiredRefreshRate = 60_Hz;
     lr2.name = "60Hz Heuristic";
-    std::tie(std::ignore, signals) = configs.getRankedRefreshRates(layers, {.touch = true});
+    std::tie(std::ignore, signals) = configs.getRankedRefreshRatesAsPair(layers, {.touch = true});
     EXPECT_TRUE(signals.touch);
 
     lr1.vote = LayerVoteType::ExplicitDefault;
@@ -1215,7 +1227,7 @@
     lr2.vote = LayerVoteType::Heuristic;
     lr2.desiredRefreshRate = 60_Hz;
     lr2.name = "60Hz Heuristic";
-    std::tie(std::ignore, signals) = configs.getRankedRefreshRates(layers, {.touch = true});
+    std::tie(std::ignore, signals) = configs.getRankedRefreshRatesAsPair(layers, {.touch = true});
     EXPECT_FALSE(signals.touch);
 
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
@@ -1224,7 +1236,7 @@
     lr2.vote = LayerVoteType::Heuristic;
     lr2.desiredRefreshRate = 60_Hz;
     lr2.name = "60Hz Heuristic";
-    std::tie(std::ignore, signals) = configs.getRankedRefreshRates(layers, {.touch = true});
+    std::tie(std::ignore, signals) = configs.getRankedRefreshRatesAsPair(layers, {.touch = true});
     EXPECT_TRUE(signals.touch);
 
     lr1.vote = LayerVoteType::ExplicitDefault;
@@ -1233,7 +1245,7 @@
     lr2.vote = LayerVoteType::Heuristic;
     lr2.desiredRefreshRate = 60_Hz;
     lr2.name = "60Hz Heuristic";
-    std::tie(std::ignore, signals) = configs.getRankedRefreshRates(layers, {.touch = true});
+    std::tie(std::ignore, signals) = configs.getRankedRefreshRatesAsPair(layers, {.touch = true});
     EXPECT_FALSE(signals.touch);
 }
 
@@ -1352,7 +1364,7 @@
     const auto [mode, signals] =
             configs.getRankedRefreshRates(layers, {.touch = true, .idle = true});
 
-    EXPECT_EQ(mode.begin()->displayModePtr, kMode60);
+    EXPECT_EQ(mode.begin()->modePtr, kMode60);
     EXPECT_FALSE(signals.touch);
 }
 
@@ -1407,18 +1419,15 @@
     lr5.name = "30Hz";
     lr5.focused = true;
 
-    std::vector<RefreshRateRanking> expectedRankings = {
-            RefreshRateRanking{kMode120}, RefreshRateRanking{kMode90}, RefreshRateRanking{kMode72},
-            RefreshRateRanking{kMode60},  RefreshRateRanking{kMode30},
-    };
+    std::array expectedRanking = {kMode120, kMode90, kMode72, kMode60, kMode30};
+    auto actualRanking = configs.getRankedRefreshRates(layers, {}).ranking;
 
-    std::vector<RefreshRateRanking> actualOrder =
-            configs.getRankedRefreshRatesAndSignals(layers, {}).first;
-    ASSERT_EQ(expectedRankings.size(), actualOrder.size());
-    for (size_t i = 0; i < expectedRankings.size(); ++i) {
-        EXPECT_EQ(expectedRankings[i].displayModePtr, actualOrder[i].displayModePtr)
-                << "Expected fps " << expectedRankings[i].displayModePtr->getFps().getIntValue()
-                << " Actual fps " << actualOrder[i].displayModePtr->getFps().getIntValue();
+    ASSERT_EQ(expectedRanking.size(), actualRanking.size());
+
+    for (size_t i = 0; i < expectedRanking.size(); ++i) {
+        EXPECT_EQ(expectedRanking[i], actualRanking[i].modePtr)
+                << "Expected fps " << expectedRanking[i]->getFps().getIntValue() << " Actual fps "
+                << actualRanking[i].modePtr->getFps().getIntValue();
     }
 
     lr1.vote = LayerVoteType::Max;
@@ -1436,18 +1445,15 @@
     lr5.desiredRefreshRate = 120_Hz;
     lr5.name = "120Hz";
 
-    expectedRankings = {
-            RefreshRateRanking{kMode120}, RefreshRateRanking{kMode90}, RefreshRateRanking{kMode72},
-            RefreshRateRanking{kMode60},  RefreshRateRanking{kMode30},
-    };
+    expectedRanking = {kMode120, kMode90, kMode72, kMode60, kMode30};
+    actualRanking = configs.getRankedRefreshRates(layers, {}).ranking;
 
-    actualOrder = configs.getRankedRefreshRatesAndSignals(layers, {}).first;
+    ASSERT_EQ(expectedRanking.size(), actualRanking.size());
 
-    ASSERT_EQ(expectedRankings.size(), actualOrder.size());
-    for (size_t i = 0; i < expectedRankings.size(); ++i) {
-        EXPECT_EQ(expectedRankings[i].displayModePtr, actualOrder[i].displayModePtr)
-                << "Expected fps " << expectedRankings[i].displayModePtr->getFps().getIntValue()
-                << " Actual fps " << actualOrder[i].displayModePtr->getFps().getIntValue();
+    for (size_t i = 0; i < expectedRanking.size(); ++i) {
+        EXPECT_EQ(expectedRanking[i], actualRanking[i].modePtr)
+                << "Expected fps " << expectedRanking[i]->getFps().getIntValue() << " Actual fps "
+                << actualRanking[i].modePtr->getFps().getIntValue();
     }
 
     lr1.vote = LayerVoteType::Heuristic;
@@ -1463,17 +1469,15 @@
     lr5.desiredRefreshRate = 72_Hz;
     lr5.name = "72Hz";
 
-    expectedRankings = {
-            RefreshRateRanking{kMode30},  RefreshRateRanking{kMode60}, RefreshRateRanking{kMode90},
-            RefreshRateRanking{kMode120}, RefreshRateRanking{kMode72},
-    };
+    expectedRanking = {kMode30, kMode60, kMode90, kMode120, kMode72};
+    actualRanking = configs.getRankedRefreshRates(layers, {}).ranking;
 
-    actualOrder = configs.getRankedRefreshRatesAndSignals(layers, {}).first;
-    ASSERT_EQ(expectedRankings.size(), actualOrder.size());
-    for (size_t i = 0; i < expectedRankings.size(); ++i) {
-        EXPECT_EQ(expectedRankings[i].displayModePtr, actualOrder[i].displayModePtr)
-                << "Expected fps " << expectedRankings[i].displayModePtr->getFps().getIntValue()
-                << " Actual fps " << actualOrder[i].displayModePtr->getFps().getIntValue();
+    ASSERT_EQ(expectedRanking.size(), actualRanking.size());
+
+    for (size_t i = 0; i < expectedRanking.size(); ++i) {
+        EXPECT_EQ(expectedRanking[i], actualRanking[i].modePtr)
+                << "Expected fps " << expectedRanking[i]->getFps().getIntValue() << " Actual fps "
+                << actualRanking[i].modePtr->getFps().getIntValue();
     }
 
     lr1.desiredRefreshRate = 120_Hz;
@@ -1492,17 +1496,15 @@
     lr5.desiredRefreshRate = 120_Hz;
     lr5.name = "120Hz-2";
 
-    expectedRankings = {
-            RefreshRateRanking{kMode90}, RefreshRateRanking{kMode60}, RefreshRateRanking{kMode120},
-            RefreshRateRanking{kMode72}, RefreshRateRanking{kMode30},
-    };
+    expectedRanking = {kMode90, kMode60, kMode120, kMode72, kMode30};
+    actualRanking = configs.getRankedRefreshRates(layers, {}).ranking;
 
-    actualOrder = configs.getRankedRefreshRatesAndSignals(layers, {}).first;
-    ASSERT_EQ(expectedRankings.size(), actualOrder.size());
-    for (size_t i = 0; i < expectedRankings.size(); ++i) {
-        EXPECT_EQ(expectedRankings[i].displayModePtr, actualOrder[i].displayModePtr)
-                << "Expected fps " << expectedRankings[i].displayModePtr->getFps().getIntValue()
-                << " Actual fps " << actualOrder[i].displayModePtr->getFps().getIntValue();
+    ASSERT_EQ(expectedRanking.size(), actualRanking.size());
+
+    for (size_t i = 0; i < expectedRanking.size(); ++i) {
+        EXPECT_EQ(expectedRanking[i], actualRanking[i].modePtr)
+                << "Expected fps " << expectedRanking[i]->getFps().getIntValue() << " Actual fps "
+                << actualRanking[i].modePtr->getFps().getIntValue();
     }
 }
 
@@ -1513,8 +1515,8 @@
     EXPECT_EQ(SetPolicyResult::Changed,
               configs.setDisplayManagerPolicy({kModeId90, {90_Hz, 90_Hz}, {60_Hz, 90_Hz}}));
 
-    const auto [mode, signals] = configs.getRankedRefreshRatesAndSignals({}, {});
-    EXPECT_EQ(mode.front().displayModePtr, kMode90);
+    const auto [ranking, signals] = configs.getRankedRefreshRates({}, {});
+    EXPECT_EQ(ranking.front().modePtr, kMode90);
     EXPECT_FALSE(signals.touch);
 
     std::vector<LayerRequirement> layers = {{.weight = 1.f}};
@@ -1892,13 +1894,12 @@
         layers[0].vote = voteType;
         layers[0].desiredRefreshRate = 90_Hz;
 
-        const auto [refreshRate, signals] =
-                configs.getRankedRefreshRatesAndSignals(layers,
-                                                        {.touch = touchActive, .idle = true});
+        const auto [ranking, signals] =
+                configs.getRankedRefreshRates(layers, {.touch = touchActive, .idle = true});
 
         // Refresh rate will be chosen by either touch state or idle state.
         EXPECT_EQ(!touchActive, signals.idle);
-        return refreshRate.front().displayModePtr->getId();
+        return ranking.front().modePtr->getId();
     };
 
     EXPECT_EQ(SetPolicyResult::Changed,
@@ -2059,12 +2060,13 @@
     const auto args = std::make_pair(std::vector<LayerRequirement>{},
                                      GlobalSignals{.touch = true, .idle = true});
 
-    const auto result = std::make_pair(std::vector<RefreshRateRanking>{RefreshRateRanking{kMode90}},
-                                       GlobalSignals{.touch = true});
+    const RefreshRateConfigs::RankedRefreshRates result = {{RefreshRateConfigs::ScoredRefreshRate{
+                                                                   kMode90}},
+                                                           {.touch = true}};
 
     configs.mutableGetRankedRefreshRatesCache() = {args, result};
 
-    EXPECT_EQ(result, configs.getRankedRefreshRatesAndSignals(args.first, args.second));
+    EXPECT_EQ(result, configs.getRankedRefreshRates(args.first, args.second));
 }
 
 TEST_F(RefreshRateConfigsTest, getBestRefreshRate_WritesCache) {
@@ -2075,7 +2077,7 @@
     std::vector<LayerRequirement> layers = {{.weight = 1.f}, {.weight = 0.5f}};
     RefreshRateConfigs::GlobalSignals globalSignals{.touch = true, .idle = true};
 
-    const auto result = configs.getRankedRefreshRatesAndSignals(layers, globalSignals);
+    const auto result = configs.getRankedRefreshRates(layers, globalSignals);
 
     const auto& cache = configs.mutableGetRankedRefreshRatesCache();
     ASSERT_TRUE(cache);
diff --git a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
index 392398d..147433b 100644
--- a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
@@ -43,8 +43,6 @@
 using MockLayer = android::mock::MockLayer;
 using FakeDisplayDeviceInjector = TestableSurfaceFlinger::FakeDisplayDeviceInjector;
 
-constexpr PhysicalDisplayId PHYSICAL_DISPLAY_ID = PhysicalDisplayId::fromPort(255u);
-
 class SchedulerTest : public testing::Test {
 protected:
     class MockEventThreadConnection : public android::EventThreadConnection {
@@ -61,14 +59,28 @@
 
     SchedulerTest();
 
-    static inline const DisplayModePtr kMode60_1 = createDisplayMode(DisplayModeId(0), 60_Hz);
-    static inline const DisplayModePtr kMode120_1 = createDisplayMode(DisplayModeId(1), 120_Hz);
-    static inline const DisplayModePtr kMode60_2 = createDisplayMode(DisplayModeId(2), 60_Hz);
-    static inline const DisplayModePtr kMode120_2 = createDisplayMode(DisplayModeId(3), 120_Hz);
-    static inline const DisplayModePtr kMode60_3 = createDisplayMode(DisplayModeId(4), 60_Hz);
+    static constexpr PhysicalDisplayId kDisplayId1 = PhysicalDisplayId::fromPort(255u);
+    static inline const DisplayModePtr kDisplay1Mode60 =
+            createDisplayMode(kDisplayId1, DisplayModeId(0), 60_Hz);
+    static inline const DisplayModePtr kDisplay1Mode120 =
+            createDisplayMode(kDisplayId1, DisplayModeId(1), 120_Hz);
+    static inline const DisplayModes kDisplay1Modes = makeModes(kDisplay1Mode60, kDisplay1Mode120);
+
+    static constexpr PhysicalDisplayId kDisplayId2 = PhysicalDisplayId::fromPort(254u);
+    static inline const DisplayModePtr kDisplay2Mode60 =
+            createDisplayMode(kDisplayId2, DisplayModeId(0), 60_Hz);
+    static inline const DisplayModePtr kDisplay2Mode120 =
+            createDisplayMode(kDisplayId2, DisplayModeId(1), 120_Hz);
+    static inline const DisplayModes kDisplay2Modes = makeModes(kDisplay2Mode60, kDisplay2Mode120);
+
+    static constexpr PhysicalDisplayId kDisplayId3 = PhysicalDisplayId::fromPort(253u);
+    static inline const DisplayModePtr kDisplay3Mode60 =
+            createDisplayMode(kDisplayId3, DisplayModeId(0), 60_Hz);
+    static inline const DisplayModes kDisplay3Modes = makeModes(kDisplay3Mode60);
 
     std::shared_ptr<RefreshRateConfigs> mConfigs =
-            std::make_shared<RefreshRateConfigs>(makeModes(kMode60_1), kMode60_1->getId());
+            std::make_shared<RefreshRateConfigs>(makeModes(kDisplay1Mode60),
+                                                 kDisplay1Mode60->getId());
 
     mock::SchedulerCallback mSchedulerCallback;
     TestableScheduler* mScheduler = new TestableScheduler{mConfigs, mSchedulerCallback};
@@ -114,7 +126,7 @@
 
     // The EXPECT_CALLS make sure we don't call the functions on the subsequent event threads.
     EXPECT_CALL(*mEventThread, onHotplugReceived(_, _)).Times(0);
-    mScheduler->onHotplugReceived(handle, PHYSICAL_DISPLAY_ID, false);
+    mScheduler->onHotplugReceived(handle, kDisplayId1, false);
 
     EXPECT_CALL(*mEventThread, onScreenAcquired()).Times(0);
     mScheduler->onScreenAcquired(handle);
@@ -138,8 +150,8 @@
     ASSERT_EQ(mEventThreadConnection, connection);
     EXPECT_TRUE(mScheduler->getEventConnection(mConnectionHandle));
 
-    EXPECT_CALL(*mEventThread, onHotplugReceived(PHYSICAL_DISPLAY_ID, false)).Times(1);
-    mScheduler->onHotplugReceived(mConnectionHandle, PHYSICAL_DISPLAY_ID, false);
+    EXPECT_CALL(*mEventThread, onHotplugReceived(kDisplayId1, false)).Times(1);
+    mScheduler->onHotplugReceived(mConnectionHandle, kDisplayId1, false);
 
     EXPECT_CALL(*mEventThread, onScreenAcquired()).Times(1);
     mScheduler->onScreenAcquired(mConnectionHandle);
@@ -185,8 +197,7 @@
     ASSERT_EQ(1u, mScheduler->layerHistorySize());
 
     mScheduler->setRefreshRateConfigs(
-            std::make_shared<RefreshRateConfigs>(makeModes(kMode60_1, kMode120_1),
-                                                 kMode60_1->getId()));
+            std::make_shared<RefreshRateConfigs>(kDisplay1Modes, kDisplay1Mode60->getId()));
 
     ASSERT_EQ(0u, mScheduler->getNumActiveLayers());
     mScheduler->recordLayerHistory(layer.get(), 0, LayerHistory::LayerUpdateType::Buffer);
@@ -203,7 +214,7 @@
 TEST_F(SchedulerTest, onNonPrimaryDisplayModeChanged_invalidParameters) {
     const auto mode = DisplayMode::Builder(hal::HWConfigId(0))
                               .setId(DisplayModeId(111))
-                              .setPhysicalDisplayId(PHYSICAL_DISPLAY_ID)
+                              .setPhysicalDisplayId(kDisplayId1)
                               .setVsyncPeriod(111111)
                               .build();
 
@@ -225,14 +236,15 @@
 }
 
 MATCHER(Is120Hz, "") {
-    return isApproxEqual(arg.front().displayModePtr->getFps(), 120_Hz);
+    return isApproxEqual(arg.front().modePtr->getFps(), 120_Hz);
 }
 
 TEST_F(SchedulerTest, chooseRefreshRateForContentSelectsMaxRefreshRate) {
-    auto display =
-            mFakeDisplayInjector.injectInternalDisplay([&](FakeDisplayDeviceInjector& injector) {
-                injector.setDisplayModes(makeModes(kMode60_1, kMode120_1), kMode60_1->getId());
-            });
+    const auto display = mFakeDisplayInjector.injectInternalDisplay(
+            [&](FakeDisplayDeviceInjector& injector) {
+                injector.setDisplayModes(kDisplay1Modes, kDisplay1Mode60->getId());
+            },
+            {.displayId = kDisplayId1});
 
     mScheduler->registerDisplay(display);
     mScheduler->setRefreshRateConfigs(display->holdRefreshRateConfigs());
@@ -256,11 +268,12 @@
     mScheduler->chooseRefreshRateForContent();
 }
 
-TEST_F(SchedulerTest, getBestDisplayMode_singleDisplay) {
-    auto display =
-            mFakeDisplayInjector.injectInternalDisplay([&](FakeDisplayDeviceInjector& injector) {
-                injector.setDisplayModes(makeModes(kMode60_1, kMode120_1), kMode60_1->getId());
-            });
+TEST_F(SchedulerTest, chooseDisplayModesSingleDisplay) {
+    const auto display = mFakeDisplayInjector.injectInternalDisplay(
+            [&](FakeDisplayDeviceInjector& injector) {
+                injector.setDisplayModes(kDisplay1Modes, kDisplay1Mode60->getId());
+            },
+            {.displayId = kDisplayId1});
 
     mScheduler->registerDisplay(display);
 
@@ -270,115 +283,125 @@
     GlobalSignals globalSignals = {.idle = true};
     mScheduler->setTouchStateAndIdleTimerPolicy(globalSignals);
 
-    std::vector<DisplayModeConfig> displayModeConfigs = mScheduler->getBestDisplayModeConfigs();
-    ASSERT_EQ(1ul, displayModeConfigs.size());
-    EXPECT_EQ(displayModeConfigs.front().displayModePtr, kMode60_1);
-    EXPECT_EQ(displayModeConfigs.front().signals, globalSignals);
+    using DisplayModeChoice = TestableScheduler::DisplayModeChoice;
+
+    auto modeChoices = mScheduler->chooseDisplayModes();
+    ASSERT_EQ(1u, modeChoices.size());
+
+    auto choice = modeChoices.get(kDisplayId1);
+    ASSERT_TRUE(choice);
+    EXPECT_EQ(choice->get(), DisplayModeChoice(kDisplay1Mode60, globalSignals));
 
     globalSignals = {.idle = false};
     mScheduler->setTouchStateAndIdleTimerPolicy(globalSignals);
-    displayModeConfigs = mScheduler->getBestDisplayModeConfigs();
-    ASSERT_EQ(1ul, displayModeConfigs.size());
-    EXPECT_EQ(displayModeConfigs.front().displayModePtr, kMode120_1);
-    EXPECT_EQ(displayModeConfigs.front().signals, globalSignals);
+
+    modeChoices = mScheduler->chooseDisplayModes();
+    ASSERT_EQ(1u, modeChoices.size());
+
+    choice = modeChoices.get(kDisplayId1);
+    ASSERT_TRUE(choice);
+    EXPECT_EQ(choice->get(), DisplayModeChoice(kDisplay1Mode120, globalSignals));
 
     globalSignals = {.touch = true};
     mScheduler->replaceTouchTimer(10);
     mScheduler->setTouchStateAndIdleTimerPolicy(globalSignals);
-    displayModeConfigs = mScheduler->getBestDisplayModeConfigs();
-    ASSERT_EQ(1ul, displayModeConfigs.size());
-    EXPECT_EQ(displayModeConfigs.front().displayModePtr, kMode120_1);
-    EXPECT_EQ(displayModeConfigs.front().signals, globalSignals);
 
-    mScheduler->unregisterDisplay(display->getPhysicalId());
+    modeChoices = mScheduler->chooseDisplayModes();
+    ASSERT_EQ(1u, modeChoices.size());
+
+    choice = modeChoices.get(kDisplayId1);
+    ASSERT_TRUE(choice);
+    EXPECT_EQ(choice->get(), DisplayModeChoice(kDisplay1Mode120, globalSignals));
+
+    mScheduler->unregisterDisplay(kDisplayId1);
     EXPECT_TRUE(mScheduler->mutableDisplays().empty());
 }
 
-TEST_F(SchedulerTest, getBestDisplayModes_multipleDisplays) {
-    auto display1 =
-            mFakeDisplayInjector.injectInternalDisplay([&](FakeDisplayDeviceInjector& injector) {
-                injector.setDisplayModes(makeModes(kMode60_1, kMode120_1), kMode60_1->getId());
-            });
-    auto display2 = mFakeDisplayInjector.injectInternalDisplay(
+TEST_F(SchedulerTest, chooseDisplayModesMultipleDisplays) {
+    const auto display1 = mFakeDisplayInjector.injectInternalDisplay(
             [&](FakeDisplayDeviceInjector& injector) {
-                injector.setDisplayModes(makeModes(kMode60_2, kMode120_2), kMode60_2->getId());
+                injector.setDisplayModes(kDisplay1Modes, kDisplay1Mode60->getId());
             },
-            {.port = 253u, .hwcDisplayId = 42, .isPrimary = false});
+            {.displayId = kDisplayId1, .hwcDisplayId = 42, .isPrimary = true});
+    const auto display2 = mFakeDisplayInjector.injectInternalDisplay(
+            [&](FakeDisplayDeviceInjector& injector) {
+                injector.setDisplayModes(kDisplay2Modes, kDisplay2Mode60->getId());
+            },
+            {.displayId = kDisplayId2, .hwcDisplayId = 41, .isPrimary = false});
 
     mScheduler->registerDisplay(display1);
     mScheduler->registerDisplay(display2);
 
-    std::vector<sp<DisplayDevice>> expectedDisplays = {display1, display2};
-    std::vector<RefreshRateConfigs::LayerRequirement> layers = {{.weight = 1.f}, {.weight = 1.f}};
-    GlobalSignals globalSignals = {.idle = true};
-    std::vector<DisplayModeConfig> expectedConfigs = {DisplayModeConfig{globalSignals, kMode60_1},
-                                                      DisplayModeConfig{globalSignals, kMode60_2}};
+    using DisplayModeChoice = TestableScheduler::DisplayModeChoice;
+    TestableScheduler::DisplayModeChoiceMap expectedChoices;
 
-    mScheduler->setContentRequirements(layers);
-    mScheduler->setTouchStateAndIdleTimerPolicy(globalSignals);
-    std::vector<DisplayModeConfig> displayModeConfigs = mScheduler->getBestDisplayModeConfigs();
-    ASSERT_EQ(displayModeConfigs.size(), expectedConfigs.size());
-    for (size_t i = 0; i < expectedConfigs.size(); ++i) {
-        EXPECT_EQ(expectedConfigs.at(i).displayModePtr, displayModeConfigs.at(i).displayModePtr)
-                << "Expected fps " << expectedConfigs.at(i).displayModePtr->getFps().getIntValue()
-                << " Actual fps "
-                << displayModeConfigs.at(i).displayModePtr->getFps().getIntValue();
-        EXPECT_EQ(globalSignals, displayModeConfigs.at(i).signals);
+    {
+        const GlobalSignals globalSignals = {.idle = true};
+        expectedChoices =
+                ftl::init::map<const PhysicalDisplayId&,
+                               DisplayModeChoice>(kDisplayId1, kDisplay1Mode60,
+                                                  globalSignals)(kDisplayId2, kDisplay2Mode60,
+                                                                 globalSignals);
+
+        std::vector<RefreshRateConfigs::LayerRequirement> layers = {{.weight = 1.f},
+                                                                    {.weight = 1.f}};
+        mScheduler->setContentRequirements(layers);
+        mScheduler->setTouchStateAndIdleTimerPolicy(globalSignals);
+
+        const auto actualChoices = mScheduler->chooseDisplayModes();
+        EXPECT_EQ(expectedChoices, actualChoices);
     }
+    {
+        const GlobalSignals globalSignals = {.idle = false};
+        expectedChoices =
+                ftl::init::map<const PhysicalDisplayId&,
+                               DisplayModeChoice>(kDisplayId1, kDisplay1Mode120,
+                                                  globalSignals)(kDisplayId2, kDisplay2Mode120,
+                                                                 globalSignals);
 
-    expectedConfigs = std::vector<DisplayModeConfig>{DisplayModeConfig{globalSignals, kMode120_1},
-                                                     DisplayModeConfig{globalSignals, kMode120_2}};
+        mScheduler->setTouchStateAndIdleTimerPolicy(globalSignals);
 
-    globalSignals = {.idle = false};
-    mScheduler->setTouchStateAndIdleTimerPolicy(globalSignals);
-    displayModeConfigs = mScheduler->getBestDisplayModeConfigs();
-    ASSERT_EQ(expectedConfigs.size(), displayModeConfigs.size());
-    for (size_t i = 0; i < expectedConfigs.size(); ++i) {
-        EXPECT_EQ(expectedConfigs.at(i).displayModePtr, displayModeConfigs.at(i).displayModePtr)
-                << "Expected fps " << expectedConfigs.at(i).displayModePtr->getFps().getIntValue()
-                << " Actual fps "
-                << displayModeConfigs.at(i).displayModePtr->getFps().getIntValue();
-        EXPECT_EQ(globalSignals, displayModeConfigs.at(i).signals);
+        const auto actualChoices = mScheduler->chooseDisplayModes();
+        EXPECT_EQ(expectedChoices, actualChoices);
     }
+    {
+        const GlobalSignals globalSignals = {.touch = true};
+        mScheduler->replaceTouchTimer(10);
+        mScheduler->setTouchStateAndIdleTimerPolicy(globalSignals);
 
-    globalSignals = {.touch = true};
-    mScheduler->replaceTouchTimer(10);
-    mScheduler->setTouchStateAndIdleTimerPolicy(globalSignals);
-    displayModeConfigs = mScheduler->getBestDisplayModeConfigs();
-    ASSERT_EQ(expectedConfigs.size(), displayModeConfigs.size());
-    for (size_t i = 0; i < expectedConfigs.size(); ++i) {
-        EXPECT_EQ(expectedConfigs.at(i).displayModePtr, displayModeConfigs.at(i).displayModePtr)
-                << "Expected fps " << expectedConfigs.at(i).displayModePtr->getFps().getIntValue()
-                << " Actual fps "
-                << displayModeConfigs.at(i).displayModePtr->getFps().getIntValue();
-        EXPECT_EQ(globalSignals, displayModeConfigs.at(i).signals);
+        expectedChoices =
+                ftl::init::map<const PhysicalDisplayId&,
+                               DisplayModeChoice>(kDisplayId1, kDisplay1Mode120,
+                                                  globalSignals)(kDisplayId2, kDisplay2Mode120,
+                                                                 globalSignals);
+
+        const auto actualChoices = mScheduler->chooseDisplayModes();
+        EXPECT_EQ(expectedChoices, actualChoices);
     }
+    {
+        // This display does not support 120 Hz, so we should choose 60 Hz despite the touch signal.
+        const auto display3 = mFakeDisplayInjector.injectInternalDisplay(
+                [&](FakeDisplayDeviceInjector& injector) {
+                    injector.setDisplayModes(kDisplay3Modes, kDisplay3Mode60->getId());
+                },
+                {.displayId = kDisplayId3, .hwcDisplayId = 40, .isPrimary = false});
 
-    // Filters out the 120Hz as it's not present on the display3, even with touch active
-    // we select 60Hz here.
-    auto display3 = mFakeDisplayInjector.injectInternalDisplay(
-            [&](FakeDisplayDeviceInjector& injector) {
-                injector.setDisplayModes(makeModes(kMode60_3), kMode60_3->getId());
-            },
-            {.port = 252u, .hwcDisplayId = 41, .isPrimary = false});
+        mScheduler->registerDisplay(display3);
 
-    mScheduler->registerDisplay(display3);
+        const GlobalSignals globalSignals = {.touch = true};
+        mScheduler->replaceTouchTimer(10);
+        mScheduler->setTouchStateAndIdleTimerPolicy(globalSignals);
 
-    expectedDisplays = {display1, display2, display3};
-    globalSignals = {.touch = true};
-    mScheduler->replaceTouchTimer(10);
-    expectedConfigs = std::vector<DisplayModeConfig>{DisplayModeConfig{globalSignals, kMode60_1},
-                                                     DisplayModeConfig{globalSignals, kMode60_2},
-                                                     DisplayModeConfig{globalSignals, kMode60_3}};
-    mScheduler->setTouchStateAndIdleTimerPolicy(globalSignals);
-    displayModeConfigs = mScheduler->getBestDisplayModeConfigs();
-    ASSERT_EQ(expectedConfigs.size(), displayModeConfigs.size());
-    for (size_t i = 0; i < expectedConfigs.size(); ++i) {
-        EXPECT_EQ(expectedConfigs.at(i).displayModePtr, displayModeConfigs.at(i).displayModePtr)
-                << "Expected fps " << expectedConfigs.at(i).displayModePtr->getFps().getIntValue()
-                << " Actual fps "
-                << displayModeConfigs.at(i).displayModePtr->getFps().getIntValue();
-        EXPECT_EQ(globalSignals, displayModeConfigs.at(i).signals);
+        expectedChoices =
+                ftl::init::map<const PhysicalDisplayId&,
+                               DisplayModeChoice>(kDisplayId1, kDisplay1Mode60,
+                                                  globalSignals)(kDisplayId2, kDisplay2Mode60,
+                                                                 globalSignals)(kDisplayId3,
+                                                                                kDisplay3Mode60,
+                                                                                globalSignals);
+
+        const auto actualChoices = mScheduler->chooseDisplayModes();
+        EXPECT_EQ(expectedChoices, actualChoices);
     }
 }
 
diff --git a/services/surfaceflinger/tests/unittests/TestableScheduler.h b/services/surfaceflinger/tests/unittests/TestableScheduler.h
index 68df987..26b2b67 100644
--- a/services/surfaceflinger/tests/unittests/TestableScheduler.h
+++ b/services/surfaceflinger/tests/unittests/TestableScheduler.h
@@ -107,9 +107,12 @@
         mPolicy.contentRequirements = std::move(layers);
     }
 
-    std::vector<DisplayModeConfig> getBestDisplayModeConfigs() {
+    using Scheduler::DisplayModeChoice;
+    using Scheduler::DisplayModeChoiceMap;
+
+    DisplayModeChoiceMap chooseDisplayModes() {
         std::lock_guard<std::mutex> lock(mPolicyLock);
-        return Scheduler::getBestDisplayModeConfigs();
+        return Scheduler::chooseDisplayModes();
     }
 
     void dispatchCachedReportedMode() {
diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockDisplayMode.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockDisplayMode.h
index a83ecbc..c78b6bd 100644
--- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockDisplayMode.h
+++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockDisplayMode.h
@@ -33,4 +33,9 @@
             .build();
 }
 
+inline DisplayModePtr createDisplayMode(PhysicalDisplayId displayId, DisplayModeId modeId,
+                                        Fps refreshRate) {
+    return createDisplayMode(modeId, refreshRate, {}, {}, displayId);
+}
+
 } // namespace android::mock
diff --git a/services/surfaceflinger/tests/unittests/mock/MockSchedulerCallback.h b/services/surfaceflinger/tests/unittests/mock/MockSchedulerCallback.h
index 8af2dfa..7d4b159 100644
--- a/services/surfaceflinger/tests/unittests/mock/MockSchedulerCallback.h
+++ b/services/surfaceflinger/tests/unittests/mock/MockSchedulerCallback.h
@@ -24,14 +24,14 @@
 
 struct SchedulerCallback final : ISchedulerCallback {
     MOCK_METHOD(void, setVsyncEnabled, (bool), (override));
-    MOCK_METHOD(void, requestDisplayModes, (std::vector<scheduler::DisplayModeConfig>), (override));
+    MOCK_METHOD(void, requestDisplayModes, (std::vector<display::DisplayModeRequest>), (override));
     MOCK_METHOD(void, kernelTimerChanged, (bool), (override));
     MOCK_METHOD(void, triggerOnFrameRateOverridesChanged, (), (override));
 };
 
 struct NoOpSchedulerCallback final : ISchedulerCallback {
     void setVsyncEnabled(bool) override {}
-    void requestDisplayModes(std::vector<scheduler::DisplayModeConfig>) override {}
+    void requestDisplayModes(std::vector<display::DisplayModeRequest>) override {}
     void kernelTimerChanged(bool) override {}
     void triggerOnFrameRateOverridesChanged() override {}
 };