SF: Unify data types for display modes

Remove the RefreshRateConfigs::RefreshRate wrapper around DisplayMode.
Store DisplayModes as a SmallMap, so that RefreshRateConfigs uses the
same data structure for lookup by ID. Use iterators into that map for
all bookkeeping in RefreshRateConfigs.

Bug: 182939859
Bug: 185535769
Test: libsurfaceflinger_unittest
Change-Id: I7708fa997089802c45d906b17b7a073f5c82105e
diff --git a/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp b/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp
index 65c8613..3226f22 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp
+++ b/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp
@@ -38,10 +38,33 @@
 namespace android::scheduler {
 namespace {
 
+struct RefreshRateScore {
+    DisplayModeIterator modeIt;
+    float score;
+};
+
+template <typename Iterator>
+const DisplayModePtr& getMaxScoreRefreshRate(Iterator begin, Iterator end) {
+    const auto it =
+            std::max_element(begin, end, [](RefreshRateScore max, RefreshRateScore current) {
+                const auto& [modeIt, score] = current;
+
+                std::string name = to_string(modeIt->second->getFps());
+                ALOGV("%s scores %.2f", name.c_str(), score);
+
+                ATRACE_INT(name.c_str(), static_cast<int>(std::round(score * 100)));
+
+                constexpr float kEpsilon = 0.0001f;
+                return score > max.score * (1 + kEpsilon);
+            });
+
+    return it->modeIt->second;
+}
+
 constexpr RefreshRateConfigs::GlobalSignals kNoSignals;
 
 std::string formatLayerInfo(const RefreshRateConfigs::LayerRequirement& layer, float weight) {
-    return base::StringPrintf("%s (type=%s, weight=%.2f seamlessness=%s) %s", layer.name.c_str(),
+    return base::StringPrintf("%s (type=%s, weight=%.2f, seamlessness=%s) %s", layer.name.c_str(),
                               ftl::enum_string(layer.vote).c_str(), weight,
                               ftl::enum_string(layer.seamlessness).c_str(),
                               to_string(layer.desiredRefreshRate).c_str());
@@ -52,8 +75,8 @@
     knownFrameRates.reserve(knownFrameRates.size() + modes.size());
 
     // Add all supported refresh rates.
-    for (const auto& mode : modes) {
-        knownFrameRates.push_back(Fps::fromPeriodNsecs(mode->getVsyncPeriod()));
+    for (const auto& [id, mode] : modes) {
+        knownFrameRates.push_back(mode->getFps());
     }
 
     // Sort and remove duplicates.
@@ -64,17 +87,51 @@
     return knownFrameRates;
 }
 
-} // namespace
+// The Filter is a `bool(const DisplayMode&)` predicate.
+template <typename Filter>
+std::vector<DisplayModeIterator> sortByRefreshRate(const DisplayModes& modes, Filter&& filter) {
+    std::vector<DisplayModeIterator> sortedModes;
+    sortedModes.reserve(modes.size());
 
-using AllRefreshRatesMapType = RefreshRateConfigs::AllRefreshRatesMapType;
-using RefreshRate = RefreshRateConfigs::RefreshRate;
+    for (auto it = modes.begin(); it != modes.end(); ++it) {
+        const auto& [id, mode] = *it;
 
-std::string RefreshRate::toString() const {
-    return base::StringPrintf("{id=%d, hwcId=%d, fps=%.2f, width=%d, height=%d group=%d}",
-                              getModeId().value(), mode->getHwcId(), getFps().getValue(),
-                              mode->getWidth(), mode->getHeight(), getModeGroup());
+        if (filter(*mode)) {
+            ALOGV("%s: including mode %d", __func__, id.value());
+            sortedModes.push_back(it);
+        }
+    }
+
+    std::sort(sortedModes.begin(), sortedModes.end(), [](auto it1, auto it2) {
+        const auto& mode1 = it1->second;
+        const auto& mode2 = it2->second;
+
+        if (mode1->getVsyncPeriod() == mode2->getVsyncPeriod()) {
+            return mode1->getGroup() > mode2->getGroup();
+        }
+
+        return mode1->getVsyncPeriod() > mode2->getVsyncPeriod();
+    });
+
+    return sortedModes;
 }
 
+bool canModesSupportFrameRateOverride(const std::vector<DisplayModeIterator>& sortedModes) {
+    for (const auto it1 : sortedModes) {
+        const auto& mode1 = it1->second;
+        for (const auto it2 : sortedModes) {
+            const auto& mode2 = it2->second;
+
+            if (RefreshRateConfigs::getFrameRateDivisor(mode1->getFps(), mode2->getFps()) >= 2) {
+                return true;
+            }
+        }
+    }
+    return false;
+}
+
+} // namespace
+
 std::string RefreshRateConfigs::Policy::toString() const {
     return base::StringPrintf("default mode ID: %d, allowGroupSwitching = %d"
                               ", primary range: %s, app request range: %s",
@@ -94,15 +151,14 @@
     return {quotient, remainder};
 }
 
-bool RefreshRateConfigs::isVoteAllowed(const LayerRequirement& layer,
-                                       const RefreshRate& refreshRate) const {
+bool RefreshRateConfigs::isVoteAllowed(const LayerRequirement& layer, Fps refreshRate) const {
     using namespace fps_approx_ops;
 
     switch (layer.vote) {
         case LayerVoteType::ExplicitExactOrMultiple:
         case LayerVoteType::Heuristic:
             if (mConfig.frameRateMultipleThreshold != 0 &&
-                refreshRate.getFps() >= Fps::fromValue(mConfig.frameRateMultipleThreshold) &&
+                refreshRate >= Fps::fromValue(mConfig.frameRateMultipleThreshold) &&
                 layer.desiredRefreshRate < Fps::fromValue(mConfig.frameRateMultipleThreshold / 2)) {
                 // Don't vote high refresh rates past the threshold for layers with a low desired
                 // refresh rate. For example, desired 24 fps with 120 Hz threshold means no vote for
@@ -120,11 +176,11 @@
     return true;
 }
 
-float RefreshRateConfigs::calculateNonExactMatchingLayerScoreLocked(
-        const LayerRequirement& layer, const RefreshRate& refreshRate) const {
+float RefreshRateConfigs::calculateNonExactMatchingLayerScoreLocked(const LayerRequirement& layer,
+                                                                    Fps refreshRate) const {
     constexpr float kScoreForFractionalPairs = .8f;
 
-    const auto displayPeriod = refreshRate.getVsyncPeriod();
+    const auto displayPeriod = refreshRate.getPeriodNsecs();
     const auto layerPeriod = layer.desiredRefreshRate.getPeriodNsecs();
     if (layer.vote == LayerVoteType::ExplicitDefault) {
         // Find the actual rate the layer will render, assuming
@@ -147,7 +203,7 @@
 
     if (layer.vote == LayerVoteType::ExplicitExactOrMultiple ||
         layer.vote == LayerVoteType::Heuristic) {
-        if (isFractionalPairOrMultiple(refreshRate.getFps(), layer.desiredRefreshRate)) {
+        if (isFractionalPairOrMultiple(refreshRate, layer.desiredRefreshRate)) {
             return kScoreForFractionalPairs;
         }
 
@@ -182,8 +238,7 @@
     return 0;
 }
 
-float RefreshRateConfigs::calculateLayerScoreLocked(const LayerRequirement& layer,
-                                                    const RefreshRate& refreshRate,
+float RefreshRateConfigs::calculateLayerScoreLocked(const LayerRequirement& layer, Fps refreshRate,
                                                     bool isSeamlessSwitch) const {
     if (!isVoteAllowed(layer, refreshRate)) {
         return 0;
@@ -195,14 +250,14 @@
 
     // If the layer wants Max, give higher score to the higher refresh rate
     if (layer.vote == LayerVoteType::Max) {
-        const auto ratio = refreshRate.getFps().getValue() /
-                mAppRequestRefreshRates.back()->getFps().getValue();
+        const auto& maxRefreshRate = mAppRequestRefreshRates.back()->second;
+        const auto ratio = refreshRate.getValue() / maxRefreshRate->getFps().getValue();
         // use ratio^2 to get a lower score the more we get further from peak
         return ratio * ratio;
     }
 
     if (layer.vote == LayerVoteType::ExplicitExact) {
-        const int divisor = getFrameRateDivisor(refreshRate.getFps(), layer.desiredRefreshRate);
+        const int divisor = getFrameRateDivisor(refreshRate, layer.desiredRefreshRate);
         if (mSupportsFrameRateOverrideByContent) {
             // Since we support frame rate override, allow refresh rates which are
             // multiples of the layer's request, as those apps would be throttled
@@ -215,7 +270,7 @@
 
     // If the layer frame rate is a divisor of the refresh rate it should score
     // the highest score.
-    if (getFrameRateDivisor(refreshRate.getFps(), layer.desiredRefreshRate) > 0) {
+    if (getFrameRateDivisor(refreshRate, layer.desiredRefreshRate) > 0) {
         return 1.0f * seamlessness;
     }
 
@@ -227,14 +282,9 @@
             kNonExactMatchingPenalty;
 }
 
-struct RefreshRateScore {
-    const RefreshRate* refreshRate;
-    float score;
-};
-
 auto RefreshRateConfigs::getBestRefreshRate(const std::vector<LayerRequirement>& layers,
                                             GlobalSignals signals) const
-        -> std::pair<RefreshRate, GlobalSignals> {
+        -> std::pair<DisplayModePtr, GlobalSignals> {
     std::lock_guard lock(mLock);
 
     if (mGetBestRefreshRateCache &&
@@ -249,7 +299,7 @@
 
 auto RefreshRateConfigs::getBestRefreshRateLocked(const std::vector<LayerRequirement>& layers,
                                                   GlobalSignals signals) const
-        -> std::pair<RefreshRate, GlobalSignals> {
+        -> std::pair<DisplayModePtr, GlobalSignals> {
     ATRACE_CALL();
     ALOGV("%s: %zu layers", __func__, layers.size());
 
@@ -298,21 +348,22 @@
             explicitExactOrMultipleVoteLayers > 0 || explicitExact > 0;
 
     const Policy* policy = getCurrentPolicyLocked();
-    const auto& defaultMode = mRefreshRates.at(policy->defaultMode);
+    const auto& defaultMode = mDisplayModes.get(policy->defaultMode)->get();
     // If the default mode group is different from the group of current mode,
     // this means a layer requesting a seamed mode switch just disappeared and
     // we should switch back to the default group.
     // However if a seamed layer is still present we anchor around the group
     // of the current mode, in order to prevent unnecessary seamed mode switches
     // (e.g. when pausing a video playback).
-    const auto anchorGroup = seamedFocusedLayers > 0 ? mCurrentRefreshRate->getModeGroup()
-                                                     : defaultMode->getModeGroup();
+    const auto anchorGroup =
+            seamedFocusedLayers > 0 ? mActiveModeIt->second->getGroup() : defaultMode->getGroup();
 
     // Consider the touch event if there are no Explicit* layers. Otherwise wait until after we've
     // selected a refresh rate to see if we should apply touch boost.
     if (signals.touch && !hasExplicitVoteLayers) {
-        ALOGV("TouchBoost - choose %s", getMaxRefreshRateByPolicyLocked().getName().c_str());
-        return {getMaxRefreshRateByPolicyLocked(anchorGroup), GlobalSignals{.touch = true}};
+        const DisplayModePtr& max = getMaxRefreshRateByPolicyLocked(anchorGroup);
+        ALOGV("TouchBoost - choose %s", to_string(max->getFps()).c_str());
+        return {max, GlobalSignals{.touch = true}};
     }
 
     // If the primary range consists of a single refresh rate then we can only
@@ -322,28 +373,30 @@
             isApproxEqual(policy->primaryRange.min, policy->primaryRange.max);
 
     if (!signals.touch && signals.idle && !(primaryRangeIsSingleRate && hasExplicitVoteLayers)) {
-        ALOGV("Idle - choose %s", getMinRefreshRateByPolicyLocked().getName().c_str());
-        return {getMinRefreshRateByPolicyLocked(), GlobalSignals{.idle = true}};
+        const DisplayModePtr& min = getMinRefreshRateByPolicyLocked();
+        ALOGV("Idle - choose %s", to_string(min->getFps()).c_str());
+        return {min, GlobalSignals{.idle = true}};
     }
 
     if (layers.empty() || noVoteLayers == layers.size()) {
-        const auto& refreshRate = getMaxRefreshRateByPolicyLocked(anchorGroup);
-        ALOGV("no layers with votes - choose %s", refreshRate.getName().c_str());
-        return {refreshRate, kNoSignals};
+        const DisplayModePtr& max = getMaxRefreshRateByPolicyLocked(anchorGroup);
+        ALOGV("no layers with votes - choose %s", to_string(max->getFps()).c_str());
+        return {max, kNoSignals};
     }
 
     // Only if all layers want Min we should return Min
     if (noVoteLayers + minVoteLayers == layers.size()) {
-        ALOGV("all layers Min - choose %s", getMinRefreshRateByPolicyLocked().getName().c_str());
-        return {getMinRefreshRateByPolicyLocked(), kNoSignals};
+        const DisplayModePtr& min = getMinRefreshRateByPolicyLocked();
+        ALOGV("all layers Min - choose %s", to_string(min->getFps()).c_str());
+        return {min, kNoSignals};
     }
 
     // Find the best refresh rate based on score
     std::vector<RefreshRateScore> scores;
     scores.reserve(mAppRequestRefreshRates.size());
 
-    for (const auto refreshRate : mAppRequestRefreshRates) {
-        scores.emplace_back(RefreshRateScore{refreshRate, 0.0f});
+    for (const DisplayModeIterator modeIt : mAppRequestRefreshRates) {
+        scores.emplace_back(RefreshRateScore{modeIt, 0.0f});
     }
 
     for (const auto& layer : layers) {
@@ -354,17 +407,16 @@
             continue;
         }
 
-        auto weight = layer.weight;
+        const auto weight = layer.weight;
 
-        for (auto i = 0u; i < scores.size(); i++) {
-            const bool isSeamlessSwitch =
-                    scores[i].refreshRate->getModeGroup() == mCurrentRefreshRate->getModeGroup();
+        for (auto& [modeIt, score] : scores) {
+            const auto& [id, mode] = *modeIt;
+            const bool isSeamlessSwitch = mode->getGroup() == mActiveModeIt->second->getGroup();
 
             if (layer.seamlessness == Seamlessness::OnlySeamless && !isSeamlessSwitch) {
                 ALOGV("%s ignores %s to avoid non-seamless switch. Current mode = %s",
-                      formatLayerInfo(layer, weight).c_str(),
-                      scores[i].refreshRate->toString().c_str(),
-                      mCurrentRefreshRate->toString().c_str());
+                      formatLayerInfo(layer, weight).c_str(), to_string(*mode).c_str(),
+                      to_string(*mActiveModeIt->second).c_str());
                 continue;
             }
 
@@ -372,9 +424,8 @@
                 !layer.focused) {
                 ALOGV("%s ignores %s because it's not focused and the switch is going to be seamed."
                       " Current mode = %s",
-                      formatLayerInfo(layer, weight).c_str(),
-                      scores[i].refreshRate->toString().c_str(),
-                      mCurrentRefreshRate->toString().c_str());
+                      formatLayerInfo(layer, weight).c_str(), to_string(*mode).c_str(),
+                      to_string(*mActiveModeIt->second).c_str());
                 continue;
             }
 
@@ -383,17 +434,14 @@
             // mode group otherwise. In second case, if the current mode group is different
             // from the default, this means a layer with seamlessness=SeamedAndSeamless has just
             // disappeared.
-            const bool isInPolicyForDefault = scores[i].refreshRate->getModeGroup() == anchorGroup;
+            const bool isInPolicyForDefault = mode->getGroup() == anchorGroup;
             if (layer.seamlessness == Seamlessness::Default && !isInPolicyForDefault) {
                 ALOGV("%s ignores %s. Current mode = %s", formatLayerInfo(layer, weight).c_str(),
-                      scores[i].refreshRate->toString().c_str(),
-                      mCurrentRefreshRate->toString().c_str());
+                      to_string(*mode).c_str(), to_string(*mActiveModeIt->second).c_str());
                 continue;
             }
 
-            const bool inPrimaryRange =
-                    policy->primaryRange.includes(scores[i].refreshRate->getFps());
-
+            const bool inPrimaryRange = policy->primaryRange.includes(mode->getFps());
             if ((primaryRangeIsSingleRate || !inPrimaryRange) &&
                 !(layer.focused &&
                   (layer.vote == LayerVoteType::ExplicitDefault ||
@@ -404,30 +452,31 @@
             }
 
             const auto layerScore =
-                    calculateLayerScoreLocked(layer, *scores[i].refreshRate, isSeamlessSwitch);
+                    calculateLayerScoreLocked(layer, mode->getFps(), isSeamlessSwitch);
             ALOGV("%s gives %s score of %.4f", formatLayerInfo(layer, weight).c_str(),
-                  scores[i].refreshRate->getName().c_str(), layerScore);
-            scores[i].score += weight * layerScore;
+                  to_string(mode->getFps()).c_str(), layerScore);
+
+            score += weight * layerScore;
         }
     }
 
     // Now that we scored all the refresh rates we need to pick the one that got the highest score.
     // In case of a tie we will pick the higher refresh rate if any of the layers wanted Max,
     // or the lower otherwise.
-    const RefreshRate* bestRefreshRate = maxVoteLayers > 0
-            ? getBestRefreshRate(scores.rbegin(), scores.rend())
-            : getBestRefreshRate(scores.begin(), scores.end());
+    const DisplayModePtr& bestRefreshRate = maxVoteLayers > 0
+            ? getMaxScoreRefreshRate(scores.rbegin(), scores.rend())
+            : getMaxScoreRefreshRate(scores.begin(), scores.end());
 
     if (primaryRangeIsSingleRate) {
         // If we never scored any layers, then choose the rate from the primary
         // range instead of picking a random score from the app range.
         if (std::all_of(scores.begin(), scores.end(),
                         [](RefreshRateScore score) { return score.score == 0; })) {
-            const auto& refreshRate = getMaxRefreshRateByPolicyLocked(anchorGroup);
-            ALOGV("layers not scored - choose %s", refreshRate.getName().c_str());
-            return {refreshRate, kNoSignals};
+            const DisplayModePtr& max = getMaxRefreshRateByPolicyLocked(anchorGroup);
+            ALOGV("layers not scored - choose %s", to_string(max->getFps()).c_str());
+            return {max, kNoSignals};
         } else {
-            return {*bestRefreshRate, kNoSignals};
+            return {bestRefreshRate, kNoSignals};
         }
     }
 
@@ -435,7 +484,7 @@
     // interactive (as opposed to ExplicitExactOrMultiple) and therefore if those posted an explicit
     // vote we should not change it if we get a touch event. Only apply touch boost if it will
     // actually increase the refresh rate over the normal selection.
-    const RefreshRate& touchRefreshRate = getMaxRefreshRateByPolicyLocked(anchorGroup);
+    const DisplayModePtr& touchRefreshRate = getMaxRefreshRateByPolicyLocked(anchorGroup);
 
     const bool touchBoostForExplicitExact = [&] {
         if (mSupportsFrameRateOverrideByContent) {
@@ -450,12 +499,12 @@
     using fps_approx_ops::operator<;
 
     if (signals.touch && explicitDefaultVoteLayers == 0 && touchBoostForExplicitExact &&
-        bestRefreshRate->getFps() < touchRefreshRate.getFps()) {
-        ALOGV("TouchBoost - choose %s", touchRefreshRate.getName().c_str());
+        bestRefreshRate->getFps() < touchRefreshRate->getFps()) {
+        ALOGV("TouchBoost - choose %s", to_string(touchRefreshRate->getFps()).c_str());
         return {touchRefreshRate, GlobalSignals{.touch = true}};
     }
 
-    return {*bestRefreshRate, kNoSignals};
+    return {bestRefreshRate, kNoSignals};
 }
 
 std::unordered_map<uid_t, std::vector<const RefreshRateConfigs::LayerRequirement*>>
@@ -489,26 +538,28 @@
     return layersByUid;
 }
 
-std::vector<RefreshRateScore> initializeScoresForAllRefreshRates(
-        const AllRefreshRatesMapType& refreshRates) {
-    std::vector<RefreshRateScore> scores;
-    scores.reserve(refreshRates.size());
-    for (const auto& [ignored, refreshRate] : refreshRates) {
-        scores.emplace_back(RefreshRateScore{refreshRate.get(), 0.0f});
-    }
-    std::sort(scores.begin(), scores.end(),
-              [](const auto& a, const auto& b) { return *a.refreshRate < *b.refreshRate; });
-    return scores;
-}
-
 RefreshRateConfigs::UidToFrameRateOverride RefreshRateConfigs::getFrameRateOverrides(
-        const std::vector<LayerRequirement>& layers, Fps displayFrameRate,
+        const std::vector<LayerRequirement>& layers, Fps displayRefreshRate,
         GlobalSignals globalSignals) const {
     ATRACE_CALL();
 
-    ALOGV("getFrameRateOverrides %zu layers", layers.size());
+    ALOGV("%s: %zu layers", __func__, layers.size());
+
     std::lock_guard lock(mLock);
-    std::vector<RefreshRateScore> scores = initializeScoresForAllRefreshRates(mRefreshRates);
+
+    std::vector<RefreshRateScore> scores;
+    scores.reserve(mDisplayModes.size());
+
+    for (auto it = mDisplayModes.begin(); it != mDisplayModes.end(); ++it) {
+        scores.emplace_back(RefreshRateScore{it, 0.0f});
+    }
+
+    std::sort(scores.begin(), scores.end(), [](const auto& lhs, const auto& rhs) {
+        const auto& mode1 = lhs.modeIt->second;
+        const auto& mode2 = rhs.modeIt->second;
+        return isStrictlyLess(mode1->getFps(), mode2->getFps());
+    });
+
     std::unordered_map<uid_t, std::vector<const LayerRequirement*>> layersByUid =
             groupLayersByUid(layers);
     UidToFrameRateOverride frameRateOverrides;
@@ -524,8 +575,8 @@
             continue;
         }
 
-        for (auto& score : scores) {
-            score.score = 0;
+        for (auto& [_, score] : scores) {
+            score = 0;
         }
 
         for (const auto& layer : layersWithSameUid) {
@@ -536,137 +587,114 @@
             LOG_ALWAYS_FATAL_IF(layer->vote != LayerVoteType::ExplicitDefault &&
                                 layer->vote != LayerVoteType::ExplicitExactOrMultiple &&
                                 layer->vote != LayerVoteType::ExplicitExact);
-            for (RefreshRateScore& score : scores) {
-                const auto layerScore = calculateLayerScoreLocked(*layer, *score.refreshRate,
-                                                                  /*isSeamlessSwitch*/ true);
-                score.score += layer->weight * layerScore;
+            for (auto& [modeIt, score] : scores) {
+                constexpr bool isSeamlessSwitch = true;
+                const auto layerScore = calculateLayerScoreLocked(*layer, modeIt->second->getFps(),
+                                                                  isSeamlessSwitch);
+                score += layer->weight * layerScore;
             }
         }
 
         // We just care about the refresh rates which are a divisor of the
         // display refresh rate
-        auto iter =
-                std::remove_if(scores.begin(), scores.end(), [&](const RefreshRateScore& score) {
-                    return getFrameRateDivisor(displayFrameRate, score.refreshRate->getFps()) == 0;
-                });
-        scores.erase(iter, scores.end());
+        const auto it = std::remove_if(scores.begin(), scores.end(), [&](RefreshRateScore score) {
+            const auto& [id, mode] = *score.modeIt;
+            return getFrameRateDivisor(displayRefreshRate, mode->getFps()) == 0;
+        });
+        scores.erase(it, scores.end());
 
         // If we never scored any layers, we don't have a preferred frame rate
         if (std::all_of(scores.begin(), scores.end(),
-                        [](const RefreshRateScore& score) { return score.score == 0; })) {
+                        [](RefreshRateScore score) { return score.score == 0; })) {
             continue;
         }
 
         // Now that we scored all the refresh rates we need to pick the one that got the highest
         // score.
-        const RefreshRate* bestRefreshRate = getBestRefreshRate(scores.begin(), scores.end());
+        const DisplayModePtr& bestRefreshRate =
+                getMaxScoreRefreshRate(scores.begin(), scores.end());
+
         frameRateOverrides.emplace(uid, bestRefreshRate->getFps());
     }
 
     return frameRateOverrides;
 }
 
-template <typename Iter>
-const RefreshRate* RefreshRateConfigs::getBestRefreshRate(Iter begin, Iter end) const {
-    constexpr auto kEpsilon = 0.0001f;
-    const RefreshRate* bestRefreshRate = begin->refreshRate;
-    float max = begin->score;
-    for (auto i = begin; i != end; ++i) {
-        const auto [refreshRate, score] = *i;
-        ALOGV("%s scores %.2f", refreshRate->getName().c_str(), score);
-
-        ATRACE_INT(refreshRate->getName().c_str(), static_cast<int>(std::round(score * 100)));
-
-        if (score > max * (1 + kEpsilon)) {
-            max = score;
-            bestRefreshRate = refreshRate;
-        }
-    }
-
-    return bestRefreshRate;
-}
-
 std::optional<Fps> RefreshRateConfigs::onKernelTimerChanged(
-        std::optional<DisplayModeId> desiredActiveConfigId, bool timerExpired) const {
+        std::optional<DisplayModeId> desiredActiveModeId, bool timerExpired) const {
     std::lock_guard lock(mLock);
 
-    const auto& current = desiredActiveConfigId ? *mRefreshRates.at(*desiredActiveConfigId)
-                                                : *mCurrentRefreshRate;
-    const auto& min = *mMinSupportedRefreshRate;
+    const DisplayModePtr& current = desiredActiveModeId
+            ? mDisplayModes.get(*desiredActiveModeId)->get()
+            : mActiveModeIt->second;
 
-    if (current != min) {
-        const auto& refreshRate = timerExpired ? min : current;
-        return refreshRate.getFps();
+    const DisplayModePtr& min = mMinRefreshRateModeIt->second;
+    if (current == min) {
+        return {};
     }
 
-    return {};
+    const auto& mode = timerExpired ? min : current;
+    return mode->getFps();
 }
 
-const RefreshRate& RefreshRateConfigs::getMinRefreshRateByPolicyLocked() const {
-    for (auto refreshRate : mPrimaryRefreshRates) {
-        if (mCurrentRefreshRate->getModeGroup() == refreshRate->getModeGroup()) {
-            return *refreshRate;
+const DisplayModePtr& RefreshRateConfigs::getMinRefreshRateByPolicyLocked() const {
+    for (const DisplayModeIterator modeIt : mPrimaryRefreshRates) {
+        const auto& mode = modeIt->second;
+        if (mActiveModeIt->second->getGroup() == mode->getGroup()) {
+            return mode;
         }
     }
+
     ALOGE("Can't find min refresh rate by policy with the same mode group"
           " as the current mode %s",
-          mCurrentRefreshRate->toString().c_str());
-    // Defaulting to the lowest refresh rate
-    return *mPrimaryRefreshRates.front();
+          to_string(*mActiveModeIt->second).c_str());
+
+    // Default to the lowest refresh rate.
+    return mPrimaryRefreshRates.front()->second;
 }
 
-RefreshRate RefreshRateConfigs::getMaxRefreshRateByPolicy() const {
+DisplayModePtr RefreshRateConfigs::getMaxRefreshRateByPolicy() const {
     std::lock_guard lock(mLock);
     return getMaxRefreshRateByPolicyLocked();
 }
 
-const RefreshRate& RefreshRateConfigs::getMaxRefreshRateByPolicyLocked(int anchorGroup) const {
-    for (auto it = mPrimaryRefreshRates.rbegin(); it != mPrimaryRefreshRates.rend(); it++) {
-        const auto& refreshRate = (**it);
-        if (anchorGroup == refreshRate.getModeGroup()) {
-            return refreshRate;
+const DisplayModePtr& RefreshRateConfigs::getMaxRefreshRateByPolicyLocked(int anchorGroup) const {
+    for (auto it = mPrimaryRefreshRates.rbegin(); it != mPrimaryRefreshRates.rend(); ++it) {
+        const auto& mode = (*it)->second;
+        if (anchorGroup == mode->getGroup()) {
+            return mode;
         }
     }
+
     ALOGE("Can't find max refresh rate by policy with the same mode group"
           " as the current mode %s",
-          mCurrentRefreshRate->toString().c_str());
-    // Defaulting to the highest refresh rate
-    return *mPrimaryRefreshRates.back();
+          to_string(*mActiveModeIt->second).c_str());
+
+    // Default to the highest refresh rate.
+    return mPrimaryRefreshRates.back()->second;
 }
 
-RefreshRate RefreshRateConfigs::getCurrentRefreshRate() const {
+DisplayModePtr RefreshRateConfigs::getActiveMode() const {
     std::lock_guard lock(mLock);
-    return *mCurrentRefreshRate;
+    return mActiveModeIt->second;
 }
 
-RefreshRate RefreshRateConfigs::getCurrentRefreshRateByPolicy() const {
-    std::lock_guard lock(mLock);
-    return getCurrentRefreshRateByPolicyLocked();
-}
-
-const RefreshRate& RefreshRateConfigs::getCurrentRefreshRateByPolicyLocked() const {
-    if (std::find(mAppRequestRefreshRates.begin(), mAppRequestRefreshRates.end(),
-                  mCurrentRefreshRate) != mAppRequestRefreshRates.end()) {
-        return *mCurrentRefreshRate;
-    }
-    return *mRefreshRates.at(getCurrentPolicyLocked()->defaultMode);
-}
-
-void RefreshRateConfigs::setCurrentModeId(DisplayModeId modeId) {
+void RefreshRateConfigs::setActiveModeId(DisplayModeId modeId) {
     std::lock_guard lock(mLock);
 
     // Invalidate the cached invocation to getBestRefreshRate. This forces
     // the refresh rate to be recomputed on the next call to getBestRefreshRate.
     mGetBestRefreshRateCache.reset();
 
-    mCurrentRefreshRate = mRefreshRates.at(modeId).get();
+    mActiveModeIt = mDisplayModes.find(modeId);
+    LOG_ALWAYS_FATAL_IF(mActiveModeIt == mDisplayModes.end());
 }
 
-RefreshRateConfigs::RefreshRateConfigs(const DisplayModes& modes, DisplayModeId currentModeId,
+RefreshRateConfigs::RefreshRateConfigs(DisplayModes modes, DisplayModeId activeModeId,
                                        Config config)
       : mKnownFrameRates(constructKnownFrameRates(modes)), mConfig(config) {
     initializeIdleTimer();
-    updateDisplayModes(modes, currentModeId);
+    updateDisplayModes(std::move(modes), activeModeId);
 }
 
 void RefreshRateConfigs::initializeIdleTimer() {
@@ -688,64 +716,43 @@
     }
 }
 
-void RefreshRateConfigs::updateDisplayModes(const DisplayModes& modes,
-                                            DisplayModeId currentModeId) {
+void RefreshRateConfigs::updateDisplayModes(DisplayModes modes, DisplayModeId activeModeId) {
     std::lock_guard lock(mLock);
 
-    // The current mode should be supported
-    LOG_ALWAYS_FATAL_IF(std::none_of(modes.begin(), modes.end(), [&](DisplayModePtr mode) {
-        return mode->getId() == currentModeId;
-    }));
-
     // Invalidate the cached invocation to getBestRefreshRate. This forces
     // the refresh rate to be recomputed on the next call to getBestRefreshRate.
     mGetBestRefreshRateCache.reset();
 
-    mRefreshRates.clear();
-    for (const auto& mode : modes) {
-        const auto modeId = mode->getId();
-        mRefreshRates.emplace(modeId,
-                              std::make_unique<RefreshRate>(mode, RefreshRate::ConstructorTag(0)));
-        if (modeId == currentModeId) {
-            mCurrentRefreshRate = mRefreshRates.at(modeId).get();
-        }
-    }
+    mDisplayModes = std::move(modes);
+    mActiveModeIt = mDisplayModes.find(activeModeId);
+    LOG_ALWAYS_FATAL_IF(mActiveModeIt == mDisplayModes.end());
 
-    std::vector<const RefreshRate*> sortedModes;
-    getSortedRefreshRateListLocked([](const RefreshRate&) { return true; }, &sortedModes);
+    const auto sortedModes =
+            sortByRefreshRate(mDisplayModes, [](const DisplayMode&) { return true; });
+    mMinRefreshRateModeIt = sortedModes.front();
+    mMaxRefreshRateModeIt = sortedModes.back();
+
     // Reset the policy because the old one may no longer be valid.
     mDisplayManagerPolicy = {};
-    mDisplayManagerPolicy.defaultMode = currentModeId;
-    mMinSupportedRefreshRate = sortedModes.front();
-    mMaxSupportedRefreshRate = sortedModes.back();
+    mDisplayManagerPolicy.defaultMode = activeModeId;
 
-    mSupportsFrameRateOverrideByContent = false;
-    if (mConfig.enableFrameRateOverride) {
-        for (const auto& mode1 : sortedModes) {
-            for (const auto& mode2 : sortedModes) {
-                if (getFrameRateDivisor(mode1->getFps(), mode2->getFps()) >= 2) {
-                    mSupportsFrameRateOverrideByContent = true;
-                    break;
-                }
-            }
-        }
-    }
+    mSupportsFrameRateOverrideByContent =
+            mConfig.enableFrameRateOverride && canModesSupportFrameRateOverride(sortedModes);
 
     constructAvailableRefreshRates();
 }
 
 bool RefreshRateConfigs::isPolicyValidLocked(const Policy& policy) const {
     // defaultMode must be a valid mode, and within the given refresh rate range.
-    auto iter = mRefreshRates.find(policy.defaultMode);
-    if (iter == mRefreshRates.end()) {
+    if (const auto mode = mDisplayModes.get(policy.defaultMode)) {
+        if (!policy.primaryRange.includes(mode->get()->getFps())) {
+            ALOGE("Default mode is not in the primary range.");
+            return false;
+        }
+    } else {
         ALOGE("Default mode is not found.");
         return false;
     }
-    const RefreshRate& refreshRate = *iter->second;
-    if (!policy.primaryRange.includes(refreshRate.getFps())) {
-        ALOGE("Default mode is not in the primary range.");
-        return false;
-    }
 
     using namespace fps_approx_ops;
     return policy.appRequestRange.min <= policy.primaryRange.min &&
@@ -799,77 +806,46 @@
 
 bool RefreshRateConfigs::isModeAllowed(DisplayModeId modeId) const {
     std::lock_guard lock(mLock);
-    for (const RefreshRate* refreshRate : mAppRequestRefreshRates) {
-        if (refreshRate->getModeId() == modeId) {
-            return true;
-        }
-    }
-    return false;
-}
-
-void RefreshRateConfigs::getSortedRefreshRateListLocked(
-        const std::function<bool(const RefreshRate&)>& shouldAddRefreshRate,
-        std::vector<const RefreshRate*>* outRefreshRates) {
-    outRefreshRates->clear();
-    outRefreshRates->reserve(mRefreshRates.size());
-    for (const auto& [type, refreshRate] : mRefreshRates) {
-        if (shouldAddRefreshRate(*refreshRate)) {
-            ALOGV("getSortedRefreshRateListLocked: mode %d added to list policy",
-                  refreshRate->getModeId().value());
-            outRefreshRates->push_back(refreshRate.get());
-        }
-    }
-
-    std::sort(outRefreshRates->begin(), outRefreshRates->end(),
-              [](const auto refreshRate1, const auto refreshRate2) {
-                  if (refreshRate1->mode->getVsyncPeriod() !=
-                      refreshRate2->mode->getVsyncPeriod()) {
-                      return refreshRate1->mode->getVsyncPeriod() >
-                              refreshRate2->mode->getVsyncPeriod();
-                  } else {
-                      return refreshRate1->mode->getGroup() > refreshRate2->mode->getGroup();
-                  }
-              });
+    return std::any_of(mAppRequestRefreshRates.begin(), mAppRequestRefreshRates.end(),
+                       [modeId](DisplayModeIterator modeIt) {
+                           return modeIt->second->getId() == modeId;
+                       });
 }
 
 void RefreshRateConfigs::constructAvailableRefreshRates() {
-    // Filter modes based on current policy and sort based on vsync period
+    // Filter modes based on current policy and sort on refresh rate.
     const Policy* policy = getCurrentPolicyLocked();
-    const auto& defaultMode = mRefreshRates.at(policy->defaultMode)->mode;
-    ALOGV("constructAvailableRefreshRates: %s ", policy->toString().c_str());
+    ALOGV("%s: %s ", __func__, policy->toString().c_str());
 
-    auto filterRefreshRates =
-            [&](FpsRange range, const char* rangeName,
-                std::vector<const RefreshRate*>* outRefreshRates) REQUIRES(mLock) {
-                getSortedRefreshRateListLocked(
-                        [&](const RefreshRate& refreshRate) REQUIRES(mLock) {
-                            const auto& mode = refreshRate.mode;
+    const auto& defaultMode = mDisplayModes.get(policy->defaultMode)->get();
 
-                            return mode->getHeight() == defaultMode->getHeight() &&
-                                    mode->getWidth() == defaultMode->getWidth() &&
-                                    mode->getDpiX() == defaultMode->getDpiX() &&
-                                    mode->getDpiY() == defaultMode->getDpiY() &&
-                                    (policy->allowGroupSwitching ||
-                                     mode->getGroup() == defaultMode->getGroup()) &&
-                                    range.includes(mode->getFps());
-                        },
-                        outRefreshRates);
+    const auto filterRefreshRates = [&](FpsRange range, const char* rangeName) REQUIRES(mLock) {
+        const auto filter = [&](const DisplayMode& mode) {
+            return mode.getResolution() == defaultMode->getResolution() &&
+                    mode.getDpi() == defaultMode->getDpi() &&
+                    (policy->allowGroupSwitching || mode.getGroup() == defaultMode->getGroup()) &&
+                    range.includes(mode.getFps());
+        };
 
-                LOG_ALWAYS_FATAL_IF(outRefreshRates->empty(), "No matching modes for %s range %s",
-                                    rangeName, to_string(range).c_str());
+        const auto modes = sortByRefreshRate(mDisplayModes, filter);
+        LOG_ALWAYS_FATAL_IF(modes.empty(), "No matching modes for %s range %s", rangeName,
+                            to_string(range).c_str());
 
-                auto stringifyRefreshRates = [&]() -> std::string {
-                    std::string str;
-                    for (auto refreshRate : *outRefreshRates) {
-                        base::StringAppendF(&str, "%s ", refreshRate->getName().c_str());
-                    }
-                    return str;
-                };
-                ALOGV("%s refresh rates: %s", rangeName, stringifyRefreshRates().c_str());
-            };
+        const auto stringifyModes = [&] {
+            std::string str;
+            for (const auto modeIt : modes) {
+                str += to_string(modeIt->second->getFps());
+                str.push_back(' ');
+            }
+            return str;
+        };
+        ALOGV("%s refresh rates: %s", rangeName, stringifyModes().c_str());
 
-    filterRefreshRates(policy->primaryRange, "primary", &mPrimaryRefreshRates);
-    filterRefreshRates(policy->appRequestRange, "app request", &mAppRequestRefreshRates);
+        return modes;
+    };
+
+    mPrimaryRefreshRates = filterRefreshRates(policy->primaryRange, "primary");
+    mAppRequestRefreshRates = filterRefreshRates(policy->appRequestRange, "app request");
 }
 
 Fps RefreshRateConfigs::findClosestKnownFrameRate(Fps frameRate) const {
@@ -893,36 +869,39 @@
 
 RefreshRateConfigs::KernelIdleTimerAction RefreshRateConfigs::getIdleTimerAction() const {
     std::lock_guard lock(mLock);
-    const auto& deviceMin = *mMinSupportedRefreshRate;
-    const auto& minByPolicy = getMinRefreshRateByPolicyLocked();
-    const auto& maxByPolicy = getMaxRefreshRateByPolicyLocked();
-    const auto& currentPolicy = getCurrentPolicyLocked();
+
+    const Fps deviceMinFps = mMinRefreshRateModeIt->second->getFps();
+    const DisplayModePtr& minByPolicy = getMinRefreshRateByPolicyLocked();
 
     // Kernel idle timer will set the refresh rate to the device min. If DisplayManager says that
     // the min allowed refresh rate is higher than the device min, we do not want to enable the
     // timer.
-    if (deviceMin < minByPolicy) {
-        return RefreshRateConfigs::KernelIdleTimerAction::TurnOff;
+    if (isStrictlyLess(deviceMinFps, minByPolicy->getFps())) {
+        return KernelIdleTimerAction::TurnOff;
     }
+
+    const DisplayModePtr& maxByPolicy = getMaxRefreshRateByPolicyLocked();
     if (minByPolicy == maxByPolicy) {
-        // when min primary range in display manager policy is below device min turn on the timer.
-        if (isApproxLess(currentPolicy->primaryRange.min, deviceMin.getFps())) {
-            return RefreshRateConfigs::KernelIdleTimerAction::TurnOn;
+        // Turn on the timer when the min of the primary range is below the device min.
+        if (const Policy* currentPolicy = getCurrentPolicyLocked();
+            isApproxLess(currentPolicy->primaryRange.min, deviceMinFps)) {
+            return KernelIdleTimerAction::TurnOn;
         }
-        return RefreshRateConfigs::KernelIdleTimerAction::TurnOff;
+        return KernelIdleTimerAction::TurnOff;
     }
+
     // Turn on the timer in all other cases.
-    return RefreshRateConfigs::KernelIdleTimerAction::TurnOn;
+    return KernelIdleTimerAction::TurnOn;
 }
 
-int RefreshRateConfigs::getFrameRateDivisor(Fps displayFrameRate, Fps layerFrameRate) {
+int RefreshRateConfigs::getFrameRateDivisor(Fps displayRefreshRate, Fps layerFrameRate) {
     // This calculation needs to be in sync with the java code
     // in DisplayManagerService.getDisplayInfoForFrameRateOverride
 
     // The threshold must be smaller than 0.001 in order to differentiate
     // between the fractional pairs (e.g. 59.94 and 60).
     constexpr float kThreshold = 0.0009f;
-    const auto numPeriods = displayFrameRate.getValue() / layerFrameRate.getValue();
+    const auto numPeriods = displayRefreshRate.getValue() / layerFrameRate.getValue();
     const auto numPeriodsRounded = std::round(numPeriods);
     if (std::abs(numPeriods - numPeriodsRounded) > kThreshold) {
         return 0;
@@ -952,29 +931,32 @@
                             currentPolicy.toString().c_str());
     }
 
-    auto mode = mCurrentRefreshRate->mode;
-    base::StringAppendF(&result, "Current mode: %s\n", mCurrentRefreshRate->toString().c_str());
+    base::StringAppendF(&result, "Active mode: %s\n", to_string(*mActiveModeIt->second).c_str());
 
-    result.append("Refresh rates:\n");
-    for (const auto& [id, refreshRate] : mRefreshRates) {
-        mode = refreshRate->mode;
-        base::StringAppendF(&result, "\t%s\n", refreshRate->toString().c_str());
+    result.append("Display modes:\n");
+    for (const auto& [id, mode] : mDisplayModes) {
+        result.push_back('\t');
+        result.append(to_string(*mode));
+        result.push_back('\n');
     }
 
     base::StringAppendF(&result, "Supports Frame Rate Override By Content: %s\n",
                         mSupportsFrameRateOverrideByContent ? "yes" : "no");
-    base::StringAppendF(&result, "Idle timer: ");
-    if (mConfig.kernelIdleTimerController.has_value()) {
-        if (mConfig.kernelIdleTimerController == KernelIdleTimerController::Sysprop) {
-            base::StringAppendF(&result, "(kernel(sysprop))");
-        } else {
-            base::StringAppendF(&result, "(kernel(hwc))");
-        }
+
+    result.append("Idle timer: ");
+    if (const auto controller = mConfig.kernelIdleTimerController) {
+        base::StringAppendF(&result, "(kernel via %s) ", ftl::enum_string(*controller).c_str());
     } else {
-        base::StringAppendF(&result, "(platform)");
+        result.append("(platform) ");
     }
-    base::StringAppendF(&result, " %s\n", mIdleTimer ? mIdleTimer->dump().c_str() : "off");
-    result.append("\n");
+
+    if (mIdleTimer) {
+        result.append(mIdleTimer->dump());
+    } else {
+        result.append("off");
+    }
+
+    result.append("\n\n");
 }
 
 std::chrono::milliseconds RefreshRateConfigs::getIdleTimerTimeout() {
diff --git a/services/surfaceflinger/Scheduler/RefreshRateConfigs.h b/services/surfaceflinger/Scheduler/RefreshRateConfigs.h
index 30d3edd..05a8692 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateConfigs.h
+++ b/services/surfaceflinger/Scheduler/RefreshRateConfigs.h
@@ -56,50 +56,6 @@
     static constexpr nsecs_t MARGIN_FOR_PERIOD_CALCULATION =
             std::chrono::nanoseconds(800us).count();
 
-    class RefreshRate {
-    private:
-        // Effectively making the constructor private while allowing
-        // std::make_unique to create the object
-        struct ConstructorTag {
-            explicit ConstructorTag(int) {}
-        };
-
-    public:
-        RefreshRate(DisplayModePtr mode, ConstructorTag) : mode(mode) {}
-
-        DisplayModeId getModeId() const { return mode->getId(); }
-        nsecs_t getVsyncPeriod() const { return mode->getVsyncPeriod(); }
-        int32_t getModeGroup() const { return mode->getGroup(); }
-        std::string getName() const { return to_string(getFps()); }
-        Fps getFps() const { return mode->getFps(); }
-        DisplayModePtr getMode() const { return mode; }
-
-        // Checks whether the fps of this RefreshRate struct is within a given min and max refresh
-        // rate passed in. Margin of error is applied to the boundaries for approximation.
-        bool inPolicy(Fps minRefreshRate, Fps maxRefreshRate) const;
-
-        bool operator==(const RefreshRate& other) const { return mode == other.mode; }
-        bool operator!=(const RefreshRate& other) const { return !operator==(other); }
-
-        bool operator<(const RefreshRate& other) const {
-            return isStrictlyLess(getFps(), other.getFps());
-        }
-
-        std::string toString() const;
-        friend std::ostream& operator<<(std::ostream& os, const RefreshRate& refreshRate) {
-            return os << refreshRate.toString();
-        }
-
-    private:
-        friend RefreshRateConfigs;
-        friend class RefreshRateConfigsTest;
-
-        DisplayModePtr mode;
-    };
-
-    using AllRefreshRatesMapType =
-            std::unordered_map<DisplayModeId, std::unique_ptr<const RefreshRate>>;
-
     struct Policy {
     private:
         static constexpr int kAllowGroupSwitchingDefault = false;
@@ -236,12 +192,12 @@
 
     // Returns the refresh rate that best fits the given layers, and whether the refresh rate was
     // chosen based on touch boost and/or idle timer.
-    std::pair<RefreshRate, GlobalSignals> getBestRefreshRate(const std::vector<LayerRequirement>&,
-                                                             GlobalSignals) const EXCLUDES(mLock);
+    std::pair<DisplayModePtr, GlobalSignals> getBestRefreshRate(
+            const std::vector<LayerRequirement>&, GlobalSignals) const EXCLUDES(mLock);
 
     FpsRange getSupportedRefreshRateRange() const EXCLUDES(mLock) {
         std::lock_guard lock(mLock);
-        return {mMinSupportedRefreshRate->getFps(), mMaxSupportedRefreshRate->getFps()};
+        return {mMinRefreshRateModeIt->second->getFps(), mMaxRefreshRateModeIt->second->getFps()};
     }
 
     std::optional<Fps> onKernelTimerChanged(std::optional<DisplayModeId> desiredActiveModeId,
@@ -249,30 +205,15 @@
 
     // Returns the highest refresh rate according to the current policy. May change at runtime. Only
     // uses the primary range, not the app request range.
-    RefreshRate getMaxRefreshRateByPolicy() const EXCLUDES(mLock);
+    DisplayModePtr getMaxRefreshRateByPolicy() const EXCLUDES(mLock);
 
-    // Returns the current refresh rate
-    RefreshRate getCurrentRefreshRate() const EXCLUDES(mLock);
-
-    // Returns the current refresh rate, if allowed. Otherwise the default that is allowed by
-    // the policy.
-    RefreshRate getCurrentRefreshRateByPolicy() const;
-
-    // Returns the refresh rate that corresponds to a DisplayModeId. This may change at
-    // runtime.
-    // TODO(b/159590486) An invalid mode id may be given here if the dipslay modes have changed.
-    RefreshRate getRefreshRateFromModeId(DisplayModeId modeId) const EXCLUDES(mLock) {
-        std::lock_guard lock(mLock);
-        return *mRefreshRates.at(modeId);
-    };
-
-    // Stores the current modeId the device operates at
-    void setCurrentModeId(DisplayModeId) EXCLUDES(mLock);
+    void setActiveModeId(DisplayModeId) EXCLUDES(mLock);
+    DisplayModePtr getActiveMode() const EXCLUDES(mLock);
 
     // Returns a known frame rate that is the closest to frameRate
     Fps findClosestKnownFrameRate(Fps frameRate) const;
 
-    enum class KernelIdleTimerController { Sysprop, HwcApi };
+    enum class KernelIdleTimerController { Sysprop, HwcApi, ftl_last = HwcApi };
 
     // Configuration flags.
     struct Config {
@@ -291,7 +232,7 @@
         std::optional<KernelIdleTimerController> kernelIdleTimerController;
     };
 
-    RefreshRateConfigs(const DisplayModes&, DisplayModeId,
+    RefreshRateConfigs(DisplayModes, DisplayModeId activeModeId,
                        Config config = {.enableFrameRateOverride = false,
                                         .frameRateMultipleThreshold = 0,
                                         .idleTimerTimeout = 0ms,
@@ -305,7 +246,7 @@
     // differ in resolution.
     bool canSwitch() const EXCLUDES(mLock) {
         std::lock_guard lock(mLock);
-        return mRefreshRates.size() > 1;
+        return mDisplayModes.size() > 1;
     }
 
     // Class to enumerate options around toggling the kernel timer on and off.
@@ -323,7 +264,7 @@
     // Return the display refresh rate divisor to match the layer
     // frame rate, or 0 if the display refresh rate is not a multiple of the
     // layer refresh rate.
-    static int getFrameRateDivisor(Fps displayFrameRate, Fps layerFrameRate);
+    static int getFrameRateDivisor(Fps displayRefreshRate, Fps layerFrameRate);
 
     // Returns if the provided frame rates have a ratio t*1000/1001 or t*1001/1000
     // for an integer t.
@@ -391,54 +332,39 @@
 
     void constructAvailableRefreshRates() REQUIRES(mLock);
 
-    void getSortedRefreshRateListLocked(
-            const std::function<bool(const RefreshRate&)>& shouldAddRefreshRate,
-            std::vector<const RefreshRate*>* outRefreshRates) REQUIRES(mLock);
-
-    std::pair<RefreshRate, GlobalSignals> getBestRefreshRateLocked(
+    std::pair<DisplayModePtr, GlobalSignals> getBestRefreshRateLocked(
             const std::vector<LayerRequirement>&, GlobalSignals) const REQUIRES(mLock);
 
-    // Returns the refresh rate with the highest score in the collection specified from begin
-    // to end. If there are more than one with the same highest refresh rate, the first one is
-    // returned.
-    template <typename Iter>
-    const RefreshRate* getBestRefreshRate(Iter begin, Iter end) const;
-
     // Returns number of display frames and remainder when dividing the layer refresh period by
     // display refresh period.
     std::pair<nsecs_t, nsecs_t> getDisplayFrames(nsecs_t layerPeriod, nsecs_t displayPeriod) const;
 
     // Returns the lowest refresh rate according to the current policy. May change at runtime. Only
     // uses the primary range, not the app request range.
-    const RefreshRate& getMinRefreshRateByPolicyLocked() const REQUIRES(mLock);
+    const DisplayModePtr& getMinRefreshRateByPolicyLocked() const REQUIRES(mLock);
 
     // Returns the highest refresh rate according to the current policy. May change at runtime. Only
     // uses the primary range, not the app request range.
-    const RefreshRate& getMaxRefreshRateByPolicyLocked() const REQUIRES(mLock) {
-        return getMaxRefreshRateByPolicyLocked(mCurrentRefreshRate->getModeGroup());
+    const DisplayModePtr& getMaxRefreshRateByPolicyLocked(int anchorGroup) const REQUIRES(mLock);
+    const DisplayModePtr& getMaxRefreshRateByPolicyLocked() const REQUIRES(mLock) {
+        return getMaxRefreshRateByPolicyLocked(mActiveModeIt->second->getGroup());
     }
 
-    const RefreshRate& getMaxRefreshRateByPolicyLocked(int anchorGroup) const REQUIRES(mLock);
-
-    // Returns the current refresh rate, if allowed. Otherwise the default that is allowed by
-    // the policy.
-    const RefreshRate& getCurrentRefreshRateByPolicyLocked() const REQUIRES(mLock);
-
     const Policy* getCurrentPolicyLocked() const REQUIRES(mLock);
     bool isPolicyValidLocked(const Policy& policy) const REQUIRES(mLock);
 
     // Returns whether the layer is allowed to vote for the given refresh rate.
-    bool isVoteAllowed(const LayerRequirement&, const RefreshRate&) const;
+    bool isVoteAllowed(const LayerRequirement&, Fps) const;
 
     // calculates a score for a layer. Used to determine the display refresh rate
     // and the frame rate override for certains applications.
-    float calculateLayerScoreLocked(const LayerRequirement&, const RefreshRate&,
+    float calculateLayerScoreLocked(const LayerRequirement&, Fps refreshRate,
                                     bool isSeamlessSwitch) const REQUIRES(mLock);
 
-    float calculateNonExactMatchingLayerScoreLocked(const LayerRequirement&,
-                                                    const RefreshRate&) const REQUIRES(mLock);
+    float calculateNonExactMatchingLayerScoreLocked(const LayerRequirement&, Fps refreshRate) const
+            REQUIRES(mLock);
 
-    void updateDisplayModes(const DisplayModes& mode, DisplayModeId currentModeId) EXCLUDES(mLock);
+    void updateDisplayModes(DisplayModes, DisplayModeId activeModeId) EXCLUDES(mLock);
 
     void initializeIdleTimer();
 
@@ -449,32 +375,22 @@
                                                              : mIdleTimerCallbacks->platform;
     }
 
-    // The list of refresh rates, indexed by display modes ID. This may change after this
-    // object is initialized.
-    AllRefreshRatesMapType mRefreshRates GUARDED_BY(mLock);
+    // The display modes of the active display. The DisplayModeIterators below are pointers into
+    // this container, so must be invalidated whenever the DisplayModes change. The Policy below
+    // is also dependent, so must be reset as well.
+    DisplayModes mDisplayModes GUARDED_BY(mLock);
 
-    // The list of refresh rates in the primary range of the current policy, ordered by vsyncPeriod
-    // (the first element is the lowest refresh rate).
-    std::vector<const RefreshRate*> mPrimaryRefreshRates GUARDED_BY(mLock);
+    DisplayModeIterator mActiveModeIt GUARDED_BY(mLock);
+    DisplayModeIterator mMinRefreshRateModeIt GUARDED_BY(mLock);
+    DisplayModeIterator mMaxRefreshRateModeIt GUARDED_BY(mLock);
 
-    // The list of refresh rates in the app request range of the current policy, ordered by
-    // vsyncPeriod (the first element is the lowest refresh rate).
-    std::vector<const RefreshRate*> mAppRequestRefreshRates GUARDED_BY(mLock);
+    // Display modes that satisfy the Policy's ranges, filtered and sorted by refresh rate.
+    std::vector<DisplayModeIterator> mPrimaryRefreshRates GUARDED_BY(mLock);
+    std::vector<DisplayModeIterator> mAppRequestRefreshRates GUARDED_BY(mLock);
 
-    // The current display mode. This will change at runtime. This is set by SurfaceFlinger on
-    // the main thread, and read by the Scheduler (and other objects) on other threads.
-    const RefreshRate* mCurrentRefreshRate GUARDED_BY(mLock);
-
-    // The policy values will change at runtime. They're set by SurfaceFlinger on the main thread,
-    // and read by the Scheduler (and other objects) on other threads.
     Policy mDisplayManagerPolicy GUARDED_BY(mLock);
     std::optional<Policy> mOverridePolicy GUARDED_BY(mLock);
 
-    // The min and max refresh rates supported by the device.
-    // This may change at runtime.
-    const RefreshRate* mMinSupportedRefreshRate GUARDED_BY(mLock);
-    const RefreshRate* mMaxSupportedRefreshRate GUARDED_BY(mLock);
-
     mutable std::mutex mLock;
 
     // A sorted list of known frame rates that a Heuristic layer will choose
@@ -486,7 +402,7 @@
 
     struct GetBestRefreshRateCache {
         std::pair<std::vector<LayerRequirement>, GlobalSignals> arguments;
-        std::pair<RefreshRate, GlobalSignals> result;
+        std::pair<DisplayModePtr, GlobalSignals> result;
     };
     mutable std::optional<GetBestRefreshRateCache> mGetBestRefreshRateCache GUARDED_BY(mLock);
 
diff --git a/services/surfaceflinger/Scheduler/RefreshRateStats.h b/services/surfaceflinger/Scheduler/RefreshRateStats.h
index f1ad755..ed65bc6 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateStats.h
+++ b/services/surfaceflinger/Scheduler/RefreshRateStats.h
@@ -17,7 +17,9 @@
 #pragma once
 
 #include <chrono>
-#include <numeric>
+#include <cinttypes>
+#include <cstdlib>
+#include <string>
 
 #include <android-base/stringprintf.h>
 #include <ftl/small_map.h>
@@ -25,6 +27,7 @@
 
 #include <scheduler/Fps.h>
 
+#include "DisplayHardware/Hal.h"
 #include "TimeStats/TimeStats.h"
 
 namespace android::scheduler {
@@ -41,16 +44,16 @@
     static constexpr int64_t MS_PER_HOUR = 60 * MS_PER_MIN;
     static constexpr int64_t MS_PER_DAY = 24 * MS_PER_HOUR;
 
+    using PowerMode = android::hardware::graphics::composer::hal::PowerMode;
+
 public:
     // TODO(b/185535769): Inject clock to avoid sleeping in tests.
-    RefreshRateStats(TimeStats& timeStats, Fps currentRefreshRate,
-                     android::hardware::graphics::composer::hal::PowerMode currentPowerMode)
+    RefreshRateStats(TimeStats& timeStats, Fps currentRefreshRate, PowerMode currentPowerMode)
           : mTimeStats(timeStats),
             mCurrentRefreshRate(currentRefreshRate),
             mCurrentPowerMode(currentPowerMode) {}
 
-    // Sets power mode.
-    void setPowerMode(android::hardware::graphics::composer::hal::PowerMode mode) {
+    void setPowerMode(PowerMode mode) {
         if (mCurrentPowerMode == mode) {
             return;
         }
@@ -115,7 +118,7 @@
 
         uint32_t fps = 0;
 
-        if (mCurrentPowerMode == android::hardware::graphics::composer::hal::PowerMode::ON) {
+        if (mCurrentPowerMode == PowerMode::ON) {
             // Normal power mode is counted under different config modes.
             const auto total = std::as_const(mFpsTotalTimes)
                                        .get(mCurrentRefreshRate)
@@ -144,7 +147,7 @@
     TimeStats& mTimeStats;
 
     Fps mCurrentRefreshRate;
-    android::hardware::graphics::composer::hal::PowerMode mCurrentPowerMode;
+    PowerMode mCurrentPowerMode;
 
     ftl::SmallMap<Fps, std::chrono::milliseconds, 2, FpsApproxEqual> mFpsTotalTimes;
     std::chrono::milliseconds mScreenOffTime = std::chrono::milliseconds::zero();
diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp
index 1fa455a..74d7739 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.cpp
+++ b/services/surfaceflinger/Scheduler/Scheduler.cpp
@@ -166,18 +166,15 @@
 
 impl::EventThread::GetVsyncPeriodFunction Scheduler::makeGetVsyncPeriodFunction() const {
     return [this](uid_t uid) {
-        const auto refreshRateConfigs = holdRefreshRateConfigs();
-        nsecs_t basePeriod = refreshRateConfigs->getCurrentRefreshRate().getVsyncPeriod();
+        const Fps refreshRate = holdRefreshRateConfigs()->getActiveMode()->getFps();
+        const nsecs_t basePeriod = refreshRate.getPeriodNsecs();
+
         const auto frameRate = getFrameRateOverride(uid);
         if (!frameRate.has_value()) {
             return basePeriod;
         }
 
-        const auto divisor =
-                scheduler::RefreshRateConfigs::getFrameRateDivisor(refreshRateConfigs
-                                                                           ->getCurrentRefreshRate()
-                                                                           .getFps(),
-                                                                   *frameRate);
+        const auto divisor = RefreshRateConfigs::getFrameRateDivisor(refreshRate, *frameRate);
         if (divisor <= 1) {
             return basePeriod;
         }
@@ -307,7 +304,7 @@
     // mode change is in progress. In that case we shouldn't dispatch an event
     // as it will be dispatched when the current mode changes.
     if (std::scoped_lock lock(mRefreshRateConfigsLock);
-        mRefreshRateConfigs->getCurrentRefreshRate().getMode() != mPolicy.mode) {
+        mRefreshRateConfigs->getActiveMode() != mPolicy.mode) {
         return;
     }
 
@@ -422,7 +419,7 @@
     }
 }
 
-void Scheduler::resyncToHardwareVsync(bool makeAvailable, nsecs_t period) {
+void Scheduler::resyncToHardwareVsync(bool makeAvailable, Fps refreshRate) {
     {
         std::lock_guard<std::mutex> lock(mHWVsyncLock);
         if (makeAvailable) {
@@ -434,11 +431,7 @@
         }
     }
 
-    if (period <= 0) {
-        return;
-    }
-
-    setVsyncPeriod(period);
+    setVsyncPeriod(refreshRate.getPeriodNsecs());
 }
 
 void Scheduler::resync() {
@@ -448,15 +441,17 @@
     const nsecs_t last = mLastResyncTime.exchange(now);
 
     if (now - last > kIgnoreDelay) {
-        const auto vsyncPeriod = [&] {
+        const auto refreshRate = [&] {
             std::scoped_lock lock(mRefreshRateConfigsLock);
-            return mRefreshRateConfigs->getCurrentRefreshRate().getVsyncPeriod();
+            return mRefreshRateConfigs->getActiveMode()->getFps();
         }();
-        resyncToHardwareVsync(false, vsyncPeriod);
+        resyncToHardwareVsync(false, refreshRate);
     }
 }
 
 void Scheduler::setVsyncPeriod(nsecs_t period) {
+    if (period <= 0) return;
+
     std::lock_guard<std::mutex> lock(mHWVsyncLock);
     mVsyncSchedule->getController().startPeriodTransition(period);
 
@@ -578,21 +573,20 @@
 
     // TODO(145561154): cleanup the kernel idle timer implementation and the refresh rate
     // magic number
-    const auto refreshRate = [&] {
+    const Fps refreshRate = [&] {
         std::scoped_lock lock(mRefreshRateConfigsLock);
-        return mRefreshRateConfigs->getCurrentRefreshRate();
+        return mRefreshRateConfigs->getActiveMode()->getFps();
     }();
 
     constexpr Fps FPS_THRESHOLD_FOR_KERNEL_TIMER = 65_Hz;
     using namespace fps_approx_ops;
 
-    if (state == TimerState::Reset && refreshRate.getFps() > FPS_THRESHOLD_FOR_KERNEL_TIMER) {
+    if (state == TimerState::Reset && refreshRate > FPS_THRESHOLD_FOR_KERNEL_TIMER) {
         // If we're not in performance mode then the kernel timer shouldn't do
         // anything, as the refresh rate during DPU power collapse will be the
         // same.
-        resyncToHardwareVsync(true /* makeAvailable */, refreshRate.getVsyncPeriod());
-    } else if (state == TimerState::Expired &&
-               refreshRate.getFps() <= FPS_THRESHOLD_FOR_KERNEL_TIMER) {
+        resyncToHardwareVsync(true /* makeAvailable */, refreshRate);
+    } else if (state == TimerState::Expired && refreshRate <= FPS_THRESHOLD_FOR_KERNEL_TIMER) {
         // Disable HW VSYNC if the timer expired, as we don't need it enabled if
         // we're not pushing frames, and if we're in PERFORMANCE mode then we'll
         // need to update the VsyncController model anyway.
@@ -693,11 +687,9 @@
         }
     }
     if (refreshRateChanged) {
-        const auto newRefreshRate = refreshRateConfigs->getRefreshRateFromModeId(newMode->getId());
-
-        mSchedulerCallback.changeRefreshRate(newRefreshRate,
-                                             consideredSignals.idle ? DisplayModeEvent::None
-                                                                    : DisplayModeEvent::Changed);
+        mSchedulerCallback.requestDisplayMode(std::move(newMode),
+                                              consideredSignals.idle ? DisplayModeEvent::None
+                                                                     : DisplayModeEvent::Changed);
     }
     if (frameRateOverridesChanged) {
         mSchedulerCallback.triggerOnFrameRateOverridesChanged();
@@ -715,16 +707,13 @@
     if (mDisplayPowerTimer &&
         (!mPolicy.isDisplayPowerStateNormal || mPolicy.displayPowerTimer == TimerState::Reset)) {
         constexpr GlobalSignals kNoSignals;
-        return {configs->getMaxRefreshRateByPolicy().getMode(), kNoSignals};
+        return {configs->getMaxRefreshRateByPolicy(), kNoSignals};
     }
 
     const GlobalSignals signals{.touch = mTouchTimer && mPolicy.touch == TouchState::Active,
                                 .idle = mPolicy.idleTimer == TimerState::Expired};
 
-    const auto [refreshRate, consideredSignals] =
-            configs->getBestRefreshRate(mPolicy.contentRequirements, signals);
-
-    return {refreshRate.getMode(), consideredSignals};
+    return configs->getBestRefreshRate(mPolicy.contentRequirements, signals);
 }
 
 DisplayModePtr Scheduler::getPreferredDisplayMode() {
diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h
index f6c81c0..a8113d4 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.h
+++ b/services/surfaceflinger/Scheduler/Scheduler.h
@@ -86,12 +86,11 @@
     // Indicates frame activity, i.e. whether commit and/or composite is taking place.
     enum class FrameHint { kNone, kActive };
 
-    using RefreshRate = RefreshRateConfigs::RefreshRate;
     using DisplayModeEvent = scheduler::DisplayModeEvent;
 
     virtual void scheduleComposite(FrameHint) = 0;
     virtual void setVsyncEnabled(bool) = 0;
-    virtual void changeRefreshRate(const RefreshRate&, DisplayModeEvent) = 0;
+    virtual void requestDisplayMode(DisplayModePtr, DisplayModeEvent) = 0;
     virtual void kernelTimerChanged(bool expired) = 0;
     virtual void triggerOnFrameRateOverridesChanged() = 0;
 
@@ -166,8 +165,7 @@
     // If makeAvailable is true, then hardware vsync will be turned on.
     // Otherwise, if hardware vsync is not already enabled then this method will
     // no-op.
-    // The period is the vsync period from the current display configuration.
-    void resyncToHardwareVsync(bool makeAvailable, nsecs_t period);
+    void resyncToHardwareVsync(bool makeAvailable, Fps refreshRate);
     void resync() EXCLUDES(mRefreshRateConfigsLock);
     void forceNextResync() { mLastResyncTime = 0; }
 
@@ -236,7 +234,7 @@
 
     nsecs_t getVsyncPeriodFromRefreshRateConfigs() const EXCLUDES(mRefreshRateConfigsLock) {
         std::scoped_lock lock(mRefreshRateConfigsLock);
-        return mRefreshRateConfigs->getCurrentRefreshRate().getVsyncPeriod();
+        return mRefreshRateConfigs->getActiveMode()->getFps().getPeriodNsecs();
     }
 
     // Returns the framerate of the layer with the given sequence ID
diff --git a/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.h b/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.h
index 3186d6d..4923031 100644
--- a/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.h
+++ b/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.h
@@ -29,6 +29,7 @@
 
 namespace android::scheduler {
 
+class TimeKeeper;
 class VSyncTracker;
 
 // VSyncDispatchTimerQueueEntry is a helper class representing internal state for each entry in